Lexical: Added table toolbar, organised button code

This commit is contained in:
Dan Brown 2024-07-21 15:11:24 +01:00
parent 63f4b42453
commit b618287585
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
19 changed files with 756 additions and 547 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21 19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14c1.1 0 2 .9 2 2zm-2 0V5h-4v2.2h-2V5h-2v2.2H9V5H5v14h4v-2.1h2V19h2v-2.1h2V19Z"/><path d="M14.829 10.585 13.415 12l1.414 1.414c.943.943-.472 2.357-1.414 1.414L12 13.414l-1.414 1.414c-.944.944-2.358-.47-1.414-1.414L10.586 12l-1.414-1.415c-.943-.942.471-2.357 1.414-1.414L12 10.585l1.344-1.343c1.111-1.112 2.2.627 1.485 1.343z" style="fill-rule:nonzero"/></svg>

After

Width:  |  Height:  |  Size: 481 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 21a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14c0 1.1-.9 2-2 2zm0-2h14v-4h-2.2v-2H19v-2h-2.2V9H19V5H5v4h2.1v2H5v2h2.1v2H5Z"/><path d="M13.415 14.829 12 13.415l-1.414 1.414c-.943.943-2.357-.472-1.414-1.414L10.586 12l-1.414-1.414c-.944-.944.47-2.358 1.414-1.414L12 10.586l1.415-1.414c.942-.943 2.357.471 1.414 1.414L13.415 12l1.343 1.344c1.112 1.111-.627 2.2-1.343 1.485z" style="fill-rule:nonzero"/></svg>

After

Width:  |  Height:  |  Size: 481 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 21a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14c0 1.1-.9 2-2 2zm0-2h14V5H5v14z"/><path d="m13.711 15.423-1.71-1.712-1.712 1.712c-1.14 1.14-2.852-.57-1.71-1.712l1.71-1.71-1.71-1.712c-1.143-1.142.568-2.853 1.71-1.71L12 10.288l1.711-1.71c1.141-1.142 2.852.57 1.712 1.71L13.71 12l1.626 1.626c1.345 1.345-.76 2.663-1.626 1.797z" style="fill-rule:nonzero;stroke-width:1.20992"/></svg>

After

Width:  |  Height:  |  Size: 455 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16 5h-5v14h5c1.235 0 1.234 2 0 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11c1.229 0 1.236 2 0 2zm-7 6V5H5v6zm0 8v-6H5v6zm11.076-6h-2v2c0 1.333-2 1.333-2 0v-2h-2c-1.335 0-1.335-2 0-2h2V9c0-1.333 2-1.333 2 0v2h1.9c1.572 0 1.113 2 .1 2z"/></svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8 19h5V5H8C6.764 5 6.766 3 8 3h11a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H8c-1.229 0-1.236-2 0-2zm7-6v6h4v-6zm0-8v6h4V5ZM3.924 11h2V9c0-1.333 2-1.333 2 0v2h2c1.335 0 1.335 2 0 2h-2v2c0 1.333-2 1.333-2 0v-2h-1.9c-1.572 0-1.113-2-.1-2z"/></svg>

After

Width:  |  Height:  |  Size: 303 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 8v5h14V8c0-1.235 2-1.234 2 0v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8C3 6.77 5 6.764 5 8zm6 7H5v4h6zm8 0h-6v4h6zM13 3.924v2h2c1.333 0 1.333 2 0 2h-2v2c0 1.335-2 1.335-2 0v-2H9c-1.333 0-1.333-2 0-2h2v-1.9c0-1.572 2-1.113 2-.1z"/></svg>

After

Width:  |  Height:  |  Size: 300 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 16v-5H5v5c0 1.235-2 1.234-2 0V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v11c0 1.229-2 1.236-2 0zm-6-7h6V5h-6zM5 9h6V5H5Zm6 11.076v-2H9c-1.333 0-1.333-2 0-2h2v-2c0-1.335 2-1.335 2 0v2h2c1.333 0 1.333 2 0 2h-2v1.9c0 1.572-2 1.113-2 .1z"/></svg>

After

Width:  |  Height:  |  Size: 305 B

View File

