]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/defaults/buttons/objects.ts
Lexical: Added table toolbar, organised button code
[bookstack] / resources / js / wysiwyg / ui / defaults / buttons / objects.ts
1 import {EditorButtonDefinition} from "../../framework/buttons";
2 import linkIcon from "@icons/editor/link.svg";
3 import {EditorUiContext} from "../../framework/core";
4 import {
5     $createNodeSelection,
6     $createTextNode,
7     $getRoot,
8     $getSelection,
9     $setSelection,
10     BaseSelection,
11     ElementNode
12 } from "lexical";
13 import {$getNodeFromSelection, $insertNewBlockNodeAtSelection, $selectionContainsNodeType} from "../../../helpers";
14 import {$isLinkNode, LinkNode} from "@lexical/link";
15 import unlinkIcon from "@icons/editor/unlink.svg";
16 import imageIcon from "@icons/editor/image.svg";
17 import {$isImageNode, ImageNode} from "../../../nodes/image";
18 import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
19 import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../../nodes/horizontal-rule";
20 import codeBlockIcon from "@icons/editor/code-block.svg";
21 import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../../nodes/code-block";
22 import editIcon from "@icons/edit.svg";
23 import diagramIcon from "@icons/editor/diagram.svg";
24 import {$createDiagramNode, $isDiagramNode, $openDrawingEditorForNode, DiagramNode} from "../../../nodes/diagram";
25 import detailsIcon from "@icons/editor/details.svg";
26 import {$createDetailsNode, $isDetailsNode} from "../../../nodes/details";
27
28 export const link: EditorButtonDefinition = {
29     label: 'Insert/edit link',
30     icon: linkIcon,
31     action(context: EditorUiContext) {
32         const linkModal = context.manager.createModal('link');
33         context.editor.getEditorState().read(() => {
34             const selection = $getSelection();
35             const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
36
37             let formDefaults = {};
38             if (selectedLink) {
39                 formDefaults = {
40                     url: selectedLink.getURL(),
41                     text: selectedLink.getTextContent(),
42                     title: selectedLink.getTitle(),
43                     target: selectedLink.getTarget(),
44                 }
45
46                 context.editor.update(() => {
47                     const selection = $createNodeSelection();
48                     selection.add(selectedLink.getKey());
49                     $setSelection(selection);
50                 });
51             }
52
53             linkModal.show(formDefaults);
54         });
55     },
56     isActive(selection: BaseSelection|null): boolean {
57         return $selectionContainsNodeType(selection, $isLinkNode);
58     }
59 };
60
61 export const unlink: EditorButtonDefinition = {
62     label: 'Remove link',
63     icon: unlinkIcon,
64     action(context: EditorUiContext) {
65         context.editor.update(() => {
66             const selection = context.lastSelection;
67             const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode|null;
68             const selectionPoints = selection?.getStartEndPoints();
69
70             if (selectedLink) {
71                 const newNode = $createTextNode(selectedLink.getTextContent());
72                 selectedLink.replace(newNode);
73                 if (selectionPoints?.length === 2) {
74                     newNode.select(selectionPoints[0].offset, selectionPoints[1].offset);
75                 } else {
76                     newNode.select();
77                 }
78             }
79         });
80     },
81     isActive(selection: BaseSelection|null): boolean {
82         return false;
83     }
84 };
85
86
87
88 export const image: EditorButtonDefinition = {
89     label: 'Insert/Edit Image',
90     icon: imageIcon,
91     action(context: EditorUiContext) {
92         const imageModal = context.manager.createModal('image');
93         const selection = context.lastSelection;
94         const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode|null;
95
96         context.editor.getEditorState().read(() => {
97             let formDefaults = {};
98             if (selectedImage) {
99                 formDefaults = {
100                     src: selectedImage.getSrc(),
101                     alt: selectedImage.getAltText(),
102                     height: selectedImage.getHeight(),
103                     width: selectedImage.getWidth(),
104                 }
105
106                 context.editor.update(() => {
107                     const selection = $createNodeSelection();
108                     selection.add(selectedImage.getKey());
109                     $setSelection(selection);
110                 });
111             }
112
113             imageModal.show(formDefaults);
114         });
115     },
116     isActive(selection: BaseSelection|null): boolean {
117         return $selectionContainsNodeType(selection, $isImageNode);
118     }
119 };
120
121 export const horizontalRule: EditorButtonDefinition = {
122     label: 'Insert horizontal line',
123     icon: horizontalRuleIcon,
124     action(context: EditorUiContext) {
125         context.editor.update(() => {
126             $insertNewBlockNodeAtSelection($createHorizontalRuleNode(), false);
127         });
128     },
129     isActive(selection: BaseSelection|null): boolean {
130         return $selectionContainsNodeType(selection, $isHorizontalRuleNode);
131     }
132 };
133
134 export const codeBlock: EditorButtonDefinition = {
135     label: 'Insert code block',
136     icon: codeBlockIcon,
137     action(context: EditorUiContext) {
138         context.editor.getEditorState().read(() => {
139             const selection = $getSelection();
140             const codeBlock = $getNodeFromSelection(context.lastSelection, $isCodeBlockNode) as (CodeBlockNode|null);
141             if (codeBlock === null) {
142                 context.editor.update(() => {
143                     const codeBlock = $createCodeBlockNode();
144                     codeBlock.setCode(selection?.getTextContent() || '');
145                     $insertNewBlockNodeAtSelection(codeBlock, true);
146                     $openCodeEditorForNode(context.editor, codeBlock);
147                     codeBlock.selectStart();
148                 });
149             } else {
150                 $openCodeEditorForNode(context.editor, codeBlock);
151             }
152         });
153     },
154     isActive(selection: BaseSelection|null): boolean {
155         return $selectionContainsNodeType(selection, $isCodeBlockNode);
156     }
157 };
158
159 export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
160     label: 'Edit code block',
161     icon: editIcon,
162 });
163
164 export const diagram: EditorButtonDefinition = {
165     label: 'Insert/edit drawing',
166     icon: diagramIcon,
167     action(context: EditorUiContext) {
168         context.editor.getEditorState().read(() => {
169             const selection = $getSelection();
170             const diagramNode = $getNodeFromSelection(context.lastSelection, $isDiagramNode) as (DiagramNode|null);
171             if (diagramNode === null) {
172                 context.editor.update(() => {
173                     const diagram = $createDiagramNode();
174                     $insertNewBlockNodeAtSelection(diagram, true);
175                     $openDrawingEditorForNode(context, diagram);
176                     diagram.selectStart();
177                 });
178             } else {
179                 $openDrawingEditorForNode(context, diagramNode);
180             }
181         });
182     },
183     isActive(selection: BaseSelection|null): boolean {
184         return $selectionContainsNodeType(selection, $isDiagramNode);
185     }
186 };
187
188
189 export const details: EditorButtonDefinition = {
190     label: 'Insert collapsible block',
191     icon: detailsIcon,
192     action(context: EditorUiContext) {
193         context.editor.update(() => {
194             const selection = $getSelection();
195             const detailsNode = $createDetailsNode();
196             const selectionNodes = selection?.getNodes() || [];
197             const topLevels = selectionNodes.map(n => n.getTopLevelElement())
198                 .filter(n => n !== null) as ElementNode[];
199             const uniqueTopLevels = [...new Set(topLevels)];
200
201             if (uniqueTopLevels.length > 0) {
202                 uniqueTopLevels[0].insertAfter(detailsNode);
203             } else {
204                 $getRoot().append(detailsNode);
205             }
206
207             for (const node of uniqueTopLevels) {
208                 detailsNode.append(node);
209             }
210         });
211     },
212     isActive(selection: BaseSelection|null): boolean {
213         return $selectionContainsNodeType(selection, $isDetailsNode);
214     }
215 }