suneditor 3.1.4 → 3.2.1

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 (217) hide show
  1. package/dist/suneditor-contents.min.css +1 -1
  2. package/dist/suneditor.min.css +28 -1
  3. package/dist/suneditor.min.js +1 -1
  4. package/package.json +2 -1
  5. package/src/assets/design/color.css +12 -0
  6. package/src/assets/design/size.css +9 -2
  7. package/src/assets/icons/defaultIcons.js +96 -47
  8. package/src/assets/suneditor-contents.css +3 -2
  9. package/src/assets/suneditor.css +481 -31
  10. package/src/core/config/contextProvider.js +2 -1
  11. package/src/core/config/eventManager.js +41 -2
  12. package/src/core/config/optionProvider.js +23 -2
  13. package/src/core/editor.js +32 -14
  14. package/src/core/event/actions/index.js +76 -9
  15. package/src/core/event/effects/common.registry.js +2 -1
  16. package/src/core/event/effects/keydown.registry.js +169 -33
  17. package/src/core/event/effects/ruleHelpers.js +20 -6
  18. package/src/core/event/eventOrchestrator.js +162 -30
  19. package/src/core/event/handlers/handler_toolbar.js +4 -2
  20. package/src/core/event/handlers/handler_ww_dragDrop.js +10 -0
  21. package/src/core/event/handlers/handler_ww_input.js +84 -2
  22. package/src/core/event/handlers/handler_ww_key.js +82 -21
  23. package/src/core/event/handlers/handler_ww_mouse.js +45 -8
  24. package/src/core/event/ports.js +27 -13
  25. package/src/core/event/reducers/keydown.reducer.js +33 -5
  26. package/src/core/event/rules/keydown.rule.arrow.js +12 -2
  27. package/src/core/event/rules/keydown.rule.backspace.js +110 -15
  28. package/src/core/event/rules/keydown.rule.delete.js +62 -7
  29. package/src/core/event/rules/keydown.rule.enter.js +62 -31
  30. package/src/core/event/support/defaultLineManager.js +9 -2
  31. package/src/core/event/support/selectionState.js +13 -3
  32. package/src/core/kernel/coreKernel.js +1 -0
  33. package/src/core/logic/dom/char.js +13 -3
  34. package/src/core/logic/dom/format.js +171 -39
  35. package/src/core/logic/dom/html.js +350 -72
  36. package/src/core/logic/dom/inline.js +229 -34
  37. package/src/core/logic/dom/listFormat.js +111 -20
  38. package/src/core/logic/dom/nodeTransform.js +36 -11
  39. package/src/core/logic/dom/offset.js +116 -31
  40. package/src/core/logic/dom/selection.js +147 -29
  41. package/src/core/logic/panel/blockHandle.js +785 -0
  42. package/src/core/logic/panel/blockResolver.js +278 -0
  43. package/src/core/logic/panel/finder.js +16 -6
  44. package/src/core/logic/panel/menu.js +119 -18
  45. package/src/core/logic/panel/toolbar.js +75 -19
  46. package/src/core/logic/panel/viewer.js +76 -20
  47. package/src/core/logic/shell/_commandExecutor.js +44 -9
  48. package/src/core/logic/shell/commandDispatcher.js +46 -4
  49. package/src/core/logic/shell/component.js +102 -25
  50. package/src/core/logic/shell/focusManager.js +13 -3
  51. package/src/core/logic/shell/history.js +21 -6
  52. package/src/core/logic/shell/pluginManager.js +21 -13
  53. package/src/core/logic/shell/shortcuts.js +9 -1
  54. package/src/core/logic/shell/ui.js +139 -37
  55. package/src/core/schema/frameContext.js +34 -3
  56. package/src/core/schema/options.js +46 -4
  57. package/src/core/section/constructor.js +537 -106
  58. package/src/core/section/documentType.js +35 -6
  59. package/src/helper/converter.js +24 -7
  60. package/src/helper/dom/domCheck.js +25 -5
  61. package/src/helper/dom/domQuery.js +2 -1
  62. package/src/helper/dom/domUtils.js +17 -4
  63. package/src/helper/env.js +11 -2
  64. package/src/helper/keyCodeMap.js +6 -2
  65. package/src/helper/markdown.js +24 -5
  66. package/src/helper/msOffice.js +9 -2
  67. package/src/interfaces/plugins.js +1 -1
  68. package/src/langs/ckb.js +0 -1
  69. package/src/langs/cs.js +0 -1
  70. package/src/langs/da.js +0 -1
  71. package/src/langs/de.js +0 -1
  72. package/src/langs/en.js +0 -1
  73. package/src/langs/es.js +0 -1
  74. package/src/langs/fa.js +0 -1
  75. package/src/langs/fr.js +0 -1
  76. package/src/langs/he.js +0 -1
  77. package/src/langs/hu.js +0 -1
  78. package/src/langs/index.js +27 -1
  79. package/src/langs/it.js +0 -1
  80. package/src/langs/ja.js +0 -1
  81. package/src/langs/km.js +0 -1
  82. package/src/langs/ko.js +0 -1
  83. package/src/langs/lv.js +0 -1
  84. package/src/langs/nl.js +0 -1
  85. package/src/langs/pl.js +0 -1
  86. package/src/langs/pt_br.js +0 -1
  87. package/src/langs/ro.js +0 -1
  88. package/src/langs/ru.js +0 -1
  89. package/src/langs/se.js +0 -1
  90. package/src/langs/tr.js +0 -1
  91. package/src/langs/uk.js +0 -1
  92. package/src/langs/ur.js +0 -1
  93. package/src/langs/zh_cn.js +0 -1
  94. package/src/modules/contract/Browser.js +58 -14
  95. package/src/modules/contract/ColorPicker.js +12 -4
  96. package/src/modules/contract/Controller.js +37 -7
  97. package/src/modules/contract/Figure.js +156 -36
  98. package/src/modules/contract/HueSlider.js +27 -4
  99. package/src/modules/contract/Modal.js +50 -10
  100. package/src/modules/manager/FileManager.js +50 -8
  101. package/src/modules/ui/CommandMenu.js +597 -0
  102. package/src/modules/ui/ModalAnchorEditor.js +33 -6
  103. package/src/modules/ui/SelectMenu.js +481 -36
  104. package/src/modules/ui/index.js +1 -0
  105. package/src/plugins/browser/audioGallery.js +2 -1
  106. package/src/plugins/browser/fileBrowser.js +9 -2
  107. package/src/plugins/browser/fileGallery.js +2 -1
  108. package/src/plugins/browser/imageGallery.js +18 -3
  109. package/src/plugins/browser/videoGallery.js +13 -4
  110. package/src/plugins/command/blockquote.js +6 -1
  111. package/src/plugins/command/codeBlock.js +43 -5
  112. package/src/plugins/command/exportPDF.js +20 -4
  113. package/src/plugins/command/fileUpload.js +46 -11
  114. package/src/plugins/command/list_bulleted.js +6 -1
  115. package/src/plugins/command/list_numbered.js +6 -1
  116. package/src/plugins/dropdown/align.js +21 -33
  117. package/src/plugins/dropdown/backgroundColor.js +10 -2
  118. package/src/plugins/dropdown/blockStyle.js +17 -22
  119. package/src/plugins/dropdown/font.js +34 -34
  120. package/src/plugins/dropdown/hr.js +15 -43
  121. package/src/plugins/dropdown/layout.js +10 -27
  122. package/src/plugins/dropdown/lineHeight.js +10 -33
  123. package/src/plugins/dropdown/list.js +19 -25
  124. package/src/plugins/dropdown/paragraphStyle.js +16 -24
  125. package/src/plugins/dropdown/table/index.js +72 -17
  126. package/src/plugins/dropdown/table/render/table.html.js +3 -1
  127. package/src/plugins/dropdown/table/services/table.cell.js +33 -7
  128. package/src/plugins/dropdown/table/services/table.clipboard.js +5 -1
  129. package/src/plugins/dropdown/table/services/table.grid.js +40 -9
  130. package/src/plugins/dropdown/table/services/table.resize.js +55 -11
  131. package/src/plugins/dropdown/table/services/table.selection.js +27 -7
  132. package/src/plugins/dropdown/table/services/table.style.js +120 -20
  133. package/src/plugins/dropdown/template.js +13 -29
  134. package/src/plugins/dropdown/textStyle.js +22 -35
  135. package/src/plugins/field/autocomplete.js +8 -4
  136. package/src/plugins/field/slashCommand.js +297 -0
  137. package/src/plugins/index.js +3 -0
  138. package/src/plugins/input/fontSize.js +37 -8
  139. package/src/plugins/input/pageNavigator.js +7 -1
  140. package/src/plugins/modal/audio.js +71 -17
  141. package/src/plugins/modal/drawing.js +41 -11
  142. package/src/plugins/modal/embed.js +92 -22
  143. package/src/plugins/modal/image/index.js +114 -28
  144. package/src/plugins/modal/image/services/image.size.js +9 -2
  145. package/src/plugins/modal/image/services/image.upload.js +38 -4
  146. package/src/plugins/modal/link.js +9 -2
  147. package/src/plugins/modal/math.js +10 -2
  148. package/src/plugins/modal/video/index.js +112 -26
  149. package/src/plugins/modal/video/render/video.html.js +4 -1
  150. package/src/plugins/modal/video/services/video.size.js +15 -4
  151. package/src/plugins/modal/video/services/video.upload.js +10 -2
  152. package/src/plugins/popup/anchor.js +14 -2
  153. package/src/suneditor.js +8 -2
  154. package/src/themes/cobalt.css +12 -0
  155. package/src/themes/cream.css +12 -0
  156. package/src/themes/dark.css +12 -0
  157. package/src/themes/midnight.css +12 -0
  158. package/src/typedef.js +32 -21
  159. package/types/assets/icons/defaultIcons.d.ts +1 -0
  160. package/types/core/config/contextProvider.d.ts +7 -1
  161. package/types/core/config/eventManager.d.ts +37 -4
  162. package/types/core/config/optionProvider.d.ts +5 -1
  163. package/types/core/event/actions/index.d.ts +44 -6
  164. package/types/core/event/effects/keydown.registry.d.ts +37 -7
  165. package/types/core/event/effects/ruleHelpers.d.ts +6 -1
  166. package/types/core/event/eventOrchestrator.d.ts +13 -3
  167. package/types/core/event/handlers/handler_ww_clipboard.d.ts +15 -3
  168. package/types/core/event/handlers/handler_ww_dragDrop.d.ts +14 -2
  169. package/types/core/event/handlers/handler_ww_input.d.ts +10 -2
  170. package/types/core/event/handlers/handler_ww_key.d.ts +10 -2
  171. package/types/core/event/handlers/handler_ww_mouse.d.ts +25 -5
  172. package/types/core/event/ports.d.ts +11 -4
  173. package/types/core/event/reducers/keydown.reducer.d.ts +12 -3
  174. package/types/core/event/rules/keydown.rule.backspace.d.ts +6 -1
  175. package/types/core/kernel/store.d.ts +4 -1
  176. package/types/core/logic/dom/format.d.ts +32 -14
  177. package/types/core/logic/dom/html.d.ts +11 -3
  178. package/types/core/logic/dom/nodeTransform.d.ts +6 -6
  179. package/types/core/logic/panel/blockHandle.d.ts +64 -0
  180. package/types/core/logic/panel/blockResolver.d.ts +50 -0
  181. package/types/core/logic/panel/menu.d.ts +72 -3
  182. package/types/core/logic/shell/commandDispatcher.d.ts +8 -0
  183. package/types/core/logic/shell/component.d.ts +5 -3
  184. package/types/core/logic/shell/shortcuts.d.ts +10 -1
  185. package/types/core/logic/shell/ui.d.ts +7 -1
  186. package/types/core/schema/context.d.ts +7 -1
  187. package/types/core/schema/frameContext.d.ts +36 -4
  188. package/types/core/schema/options.d.ts +83 -2
  189. package/types/core/section/constructor.d.ts +38 -1
  190. package/types/events.d.ts +208 -32
  191. package/types/helper/converter.d.ts +4 -2
  192. package/types/helper/dom/domCheck.d.ts +12 -2
  193. package/types/helper/dom/domQuery.d.ts +35 -7
  194. package/types/helper/dom/domUtils.d.ts +4 -1
  195. package/types/interfaces/plugins.d.ts +2 -2
  196. package/types/langs/_Lang.d.ts +0 -1
  197. package/types/modules/contract/ColorPicker.d.ts +2 -2
  198. package/types/modules/contract/Controller.d.ts +7 -0
  199. package/types/modules/contract/Figure.d.ts +31 -4
  200. package/types/modules/ui/CommandMenu.d.ts +262 -0
  201. package/types/modules/ui/SelectMenu.d.ts +56 -3
  202. package/types/modules/ui/index.d.ts +1 -0
  203. package/types/plugins/browser/audioGallery.d.ts +1 -1
  204. package/types/plugins/browser/fileBrowser.d.ts +1 -1
  205. package/types/plugins/browser/fileGallery.d.ts +1 -1
  206. package/types/plugins/browser/imageGallery.d.ts +1 -1
  207. package/types/plugins/browser/videoGallery.d.ts +1 -1
  208. package/types/plugins/dropdown/align.d.ts +1 -1
  209. package/types/plugins/dropdown/blockStyle.d.ts +3 -1
  210. package/types/plugins/dropdown/table/index.d.ts +4 -1
  211. package/types/plugins/dropdown/table/services/table.clipboard.d.ts +5 -1
  212. package/types/plugins/dropdown/table/services/table.grid.d.ts +10 -2
  213. package/types/plugins/field/autocomplete.d.ts +0 -1
  214. package/types/plugins/field/slashCommand.d.ts +165 -0
  215. package/types/plugins/index.d.ts +3 -0
  216. package/types/plugins/modal/link.d.ts +4 -1
  217. package/types/typedef.d.ts +48 -23
