import {EditorBasicButtonDefinition, EditorButton, EditorButtonDefinition} from "../framework/buttons";
import {
$createNodeSelection,
- $createParagraphNode, $getRoot, $getSelection,
- $isParagraphNode, $isTextNode, $setSelection,
- BaseSelection, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, ElementNode, FORMAT_TEXT_COMMAND,
+ $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,
+ REDO_COMMAND,
+ TextFormatType,
UNDO_COMMAND
} from "lexical";
import {
- getNodeFromSelection,
- selectionContainsNodeType,
- selectionContainsTextFormat,
- toggleSelectionBlockNodeType
+ $getBlockElementNodesInSelection,
+ $getNodeFromSelection, $insertNewBlockNodeAtSelection, $selectionContainsElementFormat,
+ $selectionContainsNodeType,
+ $selectionContainsTextFormat,
+ $toggleSelectionBlockNodeType
} from "../../helpers";
import {$createCalloutNode, $isCalloutNodeOfCategory, CalloutCategory} from "../../nodes/callout";
import {
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 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 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 imageIcon from "@icons/editor/image.svg"
-import detailsIcon from "@icons/editor/details.svg"
-import sourceIcon from "@icons/editor/source-view.svg"
+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 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";
export const undo: EditorButtonDefinition = {
label: 'Undo',
return {
label: `${name} Callout`,
action(context: EditorUiContext) {
- toggleSelectionBlockNodeType(
- context.editor,
- (node) => $isCalloutNodeOfCategory(node, category),
- () => $createCalloutNode(category),
- )
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType(
+ (node) => $isCalloutNodeOfCategory(node, category),
+ () => $createCalloutNode(category),
+ )
+ });
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsNodeType(selection, (node) => $isCalloutNodeOfCategory(node, category));
+ return $selectionContainsNodeType(selection, (node) => $isCalloutNodeOfCategory(node, category));
}
};
}
return {
label: name,
action(context: EditorUiContext) {
- toggleSelectionBlockNodeType(
- context.editor,
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType(
(node) => isHeaderNodeOfTag(node, tag),
() => $createHeadingNode(tag),
- )
+ )
+ });
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsNodeType(selection, (node) => isHeaderNodeOfTag(node, tag));
+ return $selectionContainsNodeType(selection, (node) => isHeaderNodeOfTag(node, tag));
}
};
}
export const blockquote: EditorButtonDefinition = {
label: 'Blockquote',
action(context: EditorUiContext) {
- toggleSelectionBlockNodeType(context.editor, $isQuoteNode, $createQuoteNode);
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType($isQuoteNode, $createQuoteNode);
+ });
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsNodeType(selection, $isQuoteNode);
+ return $selectionContainsNodeType(selection, $isQuoteNode);
}
};
export const paragraph: EditorButtonDefinition = {
label: 'Paragraph',
action(context: EditorUiContext) {
- toggleSelectionBlockNodeType(context.editor, $isParagraphNode, $createParagraphNode);
+ context.editor.update(() => {
+ $toggleSelectionBlockNodeType($isParagraphNode, $createParagraphNode);
+ });
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsNodeType(selection, $isParagraphNode);
+ return $selectionContainsNodeType(selection, $isParagraphNode);
}
}
context.editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsTextFormat(selection, format);
+ 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'};
-export const highlightColor: EditorBasicButtonDefinition = {label: 'Highlight color'};
+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);
for (const node of selection?.getNodes() || []) {
if ($isTextNode(node)) {
node.setFormat(0);
+ node.setStyle('');
}
}
});
}
};
+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,
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const selection = $getSelection();
- if (this.isActive(selection)) {
+ 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 $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => {
return $isListNode(node) && (node as ListNode).getListType() === type;
});
}
const linkModal = context.manager.createModal('link');
context.editor.getEditorState().read(() => {
const selection = $getSelection();
- const selectedLink = getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
+ const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
let formDefaults = {};
if (selectedLink) {
});
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsNodeType(selection, $isLinkNode);
+ 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 = {
action(context: EditorUiContext) {
const imageModal = context.manager.createModal('image');
const selection = context.lastSelection;
- const selectedImage = getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
+ const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
context.editor.getEditorState().read(() => {
let formDefaults = {};
});
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsNodeType(selection, $isImageNode);
+ 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 details: EditorButtonDefinition = {
label: 'Insert collapsible block',
icon: detailsIcon,
});
},
isActive(selection: BaseSelection|null): boolean {
- return selectionContainsNodeType(selection, $isDetailsNode);
+ return $selectionContainsNodeType(selection, $isDetailsNode);
}
}
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