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,430 +1,438 @@
1
- /**
2
- * @fileoverview Toolbar class
3
- */
4
-
5
- import { dom, env } from '../../helper';
6
- import CoreInjector from '../../editorInjector/_core';
7
- import { CreateToolBar, UpdateButton } from '../section/constructor';
8
-
9
- const { _w } = env;
10
-
11
- /**
12
- * @typedef {Omit<Toolbar & Partial<__se__EditorInjector>, 'toolbar' | 'subToolbar'>} ToolbarThis
13
- */
14
-
15
- /**
16
- * @constructor
17
- * @this {ToolbarThis}
18
- * @description Toolbar class
19
- * @param {__se__EditorCore} editor - The root editor instance
20
- * @param {Object} options - toolbar options
21
- * @param {String} options.keyName - toolbar key name
22
- * @param {Boolean} options.balloon - balloon toolbar
23
- * @param {Boolean} options.inline - inline toolbar
24
- * @param {Boolean} options.balloonAlways - balloon toolbar always show
25
- * @param {Array<Node>} options.res - responsive toolbar button list
26
- */
27
- function Toolbar(editor, { keyName, balloon, inline, balloonAlways, res }) {
28
- CoreInjector.call(this, editor);
29
-
30
- // members
31
- this.keyName = keyName;
32
- this.isSub = /sub/.test(keyName);
33
- this.currentMoreLayerActiveButton = null;
34
- this._isBalloon = balloon;
35
- this._isInline = inline;
36
- this._isBalloonAlways = balloonAlways;
37
- this._responsiveCurrentSize = 'default';
38
- this._originRes = res;
39
- this._rButtonArray = res;
40
- this._rButtonsInfo = null;
41
- this._rButtonsize = null;
42
- this._sticky = false;
43
- this._isViewPortSize = 'visualViewport' in _w;
44
- this._inlineToolbarAttr = {
45
- top: '',
46
- width: '',
47
- isShow: false
48
- };
49
- this._balloonOffset = {
50
- top: 0,
51
- left: 0
52
- };
53
-
54
- this._setResponsive();
55
- }
56
-
57
- Toolbar.prototype = {
58
- /**
59
- * @this {ToolbarThis}
60
- * @description Disable the toolbar
61
- */
62
- disable() {
63
- /** off menus */
64
- this._moreLayerOff();
65
- this.menu.dropdownOff();
66
- this.menu.containerOff();
67
- dom.utils.setDisabled(this.context.get(this.keyName + '.buttonTray').querySelectorAll('.se-menu-list .se-toolbar-btn[data-type]'), true);
68
- },
69
-
70
- /**
71
- * @this {ToolbarThis}
72
- * @description Enable the toolbar
73
- */
74
- enable() {
75
- dom.utils.setDisabled(this.context.get(this.keyName + '.buttonTray').querySelectorAll('.se-menu-list .se-toolbar-btn[data-type]'), false);
76
- },
77
-
78
- /**
79
- * @this {ToolbarThis}
80
- * @description Show the toolbar
81
- */
82
- show() {
83
- if (this._isInline) {
84
- this._showInline();
85
- } else if (this._isBalloon) {
86
- this._showBalloon();
87
- } else {
88
- this.context.get(this.keyName + '.main').style.display = '';
89
- if (!this.isSub) this.editor.frameContext.get('_stickyDummy').style.display = '';
90
- }
91
-
92
- if (!this.isSub) this.resetResponsiveToolbar();
93
- },
94
-
95
- /**
96
- * @this {ToolbarThis}
97
- * @description Hide the toolbar
98
- */
99
- hide() {
100
- if (this._isInline) {
101
- this.context.get(this.keyName + '.main').style.display = 'none';
102
- this.context.get(this.keyName + '.main').style.top = '0px';
103
- this._inlineToolbarAttr.isShow = false;
104
- } else {
105
- this.context.get(this.keyName + '.main').style.display = 'none';
106
- if (!this.isSub) this.editor.frameContext.get('_stickyDummy').style.display = 'none';
107
- if (this._isBalloon) {
108
- this._balloonOffset = {
109
- top: 0,
110
- left: 0
111
- };
112
- }
113
- }
114
- },
115
-
116
- /**
117
- * @this {ToolbarThis}
118
- * @description Reset buttons of the responsive toolbar.
119
- */
120
- resetResponsiveToolbar() {
121
- this.menu.containerOff();
122
-
123
- const responsiveSize = this._rButtonsize;
124
- if (responsiveSize) {
125
- let w = 0;
126
- if (((this._isBalloon || this._isInline) && this.options.get('toolbar_width') === 'auto') || (this.editor.isSubBalloon && this.options.get('toolbar.sub_width') === 'auto')) {
127
- w = this.editor.frameContext.get('topArea').offsetWidth;
128
- } else {
129
- w = this.context.get(this.keyName + '.main').offsetWidth;
130
- }
131
-
132
- let responsiveWidth = 'default';
133
- for (let i = 1, len = responsiveSize.length; i < len; i++) {
134
- if (w < responsiveSize[i]) {
135
- responsiveWidth = responsiveSize[i] + '';
136
- break;
137
- }
138
- }
139
-
140
- if (this._responsiveCurrentSize !== responsiveWidth) {
141
- this._responsiveCurrentSize = responsiveWidth;
142
- this.setButtons(this._rButtonsInfo[responsiveWidth]);
143
- // this.viewer._resetFullScreenHeight();
144
- }
145
- }
146
- },
147
-
148
- /**
149
- * @this {ToolbarThis}
150
- * @description Reset the buttons on the toolbar. (Editor is not reloaded)
151
- * - You cannot set a new plugin for the button.
152
- * @param {Array} buttonList Button list
153
- */
154
- setButtons(buttonList) {
155
- this._moreLayerOff();
156
- this.menu.dropdownOff();
157
- this.menu.containerOff();
158
-
159
- const { options, icons, lang, isSub } = this;
160
- const newToolbar = CreateToolBar(buttonList, this.plugins, options, icons, lang, true);
161
-
162
- newToolbar.updateButtons.forEach((v) => UpdateButton(v.button, v.plugin, this.icons, this.lang));
163
-
164
- let cmdButtons;
165
- if (isSub) cmdButtons = this.editor.subAllCommandButtons = new Map();
166
- else cmdButtons = this.editor.allCommandButtons = new Map();
167
- this.editor.commandTargets = new Map();
168
- this.editor.shortcutsKeyMap = new Map();
169
- this.editor.__saveCommandButtons(cmdButtons, newToolbar.buttonTray);
170
-
171
- this.context.get(this.keyName + '.main').replaceChild(newToolbar.buttonTray, this.context.get(this.keyName + '.buttonTray'));
172
- this.context.set(this.keyName + '.buttonTray', newToolbar.buttonTray);
173
-
174
- this.editor.__setDisabledButtons();
175
-
176
- this.history.resetButtons(this.editor.frameContext.get('key'), null);
177
- this._resetSticky();
178
-
179
- this.editor.effectNode = null;
180
- this.viewer._setButtonsActive();
181
- if (this.status.hasFocus) this.eventManager.applyTagEffect();
182
- if (this.editor.frameContext.get('isReadOnly')) this.ui.setControllerOnDisabledButtons(true);
183
-
184
- this.triggerEvent('onSetToolbarButtons', { buttonTray: newToolbar.buttonTray, frameContext: this.editor.frameContext });
185
- },
186
-
187
- /**
188
- * @private
189
- * @this {ToolbarThis}
190
- * @description Reset the sticky toolbar position based on the editor state.
191
- */
192
- _resetSticky() {
193
- const wrapper = this.editor.frameContext.get('wrapper');
194
- if (!wrapper) return;
195
-
196
- const toolbar = this.context.get(this.keyName + '.main');
197
- if (this.editor.frameContext.get('isFullScreen') || toolbar.offsetWidth === 0 || this.options.get('toolbar_sticky') < 0) return;
198
-
199
- const currentScrollY = this._isViewPortSize ? _w.visualViewport.pageTop : _w.scrollY;
200
-
201
- const minHeight = this.editor.frameContext.get('_minHeight');
202
- const editorHeight = wrapper.offsetHeight;
203
- const editorOffset = this.offset.getGlobal(this.editor.frameContext.get('topArea'));
204
- const y = currentScrollY + this.options.get('toolbar_sticky');
205
- const t = (this._isBalloon || this._isInline ? editorOffset.top : this.offset.getGlobal(this.options.get('toolbar_container')).top) - (this._isInline ? toolbar.offsetHeight : 0);
206
- const inlineOffset = 1;
207
-
208
- const offSticky = !this.options.get('toolbar_container')
209
- ? editorHeight + t + this.options.get('toolbar_sticky') - y - minHeight
210
- : editorOffset.top - currentScrollY + editorHeight - minHeight - this.options.get('toolbar_sticky') - toolbar.offsetHeight;
211
- if (y < t) {
212
- this._offSticky();
213
- } else if (offSticky < 0) {
214
- if (!this._sticky) this._onSticky(inlineOffset);
215
- toolbar.style.top = inlineOffset + offSticky + this.__getViewportTop() + 'px';
216
- } else {
217
- this._onSticky(inlineOffset);
218
- }
219
- },
220
-
221
- /**
222
- * @private
223
- * @this {ToolbarThis}
224
- * @description Enable sticky toolbar mode and adjust position.
225
- */
226
- _onSticky(inlineOffset) {
227
- const toolbar = this.context.get(this.keyName + '.main');
228
-
229
- if (!this._isInline) {
230
- const stickyDummy = !this.options.get('toolbar_container') ? this.editor.frameContext.get('_stickyDummy') : this.context.get('_stickyDummy');
231
- stickyDummy.style.height = toolbar.offsetHeight + 'px';
232
- stickyDummy.style.display = 'block';
233
- }
234
-
235
- const toolbarTopPosition = this.options.get('toolbar_sticky') + inlineOffset + this.__getViewportTop();
236
- toolbar.style.top = `${toolbarTopPosition}px`;
237
- toolbar.style.width = this._isInline ? this._inlineToolbarAttr.width : toolbar.offsetWidth + 'px';
238
- dom.utils.addClass(toolbar, 'se-toolbar-sticky');
239
- this._sticky = true;
240
- },
241
-
242
- /**
243
- * @private
244
- * @this {ToolbarThis}
245
- * @description Get the viewport's top offset.
246
- * @returns {number}
247
- */
248
- __getViewportTop() {
249
- if (this._isViewPortSize) {
250
- return _w.visualViewport.offsetTop;
251
- }
252
- return 0;
253
- },
254
-
255
- /**
256
- * @private
257
- * @this {ToolbarThis}
258
- * @description Disable sticky toolbar mode.
259
- */
260
- _offSticky() {
261
- const stickyDummy = !this.options.get('toolbar_container') ? this.editor.frameContext.get('_stickyDummy') : this.context.get('_stickyDummy');
262
- stickyDummy.style.display = 'none';
263
-
264
- const toolbar = this.context.get(this.keyName + '.main');
265
- toolbar.style.top = this._isInline ? this._inlineToolbarAttr.top : '';
266
- toolbar.style.width = this._isInline ? this._inlineToolbarAttr.width : '';
267
- this.editor.frameContext.get('wrapper').style.marginTop = '';
268
-
269
- dom.utils.removeClass(toolbar, 'se-toolbar-sticky');
270
- this._sticky = false;
271
- },
272
-
273
- /**
274
- * @private
275
- * @this {ToolbarThis}
276
- * @description Set up responsive behavior for the toolbar buttons.
277
- */
278
- _setResponsive() {
279
- if (this._rButtonArray?.length === 0) {
280
- this._rButtonArray = null;
281
- return;
282
- }
283
-
284
- this._responsiveCurrentSize = 'default';
285
- const _rButtonsize = (this._rButtonsize = []);
286
- const _responsiveButtons = this._originRes;
287
- const buttonsObj = (this._rButtonsInfo = {
288
- default: _responsiveButtons[0]
289
- });
290
-
291
- for (let i = 1, len = _responsiveButtons.length, size, buttonGroup; i < len; i++) {
292
- buttonGroup = _responsiveButtons[i];
293
- size = buttonGroup[0] * 1;
294
- _rButtonsize.push(size);
295
- buttonsObj[size] = buttonGroup[1];
296
- }
297
-
298
- _rButtonsize.sort((a, b) => a - b).unshift('default');
299
- },
300
-
301
- /**
302
- * @private
303
- * @this {ToolbarThis}
304
- * @description Show the balloon toolbar based on the current selection.
305
- * @param {?Range=} rangeObj - Selection range
306
- */
307
- _showBalloon(rangeObj) {
308
- if (!this._isBalloon || this.editor.opendControllers.length > 0) {
309
- return;
310
- }
311
- if (this.isSub) this.resetResponsiveToolbar();
312
-
313
- const range = rangeObj || this.selection.getRange();
314
- const toolbar = this.context.get(this.keyName + '.main');
315
- const selection = this.selection.get();
316
-
317
- let isDirTop;
318
- if (this._isBalloonAlways && range.collapsed) {
319
- isDirTop = true;
320
- } else if (selection.focusNode === selection.anchorNode) {
321
- isDirTop = selection.focusOffset < selection.anchorOffset;
322
- } else {
323
- const childNodes = dom.query.getListChildNodes(range.commonAncestorContainer, null);
324
- isDirTop = dom.utils.getArrayIndex(childNodes, selection.focusNode) < dom.utils.getArrayIndex(childNodes, selection.anchorNode);
325
- }
326
-
327
- this._setBalloonOffset(isDirTop, range);
328
-
329
- this.triggerEvent('onShowToolbar', { toolbar, mode: 'balloon', frameContext: this.editor.frameContext });
330
- },
331
-
332
- /**
333
- * @private
334
- * @this {ToolbarThis}
335
- * @description Adjust the balloon toolbar's position.
336
- * @param {boolean} positionTop - Whether the toolbar should be positioned above the selection
337
- * @param {Range} [range] - Selection range
338
- */
339
- _setBalloonOffset(positionTop, range) {
340
- const toolbar = this.context.get(this.keyName + '.main');
341
- const topArea = this.editor.frameContext.get('topArea');
342
- const offsets = this.offset.getGlobal(topArea);
343
- const stickyTop = offsets.top;
344
-
345
- if (!this.offset.setRangePosition(toolbar, range, { position: positionTop ? 'top' : 'bottom', addTop: stickyTop })) {
346
- this.hide();
347
- return;
348
- }
349
-
350
- if (this.options.get('toolbar_container')) {
351
- const editorParent = topArea.parentElement;
352
-
353
- let container = this.options.get('toolbar_container');
354
- let left = container.offsetLeft;
355
- let top = container.offsetTop;
356
-
357
- while (!container.parentElement.contains(editorParent) || !/^(BODY|HTML)$/i.test(container.parentElement.nodeName)) {
358
- container = container.offsetParent;
359
- left += container.offsetLeft;
360
- top += container.offsetTop;
361
- }
362
-
363
- toolbar.style.left = toolbar.offsetLeft - left + topArea.offsetLeft + 'px';
364
- toolbar.style.top = toolbar.offsetTop - top + topArea.offsetTop + 'px';
365
- }
366
-
367
- const wwScroll = this.offset.getWWScroll();
368
- this._balloonOffset = {
369
- top: toolbar.offsetTop + wwScroll.top,
370
- left: toolbar.offsetLeft + wwScroll.left,
371
- position: positionTop ? 'top' : 'bottom'
372
- };
373
- },
374
-
375
- /**
376
- * @private
377
- * @this {ToolbarThis}
378
- * @description Show the inline toolbar mode.
379
- */
380
- _showInline() {
381
- if (!this._isInline) return;
382
-
383
- const toolbar = this.context.get(this.keyName + '.main');
384
- toolbar.style.visibility = 'hidden';
385
- this._offSticky();
386
-
387
- toolbar.style.display = 'block';
388
- toolbar.style.top = '0px';
389
- this._inlineToolbarAttr.width = toolbar.style.width = this.options.get(this.keyName + '_width');
390
- this._inlineToolbarAttr.top = toolbar.style.top = -1 + (this.offset.getGlobal(this.editor.frameContext.get('topArea')).top - this.offset.getGlobal(toolbar).top - toolbar.offsetHeight) + 'px';
391
-
392
- this._resetSticky();
393
- this._inlineToolbarAttr.isShow = true;
394
-
395
- this.triggerEvent('onShowToolbar', { toolbar, mode: 'inline' });
396
-
397
- toolbar.style.visibility = '';
398
- },
399
-
400
- /**
401
- * @private
402
- * @this {ToolbarThis}
403
- * @description Show a more options layer for toolbar buttons.
404
- * @param {Node} button - Button element
405
- * @param {Node} layer - More options layer element
406
- */
407
- _moreLayerOn(button, layer) {
408
- this._moreLayerOff();
409
- this.currentMoreLayerActiveButton = /** @type {HTMLButtonElement} */ (button);
410
- /** @type {HTMLElement} */ (layer).style.display = 'block';
411
- },
412
-
413
- /**
414
- * @private
415
- * @this {ToolbarThis}
416
- * @description Hide the currently active more options layer.
417
- */
418
- _moreLayerOff() {
419
- if (this.currentMoreLayerActiveButton) {
420
- const layer = this.context.get(this.keyName + '.main').querySelector('.' + this.currentMoreLayerActiveButton.getAttribute('data-command'));
421
- layer.style.display = 'none';
422
- dom.utils.removeClass(this.currentMoreLayerActiveButton, 'on');
423
- this.currentMoreLayerActiveButton = null;
424
- }
425
- },
426
-
427
- constructor: Toolbar
428
- };
429
-
430
- export default Toolbar;
1
+ /**
2
+ * @fileoverview Toolbar class
3
+ */
4
+
5
+ import { dom, env } from '../../helper';
6
+ import CoreInjector from '../../editorInjector/_core';
7
+ import { CreateToolBar, UpdateButton } from '../section/constructor';
8
+
9
+ const { _w } = env;
10
+
11
+ /**
12
+ * @typedef {Omit<Toolbar & Partial<__se__EditorInjector>, 'toolbar' | 'subToolbar'>} ToolbarThis
13
+ */
14
+
15
+ /**
16
+ * @constructor
17
+ * @this {ToolbarThis}
18
+ * @description Toolbar class
19
+ * @param {__se__EditorCore} editor - The root editor instance
20
+ * @param {Object} options - toolbar options
21
+ * @param {String} options.keyName - toolbar key name
22
+ * @param {Boolean} options.balloon - balloon toolbar
23
+ * @param {Boolean} options.inline - inline toolbar
24
+ * @param {Boolean} options.balloonAlways - balloon toolbar always show
25
+ * @param {Array<Node>} options.res - responsive toolbar button list
26
+ */
27
+ function Toolbar(editor, { keyName, balloon, inline, balloonAlways, res }) {
28
+ CoreInjector.call(this, editor);
29
+
30
+ // members
31
+ this.keyName = keyName;
32
+ this.isSub = /sub/.test(keyName);
33
+ this.currentMoreLayerActiveButton = null;
34
+ this._isBalloon = balloon;
35
+ this._isInline = inline;
36
+ this._isBalloonAlways = balloonAlways;
37
+ this._responsiveCurrentSize = 'default';
38
+ this._originRes = res;
39
+ this._rButtonArray = res;
40
+ this._rButtonsInfo = null;
41
+ this._rButtonsize = null;
42
+ this._sticky = false;
43
+ this._isViewPortSize = 'visualViewport' in _w;
44
+ this._inlineToolbarAttr = {
45
+ top: '',
46
+ width: '',
47
+ isShow: false
48
+ };
49
+ this._balloonOffset = {
50
+ top: 0,
51
+ left: 0
52
+ };
53
+
54
+ this._setResponsive();
55
+ }
56
+
57
+ Toolbar.prototype = {
58
+ /**
59
+ * @this {ToolbarThis}
60
+ * @description Disable the toolbar
61
+ */
62
+ disable() {
63
+ /** off menus */
64
+ this._moreLayerOff();
65
+ this.menu.dropdownOff();
66
+ this.menu.containerOff();
67
+ dom.utils.setDisabled(this.context.get(this.keyName + '.buttonTray').querySelectorAll('.se-menu-list .se-toolbar-btn[data-type]'), true);
68
+ },
69
+
70
+ /**
71
+ * @this {ToolbarThis}
72
+ * @description Enable the toolbar
73
+ */
74
+ enable() {
75
+ dom.utils.setDisabled(this.context.get(this.keyName + '.buttonTray').querySelectorAll('.se-menu-list .se-toolbar-btn[data-type]'), false);
76
+ },
77
+
78
+ /**
79
+ * @this {ToolbarThis}
80
+ * @description Show the toolbar
81
+ */
82
+ show() {
83
+ if (this._isInline) {
84
+ this._showInline();
85
+ } else if (this._isBalloon) {
86
+ this._showBalloon();
87
+ } else {
88
+ this.context.get(this.keyName + '.main').style.display = '';
89
+ if (!this.isSub) this.editor.frameContext.get('_stickyDummy').style.display = '';
90
+ }
91
+
92
+ if (!this.isSub) this.resetResponsiveToolbar();
93
+ },
94
+
95
+ /**
96
+ * @this {ToolbarThis}
97
+ * @description Hide the toolbar
98
+ */
99
+ hide() {
100
+ if (this._isInline) {
101
+ this.context.get(this.keyName + '.main').style.display = 'none';
102
+ this.context.get(this.keyName + '.main').style.top = '0px';
103
+ this._inlineToolbarAttr.isShow = false;
104
+ } else {
105
+ this.context.get(this.keyName + '.main').style.display = 'none';
106
+ if (!this.isSub) this.editor.frameContext.get('_stickyDummy').style.display = 'none';
107
+ if (this._isBalloon) {
108
+ this._balloonOffset = {
109
+ top: 0,
110
+ left: 0
111
+ };
112
+ }
113
+ }
114
+ },
115
+
116
+ /**
117
+ * @this {ToolbarThis}
118
+ * @description Reset buttons of the responsive toolbar.
119
+ */
120
+ resetResponsiveToolbar() {
121
+ this.menu.containerOff();
122
+
123
+ const responsiveSize = this._rButtonsize;
124
+ if (responsiveSize) {
125
+ let w = 0;
126
+ if (((this._isBalloon || this._isInline) && this.options.get('toolbar_width') === 'auto') || (this.editor.isSubBalloon && this.options.get('toolbar.sub_width') === 'auto')) {
127
+ w = this.editor.frameContext.get('topArea').offsetWidth;
128
+ } else {
129
+ w = this.context.get(this.keyName + '.main').offsetWidth;
130
+ }
131
+
132
+ let responsiveWidth = 'default';
133
+ for (let i = 1, len = responsiveSize.length; i < len; i++) {
134
+ if (w < responsiveSize[i]) {
135
+ responsiveWidth = responsiveSize[i] + '';
136
+ break;
137
+ }
138
+ }
139
+
140
+ if (this._responsiveCurrentSize !== responsiveWidth) {
141
+ this._responsiveCurrentSize = responsiveWidth;
142
+ this.setButtons(this._rButtonsInfo[responsiveWidth]);
143
+ // this.viewer._resetFullScreenHeight();
144
+ }
145
+ }
146
+ },
147
+
148
+ /**
149
+ * @this {ToolbarThis}
150
+ * @description Reset the buttons on the toolbar. (Editor is not reloaded)
151
+ * - You cannot set a new plugin for the button.
152
+ * @param {Array} buttonList Button list
153
+ */
154
+ setButtons(buttonList) {
155
+ this._moreLayerOff();
156
+ this.menu.dropdownOff();
157
+ this.menu.containerOff();
158
+
159
+ const { options, icons, lang } = this;
160
+ const newToolbar = CreateToolBar(buttonList, this.plugins, options, icons, lang, true);
161
+
162
+ newToolbar.updateButtons.forEach((v) => UpdateButton(v.button, v.plugin, this.icons, this.lang));
163
+
164
+ this.context.get(this.keyName + '.main').replaceChild(newToolbar.buttonTray, this.context.get(this.keyName + '.buttonTray'));
165
+ this.context.set(this.keyName + '.buttonTray', newToolbar.buttonTray);
166
+
167
+ this._resetButtonInfo();
168
+
169
+ this.triggerEvent('onSetToolbarButtons', { buttonTray: newToolbar.buttonTray, frameContext: this.editor.frameContext });
170
+ },
171
+
172
+ /**
173
+ * @private
174
+ * @this {ToolbarThis}
175
+ * @description Reset the common buttons info.
176
+ */
177
+ _resetButtonInfo() {
178
+ this.editor.allCommandButtons = new Map();
179
+ this.editor.subAllCommandButtons = new Map();
180
+ this.editor.commandTargets = new Map();
181
+ this.editor.shortcutsKeyMap = new Map();
182
+
183
+ this.editor.__cachingButtons();
184
+ this.editor.__cachingShortcuts();
185
+
186
+ this.history.resetButtons(this.editor.frameContext.get('key'), null);
187
+ this._resetSticky();
188
+
189
+ this.editor.effectNode = null;
190
+ this.viewer._setButtonsActive();
191
+ if (this.status.hasFocus) this.eventManager.applyTagEffect();
192
+ if (this.editor.frameContext.get('isReadOnly')) this.ui.setControllerOnDisabledButtons(true);
193
+ },
194
+
195
+ /**
196
+ * @private
197
+ * @this {ToolbarThis}
198
+ * @description Reset the sticky toolbar position based on the editor state.
199
+ */
200
+ _resetSticky() {
201
+ const wrapper = this.editor.frameContext.get('wrapper');
202
+ if (!wrapper) return;
203
+
204
+ const toolbar = this.context.get(this.keyName + '.main');
205
+ if (this.editor.frameContext.get('isFullScreen') || toolbar.offsetWidth === 0 || this.options.get('toolbar_sticky') < 0) return;
206
+
207
+ const currentScrollY = this._isViewPortSize ? _w.visualViewport.pageTop : _w.scrollY;
208
+
209
+ const minHeight = this.editor.frameContext.get('_minHeight');
210
+ const editorHeight = wrapper.offsetHeight;
211
+ const editorOffset = this.offset.getGlobal(this.editor.frameContext.get('topArea'));
212
+ const y = currentScrollY + this.options.get('toolbar_sticky');
213
+ const t = (this._isBalloon || this._isInline ? editorOffset.top : this.offset.getGlobal(this.options.get('toolbar_container')).top) - (this._isInline ? toolbar.offsetHeight : 0);
214
+ const inlineOffset = 1;
215
+
216
+ const offSticky = !this.options.get('toolbar_container')
217
+ ? editorHeight + t + this.options.get('toolbar_sticky') - y - minHeight
218
+ : editorOffset.top - currentScrollY + editorHeight - minHeight - this.options.get('toolbar_sticky') - toolbar.offsetHeight;
219
+ if (y < t) {
220
+ this._offSticky();
221
+ } else if (offSticky < 0) {
222
+ if (!this._sticky) this._onSticky(inlineOffset);
223
+ toolbar.style.top = inlineOffset + offSticky + this.__getViewportTop() + 'px';
224
+ } else {
225
+ this._onSticky(inlineOffset);
226
+ }
227
+ },
228
+
229
+ /**
230
+ * @private
231
+ * @this {ToolbarThis}
232
+ * @description Enable sticky toolbar mode and adjust position.
233
+ */
234
+ _onSticky(inlineOffset) {
235
+ const toolbar = this.context.get(this.keyName + '.main');
236
+
237
+ if (!this._isInline) {
238
+ const stickyDummy = !this.options.get('toolbar_container') ? this.editor.frameContext.get('_stickyDummy') : this.context.get('_stickyDummy');
239
+ stickyDummy.style.height = toolbar.offsetHeight + 'px';
240
+ stickyDummy.style.display = 'block';
241
+ }
242
+
243
+ const toolbarTopPosition = this.options.get('toolbar_sticky') + inlineOffset + this.__getViewportTop();
244
+ toolbar.style.top = `${toolbarTopPosition}px`;
245
+ toolbar.style.width = this._isInline ? this._inlineToolbarAttr.width : toolbar.offsetWidth + 'px';
246
+ dom.utils.addClass(toolbar, 'se-toolbar-sticky');
247
+ this._sticky = true;
248
+ },
249
+
250
+ /**
251
+ * @private
252
+ * @this {ToolbarThis}
253
+ * @description Get the viewport's top offset.
254
+ * @returns {number}
255
+ */
256
+ __getViewportTop() {
257
+ if (this._isViewPortSize) {
258
+ return _w.visualViewport.offsetTop;
259
+ }
260
+ return 0;
261
+ },
262
+
263
+ /**
264
+ * @private
265
+ * @this {ToolbarThis}
266
+ * @description Disable sticky toolbar mode.
267
+ */
268
+ _offSticky() {
269
+ const stickyDummy = !this.options.get('toolbar_container') ? this.editor.frameContext.get('_stickyDummy') : this.context.get('_stickyDummy');
270
+ stickyDummy.style.display = 'none';
271
+
272
+ const toolbar = this.context.get(this.keyName + '.main');
273
+ toolbar.style.top = this._isInline ? this._inlineToolbarAttr.top : '';
274
+ toolbar.style.width = this._isInline ? this._inlineToolbarAttr.width : '';
275
+ this.editor.frameContext.get('wrapper').style.marginTop = '';
276
+
277
+ dom.utils.removeClass(toolbar, 'se-toolbar-sticky');
278
+ this._sticky = false;
279
+ },
280
+
281
+ /**
282
+ * @private
283
+ * @this {ToolbarThis}
284
+ * @description Set up responsive behavior for the toolbar buttons.
285
+ */
286
+ _setResponsive() {
287
+ if (this._rButtonArray?.length === 0) {
288
+ this._rButtonArray = null;
289
+ return;
290
+ }
291
+
292
+ this._responsiveCurrentSize = 'default';
293
+ const _rButtonsize = (this._rButtonsize = []);
294
+ const _responsiveButtons = this._originRes;
295
+ const buttonsObj = (this._rButtonsInfo = {
296
+ default: _responsiveButtons[0]
297
+ });
298
+
299
+ for (let i = 1, len = _responsiveButtons.length, size, buttonGroup; i < len; i++) {
300
+ buttonGroup = _responsiveButtons[i];
301
+ size = buttonGroup[0] * 1;
302
+ _rButtonsize.push(size);
303
+ buttonsObj[size] = buttonGroup[1];
304
+ }
305
+
306
+ _rButtonsize.sort((a, b) => a - b).unshift('default');
307
+ },
308
+
309
+ /**
310
+ * @private
311
+ * @this {ToolbarThis}
312
+ * @description Show the balloon toolbar based on the current selection.
313
+ * @param {?Range=} rangeObj - Selection range
314
+ */
315
+ _showBalloon(rangeObj) {
316
+ if (!this._isBalloon) {
317
+ return;
318
+ }
319
+ if (this.isSub) this.resetResponsiveToolbar();
320
+
321
+ const range = rangeObj || this.selection.getRange();
322
+ const toolbar = this.context.get(this.keyName + '.main');
323
+ const selection = this.selection.get();
324
+
325
+ let isDirTop;
326
+ if (this._isBalloonAlways && range.collapsed) {
327
+ isDirTop = true;
328
+ } else if (selection.focusNode === selection.anchorNode) {
329
+ isDirTop = selection.focusOffset < selection.anchorOffset;
330
+ } else {
331
+ const childNodes = dom.query.getListChildNodes(range.commonAncestorContainer, null);
332
+ isDirTop = dom.utils.getArrayIndex(childNodes, selection.focusNode) < dom.utils.getArrayIndex(childNodes, selection.anchorNode);
333
+ }
334
+
335
+ this._setBalloonOffset(isDirTop, range);
336
+
337
+ this.triggerEvent('onShowToolbar', { toolbar, mode: 'balloon', frameContext: this.editor.frameContext });
338
+ },
339
+
340
+ /**
341
+ * @private
342
+ * @this {ToolbarThis}
343
+ * @description Adjust the balloon toolbar's position.
344
+ * @param {boolean} positionTop - Whether the toolbar should be positioned above the selection
345
+ * @param {Range} [range] - Selection range
346
+ */
347
+ _setBalloonOffset(positionTop, range) {
348
+ const toolbar = this.context.get(this.keyName + '.main');
349
+ const topArea = this.editor.frameContext.get('topArea');
350
+ const offsets = this.offset.getGlobal(topArea);
351
+ const stickyTop = offsets.top;
352
+
353
+ if (!this.offset.setRangePosition(toolbar, range, { position: positionTop ? 'top' : 'bottom', addTop: stickyTop })) {
354
+ this.hide();
355
+ return;
356
+ }
357
+
358
+ if (this.options.get('toolbar_container')) {
359
+ const editorParent = topArea.parentElement;
360
+
361
+ let container = this.options.get('toolbar_container');
362
+ let left = container.offsetLeft;
363
+ let top = container.offsetTop;
364
+
365
+ while (!container.parentElement.contains(editorParent) && !/^(BODY|HTML)$/i.test(container.parentElement.nodeName)) {
366
+ container = container.offsetParent;
367
+ left += container.offsetLeft;
368
+ top += container.offsetTop;
369
+ }
370
+
371
+ toolbar.style.left = toolbar.offsetLeft - left + topArea.offsetLeft + 'px';
372
+ toolbar.style.top = toolbar.offsetTop - top + topArea.offsetTop + 'px';
373
+ }
374
+
375
+ const wwScroll = this.offset.getWWScroll();
376
+ this._balloonOffset = {
377
+ top: toolbar.offsetTop + wwScroll.top,
378
+ left: toolbar.offsetLeft + wwScroll.left,
379
+ position: positionTop ? 'top' : 'bottom'
380
+ };
381
+ },
382
+
383
+ /**
384
+ * @private
385
+ * @this {ToolbarThis}
386
+ * @description Show the inline toolbar mode.
387
+ */
388
+ _showInline() {
389
+ if (!this._isInline) return;
390
+
391
+ const toolbar = this.context.get(this.keyName + '.main');
392
+ toolbar.style.visibility = 'hidden';
393
+ this._offSticky();
394
+
395
+ toolbar.style.display = 'block';
396
+ toolbar.style.top = '0px';
397
+ this._inlineToolbarAttr.width = toolbar.style.width = this.options.get(this.keyName + '_width');
398
+ this._inlineToolbarAttr.top = toolbar.style.top = -1 + (this.offset.getGlobal(this.editor.frameContext.get('topArea')).top - this.offset.getGlobal(toolbar).top - toolbar.offsetHeight) + 'px';
399
+
400
+ this._resetSticky();
401
+ this._inlineToolbarAttr.isShow = true;
402
+
403
+ this.triggerEvent('onShowToolbar', { toolbar, mode: 'inline' });
404
+
405
+ toolbar.style.visibility = '';
406
+ },
407
+
408
+ /**
409
+ * @private
410
+ * @this {ToolbarThis}
411
+ * @description Show a more options layer for toolbar buttons.
412
+ * @param {Node} button - Button element
413
+ * @param {Node} layer - More options layer element
414
+ */
415
+ _moreLayerOn(button, layer) {
416
+ this._moreLayerOff();
417
+ this.currentMoreLayerActiveButton = /** @type {HTMLButtonElement} */ (button);
418
+ /** @type {HTMLElement} */ (layer).style.display = 'block';
419
+ },
420
+
421
+ /**
422
+ * @private
423
+ * @this {ToolbarThis}
424
+ * @description Hide the currently active more options layer.
425
+ */
426
+ _moreLayerOff() {
427
+ if (this.currentMoreLayerActiveButton) {
428
+ const layer = this.context.get(this.keyName + '.main').querySelector('.' + this.currentMoreLayerActiveButton.getAttribute('data-command'));
429
+ layer.style.display = 'none';
430
+ dom.utils.removeClass(this.currentMoreLayerActiveButton, 'on');
431
+ this.currentMoreLayerActiveButton = null;
432
+ }
433
+ },
434
+
435
+ constructor: Toolbar
436
+ };
437
+
438
+ export default Toolbar;