@@ -4,7 +4,13 @@ import { _DragHandle } from '../../modules/ui';
4
4
 
5
5
  // event handlers
6
6
  import { ButtonsHandler, OnClick_menuTray, OnClick_toolbar } from '../event/handlers/handler_toolbar';
7
- import { OnMouseDown_wysiwyg, OnMouseUp_wysiwyg, OnClick_wysiwyg, OnMouseMove_wysiwyg, OnMouseLeave_wysiwyg } from '../event/handlers/handler_ww_mouse';
7
+ import {
8
+ OnMouseDown_wysiwyg,
9
+ OnMouseUp_wysiwyg,
10
+ OnClick_wysiwyg,
11
+ OnMouseMove_wysiwyg,
12
+ OnMouseLeave_wysiwyg,
13
+ } from '../event/handlers/handler_ww_mouse';
8
14
  import { OnBeforeInput_wysiwyg, OnInput_wysiwyg } from '../event/handlers/handler_ww_input';
9
15
  import { OnKeyDown_wysiwyg, OnKeyUp_wysiwyg } from '../event/handlers/handler_ww_key';
10
16
  import { OnPaste_wysiwyg, OnCopy_wysiwyg, OnCut_wysiwyg } from '../event/handlers/handler_ww_clipboard';
@@ -106,9 +112,6 @@ class EventOrchestrator extends KernelInjector {
106
112
  this.__eventDoc = null;
107
113
  /** @type {string} */
108
114
  this.__secopy = null;
109
-
110
- /** @type {HTMLInputElement} */
111
- this.__focusTemp = this.#contextProvider.carrierWrapper.querySelector('.__se__focus__temp__');
112
115
  }
