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,1708 +1,1810 @@
1
- import { env, converter, dom, numbers } from '../helper';
2
- import Constructor, { InitOptions, UpdateButton, CreateShortcuts, CreateStatusbar, OPTION_FIXED_FLAG } from './section/constructor';
3
- import { UpdateStatusbarContext } from './section/context';
4
- import { BASIC_COMMANDS, ACTIVE_EVENT_COMMANDS, SELECT_ALL, DIR_BTN_ACTIVE, SAVE, COPY_FORMAT, FONT_STYLE, PAGE_BREAK } from './section/actives';
5
- import History from './base/history';
6
- import EventManager from './base/eventManager';
7
- import Events from '../events';
8
- import DocumentType from './section/documentType';
9
-
10
- // class injector
11
- import ClassInjector from '../editorInjector/_classes';
12
-
13
- // classes
14
- import Char from './class/char';
15
- import Component from './class/component';
16
- import Format from './class/format';
17
- import HTML from './class/html';
18
- import Menu from './class/menu';
19
- import NodeTransform from './class/nodeTransform';
20
- import Offset from './class/offset';
21
- import Selection_ from './class/selection';
22
- import Shortcuts from './class/shortcuts';
23
- import Toolbar from './class/toolbar';
24
- import UI from './class/ui';
25
- import Viewer from './class/viewer';
26
-
27
- const COMMAND_BUTTONS = '.se-menu-list .se-toolbar-btn[data-command]';
28
- const DISABLE_BUTTONS_CODEVIEW = `${COMMAND_BUTTONS}:not([class~="se-code-view-enabled"]):not([data-type="MORE"])`;
29
- const DISABLE_BUTTONS_CONTROLLER = `${COMMAND_BUTTONS}:not([class~="se-component-enabled"]):not([data-type="MORE"])`;
30
-
31
- /**
32
- * @typedef {import('./section/constructor').EditorInitOptions} EditorInitOptions_editor
33
- */
34
-
35
- /**
36
- * @typedef {import('./section/constructor').EditorFrameOptions} EditorFrameOptions_editor
37
- */
38
-
39
- /**
40
- * @typedef {import('../modules/Controller').ControllerInfo} ControllerInfo_editor
41
- */
42
-
43
- /**
44
- * @constructor
45
- * @description SunEditor constructor function.
46
- * @param {Array<{target: Element, key: *, options: EditorFrameOptions_editor}>} multiTargets Target element
47
- * @param {EditorInitOptions_editor} options options
48
- */
49
- function Editor(multiTargets, options) {
50
- const _d = multiTargets[0].target.ownerDocument || env._d;
51
- const _w = _d.defaultView || env._w;
52
- const product = Constructor(multiTargets, options);
53
-
54
- /**
55
- * @description Frame root key array
56
- * @type {Array<*>}
57
- */
58
- this.rootKeys = product.rootKeys;
59
-
60
- /**
61
- * @description Frame root map
62
- * @type {Map<*, __se__FrameContext>}
63
- */
64
- this.frameRoots = product.frameRoots;
65
-
66
- /**
67
- * @description Editor context object
68
- * @type {__se__Context}
69
- */
70
- this.context = product.context;
71
-
72
- /**
73
- * @description Current focusing frame context
74
- * @type {__se__FrameContext}
75
- */
76
- this.frameContext = new Map();
77
-
78
- /**
79
- * @description Current focusing frame context options
80
- * @type {__se__FrameOptions}
81
- */
82
- this.frameOptions = new Map();
83
-
84
- /**
85
- * @description Document object
86
- * @type {Document}
87
- */
88
- this._d = _d;
89
-
90
- /**
91
- * @description Window object
92
- * @type {Window}
93
- */
94
- this._w = _w;
95
-
96
- /**
97
- * @description Controllers carrier
98
- * @type {HTMLElement}
99
- */
100
- this.carrierWrapper = product.carrierWrapper;
101
-
102
- /**
103
- * @description Editor options
104
- * @type {Map<string, *>}
105
- */
106
- this.options = product.options;
107
-
108
- /**
109
- * @description Plugins
110
- * @type {Object<string, *>}
111
- */
112
- this.plugins = product.plugins || {};
113
-
114
- /**
115
- * @description Events object, call by triggerEvent function
116
- * @type {Object<string, *>}
117
- */
118
- this.events = null;
119
-
120
- /**
121
- * @description Call the event function by injecting self: this.
122
- * @type {(eventName: string, ...args: *) => Promise<*>}
123
- */
124
- this.triggerEvent = null;
125
-
126
- /**
127
- * @description Default icons object
128
- * @type {Object<string, string>}
129
- */
130
- this.icons = product.icons;
131
-
132
- /**
133
- * @description loaded language
134
- * @type {Object<string, *>}
135
- */
136
- this.lang = product.lang;
137
-
138
- /**
139
- * @description Variables used internally in editor operation
140
- * @type {__se__EditorStatus}
141
- */
142
- this.status = {
143
- hasFocus: false,
144
- tabSize: 4,
145
- indentSize: 25,
146
- codeIndentSize: 2,
147
- currentNodes: [],
148
- currentNodesMap: [],
149
- onSelected: false,
150
- rootKey: product.rootId,
151
- _range: null,
152
- _onMousedown: false
153
- };
154
-
155
- /**
156
- * @description Is classic mode?
157
- * @type {boolean}
158
- */
159
- this.isClassic = false;
160
-
161
- /**
162
- * @description Is inline mode?
163
- * @type {boolean}
164
- */
165
- this.isInline = false;
166
-
167
- /**
168
- * @description Is balloon|balloon-always mode?
169
- * @type {boolean}
170
- */
171
- this.isBalloon = false;
172
-
173
- /**
174
- * @description Is balloon-always mode?
175
- * @type {boolean}
176
- */
177
- this.isBalloonAlways = false;
178
-
179
- /**
180
- * @description Is subToolbar balloon|balloon-always mode?
181
- * @type {boolean}
182
- */
183
- this.isSubBalloon = false;
184
-
185
- /**
186
- * @description Is subToolbar balloon-always mode?
187
- * @type {boolean}
188
- */
189
- this.isSubBalloonAlways = false;
190
-
191
- /**
192
- * @description All command buttons map
193
- * @type {Map<string, HTMLElement>}
194
- */
195
- this.allCommandButtons = new Map();
196
-
197
- /**
198
- * @description All command buttons map
199
- * @type {Map<string, HTMLElement>}
200
- */
201
- this.subAllCommandButtons = new Map();
202
-
203
- /**
204
- * @description Shoutcuts key map
205
- * @type {Map<string, *>}
206
- */
207
- this.shortcutsKeyMap = new Map();
208
-
209
- /**
210
- * @description Shoutcuts reverse key array
211
- * - An array of key codes generated with the reverseButtons option, used to reverse the action for a specific key combination.
212
- * @type {Array<string>}
213
- */
214
- this.reverseKeys = [];
215
-
216
- /**
217
- * @description A map with the plugin's buttons having an "active" method and the default command buttons with an "active" action.
218
- * - Each button is contained in an array.
219
- * @type {Map<string, Array<HTMLButtonElement>>}
220
- */
221
- this.commandTargets = new Map();
222
-
223
- /**
224
- * @description Plugins array with "active" method.
225
- * - "activeCommands" runs the "add" method when creating the editor.
226
- * @type {Array<string>}
227
- */
228
- this.activeCommands = null;
229
-
230
- /**
231
- * @description The selection node (selection.getNode()) to which the effect was last applied
232
- * @type {Node|null}
233
- */
234
- this.effectNode = null;
235
-
236
- /**
237
- * @description Currently open "Modal" instance
238
- * @type {*}
239
- */
240
- this.opendModal = null;
241
-
242
- /**
243
- * @description Currently open "Controller" info array
244
- * @type {Array<ControllerInfo_editor>}
245
- */
246
- this.opendControllers = [];
247
-
248
- /**
249
- * @description Currently open "Controller" caller plugin name
250
- */
251
- this.currentControllerName = '';
252
-
253
- /**
254
- * @description Currently open "Browser" instance
255
- * @type {*}
256
- */
257
- this.opendBrowser = null;
258
-
259
- /**
260
- * @description Whether "SelectMenu" is open
261
- * @type {boolean}
262
- */
263
- this.selectMenuOn = false;
264
-
265
- // ------ base ------
266
- /** @description History class instance @type {ReturnType<typeof import('./base/history').default>} */
267
- this.history = null;
268
- /** @description EventManager class instance @type {import('./base/eventManager').default} */
269
- this.eventManager = null;
270
-
271
- // ------ class ------
272
- /** @description Toolbar class instance @type {import('./class/toolbar').default} */
273
- this.toolbar = null;
274
- /** @description Sub-Toolbar class instance @type {import('./class/toolbar').default|null} */
275
- this.subToolbar = null;
276
- /** @description Char class instance @type {import('./class/char').default} */
277
- this.char = null;
278
- /** @description Component class instance @type {import('./class/component').default} */
279
- this.component = null;
280
- /** @description Format class instance @type {import('./class/format').default} */
281
- this.format = null;
282
- /** @description HTML class instance @type {import('./class/html').default} */
283
- this.html = null;
284
- /** @description Menu class instance @type {import('./class/menu').default} */
285
- this.menu = null;
286
- /** @description NodeTransform class instance @type {import('./class/nodeTransform').default} */
287
- this.nodeTransform = null;
288
- /** @description Offset class instance @type {import('./class/offset').default} */
289
- this.offset = null;
290
- /** @description Selection class instance @type {import('./class/selection').default} */
291
- this.selection = null;
292
- /** @description Shortcuts class instance @type {import('./class/shortcuts').default} */
293
- this.shortcuts = null;
294
- /** @description UI class instance @type {import('./class/ui').default} */
295
- this.ui = null;
296
- /** @description Viewer class instance @type {import('./class/viewer').default} */
297
- this.viewer = null;
298
-
299
- // ------------------------------------------------------- private properties -------------------------------------------------------
300
- /**
301
- * @description Line breaker (top)
302
- * @type {HTMLElement}
303
- */
304
- this._lineBreaker_t = null;
305
-
306
- /**
307
- * @description Line breaker (bottom)
308
- * @type {HTMLElement}
309
- */
310
- this._lineBreaker_b = null;
311
-
312
- /**
313
- * @description Closest ShadowRoot to editor if found
314
- * @type {ShadowRoot}
315
- */
316
- this._shadowRoot = null;
317
-
318
- /**
319
- * @description Plugin call event map
320
- * @type {Map<string, Array<((...args: *) => *) & { index: number }>>}
321
- */
322
- this._onPluginEvents = null;
323
-
324
- /**
325
- * @description Copy format info
326
- * - eventManager.__cacheStyleNodes copied
327
- * @type {Array<Node>|null}
328
- */
329
- this._onCopyFormatInfo = null;
330
-
331
- /**
332
- * @description Copy format init method
333
- * @type {(...args: *) => *|null}
334
- */
335
- this._onCopyFormatInitMethod = null;
336
-
337
- /**
338
- * @description Controller target's frame div (editor.frameContext.get('topArea'))
339
- * @type {HTMLElement|null}
340
- */
341
- this._controllerTargetContext = null;
342
-
343
- /**
344
- * @description List of buttons that are disabled when "controller" is opened
345
- * @type {Array<HTMLButtonElement|HTMLInputElement>}
346
- */
347
- this._controllerOnDisabledButtons = [];
348
-
349
- /**
350
- * @description List of buttons that are disabled when "codeView" mode opened
351
- * @type {Array<HTMLButtonElement|HTMLInputElement>}
352
- */
353
- this._codeViewDisabledButtons = [];
354
-
355
- /**
356
- * @description List of buttons to run plugins in the toolbar
357
- * @type {Array<HTMLElement>}
358
- */
359
- this._pluginCallButtons = product.pluginCallButtons;
360
-
361
- /**
362
- * @description List of buttons to run plugins in the Sub-Toolbar
363
- * @type {Array<HTMLElement>}
364
- */
365
- this._pluginCallButtons_sub = product.pluginCallButtons_sub;
366
-
367
- /**
368
- * @description Responsive Toolbar Button Structure array
369
- * @type {Array<*>}
370
- */
371
- this._responsiveButtons = product.responsiveButtons;
372
-
373
- /**
374
- * @description Responsive Sub-Toolbar Button Structure array
375
- * @type {Array<*>}
376
- */
377
- this._responsiveButtons_sub = product.responsiveButtons_sub;
378
-
379
- /**
380
- * @description Variable that controls the "blur" event in the editor of inline or balloon mode when the focus is moved to dropdown
381
- * @type {boolean}
382
- */
383
- this._notHideToolbar = false;
384
-
385
- /**
386
- * @description Variables for controlling focus and blur events
387
- * @type {boolean}
388
- */
389
- this._preventBlur = false;
390
-
391
- /**
392
- * @description Variables for controlling selection change events
393
- */
394
- this._preventSelection = false;
395
-
396
- /**
397
- * @description If true, initialize all indexes of image, video information
398
- * @type {boolean}
399
- */
400
- this._componentsInfoInit = true;
401
-
402
- /**
403
- * @description If true, reset all indexes of image, video information
404
- * @type {boolean}
405
- */
406
- this._componentsInfoReset = false;
407
-
408
- /**
409
- * @description plugin retainFormat info Map()
410
- * @type {Map<string, ((...args: *) => *)>}
411
- */
412
- this._MELInfo = null;
413
-
414
- /**
415
- * @description Properties for managing files in the "FileManager" module
416
- * @type {Array<*>}
417
- */
418
- this._fileInfoPluginsCheck = null;
419
-
420
- /**
421
- * @description Properties for managing files in the "FileManager" module
422
- * @type {Array<*>}
423
- */
424
- this._fileInfoPluginsReset = null;
425
-
426
- /**
427
- * @description Variables for file component management
428
- * @type {Object<string, *>}
429
- */
430
- this._fileManager = {
431
- tags: null,
432
- regExp: null,
433
- pluginRegExp: null,
434
- pluginMap: null
435
- };
436
-
437
- /**
438
- * @description Variables for managing the components
439
- * @type {Array<*>}
440
- */
441
- this._componentManager = [];
442
-
443
- /**
444
- * @description Current Figure container.
445
- * @type {HTMLElement|null}
446
- */
447
- this._figureContainer = null;
448
-
449
- /**
450
- * @description Origin options
451
- * @type {EditorInitOptions_editor}
452
- */
453
- this._originOptions = options;
454
-
455
- /** ----- Create editor ------------------------------------------------------------ */
456
- this.__Create(options);
457
- }
458
-
459
- Editor.prototype = {
460
- /**
461
- * @description If the plugin is not added, add the plugin and call the 'add' function.
462
- * - If the plugin is added call callBack function.
463
- * @param {string} pluginName The name of the plugin to call
464
- * @param {?Array<HTMLElement>} targets Plugin target button (This is not necessary if you have a button list when creating the editor)
465
- * @param {?Object<string, *>} pluginOptions Plugin's options
466
- */
467
- registerPlugin(pluginName, targets, pluginOptions) {
468
- let plugin = this.plugins[pluginName];
469
- if (!plugin) {
470
- throw Error(`[SUNEDITOR.registerPlugin.fail] The called plugin does not exist or is in an invalid format. (pluginName: "${pluginName}")`);
471
- } else if (typeof this.plugins[pluginName] === 'function') {
472
- plugin = this.plugins[pluginName] = new this.plugins[pluginName](this, pluginOptions || {});
473
- if (typeof plugin.init === 'function') plugin.init();
474
- }
475
-
476
- if (targets) {
477
- for (let i = 0, len = targets.length; i < len; i++) {
478
- UpdateButton(targets[i], plugin, this.icons, this.lang);
479
- }
480
-
481
- if (!this.activeCommands.includes(pluginName) && typeof this.plugins[pluginName].active === 'function') {
482
- this.activeCommands.push(pluginName);
483
- }
484
- }
485
- },
486
-
487
- /**
488
- * @description Run plugin calls and basic commands.
489
- * @param {string} command Command string
490
- * @param {string} type Display type string ('command', 'dropdown', 'modal', 'container')
491
- * @param {?Node=} button The element of command button
492
- */
493
- run(command, type, button) {
494
- if (type) {
495
- if (/more/i.test(type)) {
496
- const toolbar = dom.query.getParentElement(button, '.se-toolbar');
497
- const toolInst = dom.utils.hasClass(toolbar, 'se-toolbar-sub') ? this.subToolbar : this.toolbar;
498
- if (button !== toolInst.currentMoreLayerActiveButton) {
499
- const layer = toolbar.querySelector('.' + command);
500
- if (layer) {
501
- toolInst._moreLayerOn(button, layer);
502
- toolInst._showBalloon();
503
- toolInst._showInline();
504
- }
505
- dom.utils.addClass(button, 'on');
506
- } else if (toolInst.currentMoreLayerActiveButton) {
507
- toolInst._moreLayerOff();
508
- toolInst._showBalloon();
509
- toolInst._showInline();
510
- }
511
-
512
- this.viewer._resetFullScreenHeight();
513
- return;
514
- }
515
-
516
- if (/container/.test(type) && (this.menu.targetMap[command] === null || button !== this.menu.currentContainerActiveButton)) {
517
- this.menu.containerOn(button);
518
- return;
519
- }
520
-
521
- if (this.frameContext.get('isReadOnly') && dom.utils.arrayIncludes(this._controllerOnDisabledButtons, button)) return;
522
- if (/dropdown/.test(type) && (this.menu.targetMap[command] === null || button !== this.menu.currentDropdownActiveButton)) {
523
- this.menu.dropdownOn(button);
524
- return;
525
- } else if (/modal/.test(type)) {
526
- this.plugins[command].open(button);
527
- return;
528
- } else if (/command/.test(type)) {
529
- this.plugins[command].action(button);
530
- } else if (/browser/.test(type)) {
531
- this.plugins[command].open(null);
532
- } else if (/popup/.test(type)) {
533
- this.plugins[command].show();
534
- }
535
- } else if (command) {
536
- this.commandHandler(command, button);
537
- }
538
-
539
- if (/dropdown/.test(type)) {
540
- this.menu.dropdownOff();
541
- } else if (!/command/.test(type)) {
542
- this.menu.dropdownOff();
543
- this.menu.containerOff();
544
- }
545
- },
546
-
547
- /**
548
- * @description Execute default command of command button
549
- * - (selectAll, codeView, fullScreen, indent, outdent, undo, redo, removeFormat, print, preview, showBlocks, save, bold, underline, italic, strike, subscript, superscript, copy, cut, paste)
550
- * @param {string} command Property of command button (data-value)
551
- * @param {?Node=} button Command button
552
- * @returns {Promise<void>}
553
- */
554
- async commandHandler(command, button) {
555
- if (this.frameContext.get('isReadOnly') && !/copy|cut|selectAll|codeView|fullScreen|print|preview|showBlocks/.test(command)) return;
556
-
557
- switch (command) {
558
- case 'selectAll':
559
- SELECT_ALL(this);
560
- break;
561
- case 'copy': {
562
- const range = this.selection.getRange();
563
- if (range.collapsed) break;
564
-
565
- const container = dom.utils.createElement('div', null, range.cloneContents());
566
- await this.html.copy(container.innerHTML);
567
-
568
- break;
569
- }
570
- case 'newDocument':
571
- this.html.set(`<${this.options.get('defaultLine')}><br></${this.options.get('defaultLine')}>`);
572
- this.focus();
573
- this.history.push(false);
574
- break;
575
- case 'codeView':
576
- this.viewer.codeView(!this.frameContext.get('isCodeView'));
577
- break;
578
- case 'fullScreen':
579
- this.viewer.fullScreen(!this.frameContext.get('isFullScreen'));
580
- break;
581
- case 'indent':
582
- this.format.indent();
583
- break;
584
- case 'outdent':
585
- this.format.outdent();
586
- break;
587
- case 'undo':
588
- this.history.undo();
589
- break;
590
- case 'redo':
591
- this.history.redo();
592
- break;
593
- case 'removeFormat':
594
- this.format.removeInlineElement();
595
- this.focus();
596
- break;
597
- case 'print':
598
- this.viewer.print();
599
- break;
600
- case 'preview':
601
- this.viewer.preview();
602
- break;
603
- case 'showBlocks':
604
- this.viewer.showBlocks(!this.frameContext.get('isShowBlocks'));
605
- break;
606
- case 'dir':
607
- this.setDir(this.options.get('_rtl') ? 'ltr' : 'rtl');
608
- break;
609
- case 'dir_ltr':
610
- this.setDir('ltr');
611
- break;
612
- case 'dir_rtl':
613
- this.setDir('rtl');
614
- break;
615
- case 'save':
616
- await SAVE(this);
617
- break;
618
- case 'copyFormat':
619
- COPY_FORMAT(this, button);
620
- break;
621
- case 'pageBreak':
622
- PAGE_BREAK(this);
623
- break;
624
- case 'pageUp':
625
- this.frameContext.get('documentType').pageUp();
626
- break;
627
- case 'pageDown':
628
- this.frameContext.get('documentType').pageDown();
629
- break;
630
- default:
631
- FONT_STYLE(this, command);
632
- }
633
- },
634
-
635
- /**
636
- * @description Execute "editor.run" with command button.
637
- * @param {Node} target Command target
638
- */
639
- runFromTarget(target) {
640
- if (dom.check.isInputElement(target)) return;
641
-
642
- const targetBtn = /** @type {HTMLButtonElement} */ (dom.query.getCommandTarget(target));
643
- if (!targetBtn) return;
644
-
645
- const command = targetBtn.getAttribute('data-command');
646
- const type = targetBtn.getAttribute('data-type');
647
-
648
- if (!command && !type) return;
649
- if (targetBtn.disabled) return;
650
-
651
- this.run(command, type, target);
652
- },
653
-
654
- /**
655
- * @description It is executed by inserting the button of commandTargets as the argument value of the "f" function.
656
- * - "func" is called as long as the button array's length.
657
- * @param {string} cmd data-command
658
- * @param {(...args: *) => *} func Function.
659
- */
660
- applyCommandTargets(cmd, func) {
661
- if (this.commandTargets.has(cmd)) {
662
- this.commandTargets.get(cmd).forEach(func);
663
- }
664
- },
665
-
666
- /**
667
- * @description Execute a function by traversing all root targets.
668
- * @param {(...args: *) => *} f Function
669
- */
670
- applyFrameRoots(f) {
671
- this.frameRoots.forEach(f);
672
- },
673
-
674
- /**
675
- * @description Checks if the content of the editor is empty.
676
- * - Display criteria for "placeholder".
677
- * @param {?__se__FrameContext=} fc Frame context, if not present, currently selected frame context.
678
- * @returns {boolean}
679
- */
680
- isEmpty(fc) {
681
- fc = fc || this.frameContext;
682
- const wysiwyg = fc.get('wysiwyg');
683
- return dom.check.isZeroWidth(wysiwyg.textContent) && !wysiwyg.querySelector(this.options.get('allowedEmptyTags')) && (wysiwyg.innerText.match(/\n/g) || '').length <= 1;
684
- },
685
-
686
- /**
687
- * @description Set direction to "rtl" or "ltr".
688
- * @param {string} dir "rtl" or "ltr"
689
- */
690
- setDir(dir) {
691
- const rtl = dir === 'rtl';
692
- if (this.options.get('_rtl') === rtl) return;
693
-
694
- try {
695
- this.options.set('_rtl', rtl);
696
- this.ui._offCurrentController();
697
-
698
- const fc = this.frameContext;
699
- const plugins = this.plugins;
700
- for (const k in plugins) {
701
- if (typeof plugins[k].setDir === 'function') plugins[k].setDir(dir);
702
- }
703
-
704
- const toolbarWrapper = this.context.get('toolbar._wrapper');
705
- const statusbarWrapper = this.context.get('statusbar._wrapper');
706
- if (rtl) {
707
- this.applyFrameRoots((e) => {
708
- dom.utils.addClass([e.get('topArea'), e.get('wysiwyg'), e.get('documentTypePageMirror')], 'se-rtl');
709
- });
710
- dom.utils.addClass([this.carrierWrapper, toolbarWrapper, statusbarWrapper], 'se-rtl');
711
- } else {
712
- this.applyFrameRoots((e) => {
713
- dom.utils.removeClass([e.get('topArea'), e.get('wysiwyg'), e.get('documentTypePageMirror')], 'se-rtl');
714
- });
715
- dom.utils.removeClass([this.carrierWrapper, toolbarWrapper, statusbarWrapper], 'se-rtl');
716
- }
717
-
718
- const lineNodes = dom.query.getListChildren(fc.get('wysiwyg'), (current) => {
719
- return this.format.isLine(current) && !!(current.style.marginRight || current.style.marginLeft || current.style.textAlign);
720
- });
721
-
722
- for (let i = 0, n, l, r; (n = lineNodes[i]); i++) {
723
- n = lineNodes[i];
724
- // indent margin
725
- r = n.style.marginRight;
726
- l = n.style.marginLeft;
727
- if (r || l) {
728
- n.style.marginRight = l;
729
- n.style.marginLeft = r;
730
- }
731
- // text align
732
- r = n.style.textAlign;
733
- if (r === 'left') n.style.textAlign = 'right';
734
- else if (r === 'right') n.style.textAlign = 'left';
735
- }
736
-
737
- DIR_BTN_ACTIVE(this, rtl);
738
-
739
- // document type
740
- if (fc.has('documentType-use-header')) {
741
- if (rtl) fc.get('wrapper').appendChild(fc.get('documentTypeInner'));
742
- else fc.get('wrapper').insertBefore(fc.get('documentTypeInner'), fc.get('wysiwygFrame'));
743
- }
744
- if (fc.has('documentType-use-page')) {
745
- if (rtl) fc.get('wrapper').insertBefore(fc.get('documentTypePage'), fc.get('wysiwygFrame'));
746
- else fc.get('wrapper').appendChild(fc.get('documentTypePage'));
747
- }
748
-
749
- if (this.isBalloon) this.toolbar._showBalloon();
750
- else if (this.isSubBalloon) this.subToolbar._showBalloon();
751
- } catch (e) {
752
- this.options.set('_rtl', !rtl);
753
- console.warn(`[SUNEDITOR.setDir.fail] ${e.toString()}`);
754
- }
755
-
756
- this.effectNode = null;
757
- this.eventManager.applyTagEffect();
758
- },
759
-
760
- /**
761
- * @description Add or reset option property (Editor is reloaded)
762
- * @param {EditorInitOptions_editor} newOptions Options
763
- */
764
- resetOptions(newOptions) {
765
- const _keys = Object.keys;
766
- this.viewer.codeView(false);
767
- this.viewer.showBlocks(false);
768
-
769
- const newOptionKeys = _keys(newOptions);
770
- CheckResetKeys(newOptionKeys, this.plugins, '');
771
- if (newOptionKeys.length === 0) return;
772
-
773
- // option merge
774
- const rootDiff = {};
775
- const rootKeys = this.rootKeys;
776
- const frameRoots = this.frameRoots;
777
- const newRoots = [];
778
- const newRootKeys = {};
779
- this._originOptions = [newOptions, this._originOptions].reduce(function (init, option) {
780
- for (const key in option) {
781
- if (rootKeys.includes(key) && option[key]) {
782
- const nro = option[key];
783
- const newKeys = _keys(nro);
784
- CheckResetKeys(newKeys, null, key + '.');
785
- if (newKeys.length === 0) continue;
786
-
787
- rootDiff[key] = new Map();
788
- const o = frameRoots.get(key).get('options').get('_origin');
789
- for (const rk in nro) {
790
- const roV = nro[rk];
791
- if (!newKeys.includes(rk) || o[rk] === roV) continue;
792
- rootDiff[key].set(GetResetDiffKey(rk), true);
793
- o[rk] = roV;
794
- }
795
- newRoots.push((newRootKeys[key] = { options: o }));
796
- } else {
797
- init[key] = option[key];
798
- }
799
- }
800
- return init;
801
- }, {});
802
-
803
- // init options
804
- const options = this.options;
805
- const newMap = InitOptions(this._originOptions, newRoots, this.plugins).o;
806
- /** --------- root start --------- */
807
- for (let i = 0, k; (k = newOptionKeys[i]); i++) {
808
- if (newRootKeys[k]) {
809
- const diff = rootDiff[k];
810
- const fc = frameRoots.get(k);
811
- const originOptions = fc.get('options');
812
- const newRootOptions = newRootKeys[k].options;
813
-
814
- // statusbar
815
- if (diff.has('statusbar')) {
816
- dom.utils.removeItem(fc.get('statusbar'));
817
- if (newRootOptions.get('statusbar')) {
818
- const statusbar = CreateStatusbar(newRootOptions, null).statusbar;
819
- fc.get('container').appendChild(statusbar);
820
- UpdateStatusbarContext(statusbar, fc);
821
- this.eventManager.__addStatusbarEvent(fc, newRootOptions);
822
- } else {
823
- this.eventManager.removeEvent(originOptions.get('__statusbarEvent'));
824
- newRootOptions.set('__statusbarEvent', null);
825
- UpdateStatusbarContext(null, fc);
826
- }
827
- }
828
-
829
- // iframe's options
830
- if (diff.has('iframe_attributes')) {
831
- const frame = fc.get('wysiwygFrame');
832
- const originAttr = originOptions.get('iframe_attributes');
833
- const newAttr = newRootOptions.get('iframe_attributes');
834
- for (const origin_k in originAttr) frame.removeAttribute(origin_k, originAttr[origin_k]);
835
- for (const new_k in newAttr) frame.setAttribute(new_k, newAttr[new_k]);
836
- }
837
- if (diff.has('iframe_cssFileName')) {
838
- const docHead = fc.get('_wd').head;
839
- const links = docHead.getElementsByTagName('link');
840
- while (links[0]) docHead.removeChild(links[0]);
841
- const parseDocument = new DOMParser().parseFromString(converter._setIframeStyleLinks(newRootOptions.get('iframe_cssFileName')), 'text/html');
842
- const newLinks = parseDocument.head.children;
843
- const sTag = docHead.querySelector('style');
844
- while (newLinks[0]) docHead.insertBefore(newLinks[0], sTag);
845
- }
846
-
847
- // --- options set ---
848
- fc.set('options', newRootOptions);
849
-
850
- // frame styles
851
- this.ui.setEditorStyle(newRootOptions.get('_defaultStyles'), fc);
852
-
853
- // frame attributes
854
- const frame = fc.get('wysiwyg');
855
- const originAttr = originOptions.get('editableFrameAttributes');
856
- const newAttr = newRootOptions.get('editableFrameAttributes');
857
- for (const origin_k in originAttr) frame.removeAttribute(origin_k, originAttr[origin_k]);
858
- for (const new_k in newAttr) frame.setAttribute(new_k, newAttr[new_k]);
859
-
860
- continue;
861
- }
862
- /** --------- root end --------- */
863
-
864
- options.set(k, newMap.get(k));
865
-
866
- /** apply option */
867
- // history delay time
868
- if (k === 'historyStackDelayTime') {
869
- this.history.resetDelayTime(options.get('historyStackDelayTime'));
870
- continue;
871
- }
872
- // set dir
873
- if (k === 'textDirection') {
874
- this.setDir(options.get('_rtl') ? 'ltr' : 'rtl');
875
- continue;
876
- }
877
- }
878
-
879
- /** apply options */
880
- // toolbar
881
- const toolbar = this.context.get('toolbar.main');
882
- // width
883
- if (/inline|balloon/i.test(options.get('mode')) && newOptionKeys.includes('toolbar_width')) {
884
- toolbar.style.width = options.get('toolbar_width');
885
- }
886
- // hide
887
- if (options.get('toolbar_hide')) {
888
- toolbar.style.display = 'none';
889
- }
890
- // shortcuts hint
891
- if (options.get('shortcutsHint')) {
892
- dom.utils.removeClass(toolbar, 'se-shortcut-hide');
893
- } else {
894
- dom.utils.addClass(toolbar, 'se-shortcut-hide');
895
- }
896
-
897
- // theme
898
- if (this._originOptions.theme !== (newOptions.theme ?? this._originOptions.theme)) {
899
- this.ui.setTheme(newOptions.theme);
900
- }
901
-
902
- this.effectNode = null;
903
- this._setFrameInfo(this.frameRoots.get(this.status.rootKey));
904
- },
905
-
906
- /**
907
- * @description Change the current root index.
908
- * @param {*} rootKey
909
- */
910
- changeFrameContext(rootKey) {
911
- if (rootKey === this.status.rootKey) return;
912
-
913
- this.status.rootKey = rootKey;
914
- this._setFrameInfo(this.frameRoots.get(rootKey));
915
- this.toolbar._resetSticky();
916
- },
917
-
918
- /**
919
- * @description javascript execCommand
920
- * @param {string} command javascript execCommand function property
921
- * @param {boolean=} showDefaultUI javascript execCommand function property
922
- * @param {string=} value javascript execCommand function property
923
- */
924
- execCommand(command, showDefaultUI, value) {
925
- this.frameContext.get('_wd').execCommand(command, showDefaultUI, command === 'formatBlock' ? '<' + value + '>' : value);
926
- this.history.push(true);
927
- },
928
-
929
- /**
930
- * @description Focus to wysiwyg area
931
- * @param {*} rootKey Root index
932
- */
933
- focus(rootKey) {
934
- if (rootKey) this.changeFrameContext(rootKey);
935
- if (this.frameContext.get('wysiwygFrame').style.display === 'none') return;
936
- this._preventBlur = false;
937
-
938
- if (this.frameOptions.get('iframe') || !this.frameContext.get('wysiwyg').contains(this.selection.getNode())) {
939
- this._nativeFocus();
940
- } else {
941
- try {
942
- const range = this.selection.getRange();
943
- if (range.startContainer === range.endContainer && dom.check.isWysiwygFrame(range.startContainer)) {
944
- const currentNode = /** @type {HTMLElement} */ (range.commonAncestorContainer).children[range.startOffset];
945
- if (!this.format.isLine(currentNode) && !this.component.is(currentNode)) {
946
- const br = dom.utils.createElement('BR');
947
- const format = dom.utils.createElement(this.options.get('defaultLine'), null, br);
948
- this.frameContext.get('wysiwyg').insertBefore(format, currentNode);
949
- this.selection.setRange(br, 0, br, 0);
950
- return;
951
- }
952
- }
953
- this.selection.setRange(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
954
- } catch (e) {
955
- console.warn('[SUNEDITOR.focus.warn] ', e);
956
- this._nativeFocus();
957
- }
958
- }
959
-
960
- if (this.isBalloon) this.eventManager._toggleToolbarBalloon();
961
- },
962
-
963
- /**
964
- * @description If "focusEl" is a component, then that component is selected; if it is a format element, the last text is selected
965
- * - If "focusEdge" is null, then selected last element
966
- * @param {?Node=} focusEl Focus element
967
- */
968
- focusEdge(focusEl) {
969
- this._preventBlur = false;
970
- if (!focusEl) focusEl = this.frameContext.get('wysiwyg').lastElementChild;
971
-
972
- const fileComponentInfo = this.component.get(focusEl);
973
- if (fileComponentInfo) {
974
- this.component.select(fileComponentInfo.target, fileComponentInfo.pluginName);
975
- } else if (focusEl) {
976
- if (focusEl.nodeType !== 3) {
977
- focusEl = dom.query.getEdgeChild(
978
- focusEl,
979
- function (current) {
980
- return current.childNodes.length === 0 || current.nodeType === 3;
981
- },
982
- true
983
- );
984
- }
985
- if (!focusEl) this._nativeFocus();
986
- else this.selection.setRange(focusEl, focusEl.textContent.length, focusEl, focusEl.textContent.length);
987
- } else {
988
- this.focus();
989
- }
990
- },
991
-
992
- /**
993
- * @description Focusout to wysiwyg area (.blur())
994
- */
995
- blur() {
996
- if (this.frameOptions.get('iframe')) {
997
- this.frameContext.get('wysiwygFrame').blur();
998
- } else {
999
- this.frameContext.get('wysiwyg').blur();
1000
- }
1001
- },
1002
-
1003
- /**
1004
- * @description Destroy the suneditor
1005
- */
1006
- destroy() {
1007
- /** remove history */
1008
- this.history.destroy();
1009
-
1010
- /** remove event listeners */
1011
- this.eventManager._removeAllEvents();
1012
-
1013
- /** destroy external library */
1014
- if (this.options.get('codeMirror6Editor')) {
1015
- this.options.get('codeMirror6Editor').destroy();
1016
- }
1017
-
1018
- /** remove element */
1019
- dom.utils.removeItem(this.carrierWrapper);
1020
- dom.utils.removeItem(this.context.get('toolbar._wrapper'));
1021
- dom.utils.removeItem(this.context.get('toolbar.sub._wrapper'));
1022
- dom.utils.removeItem(this.context.get('statusbar._wrapper'));
1023
- this.applyFrameRoots((e) => {
1024
- dom.utils.removeItem(e.get('topArea'));
1025
- e.get('options').clear();
1026
- e.clear();
1027
- });
1028
-
1029
- /** remove object reference */
1030
- this.options.clear();
1031
- this.context.clear();
1032
-
1033
- let obj = this.plugins;
1034
- for (const k in obj) {
1035
- const p = obj[k];
1036
- if (typeof p._destroy === 'function') p._destroy();
1037
- for (const pk in p) {
1038
- delete p[pk];
1039
- }
1040
- delete obj[k];
1041
- }
1042
- obj = this.events;
1043
- for (const k in obj) {
1044
- delete obj[k];
1045
- }
1046
-
1047
- obj = ['eventManager', 'char', 'component', 'format', 'html', 'menu', 'nodeTransform', 'offset', 'selection', 'shortcuts', 'toolbar', 'ui', 'viewer'];
1048
- for (let i = 0, len = obj.length, c; i < len; i++) {
1049
- c = this[obj[i]];
1050
- for (const k in c) {
1051
- delete c[k];
1052
- }
1053
- }
1054
- obj = this.subToolbar;
1055
- if (obj) {
1056
- for (const k in obj) {
1057
- delete obj[k];
1058
- }
1059
- }
1060
-
1061
- obj = null;
1062
- for (const k in this) {
1063
- delete this[k];
1064
- }
1065
-
1066
- return null;
1067
- },
1068
-
1069
- /** ----- private methods ----------------------------------------------------------------------------------------------------------------------------- */
1070
- /**
1071
- * @private
1072
- * @description Set frameContext, frameOptions
1073
- * @param {__se__FrameContext} rt Root target[key] FrameContext
1074
- */
1075
- _setFrameInfo(rt) {
1076
- this.frameContext = rt;
1077
- this.frameOptions = rt.get('options');
1078
- rt.set('_editorHeight', rt.get('wysiwygFrame').offsetHeight);
1079
- this._lineBreaker_t = rt.get('lineBreaker_t');
1080
- this._lineBreaker_b = rt.get('lineBreaker_b');
1081
- },
1082
-
1083
- /**
1084
- * @private
1085
- * @description Focus to wysiwyg area using "native focus function"
1086
- */
1087
- _nativeFocus() {
1088
- this.selection.__focus();
1089
- this.selection._init();
1090
- },
1091
-
1092
- /**
1093
- * @private
1094
- * @description Check the components such as image and video and modify them according to the format.
1095
- * @param {boolean} loaded If true, the component is loaded.
1096
- */
1097
- _checkComponents(loaded) {
1098
- for (let i = 0, len = this._fileInfoPluginsCheck.length; i < len; i++) {
1099
- this._fileInfoPluginsCheck[i](loaded);
1100
- }
1101
- },
1102
-
1103
- /**
1104
- * @private
1105
- * @description Initialize the information of the components.
1106
- */
1107
- _resetComponents() {
1108
- for (let i = 0, len = this._fileInfoPluginsReset.length; i < len; i++) {
1109
- this._fileInfoPluginsReset[i]();
1110
- }
1111
- },
1112
-
1113
- /**
1114
- * @private
1115
- * @description Initializ wysiwyg area (Only called from core._init)
1116
- * @param {__se__FrameContext} e frameContext
1117
- * @param {string} value initial html string
1118
- */
1119
- _initWysiwygArea(e, value) {
1120
- // set content
1121
- e.get('wysiwyg').innerHTML =
1122
- this.html.clean(typeof value === 'string' ? value : (/^TEXTAREA$/i.test(e.get('originElement').nodeName) ? e.get('originElement').value : e.get('originElement').innerHTML) || '', {
1123
- forceFormat: true,
1124
- whitelist: null,
1125
- blacklist: null,
1126
- _freeCodeViewMode: this.options.get('freeCodeViewMode')
1127
- }) || '<' + this.options.get('defaultLine') + '><br></' + this.options.get('defaultLine') + '>';
1128
-
1129
- // char counter
1130
- if (e.has('charCounter')) e.get('charCounter').textContent = this.char.getLength();
1131
-
1132
- // document type init
1133
- if (this.options.get('type') === 'document') {
1134
- e.set('documentType', new DocumentType(this, e));
1135
- if (e.get('documentType').useHeader) {
1136
- e.set('documentType-use-header', true);
1137
- }
1138
- if (e.get('documentType').usePage) {
1139
- e.set('documentType-use-page', true);
1140
- e.get('documentTypePageMirror').innerHTML = e.get('wysiwyg').innerHTML;
1141
- }
1142
- }
1143
- },
1144
-
1145
- /**
1146
- * @private
1147
- * @description Called when there are changes to tags in the wysiwyg region.
1148
- * @param {__se__FrameContext} fc - Frame context object
1149
- */
1150
- _resourcesStateChange(fc) {
1151
- this._iframeAutoHeight(fc);
1152
- this._checkPlaceholder(fc);
1153
- if (this.options.get('type') === 'document' && fc.get('documentType').usePage) {
1154
- fc.get('documentTypePageMirror').innerHTML = fc.get('wysiwyg').innerHTML;
1155
- }
1156
- },
1157
-
1158
- /**
1159
- * @private
1160
- * @description Modify the height value of the iframe when the height of the iframe is automatic.
1161
- * @param {__se__FrameContext} fc - Frame context object
1162
- */
1163
- _iframeAutoHeight(fc) {
1164
- const autoFrame = fc.get('_iframeAuto');
1165
-
1166
- if (autoFrame) {
1167
- this._w.setTimeout(() => {
1168
- const h = autoFrame.offsetHeight;
1169
- fc.get('wysiwygFrame').style.height = h + 'px';
1170
- if (!env.isResizeObserverSupported) this.__callResizeFunction(fc, h, null);
1171
- }, 0);
1172
- } else if (!env.isResizeObserverSupported) {
1173
- this.__callResizeFunction(fc, fc.get('wysiwygFrame').offsetHeight, null);
1174
- }
1175
- },
1176
-
1177
- /**
1178
- * @private
1179
- * @description Call the "onResizeEditor" event
1180
- * @param {__se__FrameContext} fc - Frame context object
1181
- * @param {number} h - Height value
1182
- * @param {ResizeObserverEntry} resizeObserverEntry - ResizeObserverEntry object
1183
- */
1184
- __callResizeFunction(fc, h, resizeObserverEntry) {
1185
- h =
1186
- h === -1
1187
- ? resizeObserverEntry?.borderBoxSize && resizeObserverEntry.borderBoxSize[0]
1188
- ? resizeObserverEntry.borderBoxSize[0].blockSize
1189
- : resizeObserverEntry.contentRect.height + numbers.get(fc.get('wwComputedStyle').getPropertyValue('padding-left')) + numbers.get(fc.get('wwComputedStyle').getPropertyValue('padding-right'))
1190
- : h;
1191
- if (fc.get('_editorHeight') !== h) {
1192
- this.triggerEvent('onResizeEditor', { height: h, prevHeight: fc.get('_editorHeight'), frameContext: fc, observerEntry: resizeObserverEntry });
1193
- fc.set('_editorHeight', h);
1194
- }
1195
-
1196
- // document type page
1197
- if (fc.has('documentType-use-page')) {
1198
- fc.get('documentType').resizePage();
1199
- }
1200
- },
1201
-
1202
- /**
1203
- * @private
1204
- * @description Set display property when there is placeholder.
1205
- * @param {?__se__FrameContext=} fc - Frame context object, If null fc is this.frameContext
1206
- */
1207
- _checkPlaceholder(fc) {
1208
- fc = fc || this.frameContext;
1209
- const placeholder = fc.get('placeholder');
1210
-
1211
- if (placeholder) {
1212
- if (fc.get('isCodeView')) {
1213
- placeholder.style.display = 'none';
1214
- return;
1215
- }
1216
-
1217
- if (this.isEmpty(fc)) {
1218
- placeholder.style.display = 'block';
1219
- } else {
1220
- placeholder.style.display = 'none';
1221
- }
1222
- }
1223
- },
1224
-
1225
- /**
1226
- * @private
1227
- * @description Initializ editor
1228
- * @param {EditorInitOptions_editor} options Options
1229
- */
1230
- __editorInit(options) {
1231
- this.applyFrameRoots((e) => {
1232
- this.__setEditorParams(e);
1233
- });
1234
-
1235
- // initialize core and add event listeners
1236
- this._setFrameInfo(this.frameRoots.get(this.status.rootKey));
1237
- this.__init(options);
1238
- for (const v of this._onPluginEvents.values()) {
1239
- v.sort((a, b) => a.index - b.index);
1240
- }
1241
-
1242
- this.applyFrameRoots((e) => {
1243
- this.eventManager._addFrameEvents(e);
1244
- this._initWysiwygArea(e, e.get('options').get('value'));
1245
- });
1246
-
1247
- this.eventManager.__eventDoc = null;
1248
- this._componentsInfoInit = false;
1249
- this._componentsInfoReset = false;
1250
- this._checkComponents(true);
1251
-
1252
- this._w.setTimeout(() => {
1253
- // toolbar visibility
1254
- this.context.get('toolbar.main').style.visibility = '';
1255
- // roots
1256
- this.applyFrameRoots((e) => {
1257
- if (typeof this._resourcesStateChange !== 'function') return;
1258
- // observer
1259
- if (this.eventManager._wwFrameObserver) this.eventManager._wwFrameObserver.observe(e.get('wysiwygFrame'));
1260
- if (this.eventManager._toolbarObserver) this.eventManager._toolbarObserver.observe(e.get('_toolbarShadow'));
1261
- // resource state
1262
- this._resourcesStateChange(e);
1263
- });
1264
- // history reset
1265
- this.history.reset();
1266
- // user event
1267
- this.triggerEvent('onload', {});
1268
- }, 0);
1269
- },
1270
-
1271
- /**
1272
- * @private
1273
- * @description Initializ core variable
1274
- * @param {EditorInitOptions_editor} options Options
1275
- */
1276
- __init(options) {
1277
- // file components
1278
- this._fileInfoPluginsCheck = [];
1279
- this._fileInfoPluginsReset = [];
1280
-
1281
- // text components
1282
- this._MELInfo = new Map();
1283
-
1284
- // Command and file plugins registration
1285
- this.activeCommands = ACTIVE_EVENT_COMMANDS;
1286
- this._onPluginEvents = new Map([
1287
- ['onMouseMove', []],
1288
- ['onMouseLeave', []],
1289
- ['onMouseDown', []],
1290
- ['onMouseUp', []],
1291
- ['onScroll', []],
1292
- ['onClick', []],
1293
- ['onInput', []],
1294
- ['onKeyDown', []],
1295
- ['onKeyUp', []],
1296
- ['onFocus', []],
1297
- ['onBlur', []],
1298
- ['onPaste', []],
1299
- ['onFilePasteAndDrop', []]
1300
- ]);
1301
- this._fileManager.tags = [];
1302
- this._fileManager.pluginMap = {};
1303
- this._fileManager.tagAttrs = {};
1304
-
1305
- const plugins = this.plugins;
1306
- const filePluginRegExp = [];
1307
- let plugin;
1308
- for (const key in plugins) {
1309
- this.registerPlugin(key, this._pluginCallButtons[key], options[key]);
1310
- this.registerPlugin(key, this._pluginCallButtons_sub[key], options[key]);
1311
- plugin = this.plugins[key];
1312
-
1313
- // Filemanager
1314
- if (typeof plugin.__fileManagement === 'object') {
1315
- const fm = plugin.__fileManagement;
1316
- this._fileInfoPluginsCheck.push(fm._checkInfo.bind(fm));
1317
- this._fileInfoPluginsReset.push(fm._resetInfo.bind(fm));
1318
- if (Array.isArray(fm.tagNames)) {
1319
- const tagNames = fm.tagNames;
1320
- this._fileManager.tags = this._fileManager.tags.concat(tagNames);
1321
- filePluginRegExp.push(key);
1322
- for (let tag = 0, tLen = tagNames.length, t; tag < tLen; tag++) {
1323
- t = tagNames[tag].toLowerCase();
1324
- this._fileManager.pluginMap[t] = key;
1325
- if (fm.tagAttrs) {
1326
- this._fileManager.tagAttrs[t] = fm.tagAttrs;
1327
- }
1328
- }
1329
- }
1330
- }
1331
-
1332
- // Not file component
1333
- if (typeof plugin.constructor.component === 'function') {
1334
- this._componentManager.push(
1335
- function (launcher, element) {
1336
- if (!element || !(element = launcher.component?.call(this, element))) return null;
1337
- return {
1338
- target: element,
1339
- pluginName: launcher.key,
1340
- options: launcher.options
1341
- };
1342
- }.bind(plugin, plugin.constructor)
1343
- );
1344
- }
1345
-
1346
- // plugin event
1347
- const pluginOptions = plugin.constructor.options || {};
1348
- this._onPluginEvents.forEach((v, k) => {
1349
- if (typeof plugin[k] === 'function') {
1350
- const f = plugin[k].bind(plugin);
1351
- f.index = pluginOptions[`eventIndex_${k}`] || pluginOptions.eventIndex || 0;
1352
- v.push(f);
1353
- }
1354
- });
1355
-
1356
- // plugin maintain
1357
- if (plugin.retainFormat) {
1358
- const info = plugin.retainFormat();
1359
- this._MELInfo.set(info.query, info.method);
1360
- }
1361
- }
1362
-
1363
- if (this.options.get('buttons').has('pageBreak') || this.options.get('buttons_sub')?.has('pageBreak')) {
1364
- this._componentManager.push((element) => {
1365
- if (!element || !dom.utils.hasClass(element, 'se-page-break')) return null;
1366
- return {
1367
- target: element,
1368
- launcher: {
1369
- destroy: (target) => {
1370
- const focusEl = target.previousElementSibling || target.nextElementSibling;
1371
- dom.utils.removeItem(target);
1372
- // focus
1373
- this.focusEdge(focusEl);
1374
- this.history.push(false);
1375
- }
1376
- }
1377
- };
1378
- });
1379
- }
1380
-
1381
- this._fileManager.regExp = new RegExp(`^(${this._fileManager.tags.join('|') || '\\^'})$`, 'i');
1382
- this._fileManager.pluginRegExp = new RegExp(`^(${filePluginRegExp.length === 0 ? '\\^' : filePluginRegExp.join('|')})$`, 'i');
1383
-
1384
- delete this._pluginCallButtons;
1385
- delete this._pluginCallButtons_sub;
1386
-
1387
- this.__cachingButtons();
1388
- this.__cachingShortcuts();
1389
- },
1390
-
1391
- /**
1392
- * @private
1393
- * @description Caching basic buttons to use
1394
- */
1395
- __cachingButtons() {
1396
- const ctx = this.context;
1397
- this.__setDisabledButtons();
1398
- this.__saveCommandButtons(this.allCommandButtons, ctx.get('toolbar.buttonTray'));
1399
- if (this.options.has('_subMode')) {
1400
- this.__saveCommandButtons(this.subAllCommandButtons, ctx.get('toolbar.sub.buttonTray'));
1401
- }
1402
- },
1403
-
1404
- /**
1405
- * @private
1406
- * @description Set the disabled button list
1407
- * - this._codeViewDisabledButtons, this._controllerOnDisabledButtons
1408
- */
1409
- __setDisabledButtons() {
1410
- const ctx = this.context;
1411
-
1412
- this._codeViewDisabledButtons = converter.nodeListToArray(ctx.get('toolbar.buttonTray').querySelectorAll(DISABLE_BUTTONS_CODEVIEW));
1413
- this._controllerOnDisabledButtons = converter.nodeListToArray(ctx.get('toolbar.buttonTray').querySelectorAll(DISABLE_BUTTONS_CONTROLLER));
1414
-
1415
- if (this.options.has('_subMode')) {
1416
- this._codeViewDisabledButtons = this._codeViewDisabledButtons.concat(converter.nodeListToArray(ctx.get('toolbar.sub.buttonTray').querySelectorAll(DISABLE_BUTTONS_CODEVIEW)));
1417
- this._controllerOnDisabledButtons = this._controllerOnDisabledButtons.concat(converter.nodeListToArray(ctx.get('toolbar.sub.buttonTray').querySelectorAll(DISABLE_BUTTONS_CONTROLLER)));
1418
- }
1419
- },
1420
-
1421
- /**
1422
- * @private
1423
- * @description Save the current buttons
1424
- * @param {Map<string, Element>} cmdButtons Command button map
1425
- * @param {Element} tray Button tray
1426
- */
1427
- __saveCommandButtons(cmdButtons, tray) {
1428
- const currentButtons = tray.querySelectorAll(COMMAND_BUTTONS);
1429
- const shortcuts = this.options.get('shortcuts');
1430
- const reverseCommandArray = this.options.get('_reverseCommandArray');
1431
- const keyMap = this.shortcutsKeyMap;
1432
- const reverseKeys = this.reverseKeys;
1433
-
1434
- for (let i = 0, len = currentButtons.length, e, c; i < len; i++) {
1435
- e = /** @type {HTMLButtonElement} */ (currentButtons[i]);
1436
- c = e.getAttribute('data-command');
1437
- // command set
1438
- cmdButtons.set(c, e);
1439
- this.__setCommandTargets(c, e);
1440
- // shortcuts
1441
- CreateShortcuts(c, e, shortcuts[c], keyMap, reverseCommandArray, reverseKeys);
1442
- }
1443
- },
1444
-
1445
- /**
1446
- * @private
1447
- * @description Caches shortcut keys for commands.
1448
- */
1449
- __cachingShortcuts() {
1450
- const shortcuts = this.options.get('shortcuts');
1451
- const reverseCommandArray = this.options.get('_reverseCommandArray');
1452
- const keyMap = this.shortcutsKeyMap;
1453
- const reverseKeys = this.reverseKeys;
1454
- for (const key of Object.keys(shortcuts)) {
1455
- if (!key.startsWith('_')) continue;
1456
- CreateShortcuts('', null, shortcuts[key], keyMap, reverseCommandArray, reverseKeys);
1457
- }
1458
- },
1459
-
1460
- /**
1461
- * @private
1462
- * @description Sets command target elements.
1463
- * @param {string} cmd - The command identifier.
1464
- * @param {HTMLButtonElement} target - The associated command button.
1465
- */
1466
- __setCommandTargets(cmd, target) {
1467
- if (!cmd || !target) return;
1468
-
1469
- const isBasicCmd = BASIC_COMMANDS.includes(cmd);
1470
- if (!isBasicCmd && !this.plugins[cmd]) return;
1471
-
1472
- if (!this.commandTargets.get(cmd)) {
1473
- this.commandTargets.set(cmd, [target]);
1474
- } else if (!this.commandTargets.get(cmd).includes(target)) {
1475
- this.commandTargets.get(cmd).push(target);
1476
- }
1477
- },
1478
-
1479
- /**
1480
- * @private
1481
- * @description Configures the document properties of an iframe editor.
1482
- * @param {HTMLIFrameElement} frame - The editor iframe.
1483
- * @param {Map<string, *>} originOptions - The original options.
1484
- * @param {__se__FrameOptions} targetOptions - The new options.
1485
- */
1486
- __setIframeDocument(frame, originOptions, targetOptions) {
1487
- frame.setAttribute('scrolling', 'auto');
1488
- frame.contentDocument.head.innerHTML =
1489
- '<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">' +
1490
- converter._setIframeStyleLinks(targetOptions.get('iframe_cssFileName')) +
1491
- converter._setAutoHeightStyle(targetOptions.get('height'));
1492
- frame.contentDocument.body.className = originOptions.get('_editableClass');
1493
- frame.contentDocument.body.setAttribute('contenteditable', 'true');
1494
- },
1495
-
1496
- /**
1497
- * @private
1498
- * @description Set the FrameContext parameters and options
1499
- * @param {__se__FrameContext} e - Frame context object
1500
- */
1501
- __setEditorParams(e) {
1502
- const frameOptions = e.get('options');
1503
- const _w = this._w;
1504
-
1505
- e.set('wwComputedStyle', _w.getComputedStyle(e.get('wysiwyg')));
1506
-
1507
- if (!frameOptions.get('iframe') && typeof ShadowRoot === 'function') {
1508
- let child = e.get('wysiwygFrame');
1509
- while (child) {
1510
- if (child.shadowRoot) {
1511
- this._shadowRoot = child.shadowRoot;
1512
- break;
1513
- } else if (child instanceof ShadowRoot) {
1514
- this._shadowRoot = child;
1515
- break;
1516
- }
1517
- child = child.parentNode;
1518
- }
1519
- }
1520
-
1521
- // wisywig attributes
1522
- const attr = frameOptions.get('editableFrameAttributes');
1523
- for (const k in attr) {
1524
- e.get('wysiwyg').setAttribute(k, attr[k]);
1525
- }
1526
-
1527
- // init, validate
1528
- if (frameOptions.get('iframe')) {
1529
- e.set('_ww', e.get('wysiwygFrame').contentWindow);
1530
- e.set('_wd', e.get('wysiwygFrame').contentDocument);
1531
- e.set('wysiwyg', e.get('_wd').body);
1532
- // e.get('wysiwyg').className += ' ' + options.get('_editableClass');
1533
- if (frameOptions.get('_defaultStyles').editor) e.get('wysiwyg').style.cssText = frameOptions.get('_defaultStyles').editor;
1534
- if (frameOptions.get('height') === 'auto') e.set('_iframeAuto', e.get('_wd').body);
1535
- } else {
1536
- e.set('_ww', _w);
1537
- e.set('_wd', this._d);
1538
- }
1539
- },
1540
-
1541
- /**
1542
- * @private
1543
- * @description Registers and initializes editor classes.
1544
- */
1545
- __registerClass() {
1546
- // use events
1547
- this.events = { ...Events, ...this.options.get('events') };
1548
- this.triggerEvent = async (eventName, eventData) => {
1549
- // [iframe] wysiwyg is disabled, the event is not called.
1550
- if (eventData?.frameContext?.get('wysiwyg').getAttribute('contenteditable') === 'false') return false;
1551
- const eventHandler = this.events[eventName];
1552
- if (typeof eventHandler === 'function') {
1553
- return await eventHandler({ editor: this, ...eventData });
1554
- }
1555
- return env.NO_EVENT;
1556
- };
1557
-
1558
- // history function
1559
- this.history = History(this);
1560
-
1561
- // eventManager
1562
- this.eventManager = new EventManager(this);
1563
-
1564
- // util classes
1565
- this.offset = new Offset(this);
1566
- this.shortcuts = new Shortcuts(this);
1567
- // main classes
1568
- this.toolbar = new Toolbar(this, { keyName: 'toolbar', balloon: this.isBalloon, balloonAlways: this.isBalloonAlways, inline: this.isInline, res: this._responsiveButtons });
1569
- if (this.options.has('_subMode')) {
1570
- this.subToolbar = new Toolbar(this, {
1571
- keyName: 'toolbar.sub',
1572
- balloon: this.isSubBalloon,
1573
- balloonAlways: this.isSubBalloonAlways,
1574
- inline: false,
1575
- res: this._responsiveButtons_sub
1576
- });
1577
- }
1578
- this.selection = new Selection_(this);
1579
- this.html = new HTML(this);
1580
- this.nodeTransform = new NodeTransform(this);
1581
- this.component = new Component(this);
1582
- this.format = new Format(this);
1583
- this.menu = new Menu(this);
1584
- this.char = new Char(this);
1585
- this.ui = new UI(this);
1586
- this.viewer = new Viewer(this);
1587
-
1588
- // register classes to the eventManager
1589
- ClassInjector.call(this.eventManager, this);
1590
- // register main classes
1591
- ClassInjector.call(this.char, this);
1592
- ClassInjector.call(this.component, this);
1593
- ClassInjector.call(this.format, this);
1594
- ClassInjector.call(this.html, this);
1595
- ClassInjector.call(this.menu, this);
1596
- ClassInjector.call(this.nodeTransform, this);
1597
- ClassInjector.call(this.offset, this);
1598
- ClassInjector.call(this.selection, this);
1599
- ClassInjector.call(this.shortcuts, this);
1600
- ClassInjector.call(this.toolbar, this);
1601
- ClassInjector.call(this.ui, this);
1602
- ClassInjector.call(this.viewer, this);
1603
- if (this.options.has('_subMode')) ClassInjector.call(this.subToolbar, this);
1604
-
1605
- // delete self reference
1606
- delete this.eventManager['eventManager'];
1607
- delete this.char['char'];
1608
- delete this.component['component'];
1609
- delete this.format['format'];
1610
- delete this.html['html'];
1611
- delete this.menu['menu'];
1612
- delete this.nodeTransform['nodeTransform'];
1613
- delete this.offset['offset'];
1614
- delete this.selection['selection'];
1615
- delete this.shortcuts['shortcuts'];
1616
- delete this.toolbar['toolbar'];
1617
- delete this.ui['ui'];
1618
- delete this.viewer['viewer'];
1619
- if (this.subToolbar) delete this.subToolbar['subToolbar'];
1620
-
1621
- this._responsiveButtons = this._responsiveButtons_sub = null;
1622
- },
1623
-
1624
- /**
1625
- * @private
1626
- * @description Creates the editor instance and initializes components.
1627
- * @param {EditorInitOptions_editor} originOptions - The initial editor options.
1628
- * @returns {Promise<void>}
1629
- */
1630
- async __Create(originOptions) {
1631
- // set modes
1632
- this.isInline = /inline/i.test(this.options.get('mode'));
1633
- this.isBalloon = /balloon/i.test(this.options.get('mode'));
1634
- this.isBalloonAlways = /balloon-always/i.test(this.options.get('mode'));
1635
- this.isClassic = /classic/i.test(this.options.get('mode'));
1636
- // set subToolbar modes
1637
- this.isSubBalloon = /balloon/i.test(this.options.get('_subMode'));
1638
- this.isSubBalloonAlways = /balloon-always/i.test(this.options.get('_subMode'));
1639
-
1640
- // register class
1641
- this.__registerClass();
1642
-
1643
- // common events
1644
- this.eventManager._addCommonEvents();
1645
-
1646
- // init
1647
- const iframePromises = [];
1648
- this.applyFrameRoots((e) => {
1649
- const o = e.get('originElement');
1650
- const t = e.get('topArea');
1651
- o.style.display = 'none';
1652
- t.style.display = 'block';
1653
- o.parentNode.insertBefore(t, o.nextElementSibling);
1654
-
1655
- if (e.get('options').get('iframe')) {
1656
- const iframeLoaded = new Promise((resolve) => {
1657
- this.eventManager.addEvent(e.get('wysiwygFrame'), 'load', ({ target }) => {
1658
- this.__setIframeDocument(target, this.options, e.get('options'));
1659
- resolve();
1660
- });
1661
- });
1662
- iframePromises.push(iframeLoaded);
1663
- }
1664
- });
1665
-
1666
- this.applyFrameRoots((e) => {
1667
- e.get('wrapper').appendChild(e.get('wysiwygFrame'));
1668
-
1669
- // document type
1670
- if (e.get('documentTypeInner')) {
1671
- if (this.options.get('_rtl')) e.get('wrapper').appendChild(e.get('documentTypeInner'));
1672
- else e.get('wrapper').insertBefore(e.get('documentTypeInner'), e.get('wysiwygFrame'));
1673
- }
1674
- if (e.get('documentTypePage')) {
1675
- if (this.options.get('_rtl')) e.get('wrapper').insertBefore(e.get('documentTypePage'), e.get('wysiwygFrame'));
1676
- else e.get('wrapper').appendChild(e.get('documentTypePage'));
1677
- // page mirror
1678
- e.get('wrapper').appendChild(e.get('documentTypePageMirror'));
1679
- }
1680
- });
1681
-
1682
- if (iframePromises.length > 0) {
1683
- await Promise.all(iframePromises);
1684
- }
1685
-
1686
- this.__editorInit(originOptions);
1687
- },
1688
-
1689
- Constructor: Editor
1690
- };
1691
-
1692
- function GetResetDiffKey(key) {
1693
- if (/^statusbar/i.test(key)) return 'statusbar';
1694
- return key;
1695
- }
1696
-
1697
- function CheckResetKeys(keys, plugins, root) {
1698
- for (let i = 0, len = keys.length, k; i < len; i++) {
1699
- k = keys[i];
1700
- if (OPTION_FIXED_FLAG[k] === 'fixed' || (plugins && plugins[k])) {
1701
- console.warn(`[SUNEDITOR.warn.resetOptions] "[${root + k}]" options not available in resetOptions have no effect.`);
1702
- keys.splice(i--, 1);
1703
- len--;
1704
- }
1705
- }
1706
- }
1707
-
1708
- export default Editor;
1
+ import { env, converter, dom, numbers } from '../helper';
2
+ import Constructor, { InitOptions, UpdateButton, CreateShortcuts, CreateStatusbar } from './section/constructor';
3
+ import { OPTION_FRAME_FIXED_FLAG, OPTION_FIXED_FLAG } from './section/options';
4
+ import { UpdateStatusbarContext } from './section/context';
5
+ import { BASIC_COMMANDS, ACTIVE_EVENT_COMMANDS, SELECT_ALL, DIR_BTN_ACTIVE, SAVE, COPY_FORMAT, FONT_STYLE, PAGE_BREAK } from './section/actives';
6
+ import History from './base/history';
7
+ import EventManager from './base/eventManager';
8
+ import Events from '../events';
9
+ import DocumentType from './section/documentType';
10
+
11
+ // util
12
+ import InstanceCheck from './util/instanceCheck';
13
+
14
+ // class injector
15
+ import ClassInjector from '../editorInjector/_classes';
16
+
17
+ // classes
18
+ import Char from './class/char';
19
+ import Component from './class/component';
20
+ import Format from './class/format';
21
+ import HTML from './class/html';
22
+ import Menu from './class/menu';
23
+ import NodeTransform from './class/nodeTransform';
24
+ import Offset from './class/offset';
25
+ import Selection_ from './class/selection';
26
+ import Shortcuts from './class/shortcuts';
27
+ import Toolbar from './class/toolbar';
28
+ import UI from './class/ui';
29
+ import Viewer from './class/viewer';
30
+
31
+ const COMMAND_BUTTONS = '.se-menu-list .se-toolbar-btn[data-command]';
32
+ const DISABLE_BUTTONS_CODEVIEW = `${COMMAND_BUTTONS}:not([class~="se-code-view-enabled"]):not([data-type="MORE"])`;
33
+ const DISABLE_BUTTONS_CONTROLLER = `${COMMAND_BUTTONS}:not([class~="se-component-enabled"]):not([data-type="MORE"])`;
34
+
35
+ /**
36
+ * @typedef {import('./section/options').EditorInitOptions} EditorInitOptions_editor
37
+ */
38
+
39
+ /**
40
+ * @typedef {import('./section/options').EditorFrameOptions} EditorFrameOptions_editor
41
+ */
42
+
43
+ /**
44
+ * @typedef {import('../modules/Controller').ControllerInfo} ControllerInfo_editor
45
+ */
46
+
47
+ /**
48
+ * @constructor
49
+ * @description SunEditor constructor function.
50
+ * @param {Array<{target: Element, key: *, options: EditorFrameOptions_editor}>} multiTargets Target element
51
+ * @param {EditorInitOptions_editor} options options
52
+ */
53
+ function Editor(multiTargets, options) {
54
+ const _d = multiTargets[0].target.ownerDocument || env._d;
55
+ const _w = _d.defaultView || env._w;
56
+ const product = Constructor(multiTargets, options);
57
+
58
+ /**
59
+ * @description Frame root key array
60
+ * @type {Array<*>}
61
+ */
62
+ this.rootKeys = product.rootKeys;
63
+
64
+ /**
65
+ * @description Frame root map
66
+ * @type {Map<*, __se__FrameContext>}
67
+ */
68
+ this.frameRoots = product.frameRoots;
69
+
70
+ /**
71
+ * @description Editor context object
72
+ * @type {__se__Context}
73
+ */
74
+ this.context = product.context;
75
+
76
+ /**
77
+ * @description Current focusing frame context
78
+ * @type {__se__FrameContext}
79
+ */
80
+ this.frameContext = new Map();
81
+
82
+ /**
83
+ * @description Current focusing frame context options
84
+ * @type {__se__FrameOptions}
85
+ */
86
+ this.frameOptions = new Map();
87
+
88
+ /**
89
+ * @description Document object
90
+ * @type {Document}
91
+ */
92
+ this._d = _d;
93
+
94
+ /**
95
+ * @description Window object
96
+ * @type {Window}
97
+ */
98
+ this._w = _w;
99
+
100
+ /**
101
+ * @description Controllers carrier
102
+ * @type {HTMLElement}
103
+ */
104
+ this.carrierWrapper = product.carrierWrapper;
105
+
106
+ /**
107
+ * @description Editor options
108
+ * @type {Map<string, *>}
109
+ */
110
+ this.options = product.options;
111
+
112
+ /**
113
+ * @description Plugins
114
+ * @type {Object<string, *>}
115
+ */
116
+ this.plugins = product.plugins || {};
117
+
118
+ /**
119
+ * @description Events object, call by triggerEvent function
120
+ * @type {Object<string, *>}
121
+ */
122
+ this.events = null;
123
+
124
+ /**
125
+ * @description Call the event function by injecting self: this.
126
+ * @type {(eventName: string, ...args: *) => Promise<*>}
127
+ */
128
+ this.triggerEvent = null;
129
+
130
+ /**
131
+ * @description Default icons object
132
+ * @type {Object<string, string>}
133
+ */
134
+ this.icons = product.icons;
135
+
136
+ /**
137
+ * @description loaded language
138
+ * @type {Object<string, *>}
139
+ */
140
+ this.lang = product.lang;
141
+
142
+ /**
143
+ * @description Variables used internally in editor operation
144
+ * @type {__se__EditorStatus}
145
+ */
146
+ this.status = {
147
+ hasFocus: false,
148
+ tabSize: 4,
149
+ indentSize: 25,
150
+ codeIndentSize: 2,
151
+ currentNodes: [],
152
+ currentNodesMap: [],
153
+ initViewportHeight: 0,
154
+ currentViewportHeight: 0,
155
+ onSelected: false,
156
+ rootKey: product.rootId,
157
+ _range: null,
158
+ _onMousedown: false
159
+ };
160
+
161
+ /**
162
+ * @description Is classic mode?
163
+ * @type {boolean}
164
+ */
165
+ this.isClassic = false;
166
+
167
+ /**
168
+ * @description Is inline mode?
169
+ * @type {boolean}
170
+ */
171
+ this.isInline = false;
172
+
173
+ /**
174
+ * @description Is balloon|balloon-always mode?
175
+ * @type {boolean}
176
+ */
177
+ this.isBalloon = false;
178
+
179
+ /**
180
+ * @description Is balloon-always mode?
181
+ * @type {boolean}
182
+ */
183
+ this.isBalloonAlways = false;
184
+
185
+ /**
186
+ * @description Is subToolbar balloon|balloon-always mode?
187
+ * @type {boolean}
188
+ */
189
+ this.isSubBalloon = false;
190
+
191
+ /**
192
+ * @description Is subToolbar balloon-always mode?
193
+ * @type {boolean}
194
+ */
195
+ this.isSubBalloonAlways = false;
196
+
197
+ /**
198
+ * @description All command buttons map
199
+ * @type {Map<string, HTMLElement>}
200
+ */
201
+ this.allCommandButtons = new Map();
202
+
203
+ /**
204
+ * @description All command buttons map
205
+ * @type {Map<string, HTMLElement>}
206
+ */
207
+ this.subAllCommandButtons = new Map();
208
+
209
+ /**
210
+ * @description Shoutcuts key map
211
+ * @type {Map<string, *>}
212
+ */
213
+ this.shortcutsKeyMap = new Map();
214
+
215
+ /**
216
+ * @description Shoutcuts reverse key array
217
+ * - An array of key codes generated with the reverseButtons option, used to reverse the action for a specific key combination.
218
+ * @type {Array<string>}
219
+ */
220
+ this.reverseKeys = [];
221
+
222
+ /**
223
+ * @description A map with the plugin's buttons having an "active" method and the default command buttons with an "active" action.
224
+ * - Each button is contained in an array.
225
+ * @type {Map<string, Array<HTMLButtonElement>>}
226
+ */
227
+ this.commandTargets = new Map();
228
+
229
+ /**
230
+ * @description Plugins array with "active" method.
231
+ * - "activeCommands" runs the "add" method when creating the editor.
232
+ * @type {Array<string>}
233
+ */
234
+ this.activeCommands = null;
235
+
236
+ /**
237
+ * @description The selection node (selection.getNode()) to which the effect was last applied
238
+ * @type {Node|null}
239
+ */
240
+ this.effectNode = null;
241
+
242
+ /**
243
+ * @description Currently open "Modal" instance
244
+ * @type {*}
245
+ */
246
+ this.opendModal = null;
247
+
248
+ /**
249
+ * @description Currently open "Controller" info array
250
+ * @type {Array<ControllerInfo_editor>}
251
+ */
252
+ this.opendControllers = [];
253
+
254
+ /**
255
+ * @description Currently open "Controller" caller plugin name
256
+ */
257
+ this.currentControllerName = '';
258
+
259
+ /**
260
+ * @description Currently open "Browser" instance
261
+ * @type {*}
262
+ */
263
+ this.opendBrowser = null;
264
+
265
+ /**
266
+ * @description Whether "SelectMenu" is open
267
+ * @type {boolean}
268
+ */
269
+ this.selectMenuOn = false;
270
+
271
+ // ------ base ------
272
+ /** @description History class instance @type {ReturnType<typeof import('./base/history').default>} */
273
+ this.history = null;
274
+ /** @description EventManager class instance @type {import('./base/eventManager').default} */
275
+ this.eventManager = null;
276
+
277
+ // ----- util -----
278
+ /** @description iframe-safe instanceof check utility class @type {import('./util/instanceCheck').default} */
279
+ this.instanceCheck = null;
280
+
281
+ // ------ class ------
282
+ /** @description Toolbar class instance @type {import('./class/toolbar').default} */
283
+ this.toolbar = null;
284
+ /** @description Sub-Toolbar class instance @type {import('./class/toolbar').default|null} */
285
+ this.subToolbar = null;
286
+ /** @description Char class instance @type {import('./class/char').default} */
287
+ this.char = null;
288
+ /** @description Component class instance @type {import('./class/component').default} */
289
+ this.component = null;
290
+ /** @description Format class instance @type {import('./class/format').default} */
291
+ this.format = null;
292
+ /** @description HTML class instance @type {import('./class/html').default} */
293
+ this.html = null;
294
+ /** @description Menu class instance @type {import('./class/menu').default} */
295
+ this.menu = null;
296
+ /** @description NodeTransform class instance @type {import('./class/nodeTransform').default} */
297
+ this.nodeTransform = null;
298
+ /** @description Offset class instance @type {import('./class/offset').default} */
299
+ this.offset = null;
300
+ /** @description Selection class instance @type {import('./class/selection').default} */
301
+ this.selection = null;
302
+ /** @description Shortcuts class instance @type {import('./class/shortcuts').default} */
303
+ this.shortcuts = null;
304
+ /** @description UI class instance @type {import('./class/ui').default} */
305
+ this.ui = null;
306
+ /** @description Viewer class instance @type {import('./class/viewer').default} */
307
+ this.viewer = null;
308
+
309
+ // ------------------------------------------------------- private properties -------------------------------------------------------
310
+ /**
311
+ * @description Line breaker (top)
312
+ * @type {HTMLElement}
313
+ */
314
+ this._lineBreaker_t = null;
315
+
316
+ /**
317
+ * @description Line breaker (bottom)
318
+ * @type {HTMLElement}
319
+ */
320
+ this._lineBreaker_b = null;
321
+
322
+ /**
323
+ * @description Closest ShadowRoot to editor if found
324
+ * @type {ShadowRoot & { getSelection?: () => Selection }} - Chromium-based browsers (Chrome, Edge, etc.) has a getSelection method on the ShadowRoot
325
+ */
326
+ this._shadowRoot = null;
327
+
328
+ /**
329
+ * @description Plugin call event map
330
+ * @type {Map<string, Array<((...args: *) => *) & { index: number }>>}
331
+ */
332
+ this._onPluginEvents = null;
333
+
334
+ /**
335
+ * @description Copy format info
336
+ * - eventManager.__cacheStyleNodes copied
337
+ * @type {Array<Node>|null}
338
+ */
339
+ this._onCopyFormatInfo = null;
340
+
341
+ /**
342
+ * @description Copy format init method
343
+ * @type {(...args: *) => *|null}
344
+ */
345
+ this._onCopyFormatInitMethod = null;
346
+
347
+ /**
348
+ * @description Controller target's frame div (editor.frameContext.get('topArea'))
349
+ * @type {HTMLElement|null}
350
+ */
351
+ this._controllerTargetContext = null;
352
+
353
+ /**
354
+ * @description List of buttons that are disabled when "controller" is opened
355
+ * @type {Array<HTMLButtonElement|HTMLInputElement>}
356
+ */
357
+ this._controllerOnDisabledButtons = [];
358
+
359
+ /**
360
+ * @description List of buttons that are disabled when "codeView" mode opened
361
+ * @type {Array<HTMLButtonElement|HTMLInputElement>}
362
+ */
363
+ this._codeViewDisabledButtons = [];
364
+
365
+ /**
366
+ * @description List of buttons to run plugins in the toolbar
367
+ * @type {Array<HTMLElement>}
368
+ */
369
+ this._pluginCallButtons = product.pluginCallButtons;
370
+
371
+ /**
372
+ * @description List of buttons to run plugins in the Sub-Toolbar
373
+ * @type {Array<HTMLElement>}
374
+ */
375
+ this._pluginCallButtons_sub = product.pluginCallButtons_sub;
376
+
377
+ /**
378
+ * @description Responsive Toolbar Button Structure array
379
+ * @type {Array<*>}
380
+ */
381
+ this._responsiveButtons = product.responsiveButtons;
382
+
383
+ /**
384
+ * @description Responsive Sub-Toolbar Button Structure array
385
+ * @type {Array<*>}
386
+ */
387
+ this._responsiveButtons_sub = product.responsiveButtons_sub;
388
+
389
+ /**
390
+ * @description Variable that controls the "blur" event in the editor of inline or balloon mode when the focus is moved to dropdown
391
+ * @type {boolean}
392
+ */
393
+ this._notHideToolbar = false;
394
+
395
+ /**
396
+ * @description Variables for controlling blur events
397
+ * @type {boolean}
398
+ */
399
+ this._preventBlur = false;
400
+
401
+ /**
402
+ * @description Variables for controlling focus events
403
+ * @type {boolean}
404
+ */
405
+ this._preventFocus = false;
406
+
407
+ /**
408
+ * @description Variables for controlling selection change events
409
+ */
410
+ this._preventSelection = false;
411
+
412
+ /**
413
+ * @description If true, initialize all indexes of image, video information
414
+ * @type {boolean}
415
+ */
416
+ this._componentsInfoInit = true;
417
+
418
+ /**
419
+ * @description If true, reset all indexes of image, video information
420
+ * @type {boolean}
421
+ */
422
+ this._componentsInfoReset = false;
423
+
424
+ /**
425
+ * @description plugin retainFormat info Map()
426
+ * @type {Map<string, { key: string, method: (...args: *) => * }>}
427
+ */
428
+ this._MELInfo = null;
429
+
430
+ /**
431
+ * @description Properties for managing files in the "FileManager" module
432
+ * @type {Array<*>}
433
+ */
434
+ this._fileInfoPluginsCheck = null;
435
+
436
+ /**
437
+ * @description Properties for managing files in the "FileManager" module
438
+ * @type {Array<*>}
439
+ */
440
+ this._fileInfoPluginsReset = null;
441
+
442
+ /**
443
+ * @description Variables for file component management
444
+ * @type {Object<string, *>}
445
+ */
446
+ this._fileManager = {
447
+ tags: null,
448
+ regExp: null,
449
+ pluginRegExp: null,
450
+ pluginMap: null
451
+ };
452
+
453
+ /**
454
+ * @description Variables for managing the components
455
+ * @type {Array<*>}
456
+ */
457
+ this._componentManager = [];
458
+
459
+ /**
460
+ * @description Current Figure container.
461
+ * @type {HTMLElement|null}
462
+ */
463
+ this._figureContainer = null;
464
+
465
+ /**
466
+ * @description Origin options
467
+ * @type {EditorInitOptions_editor}
468
+ */
469
+ this._originOptions = options;
470
+
471
+ /** ----- Create editor ------------------------------------------------------------ */
472
+ this.__Create(options);
473
+ }
474
+
475
+ Editor.prototype = {
476
+ /**
477
+ * @description If the plugin is not added, add the plugin and call the 'add' function.
478
+ * - If the plugin is added call callBack function.
479
+ * @param {string} pluginName The name of the plugin to call
480
+ * @param {?Array<HTMLElement>} targets Plugin target button (This is not necessary if you have a button list when creating the editor)
481
+ * @param {?Object<string, *>} pluginOptions Plugin's options
482
+ */
483
+ registerPlugin(pluginName, targets, pluginOptions) {
484
+ let plugin = this.plugins[pluginName];
485
+ if (!plugin) {
486
+ throw Error(`[SUNEDITOR.registerPlugin.fail] The called plugin does not exist or is in an invalid format. (pluginName: "${pluginName}")`);
487
+ } else if (typeof this.plugins[pluginName] === 'function') {
488
+ plugin = this.plugins[pluginName] = new this.plugins[pluginName](this, pluginOptions || {});
489
+ if (typeof plugin.init === 'function') plugin.init();
490
+ }
491
+
492
+ if (targets) {
493
+ for (let i = 0, len = targets.length; i < len; i++) {
494
+ UpdateButton(targets[i], plugin, this.icons, this.lang);
495
+ }
496
+
497
+ if (!this.activeCommands.includes(pluginName) && typeof this.plugins[pluginName].active === 'function') {
498
+ this.activeCommands.push(pluginName);
499
+ }
500
+ }
501
+ },
502
+
503
+ /**
504
+ * @description Run plugin calls and basic commands.
505
+ * @param {string} command Command string
506
+ * @param {string} type Display type string ('command', 'dropdown', 'modal', 'container')
507
+ * @param {?Node=} button The element of command button
508
+ */
509
+ run(command, type, button) {
510
+ if (type) {
511
+ if (/more/i.test(type)) {
512
+ const toolbar = dom.query.getParentElement(button, '.se-toolbar');
513
+ const toolInst = dom.utils.hasClass(toolbar, 'se-toolbar-sub') ? this.subToolbar : this.toolbar;
514
+ if (button !== toolInst.currentMoreLayerActiveButton) {
515
+ const layer = toolbar.querySelector('.' + command);
516
+ if (layer) {
517
+ toolInst._moreLayerOn(button, layer);
518
+ toolInst._showBalloon();
519
+ toolInst._showInline();
520
+ }
521
+ dom.utils.addClass(button, 'on');
522
+ } else if (toolInst.currentMoreLayerActiveButton) {
523
+ toolInst._moreLayerOff();
524
+ toolInst._showBalloon();
525
+ toolInst._showInline();
526
+ }
527
+
528
+ this.viewer._resetFullScreenHeight();
529
+ return;
530
+ }
531
+
532
+ if (/container/.test(type) && (this.menu.targetMap[command] === null || button !== this.menu.currentContainerActiveButton)) {
533
+ this.menu.containerOn(button);
534
+ return;
535
+ }
536
+
537
+ if (this.frameContext.get('isReadOnly') && dom.utils.arrayIncludes(this._controllerOnDisabledButtons, button)) return;
538
+ if (/dropdown/.test(type) && (this.menu.targetMap[command] === null || button !== this.menu.currentDropdownActiveButton)) {
539
+ this.menu.dropdownOn(button);
540
+ return;
541
+ } else if (/modal/.test(type)) {
542
+ this.plugins[command].open(button);
543
+ return;
544
+ } else if (/command/.test(type)) {
545
+ this.plugins[command].action(button);
546
+ } else if (/browser/.test(type)) {
547
+ this.plugins[command].open(null);
548
+ } else if (/popup/.test(type)) {
549
+ this.plugins[command].show();
550
+ }
551
+ } else if (command) {
552
+ this.commandHandler(command, button);
553
+ }
554
+
555
+ if (/dropdown/.test(type)) {
556
+ this.menu.dropdownOff();
557
+ } else if (!/command/.test(type)) {
558
+ this.menu.dropdownOff();
559
+ this.menu.containerOff();
560
+ }
561
+ },
562
+
563
+ /**
564
+ * @description Execute default command of command button
565
+ * - (selectAll, codeView, fullScreen, indent, outdent, undo, redo, removeFormat, print, preview, showBlocks, save, bold, underline, italic, strike, subscript, superscript, copy, cut, paste)
566
+ * @param {string} command Property of command button (data-value)
567
+ * @param {?Node=} button Command button
568
+ * @returns {Promise<void>}
569
+ */
570
+ async commandHandler(command, button) {
571
+ if (this.frameContext.get('isReadOnly') && !/copy|cut|selectAll|codeView|fullScreen|print|preview|showBlocks/.test(command)) return;
572
+
573
+ switch (command) {
574
+ case 'selectAll':
575
+ SELECT_ALL(this);
576
+ break;
577
+ case 'copy': {
578
+ const range = this.selection.getRange();
579
+ if (range.collapsed) break;
580
+
581
+ const container = dom.utils.createElement('div', null, range.cloneContents());
582
+ await this.html.copy(container.innerHTML);
583
+
584
+ break;
585
+ }
586
+ case 'newDocument':
587
+ this.html.set(`<${this.options.get('defaultLine')}><br></${this.options.get('defaultLine')}>`);
588
+ this.focus();
589
+ this.history.push(false);
590
+ break;
591
+ case 'codeView':
592
+ this.viewer.codeView(!this.frameContext.get('isCodeView'));
593
+ break;
594
+ case 'fullScreen':
595
+ this.viewer.fullScreen(!this.frameContext.get('isFullScreen'));
596
+ break;
597
+ case 'indent':
598
+ this.format.indent();
599
+ break;
600
+ case 'outdent':
601
+ this.format.outdent();
602
+ break;
603
+ case 'undo':
604
+ this.history.undo();
605
+ break;
606
+ case 'redo':
607
+ this.history.redo();
608
+ break;
609
+ case 'removeFormat':
610
+ this.format.removeInlineElement();
611
+ this.focus();
612
+ break;
613
+ case 'print':
614
+ this.viewer.print();
615
+ break;
616
+ case 'preview':
617
+ this.viewer.preview();
618
+ break;
619
+ case 'showBlocks':
620
+ this.viewer.showBlocks(!this.frameContext.get('isShowBlocks'));
621
+ break;
622
+ case 'dir':
623
+ this.setDir(this.options.get('_rtl') ? 'ltr' : 'rtl');
624
+ break;
625
+ case 'dir_ltr':
626
+ this.setDir('ltr');
627
+ break;
628
+ case 'dir_rtl':
629
+ this.setDir('rtl');
630
+ break;
631
+ case 'save':
632
+ await SAVE(this);
633
+ break;
634
+ case 'copyFormat':
635
+ COPY_FORMAT(this, button);
636
+ break;
637
+ case 'pageBreak':
638
+ PAGE_BREAK(this);
639
+ break;
640
+ case 'pageUp':
641
+ this.frameContext.get('documentType').pageUp();
642
+ break;
643
+ case 'pageDown':
644
+ this.frameContext.get('documentType').pageDown();
645
+ break;
646
+ default:
647
+ FONT_STYLE(this, command);
648
+ }
649
+ },
650
+
651
+ /**
652
+ * @description Execute "editor.run" with command button.
653
+ * @param {Node} target Command target
654
+ */
655
+ runFromTarget(target) {
656
+ if (dom.check.isInputElement(target)) return;
657
+
658
+ const targetBtn = /** @type {HTMLButtonElement} */ (dom.query.getCommandTarget(target));
659
+ if (!targetBtn) return;
660
+
661
+ const command = targetBtn.getAttribute('data-command');
662
+ const type = targetBtn.getAttribute('data-type');
663
+
664
+ if (!command && !type) return;
665
+ if (targetBtn.disabled) return;
666
+
667
+ this.run(command, type, target);
668
+ },
669
+
670
+ /**
671
+ * @description It is executed by inserting the button of commandTargets as the argument value of the "f" function.
672
+ * - "func" is called as long as the button array's length.
673
+ * @param {string} cmd data-command
674
+ * @param {(...args: *) => *} func Function.
675
+ */
676
+ applyCommandTargets(cmd, func) {
677
+ if (this.commandTargets.has(cmd)) {
678
+ this.commandTargets.get(cmd).forEach(func);
679
+ }
680
+ },
681
+
682
+ /**
683
+ * @description Execute a function by traversing all root targets.
684
+ * @param {(...args: *) => *} f Function
685
+ */
686
+ applyFrameRoots(f) {
687
+ this.frameRoots.forEach(f);
688
+ },
689
+
690
+ /**
691
+ * @description Checks if the content of the editor is empty.
692
+ * - Display criteria for "placeholder".
693
+ * @param {?__se__FrameContext=} fc Frame context, if not present, currently selected frame context.
694
+ * @returns {boolean}
695
+ */
696
+ isEmpty(fc) {
697
+ fc = fc || this.frameContext;
698
+ const wysiwyg = fc.get('wysiwyg');
699
+ return dom.check.isZeroWidth(wysiwyg.textContent) && !wysiwyg.querySelector(this.options.get('allowedEmptyTags')) && (wysiwyg.innerText.match(/\n/g) || '').length <= 1;
700
+ },
701
+
702
+ /**
703
+ * @description Set direction to "rtl" or "ltr".
704
+ * @param {string} dir "rtl" or "ltr"
705
+ */
706
+ setDir(dir) {
707
+ const rtl = dir === 'rtl';
708
+ if (this.options.get('_rtl') === rtl) return;
709
+
710
+ try {
711
+ this.options.set('_rtl', rtl);
712
+ this.ui._offCurrentController();
713
+
714
+ const fc = this.frameContext;
715
+ const plugins = this.plugins;
716
+ for (const k in plugins) {
717
+ if (typeof plugins[k].setDir === 'function') plugins[k].setDir(dir);
718
+ }
719
+
720
+ const toolbarWrapper = this.context.get('toolbar._wrapper');
721
+ const statusbarWrapper = this.context.get('statusbar._wrapper');
722
+ if (rtl) {
723
+ this.applyFrameRoots((e) => {
724
+ dom.utils.addClass([e.get('topArea'), e.get('wysiwyg'), e.get('documentTypePageMirror')], 'se-rtl');
725
+ });
726
+ dom.utils.addClass([this.carrierWrapper, toolbarWrapper, statusbarWrapper], 'se-rtl');
727
+ } else {
728
+ this.applyFrameRoots((e) => {
729
+ dom.utils.removeClass([e.get('topArea'), e.get('wysiwyg'), e.get('documentTypePageMirror')], 'se-rtl');
730
+ });
731
+ dom.utils.removeClass([this.carrierWrapper, toolbarWrapper, statusbarWrapper], 'se-rtl');
732
+ }
733
+
734
+ const lineNodes = dom.query.getListChildren(fc.get('wysiwyg'), (current) => {
735
+ return this.format.isLine(current) && !!(current.style.marginRight || current.style.marginLeft || current.style.textAlign);
736
+ });
737
+
738
+ for (let i = 0, n, l, r; (n = lineNodes[i]); i++) {
739
+ n = lineNodes[i];
740
+ // indent margin
741
+ r = n.style.marginRight;
742
+ l = n.style.marginLeft;
743
+ if (r || l) {
744
+ n.style.marginRight = l;
745
+ n.style.marginLeft = r;
746
+ }
747
+ // text align
748
+ r = n.style.textAlign;
749
+ if (r === 'left') n.style.textAlign = 'right';
750
+ else if (r === 'right') n.style.textAlign = 'left';
751
+ }
752
+
753
+ DIR_BTN_ACTIVE(this, rtl);
754
+
755
+ // document type
756
+ if (fc.has('documentType-use-header')) {
757
+ if (rtl) fc.get('wrapper').appendChild(fc.get('documentTypeInner'));
758
+ else fc.get('wrapper').insertBefore(fc.get('documentTypeInner'), fc.get('wysiwygFrame'));
759
+ }
760
+ if (fc.has('documentType-use-page')) {
761
+ if (rtl) fc.get('wrapper').insertBefore(fc.get('documentTypePage'), fc.get('wysiwygFrame'));
762
+ else fc.get('wrapper').appendChild(fc.get('documentTypePage'));
763
+ }
764
+
765
+ if (this.isBalloon) this.toolbar._showBalloon();
766
+ else if (this.isSubBalloon) this.subToolbar._showBalloon();
767
+ } catch (e) {
768
+ this.options.set('_rtl', !rtl);
769
+ console.warn(`[SUNEDITOR.setDir.fail] ${e.toString()}`);
770
+ }
771
+
772
+ this.effectNode = null;
773
+ this.eventManager.applyTagEffect();
774
+ },
775
+
776
+ /**
777
+ * @description Add or reset option property (Editor is reloaded)
778
+ * @param {EditorInitOptions_editor} newOptions Options
779
+ */
780
+ resetOptions(newOptions) {
781
+ this.viewer.codeView(false);
782
+ this.viewer.showBlocks(false);
783
+
784
+ const rootDiff = new Map();
785
+ const frameRoots = this.frameRoots;
786
+ const newRoots = [];
787
+ const newRootKeys = new Map();
788
+
789
+ // frame roots
790
+ const nRoot = {};
791
+ for (const k in newOptions) {
792
+ if (OPTION_FRAME_FIXED_FLAG[k] === undefined) continue;
793
+ nRoot[k] = newOptions[k];
794
+ delete newOptions[k];
795
+ }
796
+ for (const rootKey of frameRoots.keys()) {
797
+ newOptions[rootKey || ''] = { ...nRoot, ...newOptions[rootKey || ''] };
798
+ }
799
+
800
+ // check reoption validation
801
+ const newOptionKeys = Object.keys(newOptions);
802
+ CheckResetKeys(newOptionKeys, this.plugins, '');
803
+ if (newOptionKeys.length === 0) return;
804
+
805
+ if (frameRoots.size === 1) {
806
+ newOptionKeys.unshift(null);
807
+ }
808
+
809
+ // option merge
810
+ const _originOptions = [this._originOptions, newOptions].reduce((init, option) => {
811
+ for (const key in option) {
812
+ if (frameRoots.has(key || null)) {
813
+ RestoreFrameOptions(key, option, frameRoots, rootDiff, newRootKeys, newRoots);
814
+ } else {
815
+ init[key] = option[key];
816
+ }
817
+ }
818
+ return init;
819
+ }, {});
820
+
821
+ // init options
822
+ const options = this.options;
823
+ const newO = InitOptions(_originOptions, newRoots, this.plugins);
824
+ const newOptionMap = newO.o;
825
+ const newFrameMap = newO.frameMap;
826
+ /** --------- [root start] --------- */
827
+ for (let i = 0, len = newOptionKeys.length, k; i < len; i++) {
828
+ k = newOptionKeys[i] || null;
829
+
830
+ if (newRootKeys.has(k)) {
831
+ const diff = rootDiff.get(k);
832
+ const fc = frameRoots.get(k);
833
+ const originOptions = fc.get('options');
834
+ const newRootOptions = newFrameMap.get(k);
835
+
836
+ // --- set options : fc ---
837
+ fc.set('options', newRootOptions);
838
+
839
+ // statusbar-changed
840
+ if (diff.has('statusbar-changed')) {
841
+ // statusbar
842
+ dom.utils.removeItem(fc.get('statusbar'));
843
+ if (newRootOptions.get('statusbar')) {
844
+ const statusbar = CreateStatusbar(newRootOptions, null).statusbar;
845
+ fc.get('container').appendChild(statusbar);
846
+ UpdateStatusbarContext(statusbar, fc);
847
+ this.eventManager.__addStatusbarEvent(fc, newRootOptions);
848
+ } else {
849
+ this.eventManager.removeEvent(originOptions.get('__statusbarEvent'));
850
+ newRootOptions.set('__statusbarEvent', null);
851
+ UpdateStatusbarContext(null, fc);
852
+ }
853
+ // charCounter
854
+ if (fc.get('statusbar')) {
855
+ this.char.display(fc);
856
+ }
857
+ }
858
+
859
+ // iframe's options
860
+ if (diff.has('iframe_attributes')) {
861
+ const frame = fc.get('wysiwygFrame');
862
+ const originAttr = originOptions.get('iframe_attributes');
863
+ const newAttr = newRootOptions.get('iframe_attributes');
864
+ for (const origin_k in originAttr) frame.removeAttribute(origin_k, originAttr[origin_k]);
865
+ for (const new_k in newAttr) frame.setAttribute(new_k, newAttr[new_k]);
866
+ }
867
+
868
+ if (diff.has('iframe_cssFileName')) {
869
+ const docHead = fc.get('_wd').head;
870
+ const links = docHead.getElementsByTagName('link');
871
+ while (links[0]) docHead.removeChild(links[0]);
872
+ const parseDocument = new DOMParser().parseFromString(converter._setIframeStyleLinks(newRootOptions.get('iframe_cssFileName')), 'text/html');
873
+ const newLinks = parseDocument.head.children;
874
+ const sTag = docHead.querySelector('style');
875
+ while (newLinks[0]) docHead.insertBefore(newLinks[0], sTag);
876
+ }
877
+
878
+ if (diff.has('placeholder')) {
879
+ fc.get('placeholder').textContent = newRootOptions.get('placeholder');
880
+ }
881
+
882
+ // frame styles
883
+ this.ui.setEditorStyle(newRootOptions.get('editorStyle'), fc);
884
+
885
+ // frame attributes
886
+ const frame = fc.get('wysiwyg');
887
+ const originAttr = originOptions.get('editableFrameAttributes');
888
+ const newAttr = newRootOptions.get('editableFrameAttributes');
889
+ for (const origin_k in originAttr) frame.removeAttribute(origin_k, originAttr[origin_k]);
890
+ for (const new_k in newAttr) frame.setAttribute(new_k, newAttr[new_k]);
891
+
892
+ continue;
893
+ }
894
+ /** --------- [root end] --------- */
895
+
896
+ // --- set options ---
897
+ options.set(k, newOptionMap.get(k));
898
+
899
+ /** Options that require a function call */
900
+ switch (k) {
901
+ case 'theme': {
902
+ this.ui.setTheme(options.get('theme'));
903
+ break;
904
+ }
905
+ case 'events': {
906
+ const events = options.get('events');
907
+ for (const name in events) {
908
+ this.events[name] = events[name];
909
+ }
910
+ break;
911
+ }
912
+ case 'autoStyleify': {
913
+ this.html.__resetAutoStyleify(options.get('autoStyleify'));
914
+ break;
915
+ }
916
+ case 'textDirection': {
917
+ this.setDir(options.get('_rtl') ? 'ltr' : 'rtl');
918
+ break;
919
+ }
920
+ case 'historyStackDelayTime': {
921
+ this.history.resetDelayTime(options.get('historyStackDelayTime'));
922
+ break;
923
+ }
924
+ case 'defaultLineBreakFormat': {
925
+ this.format.__resetBrLineBreak(options.get('defaultLineBreakFormat'));
926
+ }
927
+ }
928
+ }
929
+
930
+ /** apply options */
931
+ // _origin
932
+ this._originOptions = _originOptions;
933
+
934
+ // --- [toolbar] ---
935
+ const toolbar = this.context.get('toolbar.main');
936
+ // width
937
+ if (/inline|balloon/i.test(options.get('mode')) && newOptionKeys.includes('toolbar_width')) {
938
+ toolbar.style.width = options.get('toolbar_width');
939
+ }
940
+ // hide
941
+ if (options.get('toolbar_hide')) {
942
+ toolbar.style.display = 'none';
943
+ } else {
944
+ toolbar.style.display = '';
945
+ }
946
+ // shortcuts hint
947
+ if (options.get('shortcutsHint')) {
948
+ dom.utils.removeClass(toolbar, 'se-shortcut-hide');
949
+ } else {
950
+ dom.utils.addClass(toolbar, 'se-shortcut-hide');
951
+ }
952
+
953
+ this.effectNode = null;
954
+ this._setFrameInfo(this.frameRoots.get(this.status.rootKey));
955
+ },
956
+
957
+ /**
958
+ * @description Change the current root index.
959
+ * @param {*} rootKey
960
+ */
961
+ changeFrameContext(rootKey) {
962
+ if (rootKey === this.status.rootKey) return;
963
+
964
+ this.status.rootKey = rootKey;
965
+ this._setFrameInfo(this.frameRoots.get(rootKey));
966
+ this.toolbar._resetSticky();
967
+ },
968
+
969
+ /**
970
+ * @description Sets a CSS variable on the root element of the editor.
971
+ * If the editor is using an iframe or multi root applies it to all iframe roots instead.
972
+ * @param {string} name - The CSS variable name (e.g. `--se-color-primary`)
973
+ * @param {string} value - The CSS variable value
974
+ */
975
+ setRootCssVar(name, value) {
976
+ if (this.frameOptions.get('iframe')) {
977
+ this.applyFrameRoots((root) => {
978
+ root.get('_wd').documentElement.style.setProperty(name, value);
979
+ });
980
+ } else {
981
+ this._d.documentElement.style.setProperty(name, value);
982
+ }
983
+ },
984
+
985
+ /**
986
+ * @description javascript execCommand
987
+ * @param {string} command javascript execCommand function property
988
+ * @param {boolean=} showDefaultUI javascript execCommand function property
989
+ * @param {string=} value javascript execCommand function property
990
+ */
991
+ execCommand(command, showDefaultUI, value) {
992
+ this.frameContext.get('_wd').execCommand(command, showDefaultUI, command === 'formatBlock' ? '<' + value + '>' : value);
993
+ this.history.push(true);
994
+ },
995
+
996
+ /**
997
+ * @description Focus to wysiwyg area
998
+ * @param {*} rootKey Root index
999
+ */
1000
+ focus(rootKey) {
1001
+ if (rootKey) this.changeFrameContext(rootKey);
1002
+ if (this.frameContext.get('wysiwygFrame').style.display === 'none') return;
1003
+ this._preventBlur = false;
1004
+
1005
+ if (this.frameOptions.get('iframe') || !this.frameContext.get('wysiwyg').contains(this.selection.getNode())) {
1006
+ this._nativeFocus();
1007
+ } else {
1008
+ try {
1009
+ const range = this.selection.getRange();
1010
+ if (range.startContainer === range.endContainer && dom.check.isWysiwygFrame(range.startContainer)) {
1011
+ const currentNode = /** @type {HTMLElement} */ (range.commonAncestorContainer).children[range.startOffset];
1012
+ if (!this.format.isLine(currentNode) && !this.component.is(currentNode)) {
1013
+ const br = dom.utils.createElement('BR');
1014
+ const format = dom.utils.createElement(this.options.get('defaultLine'), null, br);
1015
+ this.frameContext.get('wysiwyg').insertBefore(format, currentNode);
1016
+ this.selection.setRange(br, 0, br, 0);
1017
+ return;
1018
+ }
1019
+ }
1020
+ this.selection.setRange(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
1021
+ } catch (e) {
1022
+ console.warn('[SUNEDITOR.focus.warn] ', e);
1023
+ this._nativeFocus();
1024
+ }
1025
+ }
1026
+
1027
+ if (this.isBalloon) this.eventManager._toggleToolbarBalloon();
1028
+ },
1029
+
1030
+ /**
1031
+ * @description If "focusEl" is a component, then that component is selected; if it is a format element, the last text is selected
1032
+ * - If "focusEdge" is null, then selected last element
1033
+ * @param {?Node=} focusEl Focus element
1034
+ */
1035
+ focusEdge(focusEl) {
1036
+ this._preventBlur = false;
1037
+ if (!focusEl) focusEl = this.frameContext.get('wysiwyg').lastElementChild;
1038
+
1039
+ const fileComponentInfo = this.component.get(focusEl);
1040
+ if (fileComponentInfo) {
1041
+ this.component.select(fileComponentInfo.target, fileComponentInfo.pluginName);
1042
+ } else if (focusEl) {
1043
+ if (focusEl.nodeType !== 3) {
1044
+ focusEl = dom.query.getEdgeChild(
1045
+ focusEl,
1046
+ function (current) {
1047
+ return current.childNodes.length === 0 || current.nodeType === 3;
1048
+ },
1049
+ true
1050
+ );
1051
+ }
1052
+ if (!focusEl) this._nativeFocus();
1053
+ else this.selection.setRange(focusEl, focusEl.textContent.length, focusEl, focusEl.textContent.length);
1054
+ } else {
1055
+ this.focus();
1056
+ }
1057
+ },
1058
+
1059
+ /**
1060
+ * @description Focusout to wysiwyg area (.blur())
1061
+ */
1062
+ blur() {
1063
+ if (this.frameOptions.get('iframe')) {
1064
+ this.frameContext.get('wysiwygFrame').blur();
1065
+ } else {
1066
+ this.frameContext.get('wysiwyg').blur();
1067
+ }
1068
+ },
1069
+
1070
+ /**
1071
+ * @description Destroy the suneditor
1072
+ */
1073
+ destroy() {
1074
+ /** remove history */
1075
+ this.history.destroy();
1076
+
1077
+ /** remove event listeners */
1078
+ this.eventManager._removeAllEvents();
1079
+
1080
+ /** destroy external library */
1081
+ if (this.options.get('codeMirror6Editor')) {
1082
+ this.options.get('codeMirror6Editor').destroy();
1083
+ }
1084
+
1085
+ /** remove element */
1086
+ dom.utils.removeItem(this.carrierWrapper);
1087
+ dom.utils.removeItem(this.context.get('toolbar._wrapper'));
1088
+ dom.utils.removeItem(this.context.get('toolbar.sub._wrapper'));
1089
+ dom.utils.removeItem(this.context.get('statusbar._wrapper'));
1090
+ this.applyFrameRoots((e) => {
1091
+ dom.utils.removeItem(e.get('topArea'));
1092
+ e.get('options').clear();
1093
+ e.clear();
1094
+ });
1095
+
1096
+ /** remove object reference */
1097
+ this.options.clear();
1098
+ this.context.clear();
1099
+
1100
+ let obj = this.plugins;
1101
+ for (const k in obj) {
1102
+ const p = obj[k];
1103
+ if (typeof p._destroy === 'function') p._destroy();
1104
+ for (const pk in p) {
1105
+ delete p[pk];
1106
+ }
1107
+ delete obj[k];
1108
+ }
1109
+ obj = this.events;
1110
+ for (const k in obj) {
1111
+ delete obj[k];
1112
+ }
1113
+
1114
+ obj = ['eventManager', 'instanceCheck', 'char', 'component', 'format', 'html', 'menu', 'nodeTransform', 'offset', 'selection', 'shortcuts', 'toolbar', 'ui', 'viewer'];
1115
+ for (let i = 0, len = obj.length, c; i < len; i++) {
1116
+ c = this[obj[i]];
1117
+ for (const k in c) {
1118
+ delete c[k];
1119
+ }
1120
+ }
1121
+ obj = this.subToolbar;
1122
+ if (obj) {
1123
+ for (const k in obj) {
1124
+ delete obj[k];
1125
+ }
1126
+ }
1127
+
1128
+ obj = null;
1129
+ for (const k in this) {
1130
+ delete this[k];
1131
+ }
1132
+
1133
+ return null;
1134
+ },
1135
+
1136
+ /** ----- private methods ----------------------------------------------------------------------------------------------------------------------------- */
1137
+ /**
1138
+ * @private
1139
+ * @description Set frameContext, frameOptions
1140
+ * @param {__se__FrameContext} rt Root target[key] FrameContext
1141
+ */
1142
+ _setFrameInfo(rt) {
1143
+ this.frameContext = rt;
1144
+ this.frameOptions = rt.get('options');
1145
+ rt.set('_editorHeight', rt.get('wysiwygFrame').offsetHeight);
1146
+ this._lineBreaker_t = rt.get('lineBreaker_t');
1147
+ this._lineBreaker_b = rt.get('lineBreaker_b');
1148
+ },
1149
+
1150
+ /**
1151
+ * @private
1152
+ * @description Focus to wysiwyg area using "native focus function"
1153
+ */
1154
+ _nativeFocus() {
1155
+ this.selection.__focus();
1156
+ this.selection._init();
1157
+ },
1158
+
1159
+ /**
1160
+ * @private
1161
+ * @description Check the components such as image and video and modify them according to the format.
1162
+ * @param {boolean} loaded If true, the component is loaded.
1163
+ */
1164
+ _checkComponents(loaded) {
1165
+ for (let i = 0, len = this._fileInfoPluginsCheck.length; i < len; i++) {
1166
+ this._fileInfoPluginsCheck[i](loaded);
1167
+ }
1168
+ },
1169
+
1170
+ /**
1171
+ * @private
1172
+ * @description Initialize the information of the components.
1173
+ */
1174
+ _resetComponents() {
1175
+ for (let i = 0, len = this._fileInfoPluginsReset.length; i < len; i++) {
1176
+ this._fileInfoPluginsReset[i]();
1177
+ }
1178
+ },
1179
+
1180
+ /**
1181
+ * @private
1182
+ * @description Initializ wysiwyg area (Only called from core._init)
1183
+ * @param {__se__FrameContext} e frameContext
1184
+ * @param {string} value initial html string
1185
+ */
1186
+ _initWysiwygArea(e, value) {
1187
+ // set content
1188
+ e.get('wysiwyg').innerHTML =
1189
+ this.html.clean(typeof value === 'string' ? value : (/^TEXTAREA$/i.test(e.get('originElement').nodeName) ? e.get('originElement').value : e.get('originElement').innerHTML) || '', {
1190
+ forceFormat: true,
1191
+ whitelist: null,
1192
+ blacklist: null,
1193
+ _freeCodeViewMode: this.options.get('freeCodeViewMode')
1194
+ }) || '<' + this.options.get('defaultLine') + '><br></' + this.options.get('defaultLine') + '>';
1195
+
1196
+ // char counter
1197
+ if (e.has('charCounter')) e.get('charCounter').textContent = this.char.getLength();
1198
+
1199
+ // document type init
1200
+ if (this.options.get('type') === 'document') {
1201
+ e.set('documentType', new DocumentType(this, e));
1202
+ if (e.get('documentType').useHeader) {
1203
+ e.set('documentType-use-header', true);
1204
+ }
1205
+ if (e.get('documentType').usePage) {
1206
+ e.set('documentType-use-page', true);
1207
+ e.get('documentTypePageMirror').innerHTML = e.get('wysiwyg').innerHTML;
1208
+ }
1209
+ }
1210
+ },
1211
+
1212
+ /**
1213
+ * @private
1214
+ * @description Called when there are changes to tags in the wysiwyg region.
1215
+ * @param {__se__FrameContext} fc - Frame context object
1216
+ */
1217
+ _resourcesStateChange(fc) {
1218
+ this._iframeAutoHeight(fc);
1219
+ this._checkPlaceholder(fc);
1220
+ // document type page
1221
+ if (fc.has('documentType-use-page') && fc.get('options').get('height') !== 'auto') {
1222
+ fc.get('documentTypePageMirror').innerHTML = fc.get('wysiwyg').innerHTML;
1223
+ fc.get('documentType').rePage(true);
1224
+ }
1225
+ },
1226
+
1227
+ /**
1228
+ * @private
1229
+ * @description Modify the height value of the iframe when the height of the iframe is automatic.
1230
+ * @param {__se__FrameContext} fc - Frame context object
1231
+ */
1232
+ _iframeAutoHeight(fc) {
1233
+ const autoFrame = fc.get('_iframeAuto');
1234
+
1235
+ if (autoFrame) {
1236
+ this._w.setTimeout(() => {
1237
+ const h = autoFrame.offsetHeight;
1238
+ fc.get('wysiwygFrame').style.height = h + 'px';
1239
+ if (!env.isResizeObserverSupported) this.__callResizeFunction(fc, h, null);
1240
+ }, 0);
1241
+ } else if (!env.isResizeObserverSupported) {
1242
+ this.__callResizeFunction(fc, fc.get('wysiwygFrame').offsetHeight, null);
1243
+ }
1244
+ },
1245
+
1246
+ /**
1247
+ * @private
1248
+ * @description Call the "onResizeEditor" event
1249
+ * @param {__se__FrameContext} fc - Frame context object
1250
+ * @param {number} h - Height value
1251
+ * @param {ResizeObserverEntry} resizeObserverEntry - ResizeObserverEntry object
1252
+ */
1253
+ __callResizeFunction(fc, h, resizeObserverEntry) {
1254
+ h =
1255
+ h === -1
1256
+ ? resizeObserverEntry?.borderBoxSize && resizeObserverEntry.borderBoxSize[0]
1257
+ ? resizeObserverEntry.borderBoxSize[0].blockSize
1258
+ : resizeObserverEntry.contentRect.height + numbers.get(fc.get('wwComputedStyle').getPropertyValue('padding-left')) + numbers.get(fc.get('wwComputedStyle').getPropertyValue('padding-right'))
1259
+ : h;
1260
+ if (fc.get('_editorHeight') !== h) {
1261
+ this.triggerEvent('onResizeEditor', { height: h, prevHeight: fc.get('_editorHeight'), frameContext: fc, observerEntry: resizeObserverEntry });
1262
+ fc.set('_editorHeight', h);
1263
+ }
1264
+
1265
+ // document type page
1266
+ if (fc.has('documentType-use-page')) {
1267
+ fc.get('documentType').resizePage();
1268
+ }
1269
+ },
1270
+
1271
+ /**
1272
+ * @private
1273
+ * @description Set display property when there is placeholder.
1274
+ * @param {?__se__FrameContext=} fc - Frame context object, If null fc is this.frameContext
1275
+ */
1276
+ _checkPlaceholder(fc) {
1277
+ fc = fc || this.frameContext;
1278
+ const placeholder = fc.get('placeholder');
1279
+
1280
+ if (placeholder) {
1281
+ if (fc.get('isCodeView')) {
1282
+ placeholder.style.display = 'none';
1283
+ return;
1284
+ }
1285
+
1286
+ if (this.isEmpty(fc)) {
1287
+ placeholder.style.display = 'block';
1288
+ } else {
1289
+ placeholder.style.display = 'none';
1290
+ }
1291
+ }
1292
+ },
1293
+
1294
+ /**
1295
+ * @private
1296
+ * @description Initializ editor
1297
+ * @param {EditorInitOptions_editor} options Options
1298
+ */
1299
+ __editorInit(options) {
1300
+ this.status.initViewportHeight = this._w.visualViewport.height;
1301
+ this.eventManager.__setViewportSize();
1302
+
1303
+ this.applyFrameRoots((e) => {
1304
+ this.__setEditorParams(e);
1305
+ });
1306
+
1307
+ // initialize core and add event listeners
1308
+ this._setFrameInfo(this.frameRoots.get(this.status.rootKey));
1309
+ this.__init(options);
1310
+ for (const v of this._onPluginEvents.values()) {
1311
+ v.sort((a, b) => a.index - b.index);
1312
+ }
1313
+
1314
+ this.applyFrameRoots((e) => {
1315
+ this.eventManager._addFrameEvents(e);
1316
+ this._initWysiwygArea(e, e.get('options').get('value'));
1317
+ if (e.get('options').get('iframe') && e.get('options').get('height') === 'auto') {
1318
+ this.__callResizeFunction(e, e.get('wysiwygFrame').offsetHeight, null);
1319
+ }
1320
+ });
1321
+
1322
+ this.eventManager.__eventDoc = null;
1323
+ this._componentsInfoInit = false;
1324
+ this._componentsInfoReset = false;
1325
+ this._checkComponents(true);
1326
+
1327
+ this._w.setTimeout(() => {
1328
+ // toolbar visibility
1329
+ this.context.get('toolbar.main').style.visibility = '';
1330
+ // roots
1331
+ this.applyFrameRoots((e) => {
1332
+ if (typeof this._resourcesStateChange !== 'function') return;
1333
+ // observer
1334
+ if (this.eventManager._wwFrameObserver) this.eventManager._wwFrameObserver.observe(e.get('wysiwygFrame'));
1335
+ if (this.eventManager._toolbarObserver) this.eventManager._toolbarObserver.observe(e.get('_toolbarShadow'));
1336
+ // resource state
1337
+ this._resourcesStateChange(e);
1338
+ });
1339
+ // history reset
1340
+ this.history.reset();
1341
+ // user event
1342
+ this.triggerEvent('onload', {});
1343
+ }, 0);
1344
+ },
1345
+
1346
+ /**
1347
+ * @private
1348
+ * @description Initializ core variable
1349
+ * @param {EditorInitOptions_editor} options Options
1350
+ */
1351
+ __init(options) {
1352
+ // file components
1353
+ this._fileInfoPluginsCheck = [];
1354
+ this._fileInfoPluginsReset = [];
1355
+
1356
+ // text components
1357
+ this._MELInfo = new Map();
1358
+
1359
+ // Command and file plugins registration
1360
+ this.activeCommands = ACTIVE_EVENT_COMMANDS;
1361
+ this._onPluginEvents = new Map([
1362
+ ['onMouseMove', []],
1363
+ ['onMouseLeave', []],
1364
+ ['onMouseDown', []],
1365
+ ['onMouseUp', []],
1366
+ ['onScroll', []],
1367
+ ['onClick', []],
1368
+ ['onInput', []],
1369
+ ['onKeyDown', []],
1370
+ ['onKeyUp', []],
1371
+ ['onFocus', []],
1372
+ ['onBlur', []],
1373
+ ['onPaste', []],
1374
+ ['onFilePasteAndDrop', []]
1375
+ ]);
1376
+ this._fileManager.tags = [];
1377
+ this._fileManager.pluginMap = {};
1378
+ this._fileManager.tagAttrs = {};
1379
+
1380
+ const plugins = this.plugins;
1381
+ const filePluginRegExp = [];
1382
+ let plugin;
1383
+ for (const key in plugins) {
1384
+ this.registerPlugin(key, this._pluginCallButtons[key], options[key]);
1385
+ this.registerPlugin(key, this._pluginCallButtons_sub[key], options[key]);
1386
+ plugin = this.plugins[key];
1387
+
1388
+ // Filemanager
1389
+ if (typeof plugin.__fileManagement === 'object') {
1390
+ const fm = plugin.__fileManagement;
1391
+ this._fileInfoPluginsCheck.push(fm._checkInfo.bind(fm));
1392
+ this._fileInfoPluginsReset.push(fm._resetInfo.bind(fm));
1393
+ if (Array.isArray(fm.tagNames)) {
1394
+ const tagNames = fm.tagNames;
1395
+ this._fileManager.tags = this._fileManager.tags.concat(tagNames);
1396
+ filePluginRegExp.push(key);
1397
+ for (let tag = 0, tLen = tagNames.length, t; tag < tLen; tag++) {
1398
+ t = tagNames[tag].toLowerCase();
1399
+ this._fileManager.pluginMap[t] = key;
1400
+ if (fm.tagAttrs) {
1401
+ this._fileManager.tagAttrs[t] = fm.tagAttrs;
1402
+ }
1403
+ }
1404
+ }
1405
+ }
1406
+
1407
+ // Not file component
1408
+ if (typeof plugin.constructor.component === 'function') {
1409
+ this._componentManager.push(
1410
+ function (launcher, element) {
1411
+ if (!element || !(element = launcher.component?.call(this, element))) return null;
1412
+ return {
1413
+ target: element,
1414
+ pluginName: launcher.key,
1415
+ options: launcher.options
1416
+ };
1417
+ }.bind(plugin, plugin.constructor)
1418
+ );
1419
+ }
1420
+
1421
+ // plugin event
1422
+ const pluginOptions = plugin.constructor.options || {};
1423
+ this._onPluginEvents.forEach((v, k) => {
1424
+ if (typeof plugin[k] === 'function') {
1425
+ const f = plugin[k].bind(plugin);
1426
+ f.index = pluginOptions[`eventIndex_${k}`] || pluginOptions.eventIndex || 0;
1427
+ v.push(f);
1428
+ }
1429
+ });
1430
+
1431
+ // plugin maintain
1432
+ if (plugin.retainFormat) {
1433
+ const info = plugin.retainFormat();
1434
+ this._MELInfo.set(info.query, { key: plugin.constructor.key, method: info.method });
1435
+ }
1436
+ }
1437
+
1438
+ if (this.options.get('buttons').has('pageBreak') || this.options.get('buttons_sub')?.has('pageBreak')) {
1439
+ this._componentManager.push((element) => {
1440
+ if (!element || !dom.utils.hasClass(element, 'se-page-break')) return null;
1441
+ return {
1442
+ target: element,
1443
+ launcher: {
1444
+ destroy: (target) => {
1445
+ const focusEl = target.previousElementSibling || target.nextElementSibling;
1446
+ dom.utils.removeItem(target);
1447
+ // focus
1448
+ this.focusEdge(focusEl);
1449
+ this.history.push(false);
1450
+ }
1451
+ }
1452
+ };
1453
+ });
1454
+ }
1455
+
1456
+ this._fileManager.regExp = new RegExp(`^(${this._fileManager.tags.join('|') || '\\^'})$`, 'i');
1457
+ this._fileManager.pluginRegExp = new RegExp(`^(${filePluginRegExp.length === 0 ? '\\^' : filePluginRegExp.join('|')})$`, 'i');
1458
+
1459
+ delete this._pluginCallButtons;
1460
+ delete this._pluginCallButtons_sub;
1461
+
1462
+ this.__cachingButtons();
1463
+ this.__cachingShortcuts();
1464
+ },
1465
+
1466
+ /**
1467
+ * @private
1468
+ * @description Caching basic buttons to use
1469
+ */
1470
+ __cachingButtons() {
1471
+ const ctx = this.context;
1472
+ this.__setDisabledButtons();
1473
+ this.__saveCommandButtons(this.allCommandButtons, ctx.get('toolbar.buttonTray'));
1474
+ if (this.options.has('_subMode')) {
1475
+ this.__saveCommandButtons(this.subAllCommandButtons, ctx.get('toolbar.sub.buttonTray'));
1476
+ }
1477
+ },
1478
+
1479
+ /**
1480
+ * @private
1481
+ * @description Set the disabled button list
1482
+ * - this._codeViewDisabledButtons, this._controllerOnDisabledButtons
1483
+ */
1484
+ __setDisabledButtons() {
1485
+ const ctx = this.context;
1486
+
1487
+ this._codeViewDisabledButtons = converter.nodeListToArray(ctx.get('toolbar.buttonTray').querySelectorAll(DISABLE_BUTTONS_CODEVIEW));
1488
+ this._controllerOnDisabledButtons = converter.nodeListToArray(ctx.get('toolbar.buttonTray').querySelectorAll(DISABLE_BUTTONS_CONTROLLER));
1489
+
1490
+ if (this.options.has('_subMode')) {
1491
+ this._codeViewDisabledButtons = this._codeViewDisabledButtons.concat(converter.nodeListToArray(ctx.get('toolbar.sub.buttonTray').querySelectorAll(DISABLE_BUTTONS_CODEVIEW)));
1492
+ this._controllerOnDisabledButtons = this._controllerOnDisabledButtons.concat(converter.nodeListToArray(ctx.get('toolbar.sub.buttonTray').querySelectorAll(DISABLE_BUTTONS_CONTROLLER)));
1493
+ }
1494
+ },
1495
+
1496
+ /**
1497
+ * @private
1498
+ * @description Save the current buttons
1499
+ * @param {Map<string, Element>} cmdButtons Command button map
1500
+ * @param {Element} tray Button tray
1501
+ */
1502
+ __saveCommandButtons(cmdButtons, tray) {
1503
+ const currentButtons = tray.querySelectorAll(COMMAND_BUTTONS);
1504
+ const shortcuts = this.options.get('shortcuts');
1505
+ const reverseCommandArray = this.options.get('_reverseCommandArray');
1506
+ const keyMap = this.shortcutsKeyMap;
1507
+ const reverseKeys = this.reverseKeys;
1508
+
1509
+ for (let i = 0, len = currentButtons.length, e, c; i < len; i++) {
1510
+ e = /** @type {HTMLButtonElement} */ (currentButtons[i]);
1511
+ c = e.getAttribute('data-command');
1512
+ // command set
1513
+ cmdButtons.set(c, e);
1514
+ this.__setCommandTargets(c, e);
1515
+ // shortcuts
1516
+ CreateShortcuts(c, e, shortcuts[c], keyMap, reverseCommandArray, reverseKeys);
1517
+ }
1518
+ },
1519
+
1520
+ /**
1521
+ * @private
1522
+ * @description Caches custom(starts with "_") shortcut keys for commands.
1523
+ */
1524
+ __cachingShortcuts() {
1525
+ const shortcuts = this.options.get('shortcuts');
1526
+ const reverseCommandArray = this.options.get('_reverseCommandArray');
1527
+ const keyMap = this.shortcutsKeyMap;
1528
+ const reverseKeys = this.reverseKeys;
1529
+ for (const key of Object.keys(shortcuts)) {
1530
+ if (!key.startsWith('_')) continue;
1531
+ CreateShortcuts('', null, shortcuts[key], keyMap, reverseCommandArray, reverseKeys);
1532
+ }
1533
+ },
1534
+
1535
+ /**
1536
+ * @private
1537
+ * @description Sets command target elements.
1538
+ * @param {string} cmd - The command identifier.
1539
+ * @param {HTMLButtonElement} target - The associated command button.
1540
+ */
1541
+ __setCommandTargets(cmd, target) {
1542
+ if (!cmd || !target) return;
1543
+
1544
+ const isBasicCmd = BASIC_COMMANDS.includes(cmd);
1545
+ if (!isBasicCmd && !this.plugins[cmd]) return;
1546
+
1547
+ if (!this.commandTargets.get(cmd)) {
1548
+ this.commandTargets.set(cmd, [target]);
1549
+ } else if (!this.commandTargets.get(cmd).includes(target)) {
1550
+ this.commandTargets.get(cmd).push(target);
1551
+ }
1552
+ },
1553
+
1554
+ /**
1555
+ * @private
1556
+ * @description Configures the document properties of an iframe editor.
1557
+ * @param {HTMLIFrameElement} frame - The editor iframe.
1558
+ * @param {Map<string, *>} originOptions - The original options.
1559
+ * @param {__se__FrameOptions} targetOptions - The new options.
1560
+ */
1561
+ __setIframeDocument(frame, originOptions, targetOptions) {
1562
+ frame.setAttribute('scrolling', 'auto');
1563
+ frame.contentDocument.head.innerHTML =
1564
+ '<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">' +
1565
+ converter._setIframeStyleLinks(targetOptions.get('iframe_cssFileName')) +
1566
+ converter._setAutoHeightStyle(targetOptions.get('height'));
1567
+ frame.contentDocument.body.className = originOptions.get('_editableClass');
1568
+ frame.contentDocument.body.setAttribute('contenteditable', 'true');
1569
+ },
1570
+
1571
+ /**
1572
+ * @private
1573
+ * @description Set the FrameContext parameters and options
1574
+ * @param {__se__FrameContext} e - Frame context object
1575
+ */
1576
+ __setEditorParams(e) {
1577
+ const frameOptions = e.get('options');
1578
+ const _w = this._w;
1579
+
1580
+ e.set('wwComputedStyle', _w.getComputedStyle(e.get('wysiwyg')));
1581
+
1582
+ if (!frameOptions.get('iframe') && typeof ShadowRoot === 'function') {
1583
+ let child = e.get('wysiwygFrame');
1584
+ while (child) {
1585
+ if (child.shadowRoot) {
1586
+ this._shadowRoot = child.shadowRoot;
1587
+ break;
1588
+ } else if (child instanceof ShadowRoot) {
1589
+ this._shadowRoot = child;
1590
+ break;
1591
+ }
1592
+ child = child.parentNode;
1593
+ }
1594
+ }
1595
+
1596
+ // init, validate
1597
+ if (frameOptions.get('iframe')) {
1598
+ e.set('_ww', e.get('wysiwygFrame').contentWindow);
1599
+ e.set('_wd', e.get('wysiwygFrame').contentDocument);
1600
+ e.set('wysiwyg', e.get('_wd').body);
1601
+ // e.get('wysiwyg').className += ' ' + options.get('_editableClass');
1602
+ if (frameOptions.get('_defaultStyles').editor) e.get('wysiwyg').style.cssText = frameOptions.get('_defaultStyles').editor;
1603
+ if (frameOptions.get('height') === 'auto') e.set('_iframeAuto', e.get('_wd').body);
1604
+ } else {
1605
+ e.set('_ww', _w);
1606
+ e.set('_wd', this._d);
1607
+ }
1608
+
1609
+ // wisywig attributes
1610
+ const attr = frameOptions.get('editableFrameAttributes');
1611
+ for (const k in attr) {
1612
+ e.get('wysiwyg').setAttribute(k, attr[k]);
1613
+ }
1614
+ },
1615
+
1616
+ /**
1617
+ * @private
1618
+ * @description Registers and initializes editor classes.
1619
+ */
1620
+ __registerClass() {
1621
+ // use events
1622
+ this.events = { ...Events, ...this.options.get('events') };
1623
+ this.triggerEvent = async (eventName, eventData) => {
1624
+ // [iframe] wysiwyg is disabled, the event is not called.
1625
+ if (eventData?.frameContext?.get('wysiwyg').getAttribute('contenteditable') === 'false') return false;
1626
+ const eventHandler = this.events[eventName];
1627
+ if (typeof eventHandler === 'function') {
1628
+ return await eventHandler({ editor: this, ...eventData });
1629
+ }
1630
+ return env.NO_EVENT;
1631
+ };
1632
+
1633
+ // history function
1634
+ this.history = History(this);
1635
+
1636
+ // eventManager
1637
+ this.eventManager = new EventManager(this);
1638
+
1639
+ // util
1640
+ this.instanceCheck = new InstanceCheck(this);
1641
+
1642
+ // main classes
1643
+ this.offset = new Offset(this);
1644
+ this.shortcuts = new Shortcuts(this);
1645
+ this.toolbar = new Toolbar(this, { keyName: 'toolbar', balloon: this.isBalloon, balloonAlways: this.isBalloonAlways, inline: this.isInline, res: this._responsiveButtons });
1646
+ if (this.options.has('_subMode')) {
1647
+ this.subToolbar = new Toolbar(this, {
1648
+ keyName: 'toolbar.sub',
1649
+ balloon: this.isSubBalloon,
1650
+ balloonAlways: this.isSubBalloonAlways,
1651
+ inline: false,
1652
+ res: this._responsiveButtons_sub
1653
+ });
1654
+ }
1655
+ this.selection = new Selection_(this);
1656
+ this.html = new HTML(this);
1657
+ this.nodeTransform = new NodeTransform(this);
1658
+ this.component = new Component(this);
1659
+ this.format = new Format(this);
1660
+ this.menu = new Menu(this);
1661
+ this.char = new Char(this);
1662
+ this.ui = new UI(this);
1663
+ this.viewer = new Viewer(this);
1664
+
1665
+ // register classes to the eventManager
1666
+ ClassInjector.call(this.eventManager, this);
1667
+ // register main classes
1668
+ ClassInjector.call(this.char, this);
1669
+ ClassInjector.call(this.component, this);
1670
+ ClassInjector.call(this.format, this);
1671
+ ClassInjector.call(this.html, this);
1672
+ ClassInjector.call(this.menu, this);
1673
+ ClassInjector.call(this.nodeTransform, this);
1674
+ ClassInjector.call(this.offset, this);
1675
+ ClassInjector.call(this.selection, this);
1676
+ ClassInjector.call(this.shortcuts, this);
1677
+ ClassInjector.call(this.toolbar, this);
1678
+ ClassInjector.call(this.ui, this);
1679
+ ClassInjector.call(this.viewer, this);
1680
+ if (this.options.has('_subMode')) ClassInjector.call(this.subToolbar, this);
1681
+
1682
+ // delete self reference
1683
+ delete this.eventManager['eventManager'];
1684
+ delete this.char['char'];
1685
+ delete this.component['component'];
1686
+ delete this.format['format'];
1687
+ delete this.html['html'];
1688
+ delete this.menu['menu'];
1689
+ delete this.nodeTransform['nodeTransform'];
1690
+ delete this.offset['offset'];
1691
+ delete this.selection['selection'];
1692
+ delete this.shortcuts['shortcuts'];
1693
+ delete this.toolbar['toolbar'];
1694
+ delete this.ui['ui'];
1695
+ delete this.viewer['viewer'];
1696
+ if (this.subToolbar) delete this.subToolbar['subToolbar'];
1697
+
1698
+ this._responsiveButtons = this._responsiveButtons_sub = null;
1699
+ },
1700
+
1701
+ /**
1702
+ * @private
1703
+ * @description Creates the editor instance and initializes components.
1704
+ * @param {EditorInitOptions_editor} originOptions - The initial editor options.
1705
+ * @returns {Promise<void>}
1706
+ */
1707
+ async __Create(originOptions) {
1708
+ // set modes
1709
+ this.isInline = /inline/i.test(this.options.get('mode'));
1710
+ this.isBalloon = /balloon/i.test(this.options.get('mode'));
1711
+ this.isBalloonAlways = /balloon-always/i.test(this.options.get('mode'));
1712
+ this.isClassic = /classic/i.test(this.options.get('mode'));
1713
+ // set subToolbar modes
1714
+ this.isSubBalloon = /balloon/i.test(this.options.get('_subMode'));
1715
+ this.isSubBalloonAlways = /balloon-always/i.test(this.options.get('_subMode'));
1716
+
1717
+ // register class
1718
+ this.__registerClass();
1719
+
1720
+ // common events
1721
+ this.eventManager._addCommonEvents();
1722
+
1723
+ // init
1724
+ const iframePromises = [];
1725
+ this.applyFrameRoots((e) => {
1726
+ const o = e.get('originElement');
1727
+ const t = e.get('topArea');
1728
+ o.style.display = 'none';
1729
+ t.style.display = 'block';
1730
+ o.parentNode.insertBefore(t, o.nextElementSibling);
1731
+
1732
+ if (e.get('options').get('iframe')) {
1733
+ const iframeLoaded = new Promise((resolve) => {
1734
+ this.eventManager.addEvent(e.get('wysiwygFrame'), 'load', ({ target }) => {
1735
+ this.__setIframeDocument(target, this.options, e.get('options'));
1736
+ resolve();
1737
+ });
1738
+ });
1739
+ iframePromises.push(iframeLoaded);
1740
+ }
1741
+ });
1742
+
1743
+ this.applyFrameRoots((e) => {
1744
+ e.get('wrapper').appendChild(e.get('wysiwygFrame'));
1745
+
1746
+ // document type
1747
+ if (e.get('documentTypeInner')) {
1748
+ if (this.options.get('_rtl')) e.get('wrapper').appendChild(e.get('documentTypeInner'));
1749
+ else e.get('wrapper').insertBefore(e.get('documentTypeInner'), e.get('wysiwygFrame'));
1750
+ }
1751
+ if (e.get('documentTypePage')) {
1752
+ if (this.options.get('_rtl')) e.get('wrapper').insertBefore(e.get('documentTypePage'), e.get('wysiwygFrame'));
1753
+ else e.get('wrapper').appendChild(e.get('documentTypePage'));
1754
+ // page mirror
1755
+ e.get('wrapper').appendChild(e.get('documentTypePageMirror'));
1756
+ }
1757
+ });
1758
+
1759
+ if (iframePromises.length > 0) {
1760
+ await Promise.all(iframePromises);
1761
+ }
1762
+
1763
+ this.__editorInit(originOptions);
1764
+ },
1765
+
1766
+ Constructor: Editor
1767
+ };
1768
+
1769
+ function RestoreFrameOptions(key, option, frameRoots, rootDiff, newRootKeys, newRoots) {
1770
+ const nro = option[key];
1771
+ const newKeys = Object.keys(nro);
1772
+ CheckResetKeys(newKeys, null, key + '.');
1773
+ if (newKeys.length === 0) return false;
1774
+
1775
+ const rootKey = key || null;
1776
+ rootDiff.set(rootKey, new Map());
1777
+
1778
+ const o = frameRoots.get(rootKey).get('options').get('_origin');
1779
+ const no = {};
1780
+ const hasOwn = Object.prototype.hasOwnProperty;
1781
+ for (const rk in nro) {
1782
+ if (!hasOwn.call(OPTION_FRAME_FIXED_FLAG, rk)) continue;
1783
+ const roV = nro[rk];
1784
+ if (!newKeys.includes(rk) || o[rk] === roV) continue;
1785
+ rootDiff.get(rootKey).set(GetResetDiffKey(rk), true);
1786
+ no[rk] = roV;
1787
+ }
1788
+
1789
+ const newO = { ...o, ...no };
1790
+ newRootKeys.set(rootKey, new Map(Object.entries(newO)));
1791
+ newRoots.push({ key: rootKey, options: newO });
1792
+ }
1793
+
1794
+ function GetResetDiffKey(key) {
1795
+ if (/^statusbar|^charCounter/.test(key)) return 'statusbar-changed';
1796
+ return key;
1797
+ }
1798
+
1799
+ function CheckResetKeys(keys, plugins, root) {
1800
+ for (let i = 0, len = keys.length, k; i < len; i++) {
1801
+ k = keys[i];
1802
+ if (OPTION_FIXED_FLAG[k] === 'fixed' || OPTION_FRAME_FIXED_FLAG[k] === 'fixed' || (plugins && plugins[k])) {
1803
+ console.warn(`[SUNEDITOR.warn.resetOptions] The "[${root + k}]" option cannot be changed after the editor is created.`);
1804
+ keys.splice(i--, 1);
1805
+ len--;
1806
+ }
1807
+ }
1808
+ }
1809
+
1810
+ export default Editor;