@ -44,10 +44,19 @@ export function $getNodeFromSelection(selection: BaseSelection|null, matcher: Le
return node;
}
for (const parent of node.getParents()) {
if (matcher(parent)) {
return parent;
}
const matchedParent = $getParentOfType(node, matcher);
if (matchedParent) {
return matchedParent;
}
}
return null;
}
export function $getParentOfType(node: LexicalNode, matcher: LexicalNodeMatcher): LexicalNode|null {
for (const parent of node.getParents()) {
if (matcher(parent)) {
return parent;
}
}

View File

@ -2,12 +2,22 @@
## In progress
- Add Type: Video/media/embed
- TinyMce media embed supported:
- iframe
- embed
- object
- video - Can take sources
- audio - Can take sources
- Pretty much all attributes look like they were supported.
- Core old logic seen here: https://github.com/tinymce/tinymce/blob/main/modules/tinymce/src/plugins/media/main/ts/core/DataToHtml.ts
- Copy/store attributes on node based on allow list?
- width, height, src, controls, etc... Take valid values from MDN
## Main Todo
- Alignments: Use existing classes for blocks
- Alignments: Handle inline block content (image, video)
- Add Type: Video/media/embed
- Table features
- Image paste upload
- Keyboard shortcuts support

View File

@ -1,528 +0,0 @@
import {EditorBasicButtonDefinition, EditorButton, EditorButtonDefinition} from "../framework/buttons";
import {
$createNodeSelection,
$createParagraphNode,
$createTextNode,
$getRoot,
$getSelection,
$isParagraphNode,
$isTextNode,
$setSelection,
BaseSelection,
CAN_REDO_COMMAND,
CAN_UNDO_COMMAND,
COMMAND_PRIORITY_LOW,
ElementFormatType,
ElementNode,
FORMAT_TEXT_COMMAND,
LexicalNode,
REDO_COMMAND,
TextFormatType,
UNDO_COMMAND
} from "lexical";
import {
$getBlockElementNodesInSelection,
$getNodeFromSelection, $insertNewBlockNodeAtSelection, $selectionContainsElementFormat,
$selectionContainsNodeType,
$selectionContainsTextFormat,
$toggleSelectionBlockNodeType
} from "../../helpers";
import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../../nodes/callout";
import {
$createHeadingNode,
$createQuoteNode,
$isHeadingNode,
$isQuoteNode,
HeadingNode,
HeadingTagType
} from "@lexical/rich-text";
import {$isLinkNode, LinkNode} from "@lexical/link";
import {EditorUiContext} from "../framework/core";
import {$isImageNode, ImageNode} from "../../nodes/image";
import {$createDetailsNode, $isDetailsNode} from "../../nodes/details";
import {getEditorContentAsHtml} from "../../actions";
import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list";
import undoIcon from "@icons/editor/undo.svg";
import redoIcon from "@icons/editor/redo.svg";
import boldIcon from "@icons/editor/bold.svg";
import italicIcon from "@icons/editor/italic.svg";
import underlinedIcon from "@icons/editor/underlined.svg";
import textColorIcon from "@icons/editor/text-color.svg";
import highlightIcon from "@icons/editor/highlighter.svg";
import strikethroughIcon from "@icons/editor/strikethrough.svg";
import superscriptIcon from "@icons/editor/superscript.svg";
import subscriptIcon from "@icons/editor/subscript.svg";
import codeIcon from "@icons/editor/code.svg";
import formatClearIcon from "@icons/editor/format-clear.svg";
import alignLeftIcon from "@icons/editor/align-left.svg";
import alignCenterIcon from "@icons/editor/align-center.svg";
import alignRightIcon from "@icons/editor/align-right.svg";
import alignJustifyIcon from "@icons/editor/align-justify.svg";
import listBulletIcon from "@icons/editor/list-bullet.svg";
import listNumberedIcon from "@icons/editor/list-numbered.svg";
import listCheckIcon from "@icons/editor/list-check.svg";
import linkIcon from "@icons/editor/link.svg";
import unlinkIcon from "@icons/editor/unlink.svg";
import tableIcon from "@icons/editor/table.svg";
import imageIcon from "@icons/editor/image.svg";
import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
import codeBlockIcon from "@icons/editor/code-block.svg";
import diagramIcon from "@icons/editor/diagram.svg";
import detailsIcon from "@icons/editor/details.svg";
import sourceIcon from "@icons/editor/source-view.svg";
import fullscreenIcon from "@icons/editor/fullscreen.svg";
import editIcon from "@icons/edit.svg";
import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../nodes/horizontal-rule";
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
import {$createDiagramNode, $isDiagramNode, $openDrawingEditorForNode, DiagramNode} from "../../nodes/diagram";
export const undo: EditorButtonDefinition = {
label: 'Undo',
icon: undoIcon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(UNDO_COMMAND, undefined);
},
isActive(selection: BaseSelection|null): boolean {
return false;
},
setup(context: EditorUiContext, button: EditorButton) {
button.toggleDisabled(true);
context.editor.registerCommand(CAN_UNDO_COMMAND, (payload: boolean): boolean => {
button.toggleDisabled(!payload)
return false;
}, COMMAND_PRIORITY_LOW);
}
}
export const redo: EditorButtonDefinition = {
label: 'Redo',
icon: redoIcon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(REDO_COMMAND, undefined);
},
isActive(selection: BaseSelection|null): boolean {
return false;
},
setup(context: EditorUiContext, button: EditorButton) {
button.toggleDisabled(true);
context.editor.registerCommand(CAN_REDO_COMMAND, (payload: boolean): boolean => {
button.toggleDisabled(!payload)
return false;
}, COMMAND_PRIORITY_LOW);
}
}
function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
return {
label: `${name} Callout`,
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType(
(node) => $isCalloutNodeOfCategory(node, category),
() => $createCalloutNode(category),
)
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, (node) => $isCalloutNodeOfCategory(node, category));
}
};
}
export const infoCallout: EditorButtonDefinition = buildCalloutButton('info', 'Info');
export const dangerCallout: EditorButtonDefinition = buildCalloutButton('danger', 'Danger');
export const warningCallout: EditorButtonDefinition = buildCalloutButton('warning', 'Warning');
export const successCallout: EditorButtonDefinition = buildCalloutButton('success', 'Success');
const isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
return $isHeadingNode(node) && (node as HeadingNode).getTag() === tag;
};
function buildHeaderButton(tag: HeadingTagType, name: string): EditorButtonDefinition {
return {
label: name,
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType(
(node) => isHeaderNodeOfTag(node, tag),
() => $createHeadingNode(tag),
)
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, (node) => isHeaderNodeOfTag(node, tag));
}
};
}
export const h2: EditorButtonDefinition = buildHeaderButton('h2', 'Large Header');
export const h3: EditorButtonDefinition = buildHeaderButton('h3', 'Medium Header');
export const h4: EditorButtonDefinition = buildHeaderButton('h4', 'Small Header');
export const h5: EditorButtonDefinition = buildHeaderButton('h5', 'Tiny Header');
export const blockquote: EditorButtonDefinition = {
label: 'Blockquote',
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isQuoteNode);
}
};
export const paragraph: EditorButtonDefinition = {
label: 'Paragraph',
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType($isParagraphNode, $createParagraphNode);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isParagraphNode);
}
}
function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition {
return {
label: label,
icon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsTextFormat(selection, format);
}
};
}
export const bold: EditorButtonDefinition = buildFormatButton('Bold', 'bold', boldIcon);
export const italic: EditorButtonDefinition = buildFormatButton('Italic', 'italic', italicIcon);
export const underline: EditorButtonDefinition = buildFormatButton('Underline', 'underline', underlinedIcon);
export const textColor: EditorBasicButtonDefinition = {label: 'Text color', icon: textColorIcon};
export const highlightColor: EditorBasicButtonDefinition = {label: 'Highlight color', icon: highlightIcon};
export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough', strikethroughIcon);
export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon);
export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon);
export const code: EditorButtonDefinition = buildFormatButton('Inline Code', 'code', codeIcon);
export const clearFormating: EditorButtonDefinition = {
label: 'Clear formatting',
icon: formatClearIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = $getSelection();
for (const node of selection?.getNodes() || []) {
if ($isTextNode(node)) {
node.setFormat(0);
node.setStyle('');
}
}
});
},
isActive() {
return false;
}
};
function setAlignmentForSection(alignment: ElementFormatType): void {
const selection = $getSelection();
const elements = $getBlockElementNodesInSelection(selection);
for (const node of elements) {
node.setFormat(alignment);
}
}
export const alignLeft: EditorButtonDefinition = {
label: 'Align left',
icon: alignLeftIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('left'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'left');
}
};
export const alignCenter: EditorButtonDefinition = {
label: 'Align center',
icon: alignCenterIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('center'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'center');
}
};
export const alignRight: EditorButtonDefinition = {
label: 'Align right',
icon: alignRightIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('right'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'right');
}
};
export const alignJustify: EditorButtonDefinition = {
label: 'Align justify',
icon: alignJustifyIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('justify'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'justify');
}
};
function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
return {
label,
icon,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const selection = $getSelection();
if (this.isActive(selection, context)) {
removeList(context.editor);
} else {
insertList(context.editor, type);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => {
return $isListNode(node) && (node as ListNode).getListType() === type;
});
}
};
}
export const bulletList: EditorButtonDefinition = buildListButton('Bullet list', 'bullet', listBulletIcon);
export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number', listNumberedIcon);
export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check', listCheckIcon);
export const link: EditorButtonDefinition = {
label: 'Insert/edit link',
icon: linkIcon,
action(context: EditorUiContext) {
const linkModal = context.manager.createModal('link');
context.editor.getEditorState().read(() => {
const selection = $getSelection();
const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
let formDefaults = {};
if (selectedLink) {
formDefaults = {
url: selectedLink.getURL(),
text: selectedLink.getTextContent(),
title: selectedLink.getTitle(),
target: selectedLink.getTarget(),
}
context.editor.update(() => {
const selection = $createNodeSelection();
selection.add(selectedLink.getKey());
$setSelection(selection);
});
}
linkModal.show(formDefaults);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isLinkNode);
}
};
export const unlink: EditorButtonDefinition = {
label: 'Remove link',
icon: unlinkIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = context.lastSelection;
const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
const selectionPoints = selection?.getStartEndPoints();
if (selectedLink) {
const newNode = $createTextNode(selectedLink.getTextContent());
selectedLink.replace(newNode);
if (selectionPoints?.length === 2) {
newNode.select(selectionPoints[0].offset, selectionPoints[1].offset);
} else {
newNode.select();
}
}
});
},
isActive(selection: BaseSelection|null): boolean {
return false;
}
};
export const table: EditorBasicButtonDefinition = {
label: 'Table',
icon: tableIcon,
};
export const image: EditorButtonDefinition = {
label: 'Insert/Edit Image',
icon: imageIcon,
action(context: EditorUiContext) {
const imageModal = context.manager.createModal('image');
const selection = context.lastSelection;
const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
context.editor.getEditorState().read(() => {
let formDefaults = {};
if (selectedImage) {
formDefaults = {
src: selectedImage.getSrc(),
alt: selectedImage.getAltText(),
height: selectedImage.getHeight(),
width: selectedImage.getWidth(),
}
context.editor.update(() => {
const selection = $createNodeSelection();
selection.add(selectedImage.getKey());
$setSelection(selection);
});
}
imageModal.show(formDefaults);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isImageNode);
}
};
export const horizontalRule: EditorButtonDefinition = {
label: 'Insert horizontal line',
icon: horizontalRuleIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$insertNewBlockNodeAtSelection($createHorizontalRuleNode(), false);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isHorizontalRuleNode);
}
};
export const codeBlock: EditorButtonDefinition = {
label: 'Insert code block',
icon: codeBlockIcon,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const selection = $getSelection();
const codeBlock = $getNodeFromSelection(context.lastSelection, $isCodeBlockNode) as (CodeBlockNode|null);
if (codeBlock === null) {
context.editor.update(() => {
const codeBlock = $createCodeBlockNode();
codeBlock.setCode(selection?.getTextContent() || '');
$insertNewBlockNodeAtSelection(codeBlock, true);
$openCodeEditorForNode(context.editor, codeBlock);
codeBlock.selectStart();
});
} else {
$openCodeEditorForNode(context.editor, codeBlock);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isCodeBlockNode);
}
};
export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
label: 'Edit code block',
icon: editIcon,
});
export const diagram: EditorButtonDefinition = {
label: 'Insert/edit drawing',
icon: diagramIcon,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const selection = $getSelection();
const diagramNode = $getNodeFromSelection(context.lastSelection, $isDiagramNode) as (DiagramNode|null);
if (diagramNode === null) {
context.editor.update(() => {
const diagram = $createDiagramNode();
$insertNewBlockNodeAtSelection(diagram, true);
$openDrawingEditorForNode(context, diagram);
diagram.selectStart();
});
} else {
$openDrawingEditorForNode(context, diagramNode);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isDiagramNode);
}
};
export const details: EditorButtonDefinition = {
label: 'Insert collapsible block',
icon: detailsIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = $getSelection();
const detailsNode = $createDetailsNode();
const selectionNodes = selection?.getNodes() || [];
const topLevels = selectionNodes.map(n => n.getTopLevelElement())
.filter(n => n !== null) as ElementNode[];
const uniqueTopLevels = [...new Set(topLevels)];
if (uniqueTopLevels.length > 0) {
uniqueTopLevels[0].insertAfter(detailsNode);
} else {
$getRoot().append(detailsNode);
}
for (const node of uniqueTopLevels) {
detailsNode.append(node);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isDetailsNode);
}
}
export const source: EditorButtonDefinition = {
label: 'Source code',
icon: sourceIcon,
async action(context: EditorUiContext) {
const modal = context.manager.createModal('source');
const source = await getEditorContentAsHtml(context.editor);
modal.show({source});
},
isActive() {
return false;
}
};
export const fullscreen: EditorButtonDefinition = {
label: 'Fullscreen',
icon: fullscreenIcon,
async action(context: EditorUiContext, button: EditorButton) {
const isFullScreen = context.containerDOM.classList.contains('fullscreen');
context.containerDOM.classList.toggle('fullscreen', !isFullScreen);
(context.containerDOM.closest('body') as HTMLElement).classList.toggle('editor-is-fullscreen', !isFullScreen);
button.setActiveState(!isFullScreen);
},
isActive(selection, context: EditorUiContext) {
return context.containerDOM.classList.contains('fullscreen');
}
};