113
116
 
114
117
  /**
@@ -200,6 +203,32 @@ class EventOrchestrator extends KernelInjector {
200
203
  return this.defaultLineManager.execute(formatName);
201
204
  }
202
205
 
206
+ /**
207
+ * @internal
208
+ * @description Normalize the Enter range before the reducer reads it: reset to a text node, and when the
209
+ * caret sits inside a zero-width text node adjacent to a `<br>`, move it onto the `<br>`.
210
+ * @returns {?(HTMLElement|Text)} The updated selection node, or `null` when the caret is not on a line (no normalization ran).
211
+ */
212
+ _normalizeEnterRange() {
213
+ if (!this.$.format.isLine(this.$.selection.getRange()?.startContainer)) return null;
214
+
215
+ this.$.selection.resetRangeToTextNode();
216
+
217
+ const r = this.$.selection.getRange();
218
+ if (
219
+ r.startContainer === r.endContainer &&
220
+ r.startOffset !== r.endOffset &&
221
+ r.startContainer.nodeType === 3 &&
222
+ dom.check.isZeroWidth(r.startContainer)
223
+ ) {
224
+ const br = r.startContainer.nextSibling;
225
+ if (br && dom.check.isBreak(br)) this.$.selection.setRange(br, 0, br, 0);
226
+ else this.$.selection.setRange(r.endContainer, r.endOffset, r.endContainer, r.endOffset);
227
+ }
228
+
229
+ return this.$.selection.getNode();
230
+ }
231
+
203
232
  /**
204
233
  * @internal
205
234
  * @description Handles data transfer actions for `paste` and `drop` events.
@@ -245,14 +274,21 @@ class EventOrchestrator extends KernelInjector {
245
274
  // SE copy data
246
275
  const SEData = this.__secopy === plainText;
247
276
  // MS word, OneNode, Excel
248
- const MSData = /class=["']*Mso(Normal|List)/i.test(cleanData) || /content=["']*Word.Document/i.test(cleanData) || /content=["']*OneNote.File/i.test(cleanData) || /content=["']*Excel.Sheet/i.test(cleanData);
277
+ const MSData =
278
+ /class=["']*Mso(Normal|List)/i.test(cleanData) ||
279
+ /content=["']*Word.Document/i.test(cleanData) ||
280
+ /content=["']*OneNote.File/i.test(cleanData) ||
281
+ /content=["']*Excel.Sheet/i.test(cleanData);
249
282
  // from
250
283
  const from = SEData ? 'SE' : MSData ? 'MS' : '';
251
284
 
252
285
  if (onlyText) {
253
286
  cleanData = converter.htmlToEntity(plainText).replace(/\n/g, '<br>');
254
287
  } else {
255
- cleanData = cleanData.replace(/^<html>\r?\n?<body>\r?\n?\x3C!--StartFragment-->|\x3C!--EndFragment-->\r?\n?<\/body>\r?\n?<\/html>$/g, '');
288
+ cleanData = cleanData.replace(
289
+ /^<html>\r?\n?<body>\r?\n?\x3C!--StartFragment-->|\x3C!--EndFragment-->\r?\n?<\/body>\r?\n?<\/html>$/g,
290
+ '',
291
+ );
256
292
  if (MSData) {
257
293
  cleanData = cleanData.replace(/\n/g, ' ');
258
294
  plainText = plainText.replace(/\n/g, ' ');
@@ -273,10 +309,19 @@ class EventOrchestrator extends KernelInjector {
273
309
  cleanData = this.$.html.clean(cleanData, { forceFormat: false, whitelist: null, blacklist: null });
274
310
  }
275
311
 
276
- const maxCharCount = this.$.char.test(this.$.frameOptions.get('charCounter_type') === 'byte-html' ? cleanData : plainText, false);
312
+ const maxCharCount = this.$.char.test(
313
+ this.$.frameOptions.get('charCounter_type') === 'byte-html' ? cleanData : plainText,
314
+ false,
315
+ );
277
316
  // user event - paste
278
317
  if (type === 'paste') {
279
- const value = await this.#eventManager.triggerEvent('onPaste', { frameContext, event: e, data: cleanData, maxCharCount, from });
318
+ const value = await this.#eventManager.triggerEvent('onPaste', {
319
+ frameContext,
320
+ event: e,
321
+ data: cleanData,
322
+ maxCharCount,
323
+ from,
324
+ });
280
325
  if (value === false) {
281
326
  return false;
282
327
  } else if (typeof value === 'string') {
@@ -286,7 +331,13 @@ class EventOrchestrator extends KernelInjector {
286
331
  }
287
332
  // user event - drop
288
333
  if (type === 'drop') {
289
- const value = await this.#eventManager.triggerEvent('onDrop', { frameContext, event: e, data: cleanData, maxCharCount, from });
334
+ const value = await this.#eventManager.triggerEvent('onDrop', {
335
+ frameContext,
336
+ event: e,
337
+ data: cleanData,
338
+ maxCharCount,
339
+ from,
340
+ });
290
341
  if (value === false) {
291
342
  return false;
292
343
  } else if (typeof value === 'string') {
@@ -311,7 +362,14 @@ class EventOrchestrator extends KernelInjector {
311
362
 
312
363
  if (cleanData) {
313
364
  const domParser = new DOMParser().parseFromString(cleanData, 'text/html');
314
- if ((await this._callPluginEventAsync('onPaste', { frameContext, event: e, data: cleanData, doc: domParser })) !== false) {
365
+ if (
366
+ (await this._callPluginEventAsync('onPaste', {
367
+ frameContext,
368
+ event: e,
369
+ data: cleanData,
370
+ doc: domParser,
371
+ })) !== false
372
+ ) {
315
373
  this.$.html.insert(cleanData, { selectInserted: false, skipCharCount: true, skipCleaning: true });
316
374
  }
317
375
 
@@ -422,12 +480,21 @@ class EventOrchestrator extends KernelInjector {
422
480
  this.#eventManager.addEvent(
423
481
  eventWysiwyg,
424
482
  'dragover',
425
- OnDragOver_wysiwyg.bind(this, fc, dragCursor, isIframe ? this.$.frameContext.get('topArea') : null, !this.#options.get('toolbar_container') && !this.#store.mode.isBalloon && !this.#store.mode.isInline),
483
+ OnDragOver_wysiwyg.bind(
484
+ this,
485
+ fc,
486
+ dragCursor,
487
+ isIframe ? this.$.frameContext.get('topArea') : null,
488
+ !this.#options.get('toolbar_container') && !this.#store.mode.isBalloon && !this.#store.mode.isInline,
489
+ ),
426
490
  false,
427
491
  );
428
492
  this.#eventManager.addEvent(eventWysiwyg, 'dragend', OnDragEnd_wysiwyg.bind(this, dragCursor), false);
429
493
  this.#eventManager.addEvent(eventWysiwyg, 'drop', OnDrop_wysiwyg.bind(this, fc, dragCursor), false);
430
- this.#eventManager.addEvent(eventWysiwyg, 'scroll', this.#OnScroll_wysiwyg.bind(this, fc, eventWysiwyg), { passive: true, capture: false });
494
+ this.#eventManager.addEvent(eventWysiwyg, 'scroll', this.#OnScroll_wysiwyg.bind(this, fc, eventWysiwyg), {
495
+ passive: true,
496
+ capture: false,
497
+ });
431
498
  this.#eventManager.addEvent(eventWysiwyg, 'focus', this.#OnFocus_wysiwyg.bind(this, fc), false);
432
499
  this.#eventManager.addEvent(eventWysiwyg, 'blur', this.#OnBlur_wysiwyg.bind(this, fc), false);
433
500
  this.#eventManager.addEvent(codeArea, 'mousedown', this.#OnFocus_code.bind(this, fc), false);
@@ -445,8 +512,18 @@ class EventOrchestrator extends KernelInjector {
445
512
  );
446
513
 
447
514
  /** line breaker */
