]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/defaults/forms/objects.ts
Lexical: Finished off baseline shortcut implementation
[bookstack] / resources / js / wysiwyg / ui / defaults / forms / objects.ts
1 import {
2     EditorFormDefinition,
3     EditorFormField,
4     EditorFormTabs,
5     EditorSelectFormFieldDefinition
6 } from "../../framework/forms";
7 import {EditorUiContext} from "../../framework/core";
8 import {$createNodeSelection, $createTextNode, $getSelection, $insertNodes, $setSelection} from "lexical";
9 import {$isImageNode, ImageNode} from "../../../nodes/image";
10 import {$createLinkNode, $isLinkNode, LinkNode} from "@lexical/link";
11 import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "../../../nodes/media";
12 import {$insertNodeToNearestRoot} from "@lexical/utils";
13 import {$getNodeFromSelection, getLastSelection} from "../../../utils/selection";
14 import {EditorFormModal} from "../../framework/modals";
15 import {EditorActionField} from "../../framework/blocks/action-field";
16 import {EditorButton} from "../../framework/buttons";
17 import {showImageManager} from "../../../utils/images";
18 import searchImageIcon from "@icons/editor/image-search.svg";
19 import searchIcon from "@icons/search.svg";
20 import {showLinkSelector} from "../../../utils/links";
21 import {LinkField} from "../../framework/blocks/link-field";
22 import {insertOrUpdateLink} from "../../../utils/formats";
23
24 export function $showImageForm(image: ImageNode, context: EditorUiContext) {
25     const imageModal: EditorFormModal = context.manager.createModal('image');
26     const height = image.getHeight();
27     const width = image.getWidth();
28
29     const formData = {
30         src: image.getSrc(),
31         alt: image.getAltText(),
32         height: height === 0 ? '' : String(height),
33         width: width === 0 ? '' : String(width),
34     };
35
36     imageModal.show(formData);
37 }
38
39 export const image: EditorFormDefinition = {
40     submitText: 'Apply',
41     async action(formData, context: EditorUiContext) {
42         context.editor.update(() => {
43             const selection = getLastSelection(context.editor);
44             const selectedImage = $getNodeFromSelection(selection, $isImageNode);
45             if ($isImageNode(selectedImage)) {
46                 selectedImage.setSrc(formData.get('src')?.toString() || '');
47                 selectedImage.setAltText(formData.get('alt')?.toString() || '');
48
49                 selectedImage.setWidth(Number(formData.get('width')?.toString() || '0'));
50                 selectedImage.setHeight(Number(formData.get('height')?.toString() || '0'));
51             }
52         });
53         return true;
54     },
55     fields: [
56         {
57             build() {
58                 return new EditorActionField(
59                     new EditorFormField({
60                         label: 'Source',
61                         name: 'src',
62                         type: 'text',
63                     }),
64                     new EditorButton({
65                         label: 'Browse files',
66                         icon: searchImageIcon,
67                         action(context: EditorUiContext) {
68                             showImageManager((image) => {
69                                  const modal =  context.manager.getActiveModal('image');
70                                  if (modal) {
71                                      modal.getForm().setValues({
72                                          src: image.thumbs?.display || image.url,
73                                          alt: image.name,
74                                      });
75                                  }
76                             });
77                         }
78                     }),
79                 );
80             },
81         },
82         {
83             label: 'Alternative description',
84             name: 'alt',
85             type: 'text',
86         },
87         {
88             label: 'Width',
89             name: 'width',
90             type: 'text',
91         },
92         {
93             label: 'Height',
94             name: 'height',
95             type: 'text',
96         },
97     ],
98 };
99
100 export function $showLinkForm(link: LinkNode|null, context: EditorUiContext) {
101     const linkModal = context.manager.createModal('link');
102
103     let formDefaults = {};
104     if (link) {
105         formDefaults = {
106             url: link.getURL(),
107             text: link.getTextContent(),
108             title: link.getTitle(),
109             target: link.getTarget(),
110         }
111
112         context.editor.update(() => {
113             const selection = $createNodeSelection();
114             selection.add(link.getKey());
115             $setSelection(selection);
116         });
117     }
118
119     linkModal.show(formDefaults);
120 }
121
122 export const link: EditorFormDefinition = {
123     submitText: 'Apply',
124     async action(formData, context: EditorUiContext) {
125         insertOrUpdateLink(context.editor, {
126             url: formData.get('url')?.toString() || '',
127             title: formData.get('title')?.toString() || '',
128             target: formData.get('target')?.toString() || '',
129             text: formData.get('text')?.toString() || '',
130         });
131         return true;
132     },
133     fields: [
134         {
135             build() {
136                 return new EditorActionField(
137                     new LinkField(new EditorFormField({
138                         label: 'URL',
139                         name: 'url',
140                         type: 'text',
141                     })),
142                     new EditorButton({
143                         label: 'Browse links',
144                         icon: searchIcon,
145                         action(context: EditorUiContext) {
146                             showLinkSelector(entity => {
147                                 const modal =  context.manager.getActiveModal('link');
148                                 if (modal) {
149                                     modal.getForm().setValues({
150                                         url: entity.link,
151                                         text: entity.name,
152                                         title: entity.name,
153                                     });
154                                 }
155                             });
156                         }
157                     }),
158                 );
159             },
160         },
161         {
162             label: 'Text to display',
163             name: 'text',
164             type: 'text',
165         },
166         {
167             label: 'Title',
168             name: 'title',
169             type: 'text',
170         },
171         {
172             label: 'Open link in...',
173             name: 'target',
174             type: 'select',
175             valuesByLabel: {
176                 'Current window': '',
177                 'New window': '_blank',
178             }
179         } as EditorSelectFormFieldDefinition,
180     ],
181 };
182
183 export const media: EditorFormDefinition = {
184     submitText: 'Save',
185     async action(formData, context: EditorUiContext) {
186         const selectedNode: MediaNode|null = await (new Promise((res, rej) => {
187             context.editor.getEditorState().read(() => {
188                 const node = $getNodeFromSelection($getSelection(), $isMediaNode);
189                 res(node as MediaNode|null);
190             });
191         }));
192
193         const embedCode = (formData.get('embed') || '').toString().trim();
194         if (embedCode) {
195             context.editor.update(() => {
196                 const node = $createMediaNodeFromHtml(embedCode);
197                 if (selectedNode && node) {
198                     selectedNode.replace(node)
199                 } else if (node) {
200                     $insertNodeToNearestRoot(node);
201                 }
202             });
203
204             return true;
205         }
206
207         context.editor.update(() => {
208             const src = (formData.get('src') || '').toString().trim();
209             const height = (formData.get('height') || '').toString().trim();
210             const width = (formData.get('width') || '').toString().trim();
211
212             const updateNode = selectedNode || $createMediaNodeFromSrc(src);
213             updateNode.setSrc(src);
214             updateNode.setWidthAndHeight(width, height);
215             if (!selectedNode) {
216                 $insertNodeToNearestRoot(updateNode);
217             }
218         });
219
220         return true;
221     },
222     fields: [
223         {
224             build() {
225                 return new EditorFormTabs([
226                     {
227                         label: 'General',
228                         contents: [
229                             {
230                                 label: 'Source',
231                                 name: 'src',
232                                 type: 'text',
233                             },
234                             {
235                                 label: 'Width',
236                                 name: 'width',
237                                 type: 'text',
238                             },
239                             {
240                                 label: 'Height',
241                                 name: 'height',
242                                 type: 'text',
243                             },
244                         ],
245                     },
246                     {
247                         label: 'Embed',
248                         contents: [
249                             {
250                                 label: 'Paste your embed code below:',
251                                 name: 'embed',
252                                 type: 'textarea',
253                             },
254                         ],
255                     }
256                 ])
257             }
258         },
259     ],
260 };