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,561 +1,582 @@
1
- /**
2
- * @fileoverview DocumentType class
3
- */
4
-
5
- import { dom, numbers, converter, env } from '../../helper';
6
-
7
- const { _w } = env;
8
-
9
- // A4 constants in points (72 dpi - PDF standard)
10
- const MM_TO_POINTS = 2.83465; // 1mm = 2.83465pt
11
- const POINTS_TO_PIXELS = 96 / 72; // convert PDF points to screen pixels
12
- const A4_HEIGHT_MM = 297;
13
- const A4_PAGE_HEIGHT = Math.floor(A4_HEIGHT_MM * MM_TO_POINTS * POINTS_TO_PIXELS);
14
-
15
- /**
16
- * @constructor
17
- * @description DocumentType, page, header management class
18
- * @param {__se__EditorCore} editor - The root editor instance
19
- * @param {__se__FrameContext} fc - frame context object
20
- */
21
- function DocumentType(editor, fc) {
22
- // members
23
- this.editor = editor;
24
- this.context = editor.context;
25
- this.selection = editor.selection;
26
- this.offset = editor.offset;
27
- this.fc = fc;
28
- this.ww = fc.get('wysiwyg');
29
- this.wwFrame = fc.get('wysiwygFrame');
30
- this.wwWidth = -1;
31
- this.wwHeight = -1;
32
- this.isAutoHeight = fc.get('options').get('height') === 'auto';
33
- this.displayPage = this.isAutoHeight ? _w : fc.get('wysiwyg');
34
- this.innerHeaders = [];
35
- this._wwHeaders = [];
36
- this.documentTypeInner = fc.get('documentTypeInner');
37
- this.inner = null;
38
- this.page = null;
39
- this.totalPages = 0;
40
- this.pageNum = 0;
41
- this.pageHeight = -1;
42
- this.pageBreaksCnt = 0;
43
- this.pages = [];
44
- this.pages_line = [];
45
- this.prevScrollTop = 0;
46
- this.useHeader = editor.options.get('_type_options').includes('header');
47
- this.usePage = editor.options.get('_type_options').includes('page');
48
- this.navigatorButtons = [];
49
- this.pageNavigator = null;
50
- this._mirror = fc.get('documentTypePageMirror');
51
- this._mirrorCache = 0;
52
- this._positionCache = new Map();
53
- this._rePageTimeout = null;
54
-
55
- const mirrorStyles = _w.getComputedStyle(this._mirror);
56
- this._paddingTop = numbers.get(mirrorStyles.paddingTop);
57
- this._paddingBottom = numbers.get(mirrorStyles.paddingBottom);
58
-
59
- // init header
60
- if (this.useHeader) {
61
- const headers = this._getHeaders();
62
- const inner = (this.inner = this.documentTypeInner.querySelector('.se-document-lines-inner'));
63
- let headerHTML = '';
64
- for (let i = 0, len = headers.length, h; i < len; i++) {
65
- h = headers[i];
66
- headerHTML += `<div class="se-doc-item se-doc-h${numbers.get(h.nodeName)}" title="${h.textContent}">${h.textContent}</div>`;
67
- }
68
- inner.innerHTML = headerHTML;
69
- this.innerHeaders = inner.querySelectorAll('div');
70
-
71
- this.editor.eventManager.addEvent(inner, 'click', OnClickHeader.bind(this, this.ww));
72
- }
73
-
74
- // init page
75
- if (this.usePage) {
76
- this.page = fc.get('documentTypePage');
77
- this.pageNavigator = editor.plugins.pageNavigator;
78
- }
79
- }
80
-
81
- DocumentType.prototype = {
82
- /**
83
- * @description Refresh the document header area
84
- */
85
- reHeader() {
86
- if (!this.useHeader) return;
87
-
88
- const headers = this._getHeaders();
89
- const inner = this.inner;
90
- const innerHeaders = this.innerHeaders;
91
-
92
- // update or new headers
93
- for (let i = 0, len = headers.length, h, hClass, innerH; i < len; i++) {
94
- h = headers[i];
95
- hClass = `se-doc-h${numbers.get(h.nodeName)}`;
96
- innerH = innerHeaders[i];
97
-
98
- if (i < innerHeaders.length) {
99
- if (!innerH.classList.contains(hClass) || innerH.textContent !== h.textContent) {
100
- innerH.textContent = innerH.title = h.textContent;
101
- innerH.className = `se-doc-item ${hClass}`;
102
- }
103
- } else {
104
- const newHeader = document.createElement('div');
105
- newHeader.className = `se-doc-item ${hClass}`;
106
- newHeader.textContent = newHeader.title = h.textContent;
107
- inner.appendChild(newHeader);
108
- }
109
- }
110
-
111
- // remove
112
- if (innerHeaders.length > headers.length) {
113
- for (let i = headers.length; i < innerHeaders.length; i++) {
114
- inner.removeChild(innerHeaders[i]);
115
- }
116
- }
117
-
118
- this.innerHeaders = inner.querySelectorAll('div');
119
- },
120
-
121
- /**
122
- * @description Refresh the document page
123
- * @param {boolean} force - Whether to force the page to be re-rendered
124
- * @returns {Promise<void>}
125
- */
126
- async rePage(force) {
127
- if (!this.page) return;
128
- if (this._rePageTimeout) _w.clearTimeout(this._rePageTimeout);
129
-
130
- this._rePageTimeout = _w.setTimeout(async () => {
131
- await dom.utils.waitForMediaLoad(this._mirror, 1500);
132
-
133
- const mirrorHeight = this._mirror.scrollHeight;
134
- const pageBreaks = this._mirror.querySelectorAll('.se-page-break');
135
- if (!force && this.pageHeight === mirrorHeight && this.pageBreaksCnt === pageBreaks.length) return;
136
- this.pageHeight = mirrorHeight;
137
- this.pageBreaksCnt = pageBreaks.length;
138
-
139
- // page break
140
- let pageBreakHeight = 0;
141
- let lastBreakPosition = 0;
142
- let additionalPages = 0;
143
- if (pageBreaks.length > 0) {
144
- pageBreakHeight = pageBreaks[0].offsetHeight;
145
- for (let i = 0; i < pageBreaks.length; i++) {
146
- const breakPosition = pageBreaks[i].offsetTop;
147
- const sectionHeight = breakPosition - lastBreakPosition;
148
-
149
- if (sectionHeight % A4_PAGE_HEIGHT !== 0) {
150
- additionalPages++;
151
- }
152
-
153
- lastBreakPosition = breakPosition;
154
- }
155
-
156
- const lastSectionHeight = mirrorHeight - lastBreakPosition;
157
- if (lastSectionHeight > 0 && lastSectionHeight % A4_PAGE_HEIGHT !== 0) {
158
- additionalPages++;
159
- }
160
- }
161
-
162
- const scrollTop = this.isAutoHeight ? 0 : this._getWWScrollTop();
163
- const totalPages = Math.ceil(mirrorHeight / A4_PAGE_HEIGHT) + additionalPages;
164
- const wwWidth = this.wwFrame.offsetWidth + 1;
165
- const pages = [];
166
-
167
- for (let i = 0; i < pageBreaks.length; i++) {
168
- pages.push({ number: i, top: pageBreaks[i].offsetTop + pageBreakHeight - scrollTop });
169
- }
170
-
171
- // A4 position
172
- this._mirrorCache = 0;
173
- const chr = this.ww.children;
174
- const mChr = this._mirror.children;
175
- this._initializeCache(mChr);
176
- pages.push({ number: 0, top: 0 });
177
- for (let i = 1, t = 0; i < totalPages; i++) {
178
- t += A4_PAGE_HEIGHT + (i === 1 ? this._paddingTop + this._paddingBottom : this._paddingTop);
179
- if (!pages.some((p) => Math.abs(p.top - t) < 1)) {
180
- const { ci, cm, ch } = this._getElementAtPosition(t, mChr);
181
- const el = chr[ci];
182
- if (!el) break;
183
-
184
- if (chr[this._mirrorCache]) {
185
- t += numbers.get(_w.getComputedStyle(chr[this._mirrorCache]).marginBottom);
186
- }
187
-
188
- const elBottom = el.offsetTop + el.offsetHeight;
189
- const top = elBottom + cm + (el.offsetHeight - ch);
190
- pages.push({ number: i, top });
191
- }
192
- }
193
-
194
- if (pages.length === 0) {
195
- this.pages_line = [];
196
- this.totalPages = 1;
197
- this._displayCurrentPage();
198
- return;
199
- }
200
-
201
- // numbering
202
- pages.sort((a, b) => a.top - b.top);
203
- this.page.innerHTML = '';
204
- this.pages = [];
205
- for (let i = 0, t; i < totalPages; i++) {
206
- if (!pages[i]) continue;
207
- t = pages[i].top;
208
- if (mirrorHeight < t) break;
209
- const pageNumber = dom.utils.createElement('DIV', { style: `top:${t - scrollTop}px`, innerHTML: String(i + 1) }, `<div class="se-document-page-line" style="width: ${wwWidth}px;"></div>${i + 1}`);
210
- this.page.appendChild(pageNumber);
211
- this.pages.push(pageNumber);
212
- }
213
-
214
- this.pages_line = this.page.querySelectorAll('.se-document-page-line');
215
- this.totalPages = this.pages.length;
216
- this._displayCurrentPage();
217
- }, 400);
218
- },
219
-
220
- /**
221
- * @private
222
- * @description Initializes the cache for document elements.
223
- * @param {Array<HTMLElement>} mChr - List of mirrored elements.
224
- */
225
- _initializeCache(mChr) {
226
- this._positionCache.clear();
227
- for (let i = 0, len = mChr.length; i < len; i++) {
228
- const element = mChr[i];
229
- const top = element.offsetTop;
230
- const height = element.offsetHeight;
231
- const bottom = top + height;
232
-
233
- this._positionCache.set(i, {
234
- top,
235
- height,
236
- bottom: bottom
237
- });
238
- }
239
- },
240
-
241
- /**
242
- * @private
243
- * @description Retrieves the element at a given position.
244
- * @param {number} pageTop - The vertical position to check.
245
- * @param {NodeList} mChr - List of mirrored elements.
246
- * @returns {{ci: number, cm: number, ch: number}} The closest element and its related data.
247
- * - ci: The index of the closest element.
248
- * - cm: The distance between the top of the closest element and the given position.
249
- * - ch: The height of the closest element.
250
- */
251
- _getElementAtPosition(pageTop, mChr) {
252
- let start = this._mirrorCache;
253
- let end = mChr.length - 1;
254
-
255
- while (start <= end) {
256
- const mid = Math.floor((start + end) / 2);
257
- const { top, height, bottom } = this._positionCache.get(mid);
258
-
259
- if (pageTop >= top && pageTop <= bottom) {
260
- this._mirrorCache = mid;
261
- return { ci: mid, cm: pageTop - bottom, ch: height };
262
- }
263
-
264
- if (pageTop < top) {
265
- end = mid - 1;
266
- } else {
267
- start = mid + 1;
268
- }
269
- }
270
-
271
- const closestIndex = mChr[start] ? start : end;
272
- this._mirrorCache = closestIndex;
273
- const iElement = this._positionCache.get(closestIndex);
274
- return { ci: closestIndex, cm: pageTop - iElement.bottom, ch: iElement.height };
275
- },
276
-
277
- /**
278
- * @description Resizes the document page dynamically.
279
- */
280
- resizePage() {
281
- const wwWidth = this.wwFrame.offsetWidth + 1;
282
- const wwHeight = this.wwFrame.offsetHeight + 1;
283
- let rh = false;
284
- if (wwWidth === this.wwWidth && (rh = wwHeight === this.wwHeight)) return;
285
-
286
- if (wwWidth > 800) {
287
- dom.utils.removeClass(this.documentTypeInner, 'se-document-responsible');
288
- } else {
289
- dom.utils.addClass(this.documentTypeInner, 'se-document-responsible');
290
- }
291
-
292
- this.wwWidth = wwWidth;
293
- this.wwHeight = wwHeight;
294
- const pages_line = this.pages_line;
295
- for (let i = 0, len = pages_line.length; i < len; i++) {
296
- pages_line[i].style.width = `${wwWidth}px`;
297
- }
298
-
299
- if (!rh) this.rePage(true);
300
- this._displayCurrentPage();
301
- },
302
-
303
- /**
304
- * @description Scrolls the document page.
305
- */
306
- scrollPage() {
307
- const prevScrollTop = this.prevScrollTop;
308
- const scrollTop = this._getWWScrollTop();
309
- if (prevScrollTop === scrollTop) return;
310
-
311
- const pages = this.pages;
312
- for (let i = 0, len = pages.length; i < len; i++) {
313
- pages[i].style.top = `${numbers.get(pages[i].style.top) - (scrollTop - prevScrollTop)}px`;
314
- }
315
-
316
- this.prevScrollTop = scrollTop;
317
- this._displayCurrentPage();
318
- },
319
-
320
- /**
321
- * @description Scrolls the window to a specific position.
322
- */
323
- scrollWindow() {
324
- if (!this.isAutoHeight) return;
325
- this._displayCurrentPage();
326
- },
327
-
328
- /**
329
- * @description Retrieves the current page number.
330
- * @returns {number} The current page number.
331
- */
332
- getCurrentPageNumber() {
333
- if (this.totalPages <= 1) return 1;
334
-
335
- let targetPosition = 0;
336
- if (this.isAutoHeight) {
337
- const globalTop = this._getGlobalTop();
338
- targetPosition = _w.scrollY - globalTop + A4_PAGE_HEIGHT / 2;
339
- if (targetPosition <= 0) return 1;
340
- } else {
341
- targetPosition = this.wwHeight / 2;
342
- }
343
-
344
- const pages = this.pages;
345
- for (let i = 0, len = pages.length; i < len; i++) {
346
- if (pages[i].offsetTop >= targetPosition) {
347
- return (this.pageNum = i);
348
- }
349
- }
350
-
351
- return (this.pageNum = this.totalPages);
352
- },
353
-
354
- /**
355
- * @description Moves to the previous page.
356
- */
357
- pageUp() {
358
- const pageNum = this.pageNum - 1 <= 1 ? 1 : this.pageNum - 1;
359
- this._movePage(pageNum, false);
360
- },
361
-
362
- /**
363
- * @description Moves to the next page.
364
- */
365
- pageDown() {
366
- const pageNum = this.pageNum + 1 > this.pages.length ? this.pages.length : this.pageNum + 1;
367
- this._movePage(pageNum, false);
368
- },
369
-
370
- /**
371
- * @description Moves to a specific page.
372
- * @param {number} pageNum - The target page number.
373
- */
374
- pageGo(pageNum) {
375
- if (pageNum < 1) pageNum = 1;
376
- else if (pageNum > this.pages.length) pageNum = this.pages.length;
377
-
378
- this._movePage(pageNum, true);
379
- },
380
-
381
- /**
382
- * @description Highlights the header of the current line.
383
- * @param {Node} line - The "line" element to be highlighted.
384
- */
385
- on(line) {
386
- if (!this.useHeader) return;
387
-
388
- if (!this._is(line)) line = this._findLinesHeader(line);
389
- if (!line) return;
390
-
391
- const item = this._findItem(line);
392
- if (!item) return;
393
-
394
- dom.utils.removeClass(this.innerHeaders, 'active');
395
- dom.utils.addClass(item, 'active');
396
- },
397
-
398
- /**
399
- * @description Handles text changes in the document.
400
- */
401
- onChangeText(header) {
402
- if (!this.useHeader) return;
403
-
404
- if (!this._is(header)) return;
405
- const item = this._findItem(header);
406
- if (!item) return;
407
- item.textContent = header.textContent;
408
- },
409
-
410
- /**
411
- * @private
412
- * @description Displays the current page number.
413
- */
414
- _displayCurrentPage() {
415
- const pageNum = this.getCurrentPageNumber();
416
- this.pageNavigator?.display(pageNum, this.totalPages);
417
- },
418
-
419
- /**
420
- * @private
421
- * @description Retrieves the scroll position in WYSIWYG mode.
422
- * @returns {number} The current scroll position.
423
- */
424
- _getWWScrollTop() {
425
- return this.displayPage.scrollTop || this.displayPage.scrollY || 0;
426
- },
427
-
428
- /**
429
- * @private
430
- * @description Moves to a specific page and updates the view.
431
- * @param {number} pageNum - The target page number.
432
- */
433
- _movePage(pageNum, force) {
434
- const globalTop = this._getGlobalTop();
435
- const children = converter.nodeListToArray(this.ww.children);
436
- const pageTop = this.page.offsetTop + numbers.get(this.pages[pageNum - 1].style.top) + (this.isAutoHeight ? 0 : this._getWWScrollTop());
437
- for (let i = 0, len = children.length, c; i < len; i++) {
438
- c = children[i];
439
- if (c.offsetTop >= pageTop) {
440
- if (!force) this.selection.setRange(c, 0, c, 0);
441
- const scrollTop = i === 0 && !this.isAutoHeight ? 0 : c.offsetTop - this.page.offsetTop - c.offsetHeight + globalTop;
442
- this._applyPageScroll(scrollTop, () => {
443
- if (this.editor.toolbar._sticky) {
444
- this.displayPage.scrollTo({ top: scrollTop - this.context.get('toolbar.main').offsetHeight, behavior: 'smooth' });
445
- }
446
- });
447
-
448
- this.pageNum = pageNum;
449
- break;
450
- }
451
- }
452
- },
453
-
454
- /**
455
- * @private
456
- * @description Applies smooth scrolling for page navigation.
457
- */
458
- _applyPageScroll(top, callback) {
459
- this.displayPage.scrollTo({ top, behavior: 'smooth' });
460
- const checkScrollEnd = () => {
461
- if (Math.abs((this.displayPage.scrollY ?? this.displayPage.scrollTop) - top) < 1) {
462
- callback();
463
- } else {
464
- _w.requestAnimationFrame(checkScrollEnd);
465
- }
466
- };
467
-
468
- _w.requestAnimationFrame(checkScrollEnd);
469
- },
470
-
471
- /**
472
- * @private
473
- * @description Retrieves the global top offset of an element.
474
- * @returns {number} The top offset of the element.
475
- */
476
- _getGlobalTop() {
477
- return this.isAutoHeight ? this.offset.getGlobal(this.wwFrame).top : 0;
478
- },
479
-
480
- /**
481
- * @private
482
- * @description Finds an header element of innerHeaders element.
483
- * @param {Node} header - H tag element to find.
484
- * @returns {HTMLElement|null} The found element, or null if not found.
485
- */
486
- _findItem(header) {
487
- const headers = this._wwHeaders;
488
- const index = Array.prototype.indexOf.call(headers, header);
489
-
490
- if (index !== -1 && this.innerHeaders[index]) {
491
- return this.innerHeaders[index];
492
- }
493
-
494
- return null;
495
- },
496
-
497
- /**
498
- * @private
499
- * @description Finds the closest header element from a given line.
500
- * @param {Node} line - The "line" to check.
501
- * @returns {Node|null} The closest header element, or null if not found.
502
- */
503
- _findLinesHeader(line) {
504
- while (line && line !== this.ww) {
505
- if (this._is(line)) {
506
- return line;
507
- }
508
- line = /** @type {HTMLElement} */ (line).previousElementSibling || line.parentElement;
509
- }
510
-
511
- return null;
512
- },
513
-
514
- /**
515
- * @private
516
- * @description Checks if an element is a header.
517
- * @param {Node} element - The element to check.
518
- * @returns {boolean} True if the element is a header, otherwise false.
519
- */
520
- _is(element) {
521
- return /^h[1-6]$/i.test(element?.nodeName);
522
- },
523
-
524
- /**
525
- * @private
526
- * @description Retrieves all headers in the document.
527
- * @returns {Array<HTMLElement>} An array of header elements.
528
- */
529
- _getHeaders() {
530
- return (this._wwHeaders = this.ww.querySelectorAll('h1, h2, h3, h4, h5, h6'));
531
- },
532
-
533
- constructor: DocumentType
534
- };
535
-
536
- /**
537
- * @private
538
- * @param {HTMLElement} ww WYSIWYG element
539
- * @param {Event} e Event object
540
- */
541
- function OnClickHeader(ww, e) {
542
- e.preventDefault();
543
-
544
- try {
545
- this.editor._preventBlur = true;
546
- const clickedHeader = dom.query.getEventTarget(e);
547
- if (dom.utils.hasClass(clickedHeader, 'se-doc-item')) {
548
- const innerIndex = Array.prototype.indexOf.call(this.innerHeaders, clickedHeader);
549
- if (innerIndex === -1) return;
550
-
551
- const header = this._wwHeaders[innerIndex];
552
- if (header) {
553
- this.selection.scrollTo(header);
554
- }
555
- }
556
- } finally {
557
- this.editor._preventBlur = false;
558
- }
559
- }
560
-
561
- export default DocumentType;
1
+ /**
2
+ * @fileoverview DocumentType class
3
+ */
4
+
5
+ import { dom, numbers, converter, env } from '../../helper';
6
+
7
+ const { _w } = env;
8
+
9
+ // A4 constants in points (72 dpi - PDF standard)
10
+ const MM_TO_POINTS = 2.83465; // 1mm = 2.83465pt
11
+ const POINTS_TO_PIXELS = 96 / 72; // convert PDF points to screen pixels
12
+ const A4_HEIGHT_MM = 297;
13
+ const A4_PAGE_HEIGHT = Math.floor(A4_HEIGHT_MM * MM_TO_POINTS * POINTS_TO_PIXELS);
14
+
15
+ /**
16
+ * @constructor
17
+ * @description DocumentType, page, header management class
18
+ * @param {__se__EditorCore} editor - The root editor instance
19
+ * @param {__se__FrameContext} fc - frame context object
20
+ */
21
+ function DocumentType(editor, fc) {
22
+ // members
23
+ this.editor = editor;
24
+ this.context = editor.context;
25
+ this.selection = editor.selection;
26
+ this.offset = editor.offset;
27
+ this.fc = fc;
28
+ this.ww = fc.get('wysiwyg');
29
+ this.wwFrame = fc.get('wysiwygFrame');
30
+ this.wwWidth = -1;
31
+ this.wwHeight = -1;
32
+ this.isAutoHeight = fc.get('options').get('height') === 'auto';
33
+ this.displayPage = this.isAutoHeight ? _w : fc.get('wysiwyg');
34
+ this.innerHeaders = [];
35
+ this._wwHeaders = [];
36
+ this.documentTypeInner = fc.get('documentTypeInner');
37
+ this.inner = null;
38
+ this.page = null;
39
+ this.totalPages = 0;
40
+ this.pageNum = 0;
41
+ this.pageHeight = -1;
42
+ this.pageBreaksCnt = 0;
43
+ this.pages = [];
44
+ this.pages_line = [];
45
+ this.prevScrollTop = 0;
46
+ this.useHeader = editor.options.get('_type_options').includes('header');
47
+ this.usePage = editor.options.get('_type_options').includes('page');
48
+ this.navigatorButtons = [];
49
+ this.pageNavigator = null;
50
+ this._mirror = fc.get('documentTypePageMirror');
51
+ this._mirrorCache = 0;
52
+ this._positionCache = new Map();
53
+ this._rePageTimeout = null;
54
+
55
+ const mirrorStyles = _w.getComputedStyle(this._mirror);
56
+ this._paddingTop = numbers.get(mirrorStyles.paddingTop);
57
+ this._paddingBottom = numbers.get(mirrorStyles.paddingBottom);
58
+
59
+ // init header
60
+ if (this.useHeader) {
61
+ const headers = this._getHeaders();
62
+ const inner = (this.inner = this.documentTypeInner.querySelector('.se-document-lines-inner'));
63
+ let headerHTML = '';
64
+ for (let i = 0, len = headers.length, h; i < len; i++) {
65
+ h = headers[i];
66
+ headerHTML += `<div class="se-doc-item se-doc-h${numbers.get(h.nodeName)}" title="${h.textContent}">${h.textContent}</div>`;
67
+ }
68
+ inner.innerHTML = headerHTML;
69
+ this.innerHeaders = inner.querySelectorAll('div');
70
+
71
+ this.editor.eventManager.addEvent(inner, 'click', OnClickHeader.bind(this, this.ww));
72
+ }
73
+
74
+ // init page
75
+ if (this.usePage) {
76
+ this.page = fc.get('documentTypePage');
77
+ this.pageNavigator = editor.plugins.pageNavigator;
78
+ }
79
+ }
80
+
81
+ DocumentType.prototype = {
82
+ /**
83
+ * @description Refresh the document header area
84
+ */
85
+ reHeader() {
86
+ if (!this.useHeader) return;
87
+
88
+ const headers = this._getHeaders();
89
+ const inner = this.inner;
90
+ const innerHeaders = this.innerHeaders;
91
+
92
+ // update or new headers
93
+ for (let i = 0, len = headers.length, h, hClass, innerH; i < len; i++) {
94
+ h = headers[i];
95
+ hClass = `se-doc-h${numbers.get(h.nodeName)}`;
96
+ innerH = innerHeaders[i];
97
+
98
+ if (i < innerHeaders.length) {
99
+ if (!innerH.classList.contains(hClass) || innerH.textContent !== h.textContent) {
100
+ innerH.textContent = innerH.title = h.textContent;
101
+ innerH.className = `se-doc-item ${hClass}`;
102
+ }
103
+ } else {
104
+ const newHeader = document.createElement('div');
105
+ newHeader.className = `se-doc-item ${hClass}`;
106
+ newHeader.textContent = newHeader.title = h.textContent;
107
+ inner.appendChild(newHeader);
108
+ }
109
+ }
110
+
111
+ // remove
112
+ if (innerHeaders.length > headers.length) {
113
+ for (let i = headers.length; i < innerHeaders.length; i++) {
114
+ inner.removeChild(innerHeaders[i]);
115
+ }
116
+ }
117
+
118
+ this.innerHeaders = inner.querySelectorAll('div');
119
+ },
120
+
121
+ /**
122
+ * @description Refresh the document page
123
+ * @param {boolean} force - Whether to force the page to be re-rendered
124
+ * @returns {Promise<void>}
125
+ */
126
+ async rePage(force) {
127
+ if (!this.page) return;
128
+ if (this._rePageTimeout) _w.clearTimeout(this._rePageTimeout);
129
+
130
+ this._rePageTimeout = _w.setTimeout(async () => {
131
+ await dom.utils.waitForMediaLoad(this._mirror, 1500);
132
+
133
+ const heightGap = this.ww.scrollHeight > this._mirror.scrollHeight ? this.ww.scrollHeight - this._mirror.scrollHeight : 0;
134
+ const mirrorHeight = this._mirror.scrollHeight + heightGap;
135
+ const pageBreaks = this.ww.querySelectorAll('.se-page-break');
136
+ if (!force && this.pageHeight === mirrorHeight && this.pageBreaksCnt === pageBreaks.length) return;
137
+
138
+ this.pageHeight = mirrorHeight;
139
+ this.pageBreaksCnt = pageBreaks.length;
140
+
141
+ // page break
142
+ let pageBreakHeight = 0;
143
+ let lastBreakPosition = 0;
144
+ let additionalPages = 0;
145
+ if (pageBreaks.length > 0) {
146
+ pageBreakHeight = pageBreaks[0].offsetHeight;
147
+ for (let i = 0; i < pageBreaks.length; i++) {
148
+ const breakPosition = pageBreaks[i].offsetTop;
149
+ const sectionHeight = breakPosition - lastBreakPosition;
150
+ if (sectionHeight % A4_PAGE_HEIGHT !== 0) additionalPages++;
151
+ lastBreakPosition = breakPosition;
152
+ }
153
+
154
+ const lastSectionHeight = mirrorHeight - lastBreakPosition;
155
+ if (lastSectionHeight > 0 && lastSectionHeight % A4_PAGE_HEIGHT !== 0) additionalPages++;
156
+ }
157
+
158
+ const scrollTop = this.isAutoHeight ? 0 : this._getWWScrollTop();
159
+ const totalPages = Math.ceil(mirrorHeight / A4_PAGE_HEIGHT) + additionalPages;
160
+ const wwWidth = this.wwFrame.offsetWidth + 1;
161
+ const pages = [];
162
+
163
+ for (let i = 0; i < pageBreaks.length; i++) {
164
+ pages.push({ number: i, top: pageBreaks[i].offsetTop + pageBreakHeight / 2 - scrollTop });
165
+ }
166
+
167
+ this._mirrorCache = 0;
168
+ const chr = this.ww.children;
169
+ const mChr = this._mirror.children;
170
+ this._initializeCache(mChr);
171
+
172
+ pages.push({ number: 0, top: 0 });
173
+
174
+ for (let i = 1, t = 0; i < totalPages; i++) {
175
+ t += A4_PAGE_HEIGHT + (i === 1 ? this._paddingTop + this._paddingBottom : this._paddingTop);
176
+ if (!pages.some((p) => Math.abs(p.top - t) < 3)) {
177
+ const top = this._calcPageBreakTop(t, chr, mChr);
178
+ if (top === null) break;
179
+ pages.push({ number: i, top });
180
+ }
181
+ }
182
+
183
+ if (pages.length === 0) {
184
+ this.pages_line = [];
185
+ this.totalPages = 1;
186
+ this._displayCurrentPage();
187
+ return;
188
+ }
189
+
190
+ // numbering
191
+ pages.sort((a, b) => a.top - b.top);
192
+ this.page.innerHTML = '';
193
+ this.pages = [];
194
+
195
+ for (let i = 0, t; i < totalPages; i++) {
196
+ if (!pages[i]) continue;
197
+ t = pages[i].top;
198
+ if (mirrorHeight < t) break;
199
+
200
+ const pageNumber = dom.utils.createElement(
201
+ 'DIV',
202
+ {
203
+ style: `top:${t - scrollTop}px`,
204
+ innerHTML: String(i + 1)
205
+ },
206
+ `<div class="se-document-page-line" style="width: ${wwWidth}px;"></div>${i + 1}`
207
+ );
208
+
209
+ this.page.appendChild(pageNumber);
210
+ this.pages.push(pageNumber);
211
+ }
212
+
213
+ this.pages_line = this.page.querySelectorAll('.se-document-page-line');
214
+ this.totalPages = this.pages.length;
215
+ this._displayCurrentPage();
216
+ }, 400);
217
+ },
218
+
219
+ /**
220
+ * @private
221
+ * @description Calculates and compensates for the vertical gap between the rendered content (current page)
222
+ * - and the mirrored preview page due to differences in width and layout.
223
+ * @param {number} t - The initial top position value to be adjusted.
224
+ * @param {HTMLElement[]} chr - The elements array in the current (main) page.
225
+ * @param {HTMLElement[]} mChr - The elements array in the mirrored page.
226
+ * @returns {number|null} - The adjusted top value.
227
+ */
228
+ _calcPageBreakTop(t, chr, mChr) {
229
+ const { ci } = this._getElementAtPosition(t, mChr);
230
+ const mel = mChr[ci];
231
+ const el = chr[ci];
232
+ if (!mel || !el) return null;
233
+
234
+ const offsetDiff = el.offsetTop - mel.offsetTop;
235
+ const heightDiff = el.offsetHeight - mel.offsetHeight;
236
+
237
+ const top = t + offsetDiff + heightDiff / 2;
238
+ return Math.round(top);
239
+ },
240
+
241
+ /**
242
+ * @private
243
+ * @description Initializes the cache for document elements.
244
+ * @param {Array<HTMLElement>} mChr - List of mirrored elements.
245
+ */
246
+ _initializeCache(mChr) {
247
+ this._positionCache.clear();
248
+ for (let i = 0, len = mChr.length; i < len; i++) {
249
+ const element = mChr[i];
250
+ const top = element.offsetTop;
251
+ const height = element.offsetHeight;
252
+ const bottom = top + height;
253
+
254
+ this._positionCache.set(i, {
255
+ top,
256
+ height,
257
+ bottom: bottom
258
+ });
259
+ }
260
+ },
261
+
262
+ /**
263
+ * @private
264
+ * @description Retrieves the element at a given position.
265
+ * @param {number} pageTop - The vertical position to check.
266
+ * @param {HTMLElement[]} mChr - List of mirrored elements.
267
+ * @returns {{ci: number, cm: number, ch: number}} The closest element and its related data.
268
+ * - ci: The index of the closest element.
269
+ * - cm: The distance between the top of the closest element and the given position.
270
+ * - ch: The height of the closest element.
271
+ */
272
+ _getElementAtPosition(pageTop, mChr) {
273
+ let start = this._mirrorCache;
274
+ let end = mChr.length - 1;
275
+
276
+ while (start <= end) {
277
+ const mid = Math.floor((start + end) / 2);
278
+ const { top, height, bottom } = this._positionCache.get(mid);
279
+
280
+ if (pageTop >= top && pageTop <= bottom) {
281
+ this._mirrorCache = mid;
282
+ return { ci: mid, cm: pageTop - bottom, ch: height };
283
+ }
284
+
285
+ if (pageTop < top) {
286
+ end = mid - 1;
287
+ } else {
288
+ start = mid + 1;
289
+ }
290
+ }
291
+
292
+ const closestIndex = mChr[start] ? start : end;
293
+ this._mirrorCache = closestIndex;
294
+ const iElement = this._positionCache.get(closestIndex);
295
+ return { ci: closestIndex, cm: pageTop - iElement.bottom, ch: iElement.height };
296
+ },
297
+
298
+ /**
299
+ * @description Resizes the document page dynamically.
300
+ */
301
+ resizePage() {
302
+ const wwWidth = this.wwFrame.offsetWidth + 1;
303
+ const wwHeight = this.wwFrame.offsetHeight + 1;
304
+ let rh = false;
305
+ if (wwWidth === this.wwWidth && (rh = wwHeight === this.wwHeight)) return;
306
+
307
+ if (wwWidth > 800) {
308
+ dom.utils.removeClass(this.documentTypeInner, 'se-document-responsible');
309
+ } else {
310
+ dom.utils.addClass(this.documentTypeInner, 'se-document-responsible');
311
+ }
312
+
313
+ this.wwWidth = wwWidth;
314
+ this.wwHeight = wwHeight;
315
+ const pages_line = this.pages_line;
316
+ for (let i = 0, len = pages_line.length; i < len; i++) {
317
+ pages_line[i].style.width = `${wwWidth}px`;
318
+ }
319
+
320
+ if (!rh) this.rePage(true);
321
+ this._displayCurrentPage();
322
+ },
323
+
324
+ /**
325
+ * @description Scrolls the document page.
326
+ */
327
+ scrollPage() {
328
+ const prevScrollTop = this.prevScrollTop;
329
+ const scrollTop = this._getWWScrollTop();
330
+ if (prevScrollTop === scrollTop) return;
331
+
332
+ const pages = this.pages;
333
+ for (let i = 0, len = pages.length; i < len; i++) {
334
+ pages[i].style.top = `${numbers.get(pages[i].style.top) - (scrollTop - prevScrollTop)}px`;
335
+ }
336
+
337
+ this.prevScrollTop = scrollTop;
338
+ this._displayCurrentPage();
339
+ },
340
+
341
+ /**
342
+ * @description Scrolls the window to a specific position.
343
+ */
344
+ scrollWindow() {
345
+ if (!this.isAutoHeight) return;
346
+ this._displayCurrentPage();
347
+ },
348
+
349
+ /**
350
+ * @description Retrieves the current page number.
351
+ * @returns {number} The current page number.
352
+ */
353
+ getCurrentPageNumber() {
354
+ if (this.totalPages <= 1) return 1;
355
+
356
+ let targetPosition = 0;
357
+ if (this.isAutoHeight) {
358
+ const globalTop = this._getGlobalTop();
359
+ targetPosition = _w.scrollY - globalTop + A4_PAGE_HEIGHT / 2;
360
+ if (targetPosition <= 0) return 1;
361
+ } else {
362
+ targetPosition = this.wwHeight / 2;
363
+ }
364
+
365
+ const pages = this.pages;
366
+ for (let i = 0, len = pages.length; i < len; i++) {
367
+ if (pages[i].offsetTop >= targetPosition) {
368
+ return (this.pageNum = i);
369
+ }
370
+ }
371
+
372
+ return (this.pageNum = this.totalPages);
373
+ },
374
+
375
+ /**
376
+ * @description Moves to the previous page.
377
+ */
378
+ pageUp() {
379
+ const pageNum = this.pageNum - 1 <= 1 ? 1 : this.pageNum - 1;
380
+ this._movePage(pageNum, false);
381
+ },
382
+
383
+ /**
384
+ * @description Moves to the next page.
385
+ */
386
+ pageDown() {
387
+ const pageNum = this.pageNum + 1 > this.pages.length ? this.pages.length : this.pageNum + 1;
388
+ this._movePage(pageNum, false);
389
+ },
390
+
391
+ /**
392
+ * @description Moves to a specific page.
393
+ * @param {number} pageNum - The target page number.
394
+ */
395
+ pageGo(pageNum) {
396
+ if (pageNum < 1) pageNum = 1;
397
+ else if (pageNum > this.pages.length) pageNum = this.pages.length;
398
+
399
+ this._movePage(pageNum, true);
400
+ },
401
+
402
+ /**
403
+ * @description Highlights the header of the current line.
404
+ * @param {Node} line - The "line" element to be highlighted.
405
+ */
406
+ on(line) {
407
+ if (!this.useHeader) return;
408
+
409
+ if (!this._is(line)) line = this._findLinesHeader(line);
410
+ if (!line) return;
411
+
412
+ const item = this._findItem(line);
413
+ if (!item) return;
414
+
415
+ dom.utils.removeClass(this.innerHeaders, 'active');
416
+ dom.utils.addClass(item, 'active');
417
+ },
418
+
419
+ /**
420
+ * @description Handles text changes in the document.
421
+ */
422
+ onChangeText(header) {
423
+ if (!this.useHeader) return;
424
+
425
+ if (!this._is(header)) return;
426
+ const item = this._findItem(header);
427
+ if (!item) return;
428
+ item.textContent = header.textContent;
429
+ },
430
+
431
+ /**
432
+ * @private
433
+ * @description Displays the current page number.
434
+ */
435
+ _displayCurrentPage() {
436
+ const pageNum = this.getCurrentPageNumber();
437
+ this.pageNavigator?.display(pageNum, this.totalPages);
438
+ },
439
+
440
+ /**
441
+ * @private
442
+ * @description Retrieves the scroll position in WYSIWYG mode.
443
+ * @returns {number} The current scroll position.
444
+ */
445
+ _getWWScrollTop() {
446
+ return this.displayPage.scrollTop || this.displayPage.scrollY || 0;
447
+ },
448
+
449
+ /**
450
+ * @private
451
+ * @description Moves to a specific page and updates the view.
452
+ * @param {number} pageNum - The target page number.
453
+ */
454
+ _movePage(pageNum, force) {
455
+ const globalTop = this._getGlobalTop();
456
+ const children = converter.nodeListToArray(this.ww.children);
457
+ const pageTop = this.page.offsetTop + numbers.get(this.pages[pageNum - 1].style.top) + (this.isAutoHeight ? 0 : this._getWWScrollTop());
458
+ for (let i = 0, len = children.length, c; i < len; i++) {
459
+ c = children[i];
460
+ if (c.offsetTop >= pageTop) {
461
+ if (!force) this.selection.setRange(c, 0, c, 0);
462
+ const scrollTop = i === 0 && !this.isAutoHeight ? 0 : c.offsetTop - this.page.offsetTop - c.offsetHeight + globalTop;
463
+ this._applyPageScroll(scrollTop, () => {
464
+ if (this.editor.toolbar._sticky) {
465
+ this.displayPage.scrollTo({ top: scrollTop - this.context.get('toolbar.main').offsetHeight, behavior: 'smooth' });
466
+ }
467
+ });
468
+
469
+ this.pageNum = pageNum;
470
+ break;
471
+ }
472
+ }
473
+ },
474
+
475
+ /**
476
+ * @private
477
+ * @description Applies smooth scrolling for page navigation.
478
+ */
479
+ _applyPageScroll(top, callback) {
480
+ this.displayPage.scrollTo({ top, behavior: 'smooth' });
481
+ const checkScrollEnd = () => {
482
+ if (Math.abs((this.displayPage.scrollY ?? this.displayPage.scrollTop) - top) < 1) {
483
+ callback();
484
+ } else {
485
+ _w.requestAnimationFrame(checkScrollEnd);
486
+ }
487
+ };
488
+
489
+ _w.requestAnimationFrame(checkScrollEnd);
490
+ },
491
+
492
+ /**
493
+ * @private
494
+ * @description Retrieves the global top offset of an element.
495
+ * @returns {number} The top offset of the element.
496
+ */
497
+ _getGlobalTop() {
498
+ return this.isAutoHeight ? this.offset.getGlobal(this.wwFrame).top : 0;
499
+ },
500
+
501
+ /**
502
+ * @private
503
+ * @description Finds an header element of innerHeaders element.
504
+ * @param {Node} header - H tag element to find.
505
+ * @returns {HTMLElement|null} The found element, or null if not found.
506
+ */
507
+ _findItem(header) {
508
+ const headers = this._wwHeaders;
509
+ const index = Array.prototype.indexOf.call(headers, header);
510
+
511
+ if (index !== -1 && this.innerHeaders[index]) {
512
+ return this.innerHeaders[index];
513
+ }
514
+
515
+ return null;
516
+ },
517
+
518
+ /**
519
+ * @private
520
+ * @description Finds the closest header element from a given line.
521
+ * @param {Node} line - The "line" to check.
522
+ * @returns {Node|null} The closest header element, or null if not found.
523
+ */
524
+ _findLinesHeader(line) {
525
+ while (line && line !== this.ww) {
526
+ if (this._is(line)) {
527
+ return line;
528
+ }
529
+ line = /** @type {HTMLElement} */ (line).previousElementSibling || line.parentElement;
530
+ }
531
+
532
+ return null;
533
+ },
534
+
535
+ /**
536
+ * @private
537
+ * @description Checks if an element is a header.
538
+ * @param {Node} element - The element to check.
539
+ * @returns {boolean} True if the element is a header, otherwise false.
540
+ */
541
+ _is(element) {
542
+ return /^h[1-6]$/i.test(element?.nodeName);
543
+ },
544
+
545
+ /**
546
+ * @private
547
+ * @description Retrieves all headers in the document.
548
+ * @returns {Array<HTMLElement>} An array of header elements.
549
+ */
550
+ _getHeaders() {
551
+ return (this._wwHeaders = this.ww.querySelectorAll('h1, h2, h3, h4, h5, h6'));
552
+ },
553
+
554
+ constructor: DocumentType
555
+ };
556
+
557
+ /**
558
+ * @private
559
+ * @param {HTMLElement} ww WYSIWYG element
560
+ * @param {Event} e Event object
561
+ */
562
+ function OnClickHeader(ww, e) {
563
+ e.preventDefault();
564
+
565
+ try {
566
+ this.editor._preventBlur = true;
567
+ const clickedHeader = dom.query.getEventTarget(e);
568
+ if (dom.utils.hasClass(clickedHeader, 'se-doc-item')) {
569
+ const innerIndex = Array.prototype.indexOf.call(this.innerHeaders, clickedHeader);
570
+ if (innerIndex === -1) return;
571
+
572
+ const header = this._wwHeaders[innerIndex];
573
+ if (header) {
574
+ this.selection.scrollTo(header);
575
+ }
576
+ }
577
+ } finally {
578
+ this.editor._preventBlur = false;
579
+ }
580
+ }
581
+
582
+ export default DocumentType;