--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+<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>
\ No newline at end of file
--- /dev/null
+<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>
\ No newline at end of file
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;
}
}
## 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
+++ /dev/null
-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');
- }
-};
\ No newline at end of file
--- /dev/null
+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');
+ }
+};
--- /dev/null
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+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');
+ }
+};
\ No newline at end of file
--- /dev/null
+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;
+ }
+};
\ No newline at end of file
--- /dev/null
+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);
--- /dev/null
+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);
+ }
+}
\ No newline at end of file
--- /dev/null
+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;
+ }
+};
\ No newline at end of file
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";
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);
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";
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', [
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),
+ ]),
+ ];
}
\ No newline at end of file