View File

@ -0,0 +1,61 @@
import {$getSelection, BaseSelection, ElementFormatType} from "lexical";
import {$getBlockElementNodesInSelection, $selectionContainsElementFormat} from "../../../helpers";
import {EditorButtonDefinition} from "../../framework/buttons";
import alignLeftIcon from "@icons/editor/align-left.svg";
import {EditorUiContext} from "../../framework/core";
import alignCenterIcon from "@icons/editor/align-center.svg";
import alignRightIcon from "@icons/editor/align-right.svg";
import alignJustifyIcon from "@icons/editor/align-justify.svg";
function setAlignmentForSection(alignment: ElementFormatType): void {
const selection = $getSelection();
const elements = $getBlockElementNodesInSelection(selection);
for (const node of elements) {
node.setFormat(alignment);
}
}
export const alignLeft: EditorButtonDefinition = {
label: 'Align left',
icon: alignLeftIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('left'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'left');
}
};
export const alignCenter: EditorButtonDefinition = {
label: 'Align center',
icon: alignCenterIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('center'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'center');
}
};
export const alignRight: EditorButtonDefinition = {
label: 'Align right',
icon: alignRightIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('right'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'right');
}
};
export const alignJustify: EditorButtonDefinition = {
label: 'Align justify',
icon: alignJustifyIcon,
action(context: EditorUiContext) {
context.editor.update(() => setAlignmentForSection('justify'));
},
isActive(selection: BaseSelection|null) {
return $selectionContainsElementFormat(selection, 'justify');
}
};

