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