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
@@ -1,7 +1,7 @@
1
1
  import { dom, env, unicode, keyCodeMap } from '../../../helper';
2
2
  import { actionExecutor } from '../executor';
3
3
  import { makePorts } from '../ports';
4
- import { reduceKeydown } from '../reducers/keydown.reducer';
4
+ import { reduceKeydown, ENTER_FROM_BEFOREINPUT } from '../reducers/keydown.reducer';
5
5
 
6
6
  const { _w } = env;
7
7
  const FRONT_ZEROWIDTH = new RegExp(unicode.zeroWidthSpace + '+', '');
@@ -47,25 +47,37 @@ export async function OnKeyDown_wysiwyg(fc, e) {
47
47
  if (!this.$.store.mode.isSubBalloonAlways) this._hideToolbar_sub();
48
48
  }
49
49
 
50
- // user event
51
- if ((await this.$.eventManager.triggerEvent('onKeyDown', { frameContext: fc, event: e })) === false) return;
52
-
53
- /** default key action */
54
- if (keyCodeMap.isEnter(keyCode) && this.$.format.isLine(this.$.selection.getRange()?.startContainer)) {
55
- this.$.selection.resetRangeToTextNode();
56
- selectionNode = this.$.selection.getNode();
50
+ /**
51
+ * default key action — normalize the Enter range before the reducer reads it.
52
+ * Skipped when Enter is handled from `beforeinput` (ENTER_FROM_BEFOREINPUT): mutating the DOM here
53
+ * on keydown re-traps the iOS/mobile IME marked-text — `dispatchEnter` runs this same normalization
54
+ * later, after the IME has committed.
55
+ */
56
+ if (!ENTER_FROM_BEFOREINPUT && keyCodeMap.isEnter(keyCode)) {
57
+ const normalized = this._normalizeEnterRange();
58
+ if (normalized) selectionNode = normalized;
57
59
  }
58
60
 
59
61
  const range = this.$.selection.getRange();
60
62
  const formatEl = /** @type {HTMLElement} */ (this.$.format.getLine(selectionNode, null) || selectionNode);
61
63
 
62
64
  /** Shortcuts */
63
- if (ctrl && !keyCodeMap.isNonTextKey(keyCode) && this.$.shortcuts.command(e, ctrl, shift, keyCode, '', false, null, null)) {
65
+ if (
66
+ ctrl &&
67
+ !keyCodeMap.isNonTextKey(keyCode) &&
68
+ this.$.shortcuts.command(e, ctrl, shift, keyCode, '', false, null, null)
69
+ ) {
64
70
  this._onShortcutKey = true;
65
71
  e.preventDefault();
66
72
  e.stopPropagation();
67
73
  return false;
68
- } else if (!ctrl && !keyCodeMap.isNonTextKey(keyCode) && this.$.format.isLine(formatEl) && range.collapsed && dom.check.isEdgePoint(range.startContainer, 0, 'front')) {
74
+ } else if (
75
+ !ctrl &&
76
+ !keyCodeMap.isNonTextKey(keyCode) &&
77
+ this.$.format.isLine(formatEl) &&
78
+ range.collapsed &&
79
+ dom.check.isEdgePoint(range.startContainer, 0, 'front')
80
+ ) {
69
81
  const keyword = /** @type {Text} */ (range.startContainer).substringData?.(0, range.startOffset);
70
82
  if (keyword && this.$.shortcuts.command(e, false, shift, keyCode, keyword, true, formatEl, range)) {
71
83
  this._onShortcutKey = true;
@@ -77,16 +89,36 @@ export async function OnKeyDown_wysiwyg(fc, e) {
77
89
  this._onShortcutKey = false;
78
90
  }
79
91
 
80
- // plugin event
81
- if ((await this._callPluginEventAsync('onKeyDown', { frameContext: fc, event: e, range, line: formatEl })) === false) return;
82
-
83
92
  // reducer / actions
84
93
  /** @type {import('../reducers/keydown.reducer').KeydownReducerCtx} */
85
- const ctx = { e, fc, store: this.$.store, options: this.$.options, frameOptions: this.$.frameOptions, range, selectionNode, formatEl, keyCode, ctrl, alt, shift };
94
+ const ctx = {
95
+ e,
96
+ fc,
97
+ store: this.$.store,
98
+ options: this.$.options,
99
+ frameOptions: this.$.frameOptions,
100
+ range,
101
+ selectionNode,
102
+ formatEl,
103
+ keyCode,
104
+ ctrl,
105
+ alt,
106
+ shift,
107
+ };
86
108
  const ports = makePorts(this, { _styleNodes });
87
109
 
88
110
  // action execute
89
111
  const actions = await reduceKeydown(ports, ctx);
112
+
113
+ if ((await this.$.eventManager.triggerEvent('onKeyDown', { frameContext: fc, event: e })) === false) return;
114
+
115
+ // plugin event
116
+ if (
117
+ (await this._callPluginEventAsync('onKeyDown', { frameContext: fc, event: e, range, line: formatEl })) === false
118
+ )
119
+ return;
120
+
121
+ // action execute
90
122
  await actionExecutor(actions, { ports, ctx });
91
123
  }
92
124
 
@@ -110,7 +142,11 @@ export async function OnKeyUp_wysiwyg(fc, e) {
110
142
  const range = this.$.selection.getRange();
111
143
  let selectionNode = this.$.selection.getNode();
112
144
 
113
- if ((this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon) && (((this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) && !keyCodeMap.isEsc(keyCode)) || !range.collapsed)) {
145
+ if (
146
+ (this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon) &&
147
+ (((this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) && !keyCodeMap.isEsc(keyCode)) ||
148
+ !range.collapsed)
149
+ ) {
114
150
  if (this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) {
115
151
  if (!keyCodeMap.isEsc(keyCode)) this._showToolbarBalloonDelay();
116
152
  } else {
@@ -121,14 +157,25 @@ export async function OnKeyUp_wysiwyg(fc, e) {
121
157
  }
122
158
 
123
159
  /** when format tag deleted */
124
- if (keyCodeMap.isBackspace(keyCode) && dom.check.isWysiwygFrame(selectionNode) && selectionNode.textContent === '' && selectionNode.children.length === 0) {
160
+ if (
161
+ keyCodeMap.isBackspace(keyCode) &&
162
+ dom.check.isWysiwygFrame(selectionNode) &&
163
+ selectionNode.textContent === '' &&
164
+ selectionNode.children.length === 0
165
+ ) {
125
166
  e.preventDefault();
126
167
  e.stopPropagation();
127
168
 
128
169
  selectionNode.innerHTML = '';
129
170
 
130
171
  const currentNodeName = this.$.store.get('currentNodes')[0];
131
- const oFormatTag = dom.utils.createElement(this.$.format.isLine(currentNodeName) && !dom.check.isListCell(currentNodeName) ? currentNodeName : this.$.options.get('defaultLine'), null, '<br>');
172
+ const oFormatTag = dom.utils.createElement(
173
+ this.$.format.isLine(currentNodeName) && !dom.check.isListCell(currentNodeName)
174
+ ? currentNodeName
175
+ : this.$.options.get('defaultLine'),
176
+ null,
177
+ '<br>',
178
+ );
132
179
  selectionNode.appendChild(oFormatTag);
133
180
  this.$.selection.setRange(oFormatTag, 0, oFormatTag, 0);
134
181
  this.applyTagEffect();
@@ -172,7 +219,12 @@ export async function OnKeyUp_wysiwyg(fc, e) {
172
219
  }
173
220
 
174
221
  const textKey = !keyState.ctrl && !keyState.alt && !keyCodeMap.isNonTextKey(keyCode);
175
- if (textKey && selectionNode.nodeType === 3 && unicode.zeroWidthRegExp.test(selectionNode.textContent) && !(e.isComposing !== undefined ? e.isComposing : this.isComposing)) {
222
+ if (
223
+ textKey &&
224
+ selectionNode.nodeType === 3 &&
225
+ unicode.zeroWidthRegExp.test(selectionNode.textContent) &&
226
+ !(e.isComposing !== undefined ? e.isComposing : this.isComposing)
227
+ ) {
176
228
  let so = range.startOffset,
177
229
  eo = range.endOffset;
178
230
  const frontZeroWidthCnt = (selectionNode.textContent.substring(0, eo).match(FRONT_ZEROWIDTH) || '').length;
@@ -182,7 +234,12 @@ export async function OnKeyUp_wysiwyg(fc, e) {
182
234
  this.$.selection.setRange(selectionNode, so < 0 ? 0 : so, selectionNode, eo < 0 ? 0 : eo);
183
235
  }
184
236
 
185
- if (keyCodeMap.isRemoveKey(keyCode) && dom.check.isZeroWidth(formatEl?.textContent) && !formatEl.previousElementSibling && !dom.check.isListCell(formatEl)) {
237
+ if (
238
+ keyCodeMap.isRemoveKey(keyCode) &&
239
+ dom.check.isZeroWidth(formatEl?.textContent) &&
240
+ !formatEl.previousElementSibling &&
241
+ !dom.check.isListCell(formatEl)
242
+ ) {
186
243
  const rsMode = this.$.options.get('retainStyleMode');
187
244
  if (rsMode !== 'none' && _styleNodes.value?.length > 0) {
188
245
  if (rsMode === 'repeat') {
@@ -209,7 +266,10 @@ export async function OnKeyUp_wysiwyg(fc, e) {
209
266
  if (fc.has('documentType_use_header')) {
210
267
  if (keyCodeMap.isDocumentTypeObserverKey(keyCode)) {
211
268
  fc.get('documentType').reHeader();
212
- const el = dom.query.getParentElement(this.$.selection.selectionNode, this.$.format.isLine.bind(this.$.format));
269
+ const el = dom.query.getParentElement(
270
+ this.$.selection.selectionNode,
271
+ this.$.format.isLine.bind(this.$.format),
272
+ );
213
273
  fc.get('documentType').on(el);
214
274
  } else {
215
275
  const el = dom.query.getParentElement(selectionNode, (current) => current.nodeType === 1);
@@ -220,7 +280,8 @@ export async function OnKeyUp_wysiwyg(fc, e) {
220
280
  // user event
221
281
  if ((await this.$.eventManager.triggerEvent('onKeyUp', { frameContext: fc, event: e })) === false) return;
222
282
  // plugin event
223
- if ((await this._callPluginEventAsync('onKeyUp', { frameContext: fc, event: e, range, line: formatEl })) === false) return;
283
+ if ((await this._callPluginEventAsync('onKeyUp', { frameContext: fc, event: e, range, line: formatEl })) === false)
284
+ return;
224
285
 
225
286
  if (keyCodeMap.isHistoryRelevantKey(keyCode)) {
226
287
  this.$.history.push(true);
@@ -3,6 +3,12 @@ import { _DragHandle } from '../../../modules/ui';
3
3
 
4
4
  const { _w } = env;
5
5
 
6
+ /**
7
+ * Per-frame rAF state for wysiwyg mousemove coalescing.
8
+ * @type {WeakMap<*, {rafId: ?number, event: ?MouseEvent}>}
9
+ */
10
+ const __wwMousemoveRaf = new WeakMap();
11
+
6
12
  /**
7
13
  * @typedef {import('../eventOrchestrator').default} EventManagerThis_handler_ww_mouse
8
14
  */
@@ -102,7 +108,12 @@ export async function OnClick_wysiwyg(fc, e) {
102
108
  if (e.detail === 3) {
103
109
  const range = this.$.selection.getRange();
104
110
  if (this.$.format.isLine(range.endContainer) && range.endOffset === 0) {
105
- this.$.selection.setRange(range.startContainer, range.startOffset, range.startContainer, range.startContainer.textContent.length);
111
+ this.$.selection.setRange(
112
+ range.startContainer,
113
+ range.startOffset,
114
+ range.startContainer,
115
+ range.startContainer.textContent.length,
116
+ );
106
117
  }
107
118
  }
108
119
 
@@ -122,7 +133,8 @@ export async function OnClick_wysiwyg(fc, e) {
122
133
  !dom.check.isWysiwygFrame(selectionNode) &&
123
134
  !this.$.component.is(selectionNode) &&
124
135
  (!dom.check.isTableElements(selectionNode) || dom.check.isTableCell(selectionNode)) &&
125
- this._setDefaultLine(this.$.format.isBlock(rangeEl) ? 'DIV' : this.$.options.get('defaultLine')) !== null
136
+ this._setDefaultLine(this.$.format.isBlock(rangeEl) ? 'DIV' : this.$.options.get('defaultLine')) !==
137
+ null
126
138
  ) {
127
139
  e.preventDefault();
128
140
  this.$.focusManager.focus();
@@ -134,7 +146,8 @@ export async function OnClick_wysiwyg(fc, e) {
134
146
  this.$.commandDispatcher._copyFormat();
135
147
 
136
148
  // Defer balloon toggle — selection range is finalized after mouseup event returns
137
- if (this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon) _w.setTimeout(this._toggleToolbarBalloon.bind(this), 0);
149
+ if (this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon)
150
+ _w.setTimeout(this._toggleToolbarBalloon.bind(this), 0);
138
151
  }
139
152
 
140
153
  /**
@@ -144,14 +157,35 @@ export async function OnClick_wysiwyg(fc, e) {
144
157
  */
145
158
  export function OnMouseMove_wysiwyg(fc, e) {
146
159
  if (fc.get('isReadOnly') || fc.get('isDisabled')) return false;
147
- const eventTarget = dom.query.getEventTarget(e);
148
160
 
149
- // over component
150
- if (_DragHandle.get('__overInfo') !== false) {
151
- this.$.component.hoverSelect(eventTarget);
161
+ let s = __wwMousemoveRaf.get(fc);
162
+
163
+ if (!s) {
164
+ s = { rafId: null, event: null };
165
+ __wwMousemoveRaf.set(fc, s);
152
166
  }
153
167
 
154
- this._callPluginEvent('onMouseMove', { frameContext: fc, event: e });
168
+ s.event = e;
169
+ if (s.rafId !== null) return;
170
+
171
+ s.rafId = _w.requestAnimationFrame(() => {
172
+ const ev = s.event;
173
+ s.rafId = null;
174
+ s.event = null;
175
+ if (!ev) return;
176
+
177
+ const eventTarget = dom.query.getEventTarget(ev);
178
+
179
+ // over component — skip while select menu (block handle menu) is open
180
+ if (_DragHandle.get('__overInfo') !== false && !this.$.ui.selectMenuOn) {
181
+ this.$.component.hoverSelect(eventTarget);
182
+ }
183
+
184
+ // block handle
185
+ this.$.ui.blockHandle?.positionForTarget(eventTarget, ev.clientY);
186
+
187
+ this._callPluginEvent('onMouseMove', { frameContext: fc, event: ev });
188
+ });
155
189
  }
156
190
 
157
191
  /**
@@ -160,6 +194,9 @@ export function OnMouseMove_wysiwyg(fc, e) {
160
194
  * @param {MouseEvent} e - Event object
161
195
  */
162
196
  export async function OnMouseLeave_wysiwyg(fc, e) {
197
+ // block handle hide
198
+ this.$.ui.blockHandle?.hide(e);
199
+
163
200
  // user event
164
201
  if ((await this.$.eventManager.triggerEvent('onMouseLeave', { frameContext: fc, event: e })) === false) return;
165
202
  // plugin event
@@ -1,5 +1,3 @@
1
- import { isMobile } from '../../helper/env';
2
-
3
1
  /**
4
2
  * @typedef {import('./eventOrchestrator').default} EventManagerInstanceType
5
3
  */
@@ -37,6 +35,7 @@ import { isMobile } from '../../helper/env';
37
35
  * @property {(...args: Parameters<Format['isEdgeLine']>) => ReturnType<Format['isEdgeLine']>} isEdgeLine
38
36
  * @property {(...args: Parameters<Format['removeBlock']>) => ReturnType<Format['removeBlock']>} removeBlock
39
37
  * @property {(...args: Parameters<Format['addLine']>) => ReturnType<Format['addLine']>} addLine
38
+ * @property {(...args: Parameters<Format['addLineAfter']>) => ReturnType<Format['addLineAfter']>} addLineAfter
40
39
  */
41
40
 
42
41
  /**
@@ -82,7 +81,20 @@ import { isMobile } from '../../helper/env';
82
81
  * @param {*} param1._styleNodes - Style nodes reference object
83
82
  */
84
83
  export function makePorts(inst, { _styleNodes }) {
85
- const { frameContext, ui, focusManager, selection, format, listFormat, component, html, nodeTransform, history, char, menu } = inst.$;
84
+ const {
85
+ frameContext,
86
+ ui,
87
+ focusManager,
88
+ selection,
89
+ format,
90
+ listFormat,
91
+ component,
92
+ html,
93
+ nodeTransform,
94
+ history,
95
+ char,
96
+ menu,
97
+ } = inst.$;
86
98
 
87
99
  return {
88
100
  // focusManager
@@ -112,6 +124,7 @@ export function makePorts(inst, { _styleNodes }) {
112
124
  isEdgeLine: (node, offset, dir) => format.isEdgeLine(node, offset, dir),
113
125
  removeBlock: (n, p) => format.removeBlock(n, p),
114
126
  addLine: (el, nextOrTag) => format.addLine(el, nextOrTag),
127
+ addLineAfter: (el) => format.addLineAfter(el),
115
128
  },
116
129
 
117
130
  listFormat: {
@@ -159,25 +172,26 @@ export function makePorts(inst, { _styleNodes }) {
159
172
  // === enter event specific ===
160
173
  /**
161
174
  * @description Scrolls the editor view to the caret position after pressing `Enter`.
162
- * @param {Range} range Range object
175
+ * @param {Range} range Pre-Enter snapshot range (fallback only).
163
176
  */
164
177
  enterScrollTo(range) {
165
178
  ui._iframeAutoHeight(frameContext);
166
179
 
167
- // scroll to
168
- // if (isMobile && inst.scrollparents.length > 0) return;
169
- selection.scrollTo(range, { behavior: 'auto', block: 'nearest', inline: 'nearest' });
180
+ // Scroll to the *live* post-Enter caret, not the pre-Enter `range` snapshot.
181
+ selection.scrollTo(selection.getRange() || range, {
182
+ behavior: 'auto',
183
+ block: 'nearest',
184
+ inline: 'nearest',
185
+ });
170
186
  },
171
187
  /**
172
- * @description Prevents the default behavior of the `Enter` key and refocuses the editor.
173
- * @param {Event} e The keyboard event
188
+ * @description Prevents the default behavior of the `Enter` key.
189
+ * Enter now runs from `beforeinput` (post-IME-commit), so the former mobile focus-shuffle
190
+ * (temp-focus → refocus, to force-end a virtual-keyboard IME session) is unnecessary.
191
+ * @param {Event} e The keyboard/input event
174
192
  */
175
193
  enterPrevent(e) {
176
194
  e.preventDefault();
177
- if (!isMobile) return;
178
-
179
- inst.__focusTemp.focus({ preventScroll: true });
180
- frameContext.get('wysiwyg').focus({ preventScroll: true });
181
195
  },
182
196
  };
183
197
  }
@@ -9,13 +9,23 @@ import { A } from '../actions';
9
9
 
10
10
  const { isOSX_IOS } = env;
11
11
 
12
+ /**
13
+ * @description Enter is processed from the `beforeinput` event (post-IME-commit) instead of `keydown`,
14
+ * to avoid trapping iOS/mobile IME marked-text when `keydown` mutates the DOM (see `handler_ww_input.js`).
15
+ * Flip to `false` to instantly restore the legacy synchronous `keydown` Enter path — no other file needs
16
+ * touching for rollback (the `keydown` Enter gate, the `handler_ww_key` normalization guard, and the
17
+ * `beforeinput` dispatch all key off this single flag).
18
+ * @type {boolean}
19
+ */
20
+ export const ENTER_FROM_BEFOREINPUT = true;
21
+
12
22
  /**
13
23
  * @typedef {import('../ports').EventReducerPorts} EventPorts
14
24
  */
15
25
 
16
26
  /**
17
27
  * @typedef {Object} KeydownReducerCtx - Keydown Reducer Context object
18
- * @property {KeyboardEvent} ctx.e - The keyboard event
28
+ * @property {KeyboardEvent|InputEvent} ctx.e - The keyboard event (or the `beforeinput` InputEvent when Enter is dispatched from `beforeinput`)
19
29
  * @property {SunEditor.FrameContext} ctx.fc - Frame context object
20
30
  * @property {SunEditor.Store} ctx.store - Editor store object
21
31
  * @property {SunEditor.Options} ctx.options - Options object
@@ -62,8 +72,13 @@ export async function reduceKeydown(ports, ctx) {
62
72
  break;
63
73
  }
64
74
  case 'Enter' /** enter key */: {
65
- if (reduceEnterDown(actions, ports, ctx) === false) {
66
- return actions;
75
+ // Enter is handled in `beforeinput` (IME stability) — see ENTER_FROM_BEFOREINPUT.
76
+ // Gate only (not an early-return) so the post-switch `documentTypeRefreshHeader`
77
+ // still runs for a selectRange + Enter on keydown.
78
+ if (!ENTER_FROM_BEFOREINPUT) {
79
+ if (reduceEnterDown(actions, ports, ctx) === false) {
80
+ return actions;
81
+ }
67
82
  }
68
83
  break;
69
84
  }
@@ -79,13 +94,26 @@ export async function reduceKeydown(ports, ctx) {
79
94
  }
80
95
 
81
96
  const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
82
- if (!ctrl && !alt && !selectRange && !keyCodeMap.isNonTextKey(keyCode) && dom.check.isBreak(range.commonAncestorContainer)) {
97
+ if (
98
+ !ctrl &&
99
+ !alt &&
100
+ !selectRange &&
101
+ !keyCodeMap.isNonTextKey(keyCode) &&
102
+ dom.check.isBreak(range.commonAncestorContainer)
103
+ ) {
83
104
  actions.push(A.keydownInputInsertZWS());
84
105
  return actions;
85
106
  }
86
107
 
87
108
  // document type
88
- if (fc.has('documentType_use_header') && selectRange && !ctrl && !alt && !shift && !keyCodeMap.isDirectionKey(keyCode)) {
109
+ if (
110
+ fc.has('documentType_use_header') &&
111
+ selectRange &&
112
+ !ctrl &&
113
+ !alt &&
114
+ !shift &&
115
+ !keyCodeMap.isDirectionKey(keyCode)
116
+ ) {
89
117
  actions.push(A.documentTypeRefreshHeader());
90
118
  return actions;
91
119
  }
@@ -23,7 +23,16 @@ export function reduceArrowDown(actions, ports, ctx) {
23
23
  const rtl = ctx.options.get('_rtl');
24
24
 
25
25
  // In RTL, ArrowLeft is forward (toward end on the left), ArrowRight is backward (toward start on the right)
26
- const hDir = keyCode === 'ArrowLeft' ? (rtl ? 'forward' : 'back') : keyCode === 'ArrowRight' ? (rtl ? 'back' : 'forward') : null;
26
+ const hDir =
27
+ keyCode === 'ArrowLeft'
28
+ ? rtl
29
+ ? 'forward'
30
+ : 'back'
31
+ : keyCode === 'ArrowRight'
32
+ ? rtl
33
+ ? 'back'
34
+ : 'forward'
35
+ : null;
27
36
 
28
37
  // next component
29
38
  let cmponentInfo = null;
@@ -46,7 +55,8 @@ export function reduceArrowDown(actions, ports, ctx) {
46
55
  }
47
56
  } else if (hDir === 'back') {
48
57
  if (dom.check.isEdgePoint(selectionNode, range.startOffset, 'front')) {
49
- const prevEl = selectionNode.previousElementSibling || dom.query.getPreviousDeepestNode(selectionNode);
58
+ const prevEl =
59
+ selectionNode.previousElementSibling || dom.query.getPreviousDeepestNode(selectionNode);
50
60
  if (prevEl) {
51
61
  if (component.is(prevEl)) cmponentInfo = component.get(prevEl);
52
62
  } else if (component.is(formatEl.previousElementSibling)) {
@@ -1,5 +1,11 @@
1
1
  import { dom } from '../../../helper';
2
- import { cleanRemovedTags, hardDelete, isUneditableNode, setDefaultLine, isRtlBidiMismatch } from '../effects/ruleHelpers';
2
+ import {
3
+ cleanRemovedTags,
4
+ hardDelete,
5
+ isUneditableNode,
6
+ setDefaultLine,
7
+ isRtlBidiMismatch,
8
+ } from '../effects/ruleHelpers';
3
9
  import { A } from '../actions';
4
10
 
5
11
  /**
@@ -23,7 +29,11 @@ export function reduceBackspaceDown(actions, ports, ctx) {
23
29
 
24
30
  const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
25
31
  // RTL bidi guard: if offset 0 is actually at the visual end due to LTR text in RTL line, skip front-edge handling
26
- const bidiNotFront = options.get('_rtl') && !selectRange && range.startOffset === 0 && isRtlBidiMismatch(range, formatEl, 'front', fc.get('_wd'));
32
+ const bidiNotFront =
33
+ options.get('_rtl') &&
34
+ !selectRange &&
35
+ range.startOffset === 0 &&
36
+ isRtlBidiMismatch(range, formatEl, 'front', fc.get('_wd'));
27
37
 
28
38
  actions.push(A.componentDeselect());
29
39
  actions.push(A.cacheStyleNode());
@@ -33,7 +43,13 @@ export function reduceBackspaceDown(actions, ports, ctx) {
33
43
  return true;
34
44
  }
35
45
 
36
- if (!format.isNormalLine(formatEl) && !format.isBrLine(formatEl) && !fc.get('wysiwyg').firstElementChild && !component.is(selectionNode) && setDefaultLine(ports, options.get('defaultLine')) !== null) {
46
+ if (
47
+ !format.isNormalLine(formatEl) &&
48
+ !format.isBrLine(formatEl) &&
49
+ !fc.get('wysiwyg').firstElementChild &&
50
+ !component.is(selectionNode) &&
51
+ setDefaultLine(ports, options.get('defaultLine')) !== null
52
+ ) {
37
53
  actions.push(A.preventStop());
38
54
  return false;
39
55
  }
@@ -62,7 +78,9 @@ export function reduceBackspaceDown(actions, ports, ctx) {
62
78
  !selectionNode.previousSibling &&
63
79
  !dom.check.isListCell(formatEl) &&
64
80
  format.isLine(formatEl) &&
65
- (!format.isBrLine(formatEl) || format.isClosureBrLine(formatEl) || dom.check.isWysiwygFrame(formatEl.parentNode))
81
+ (!format.isBrLine(formatEl) ||
82
+ format.isClosureBrLine(formatEl) ||
83
+ dom.check.isWysiwygFrame(formatEl.parentNode))
66
84
  ) {
67
85
  // closure range
68
86
  if (format.isClosureBlock(formatEl.parentNode)) {
@@ -79,7 +97,11 @@ export function reduceBackspaceDown(actions, ports, ctx) {
79
97
  }
80
98
 
81
99
  // maintain default format
82
- if (dom.check.isWysiwygFrame(formatEl.parentNode) && formatEl.childNodes.length <= 1 && (!formatEl.firstChild || dom.check.isZeroWidth(formatEl.textContent))) {
100
+ if (
101
+ dom.check.isWysiwygFrame(formatEl.parentNode) &&
102
+ formatEl.childNodes.length <= 1 &&
103
+ (!formatEl.firstChild || dom.check.isZeroWidth(formatEl.textContent))
104
+ ) {
83
105
  actions.push(A.preventStop());
84
106
  actions.push(A.backspaceFormatMaintain(formatEl));
85
107
  }
@@ -90,7 +112,14 @@ export function reduceBackspaceDown(actions, ports, ctx) {
90
112
 
91
113
  // clean remove tag
92
114
  const startCon = range.startContainer;
93
- if (formatEl && !bidiNotFront && !formatEl.previousElementSibling && range.startOffset === 0 && startCon.nodeType === 3 && dom.check.isZeroWidth(startCon)) {
115
+ if (
116
+ formatEl &&
117
+ !bidiNotFront &&
118
+ !formatEl.previousElementSibling &&
119
+ range.startOffset === 0 &&
120
+ startCon.nodeType === 3 &&
121
+ dom.check.isZeroWidth(startCon)
122
+ ) {
94
123
  if (cleanRemovedTags(ports, startCon, formatEl) === true) return true;
95
124
  }
96
125
 
@@ -136,8 +165,12 @@ export function reduceBackspaceDown(actions, ports, ctx) {
136
165
  dom.check.isListCell(formatEl) &&
137
166
  dom.check.isList(rangeEl) &&
138
167
  (dom.check.isListCell(rangeEl.parentElement) || formatEl.previousElementSibling) &&
139
- (selectionNode === formatEl || (selectionNode.nodeType === 3 && (!selectionNode.previousSibling || dom.check.isList(selectionNode.previousSibling)))) &&
140
- (format.getLine(range.startContainer, null) !== format.getLine(range.endContainer, null) ? rangeEl.contains(range.startContainer) : range.startOffset === 0 && range.collapsed && !bidiNotFront)
168
+ (selectionNode === formatEl ||
169
+ (selectionNode.nodeType === 3 &&
170
+ (!selectionNode.previousSibling || dom.check.isList(selectionNode.previousSibling)))) &&
171
+ (format.getLine(range.startContainer, null) !== format.getLine(range.endContainer, null)
172
+ ? rangeEl.contains(range.startContainer)
173
+ : range.startOffset === 0 && range.collapsed && !bidiNotFront)
141
174
  ) {
142
175
  if (range.startContainer !== range.endContainer) {
143
176
  actions.push(A.prevent());
@@ -149,10 +182,16 @@ export function reduceBackspaceDown(actions, ports, ctx) {
149
182
  actions.push(A.prevent());
150
183
 
151
184
  let prevLast = prev;
152
- if (!prev.contains(formatEl) && dom.check.isListCell(prevLast) && dom.check.isList(prevLast.lastElementChild)) {
185
+ if (
186
+ !prev.contains(formatEl) &&
187
+ dom.check.isListCell(prevLast) &&
188
+ dom.check.isList(prevLast.lastElementChild)
189
+ ) {
153
190
  prevLast = /** @type {HTMLLIElement} */ (prevLast.lastElementChild.lastElementChild);
154
191
  while (dom.check.isListCell(prevLast) && dom.check.isList(prevLast.lastElementChild)) {
155
- prevLast = /** @type {HTMLLIElement} */ (prevLast.lastElementChild && prevLast.lastElementChild.lastElementChild);
192
+ prevLast = /** @type {HTMLLIElement} */ (
193
+ prevLast.lastElementChild && prevLast.lastElementChild.lastElementChild
194
+ );
156
195
  }
157
196
  prev = prevLast;
158
197
  }
@@ -171,7 +210,10 @@ export function reduceBackspaceDown(actions, ports, ctx) {
171
210
  let comm = commonCon;
172
211
  while (comm && comm !== rangeEl && !dom.check.isWysiwygFrame(comm)) {
173
212
  if (comm.previousSibling) {
174
- if (comm.previousSibling.nodeType === 1 || !dom.check.isZeroWidth(comm.previousSibling.textContent.trim())) {
213
+ if (
214
+ comm.previousSibling.nodeType === 1 ||
215
+ !dom.check.isZeroWidth(comm.previousSibling.textContent.trim())
216
+ ) {
175
217
  detach = false;
176
218
  break;
177
219
  }
@@ -181,7 +223,15 @@ export function reduceBackspaceDown(actions, ports, ctx) {
181
223
 
182
224
  if (detach && rangeEl.parentNode) {
183
225
  actions.push(A.prevent());
184
- actions.push(A.formatRemoveBlock(rangeEl, dom.check.isListCell(formatEl) ? [formatEl] : null, null, false, false));
226
+ actions.push(
227
+ A.formatRemoveBlock(
228
+ rangeEl,
229
+ dom.check.isListCell(formatEl) ? [formatEl] : null,
230
+ null,
231
+ false,
232
+ false,
233
+ ),
234
+ );
185
235
  actions.push(A.historyPush(true));
186
236
  return true;
187
237
  }
@@ -199,13 +249,25 @@ export function reduceBackspaceDown(actions, ports, ctx) {
199
249
  }
200
250
 
201
251
  // component
202
- if (!selectRange && formatEl && (range.startOffset === 0 || (selectionNode === formatEl ? formatEl.childNodes[range.startOffset] : false))) {
252
+ if (
253
+ !selectRange &&
254
+ formatEl &&
255
+ (range.startOffset === 0 || (selectionNode === formatEl ? formatEl.childNodes[range.startOffset] : false))
256
+ ) {
203
257
  const isList = dom.check.isListCell(formatEl);
204
258
  const sel = selectionNode === formatEl ? formatEl.childNodes[range.startOffset] : selectionNode;
205
259
  const prev = (isList ? sel : formatEl).previousSibling;
206
260
  // select file component
207
- const ignoreZWS = isList || ((commonCon.nodeType === 3 || dom.check.isBreak(commonCon)) && !commonCon.previousSibling && range.startOffset === 0);
208
- if (sel && (isList || !sel.previousSibling) && ((commonCon && component.is(commonCon.previousSibling)) || (ignoreZWS && component.is(prev)))) {
261
+ const ignoreZWS =
262
+ isList ||
263
+ ((commonCon.nodeType === 3 || dom.check.isBreak(commonCon)) &&
264
+ !commonCon.previousSibling &&
265
+ range.startOffset === 0);
266
+ if (
267
+ sel &&
268
+ (isList || !sel.previousSibling) &&
269
+ ((commonCon && component.is(commonCon.previousSibling)) || (ignoreZWS && component.is(prev)))
270
+ ) {
209
271
  const fileComponentInfo = component.get(prev);
210
272
  if (fileComponentInfo) {
211
273
  actions.push(A.preventStop());
@@ -225,5 +287,38 @@ export function reduceBackspaceDown(actions, ports, ctx) {
225
287
  }
226
288
  }
227
289
 
290
+ // empty row inside a brLine (PRE): Enter leaves the caret on a bare <br> that begins an empty row
291
+ if (
292
+ !selectRange &&
293
+ format.isBrLine(formatEl) &&
294
+ dom.check.isBreak(range.startContainer) &&
295
+ range.startOffset === 0
296
+ ) {
297
+ let rowStartBr = range.startContainer.previousSibling;
298
+ if (rowStartBr?.nodeType === 3 && dom.check.isZeroWidth(rowStartBr)) rowStartBr = rowStartBr.previousSibling;
299
+ if (dom.check.isBreak(rowStartBr)) {
300
+ actions.push(A.preventStop());
301
+ actions.push(A.backspaceBrLineRowMerge(range.startContainer, rowStartBr));
302
+ actions.push(A.historyPush(true));
303
+ return false;
304
+ }
305
+ }
306
+
307
+ // empty line: merge into the previous line
308
+ const emptyLinePrev = formatEl?.previousElementSibling;
309
+ if (
310
+ !selectRange &&
311
+ formatEl &&
312
+ format.isNormalLine(formatEl) &&
313
+ !dom.check.isListCell(formatEl) &&
314
+ dom.check.isEmptyLine(formatEl) &&
315
+ (format.isNormalLine(emptyLinePrev) || format.isBrLine(emptyLinePrev))
316
+ ) {
317
+ actions.push(A.preventStop());
318
+ actions.push(A.backspaceEmptyLineMergePrev(formatEl, emptyLinePrev));
319
+ actions.push(A.historyPush(true));
320
+ return false;
321
+ }
322
+
228
323
  return true;
229
324
  }