View File

@ -0,0 +1,85 @@
import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../../../nodes/callout";
import {EditorButtonDefinition} from "../../framework/buttons";
import {EditorUiContext} from "../../framework/core";
import {$selectionContainsNodeType, $toggleSelectionBlockNodeType} from "../../../helpers";
import {$createParagraphNode, $isParagraphNode, BaseSelection, LexicalNode} from "lexical";
import {
$createHeadingNode,
$createQuoteNode,
$isHeadingNode,
$isQuoteNode,
HeadingNode,
HeadingTagType
} from "@lexical/rich-text";
function buildCalloutButton(category: CalloutCategory, name: string): EditorButtonDefinition {
return {
label: `${name} Callout`,
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType(
(node) => $isCalloutNodeOfCategory(node, category),
() => $createCalloutNode(category),
)
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, (node) => $isCalloutNodeOfCategory(node, category));
}
};
}
export const infoCallout: EditorButtonDefinition = buildCalloutButton('info', 'Info');
export const dangerCallout: EditorButtonDefinition = buildCalloutButton('danger', 'Danger');
export const warningCallout: EditorButtonDefinition = buildCalloutButton('warning', 'Warning');
export const successCallout: EditorButtonDefinition = buildCalloutButton('success', 'Success');
const isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
return $isHeadingNode(node) && (node as HeadingNode).getTag() === tag;
};
function buildHeaderButton(tag: HeadingTagType, name: string): EditorButtonDefinition {
return {
label: name,
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType(
(node) => isHeaderNodeOfTag(node, tag),
() => $createHeadingNode(tag),
)
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, (node) => isHeaderNodeOfTag(node, tag));
}
};
}
export const h2: EditorButtonDefinition = buildHeaderButton('h2', 'Large Header');
export const h3: EditorButtonDefinition = buildHeaderButton('h3', 'Medium Header');
export const h4: EditorButtonDefinition = buildHeaderButton('h4', 'Small Header');
export const h5: EditorButtonDefinition = buildHeaderButton('h5', 'Tiny Header');
export const blockquote: EditorButtonDefinition = {
label: 'Blockquote',
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isQuoteNode);
}
};
export const paragraph: EditorButtonDefinition = {
label: 'Paragraph',
action(context: EditorUiContext) {
context.editor.update(() => {
$toggleSelectionBlockNodeType($isParagraphNode, $createParagraphNode);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isParagraphNode);
}
}

View File

@ -0,0 +1,81 @@
import {EditorButton, EditorButtonDefinition} from "../../framework/buttons";
import undoIcon from "@icons/editor/undo.svg";
import {EditorUiContext} from "../../framework/core";
import {
BaseSelection,
CAN_REDO_COMMAND,
CAN_UNDO_COMMAND,
COMMAND_PRIORITY_LOW,
REDO_COMMAND,
UNDO_COMMAND
} from "lexical";
import redoIcon from "@icons/editor/redo.svg";
import sourceIcon from "@icons/editor/source-view.svg";
import {getEditorContentAsHtml} from "../../../actions";
import fullscreenIcon from "@icons/editor/fullscreen.svg";
export const undo: EditorButtonDefinition = {
label: 'Undo',
icon: undoIcon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(UNDO_COMMAND, undefined);
},
isActive(selection: BaseSelection|null): boolean {
return false;
},
setup(context: EditorUiContext, button: EditorButton) {
button.toggleDisabled(true);
context.editor.registerCommand(CAN_UNDO_COMMAND, (payload: boolean): boolean => {
button.toggleDisabled(!payload)
return false;
}, COMMAND_PRIORITY_LOW);
}
}
export const redo: EditorButtonDefinition = {
label: 'Redo',
icon: redoIcon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(REDO_COMMAND, undefined);
},
isActive(selection: BaseSelection|null): boolean {
return false;
},
setup(context: EditorUiContext, button: EditorButton) {
button.toggleDisabled(true);
context.editor.registerCommand(CAN_REDO_COMMAND, (payload: boolean): boolean => {
button.toggleDisabled(!payload)
return false;
}, COMMAND_PRIORITY_LOW);
}
}
export const source: EditorButtonDefinition = {
label: 'Source code',
icon: sourceIcon,
async action(context: EditorUiContext) {
const modal = context.manager.createModal('source');
const source = await getEditorContentAsHtml(context.editor);
modal.show({source});
},
isActive() {
return false;
}
};
export const fullscreen: EditorButtonDefinition = {
label: 'Fullscreen',
icon: fullscreenIcon,
async action(context: EditorUiContext, button: EditorButton) {
const isFullScreen = context.containerDOM.classList.contains('fullscreen');
context.containerDOM.classList.toggle('fullscreen', !isFullScreen);
(context.containerDOM.closest('body') as HTMLElement).classList.toggle('editor-is-fullscreen', !isFullScreen);
button.setActiveState(!isFullScreen);
},
isActive(selection, context: EditorUiContext) {
return context.containerDOM.classList.contains('fullscreen');
}
};

