suneditor 3.0.0-beta.2 → 3.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CONTRIBUTING.md +186 -184
  2. package/LICENSE +21 -21
  3. package/README.md +157 -180
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +126 -123
  7. package/src/assets/design/color.css +131 -121
  8. package/src/assets/design/index.css +3 -3
  9. package/src/assets/design/size.css +37 -35
  10. package/src/assets/design/typography.css +37 -37
  11. package/src/assets/icons/defaultIcons.js +247 -232
  12. package/src/assets/suneditor-contents.css +779 -778
  13. package/src/assets/suneditor.css +43 -35
  14. package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
  19. package/src/core/base/eventManager.js +1550 -1484
  20. package/src/core/base/history.js +355 -355
  21. package/src/core/class/char.js +163 -162
  22. package/src/core/class/component.js +856 -842
  23. package/src/core/class/format.js +3433 -3422
  24. package/src/core/class/html.js +1927 -1890
  25. package/src/core/class/menu.js +357 -346
  26. package/src/core/class/nodeTransform.js +424 -424
  27. package/src/core/class/offset.js +858 -891
  28. package/src/core/class/selection.js +710 -620
  29. package/src/core/class/shortcuts.js +98 -98
  30. package/src/core/class/toolbar.js +438 -430
  31. package/src/core/class/ui.js +424 -422
  32. package/src/core/class/viewer.js +750 -750
  33. package/src/core/editor.js +1810 -1708
  34. package/src/core/section/actives.js +268 -241
  35. package/src/core/section/constructor.js +1348 -1661
  36. package/src/core/section/context.js +102 -102
  37. package/src/core/section/documentType.js +582 -561
  38. package/src/core/section/options.js +367 -0
  39. package/src/core/util/instanceCheck.js +59 -0
  40. package/src/editorInjector/_classes.js +36 -36
  41. package/src/editorInjector/_core.js +92 -92
  42. package/src/editorInjector/index.js +75 -75
  43. package/src/events.js +634 -622
  44. package/src/helper/clipboard.js +59 -59
  45. package/src/helper/converter.js +586 -564
  46. package/src/helper/dom/domCheck.js +304 -304
  47. package/src/helper/dom/domQuery.js +677 -669
  48. package/src/helper/dom/domUtils.js +618 -557
  49. package/src/helper/dom/index.js +12 -12
  50. package/src/helper/env.js +249 -240
  51. package/src/helper/index.js +25 -25
  52. package/src/helper/keyCodeMap.js +183 -183
  53. package/src/helper/numbers.js +72 -72
  54. package/src/helper/unicode.js +47 -47
  55. package/src/langs/ckb.js +231 -231
  56. package/src/langs/cs.js +231 -231
  57. package/src/langs/da.js +231 -231
  58. package/src/langs/de.js +231 -231
  59. package/src/langs/en.js +230 -230
  60. package/src/langs/es.js +231 -231
  61. package/src/langs/fa.js +231 -231
  62. package/src/langs/fr.js +231 -231
  63. package/src/langs/he.js +231 -231
  64. package/src/langs/hu.js +230 -230
  65. package/src/langs/index.js +28 -28
  66. package/src/langs/it.js +231 -231
  67. package/src/langs/ja.js +230 -230
  68. package/src/langs/km.js +230 -230
  69. package/src/langs/ko.js +230 -230
  70. package/src/langs/lv.js +231 -231
  71. package/src/langs/nl.js +231 -231
  72. package/src/langs/pl.js +231 -231
  73. package/src/langs/pt_br.js +231 -231
  74. package/src/langs/ro.js +231 -231
  75. package/src/langs/ru.js +231 -231
  76. package/src/langs/se.js +231 -231
  77. package/src/langs/tr.js +231 -231
  78. package/src/langs/uk.js +231 -231
  79. package/src/langs/ur.js +231 -231
  80. package/src/langs/zh_cn.js +231 -231
  81. package/src/modules/ApiManager.js +191 -191
  82. package/src/modules/Browser.js +669 -667
  83. package/src/modules/ColorPicker.js +364 -362
  84. package/src/modules/Controller.js +474 -454
  85. package/src/modules/Figure.js +1620 -1617
  86. package/src/modules/FileManager.js +359 -359
  87. package/src/modules/HueSlider.js +577 -565
  88. package/src/modules/Modal.js +346 -346
  89. package/src/modules/ModalAnchorEditor.js +643 -643
  90. package/src/modules/SelectMenu.js +549 -549
  91. package/src/modules/_DragHandle.js +17 -17
  92. package/src/modules/index.js +14 -14
  93. package/src/plugins/browser/audioGallery.js +83 -83
  94. package/src/plugins/browser/fileBrowser.js +103 -103
  95. package/src/plugins/browser/fileGallery.js +83 -83
  96. package/src/plugins/browser/imageGallery.js +81 -81
  97. package/src/plugins/browser/videoGallery.js +103 -103
  98. package/src/plugins/command/blockquote.js +61 -60
  99. package/src/plugins/command/exportPDF.js +134 -134
  100. package/src/plugins/command/fileUpload.js +456 -456
  101. package/src/plugins/command/list_bulleted.js +149 -148
  102. package/src/plugins/command/list_numbered.js +152 -151
  103. package/src/plugins/dropdown/align.js +157 -155
  104. package/src/plugins/dropdown/backgroundColor.js +108 -104
  105. package/src/plugins/dropdown/font.js +141 -137
  106. package/src/plugins/dropdown/fontColor.js +109 -105
  107. package/src/plugins/dropdown/formatBlock.js +170 -178
  108. package/src/plugins/dropdown/hr.js +152 -152
  109. package/src/plugins/dropdown/layout.js +83 -83
  110. package/src/plugins/dropdown/lineHeight.js +131 -130
  111. package/src/plugins/dropdown/list.js +123 -122
  112. package/src/plugins/dropdown/paragraphStyle.js +138 -138
  113. package/src/plugins/dropdown/table.js +4110 -4000
  114. package/src/plugins/dropdown/template.js +83 -83
  115. package/src/plugins/dropdown/textStyle.js +149 -149
  116. package/src/plugins/field/mention.js +242 -242
  117. package/src/plugins/index.js +120 -120
  118. package/src/plugins/input/fontSize.js +414 -410
  119. package/src/plugins/input/pageNavigator.js +71 -70
  120. package/src/plugins/modal/audio.js +677 -677
  121. package/src/plugins/modal/drawing.js +537 -531
  122. package/src/plugins/modal/embed.js +886 -886
  123. package/src/plugins/modal/image.js +1377 -1376
  124. package/src/plugins/modal/link.js +248 -240
  125. package/src/plugins/modal/math.js +563 -563
  126. package/src/plugins/modal/video.js +1226 -1226
  127. package/src/plugins/popup/anchor.js +224 -222
  128. package/src/suneditor.js +114 -107
  129. package/src/themes/dark.css +132 -122
  130. package/src/typedef.js +132 -130
  131. package/types/assets/icons/defaultIcons.d.ts +8 -0
  132. package/types/core/base/eventManager.d.ts +29 -4
  133. package/types/core/class/char.d.ts +2 -1
  134. package/types/core/class/component.d.ts +1 -2
  135. package/types/core/class/format.d.ts +8 -1
  136. package/types/core/class/html.d.ts +8 -0
  137. package/types/core/class/menu.d.ts +8 -0
  138. package/types/core/class/offset.d.ts +24 -26
  139. package/types/core/class/selection.d.ts +2 -0
  140. package/types/core/class/toolbar.d.ts +6 -0
  141. package/types/core/class/ui.d.ts +1 -1
  142. package/types/core/editor.d.ts +34 -12
  143. package/types/core/section/constructor.d.ts +5 -638
  144. package/types/core/section/documentType.d.ts +12 -2
  145. package/types/core/section/options.d.ts +740 -0
  146. package/types/core/util/instanceCheck.d.ts +50 -0
  147. package/types/editorInjector/_core.d.ts +5 -5
  148. package/types/editorInjector/index.d.ts +2 -2
  149. package/types/events.d.ts +2 -0
  150. package/types/helper/converter.d.ts +9 -0
  151. package/types/helper/dom/domQuery.d.ts +5 -5
  152. package/types/helper/dom/domUtils.d.ts +8 -0
  153. package/types/helper/env.d.ts +6 -1
  154. package/types/helper/index.d.ts +4 -1
  155. package/types/index.d.ts +122 -120
  156. package/types/langs/_Lang.d.ts +194 -194
  157. package/types/modules/ColorPicker.d.ts +5 -1
  158. package/types/modules/Controller.d.ts +8 -4
  159. package/types/modules/Figure.d.ts +2 -1
  160. package/types/modules/HueSlider.d.ts +4 -1
  161. package/types/modules/SelectMenu.d.ts +1 -1
  162. package/types/plugins/command/blockquote.d.ts +1 -0
  163. package/types/plugins/command/list_bulleted.d.ts +1 -0
  164. package/types/plugins/command/list_numbered.d.ts +1 -0
  165. package/types/plugins/dropdown/align.d.ts +1 -0
  166. package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
  167. package/types/plugins/dropdown/font.d.ts +1 -0
  168. package/types/plugins/dropdown/fontColor.d.ts +1 -0
  169. package/types/plugins/dropdown/formatBlock.d.ts +3 -2
  170. package/types/plugins/dropdown/lineHeight.d.ts +1 -0
  171. package/types/plugins/dropdown/list.d.ts +1 -0
  172. package/types/plugins/dropdown/table.d.ts +6 -0
  173. package/types/plugins/input/fontSize.d.ts +1 -0
  174. package/types/plugins/modal/drawing.d.ts +4 -0
  175. package/types/plugins/modal/link.d.ts +32 -15
  176. package/types/suneditor.d.ts +13 -9
  177. package/types/typedef.d.ts +8 -0
