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