View File

@ -0,0 +1,56 @@
import {$getSelection, $isTextNode, BaseSelection, FORMAT_TEXT_COMMAND, TextFormatType} from "lexical";
import {EditorBasicButtonDefinition, EditorButtonDefinition} from "../../framework/buttons";
import {EditorUiContext} from "../../framework/core";
import {$selectionContainsTextFormat} from "../../../helpers";
import boldIcon from "@icons/editor/bold.svg";
import italicIcon from "@icons/editor/italic.svg";
import underlinedIcon from "@icons/editor/underlined.svg";
import textColorIcon from "@icons/editor/text-color.svg";
import highlightIcon from "@icons/editor/highlighter.svg";
import strikethroughIcon from "@icons/editor/strikethrough.svg";
import superscriptIcon from "@icons/editor/superscript.svg";
import subscriptIcon from "@icons/editor/subscript.svg";
import codeIcon from "@icons/editor/code.svg";
import formatClearIcon from "@icons/editor/format-clear.svg";
function buildFormatButton(label: string, format: TextFormatType, icon: string): EditorButtonDefinition {
return {
label: label,
icon,
action(context: EditorUiContext) {
context.editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsTextFormat(selection, format);
}
};
}
export const bold: EditorButtonDefinition = buildFormatButton('Bold', 'bold', boldIcon);
export const italic: EditorButtonDefinition = buildFormatButton('Italic', 'italic', italicIcon);
export const underline: EditorButtonDefinition = buildFormatButton('Underline', 'underline', underlinedIcon);
export const textColor: EditorBasicButtonDefinition = {label: 'Text color', icon: textColorIcon};
export const highlightColor: EditorBasicButtonDefinition = {label: 'Highlight color', icon: highlightIcon};
export const strikethrough: EditorButtonDefinition = buildFormatButton('Strikethrough', 'strikethrough', strikethroughIcon);
export const superscript: EditorButtonDefinition = buildFormatButton('Superscript', 'superscript', superscriptIcon);
export const subscript: EditorButtonDefinition = buildFormatButton('Subscript', 'subscript', subscriptIcon);
export const code: EditorButtonDefinition = buildFormatButton('Inline Code', 'code', codeIcon);
export const clearFormating: EditorButtonDefinition = {
label: 'Clear formatting',
icon: formatClearIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = $getSelection();
for (const node of selection?.getNodes() || []) {
if ($isTextNode(node)) {
node.setFormat(0);
node.setStyle('');
}
}
});
},
isActive() {
return false;
}
};

