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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CONTRIBUTING.md +186 -184
  2. package/LICENSE +21 -21
  3. package/README.md +157 -180
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +126 -123
  7. package/src/assets/design/color.css +131 -121
  8. package/src/assets/design/index.css +3 -3
  9. package/src/assets/design/size.css +37 -35
  10. package/src/assets/design/typography.css +37 -37
  11. package/src/assets/icons/defaultIcons.js +247 -232
  12. package/src/assets/suneditor-contents.css +779 -778
  13. package/src/assets/suneditor.css +43 -35
  14. package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
  19. package/src/core/base/eventManager.js +1550 -1484
  20. package/src/core/base/history.js +355 -355
  21. package/src/core/class/char.js +163 -162
  22. package/src/core/class/component.js +856 -842
  23. package/src/core/class/format.js +3433 -3422
  24. package/src/core/class/html.js +1927 -1890
  25. package/src/core/class/menu.js +357 -346
  26. package/src/core/class/nodeTransform.js +424 -424
  27. package/src/core/class/offset.js +858 -891
  28. package/src/core/class/selection.js +710 -620
  29. package/src/core/class/shortcuts.js +98 -98
  30. package/src/core/class/toolbar.js +438 -430
  31. package/src/core/class/ui.js +424 -422
  32. package/src/core/class/viewer.js +750 -750
  33. package/src/core/editor.js +1810 -1708
  34. package/src/core/section/actives.js +268 -241
  35. package/src/core/section/constructor.js +1348 -1661
  36. package/src/core/section/context.js +102 -102
  37. package/src/core/section/documentType.js +582 -561
  38. package/src/core/section/options.js +367 -0
  39. package/src/core/util/instanceCheck.js +59 -0
  40. package/src/editorInjector/_classes.js +36 -36
  41. package/src/editorInjector/_core.js +92 -92
  42. package/src/editorInjector/index.js +75 -75
  43. package/src/events.js +634 -622
  44. package/src/helper/clipboard.js +59 -59
  45. package/src/helper/converter.js +586 -564
  46. package/src/helper/dom/domCheck.js +304 -304
  47. package/src/helper/dom/domQuery.js +677 -669
  48. package/src/helper/dom/domUtils.js +618 -557
  49. package/src/helper/dom/index.js +12 -12
  50. package/src/helper/env.js +249 -240
  51. package/src/helper/index.js +25 -25
  52. package/src/helper/keyCodeMap.js +183 -183
  53. package/src/helper/numbers.js +72 -72
  54. package/src/helper/unicode.js +47 -47
  55. package/src/langs/ckb.js +231 -231
  56. package/src/langs/cs.js +231 -231
  57. package/src/langs/da.js +231 -231
  58. package/src/langs/de.js +231 -231
  59. package/src/langs/en.js +230 -230
  60. package/src/langs/es.js +231 -231
  61. package/src/langs/fa.js +231 -231
  62. package/src/langs/fr.js +231 -231
  63. package/src/langs/he.js +231 -231
  64. package/src/langs/hu.js +230 -230
  65. package/src/langs/index.js +28 -28
  66. package/src/langs/it.js +231 -231
  67. package/src/langs/ja.js +230 -230
  68. package/src/langs/km.js +230 -230
  69. package/src/langs/ko.js +230 -230
  70. package/src/langs/lv.js +231 -231
  71. package/src/langs/nl.js +231 -231
  72. package/src/langs/pl.js +231 -231
  73. package/src/langs/pt_br.js +231 -231
  74. package/src/langs/ro.js +231 -231
  75. package/src/langs/ru.js +231 -231
  76. package/src/langs/se.js +231 -231
  77. package/src/langs/tr.js +231 -231
  78. package/src/langs/uk.js +231 -231
  79. package/src/langs/ur.js +231 -231
  80. package/src/langs/zh_cn.js +231 -231
  81. package/src/modules/ApiManager.js +191 -191
  82. package/src/modules/Browser.js +669 -667
  83. package/src/modules/ColorPicker.js +364 -362
  84. package/src/modules/Controller.js +474 -454
  85. package/src/modules/Figure.js +1620 -1617
  86. package/src/modules/FileManager.js +359 -359
  87. package/src/modules/HueSlider.js +577 -565
  88. package/src/modules/Modal.js +346 -346
  89. package/src/modules/ModalAnchorEditor.js +643 -643
  90. package/src/modules/SelectMenu.js +549 -549
  91. package/src/modules/_DragHandle.js +17 -17
  92. package/src/modules/index.js +14 -14
  93. package/src/plugins/browser/audioGallery.js +83 -83
  94. package/src/plugins/browser/fileBrowser.js +103 -103
  95. package/src/plugins/browser/fileGallery.js +83 -83
  96. package/src/plugins/browser/imageGallery.js +81 -81
  97. package/src/plugins/browser/videoGallery.js +103 -103
  98. package/src/plugins/command/blockquote.js +61 -60
  99. package/src/plugins/command/exportPDF.js +134 -134
  100. package/src/plugins/command/fileUpload.js +456 -456
  101. package/src/plugins/command/list_bulleted.js +149 -148
  102. package/src/plugins/command/list_numbered.js +152 -151
  103. package/src/plugins/dropdown/align.js +157 -155
  104. package/src/plugins/dropdown/backgroundColor.js +108 -104
  105. package/src/plugins/dropdown/font.js +141 -137
  106. package/src/plugins/dropdown/fontColor.js +109 -105
  107. package/src/plugins/dropdown/formatBlock.js +170 -178
  108. package/src/plugins/dropdown/hr.js +152 -152
  109. package/src/plugins/dropdown/layout.js +83 -83
  110. package/src/plugins/dropdown/lineHeight.js +131 -130
  111. package/src/plugins/dropdown/list.js +123 -122
  112. package/src/plugins/dropdown/paragraphStyle.js +138 -138
  113. package/src/plugins/dropdown/table.js +4110 -4000
  114. package/src/plugins/dropdown/template.js +83 -83
  115. package/src/plugins/dropdown/textStyle.js +149 -149
  116. package/src/plugins/field/mention.js +242 -242
  117. package/src/plugins/index.js +120 -120
  118. package/src/plugins/input/fontSize.js +414 -410
  119. package/src/plugins/input/pageNavigator.js +71 -70
  120. package/src/plugins/modal/audio.js +677 -677
  121. package/src/plugins/modal/drawing.js +537 -531
  122. package/src/plugins/modal/embed.js +886 -886
  123. package/src/plugins/modal/image.js +1377 -1376
  124. package/src/plugins/modal/link.js +248 -240
  125. package/src/plugins/modal/math.js +563 -563
  126. package/src/plugins/modal/video.js +1226 -1226
  127. package/src/plugins/popup/anchor.js +224 -222
  128. package/src/suneditor.js +114 -107
  129. package/src/themes/dark.css +132 -122
  130. package/src/typedef.js +132 -130
  131. package/types/assets/icons/defaultIcons.d.ts +8 -0
  132. package/types/core/base/eventManager.d.ts +29 -4
  133. package/types/core/class/char.d.ts +2 -1
  134. package/types/core/class/component.d.ts +1 -2
  135. package/types/core/class/format.d.ts +8 -1
  136. package/types/core/class/html.d.ts +8 -0
  137. package/types/core/class/menu.d.ts +8 -0
  138. package/types/core/class/offset.d.ts +24 -26
  139. package/types/core/class/selection.d.ts +2 -0
  140. package/types/core/class/toolbar.d.ts +6 -0
  141. package/types/core/class/ui.d.ts +1 -1
  142. package/types/core/editor.d.ts +34 -12
  143. package/types/core/section/constructor.d.ts +5 -638
  144. package/types/core/section/documentType.d.ts +12 -2
  145. package/types/core/section/options.d.ts +740 -0
  146. package/types/core/util/instanceCheck.d.ts +50 -0
  147. package/types/editorInjector/_core.d.ts +5 -5
  148. package/types/editorInjector/index.d.ts +2 -2
  149. package/types/events.d.ts +2 -0
  150. package/types/helper/converter.d.ts +9 -0
  151. package/types/helper/dom/domQuery.d.ts +5 -5
  152. package/types/helper/dom/domUtils.d.ts +8 -0
  153. package/types/helper/env.d.ts +6 -1
  154. package/types/helper/index.d.ts +4 -1
  155. package/types/index.d.ts +122 -120
  156. package/types/langs/_Lang.d.ts +194 -194
  157. package/types/modules/ColorPicker.d.ts +5 -1
  158. package/types/modules/Controller.d.ts +8 -4
  159. package/types/modules/Figure.d.ts +2 -1
  160. package/types/modules/HueSlider.d.ts +4 -1
  161. package/types/modules/SelectMenu.d.ts +1 -1
  162. package/types/plugins/command/blockquote.d.ts +1 -0
  163. package/types/plugins/command/list_bulleted.d.ts +1 -0
  164. package/types/plugins/command/list_numbered.d.ts +1 -0
  165. package/types/plugins/dropdown/align.d.ts +1 -0
  166. package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
  167. package/types/plugins/dropdown/font.d.ts +1 -0
  168. package/types/plugins/dropdown/fontColor.d.ts +1 -0
  169. package/types/plugins/dropdown/formatBlock.d.ts +3 -2
  170. package/types/plugins/dropdown/lineHeight.d.ts +1 -0
  171. package/types/plugins/dropdown/list.d.ts +1 -0
  172. package/types/plugins/dropdown/table.d.ts +6 -0
  173. package/types/plugins/input/fontSize.d.ts +1 -0
  174. package/types/plugins/modal/drawing.d.ts +4 -0
  175. package/types/plugins/modal/link.d.ts +32 -15
  176. package/types/suneditor.d.ts +13 -9
  177. package/types/typedef.d.ts +8 -0
