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 detailsToggleIcon from "@icons/editor/details-toggle.svg";
23 import tableDeleteIcon from "@icons/editor/table-delete.svg";
24 import tagIcon from "@icons/tag.svg";
25 import mediaIcon from "@icons/editor/media.svg";
26 import {$createDetailsNode, $isDetailsNode} from "@lexical/rich-text/LexicalDetailsNode";
27 import {$isMediaNode, MediaNode} from "@lexical/rich-text/LexicalMediaNode";
29 $getNodeFromSelection,
30 $insertNewBlockNodeAtSelection,
31 $selectionContainsNodeType, getLastSelection
32 } from "../../../utils/selection";
33 import {$isDiagramNode, $openDrawingEditorForNode, showDiagramManagerForInsert} from "../../../utils/diagrams";
34 import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images";
35 import {$showDetailsForm, $showImageForm, $showLinkForm} from "../forms/objects";
36 import {formatCodeBlock} from "../../../utils/formats";
38 export const link: EditorButtonDefinition = {
39 label: 'Insert/edit link',
41 action(context: EditorUiContext) {
42 context.editor.getEditorState().read(() => {
43 const selectedLink = $getNodeFromSelection($getSelection(), $isLinkNode) as LinkNode | null;
44 $showLinkForm(selectedLink, context);
47 isActive(selection: BaseSelection | null): boolean {
48 return $selectionContainsNodeType(selection, $isLinkNode);
52 export const unlink: EditorButtonDefinition = {
55 action(context: EditorUiContext) {
56 context.editor.update(() => {
57 const selection = getLastSelection(context.editor);
58 const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode | null;
61 const contents = selectedLink.getChildren().reverse();
62 for (const child of contents) {
63 selectedLink.insertAfter(child);
65 selectedLink.remove();
67 contents[contents.length - 1].selectStart();
69 context.manager.triggerFutureStateRefresh();
73 isActive(selection: BaseSelection | null): boolean {
79 export const image: EditorButtonDefinition = {
80 label: 'Insert/Edit Image',
82 action(context: EditorUiContext) {
83 context.editor.getEditorState().read(() => {
84 const selection = getLastSelection(context.editor);
85 const selectedImage = $getNodeFromSelection(selection, $isImageNode) as ImageNode | null;
87 $showImageForm(selectedImage, context);
91 showImageManager((image) => {
92 context.editor.update(() => {
93 const link = $createLinkedImageNodeFromImageData(image);
99 isActive(selection: BaseSelection | null): boolean {
100 return $selectionContainsNodeType(selection, $isImageNode);
104 export const horizontalRule: EditorButtonDefinition = {
105 label: 'Insert horizontal line',
106 icon: horizontalRuleIcon,
107 action(context: EditorUiContext) {
108 context.editor.update(() => {
109 $insertNewBlockNodeAtSelection($createHorizontalRuleNode(), false);
112 isActive(selection: BaseSelection | null): boolean {
113 return $selectionContainsNodeType(selection, $isHorizontalRuleNode);
117 export const codeBlock: EditorButtonDefinition = {
118 label: 'Insert code block',
120 action(context: EditorUiContext) {
121 formatCodeBlock(context.editor);
123 isActive(selection: BaseSelection | null): boolean {
124 return $selectionContainsNodeType(selection, $isCodeBlockNode);
128 export const editCodeBlock: EditorButtonDefinition = Object.assign({}, codeBlock, {
129 label: 'Edit code block',
133 export const diagram: EditorButtonDefinition = {
134 label: 'Insert/edit drawing',
136 action(context: EditorUiContext) {
137 context.editor.getEditorState().read(() => {
138 const selection = getLastSelection(context.editor);
139 const diagramNode = $getNodeFromSelection(selection, $isDiagramNode) as (DiagramNode | null);
140 if (diagramNode === null) {
141 context.editor.update(() => {
142 const diagram = $createDiagramNode();
143 $insertNewBlockNodeAtSelection(diagram, true);
144 $openDrawingEditorForNode(context, diagram);
145 diagram.selectStart();
148 $openDrawingEditorForNode(context, diagramNode);
152 isActive(selection: BaseSelection | null): boolean {
153 return $selectionContainsNodeType(selection, $isDiagramNode);
157 export const diagramManager: EditorButtonDefinition = {
158 label: 'Drawing manager',
159 action(context: EditorUiContext) {
160 showDiagramManagerForInsert(context);
162 isActive(): boolean {
167 export const media: EditorButtonDefinition = {
168 label: 'Insert/edit Media',
170 action(context: EditorUiContext) {
171 const mediaModal = context.manager.createModal('media');
173 context.editor.getEditorState().read(() => {
174 const selection = $getSelection();
175 const selectedNode = $getNodeFromSelection(selection, $isMediaNode) as MediaNode | null;
177 let formDefaults = {};
179 const nodeAttrs = selectedNode.getAttributes();
181 src: nodeAttrs.src || nodeAttrs.data || '',
182 width: nodeAttrs.width,
183 height: nodeAttrs.height,
188 mediaModal.show(formDefaults);
191 isActive(selection: BaseSelection | null): boolean {
192 return $selectionContainsNodeType(selection, $isMediaNode);
196 export const details: EditorButtonDefinition = {
197 label: 'Insert collapsible block',
199 action(context: EditorUiContext) {
200 context.editor.update(() => {
201 const selection = $getSelection();
202 const detailsNode = $createDetailsNode();
203 const selectionNodes = selection?.getNodes() || [];
204 const topLevels = selectionNodes.map(n => n.getTopLevelElement())
205 .filter(n => n !== null) as ElementNode[];
206 const uniqueTopLevels = [...new Set(topLevels)];
208 if (uniqueTopLevels.length > 0) {
209 uniqueTopLevels[0].insertAfter(detailsNode);
211 $getRoot().append(detailsNode);
214 for (const node of uniqueTopLevels) {
215 detailsNode.append(node);
219 isActive(selection: BaseSelection | null): boolean {
220 return $selectionContainsNodeType(selection, $isDetailsNode);
224 export const detailsEditLabel: EditorButtonDefinition = {
227 action(context: EditorUiContext) {
228 context.editor.getEditorState().read(() => {
229 const details = $getNodeFromSelection($getSelection(), $isDetailsNode);
230 if ($isDetailsNode(details)) {
231 $showDetailsForm(details, context);
235 isActive(selection: BaseSelection | null): boolean {
240 export const detailsToggle: EditorButtonDefinition = {
241 label: 'Toggle open/closed',
242 icon: detailsToggleIcon,
243 action(context: EditorUiContext) {
244 context.editor.update(() => {
245 const details = $getNodeFromSelection($getSelection(), $isDetailsNode);
246 if ($isDetailsNode(details)) {
247 details.setOpen(!details.getOpen());
248 context.manager.triggerLayoutUpdate();
252 isActive(selection: BaseSelection | null): boolean {
257 export const detailsUnwrap: EditorButtonDefinition = {
259 icon: tableDeleteIcon,
260 action(context: EditorUiContext) {
261 context.editor.update(() => {
262 const details = $getNodeFromSelection($getSelection(), $isDetailsNode);
263 if ($isDetailsNode(details)) {
264 const children = details.getChildren();
265 for (const child of children) {
266 details.insertBefore(child);
269 context.manager.triggerLayoutUpdate();
273 isActive(selection: BaseSelection | null): boolean {