View File

@ -0,0 +1,35 @@
import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list";
import {EditorButtonDefinition} from "../../framework/buttons";
import {EditorUiContext} from "../../framework/core";
import {$getSelection, BaseSelection, LexicalNode} from "lexical";
import {$selectionContainsNodeType} from "../../../helpers";
import listBulletIcon from "@icons/editor/list-bullet.svg";
import listNumberedIcon from "@icons/editor/list-numbered.svg";
import listCheckIcon from "@icons/editor/list-check.svg";
function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
return {
label,
icon,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const selection = $getSelection();
if (this.isActive(selection, context)) {
removeList(context.editor);
} else {
insertList(context.editor, type);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => {
return $isListNode(node) && (node as ListNode).getListType() === type;
});
}
};
}
export const bulletList: EditorButtonDefinition = buildListButton('Bullet list', 'bullet', listBulletIcon);
export const numberList: EditorButtonDefinition = buildListButton('Numbered list', 'number', listNumberedIcon);
export const taskList: EditorButtonDefinition = buildListButton('Task list', 'check', listCheckIcon);

View File

@ -0,0 +1,215 @@
import {EditorButtonDefinition} from "../../framework/buttons";
import linkIcon from "@icons/editor/link.svg";
import {EditorUiContext} from "../../framework/core";
import {
$createNodeSelection,
$createTextNode,
$getRoot,
$getSelection,
$setSelection,
BaseSelection,
ElementNode
} from "lexical";
import {$getNodeFromSelection, $insertNewBlockNodeAtSelection, $selectionContainsNodeType} from "../../../helpers";
import {$isLinkNode, LinkNode} from "@lexical/link";
import unlinkIcon from "@icons/editor/unlink.svg";
import imageIcon from "@icons/editor/image.svg";
import {$isImageNode, ImageNode} from "../../../nodes/image";
import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../../nodes/horizontal-rule";
import codeBlockIcon from "@icons/editor/code-block.svg";
import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../../nodes/code-block";
import editIcon from "@icons/edit.svg";
import diagramIcon from "@icons/editor/diagram.svg";
import {$createDiagramNode, $isDiagramNode, $openDrawingEditorForNode, DiagramNode} from "../../../nodes/diagram";
import detailsIcon from "@icons/editor/details.svg";
import {$createDetailsNode, $isDetailsNode} from "../../../nodes/details";
export const link: EditorButtonDefinition = {
label: 'Insert/edit link',
icon: linkIcon,
action(context: EditorUiContext) {
const linkModal = context.manager.createModal('link');
context.editor.getEditorState().read(() => {
const selection = $getSelection();
const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
let formDefaults = {};
if (selectedLink) {
formDefaults = {
url: selectedLink.getURL(),
text: selectedLink.getTextContent(),
title: selectedLink.getTitle(),
target: selectedLink.getTarget(),
}
context.editor.update(() => {
const selection = $createNodeSelection();
selection.add(selectedLink.getKey());
$setSelection(selection);
});
}
linkModal.show(formDefaults);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isLinkNode);
}
};
export const unlink: EditorButtonDefinition = {
label: 'Remove link',
icon: unlinkIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = context.lastSelection;
const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
const selectionPoints = selection?.getStartEndPoints();
if (selectedLink) {
const newNode = $createTextNode(selectedLink.getTextContent());
selectedLink.replace(newNode);
if (selectionPoints?.length === 2) {
newNode.select(selectionPoints[0].offset, selectionPoints[1].offset);
} else {
newNode.select();
}
}
});
},
isActive(selection: BaseSelection|null): boolean {
return false;
}
};
export const image: EditorButtonDefinition = {
label: 'Insert/Edit Image',
icon: imageIcon,
action(context: EditorUiContext) {
const imageModal = context.manager.createModal('image');
const selection = context.lastSelection;
const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
context.editor.getEditorState().read(() => {
let formDefaults = {};
if (selectedImage) {
formDefaults = {
src: selectedImage.getSrc(),
alt: selectedImage.getAltText(),
height: selectedImage.getHeight(),
width: selectedImage.getWidth(),
}
context.editor.update(() => {
const selection = $createNodeSelection();
selection.add(selectedImage.getKey());
$setSelection(selection);
});
}
imageModal.show(formDefaults);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isImageNode);
}
};
export const horizontalRule: EditorButtonDefinition = {
label: 'Insert horizontal line',
icon: horizontalRuleIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$insertNewBlockNodeAtSelection($createHorizontalRuleNode(), false);
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isHorizontalRuleNode);
}
};
export const codeBlock: EditorButtonDefinition = {
label: 'Insert code block',
icon: codeBlockIcon,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const selection = $getSelection();
const codeBlock = $getNodeFromSelection(context.lastSelection, $isCodeBlockNode) as (CodeBlockNode|null);
if (codeBlock === null) {
context.editor.update(() => {
const codeBlock = $createCodeBlockNode();
codeBlock.setCode(selection?.getTextContent() || '');
$insertNewBlockNodeAtSelection(codeBlock, true);
$openCodeEditorForNode(context.editor, codeBlock);
codeBlock.selectStart();
});
} else {
$openCodeEditorForNode(context.editor, codeBlock);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isCodeBlockNode);
}
};
export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
label: 'Edit code block',
icon: editIcon,
});
export const diagram: EditorButtonDefinition = {
label: 'Insert/edit drawing',
icon: diagramIcon,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const selection = $getSelection();
const diagramNode = $getNodeFromSelection(context.lastSelection, $isDiagramNode) as (DiagramNode|null);
if (diagramNode === null) {
context.editor.update(() => {
const diagram = $createDiagramNode();
$insertNewBlockNodeAtSelection(diagram, true);
$openDrawingEditorForNode(context, diagram);
diagram.selectStart();
});
} else {
$openDrawingEditorForNode(context, diagramNode);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isDiagramNode);
}
};
export const details: EditorButtonDefinition = {
label: 'Insert collapsible block',
icon: detailsIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = $getSelection();
const detailsNode = $createDetailsNode();
const selectionNodes = selection?.getNodes() || [];
const topLevels = selectionNodes.map(n => n.getTopLevelElement())
.filter(n => n !== null) as ElementNode[];
const uniqueTopLevels = [...new Set(topLevels)];
if (uniqueTopLevels.length > 0) {
uniqueTopLevels[0].insertAfter(detailsNode);
} else {
$getRoot().append(detailsNode);
}
for (const node of uniqueTopLevels) {
detailsNode.append(node);
}
});
},
isActive(selection: BaseSelection|null): boolean {
return $selectionContainsNodeType(selection, $isDetailsNode);
}
}