@@ -1,241 +1,268 @@
1
- import { dom, env, keyCodeMap } from '../../helper';
2
- const { NO_EVENT } = env;
3
-
4
- /**
5
- * @constant {Object.<string, string[]>} StyleMap - Map of font styles to CSS properties.
6
- */
7
- const StyleMap = {
8
- bold: ['font-weight'],
9
- underline: ['text-decoration'],
10
- italic: ['font-style'],
11
- strike: ['text-decoration']
12
- };
13
-
14
- let __globalEventKeydown = null;
15
- let __globalEventMousedown = null;
16
-
17
- /**
18
- * @private
19
- * @this {__se__EditorCore}
20
- * @param {Node} ww Wywsiwyg element
21
- * @param {Node} button Button element
22
- */
23
- const __RemoveCopyformt = function (ww, button) {
24
- __globalEventKeydown = this.eventManager.removeGlobalEvent('keydown', __globalEventKeydown);
25
- __globalEventMousedown = this.eventManager.removeGlobalEvent('mousedown', __globalEventMousedown);
26
- this._onCopyFormatInfo = null;
27
- this._onCopyFormatInitMethod = null;
28
- dom.utils.removeClass(ww, 'se-copy-format-cursor');
29
- dom.utils.removeClass(button, 'on');
30
-
31
- return true;
32
- };
33
-
34
- /**
35
- * @description List of commands that trigger active event handling in the editor.
36
- * - These commands typically apply inline formatting or structural changes.
37
- * @constant {string[]}
38
- */
39
- export const ACTIVE_EVENT_COMMANDS = ['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript', 'indent', 'outdent'];
40
-
41
- /**
42
- * @description List of basic editor commands, including active event commands and additional actions
43
- * - such as undo, redo, saving, full-screen toggle, and text direction commands.
44
- * @constant {string[]}
45
- */
46
- export const BASIC_COMMANDS = ACTIVE_EVENT_COMMANDS.concat(['undo', 'redo', 'save', 'fullScreen', 'showBlocks', 'codeView', 'dir', 'dir_ltr', 'dir_rtl']);
47
-
48
- /**
49
- * @description Selects all content in the editor.
50
- * @param {__se__EditorCore} editor - The root editor instance
51
- */
52
- export function SELECT_ALL(editor) {
53
- editor.ui._offCurrentController();
54
- editor.menu.containerOff();
55
-
56
- // check all tags
57
- const ww = editor.frameContext.get('wysiwyg');
58
- let prevScopeTag = null;
59
- let prevScopeTagName = '';
60
- const scopeSelectionTags = editor.options.get('scopeSelectionTags');
61
- const range = editor.selection.getRange();
62
- if (!range.collapsed) {
63
- let commonNode = range.commonAncestorContainer;
64
- let commonNodeName = commonNode.nodeName?.toLowerCase();
65
-
66
- while (commonNode && ((!commonNode.nextSibling && !commonNode.previousSibling && !scopeSelectionTags.includes(commonNodeName)) || dom.check.isContentLess(commonNodeName)) && commonNode !== ww) {
67
- commonNode = commonNode.parentElement;
68
- commonNodeName = commonNode.nodeName?.toLowerCase();
69
- }
70
-
71
- if (scopeSelectionTags.includes(commonNodeName)) {
72
- prevScopeTag = commonNode;
73
- prevScopeTagName = commonNodeName;
74
- }
75
- }
76
-
77
- // select all
78
- const scopeTagList = scopeSelectionTags.filter((tagName) => tagName !== prevScopeTagName);
79
- const scopeBaseTag = dom.query.getParentElement(prevScopeTag || editor.selection.getNode(), (current) => scopeTagList.includes(current.nodeName?.toLowerCase()));
80
- const selectArea = scopeBaseTag || ww;
81
-
82
- let first =
83
- dom.query.getEdgeChild(
84
- dom.query.getEdgeChild(selectArea, (current) => !dom.check.isContentLess(current), false),
85
- (current) => {
86
- return current.childNodes.length === 0 || current.nodeType === 3 || dom.check.isTable(current);
87
- },
88
- false
89
- ) || selectArea.firstChild;
90
- let last =
91
- dom.query.getEdgeChild(
92
- selectArea.lastChild,
93
- (current) => {
94
- return current.childNodes.length === 0 || current.nodeType === 3 || dom.check.isTable(current);
95
- },
96
- true
97
- ) || selectArea.lastChild;
98
-
99
- if (!first || !last) return;
100
-
101
- if (dom.check.isMedia(first) || editor.component.is(first.parentElement) || dom.check.isTableElements(first)) {
102
- const info = editor.component.get(first) || editor.component.get(first.parentElement);
103
- const br = dom.utils.createElement('BR');
104
- const format = dom.utils.createElement(editor.options.get('defaultLine'), null, br);
105
- first = info ? info.container || info.cover : first;
106
- first.parentNode.insertBefore(format, first);
107
- first = br;
108
- }
109
-
110
- if (dom.check.isMedia(last) || editor.component.is(last.parentElement) || dom.check.isTableElements(last)) {
111
- last = dom.utils.createElement('BR');
112
- selectArea.appendChild(dom.utils.createElement(editor.options.get('defaultLine'), null, last));
113
- }
114
-
115
- editor.toolbar._showBalloon(editor.selection.setRange(first, 0, last, last.textContent.length));
116
- }
117
-
118
- /**
119
- * @description Toggles direction button active state.
120
- * @param {__se__EditorCore} editor - The root editor instance
121
- * @param {boolean} rtl - Whether the text direction is right-to-left.
122
- */
123
- export function DIR_BTN_ACTIVE(editor, rtl) {
124
- const icons = editor.icons;
125
- const commandTargets = editor.commandTargets;
126
- const shortcutsKeyMap = editor.shortcutsKeyMap;
127
-
128
- // change reverse shortcuts key
129
- editor.reverseKeys.forEach((e) => {
130
- const info = shortcutsKeyMap.get(e);
131
- if (!info) return;
132
- const temp = info.c;
133
- info.c = info.r;
134
- info.r = temp;
135
- });
136
-
137
- // change dir buttons
138
- editor.applyCommandTargets('dir', (e) => {
139
- dom.utils.changeTxt(e.querySelector('.se-tooltip-text'), editor.lang[rtl ? 'dir_ltr' : 'dir_rtl']);
140
- dom.utils.changeElement(e.firstElementChild, icons[rtl ? 'dir_ltr' : 'dir_rtl']);
141
- });
142
-
143
- if (rtl) {
144
- dom.utils.addClass(commandTargets.get('dir_rtl'), 'active');
145
- dom.utils.removeClass(commandTargets.get('dir_ltr'), 'active');
146
- } else {
147
- dom.utils.addClass(commandTargets.get('dir_ltr'), 'active');
148
- dom.utils.removeClass(commandTargets.get('dir_rtl'), 'active');
149
- }
150
- }
151
-
152
- /**
153
- * @description Saves the editor content.
154
- * @param {__se__EditorCore} editor - The root editor instance
155
- * @returns {Promise<void>}
156
- */
157
- export async function SAVE(editor) {
158
- const fc = editor.frameContext;
159
- if (!fc.get('isChanged')) return;
160
-
161
- const data = editor.html.get();
162
- const saved = await editor.triggerEvent('onSave', { frameContext: fc, data });
163
- if (saved === NO_EVENT) {
164
- const origin = fc.get('originElement');
165
- if (/^TEXTAREA$/i.test(origin.nodeName)) {
166
- origin.value = data;
167
- } else {
168
- origin.innerHTML = data;
169
- }
170
- } else if (saved === false) {
171
- return;
172
- }
173
-
174
- fc.set('isChanged', false);
175
- fc.set('savedIndex', editor.history.getRootStack()[editor.status.rootKey].index);
176
-
177
- // set save button disable
178
- editor.applyCommandTargets('save', (e) => {
179
- e.disabled = true;
180
- });
181
- }
182
-
183
- /**
184
- * @description Copies formatting from selected text.
185
- * @param {__se__EditorCore} editor - The root editor instance
186
- * @param {Node} button - The button triggering the copy format function.
187
- */
188
- export function COPY_FORMAT(editor, button) {
189
- if (typeof editor._onCopyFormatInitMethod === 'function') {
190
- editor._onCopyFormatInitMethod();
191
- return;
192
- }
193
-
194
- const ww = editor.frameContext.get('wysiwyg');
195
- editor._onCopyFormatInfo = [...editor.eventManager.__cacheStyleNodes];
196
- editor._onCopyFormatInitMethod = __RemoveCopyformt.bind(editor, ww, button);
197
- dom.utils.addClass(ww, 'se-copy-format-cursor');
198
- dom.utils.addClass(button, 'on');
199
-
200
- __globalEventKeydown = editor.eventManager.addGlobalEvent('keydown', (e) => {
201
- if (!keyCodeMap.isEsc(e.code)) return;
202
- editor._onCopyFormatInitMethod?.();
203
- });
204
- __globalEventMousedown = editor.eventManager.addGlobalEvent('mousedown', (e) => {
205
- if (ww.contains(e.target) || e.target === button) return;
206
- editor._onCopyFormatInitMethod?.();
207
- });
208
- }
209
-
210
- /**
211
- * @description Applies font styling to selected text.
212
- * @param {__se__EditorCore} editor - The root editor instance
213
- * @param {string} command - The font style command (e.g., bold, italic, underline).
214
- */
215
- export function FONT_STYLE(editor, command) {
216
- command = editor.options.get('_defaultTagCommand')[command.toLowerCase()] || command;
217
- let nodeName = editor.options.get('convertTextTags')[command] || command;
218
- const nodesMap = editor.status.currentNodesMap;
219
- const el = nodesMap.includes(editor.options.get('_styleCommandMap')[nodeName]) ? null : dom.utils.createElement(nodeName);
220
-
221
- if (/^sub$/i.test(nodeName) && nodesMap.includes('superscript')) {
222
- nodeName = 'sup';
223
- } else if (/^sup$/i.test(nodeName) && nodesMap.includes('subscript')) {
224
- nodeName = 'sub';
225
- }
226
-
227
- editor.format.applyInlineElement(el, { stylesToModify: StyleMap[command] || null, nodesToRemove: [nodeName], strictRemove: false });
228
- editor.focus();
229
- }
230
-
231
- /**
232
- * @description Inserts a page break element into the editor.
233
- * @param {__se__EditorCore} editor - The root editor instance
234
- */
235
- export function PAGE_BREAK(editor) {
236
- const pageBreak = dom.utils.createElement('DIV', { class: 'se-component se-component-line-break se-page-break' });
237
- editor.component.insert(pageBreak, { skipCharCount: true, skipSelection: true, skipHistory: false });
238
- const line = pageBreak.nextElementSibling || editor.format.addLine(pageBreak);
239
- editor.selection.setRange(line, 1, line, 1);
240
- editor.history.push(false);
241
- }
1
+ import { dom, env, keyCodeMap } from '../../helper';
2
+ const { NO_EVENT } = env;
3
+
4
+ /**
5
+ * @constant {Object.<string, string[]>} StyleMap - Map of font styles to CSS properties.
6
+ */
7
+ const StyleMap = {
8
+ bold: ['font-weight'],
9
+ underline: ['text-decoration'],
10
+ italic: ['font-style'],
11
+ strike: ['text-decoration']
12
+ };
13
+
14
+ let __globalEventKeydown = null;
15
+ let __globalEventMousedown = null;
16
+
17
+ /**
18
+ * @private
19
+ * @this {__se__EditorCore}
20
+ * @param {Node} ww Wywsiwyg element
21
+ * @param {Node} button Button element
22
+ */
23
+ const __RemoveCopyformt = function (ww, button) {
24
+ __globalEventKeydown = this.eventManager.removeGlobalEvent('keydown', __globalEventKeydown);
25
+ __globalEventMousedown = this.eventManager.removeGlobalEvent('mousedown', __globalEventMousedown);
26
+ this._onCopyFormatInfo = null;
27
+ this._onCopyFormatInitMethod = null;
28
+ dom.utils.removeClass(ww, 'se-copy-format-cursor');
29
+ dom.utils.removeClass(button, 'on');
30
+
31
+ return true;
32
+ };
33
+
34
+ /**
35
+ * @private
36
+ * @description Finds the first and last child elements in a selection area.
37
+ * @param {Element} selectArea Selection area element
38
+ * @returns {{ first: Node, last: Node}} Object containing the first and last child elements
39
+ */
40
+ const __findFirstAndLast = function (selectArea) {
41
+ const isContentLess = dom.check.isContentLess;
42
+ const isTable = dom.check.isTable;
43
+ const first =
44
+ dom.query.getEdgeChild(
45
+ dom.query.getEdgeChild(selectArea, (current) => !isContentLess(current), false),
46
+ (current) => {
47
+ return current.childNodes.length === 0 || current.nodeType === 3 || isTable(current);
48
+ },
49
+ false
50
+ ) || selectArea.firstChild;
51
+ const last =
52
+ dom.query.getEdgeChild(
53
+ selectArea.lastChild,
54
+ (current) => {
55
+ return current.childNodes.length === 0 || current.nodeType === 3 || isTable(current);
56
+ },
57
+ true
58
+ ) || selectArea.lastChild;
59
+
60
+ return { first, last };
61
+ };
62
+
63
+ /**
64
+ * @description List of commands that trigger active event handling in the editor.
65
+ * - These commands typically apply inline formatting or structural changes.
66
+ * @constant {string[]}
67
+ */
68
+ export const ACTIVE_EVENT_COMMANDS = ['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript', 'indent', 'outdent'];
69
+
70
+ /**
71
+ * @description List of basic editor commands, including active event commands and additional actions
72
+ * - such as undo, redo, saving, full-screen toggle, and text direction commands.
73
+ * @constant {string[]}
74
+ */
75
+ export const BASIC_COMMANDS = ACTIVE_EVENT_COMMANDS.concat(['undo', 'redo', 'save', 'fullScreen', 'showBlocks', 'codeView', 'dir', 'dir_ltr', 'dir_rtl']);
76
+
77
+ /**
78
+ * @description Selects all content in the editor.
79
+ * @param {__se__EditorCore} editor - The root editor instance
80
+ */
81
+ export function SELECT_ALL(editor) {
82
+ editor.ui._offCurrentController();
83
+ editor.menu.containerOff();
84
+
85
+ // check all tags
86
+ const ww = editor.frameContext.get('wysiwyg');
87
+ let prevScopeTag = null;
88
+ let prevScopeTagName = '';
89
+ const scopeSelectionTags = editor.options.get('scopeSelectionTags');
90
+ const range = editor.selection.getRange();
91
+ if (!range.collapsed) {
92
+ let commonNode = range.commonAncestorContainer;
93
+ let commonNodeName = commonNode.nodeName?.toLowerCase();
94
+
95
+ while (commonNode && ((!commonNode.nextSibling && !commonNode.previousSibling && !scopeSelectionTags.includes(commonNodeName)) || dom.check.isContentLess(commonNodeName)) && commonNode !== ww) {
96
+ commonNode = commonNode.parentElement;
97
+ commonNodeName = commonNode.nodeName?.toLowerCase();
98
+ }
99
+
100
+ if (scopeSelectionTags.includes(commonNodeName)) {
101
+ prevScopeTag = commonNode;
102
+ prevScopeTagName = commonNodeName;
103
+ }
104
+ }
105
+
106
+ // select all
107
+ const scopeTagList = scopeSelectionTags.filter((tagName) => tagName !== prevScopeTagName);
108
+ const scopeBaseTag = dom.query.getParentElement(prevScopeTag || editor.selection.getNode(), (current) => scopeTagList.includes(current.nodeName?.toLowerCase()));
109
+
110
+ let selectArea = scopeBaseTag || ww;
111
+ let { first, last } = __findFirstAndLast(selectArea);
112
+
113
+ if (!first || !last) return;
114
+
115
+ const isZeroWidth = dom.check.isZeroWidth;
116
+ while (isZeroWidth(first) && isZeroWidth(last) && selectArea !== ww) {
117
+ selectArea = selectArea.parentElement;
118
+ ({ first, last } = __findFirstAndLast(dom.query.getParentElement(selectArea, (current) => scopeTagList.includes(current.nodeName?.toLowerCase())) || ww));
119
+ }
120
+
121
+ if (!first || !last) return;
122
+
123
+ let info = null;
124
+ if (dom.check.isMedia(first) || (info = editor.component.get(first.parentElement)) || dom.check.isTableElements(first)) {
125
+ if (!info) info = editor.component.get(first);
126
+ const br = dom.utils.createElement('BR');
127
+ const format = dom.utils.createElement(editor.options.get('defaultLine'), null, br);
128
+ first = info ? info.container || info.cover : first;
129
+ first.parentElement.insertBefore(format, first);
130
+ first = br;
131
+ }
132
+
133
+ if (dom.check.isMedia(last) || (info = editor.component.get(last.parentElement)) || dom.check.isTableElements(last)) {
134
+ if (!info) info = editor.component.get(first);
135
+ const br = dom.utils.createElement('BR');
136
+ const format = dom.utils.createElement(editor.options.get('defaultLine'), null, br);
137
+ last = info ? info.container || info.cover : last;
138
+ last.parentElement.appendChild(format);
139
+ last = br;
140
+ }
141
+
142
+ editor.toolbar._showBalloon(editor.selection.setRange(first, 0, last, last.textContent.length));
143
+ }
144
+
145
+ /**
146
+ * @description Toggles direction button active state.
147
+ * @param {__se__EditorCore} editor - The root editor instance
148
+ * @param {boolean} rtl - Whether the text direction is right-to-left.
149
+ */
150
+ export function DIR_BTN_ACTIVE(editor, rtl) {
151
+ const icons = editor.icons;
152
+ const commandTargets = editor.commandTargets;
153
+ const shortcutsKeyMap = editor.shortcutsKeyMap;
154
+
155
+ // change reverse shortcuts key
156
+ editor.reverseKeys.forEach((e) => {
157
+ const info = shortcutsKeyMap.get(e);
158
+ if (!info) return;
159
+ const temp = info.c;
160
+ info.c = info.r;
161
+ info.r = temp;
162
+ });
163
+
164
+ // change dir buttons
165
+ editor.applyCommandTargets('dir', (e) => {
166
+ dom.utils.changeTxt(e.querySelector('.se-tooltip-text'), editor.lang[rtl ? 'dir_ltr' : 'dir_rtl']);
167
+ dom.utils.changeElement(e.firstElementChild, icons[rtl ? 'dir_ltr' : 'dir_rtl']);
168
+ });
169
+
170
+ if (rtl) {
171
+ dom.utils.addClass(commandTargets.get('dir_rtl'), 'active');
172
+ dom.utils.removeClass(commandTargets.get('dir_ltr'), 'active');
173
+ } else {
174
+ dom.utils.addClass(commandTargets.get('dir_ltr'), 'active');
175
+ dom.utils.removeClass(commandTargets.get('dir_rtl'), 'active');
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @description Saves the editor content.
181
+ * @param {__se__EditorCore} editor - The root editor instance
182
+ * @returns {Promise<void>}
183
+ */
184
+ export async function SAVE(editor) {
185
+ const fc = editor.frameContext;
186
+ if (!fc.get('isChanged')) return;
187
+
188
+ const data = editor.html.get();
189
+ const saved = await editor.triggerEvent('onSave', { frameContext: fc, data });
190
+ if (saved === NO_EVENT) {
191
+ const origin = fc.get('originElement');
192
+ if (/^TEXTAREA$/i.test(origin.nodeName)) {
193
+ origin.value = data;
194
+ } else {
195
+ origin.innerHTML = data;
196
+ }
197
+ } else if (saved === false) {
198
+ return;
199
+ }
200
+
201
+ fc.set('isChanged', false);
202
+ fc.set('savedIndex', editor.history.getRootStack()[editor.status.rootKey].index);
203
+
204
+ // set save button disable
205
+ editor.applyCommandTargets('save', (e) => {
206
+ e.disabled = true;
207
+ });
208
+ }
209
+
210
+ /**
211
+ * @description Copies formatting from selected text.
212
+ * @param {__se__EditorCore} editor - The root editor instance
213
+ * @param {Node} button - The button triggering the copy format function.
214
+ */
215
+ export function COPY_FORMAT(editor, button) {
216
+ if (typeof editor._onCopyFormatInitMethod === 'function') {
217
+ editor._onCopyFormatInitMethod();
218
+ return;
219
+ }
220
+
221
+ const ww = editor.frameContext.get('wysiwyg');
222
+ editor._onCopyFormatInfo = [...editor.eventManager.__cacheStyleNodes];
223
+ editor._onCopyFormatInitMethod = __RemoveCopyformt.bind(editor, ww, button);
224
+ dom.utils.addClass(ww, 'se-copy-format-cursor');
225
+ dom.utils.addClass(button, 'on');
226
+
227
+ __globalEventKeydown = editor.eventManager.addGlobalEvent('keydown', (e) => {
228
+ if (!keyCodeMap.isEsc(e.code)) return;
229
+ editor._onCopyFormatInitMethod?.();
230
+ });
231
+ __globalEventMousedown = editor.eventManager.addGlobalEvent('mousedown', (e) => {
232
+ if (ww.contains(e.target) || e.target === button) return;
233
+ editor._onCopyFormatInitMethod?.();
234
+ });
235
+ }
236
+
237
+ /**
238
+ * @description Applies font styling to selected text.
239
+ * @param {__se__EditorCore} editor - The root editor instance
240
+ * @param {string} command - The font style command (e.g., bold, italic, underline).
241
+ */
242
+ export function FONT_STYLE(editor, command) {
243
+ command = editor.options.get('_defaultTagCommand')[command.toLowerCase()] || command;
244
+ let nodeName = editor.options.get('convertTextTags')[command] || command;
245
+ const nodesMap = editor.status.currentNodesMap;
246
+ const el = nodesMap.includes(editor.options.get('_styleCommandMap')[nodeName]) ? null : dom.utils.createElement(nodeName);
247
+
248
+ if (/^sub$/i.test(nodeName) && nodesMap.includes('superscript')) {
249
+ nodeName = 'sup';
250
+ } else if (/^sup$/i.test(nodeName) && nodesMap.includes('subscript')) {
251
+ nodeName = 'sub';
252
+ }
253
+
254
+ editor.format.applyInlineElement(el, { stylesToModify: StyleMap[command] || null, nodesToRemove: [nodeName], strictRemove: false });
255
+ editor.focus();
256
+ }
257
+
258
+ /**
259
+ * @description Inserts a page break element into the editor.
260
+ * @param {__se__EditorCore} editor - The root editor instance
261
+ */
262
+ export function PAGE_BREAK(editor) {
263
+ const pageBreak = dom.utils.createElement('DIV', { class: 'se-component se-component-line-break se-page-break' });
264
+ editor.component.insert(pageBreak, { skipCharCount: true, skipSelection: true, skipHistory: false });
265
+ const line = pageBreak.nextElementSibling || editor.format.addLine(pageBreak);
266
+ editor.selection.setRange(line, 1, line, 1);
267
+ editor.history.push(false);
268
+ }