448
- this.#eventManager.addEvent(fc.get('lineBreaker_t'), 'pointerdown', this.#DisplayLineBreak.bind(this, 't'), false);
449
- this.#eventManager.addEvent(fc.get('lineBreaker_b'), 'pointerdown', this.#DisplayLineBreak.bind(this, 'b'), false);
515
+ this.#eventManager.addEvent(
516
+ fc.get('lineBreaker_t'),
517
+ 'pointerdown',
518
+ this.#DisplayLineBreak.bind(this, 't'),
519
+ false,
520
+ );
521
+ this.#eventManager.addEvent(
522
+ fc.get('lineBreaker_b'),
523
+ 'pointerdown',
524
+ this.#DisplayLineBreak.bind(this, 'b'),
525
+ false,
526
+ );
450
527
 
451
528
  /** Events are registered mobile. */
452
529
  if (isTouchDevice) {
@@ -459,7 +536,12 @@ class EventOrchestrator extends KernelInjector {
459
536
  /** code view area auto line */
460
537
  if (!this.#options.get('hasCodeMirror')) {
461
538
  const codeNumbers = fc.get('codeNumbers');
462
- const cvAuthHeight = this.$.viewer._codeViewAutoHeight.bind(this.$.viewer, fc.get('code'), codeNumbers, this.$.frameOptions.get('height') === 'auto');
539
+ const cvAuthHeight = this.$.viewer._codeViewAutoHeight.bind(
540
+ this.$.viewer,
541
+ fc.get('code'),
542
+ codeNumbers,
543
+ this.$.frameOptions.get('height') === 'auto',
544
+ );
463
545
 
464
546
  this.#eventManager.addEvent(codeArea, 'keydown', cvAuthHeight, false);
465
547
  this.#eventManager.addEvent(codeArea, 'keyup', cvAuthHeight, false);
@@ -471,7 +553,13 @@ class EventOrchestrator extends KernelInjector {
471
553
  }
472
554
 
473
555
  /** code view numbers */
474
- if (codeNumbers) this.#eventManager.addEvent(codeArea, 'scroll', this.$.viewer._scrollLineNumbers.bind(codeArea, codeNumbers), false);
556
+ if (codeNumbers)
557
+ this.#eventManager.addEvent(
558
+ codeArea,
559
+ 'scroll',
560
+ this.$.viewer._scrollLineNumbers.bind(codeArea, codeNumbers),
561
+ false,
562
+ );
475
563
  }
476
564
 
477
565
  /** markdown view area */
@@ -480,7 +568,12 @@ class EventOrchestrator extends KernelInjector {
480
568
  this.#eventManager.addEvent(markdownArea, 'mousedown', this.#OnFocus_markdown.bind(this, fc), false);
481
569
 
482
570
  const mdNumbers = fc.get('markdownNumbers');
483
- const mdAutoHeight = this.$.viewer._markdownViewAutoHeight.bind(this.$.viewer, markdownArea, mdNumbers, this.$.frameOptions.get('height') === 'auto');
571
+ const mdAutoHeight = this.$.viewer._markdownViewAutoHeight.bind(
572
+ this.$.viewer,
573
+ markdownArea,
574
+ mdNumbers,
575
+ this.$.frameOptions.get('height') === 'auto',
576
+ );
484
577
 
485
578
  this.#eventManager.addEvent(markdownArea, 'keydown', mdAutoHeight, false);
486
579
  this.#eventManager.addEvent(markdownArea, 'keyup', mdAutoHeight, false);
@@ -491,7 +584,13 @@ class EventOrchestrator extends KernelInjector {
491
584
  this.#eventManager.addEvent(markdownArea, 'keydown', InsertTab, false);
492
585
  }
493
586
 
494
- if (mdNumbers) this.#eventManager.addEvent(markdownArea, 'scroll', this.$.viewer._scrollMarkdownLineNumbers.bind(markdownArea, mdNumbers), false);
587
+ if (mdNumbers)
588
+ this.#eventManager.addEvent(
589
+ markdownArea,
590
+ 'scroll',
591
+ this.$.viewer._scrollMarkdownLineNumbers.bind(markdownArea, mdNumbers),
592
+ false,
593
+ );
495
594
  }