View File

@ -0,0 +1,122 @@
import {EditorBasicButtonDefinition, EditorButtonDefinition} from "../../framework/buttons";
import tableIcon from "@icons/editor/table.svg";
import deleteIcon from "@icons/editor/table-delete.svg";
import deleteColumnIcon from "@icons/editor/table-delete-column.svg";
import deleteRowIcon from "@icons/editor/table-delete-row.svg";
import insertColumnAfterIcon from "@icons/editor/table-insert-column-after.svg";
import insertColumnBeforeIcon from "@icons/editor/table-insert-column-before.svg";
import insertRowAboveIcon from "@icons/editor/table-insert-row-above.svg";
import insertRowBelowIcon from "@icons/editor/table-insert-row-below.svg";
import {EditorUiContext} from "../../framework/core";
import {$getBlockElementNodesInSelection, $getNodeFromSelection, $getParentOfType} from "../../../helpers";
import {$getSelection} from "lexical";
import {$isCustomTableNode, CustomTableNode} from "../../../nodes/custom-table";
import {
$deleteTableColumn, $deleteTableColumn__EXPERIMENTAL,
$deleteTableRow__EXPERIMENTAL,
$getTableRowIndexFromTableCellNode, $insertTableColumn, $insertTableColumn__EXPERIMENTAL,
$insertTableRow, $insertTableRow__EXPERIMENTAL,
$isTableCellNode,
$isTableRowNode,
TableCellNode
} from "@lexical/table";
export const table: EditorBasicButtonDefinition = {
label: 'Table',
icon: tableIcon,
};
export const deleteTable: EditorButtonDefinition = {
label: 'Delete table',
icon: deleteIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
const table = $getNodeFromSelection($getSelection(), $isCustomTableNode);
if (table) {
table.remove();
}
});
},
isActive() {
return false;
}
};
export const insertRowAbove: EditorButtonDefinition = {
label: 'Insert row above',
icon: insertRowAboveIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$insertTableRow__EXPERIMENTAL(false);
});
},
isActive() {
return false;
}
};
export const insertRowBelow: EditorButtonDefinition = {
label: 'Insert row below',
icon: insertRowBelowIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$insertTableRow__EXPERIMENTAL(true);
});
},
isActive() {
return false;
}
};
export const deleteRow: EditorButtonDefinition = {
label: 'Delete row',
icon: deleteRowIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$deleteTableRow__EXPERIMENTAL();
});
},
isActive() {
return false;
}
};
export const insertColumnBefore: EditorButtonDefinition = {
label: 'Insert column before',
icon: insertColumnBeforeIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$insertTableColumn__EXPERIMENTAL(false);
});
},
isActive() {
return false;
}
};
export const insertColumnAfter: EditorButtonDefinition = {
label: 'Insert column after',
icon: insertColumnAfterIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$insertTableColumn__EXPERIMENTAL(true);
});
},
isActive() {
return false;
}
};
export const deleteColumn: EditorButtonDefinition = {
label: 'Delete column',
icon: deleteColumnIcon,
action(context: EditorUiContext) {
context.editor.update(() => {
$deleteTableColumn__EXPERIMENTAL();
});
},
isActive() {
return false;
}
};

