suneditor 3.0.0-beta.1 → 3.0.0-beta.3

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 (64) hide show
  1. package/CONTRIBUTING.md +166 -29
  2. package/README.md +13 -1
  3. package/dist/suneditor.min.css +1 -1
  4. package/dist/suneditor.min.js +1 -1
  5. package/package.json +13 -5
  6. package/src/assets/{variables.css → design/color.css} +45 -59
  7. package/src/assets/design/index.css +3 -0
  8. package/src/assets/design/size.css +35 -0
  9. package/src/assets/design/typography.css +37 -0
  10. package/src/assets/suneditor-contents.css +1 -1
  11. package/src/assets/suneditor.css +40 -28
  12. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +4 -2
  13. package/src/core/base/eventHandlers/handler_ww_key_input.js +16 -16
  14. package/src/core/base/eventHandlers/handler_ww_mouse.js +1 -1
  15. package/src/core/base/eventManager.js +75 -32
  16. package/src/core/class/char.js +3 -2
  17. package/src/core/class/component.js +38 -14
  18. package/src/core/class/format.js +13 -2
  19. package/src/core/class/html.js +58 -26
  20. package/src/core/class/menu.js +19 -0
  21. package/src/core/class/selection.js +1 -8
  22. package/src/core/class/toolbar.js +2 -1
  23. package/src/core/class/ui.js +3 -1
  24. package/src/core/editor.js +125 -59
  25. package/src/core/section/actives.js +73 -10
  26. package/src/core/section/constructor.js +144 -55
  27. package/src/core/section/documentType.js +2 -2
  28. package/src/events.js +25 -1
  29. package/src/helper/converter.js +23 -1
  30. package/src/helper/dom/domCheck.js +12 -2
  31. package/src/modules/Controller.js +25 -5
  32. package/src/modules/Figure.js +6 -6
  33. package/src/modules/FileManager.js +1 -1
  34. package/src/plugins/command/fileUpload.js +3 -3
  35. package/src/plugins/dropdown/formatBlock.js +4 -13
  36. package/src/plugins/dropdown/table.js +51 -18
  37. package/src/plugins/modal/audio.js +2 -2
  38. package/src/plugins/modal/embed.js +2 -2
  39. package/src/plugins/modal/image.js +3 -3
  40. package/src/plugins/modal/math.js +2 -2
  41. package/src/plugins/modal/video.js +1 -1
  42. package/src/plugins/popup/anchor.js +1 -1
  43. package/src/suneditor.js +1 -1
  44. package/src/themes/dark.css +88 -45
  45. package/types/core/base/eventManager.d.ts +23 -0
  46. package/types/core/class/char.d.ts +2 -1
  47. package/types/core/class/component.d.ts +13 -3
  48. package/types/core/class/format.d.ts +8 -1
  49. package/types/core/class/html.d.ts +8 -0
  50. package/types/core/class/menu.d.ts +8 -0
  51. package/types/core/class/ui.d.ts +1 -1
  52. package/types/core/editor.d.ts +7 -2
  53. package/types/core/section/constructor.d.ts +167 -149
  54. package/types/events.d.ts +3 -0
  55. package/types/helper/converter.d.ts +9 -0
  56. package/types/helper/dom/domCheck.d.ts +7 -0
  57. package/types/helper/index.d.ts +2 -0
  58. package/types/index.d.ts +1 -1
  59. package/types/plugins/dropdown/formatBlock.d.ts +2 -2
  60. package/types/plugins/dropdown/table.d.ts +1 -0
  61. package/.eslintignore +0 -7
  62. package/.eslintrc.json +0 -81
  63. /package/src/assets/icons/{_default.js → defaultIcons.js} +0 -0
  64. /package/types/assets/icons/{_default.d.ts → defaultIcons.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import _icons from '../../assets/icons/_default';
1
+ import _icons from '../../assets/icons/defaultIcons';
2
2
  import _defaultLang from '../../langs/en';
3
3
  import { CreateContext, CreateFrameContext } from './context';
4
4
  import { dom, numbers, converter, env } from '../../helper';
@@ -24,6 +24,9 @@ const DEFAULT_ELEMENT_WHITELIST =
24
24
  'p|pre|blockquote|h1|h2|h3|h4|h5|h6|ol|ul|li|hr|figure|figcaption|img|iframe|audio|video|source|table|thead|tbody|tr|th|td|caption|a|b|strong|var|i|em|u|ins|s|span|strike|del|sub|sup|code|svg|path|details|summary';
25
25
  const DEFAULT_TEXT_STYLE_TAGS = 'strong|span|font|b|var|i|em|u|ins|s|strike|del|sub|sup|mark|a|label|code|summary';
26
26
 
27
+ /* scopeSelectionTags */
28
+ const DEFAULT_SCOPE_SELECTION_TAGS = 'td|table|li|ol|ul|pre|figcaption|blockquote|dl|dt|dd';
29
+
27
30
  const _video_audio_attr = '|controls|autoplay|loop|muted|poster|preload|playsinline|volume|crossorigin|disableRemotePlayback|controlsList';
28
31
  const _iframe_attr = '|allowfullscreen|sandbox|loading|allow|referrerpolicy|frameborder|scrolling';
29
32
  const DEFAULT_ATTRIBUTE_WHITELIST = 'contenteditable|target|href|title|download|rel|src|alt|class|type|colspan|rowspan' + _video_audio_attr + _iframe_attr;
@@ -69,42 +72,6 @@ const DEFAULT_CONTENT_STYLES =
69
72
 
70
73
  const RETAIN_STYLE_MODE = ['repeat', 'always', 'none'];
71
74
 
72
- export const RO_UNAVAILABD = [
73
- 'mode',
74
- 'type',
75
- 'externalLibs',
76
- 'iframe',
77
- 'convertTextTags',
78
- 'textStyleTags',
79
- 'fontSizeUnits',
80
- 'spanStyles',
81
- 'lineStyles',
82
- 'tagStyles',
83
- 'reverseCommands',
84
- 'shortcutsDisable',
85
- 'shortcuts',
86
- 'buttonList',
87
- 'subToolbar',
88
- 'toolbar_container',
89
- 'statusbar_container',
90
- 'elementWhitelist',
91
- 'elementBlacklist',
92
- 'attributeWhitelist',
93
- 'attributeBlacklist',
94
- 'defaultLine',
95
- 'formatClosureBrLine',
96
- 'formatBrLine',
97
- 'formatLine',
98
- 'formatClosureBlock',
99
- 'formatBlock',
100
- '__defaultElementWhitelist',
101
- '__defaultAttributeWhitelist',
102
- '__listCommonStyle',
103
- 'icons',
104
- 'lang',
105
- 'codeMirror'
106
- ];
107
-
108
75
  /**
109
76
  * @typedef {Object} EditorFrameOptions
110
77
  * @property {string} [value=""] - Initial value for the editor.
@@ -185,6 +152,7 @@ export const RO_UNAVAILABD = [
185
152
  * - Formats that include "line", such as "Quote", still operate on a "line" basis.
186
153
  * - ● suneditor processes work in "line" units.
187
154
  * - ● When set to "br", performance may decrease when editing a lot of data.
155
+ * @property {Array<string>} [scopeSelectionTags=["td", "table", "li", "ol", "ul", "pre", "figcaption", "blockquote", "dl", "dt", "dd"]] - Tags treated as whole units when selecting all content.
188
156
  * @property {string} [__defaultElementWhitelist="br|div"] - Default allowed HTML elements. The default values are maintained.
189
157
  * @property {string} [elementWhitelist=""] - Allowed HTML elements. Delimiter: "|" (e.g. "p|div", "*").
190
158
  * @property {string} [elementBlacklist=""] - Disallowed HTML elements. Delimiter: "|" (e.g. "script|style").
@@ -248,13 +216,127 @@ export const RO_UNAVAILABD = [
248
216
  * - For example, when changing the font size or color of a list item (`<li>`),
249
217
  * - these styles will be applied to the `<li>` tag instead of wrapping the content inside additional tags.
250
218
  * @property {Object<string, *>} [externalLibs] - External libraries like CodeMirror or MathJax.
251
- * @property {Object<string, *>} [PluginOptions] - Dynamic plugin options, where the key is the plugin name and the value is its configuration.
219
+ *
220
+ * @property {Object<string, *>} [Dynamic_pluginOptions] - Dynamic plugin options, where the key is the plugin name and the value is its configuration.
252
221
  */
253
222
 
254
223
  /**
255
224
  * @typedef {EditorBaseOptions & EditorFrameOptions} EditorInitOptions
256
225
  */
257
226
 
227
+ /** ------------- [OPTIONS FRAG] ------------- */
228
+ /**
229
+ * @description For all EditorInitOptions keys, only boolean | null values are allowed.
230
+ * - 'fixed' → Immutable / null → Resettable.
231
+ * @type {Partial<Record<keyof EditorInitOptions, "fixed" | true>>}
232
+ */
233
+ export const OPTION_FRAME_FIXED_FLAG = {
234
+ value: 'fixed',
235
+ placeholder: true,
236
+ editableFrameAttributes: true,
237
+ width: true,
238
+ minWidth: true,
239
+ maxWidth: true,
240
+ height: true,
241
+ minHeight: true,
242
+ maxHeight: true,
243
+ editorStyle: true,
244
+ iframe: 'fixed',
245
+ iframe_fullPage: 'fixed',
246
+ iframe_attributes: true,
247
+ iframe_cssFileName: true,
248
+ statusbar: true,
249
+ statusbar_showPathLabel: true,
250
+ statusbar_resizeEnable: 'fixed',
251
+ charCounter: true,
252
+ charCounter_max: true,
253
+ charCounter_label: true,
254
+ charCounter_type: true
255
+ };
256
+ /**
257
+ * @description For all EditorInitOptions keys, only boolean | null values are allowed.
258
+ * - 'fixed' → Immutable / null → Resettable.
259
+ * @type {Partial<Record<keyof EditorInitOptions, "fixed" | true>>}
260
+ */
261
+ export const OPTION_FIXED_FLAG = {
262
+ plugins: 'fixed',
263
+ excludedPlugins: 'fixed',
264
+ buttonList: 'fixed',
265
+ v2Migration: 'fixed',
266
+ strictMode: 'fixed',
267
+ mode: 'fixed',
268
+ type: 'fixed',
269
+ theme: true,
270
+ lang: 'fixed',
271
+ fontSizeUnits: 'fixed',
272
+ allowedClassName: 'fixed',
273
+ closeModalOutsideClick: 'fixed',
274
+ copyFormatKeepOn: true,
275
+ syncTabIndent: true,
276
+ tabDisable: true,
277
+ autoLinkify: true,
278
+ autoStyleify: true,
279
+ scrollToOptions: true,
280
+ componentScrollToOptions: true,
281
+ retainStyleMode: true,
282
+ allowedExtraTags: 'fixed',
283
+ events: true,
284
+ __textStyleTags: 'fixed',
285
+ textStyleTags: 'fixed',
286
+ convertTextTags: 'fixed',
287
+ __tagStyles: 'fixed',
288
+ tagStyles: 'fixed',
289
+ spanStyles: 'fixed',
290
+ lineStyles: 'fixed',
291
+ textDirection: true,
292
+ reverseButtons: 'fixed',
293
+ historyStackDelayTime: true,
294
+ lineAttrReset: true,
295
+ printClass: true,
296
+ defaultLine: 'fixed',
297
+ defaultLineBreakFormat: true,
298
+ scopeSelectionTags: true,
299
+ __defaultElementWhitelist: 'fixed',
300
+ elementWhitelist: 'fixed',
301
+ elementBlacklist: 'fixed',
302
+ __defaultAttributeWhitelist: 'fixed',
303
+ attributeWhitelist: 'fixed',
304
+ attributeBlacklist: 'fixed',
305
+ __defaultFormatLine: 'fixed',
306
+ formatLine: 'fixed',
307
+ __defaultFormatBrLine: 'fixed',
308
+ formatBrLine: 'fixed',
309
+ __defaultFormatClosureBrLine: 'fixed',
310
+ formatClosureBrLine: 'fixed',
311
+ __defaultFormatBlock: 'fixed',
312
+ formatBlock: 'fixed',
313
+ __defaultFormatClosureBlock: 'fixed',
314
+ formatClosureBlock: 'fixed',
315
+ allowedEmptyTags: true,
316
+ toolbar_width: true,
317
+ toolbar_container: 'fixed',
318
+ toolbar_sticky: true,
319
+ toolbar_hide: true,
320
+ subToolbar: 'fixed',
321
+ statusbar_container: 'fixed',
322
+ shortcutsHint: true,
323
+ shortcutsDisable: 'fixed',
324
+ shortcuts: 'fixed',
325
+ fullScreenOffset: true,
326
+ previewTemplate: true,
327
+ printTemplate: true,
328
+ componentAutoSelect: true,
329
+ defaultUrlProtocol: true,
330
+ allUsedStyles: 'fixed',
331
+ toastMessageTime: true,
332
+ icons: 'fixed',
333
+ freeCodeViewMode: true,
334
+ __lineFormatFilter: true,
335
+ __pluginRetainFilter: true,
336
+ __listCommonStyle: 'fixed',
337
+ externalLibs: 'fixed'
338
+ };
339
+
258
340
  /**
259
341
  * @description Creates a new SunEditor instance with specified options.
260
342
  * @param {Array<{target: Element, key: *, options: EditorFrameOptions}>} editorTargets - Target element or multi-root object.
@@ -364,7 +446,7 @@ function Constructor(editorTargets, options) {
364
446
  const to = optionMap.frameMap.get(editTarget.key);
365
447
  const top_div = dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
366
448
  const container = dom.utils.createElement('DIV', { class: 'se-container' });
367
- const editor_div = dom.utils.createElement('DIV', { class: 'se-wrapper' + (o.get('type') === 'document' ? ' se-type-document' : '') });
449
+ const editor_div = dom.utils.createElement('DIV', { class: 'se-wrapper' + (o.get('type') === 'document' ? ' se-type-document' : '') + (o.get('_type_options').includes('header') ? ' se-type-document-header' : '') });
368
450
 
369
451
  container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-shadow' }));
370
452
 
@@ -424,10 +506,10 @@ function Constructor(editorTargets, options) {
424
506
 
425
507
  // document type
426
508
  const documentTypeInner = { inner: null, page: null, pageMirror: null };
427
- if (o.get('type-options').includes('header')) {
509
+ if (o.get('_type_options').includes('header')) {
428
510
  documentTypeInner.inner = dom.utils.createElement('DIV', { class: 'se-document-lines', style: `height: ${to.get('height')};` }, '<div class="se-document-lines-inner"></div>');
429
511
  }
430
- if (o.get('type-options').includes('page')) {
512
+ if (o.get('_type_options').includes('page')) {
431
513
  documentTypeInner.page = dom.utils.createElement('DIV', { class: 'se-document-page' }, null);
432
514
  documentTypeInner.pageMirror = dom.utils.createElement(
433
515
  'DIV',
@@ -500,13 +582,16 @@ export function CreateShortcuts(command, button, values, keyMap, rc, reverseKeys
500
582
  plugin = null;
501
583
  method = a[a.length - 1].trim?.();
502
584
  if (method.startsWith('~')) {
585
+ // plugin key, method
503
586
  plugin = command;
504
587
  method = a.pop().trim().substring(1);
505
- } else if (method.startsWith('p~')) {
588
+ } else if (method.startsWith('$~')) {
589
+ // custom key, plugin method
506
590
  const a_ = a.pop().trim().substring(2).split('.');
507
591
  plugin = a_[0];
508
592
  method = a_[1];
509
593
  } else if (method.startsWith('$')) {
594
+ // directly method
510
595
  _i = 1;
511
596
  method = values[i + 2];
512
597
  } else {
@@ -539,13 +624,16 @@ export function CreateShortcuts(command, button, values, keyMap, rc, reverseKeys
539
624
  }
540
625
  }
541
626
 
542
- k = c ? v + (s ? '1000' : '') : v;
543
- if (!keyMap.has(k)) {
544
- r = rc.indexOf(command);
545
- r = r === -1 ? '' : numbers.isOdd(r) ? rc[r + 1] : rc[r - 1];
546
- if (r) reverseKeys.push(k);
627
+ v = v.split('|');
628
+ for (let j = 0, len = v.length; j < len; j++) {
629
+ k = c ? v[j] + (s ? '1000' : '') : v[j];
630
+ if (!keyMap.has(k)) {
631
+ r = rc.indexOf(command);
632
+ r = r === -1 ? '' : numbers.isOdd(r) ? rc[r + 1] : rc[r - 1];
633
+ if (r) reverseKeys.push(k);
547
634
 
548
- keyMap.set(k, { c, s, edge, space, enter, textTrigger, plugin, command, method, r, type: button?.getAttribute('data-type'), button, key: k });
635
+ keyMap.set(k, { c, s, edge, space, enter, textTrigger, plugin, command, method, r, type: button?.getAttribute('data-type'), button, key: k });
636
+ }
549
637
  }
550
638
 
551
639
  if (!(t = values[i + 1])) continue;
@@ -619,7 +707,7 @@ export function InitOptions(options, editorTargets, plugins) {
619
707
  o.set('type', options.type?.split(':')[0] || ''); // document:header,page
620
708
  o.set('theme', options.theme || '');
621
709
  o.set('_themeClass', options.theme ? ` se-theme-${options.theme}` : '');
622
- o.set('type-options', options.type?.split(':')[1] || '');
710
+ o.set('_type_options', options.type?.split(':')[1] || '');
623
711
  o.set('externalLibs', options.externalLibs || {});
624
712
  o.set('fontSizeUnits', Array.isArray(options.fontSizeUnits) && options.fontSizeUnits.length > 0 ? options.fontSizeUnits.map((v) => v.toLowerCase()) : DEFAULT_SIZE_UNITS);
625
713
  o.set('allowedClassName', new RegExp(`${options.allowedClassName && typeof options.allowedClassName === 'string' ? options.allowedClassName + '|' : ''}${DEFAULT_CLASS_NAME}`));
@@ -735,7 +823,8 @@ export function InitOptions(options, editorTargets, plugins) {
735
823
  /** whitelist, blacklist */
736
824
  // default line
737
825
  o.set('defaultLine', typeof options.defaultLine === 'string' && options.defaultLine.length > 0 ? options.defaultLine : 'p');
738
- o.set('_defaultBrLineBreak', options.defaultLineBreakFormat === 'br');
826
+ o.set('defaultLineBreakFormat', options.defaultLineBreakFormat || 'line');
827
+ o.set('scopeSelectionTags', options.scopeSelectionTags || DEFAULT_SCOPE_SELECTION_TAGS.split('|'));
739
828
  // element
740
829
  const elw = (typeof options.elementWhitelist === 'string' ? options.elementWhitelist : '').toLowerCase();
741
830
  const mjxEls = o.get('externalLibs').mathjax ? DEFAULT_CLASS_MJX + '|' : '';
@@ -851,9 +940,9 @@ export function InitOptions(options, editorTargets, plugins) {
851
940
  list_numbered: ['!+1.+_+~shortcut', ''],
852
941
  list_bulleted: ['!+*.+_+~shortcut', ''],
853
942
  // custom
854
- _h1: ['c+s+Digit1+p~formatBlock.createHeader', ''],
855
- _h2: ['c+s+Digit2+p~formatBlock.createHeader', ''],
856
- _h3: ['c+s+Digit3+p~formatBlock.createHeader', '']
943
+ _h1: ['c+s+Digit1|Numpad1+$~formatBlock.applyHeaderByShortcut', ''],
944
+ _h2: ['c+s+Digit2|Numpad2+$~formatBlock.applyHeaderByShortcut', ''],
945
+ _h3: ['c+s+Digit3|Numpad3+$~formatBlock.applyHeaderByShortcut', '']
857
946
  },
858
947
  options.shortcuts || {}
859
948
  ].reduce((_default, _new) => {
@@ -1040,6 +1129,7 @@ function InitFrameOptions(o, origin) {
1040
1129
  fo.set('height', height ? (numbers.is(height) ? height + 'px' : height) : 'auto');
1041
1130
  fo.set('minHeight', (numbers.is(minHeight) ? minHeight + 'px' : minHeight) || '');
1042
1131
  fo.set('maxHeight', (numbers.is(maxHeight) ? maxHeight + 'px' : maxHeight) || '');
1132
+ fo.set('editorStyle', editorStyle);
1043
1133
  fo.set('_defaultStyles', converter._setDefaultOptionStyle(fo, typeof editorStyle === 'string' ? editorStyle : ''));
1044
1134
  // iframe
1045
1135
  fo.set('iframe', !!(iframe_fullPage || iframe));
@@ -1100,9 +1190,8 @@ function _initTargetElements(key, options, topDiv, targetOptions) {
1100
1190
 
1101
1191
  // textarea for code view
1102
1192
  const textarea = dom.utils.createElement('TEXTAREA', { class: 'se-wrapper-inner se-code-viewer', style: editorStyles.frame });
1103
- let placeholder = null;
1193
+ const placeholder = dom.utils.createElement('SPAN', { class: 'se-placeholder' });
1104
1194
  if (targetOptions.get('placeholder')) {
1105
- placeholder = dom.utils.createElement('SPAN', { class: 'se-placeholder' });
1106
1195
  placeholder.textContent = targetOptions.get('placeholder');
1107
1196
  }
1108
1197
 
@@ -43,8 +43,8 @@ function DocumentType(editor, fc) {
43
43
  this.pages = [];
44
44
  this.pages_line = [];
45
45
  this.prevScrollTop = 0;
46
- this.useHeader = editor.options.get('type-options').includes('header');
47
- this.usePage = editor.options.get('type-options').includes('page');
46
+ this.useHeader = editor.options.get('_type_options').includes('header');
47
+ this.usePage = editor.options.get('_type_options').includes('page');
48
48
  this.navigatorButtons = [];
49
49
  this.pageNavigator = null;
50
50
  this._mirror = fc.get('documentTypePageMirror');
package/src/events.js CHANGED
@@ -161,12 +161,24 @@ export default {
161
161
  */
162
162
  onFocus: null,
163
163
 
164
+ /**
165
+ * @description Event call back function
166
+ * @param {BaseEvent} params
167
+ */
168
+ onNativeFocus: null,
169
+
164
170
  /**
165
171
  * @description Event call back function
166
172
  * @param {BaseEvent} params
167
173
  */
168
174
  onBlur: null,
169
175
 
176
+ /**
177
+ * @description Event call back function
178
+ * @param {BaseEvent} params
179
+ */
180
+ onNativeBlur: null,
181
+
170
182
  /**
171
183
  * @description Event function on copy
172
184
  * @param {Object} params
@@ -606,5 +618,17 @@ export default {
606
618
  * @param {EmbedInfo} params.info - info object
607
619
  * @param {(newInfo?: EmbedInfo|null) => void} params.handler - handler function
608
620
  */
609
- onEmbedInputBefore: null
621
+ onEmbedInputBefore: null,
622
+
623
+ /**
624
+ * @description Called before the embed is deleted
625
+ * @param {Object} params
626
+ * @param {__se__EditorCore} params.editor - The root editor instance
627
+ * @param {HTMLElement} params.element - target element
628
+ * @param {HTMLElement} params.container - target's container element (div)
629
+ * @param {string} params.align - align value
630
+ * @param {string} params.url - embed url
631
+ * @returns {Promise<boolean>}
632
+ */
633
+ onEmbedDeleteBefore: null
610
634
  };
@@ -170,6 +170,26 @@ export function syncMaps(targetMap, referenceMap) {
170
170
  });
171
171
  }
172
172
 
173
+ /**
174
+ * @description Merges multiple Map objects into a new Map using spread syntax.
175
+ * - Entries from later maps in the arguments list will overwrite entries from earlier maps if keys conflict.
176
+ * - The original maps are not modified.
177
+ * @param {...Map<*, *>} mapsToMerge - An arbitrary number of Map objects to merge.
178
+ * @returns {Map<*, *>} A new Map containing all entries from the input maps.
179
+ */
180
+ export function mergeMaps(...mapsToMerge) {
181
+ const validMaps = mapsToMerge.filter((m) => {
182
+ if (!(m instanceof Map)) {
183
+ return false;
184
+ }
185
+ return true;
186
+ });
187
+
188
+ const allEntries = validMaps.flatMap((map) => [...map]);
189
+
190
+ return new Map(allEntries);
191
+ }
192
+
173
193
  /**
174
194
  * @description Object.values
175
195
  * @param {Object<*, *>} obj Object parameter.
@@ -269,8 +289,9 @@ export function nodeListToArray(nodeList) {
269
289
  export function swapKeyValue(obj) {
270
290
  const swappedObj = {};
271
291
 
292
+ const hasOwn = Object.prototype.hasOwnProperty;
272
293
  for (const key in obj) {
273
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
294
+ if (hasOwn.call(obj, key)) {
274
295
  swappedObj[obj[key]] = key;
275
296
  }
276
297
  }
@@ -542,6 +563,7 @@ const converter = {
542
563
  entityToHTML,
543
564
  debounce,
544
565
  syncMaps,
566
+ mergeMaps,
545
567
  getValues,
546
568
  camelToKebabCase,
547
569
  kebabToCamelCase,
@@ -162,6 +162,15 @@ export function isFigure(node) {
162
162
  return /^FIGURE$/i.test(typeof node === 'string' ? node : node?.nodeName);
163
163
  }
164
164
 
165
+ /**
166
+ * @description Checks whether the given node is a content-less (void) HTML tag
167
+ * @param {?Node|string} node The element or element name to check
168
+ * @returns {boolean}
169
+ */
170
+ export function isContentLess(node) {
171
+ return /^(BR|COLGROUP|COL|THEAD|TBODY|TFOOT|TR|AREA|BASE|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/i.test(typeof node === 'string' ? node : node?.nodeName);
172
+ }
173
+
165
174
  /**
166
175
  * @description Check the line element is empty.
167
176
  * @param {Node} node "line" element node
@@ -170,7 +179,7 @@ export function isFigure(node) {
170
179
  export function isEmptyLine(node) {
171
180
  if (!node?.parentNode) return true;
172
181
  const el = /** @type {HTMLElement} */ (node);
173
- return !el.querySelector('IMG, IFRAME, AUDIO, VIDEO, CANVAS, TABLE') && el.children.length === 0 && check.isZeroWidth(el.textContent);
182
+ return !el.querySelector('IMG, IFRAME, AUDIO, VIDEO, CANVAS, TABLE') && (el.children.length <= 1 || isBreak(el.firstElementChild)) && isZeroWidth(el.textContent);
174
183
  }
175
184
 
176
185
  /**
@@ -179,7 +188,7 @@ export function isEmptyLine(node) {
179
188
  * @returns {node is HTMLElement}
180
189
  */
181
190
  export function isWysiwygFrame(node) {
182
- return node?.nodeType === 1 && (domUtils.hasClass(node, 'se-wrapper-wysiwyg|sun-editor-carrier-wrapper') || /^BODY$/i.test(node.nodeName));
191
+ return node?.nodeType === 1 && (domUtils.hasClass(node, 'se-wrapper-wysiwyg|sun-editor-carrier-wrapper|se-wrapper') || /^BODY$/i.test(node.nodeName));
183
192
  }
184
193
 
185
194
  /**
@@ -281,6 +290,7 @@ const check = {
281
290
  isMedia,
282
291
  isIFrame,
283
292
  isFigure,
293
+ isContentLess,
284
294
  isEmptyLine,
285
295
  isWysiwygFrame,
286
296
  isNonEditable,
@@ -108,10 +108,12 @@ class Controller extends EditorInjector {
108
108
  if (this.editor.isBalloon) this.toolbar.hide();
109
109
  else if (this.editor.isSubBalloon) this.subToolbar.hide();
110
110
 
111
- if (disabled ?? this.disabled) {
112
- this.ui.setControllerOnDisabledButtons(true);
113
- } else {
114
- this.ui.setControllerOnDisabledButtons(false);
111
+ if (!this.status.hasFocus) {
112
+ if (disabled ?? this.disabled) {
113
+ this.ui.setControllerOnDisabledButtons(true);
114
+ } else {
115
+ this.ui.setControllerOnDisabledButtons(false);
116
+ }
115
117
  }
116
118
 
117
119
  this.currentPositionTarget = positionTarget || target;
@@ -429,6 +431,7 @@ class Controller extends EditorInjector {
429
431
  if (this.form.contains(eventTarget) || this._checkForm(eventTarget)) return;
430
432
  if (this.editor._fileManager.pluginRegExp.test(this.kind) && !keyCodeMap.isEsc(keyCode)) return;
431
433
 
434
+ this.#PostCloseEvent(eventTarget);
432
435
  this.close();
433
436
  }
434
437
 
@@ -443,12 +446,29 @@ class Controller extends EditorInjector {
443
446
  }
444
447
 
445
448
  this.isOpen = true;
446
- if (eventTarget === this.inst._element || eventTarget === this.currentTarget || this._checkFixed() || this.form.contains(eventTarget) || this._checkForm(eventTarget)) {
449
+ if (
450
+ eventTarget === this.inst._element ||
451
+ eventTarget === this.currentTarget ||
452
+ this._checkFixed() ||
453
+ this.form.contains(eventTarget) ||
454
+ this._checkForm(eventTarget) ||
455
+ dom.query.getParentElement(eventTarget, '.se-line-breaker-component')
456
+ ) {
447
457
  return;
448
458
  }
449
459
 
460
+ this.#PostCloseEvent(eventTarget);
450
461
  this.close(true);
451
462
  }
463
+
464
+ /**
465
+ * @param {HTMLElement} eventTarget - The target element that triggered the event.
466
+ */
467
+ #PostCloseEvent(eventTarget) {
468
+ if (!this.editor.frameContext.get('wysiwyg').contains(eventTarget)) {
469
+ this.component.__prevent = false;
470
+ }
471
+ }
452
472
  }
453
473
 
454
474
  export default Controller;
@@ -652,7 +652,7 @@ class Figure extends EditorInjector {
652
652
  */
653
653
  _asFormatChange(figureinfo, w, h) {
654
654
  const kind = this.kind;
655
- figureinfo.target.onload = () => this.component.select(figureinfo.target, kind, false);
655
+ figureinfo.target.onload = () => this.component.select(figureinfo.target, kind);
656
656
 
657
657
  this._setFigureInfo(figureinfo);
658
658
 
@@ -745,7 +745,7 @@ class Figure extends EditorInjector {
745
745
 
746
746
  this.history.push(false);
747
747
  if (!/^remove|caption$/.test(command)) {
748
- this.component.select(element, this.kind, false);
748
+ this.component.select(element, this.kind);
749
749
  }
750
750
  }
751
751
 
@@ -1318,7 +1318,7 @@ class Figure extends EditorInjector {
1318
1318
  }
1319
1319
 
1320
1320
  this.history.push(false);
1321
- this.component.select(this._element, this.kind, false);
1321
+ this.component.select(this._element, this.kind);
1322
1322
  }
1323
1323
 
1324
1324
  /**
@@ -1328,7 +1328,7 @@ class Figure extends EditorInjector {
1328
1328
  #ContainerResizingESC(e) {
1329
1329
  if (!keyCodeMap.isEsc(e.code)) return;
1330
1330
  this._offResizeEvent();
1331
- this.component.select(this._element, this.kind, false);
1331
+ this.component.select(this._element, this.kind);
1332
1332
  }
1333
1333
 
1334
1334
  /**
@@ -1337,7 +1337,7 @@ class Figure extends EditorInjector {
1337
1337
  #SetMenuAlign(value) {
1338
1338
  this.setAlign(this._element, value);
1339
1339
  this.selectMenu_align.close();
1340
- this.component.select(this._element, this.kind, false);
1340
+ this.component.select(this._element, this.kind);
1341
1341
  }
1342
1342
 
1343
1343
  /**
@@ -1367,7 +1367,7 @@ class Figure extends EditorInjector {
1367
1367
  }
1368
1368
 
1369
1369
  this.selectMenu_resize.close();
1370
- this.component.select(this._element, this.kind, false);
1370
+ this.component.select(this._element, this.kind);
1371
1371
  }
1372
1372
 
1373
1373
  #OffFigureContainer() {
@@ -202,7 +202,7 @@ class FileManager extends CoreInjector {
202
202
  el.scrollIntoView(this.options.get('componentScrollToOptions'));
203
203
  const comp = this.component.get(el);
204
204
  if (comp) {
205
- this.component.select(comp.target, comp.pluginName, false);
205
+ this.component.select(comp.target, comp.pluginName);
206
206
  } else if (typeof this.inst.select === 'function') {
207
207
  this.inst.select(el);
208
208
  }
@@ -184,7 +184,7 @@ class FileUpload extends EditorInjector {
184
184
  }
185
185
 
186
186
  this.controller.close();
187
- this.component.select(this._element, FileUpload.key, false);
187
+ this.component.select(this._element, FileUpload.key);
188
188
  }
189
189
 
190
190
  /**
@@ -321,7 +321,7 @@ class FileUpload extends EditorInjector {
321
321
  }
322
322
 
323
323
  this.history.push(false);
324
- this.component.select(target, FileUpload.key, false);
324
+ this.component.select(target, FileUpload.key);
325
325
  }
326
326
 
327
327
  /**
@@ -368,7 +368,7 @@ class FileUpload extends EditorInjector {
368
368
  const line = this.format.addLine(figure.container, null);
369
369
  if (line) this.selection.setRange(line, 0, line, 0);
370
370
  } else {
371
- this.component.select(a, FileUpload.key, false);
371
+ this.component.select(a, FileUpload.key);
372
372
  }
373
373
  }
374
374
 
@@ -1,15 +1,6 @@
1
1
  import EditorInjector from '../../editorInjector';
2
2
  import { dom } from '../../helper';
3
3
 
4
- const HEADER_KEYCODE = new Map([
5
- ['Digit1', 'h1'],
6
- ['Digit2', 'h2'],
7
- ['Digit3', 'h3'],
8
- ['Digit4', 'h4'],
9
- ['Digit5', 'h5'],
10
- ['Digit6', 'h6']
11
- ]);
12
-
13
4
  /**
14
5
  * @class
15
6
  * @description FormatBlock Plugin (P, BLOCKQUOTE, PRE, H1, H2...)
@@ -125,12 +116,12 @@ class FormatBlock extends EditorInjector {
125
116
 
126
117
  /**
127
118
  * @description Create a header tag, call by "shortcut" class
128
- * - (e.g. shortcuts._h1: ['c+s+49+p~formatBlock.createHeader', ''])
119
+ * - (e.g. shortcuts._h1: ['c+s+49+$~formatBlock.applyHeaderByShortcut', ''])
129
120
  * @param {__se__PluginShortcutInfo} params - Information of the "shortcut" plugin
130
121
  */
131
- createHeader({ keyCode }) {
132
- const headerName = HEADER_KEYCODE.get(keyCode);
133
- const tag = dom.utils.createElement(headerName);
122
+ applyHeaderByShortcut({ keyCode }) {
123
+ const headerNum = keyCode.match(/\d+$/)?.[0];
124
+ const tag = dom.utils.createElement(`H${headerNum}`);
134
125
  this.format.setLine(tag);
135
126
  }
136
127
  }