496
595
 
497
596
  if (fc.has('statusbar')) this.__addStatusbarEvent(fc, fc.get('options'));
@@ -503,13 +602,15 @@ class EventOrchestrator extends KernelInjector {
503
602
  this.#eventManager.addEvent(parent, 'scroll', OnScrollAbs, false);
504
603
  }
505
604
 
506
- /** focus temp (mobile) */
507
- this.#eventManager.addEvent(this.__focusTemp, 'focus', (e) => e.preventDefault(), false);
508
-
509
605
  /** document event */
510
606
  if (this.__eventDoc !== fc.get('_wd')) {
511
607
  this.__eventDoc = fc.get('_wd');
512
- this.#eventManager.addEvent(this.__eventDoc, 'selectionchange', this.#OnSelectionchange_document.bind(this, this.__eventDoc), false);
608
+ this.#eventManager.addEvent(
609
+ this.__eventDoc,
610
+ 'selectionchange',
611
+ this.#OnSelectionchange_document.bind(this, this.__eventDoc),
612
+ false,
613
+ );
513
614
  }
514
615
  }
515
616
 
@@ -522,7 +623,15 @@ class EventOrchestrator extends KernelInjector {
522
623
  */
523
624
  __addStatusbarEvent(fc, fo) {
524
625
  if (/\d+/.test(fo.get('height')) && fo.get('statusbar_resizeEnable')) {
525
- fo.set('__statusbarEvent', this.#eventManager.addEvent(fc.get('statusbar'), 'mousedown', this.#OnMouseDown_statusbar.bind(this), false));
626
+ fo.set(
627
+ '__statusbarEvent',
628
+ this.#eventManager.addEvent(
629
+ fc.get('statusbar'),
630
+ 'mousedown',
631
+ this.#OnMouseDown_statusbar.bind(this),
632
+ false,
633
+ ),
634
+ );
526
635
  } else {
527
636
  dom.utils.addClass(fc.get('statusbar'), 'se-resizing-none');
528
637
  }
@@ -569,7 +678,6 @@ class EventOrchestrator extends KernelInjector {
569
678
  this.__inputPlugin = null;
570
679
  this.__inputBlurEvent = null;
571
680
  this.__inputKeyEvent = null;
572
- this.__focusTemp = null;
573
681
  this.__eventDoc = null;
574
682
  this.__secopy = null;
575
683
  this._lineBreakComp = null;
@@ -673,6 +781,9 @@ class EventOrchestrator extends KernelInjector {
673
781
  // sticky
674
782
  this.#toolbar._resetSticky();
675
783
 
784
+ // placeholder
785
+ this.#ui._updatePlaceholder(frameContext);
786
+
676
787
  // user event
677
788
  this.#eventManager.triggerEvent('onFocus', { frameContext, event });
678
789
  // plugin event
@@ -735,12 +846,14 @@ class EventOrchestrator extends KernelInjector {
735
846
  }
736
847
 
737
848
  const toolbar = this.#context.get('toolbar_main');
738
- const isToolbarHidden = toolbar.style.display === 'none' || (this.#store.mode.isInline && !this.#toolbar.inlineToolbarAttr.isShow);
849
+ const isToolbarHidden =
850
+ toolbar.style.display === 'none' || (this.#store.mode.isInline && !this.#toolbar.inlineToolbarAttr.isShow);
739
851
  if (toolbar.offsetWidth === 0 && !isToolbarHidden) return;
740
852
 
741
853
  const opendBrowser = this.#ui.opendBrowser;
742
854
  if (opendBrowser && opendBrowser.area.style.display === 'block') {
743
- opendBrowser.body.style.maxHeight = dom.utils.getClientSize().h - opendBrowser.header.offsetHeight - 50 + 'px';
855
+ opendBrowser.body.style.maxHeight =
856
+ dom.utils.getClientSize().h - opendBrowser.header.offsetHeight - 50 + 'px';
744
857
  }
745
858
 
746
859
  if (this.#menu.currentDropdownActiveButton && this.#menu.currentDropdown) {
@@ -812,7 +925,11 @@ class EventOrchestrator extends KernelInjector {
812
925
  return;
813
926
  }
814
927
 
815
- if ((this.#store.get('rootKey') === rootKey && this.#store.get('_preventBlur')) || this.#store.get('_preventFocus')) return;
928
+ if (
929
+ (this.#store.get('rootKey') === rootKey && this.#store.get('_preventBlur')) ||
930
+ this.#store.get('_preventFocus')
931
+ )
932
+ return;
816
933
  this.#store.set('_preventFocus', true);
817
934
 
818
935
  dom.utils.removeClass(this.$.commandDispatcher.targets.get('codeView'), 'active');
@@ -838,6 +955,9 @@ class EventOrchestrator extends KernelInjector {
838
955
  this.#store.set('_lastSelectionNode', null);
839
956
  this.#eventManager.triggerEvent('onNativeBlur', { frameContext, event: e });
840
957
 
958
+ // Drop the per-line placeholder on blur; the empty-editor placeholder takes over if applicable.
959
+ this.#ui._updatePlaceholder(frameContext);
960
+
841
961
  if (this._inputFocus || this.#store.get('_preventBlur')) return;
842
962
  this.#store.set('_preventFocus', false);
843
963
 
@@ -897,7 +1017,9 @@ class EventOrchestrator extends KernelInjector {
897
1017
  if (!component) return;
898
1018
 
899
1019
  const isList = dom.check.isListCell(component.parentElement);
900
- const format = dom.utils.createElement(isList ? 'BR' : dom.check.isTableCell(component.parentElement) ? 'DIV' : this.#options.get('defaultLine'));
1020
+ const format = dom.utils.createElement(
1021
+ isList ? 'BR' : dom.check.isTableCell(component.parentElement) ? 'DIV' : this.#options.get('defaultLine'),
1022
+ );
901
1023
  if (!isList) format.innerHTML = '<br>';
902
1024
 
903
1025
  if (this.$.frameOptions.get('charCounter_type') === 'byte-html' && !this.$.char.check(format.outerHTML)) return;
@@ -939,7 +1061,11 @@ class EventOrchestrator extends KernelInjector {
939
1061
  this.__setViewportSize();
940
1062
 
941
1063
  if (isMobile && prevHeight > 0 && prevHeight - _w.visualViewport.height > 100 && this.#store.get('hasFocus')) {
942
- this.$.selection.scrollTo(this.$.selection.getRange(), { behavior: 'auto', block: 'nearest', inline: 'nearest' });
1064
+ this.$.selection.scrollTo(this.$.selection.getRange(), {
1065
+ behavior: 'auto',
1066
+ block: 'nearest',
1067
+ inline: 'nearest',
1068
+ });
943
1069
  }
944
1070
  }
945
1071
 
@@ -984,6 +1110,9 @@ class EventOrchestrator extends KernelInjector {
984
1110
  this.$.selection.init();
985
1111
  this.applyTagEffect();
986
1112
 
1113
+ // Notion-style per-line placeholder follows the caret's line.
1114
+ this.#ui._updatePlaceholder(root);
1115
+
987
1116
  // balloon toolbar - touch devices
988
1117
  if (isTouchDevice && (this.#store.mode.isBalloon || this.#store.mode.isSubBalloon)) {
989
1118
  this.#toggleToolbarBalloonDelay();
@@ -991,7 +1120,10 @@ class EventOrchestrator extends KernelInjector {
991
1120
 
992
1121
  // document type
993
1122
  if (root.has('documentType_use_header')) {
994
- const el = dom.query.getParentElement(this.$.selection.selectionNode, this.$.format.isLine.bind(this.$.format));
1123
+ const el = dom.query.getParentElement(
1124
+ this.$.selection.selectionNode,
1125
+ this.$.format.isLine.bind(this.$.format),
1126
+ );
995
1127
  root.get('documentType').on(el);
996
1128
  }
997
1129
  }
@@ -53,13 +53,15 @@ export function ButtonsHandler(e) {
53
53
  if (this.__inputBlurEvent) this.__removeInput();
54
54
 
55
55
  // blur event
56
- if (typeof plugin.toolbarInputChange === 'function') this.__inputPlugin = { obj: plugin, target: eventTarget, value: eventTarget.value };
56
+ if (typeof plugin.toolbarInputChange === 'function')
57
+ this.__inputPlugin = { obj: plugin, target: eventTarget, value: eventTarget.value };
57
58
  this.__inputBlurEvent = this.$.eventManager.addEvent(eventTarget, 'blur', (ev) => {
58
59
  if (plugin.isInputActive) return;
59
60
 
60
61
  try {
61
62
  const value = eventTarget.value.trim();
62
- if (typeof plugin.toolbarInputChange === 'function' && value !== this.__inputPlugin.value) plugin.toolbarInputChange({ target: eventTarget, value, event: ev });
63
+ if (typeof plugin.toolbarInputChange === 'function' && value !== this.__inputPlugin.value)
64
+ plugin.toolbarInputChange({ target: eventTarget, value, event: ev });
63
65
  } finally {
64
66
  // Defer flag reset — wysiwyg focus event fires synchronously during blur and checks this flag
65
67
  _w.setTimeout(() => (this._inputFocus = false), 0);
@@ -16,6 +16,11 @@ const { _w } = env;
16
16
  * @param {DragEvent} e - Event object
17
17
  */
18
18
  export function OnDragOver_wysiwyg(fc, dragCursor, _iframeTopArea, _innerToolbar, e) {
19
+ // Block-handle drag uses its own indicator + drop logic — skip the component
20
+ if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('application/x-suneditor-block-drag')) {
21
+ return;
22
+ }
23
+
19
24
  const { sc, so, ec, eo } = this.$.selection.getDragEventLocationRange(e);
20
25
  if (!sc) return;
21
26
 
@@ -91,6 +96,11 @@ export function OnDrop_wysiwyg(fc, dragCursor, e) {
91
96
  const dataTransfer = e.dataTransfer;
92
97
  if (!dataTransfer) return true;
93
98
 
99
+ // Block-handle drag handles its own drop — skip the text-insertion path.
100
+ if (Array.from(dataTransfer.types || []).includes('application/x-suneditor-block-drag')) {
101
+ return;
102
+ }
103
+
94
104
  const { sc, so, ec, eo } = this.$.selection.getDragEventLocationRange(e);
95
105
  if (!sc) return;
96
106
 
@@ -1,4 +1,12 @@
1
1
  import { dom, keyCodeMap } from '../../../helper';
2
+ import { actionExecutor } from '../executor';
3
+ import { makePorts } from '../ports';
4
+ import { reduceEnterDown } from '../rules/keydown.rule.enter';
5
+ import { ENTER_FROM_BEFOREINPUT } from '../reducers/keydown.reducer';
6
+
7
+ // The Enter rule/effects never touch the retain-style node cache (a backspace concern) — a local
8
+ // placeholder is enough to satisfy `makePorts`.
9
+ const _enterStyleNodes = { value: [] };
2
10
 
3
11
  /**
4
12
  * @typedef {import('../eventOrchestrator').default} EventManagerThis_handler_ww_input
@@ -16,6 +24,14 @@ export async function OnBeforeInput_wysiwyg(fc, e) {
16
24
  return false;
17
25
  }
18
26
 
27
+ // Enter is dispatched here — not on keydown — so the IME has finished committing before the DOM
28
+ // mutates (iOS/mobile marked-text stability). `insertParagraph` = Enter, `insertLineBreak` = Shift+Enter.
29
+ // ctrl/alt+Enter are shortcuts and never produce these inputTypes, so they stay on the keydown path.
30
+ if (ENTER_FROM_BEFOREINPUT && (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak')) {
31
+ await dispatchEnter.call(this, fc, e);
32
+ return;
33
+ }
34
+
19
35
  const data = (e.data === null ? '' : e.data === undefined ? ' ' : e.data) || '';
20
36
  if (!keyCodeMap.isComposing(e)) {
21
37
  if (!this.$.char.test(data, false)) {
@@ -29,11 +45,71 @@ export async function OnBeforeInput_wysiwyg(fc, e) {
29
45
  }
30
46
 
31
47
  // user event
32
- if ((await this.$.eventManager.triggerEvent('onBeforeInput', { frameContext: fc, event: e, data })) === false) return;
48
+ if ((await this.$.eventManager.triggerEvent('onBeforeInput', { frameContext: fc, event: e, data })) === false)
49
+ return;
33
50
  // plugin event
34
51
  await this._callPluginEventAsync('onBeforeInput', { frameContext: fc, event: e, data });
35
52
  }
36
53
 
54
+ /**
55
+ * @this {EventManagerThis_handler_ww_input}
56
+ * @description Runs SunEditor's Enter logic from the `beforeinput` event (post-IME-commit) by reusing the
57
+ * exact keydown Enter rule + effects — only the dispatch site moves off keydown. See ENTER_FROM_BEFOREINPUT.
58
+ * The guards mirror `OnKeyDown_wysiwyg` (selectMenu, input-element, open-dropdown) so a `beforeinput` Enter
59
+ * is dropped in the same situations a keydown Enter is.
60
+ * @param {SunEditor.FrameContext} fc - Frame context object
61
+ * @param {InputEvent} e - The `beforeinput` event (`insertParagraph` | `insertLineBreak`)
62
+ */
63
+ async function dispatchEnter(fc, e) {
64
+ // Skip while an IME composition is still active
65
+ if (e.isComposing || this.isComposing) return;
66
+ if (this.$.ui.selectMenuOn) return;
67
+
68
+ let selectionNode = this.$.selection.getNode();
69
+ if (dom.check.isInputElement(selectionNode)) return;
70
+ if (this.$.menu.currentDropdownName) return;
71
+
72
+ if (dom.check.isWysiwygFrame(selectionNode)) {
73
+ this._setDefaultLine(this.$.options.get('defaultLine'));
74
+ selectionNode = this.$.selection.getNode();
75
+ }
76
+
77
+ const normalized = this._normalizeEnterRange();
78
+ if (normalized) selectionNode = normalized;
79
+
80
+ const range = this.$.selection.getRange();
81
+ const formatEl = /** @type {HTMLElement} */ (this.$.format.getLine(selectionNode, null) || selectionNode);
82
+ const shift = e.inputType === 'insertLineBreak';
83
+
84
+ /** @type {import('../reducers/keydown.reducer').KeydownReducerCtx} */
85
+ const ctx = {
86
+ e,
87
+ fc,
88
+ store: this.$.store,
89
+ options: this.$.options,
90
+ frameOptions: this.$.frameOptions,
91
+ range,
92
+ selectionNode,
93
+ formatEl,
94
+ keyCode: 'Enter',
95
+ ctrl: false,
96
+ alt: false,
97
+ shift,
98
+ };
99
+
100
+ const ports = makePorts(this, { _styleNodes: _enterStyleNodes });
101
+ const actions = [];
102
+ reduceEnterDown(actions, ports, ctx);
103
+
104
+ // `beforeinput.preventDefault()` MUST run synchronously (before the first `await` below), or the browser
105
+ // commits its native insertParagraph/insertLineBreak on top of ours (duplicate line / cloned container).
106
+ if (actions.some((a) => a.t === 'event.prevent' || a.t === 'event.prevent.stop')) {
107
+ e.preventDefault();
108
+ }
109
+
110
+ await actionExecutor(actions, { ports, ctx });
111
+ }
112
+
37
113
  /**
38
114
  * @this {EventManagerThis_handler_ww_input}
39
115
  * @param {SunEditor.FrameContext} fc - Frame context object
@@ -49,7 +125,13 @@ export async function OnInput_wysiwyg(fc, e) {
49
125
  const range = this.$.selection.getRange();
50
126
  const selectionNode = this.$.selection.getNode();
51
127
  const formatEl = this.$.format.getLine(selectionNode, null);
52
- if (!this.$.format.isNormalLine(formatEl) && !this.$.format.isBrLine(formatEl) && range.collapsed && !this.$.component.is(selectionNode) && !dom.check.isList(selectionNode)) {
128
+ if (
129
+ !this.$.format.isNormalLine(formatEl) &&
130
+ !this.$.format.isBrLine(formatEl) &&
131
+ range.collapsed &&
132
+ !this.$.component.is(selectionNode) &&
133
+ !dom.check.isList(selectionNode)
134
+ ) {
53
135
  const rangeEl = this.$.format.getBlock(selectionNode, null);
54
136
  this._setDefaultLine(this.$.format.isBlock(rangeEl) ? 'DIV' : this.$.options.get('defaultLine'));
55
137
  }