View File

@ -3,7 +3,7 @@ import {
getCodeToolbarContent,
getImageToolbarContent,
getLinkToolbarContent,
getMainEditorFullToolbar
getMainEditorFullToolbar, getTableToolbarContent
} from "./toolbars";
import {EditorUIManager} from "./framework/manager";
import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions";
@ -61,6 +61,14 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, scro
content: getCodeToolbarContent(),
});
manager.registerContextToolbar('table', {
selector: 'td,th',
content: getTableToolbarContent(),
displayTargetLocator(originalTarget: HTMLElement): HTMLElement {
return originalTarget.closest('table') as HTMLTableElement;
}
});
// Register image decorator listener
manager.registerDecoratorType('image', ImageDecorator);
manager.registerDecoratorType('code', CodeBlockDecorator);

View File

@ -1,17 +1,4 @@
import {EditorButton} from "./framework/buttons";
import {
alignCenter, alignJustify,
alignLeft,
alignRight,
blockquote, bold, bulletList, clearFormating, code, codeBlock,
dangerCallout, details, diagram, editCodeBlock, fullscreen,
h2, h3, h4, h5, highlightColor, horizontalRule, image,
infoCallout, italic, link, numberList, paragraph,
redo, source, strikethrough, subscript,
successCallout, superscript, table, taskList, textColor, underline,
undo, unlink,
warningCallout
} from "./defaults/button-definitions";
import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiElement} from "./framework/core";
import {el} from "../helpers";
import {EditorFormatMenu} from "./framework/blocks/format-menu";
@ -21,6 +8,48 @@ import {EditorColorPicker} from "./framework/blocks/color-picker";
import {EditorTableCreator} from "./framework/blocks/table-creator";
import {EditorColorButton} from "./framework/blocks/color-button";
import {EditorOverflowContainer} from "./framework/blocks/overflow-container";
import {
deleteColumn,
deleteRow,
deleteTable, insertColumnAfter,
insertColumnBefore,
insertRowAbove,
insertRowBelow,
table
} from "./defaults/buttons/tables";
import {fullscreen, redo, source, undo} from "./defaults/buttons/controls";
import {
blockquote, dangerCallout,
h2,
h3,
h4,
h5,
infoCallout,
paragraph,
successCallout,
warningCallout
} from "./defaults/buttons/block-formats";
import {
bold, clearFormating, code,
highlightColor,
italic,
strikethrough, subscript,
superscript,
textColor,
underline
} from "./defaults/buttons/inline-formats";
import {alignCenter, alignJustify, alignLeft, alignRight} from "./defaults/buttons/alignments";
import {bulletList, numberList, taskList} from "./defaults/buttons/lists";
import {
codeBlock,
details,
diagram,
editCodeBlock,
horizontalRule,
image,
link,
unlink
} from "./defaults/buttons/objects";
export function getMainEditorFullToolbar(): EditorContainerUiElement {
return new EditorSimpleClassContainer('editor-toolbar-main', [
@ -129,4 +158,23 @@ export function getCodeToolbarContent(): EditorUiElement[] {
return [
new EditorButton(editCodeBlock),
];
}
export function getTableToolbarContent(): EditorUiElement[] {
return [
new EditorOverflowContainer(2, [
// Todo - Table properties
new EditorButton(deleteTable),
]),
new EditorOverflowContainer(3, [
new EditorButton(insertRowAbove),
new EditorButton(insertRowBelow),
new EditorButton(deleteRow),
]),
new EditorOverflowContainer(3, [
new EditorButton(insertColumnBefore),
new EditorButton(insertColumnAfter),
new EditorButton(deleteColumn),
]),
];
}