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