1 import {EditorButtonDefinition} from "../../framework/buttons";
2 import linkIcon from "@icons/editor/link.svg";
3 import {EditorUiContext} from "../../framework/core";
6 $getSelection, $insertNodes,
10 import {$isLinkNode, LinkNode} from "@lexical/link";
11 import unlinkIcon from "@icons/editor/unlink.svg";
12 import imageIcon from "@icons/editor/image.svg";
13 import {$isImageNode, ImageNode} from "@lexical/rich-text/LexicalImageNode";
14 import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
15 import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "@lexical/rich-text/LexicalHorizontalRuleNode";
16 import codeBlockIcon from "@icons/editor/code-block.svg";
17 import {$isCodeBlockNode} from "@lexical/rich-text/LexicalCodeBlockNode";
18 import editIcon from "@icons/edit.svg";
19 import diagramIcon from "@icons/editor/diagram.svg";
20 import {$createDiagramNode, DiagramNode} from "@lexical/rich-text/LexicalDiagramNode";
21 import detailsIcon from "@icons/editor/details.svg";
22 import mediaIcon from "@icons/editor/media.svg";
23 import {$createDetailsNode, $isDetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
24 import {$isMediaNode, MediaNode} from "@lexical/rich-text/LexicalMediaNode";
26 $getNodeFromSelection,
27 $insertNewBlockNodeAtSelection,
28 $selectionContainsNodeType, getLastSelection
29 } from "../../../utils/selection";
30 import {$isDiagramNode, $openDrawingEditorForNode, showDiagramManagerForInsert} from "../../../utils/diagrams";
31 import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images";
32 import {$showImageForm, $showLinkForm} from "../forms/objects";
33 import {formatCodeBlock} from "../../../utils/formats";
35 export const link: EditorButtonDefinition = {
36 label: 'Insert/edit link',
38 action(context: EditorUiContext) {
39 context.editor.getEditorState().read(() => {
40 const selectedLink = $getNodeFromSelection($getSelection(), $isLinkNode) as LinkNode | null;
41 $showLinkForm(selectedLink, context);
44 isActive(selection: BaseSelection | null): boolean {
45 return $selectionContainsNodeType(selection, $isLinkNode);
49 export const unlink: EditorButtonDefinition = {
52 action(context: EditorUiContext) {
53 context.editor.update(() => {
54 const selection = getLastSelection(context.editor);
55 const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode | null;
58 const contents = selectedLink.getChildren().reverse();
59 for (const child of contents) {
60 selectedLink.insertAfter(child);
62 selectedLink.remove();
64 contents[contents.length - 1].selectStart();
66 context.manager.triggerFutureStateRefresh();
70 isActive(selection: BaseSelection | null): boolean {
76 export const image: EditorButtonDefinition = {
77 label: 'Insert/Edit Image',
79 action(context: EditorUiContext) {
80 context.editor.getEditorState().read(() => {
81 const selection = getLastSelection(context.editor);
82 const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode | null;
84 $showImageForm(selectedImage, context);
88 showImageManager((image) => {
89 context.editor.update(() => {
90 const link = $createLinkedImageNodeFromImageData(image);
96 isActive(selection: BaseSelection | null): boolean {
97 return $selectionContainsNodeType(selection, $isImageNode);
101 export const horizontalRule: EditorButtonDefinition = {
102 label: 'Insert horizontal line',
103 icon: horizontalRuleIcon,
104 action(context: EditorUiContext) {
105 context.editor.update(() => {
106 $insertNewBlockNodeAtSelection($createHorizontalRuleNode(), false);
109 isActive(selection: BaseSelection | null): boolean {
110 return $selectionContainsNodeType(selection, $isHorizontalRuleNode);
114 export const codeBlock: EditorButtonDefinition = {
115 label: 'Insert code block',
117 action(context: EditorUiContext) {
118 formatCodeBlock(context.editor);
120 isActive(selection: BaseSelection | null): boolean {
121 return $selectionContainsNodeType(selection, $isCodeBlockNode);
125 export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
126 label: 'Edit code block',
130 export const diagram: EditorButtonDefinition = {
131 label: 'Insert/edit drawing',
133 action(context: EditorUiContext) {
134 context.editor.getEditorState().read(() => {
135 const selection = getLastSelection(context.editor);
136 const diagramNode = $getNodeFromSelection(selection, $isDiagramNode) as (DiagramNode | null);
137 if (diagramNode === null) {
138 context.editor.update(() => {
139 const diagram = $createDiagramNode();
140 $insertNewBlockNodeAtSelection(diagram, true);
141 $openDrawingEditorForNode(context, diagram);
142 diagram.selectStart();
145 $openDrawingEditorForNode(context, diagramNode);
149 isActive(selection: BaseSelection | null): boolean {
150 return $selectionContainsNodeType(selection, $isDiagramNode);
154 export const diagramManager: EditorButtonDefinition = {
155 label: 'Drawing manager',
156 action(context: EditorUiContext) {
157 showDiagramManagerForInsert(context);
159 isActive(): boolean {
164 export const media: EditorButtonDefinition = {
165 label: 'Insert/edit Media',
167 action(context: EditorUiContext) {
168 const mediaModal = context.manager.createModal('media');
170 context.editor.getEditorState().read(() => {
171 const selection = $getSelection();
172 const selectedNode = $getNodeFromSelection(selection, $isMediaNode) as MediaNode | null;
174 let formDefaults = {};
176 const nodeAttrs = selectedNode.getAttributes();
178 src: nodeAttrs.src || nodeAttrs.data || '',
179 width: nodeAttrs.width,
180 height: nodeAttrs.height,
185 mediaModal.show(formDefaults);
188 isActive(selection: BaseSelection | null): boolean {
189 return $selectionContainsNodeType(selection, $isMediaNode);
193 export const details: EditorButtonDefinition = {
194 label: 'Insert collapsible block',
196 action(context: EditorUiContext) {
197 context.editor.update(() => {
198 const selection = $getSelection();
199 const detailsNode = $createDetailsNode();
200 const selectionNodes = selection?.getNodes() || [];
201 const topLevels = selectionNodes.map(n => n.getTopLevelElement())
202 .filter(n => n !== null) as ElementNode[];
203 const uniqueTopLevels = [...new Set(topLevels)];
205 if (uniqueTopLevels.length > 0) {
206 uniqueTopLevels[0].insertAfter(detailsNode);
208 $getRoot().append(detailsNode);
211 for (const node of uniqueTopLevels) {
212 detailsNode.append(node);
216 isActive(selection: BaseSelection | null): boolean {
217 return $selectionContainsNodeType(selection, $isDetailsNode);