Lexical: Added table toolbar, organised button code
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -44,12 +44,21 @@ export function $getNodeFromSelection(selection: BaseSelection|null, matcher: Le
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()) {
|
for (const parent of node.getParents()) {
|
||||||
if (matcher(parent)) {
|
if (matcher(parent)) {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,22 @@
|
||||||
|
|
||||||
## In progress
|
## 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
|
## Main Todo
|
||||||
|
|
||||||
- Alignments: Use existing classes for blocks
|
- Alignments: Use existing classes for blocks
|
||||||
- Alignments: Handle inline block content (image, video)
|
- Alignments: Handle inline block content (image, video)
|
||||||
- Add Type: Video/media/embed
|
|
||||||
- Table features
|
- Table features
|
||||||
- Image paste upload
|
- Image paste upload
|
||||||
- Keyboard shortcuts support
|
- Keyboard shortcuts support
|
||||||
|
|
|
@ -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');
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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');
|
||||||
|
}
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -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);
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,7 +3,7 @@ import {
|
||||||
getCodeToolbarContent,
|
getCodeToolbarContent,
|
||||||
getImageToolbarContent,
|
getImageToolbarContent,
|
||||||
getLinkToolbarContent,
|
getLinkToolbarContent,
|
||||||
getMainEditorFullToolbar
|
getMainEditorFullToolbar, getTableToolbarContent
|
||||||
} from "./toolbars";
|
} from "./toolbars";
|
||||||
import {EditorUIManager} from "./framework/manager";
|
import {EditorUIManager} from "./framework/manager";
|
||||||
import {image as imageFormDefinition, link as linkFormDefinition, source as sourceFormDefinition} from "./defaults/form-definitions";
|
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(),
|
content: getCodeToolbarContent(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
manager.registerContextToolbar('table', {
|
||||||
|
selector: 'td,th',
|
||||||
|
content: getTableToolbarContent(),
|
||||||
|
displayTargetLocator(originalTarget: HTMLElement): HTMLElement {
|
||||||
|
return originalTarget.closest('table') as HTMLTableElement;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Register image decorator listener
|
// Register image decorator listener
|
||||||
manager.registerDecoratorType('image', ImageDecorator);
|
manager.registerDecoratorType('image', ImageDecorator);
|
||||||
manager.registerDecoratorType('code', CodeBlockDecorator);
|
manager.registerDecoratorType('code', CodeBlockDecorator);
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
import {EditorButton} from "./framework/buttons";
|
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 {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiElement} from "./framework/core";
|
||||||
import {el} from "../helpers";
|
import {el} from "../helpers";
|
||||||
import {EditorFormatMenu} from "./framework/blocks/format-menu";
|
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 {EditorTableCreator} from "./framework/blocks/table-creator";
|
||||||
import {EditorColorButton} from "./framework/blocks/color-button";
|
import {EditorColorButton} from "./framework/blocks/color-button";
|
||||||
import {EditorOverflowContainer} from "./framework/blocks/overflow-container";
|
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 {
|
export function getMainEditorFullToolbar(): EditorContainerUiElement {
|
||||||
return new EditorSimpleClassContainer('editor-toolbar-main', [
|
return new EditorSimpleClassContainer('editor-toolbar-main', [
|
||||||
|
@ -130,3 +159,22 @@ export function getCodeToolbarContent(): EditorUiElement[] {
|
||||||
new EditorButton(editCodeBlock),
|
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),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|