@@ -1,531 +1,537 @@
1
- import EditorInjector from '../../editorInjector';
2
- import { Modal } from '../../modules';
3
- import { dom, env } from '../../helper';
4
-
5
- const { _w, isMobile } = env;
6
-
7
- /**
8
- * @typedef {Object} DrawingPluginOptions
9
- * @property {string} [outputFormat="dataurl"] - The output format of the drawing. Options: "dataurl", "svg".
10
- * @property {boolean} [useFormatType=false] - Whether to enable format type selection (block vs inline).
11
- * @property {string} [defaultFormatType="block"] - The default format type, either "block" or "inline".
12
- * @property {boolean} [keepFormatType=false] - Whether to maintain the chosen format type after drawing.
13
- * @property {number} [lineWidth=5] - The width of the drawing line.
14
- * @property {boolean} [lineReconnect=false] - Whether to reconnect lines when drawing.
15
- * @property {CanvasLineCap} [lineCap="round"] - The style of the line cap ("butt", "round", or "square").
16
- * @property {string} [lineColor=""] - The color of the drawing line.
17
- * @property {boolean} [canResize=true] - Whether the modal form can be resized.
18
- * @property {boolean} [maintainRatio=true] - Whether to maintain the aspect ratio when resizing.
19
- * @property {Object} [formSize={}] - The size configuration for the drawing modal form.
20
- * @property {string} [formSize.width="750px"] - The width of the modal form.
21
- * @property {string} [formSize.height="50vh"] - The height of the modal form.
22
- * @property {string} [formSize.maxWidth=""] - The maximum width of the modal form.
23
- * @property {string} [formSize.maxHeight=""] - The maximum height of the modal form.
24
- * @property {string} [formSize.minWidth="150px"] - The minimum width of the modal form.
25
- * @property {string} [formSize.minHeight="100px"] - The minimum height of the modal form.
26
- */
27
-
28
- /**
29
- * @class
30
- * @description Drawing modal plugin.
31
- */
32
- class Drawing extends EditorInjector {
33
- static key = 'drawing';
34
- static type = 'modal';
35
- static className = '';
36
-
37
- /**
38
- * @constructor
39
- * @param {__se__EditorCore} editor - The root editor instance
40
- * @param {DrawingPluginOptions} pluginOptions
41
- */
42
- constructor(editor, pluginOptions) {
43
- // plugin basic properties
44
- super(editor);
45
- this.title = this.lang.drawing;
46
- this.icon = 'drawing';
47
- this.pluginOptions = {
48
- outputFormat: pluginOptions.outputFormat || 'dataurl', // dataurl, svg
49
- useFormatType: pluginOptions.useFormatType ?? false,
50
- defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : 'block',
51
- keepFormatType: pluginOptions.keepFormatType ?? false,
52
- lineWidth: pluginOptions.lineWidth || 5,
53
- lineReconnect: !!pluginOptions.lineReconnect,
54
- lineCap: ['butt', 'round', 'square'].includes(pluginOptions.lineCap) ? pluginOptions.lineCap : 'round',
55
- lineColor: pluginOptions.lineColor || '',
56
- formSize: {
57
- width: '750px',
58
- height: '50vh',
59
- maxWidth: '',
60
- maxHeight: '',
61
- minWidth: '150px',
62
- minHeight: '100px',
63
- ...pluginOptions.formSize
64
- },
65
- canResize: pluginOptions.canResize ?? true,
66
- maintainRatio: pluginOptions.maintainRatio ?? true
67
- };
68
-
69
- // exception
70
- if (!this.plugins.image) {
71
- console.warn('[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need either "image" plugin. Please add the "image" plugin.');
72
- } else if (this.pluginOptions.outputFormat === 'svg' && !this.plugins.image.pluginOptions.uploadUrl) {
73
- console.warn('[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need the "image" plugin with the "uploadUrl" option. Please add the "image" plugin with the "uploadUrl" option.');
74
- }
75
-
76
- // create HTML
77
- const modalEl = CreateHTML_modal(this);
78
-
79
- // modules
80
- this.modal = new Modal(this, modalEl);
81
-
82
- // members
83
- this.as = this.pluginOptions.defaultFormatType;
84
- if (this.pluginOptions.useFormatType) {
85
- this.asBlock = modalEl.querySelector('[data-command="asBlock"]');
86
- this.asInline = modalEl.querySelector('[data-command="asInline"]');
87
- this.eventManager.addEvent([this.asBlock, this.asInline], 'click', this.#OnClickAsButton.bind(this));
88
- }
89
-
90
- /**
91
- * @type {HTMLCanvasElement}
92
- */
93
- this.canvas = null;
94
- this.ctx = null;
95
- this.isDrawing = false;
96
- this.points = [];
97
- this.paths = [];
98
- this.resizeObserver = null;
99
- this.__events = {
100
- mousedown: isMobile ? this.#OnCanvasTouchStart.bind(this) : this.#OnCanvasMouseDown.bind(this),
101
- mousemove: isMobile ? this.#OnCanvasTouchMove.bind(this) : this.#OnCanvasMouseMove.bind(this),
102
- mouseup: this.#OnCanvasMouseUp.bind(this),
103
- mouseleave: this.#OnCanvasMouseLeave.bind(this),
104
- mouseenter: this.#OnCanvasMouseEnter.bind(this)
105
- };
106
- this.__eventsRegister = {
107
- mousedown: null,
108
- mousemove: null,
109
- mouseup: null,
110
- mouseleave: null,
111
- mouseenter: null
112
- };
113
- this.__eventNameMap = {
114
- mousedown: isMobile ? 'touchstart' : 'mousedown',
115
- mousemove: isMobile ? 'touchmove' : 'mousemove',
116
- mouseup: isMobile ? 'touchend' : 'mouseup',
117
- mouseleave: 'mouseleave',
118
- mouseenter: 'mouseenter'
119
- };
120
-
121
- // init
122
- this.eventManager.addEvent(modalEl.querySelector('[data-command="remove"]'), 'click', this.#OnRemove.bind(this));
123
- }
124
-
125
- /**
126
- * @editorMethod Modules.Modal
127
- * @description Executes the method that is called when a "Modal" module's is opened.
128
- */
129
- open() {
130
- if (this.pluginOptions.useFormatType) {
131
- this._activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
132
- }
133
- this.modal.open();
134
- this._initDrawing();
135
- }
136
-
137
- /**
138
- * @editorMethod Modules.Modal
139
- * @description Executes the method that is called when a plugin's "modal" is closed.
140
- */
141
- off() {
142
- this._destroyDrawing();
143
- }
144
-
145
- /**
146
- * @editorMethod Modules.Modal
147
- * @description This function is called when a form within a modal window is "submit".
148
- * @returns {boolean} Success or failure
149
- */
150
- modalAction() {
151
- if (this.pluginOptions.outputFormat === 'svg') {
152
- const files = this._getSVGFileList();
153
- this.plugins.image.init();
154
- this.plugins.image.submitFile(files);
155
- } else {
156
- // dataurl | svg
157
- const data = this.canvas.toDataURL();
158
- const file = { name: 'drawing', size: 0 };
159
- this.plugins.image.init();
160
- if (this.as !== 'inline') {
161
- this.plugins.image.create(data, null, 'auto', '', 'none', file, '');
162
- } else {
163
- this.plugins.image.createInline(data, null, 'auto', '', 'none', file, '');
164
- }
165
- }
166
-
167
- return true;
168
- }
169
-
170
- /**
171
- * @private
172
- * @description Initializes the drawing canvas, sets up event listeners, and configures resize handling.
173
- */
174
- _initDrawing() {
175
- const canvas = (this.canvas = this.modal.form.querySelector('.se-drawing-canvas'));
176
- this.ctx = canvas.getContext('2d');
177
- canvas.width = canvas.offsetWidth;
178
- canvas.height = canvas.offsetHeight;
179
-
180
- this.points = [];
181
- this.paths = [];
182
-
183
- this._setCtx();
184
-
185
- this.__eventsRegister.mousedown = this.eventManager.addEvent(canvas, this.__eventNameMap.mousedown, this.__events.mousedown, { passive: false, capture: true });
186
- this.__eventsRegister.mousemove = this.eventManager.addEvent(canvas, this.__eventNameMap.mousemove, this.__events.mousemove, true);
187
- this.__eventsRegister.mouseup = this.eventManager.addEvent(canvas, this.__eventNameMap.mouseup, this.__events.mouseup, true);
188
- this.__eventsRegister.mouseleave = this.eventManager.addEvent(canvas, this.__eventNameMap.mouseleave, this.__events.mouseleave);
189
- this.__eventsRegister.mouseenter = this.eventManager.addEvent(canvas, this.__eventNameMap.mouseenter, this.__events.mouseenter);
190
-
191
- if (this.resizeObserver) {
192
- this.resizeObserver.disconnect();
193
- this.resizeObserver = null;
194
- }
195
-
196
- if (env.isResizeObserverSupported) {
197
- this.resizeObserver = new ResizeObserver(() => {
198
- const prevWidth = canvas.width;
199
- const prevHeight = canvas.height;
200
- const newWidth = canvas.offsetWidth;
201
- const newHeight = canvas.offsetHeight;
202
- canvas.width = newWidth;
203
- canvas.height = newHeight;
204
- if (prevWidth !== canvas.width || prevHeight !== canvas.height) {
205
- if (this.pluginOptions.maintainRatio) this._adjustPathsToNewDimensions(prevWidth, prevHeight, newWidth, newHeight);
206
- this._drawAll();
207
- }
208
- });
209
-
210
- this.resizeObserver.observe(canvas);
211
- }
212
- }
213
-
214
- /**
215
- * @private
216
- * @description Destroys the drawing canvas, removes event listeners, and clears stored drawing data.
217
- */
218
- _destroyDrawing() {
219
- if (this.resizeObserver) {
220
- this.resizeObserver.disconnect();
221
- this.resizeObserver = null;
222
- }
223
-
224
- if (this.canvas) {
225
- this.eventManager.removeEvent(this.__eventsRegister.mousedown);
226
- this.eventManager.removeEvent(this.__eventsRegister.mousemove);
227
- this.eventManager.removeEvent(this.__eventsRegister.mouseup);
228
- this.eventManager.removeEvent(this.__eventsRegister.mouseleave);
229
- this.eventManager.removeEvent(this.__eventsRegister.mouseenter);
230
- }
231
-
232
- this.canvas = null;
233
- this.ctx = null;
234
- this.points = [];
235
- this.paths = [];
236
- this.isDrawing = false;
237
- }
238
-
239
- /**
240
- * @private
241
- * @description Configures the drawing context (canvas settings like line width, color, etc.).
242
- */
243
- _setCtx() {
244
- this.ctx.lineWidth = this.pluginOptions.lineWidth;
245
- this.ctx.lineCap = this.pluginOptions.lineCap;
246
- this.ctx.strokeStyle = this.pluginOptions.lineColor || _w.getComputedStyle(this.carrierWrapper).color;
247
- }
248
-
249
- /**
250
- * @private
251
- * @description Draws the current stroke based on collected points.
252
- */
253
- _draw() {
254
- this._setCtx();
255
- this.ctx.beginPath();
256
- this.points.forEach(([x, y], i) => {
257
- if (i === 0) {
258
- this.ctx.moveTo(x, y);
259
- } else {
260
- this.ctx.lineTo(x, y);
261
- }
262
- });
263
- this.ctx.stroke();
264
- }
265
-
266
- /**
267
- * @private
268
- * @description Redraws all stored paths onto the canvas.
269
- */
270
- _drawAll() {
271
- this._setCtx();
272
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
273
- this.paths.forEach((path) => {
274
- this.points = path;
275
- this._draw();
276
- });
277
- this.points = [];
278
- }
279
-
280
- /**
281
- * @private
282
- * @description Adjusts all stored paths to fit new canvas dimensions after a resize event.
283
- * @param {number} prevWidth - The previous width of the canvas.
284
- * @param {number} prevHeight - The previous height of the canvas.
285
- * @param {number} newWidth - The new width of the canvas.
286
- * @param {number} newHeight - The new height of the canvas.
287
- */
288
- _adjustPathsToNewDimensions(prevWidth, prevHeight, newWidth, newHeight) {
289
- const xRatio = newWidth / prevWidth;
290
- const yRatio = newHeight / prevHeight;
291
-
292
- this.paths = this.paths.map((path) => path.map(([x, y]) => [x * xRatio, y * yRatio]));
293
- }
294
-
295
- /**
296
- * @private
297
- * @description Clears the canvas and resets stored drawing paths.
298
- */
299
- _clearCanvas() {
300
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
301
- this.points = [];
302
- this.paths = [];
303
- }
304
-
305
- /**
306
- * @private
307
- * @description Generates an SVG representation of the drawn content.
308
- * @returns {*} The generated SVG element.
309
- */
310
- _getSVG() {
311
- const svgNS = 'http://www.w3.org/2000/svg';
312
- const svg = document.createElementNS(svgNS, 'svg');
313
- svg.setAttribute('width', this.canvas.width + '');
314
- svg.setAttribute('height', this.canvas.height + '');
315
- svg.setAttribute('viewBox', `0 0 ${this.canvas.width} ${this.canvas.height}`);
316
- svg.setAttribute('xmlns', svgNS);
317
-
318
- this.paths.forEach((path) => {
319
- const pathData = path.reduce((acc, [x, y], i) => {
320
- return acc + (i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`);
321
- }, '');
322
- const svgPath = document.createElementNS(svgNS, 'path');
323
- svgPath.setAttribute('d', pathData);
324
- svgPath.setAttribute('fill', 'none');
325
- svgPath.setAttribute('stroke', String(this.ctx.strokeStyle));
326
- svgPath.setAttribute('stroke-width', this.ctx.lineWidth + '');
327
- svg.appendChild(svgPath);
328
- });
329
-
330
- return svg;
331
- }
332
-
333
- /**
334
- * @private
335
- * @description Converts the SVG element into a downloadable file.
336
- * @returns {FileList} A FileList containing the generated SVG file.
337
- */
338
- _getSVGFileList() {
339
- const svgElement = this._getSVG();
340
- const serializer = new XMLSerializer();
341
- const svgString = serializer.serializeToString(svgElement);
342
- const blob = new Blob([svgString], { type: 'image/svg+xml' });
343
- const file = new File([blob], 'drawing.svg', { type: 'image/svg+xml' });
344
-
345
- // Creating a FileList
346
- const dataTransfer = new DataTransfer();
347
- dataTransfer.items.add(file);
348
-
349
- return dataTransfer.files;
350
- }
351
-
352
- /**
353
- * @private
354
- * @description Retrieves touch coordinates relative to the canvas.
355
- * @param {TouchEvent} e - The touch event.
356
- * @returns {{x: number, y: number}} An object containing the x and y coordinates.
357
- */
358
- _getCanvasTouchPointer(e) {
359
- const { touches } = e;
360
- const rect = this.canvas.getBoundingClientRect();
361
- const x = touches[0].clientX - rect.left;
362
- const y = touches[0].clientY - rect.top;
363
- return { x, y };
364
- }
365
-
366
- /**
367
- * @private
368
- * @description Activates either block or inline format mode for inserted drawings.
369
- * @param {boolean} isInline - Whether the drawing should be inserted as an inline element.
370
- */
371
- _activeAsInline(isInline) {
372
- if (isInline) {
373
- dom.utils.addClass(this.asInline, 'on');
374
- dom.utils.removeClass(this.asBlock, 'on');
375
- this.as = 'inline';
376
- } else {
377
- dom.utils.addClass(this.asBlock, 'on');
378
- dom.utils.removeClass(this.asInline, 'on');
379
- this.as = 'block';
380
- }
381
- }
382
-
383
- /**
384
- * @param {MouseEvent} e - Event object
385
- */
386
- #OnCanvasMouseDown(e) {
387
- e.preventDefault();
388
- this.isDrawing = true;
389
- this.points.push([e.offsetX, e.offsetY]);
390
- this._draw();
391
- }
392
-
393
- /**
394
- * @param {MouseEvent} e - Event object
395
- */
396
- #OnCanvasMouseMove(e) {
397
- e.preventDefault();
398
- if (!this.isDrawing) return;
399
- this.points.push([e.offsetX, e.offsetY]);
400
- this._draw();
401
- }
402
-
403
- /**
404
- * @param {TouchEvent} e - Event object
405
- */
406
- #OnCanvasTouchStart(e) {
407
- e.preventDefault();
408
- const { x, y } = this._getCanvasTouchPointer(e);
409
- this.isDrawing = true;
410
- this.points.push([x, y]);
411
- this._draw();
412
- }
413
-
414
- /**
415
- * @param {TouchEvent} e - Event object
416
- */
417
- #OnCanvasTouchMove(e) {
418
- e.preventDefault();
419
- const { x, y } = this._getCanvasTouchPointer(e);
420
- if (!this.isDrawing) return;
421
- this.points.push([x, y]);
422
- this._draw();
423
- }
424
-
425
- #OnCanvasMouseUp() {
426
- this.isDrawing = false;
427
- if (this.points.length > 0) {
428
- this.paths.push([...this.points]);
429
- this.points = [];
430
- }
431
- }
432
-
433
- #OnCanvasMouseLeave() {
434
- if (this.isDrawing) {
435
- this.paths.push([...this.points]);
436
- if (!this.pluginOptions.lineReconnect) {
437
- this.points = [];
438
- this.isDrawing = false;
439
- }
440
- }
441
- }
442
-
443
- /**
444
- * @param {MouseEvent} e - Event object
445
- */
446
- #OnCanvasMouseEnter(e) {
447
- if (e.buttons === 1) {
448
- this.isDrawing = true;
449
- if (!this.pluginOptions.lineReconnect) {
450
- this.points.push([e.offsetX, e.offsetY]);
451
- } else {
452
- const lastPath = this.paths[this.paths.length - 1];
453
- const lastPoint = lastPath[lastPath.length - 1];
454
- this.points.push([lastPoint[0], lastPoint[1]]);
455
- this.points.push([e.offsetX, e.offsetY]);
456
- }
457
- this._draw();
458
- }
459
- }
460
-
461
- #OnRemove() {
462
- this._clearCanvas();
463
- }
464
-
465
- /**
466
- * @param {MouseEvent} e - Event object
467
- */
468
- #OnClickAsButton(e) {
469
- this._activeAsInline(dom.query.getEventTarget(e).getAttribute('data-command') === 'asInline');
470
- }
471
- }
472
-
473
- function CreateHTML_modal({ lang, icons, pluginOptions }) {
474
- const { width, height, maxWidth, maxHeight, minWidth, minHeight } = pluginOptions.formSize;
475
- const html = /*html*/ `
476
- <form>
477
- <div class="se-modal-header">
478
- <button type="button" data-command="close" class="se-btn se-close-btn" title="${lang.close}" aria-label="${lang.close}">
479
- ${icons.cancel}
480
- </button>
481
- <span class="se-modal-title">${lang.drawing_modal_title}</span>
482
- </div>
483
- <div class="se-modal-body" style="width: ${width}; height: ${height}; min-width: ${minWidth}; min-height: ${minHeight};">
484
- <canvas class="se-drawing-canvas" style="width: 100%; height: 100%;"></canvas>
485
- ${pluginOptions.canResize ? '<div class="se-modal-resize-handle-w"></div><div class="se-modal-resize-handle-h"></div><div class="se-modal-resize-handle-c"></div>' : ''}
486
- </div>
487
- <div class="se-modal-body-bottom">
488
- <div class="se-modal-form">
489
- <div class="se-modal-flex-form">
490
- ${
491
- pluginOptions.useFormatType
492
- ? /*html*/ `
493
- <div class="se-modal-flex-group">
494
- <button type="button" class="se-btn se-tooltip" data-command="asBlock" aria-label="${lang.blockStyle}">
495
- ${icons.as_block}
496
- ${dom.utils.createTooltipInner(lang.blockStyle)}
497
- </button>
498
- <button type="button" class="se-btn se-tooltip" data-command="asInline" aria-label="${lang.inlineStyle}">
499
- ${icons.as_inline}
500
- ${dom.utils.createTooltipInner(lang.inlineStyle)}
501
- </button>
502
- </div>`
503
- : ''
504
- }
505
- <div class="se-modal-flex-group">
506
- <button type="button" class="se-btn se-tooltip" data-command="remove" aria-label="${lang.remove}">
507
- ${icons.eraser}
508
- ${dom.utils.createTooltipInner(lang.remove)}
509
- </button>
510
- </div>
511
- </div>
512
- </div>
513
- </div>
514
- <div class="se-modal-footer">
515
- <button type="submit" class="se-btn-primary" title="${lang.submitButton}" aria-label="${lang.submitButton}">
516
- <span>${lang.submitButton}</span>
517
- </button>
518
- </div>
519
- </form>`;
520
-
521
- return dom.utils.createElement(
522
- 'DIV',
523
- {
524
- class: 'se-modal-content se-modal-responsive',
525
- style: `max-width: ${maxWidth}; max-height: ${maxHeight};`
526
- },
527
- html
528
- );
529
- }
530
-
531
- export default Drawing;
1
+ import EditorInjector from '../../editorInjector';
2
+ import { Modal } from '../../modules';
3
+ import { dom, env } from '../../helper';
4
+
5
+ const { _w, isTouchDevice } = env;
6
+
7
+ /**
8
+ * @typedef {Object} DrawingPluginOptions
9
+ * @property {string} [outputFormat="dataurl"] - The output format of the drawing. Options: "dataurl", "svg".
10
+ * @property {boolean} [useFormatType=false] - Whether to enable format type selection (block vs inline).
11
+ * @property {string} [defaultFormatType="block"] - The default format type, either "block" or "inline".
12
+ * @property {boolean} [keepFormatType=false] - Whether to maintain the chosen format type after drawing.
13
+ * @property {number} [lineWidth=5] - The width of the drawing line.
14
+ * @property {boolean} [lineReconnect=false] - Whether to reconnect lines when drawing.
15
+ * @property {CanvasLineCap} [lineCap="round"] - The style of the line cap ("butt", "round", or "square").
16
+ * @property {string} [lineColor=""] - The color of the drawing line.
17
+ * @property {boolean} [canResize=true] - Whether the modal form can be resized.
18
+ * @property {boolean} [maintainRatio=true] - Whether to maintain the aspect ratio when resizing.
19
+ * @property {Object} [formSize={}] - The size configuration for the drawing modal form.
20
+ * @property {string} [formSize.width="750px"] - The width of the modal form.
21
+ * @property {string} [formSize.height="50vh"] - The height of the modal form.
22
+ * @property {string} [formSize.maxWidth=""] - The maximum width of the modal form.
23
+ * @property {string} [formSize.maxHeight=""] - The maximum height of the modal form.
24
+ * @property {string} [formSize.minWidth="150px"] - The minimum width of the modal form.
25
+ * @property {string} [formSize.minHeight="100px"] - The minimum height of the modal form.
26
+ */
27
+
28
+ /**
29
+ * @class
30
+ * @description Drawing modal plugin.
31
+ */
32
+ class Drawing extends EditorInjector {
33
+ static key = 'drawing';
34
+ static type = 'modal';
35
+ static className = '';
36
+
37
+ /**
38
+ * @constructor
39
+ * @param {__se__EditorCore} editor - The root editor instance
40
+ * @param {DrawingPluginOptions} pluginOptions
41
+ */
42
+ constructor(editor, pluginOptions) {
43
+ // plugin basic properties
44
+ super(editor);
45
+ this.title = this.lang.drawing;
46
+ this.icon = 'drawing';
47
+ this.pluginOptions = {
48
+ outputFormat: pluginOptions.outputFormat || 'dataurl', // dataurl, svg
49
+ useFormatType: pluginOptions.useFormatType ?? false,
50
+ defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : 'block',
51
+ keepFormatType: pluginOptions.keepFormatType ?? false,
52
+ lineWidth: pluginOptions.lineWidth || 5,
53
+ lineReconnect: !!pluginOptions.lineReconnect,
54
+ lineCap: ['butt', 'round', 'square'].includes(pluginOptions.lineCap) ? pluginOptions.lineCap : 'round',
55
+ lineColor: pluginOptions.lineColor || '',
56
+ formSize: {
57
+ width: '750px',
58
+ height: '50vh',
59
+ maxWidth: '',
60
+ maxHeight: '',
61
+ minWidth: '150px',
62
+ minHeight: '100px',
63
+ ...pluginOptions.formSize
64
+ },
65
+ canResize: pluginOptions.canResize ?? true,
66
+ maintainRatio: pluginOptions.maintainRatio ?? true
67
+ };
68
+
69
+ // exception
70
+ if (!this.plugins.image) {
71
+ console.warn('[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need either "image" plugin. Please add the "image" plugin.');
72
+ } else if (this.pluginOptions.outputFormat === 'svg' && !this.plugins.image.pluginOptions.uploadUrl) {
73
+ console.warn('[SUNEDITOR.plugins.drawing.warn] The drawing plugin must need the "image" plugin with the "uploadUrl" option. Please add the "image" plugin with the "uploadUrl" option.');
74
+ }
75
+
76
+ // create HTML
77
+ const modalEl = CreateHTML_modal(this);
78
+
79
+ // modules
80
+ this.modal = new Modal(this, modalEl);
81
+
82
+ // members
83
+ this.as = this.pluginOptions.defaultFormatType;
84
+ if (this.pluginOptions.useFormatType) {
85
+ this.asBlock = modalEl.querySelector('[data-command="asBlock"]');
86
+ this.asInline = modalEl.querySelector('[data-command="asInline"]');
87
+ this.eventManager.addEvent([this.asBlock, this.asInline], 'click', this.#OnClickAsButton.bind(this));
88
+ }
89
+
90
+ /**
91
+ * @type {HTMLCanvasElement}
92
+ */
93
+ this.canvas = null;
94
+ this.ctx = null;
95
+ this.isDrawing = false;
96
+ this.points = [];
97
+ this.paths = [];
98
+ this.resizeObserver = null;
99
+ this.__events = {
100
+ touchstart: this.#OnCanvasTouchStart.bind(this),
101
+ touchmove: this.#OnCanvasTouchMove.bind(this),
102
+ mousedown: this.#OnCanvasMouseDown.bind(this),
103
+ mousemove: this.#OnCanvasMouseMove.bind(this),
104
+ mouseup: this.#OnCanvasMouseUp.bind(this),
105
+ mouseleave: this.#OnCanvasMouseLeave.bind(this),
106
+ mouseenter: this.#OnCanvasMouseEnter.bind(this)
107
+ };
108
+ this.__eventsRegister = {
109
+ touchstart: null,
110
+ touchmove: null,
111
+ mousedown: null,
112
+ mousemove: null,
113
+ mouseup: null,
114
+ mouseleave: null,
115
+ mouseenter: null
116
+ };
117
+ this.__eventNameMap = {
118
+ mousedown: isTouchDevice ? 'touchstart' : 'mousedown',
119
+ mousemove: isTouchDevice ? 'touchmove' : 'mousemove',
120
+ mouseup: isTouchDevice ? 'touchend' : 'mouseup',
121
+ mouseleave: 'mouseleave',
122
+ mouseenter: 'mouseenter'
123
+ };
124
+
125
+ // init
126
+ this.eventManager.addEvent(modalEl.querySelector('[data-command="remove"]'), 'click', this.#OnRemove.bind(this));
127
+ }
128
+
129
+ /**
130
+ * @editorMethod Modules.Modal
131
+ * @description Executes the method that is called when a "Modal" module's is opened.
132
+ */
133
+ open() {
134
+ if (this.pluginOptions.useFormatType) {
135
+ this._activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
136
+ }
137
+ this.modal.open();
138
+ this._initDrawing();
139
+ }
140
+
141
+ /**
142
+ * @editorMethod Modules.Modal
143
+ * @description Executes the method that is called when a plugin's "modal" is closed.
144
+ */
145
+ off() {
146
+ this._destroyDrawing();
147
+ }
148
+
149
+ /**
150
+ * @editorMethod Modules.Modal
151
+ * @description This function is called when a form within a modal window is "submit".
152
+ * @returns {boolean} Success or failure
153
+ */
154
+ modalAction() {
155
+ if (this.pluginOptions.outputFormat === 'svg') {
156
+ const files = this._getSVGFileList();
157
+ this.plugins.image.init();
158
+ this.plugins.image.submitFile(files);
159
+ } else {
160
+ // dataurl | svg
161
+ const data = this.canvas.toDataURL();
162
+ const file = { name: 'drawing', size: 0 };
163
+ this.plugins.image.init();
164
+ if (this.as !== 'inline') {
165
+ this.plugins.image.create(data, null, 'auto', '', 'none', file, '');
166
+ } else {
167
+ this.plugins.image.createInline(data, null, 'auto', '', 'none', file, '');
168
+ }
169
+ }
170
+
171
+ return true;
172
+ }
173
+
174
+ /**
175
+ * @private
176
+ * @description Initializes the drawing canvas, sets up event listeners, and configures resize handling.
177
+ */
178
+ _initDrawing() {
179
+ const canvas = (this.canvas = this.modal.form.querySelector('.se-drawing-canvas'));
180
+ this.ctx = canvas.getContext('2d');
181
+ canvas.width = canvas.offsetWidth;
182
+ canvas.height = canvas.offsetHeight;
183
+
184
+ this.points = [];
185
+ this.paths = [];
186
+
187
+ this._setCtx();
188
+
189
+ this.__eventsRegister.touchstart = this.eventManager.addEvent(canvas, 'touchstart', this.__events.touchstart, { passive: false, capture: true });
190
+ this.__eventsRegister.touchmove = this.eventManager.addEvent(canvas, 'touchmove', this.__events.touchmove, true);
191
+ this.__eventsRegister.mousedown = this.eventManager.addEvent(canvas, 'mousedown', this.__events.mousedown, { passive: false, capture: true });
192
+ this.__eventsRegister.mousemove = this.eventManager.addEvent(canvas, 'mousemove', this.__events.mousemove, true);
193
+ this.__eventsRegister.mouseup = this.eventManager.addEvent(canvas, 'mouseup', this.__events.mouseup, true);
194
+ this.__eventsRegister.mouseleave = this.eventManager.addEvent(canvas, 'mouseleave', this.__events.mouseleave);
195
+ this.__eventsRegister.mouseenter = this.eventManager.addEvent(canvas, 'mouseenter', this.__events.mouseenter);
196
+
197
+ if (this.resizeObserver) {
198
+ this.resizeObserver.disconnect();
199
+ this.resizeObserver = null;
200
+ }
201
+
202
+ if (env.isResizeObserverSupported) {
203
+ this.resizeObserver = new ResizeObserver(() => {
204
+ const prevWidth = canvas.width;
205
+ const prevHeight = canvas.height;
206
+ const newWidth = canvas.offsetWidth;
207
+ const newHeight = canvas.offsetHeight;
208
+ canvas.width = newWidth;
209
+ canvas.height = newHeight;
210
+ if (prevWidth !== canvas.width || prevHeight !== canvas.height) {
211
+ if (this.pluginOptions.maintainRatio) this._adjustPathsToNewDimensions(prevWidth, prevHeight, newWidth, newHeight);
212
+ this._drawAll();
213
+ }
214
+ });
215
+
216
+ this.resizeObserver.observe(canvas);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * @private
222
+ * @description Destroys the drawing canvas, removes event listeners, and clears stored drawing data.
223
+ */
224
+ _destroyDrawing() {
225
+ if (this.resizeObserver) {
226
+ this.resizeObserver.disconnect();
227
+ this.resizeObserver = null;
228
+ }
229
+
230
+ if (this.canvas) {
231
+ this.eventManager.removeEvent(this.__eventsRegister.mousedown);
232
+ this.eventManager.removeEvent(this.__eventsRegister.mousemove);
233
+ this.eventManager.removeEvent(this.__eventsRegister.mouseup);
234
+ this.eventManager.removeEvent(this.__eventsRegister.mouseleave);
235
+ this.eventManager.removeEvent(this.__eventsRegister.mouseenter);
236
+ }
237
+
238
+ this.canvas = null;
239
+ this.ctx = null;
240
+ this.points = [];
241
+ this.paths = [];
242
+ this.isDrawing = false;
243
+ }
244
+
245
+ /**
246
+ * @private
247
+ * @description Configures the drawing context (canvas settings like line width, color, etc.).
248
+ */
249
+ _setCtx() {
250
+ this.ctx.lineWidth = this.pluginOptions.lineWidth;
251
+ this.ctx.lineCap = this.pluginOptions.lineCap;
252
+ this.ctx.strokeStyle = this.pluginOptions.lineColor || _w.getComputedStyle(this.carrierWrapper).color;
253
+ }
254
+
255
+ /**
256
+ * @private
257
+ * @description Draws the current stroke based on collected points.
258
+ */
259
+ _draw() {
260
+ this._setCtx();
261
+ this.ctx.beginPath();
262
+ this.points.forEach(([x, y], i) => {
263
+ if (i === 0) {
264
+ this.ctx.moveTo(x, y);
265
+ } else {
266
+ this.ctx.lineTo(x, y);
267
+ }
268
+ });
269
+ this.ctx.stroke();
270
+ }
271
+
272
+ /**
273
+ * @private
274
+ * @description Redraws all stored paths onto the canvas.
275
+ */
276
+ _drawAll() {
277
+ this._setCtx();
278
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
279
+ this.paths.forEach((path) => {
280
+ this.points = path;
281
+ this._draw();
282
+ });
283
+ this.points = [];
284
+ }
285
+
286
+ /**
287
+ * @private
288
+ * @description Adjusts all stored paths to fit new canvas dimensions after a resize event.
289
+ * @param {number} prevWidth - The previous width of the canvas.
290
+ * @param {number} prevHeight - The previous height of the canvas.
291
+ * @param {number} newWidth - The new width of the canvas.
292
+ * @param {number} newHeight - The new height of the canvas.
293
+ */
294
+ _adjustPathsToNewDimensions(prevWidth, prevHeight, newWidth, newHeight) {
295
+ const xRatio = newWidth / prevWidth;
296
+ const yRatio = newHeight / prevHeight;
297
+
298
+ this.paths = this.paths.map((path) => path.map(([x, y]) => [x * xRatio, y * yRatio]));
299
+ }
300
+
301
+ /**
302
+ * @private
303
+ * @description Clears the canvas and resets stored drawing paths.
304
+ */
305
+ _clearCanvas() {
306
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
307
+ this.points = [];
308
+ this.paths = [];
309
+ }
310
+
311
+ /**
312
+ * @private
313
+ * @description Generates an SVG representation of the drawn content.
314
+ * @returns {*} The generated SVG element.
315
+ */
316
+ _getSVG() {
317
+ const svgNS = 'http://www.w3.org/2000/svg';
318
+ const svg = document.createElementNS(svgNS, 'svg');
319
+ svg.setAttribute('width', this.canvas.width + '');
320
+ svg.setAttribute('height', this.canvas.height + '');
321
+ svg.setAttribute('viewBox', `0 0 ${this.canvas.width} ${this.canvas.height}`);
322
+ svg.setAttribute('xmlns', svgNS);
323
+
324
+ this.paths.forEach((path) => {
325
+ const pathData = path.reduce((acc, [x, y], i) => {
326
+ return acc + (i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`);
327
+ }, '');
328
+ const svgPath = document.createElementNS(svgNS, 'path');
329
+ svgPath.setAttribute('d', pathData);
330
+ svgPath.setAttribute('fill', 'none');
331
+ svgPath.setAttribute('stroke', String(this.ctx.strokeStyle));
332
+ svgPath.setAttribute('stroke-width', this.ctx.lineWidth + '');
333
+ svg.appendChild(svgPath);
334
+ });
335
+
336
+ return svg;
337
+ }
338
+
339
+ /**
340
+ * @private
341
+ * @description Converts the SVG element into a downloadable file.
342
+ * @returns {FileList} A FileList containing the generated SVG file.
343
+ */
344
+ _getSVGFileList() {
345
+ const svgElement = this._getSVG();
346
+ const serializer = new XMLSerializer();
347
+ const svgString = serializer.serializeToString(svgElement);
348
+ const blob = new Blob([svgString], { type: 'image/svg+xml' });
349
+ const file = new File([blob], 'drawing.svg', { type: 'image/svg+xml' });
350
+
351
+ // Creating a FileList
352
+ const dataTransfer = new DataTransfer();
353
+ dataTransfer.items.add(file);
354
+
355
+ return dataTransfer.files;
356
+ }
357
+
358
+ /**
359
+ * @private
360
+ * @description Retrieves touch coordinates relative to the canvas.
361
+ * @param {TouchEvent} e - The touch event.
362
+ * @returns {{x: number, y: number}} An object containing the x and y coordinates.
363
+ */
364
+ _getCanvasTouchPointer(e) {
365
+ const { touches } = e;
366
+ const rect = this.canvas.getBoundingClientRect();
367
+ const x = touches[0].clientX - rect.left;
368
+ const y = touches[0].clientY - rect.top;
369
+ return { x, y };
370
+ }
371
+
372
+ /**
373
+ * @private
374
+ * @description Activates either block or inline format mode for inserted drawings.
375
+ * @param {boolean} isInline - Whether the drawing should be inserted as an inline element.
376
+ */
377
+ _activeAsInline(isInline) {
378
+ if (isInline) {
379
+ dom.utils.addClass(this.asInline, 'on');
380
+ dom.utils.removeClass(this.asBlock, 'on');
381
+ this.as = 'inline';
382
+ } else {
383
+ dom.utils.addClass(this.asBlock, 'on');
384
+ dom.utils.removeClass(this.asInline, 'on');
385
+ this.as = 'block';
386
+ }
387
+ }
388
+
389
+ /**
390
+ * @param {MouseEvent} e - Event object
391
+ */
392
+ #OnCanvasMouseDown(e) {
393
+ e.preventDefault();
394
+ this.isDrawing = true;
395
+ this.points.push([e.offsetX, e.offsetY]);
396
+ this._draw();
397
+ }
398
+
399
+ /**
400
+ * @param {MouseEvent} e - Event object
401
+ */
402
+ #OnCanvasMouseMove(e) {
403
+ e.preventDefault();
404
+ if (!this.isDrawing) return;
405
+ this.points.push([e.offsetX, e.offsetY]);
406
+ this._draw();
407
+ }
408
+
409
+ /**
410
+ * @param {TouchEvent} e - Event object
411
+ */
412
+ #OnCanvasTouchStart(e) {
413
+ e.preventDefault();
414
+ const { x, y } = this._getCanvasTouchPointer(e);
415
+ this.isDrawing = true;
416
+ this.points.push([x, y]);
417
+ this._draw();
418
+ }
419
+
420
+ /**
421
+ * @param {TouchEvent} e - Event object
422
+ */
423
+ #OnCanvasTouchMove(e) {
424
+ e.preventDefault();
425
+ const { x, y } = this._getCanvasTouchPointer(e);
426
+ if (!this.isDrawing) return;
427
+ this.points.push([x, y]);
428
+ this._draw();
429
+ }
430
+
431
+ #OnCanvasMouseUp() {
432
+ this.isDrawing = false;
433
+ if (this.points.length > 0) {
434
+ this.paths.push([...this.points]);
435
+ this.points = [];
436
+ }
437
+ }
438
+
439
+ #OnCanvasMouseLeave() {
440
+ if (this.isDrawing) {
441
+ this.paths.push([...this.points]);
442
+ if (!this.pluginOptions.lineReconnect) {
443
+ this.points = [];
444
+ this.isDrawing = false;
445
+ }
446
+ }
447
+ }
448
+
449
+ /**
450
+ * @param {MouseEvent} e - Event object
451
+ */
452
+ #OnCanvasMouseEnter(e) {
453
+ if (e.buttons === 1) {
454
+ this.isDrawing = true;
455
+ if (!this.pluginOptions.lineReconnect) {
456
+ this.points.push([e.offsetX, e.offsetY]);
457
+ } else {
458
+ const lastPath = this.paths[this.paths.length - 1];
459
+ const lastPoint = lastPath[lastPath.length - 1];
460
+ this.points.push([lastPoint[0], lastPoint[1]]);
461
+ this.points.push([e.offsetX, e.offsetY]);
462
+ }
463
+ this._draw();
464
+ }
465
+ }
466
+
467
+ #OnRemove() {
468
+ this._clearCanvas();
469
+ }
470
+
471
+ /**
472
+ * @param {MouseEvent} e - Event object
473
+ */
474
+ #OnClickAsButton(e) {
475
+ this._activeAsInline(dom.query.getEventTarget(e).getAttribute('data-command') === 'asInline');
476
+ }
477
+ }
478
+
479
+ function CreateHTML_modal({ lang, icons, pluginOptions }) {
480
+ const { width, height, maxWidth, maxHeight, minWidth, minHeight } = pluginOptions.formSize;
481
+ const html = /*html*/ `
482
+ <form>
483
+ <div class="se-modal-header">
484
+ <button type="button" data-command="close" class="se-btn se-close-btn" title="${lang.close}" aria-label="${lang.close}">
485
+ ${icons.cancel}
486
+ </button>
487
+ <span class="se-modal-title">${lang.drawing_modal_title}</span>
488
+ </div>
489
+ <div class="se-modal-body" style="width: ${width}; height: ${height}; min-width: ${minWidth}; min-height: ${minHeight};">
490
+ <canvas class="se-drawing-canvas" style="width: 100%; height: 100%;"></canvas>
491
+ ${pluginOptions.canResize ? '<div class="se-modal-resize-handle-w"></div><div class="se-modal-resize-handle-h"></div><div class="se-modal-resize-handle-c"></div>' : ''}
492
+ </div>
493
+ <div class="se-modal-body-bottom">
494
+ <div class="se-modal-form">
495
+ <div class="se-modal-flex-form">
496
+ ${
497
+ pluginOptions.useFormatType
498
+ ? /*html*/ `
499
+ <div class="se-modal-flex-group">
500
+ <button type="button" class="se-btn se-tooltip" data-command="asBlock" aria-label="${lang.blockStyle}">
501
+ ${icons.as_block}
502
+ ${dom.utils.createTooltipInner(lang.blockStyle)}
503
+ </button>
504
+ <button type="button" class="se-btn se-tooltip" data-command="asInline" aria-label="${lang.inlineStyle}">
505
+ ${icons.as_inline}
506
+ ${dom.utils.createTooltipInner(lang.inlineStyle)}
507
+ </button>
508
+ </div>`
509
+ : ''
510
+ }
511
+ <div class="se-modal-flex-group">
512
+ <button type="button" class="se-btn se-tooltip" data-command="remove" aria-label="${lang.remove}">
513
+ ${icons.eraser}
514
+ ${dom.utils.createTooltipInner(lang.remove)}
515
+ </button>
516
+ </div>
517
+ </div>
518
+ </div>
519
+ </div>
520
+ <div class="se-modal-footer">
521
+ <button type="submit" class="se-btn-primary" title="${lang.submitButton}" aria-label="${lang.submitButton}">
522
+ <span>${lang.submitButton}</span>
523
+ </button>
524
+ </div>
525
+ </form>`;
526
+
527
+ return dom.utils.createElement(
528
+ 'DIV',
529
+ {
530
+ class: 'se-modal-content se-modal-responsive',
531
+ style: `max-width: ${maxWidth}; max-height: ${maxHeight};`
532
+ },
533
+ html
534
+ );
535
+ }
536
+
537
+ export default Drawing;