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,750 +1,750 @@
1
- /**
2
- * @fileoverview Viewer class
3
- */
4
-
5
- import CoreInjector from '../../editorInjector/_core';
6
- import { dom, env, converter, numbers } from '../../helper';
7
-
8
- /**
9
- * @typedef {Omit<Viewer & Partial<__se__EditorInjector>, 'viewer'>} ViewerThis
10
- */
11
-
12
- /**
13
- * @constructor
14
- * @this {ViewerThis}
15
- * @description Viewer(codeView, fullScreen, showBlocks) class
16
- * @param {__se__EditorCore} editor - The root editor instance
17
- */
18
- function Viewer(editor) {
19
- CoreInjector.call(this, editor);
20
-
21
- // members
22
- this.bodyOverflow = '';
23
- this.editorAreaOriginCssText = '';
24
- this.wysiwygOriginCssText = '';
25
- this.codeWrapperOriginCssText = '';
26
- this.codeOriginCssText = '';
27
- this.codeNumberOriginCssText = '';
28
- this.toolbarOriginCssText = '';
29
- this.arrowOriginCssText = '';
30
- this.fullScreenInnerHeight = 0;
31
- this.fullScreenSticky = false;
32
- this.fullScreenBalloon = false;
33
- this.fullScreenInline = false;
34
- this.toolbarParent = null;
35
- }
36
-
37
- Viewer.prototype = {
38
- /**
39
- * @this {ViewerThis}
40
- * @description Changes to code view or wysiwyg view
41
- * @param {boolean=} value true/false, If undefined toggle the codeView mode.
42
- */
43
- codeView(value) {
44
- const fc = this.editor.frameContext;
45
- if (value === undefined) value = !fc.get('isCodeView');
46
- if (value === fc.get('isCodeView')) return;
47
-
48
- fc.set('isCodeView', value);
49
- this.ui._offCurrentController();
50
- this.ui._offCurrentModal();
51
-
52
- const codeWrapper = fc.get('codeWrapper');
53
- const codeFrame = fc.get('code');
54
- const wysiwygFrame = fc.get('wysiwygFrame');
55
- const wrapper = fc.get('wrapper');
56
-
57
- if (value) {
58
- this._setEditorDataToCodeView();
59
- codeWrapper.style.setProperty('display', 'flex', 'important');
60
- wysiwygFrame.style.display = 'none';
61
-
62
- if (fc.get('isFullScreen')) {
63
- codeFrame.style.height = '100%';
64
- } else if (this.editor.frameOptions.get('height') === 'auto' && !this.options.get('hasCodeMirror')) {
65
- codeFrame.style.height = codeFrame.scrollHeight > 0 ? codeFrame.scrollHeight + 'px' : 'auto';
66
- }
67
-
68
- if (this.options.get('hasCodeMirror')) {
69
- this._codeMirrorEditor('refresh', null, null);
70
- }
71
-
72
- if (!fc.get('isFullScreen')) {
73
- this.editor._notHideToolbar = true;
74
- if (this.editor.isBalloon) {
75
- this.context.get('toolbar._arrow').style.display = 'none';
76
- this.context.get('toolbar.main').style.left = '';
77
- this.editor.isInline = this.toolbar._isInline = true;
78
- this.editor.isBalloon = this.toolbar._isBalloon = false;
79
- this.toolbar._showInline();
80
- }
81
- }
82
-
83
- if (this.editor.isSubBalloon) {
84
- this.subToolbar.hide();
85
- }
86
-
87
- CreateLineNumbers(fc);
88
-
89
- this.status._range = null;
90
- codeFrame.focus();
91
- dom.utils.addClass(this.editor.commandTargets.get('codeView'), 'active');
92
- dom.utils.addClass(wrapper, 'se-code-view-status');
93
- } else {
94
- if (!dom.check.isNonEditable(wysiwygFrame)) this._setCodeDataToEditor();
95
- wysiwygFrame.scrollTop = 0;
96
- codeWrapper.style.setProperty('display', 'none', 'important');
97
- wysiwygFrame.style.display = 'block';
98
-
99
- if (this.editor.frameOptions.get('height') === 'auto' && !this.options.get('hasCodeMirror')) fc.get('code').style.height = '0px';
100
-
101
- if (!fc.get('isFullScreen')) {
102
- this.editor._notHideToolbar = false;
103
- if (/balloon/.test(this.options.get('mode'))) {
104
- this.context.get('toolbar._arrow').style.display = '';
105
- this.editor.isInline = this.toolbar._isInline = false;
106
- this.editor.isBalloon = this.toolbar._isBalloon = true;
107
- this.eventManager._hideToolbar();
108
- }
109
- }
110
-
111
- this.editor._nativeFocus();
112
- dom.utils.removeClass(this.editor.commandTargets.get('codeView'), 'active');
113
-
114
- if (!dom.check.isNonEditable(wysiwygFrame)) {
115
- this.history.push(false);
116
- this.history.resetButtons(fc.get('key'), null);
117
- }
118
- dom.utils.removeClass(wrapper, 'se-code-view-status');
119
- }
120
-
121
- this.editor._checkPlaceholder(fc);
122
- dom.utils.setDisabled(this.editor._codeViewDisabledButtons, value);
123
-
124
- // document type
125
- if (fc.has('documentType-use-header')) {
126
- if (value) {
127
- fc.get('documentTypeInner').style.display = 'none';
128
- } else {
129
- fc.get('documentTypeInner').style.display = '';
130
- fc.get('documentType').reHeader();
131
- }
132
- }
133
-
134
- // user event
135
- this.triggerEvent('onToggleCodeView', { frameContext: fc, is: fc.get('isCodeView') });
136
- },
137
-
138
- /**
139
- * @this {ViewerThis}
140
- * @description Changes to full screen or default screen
141
- * @param {boolean=} value true/false, If undefined toggle the codeView mode.
142
- */
143
- fullScreen(value) {
144
- const fc = this.editor.frameContext;
145
- if (value === undefined) value = !fc.get('isFullScreen');
146
- if (value === fc.get('isFullScreen')) return;
147
-
148
- fc.set('isFullScreen', value);
149
- const topArea = fc.get('topArea');
150
- const toolbar = this.context.get('toolbar.main');
151
- const editorArea = fc.get('wrapper');
152
- const wysiwygFrame = fc.get('wysiwygFrame');
153
- const codeWrapper = fc.get('codeWrapper');
154
- const code = fc.get('code');
155
- const codeNumbers = fc.get('codeNumbers');
156
- const isCodeView = this.editor.frameContext.get('isCodeView');
157
- const arrow = this.context.get('toolbar._arrow');
158
-
159
- this.ui._offCurrentController();
160
- const wasToolbarHidden = toolbar.style.display === 'none' || (this.editor.isInline && !this.editor.toolbar._inlineToolbarAttr.isShow);
161
-
162
- if (value) {
163
- this._originCssText = topArea.style.cssText;
164
- this.editorAreaOriginCssText = editorArea.style.cssText;
165
- this.wysiwygOriginCssText = wysiwygFrame.style.cssText;
166
- this.codeWrapperOriginCssText = codeWrapper.style.cssText;
167
- this.codeOriginCssText = code.style.cssText;
168
- this.codeNumberOriginCssText = codeNumbers?.style.cssText;
169
- this.toolbarOriginCssText = toolbar.style.cssText;
170
- if (arrow) this.arrowOriginCssText = arrow.style.cssText;
171
-
172
- if (this.editor.isBalloon || this.editor.isInline) {
173
- if (arrow) arrow.style.display = 'none';
174
- this.fullScreenInline = this.editor.isInline;
175
- this.fullScreenBalloon = this.editor.isBalloon;
176
- this.editor.isInline = this.toolbar._isInline = false;
177
- this.editor.isBalloon = this.toolbar._isBalloon = false;
178
- }
179
-
180
- if (this.options.get('toolbar_container')) {
181
- this.toolbarParent = toolbar.parentElement;
182
- fc.get('container').insertBefore(toolbar, editorArea);
183
- }
184
-
185
- topArea.style.position = 'fixed';
186
- topArea.style.top = '0';
187
- topArea.style.left = '0';
188
- topArea.style.width = '100%';
189
- topArea.style.maxWidth = '100%';
190
- topArea.style.height = '100%';
191
- topArea.style.zIndex = '2147483646';
192
-
193
- if (fc.get('_stickyDummy').style.display !== 'none' && fc.get('_stickyDummy').style.display !== '') {
194
- this.fullScreenSticky = true;
195
- fc.get('_stickyDummy').style.display = 'none';
196
- dom.utils.removeClass(toolbar, 'se-toolbar-sticky');
197
- }
198
-
199
- this.bodyOverflow = this._d.body.style.overflow;
200
- this._d.body.style.overflow = 'hidden';
201
-
202
- // frame
203
- editorArea.style.cssText = toolbar.style.cssText = '';
204
- wysiwygFrame.style.cssText = (wysiwygFrame.style.cssText.match(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/) || [''])[0] + this.editor.frameOptions.get('_defaultStyles').editor + (isCodeView ? 'display: none;' : '');
205
-
206
- // code wrapper
207
- codeWrapper.style.cssText = (codeWrapper.style.cssText.match(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/) || [''])[0] + `display: ${!isCodeView ? 'none' : 'flex'} !important;`;
208
- codeWrapper.style.overflow = 'auto';
209
- codeWrapper.style.height = '100%';
210
-
211
- // code
212
- code.style.height = '';
213
-
214
- // toolbar, editor area
215
- toolbar.style.width = wysiwygFrame.style.height = '100%';
216
- toolbar.style.position = 'relative';
217
- toolbar.style.display = 'block';
218
-
219
- this.fullScreenInnerHeight = this._w.innerHeight - toolbar.offsetHeight;
220
- editorArea.style.height = this.fullScreenInnerHeight - (fc.has('statusbar') ? fc.get('statusbar').offsetHeight : 0) - this.options.get('fullScreenOffset') + 'px';
221
-
222
- if (this.editor.frameOptions.get('iframe') && this.editor.frameOptions.get('height') === 'auto') {
223
- editorArea.style.overflow = 'auto';
224
- this.editor._iframeAutoHeight(fc);
225
- }
226
-
227
- fc.get('topArea').style.marginTop = this.options.get('fullScreenOffset') + 'px';
228
-
229
- const reductionIcon = this.icons.reduction;
230
- this.editor.applyCommandTargets('fullScreen', (e) => {
231
- dom.utils.changeElement(e.firstElementChild, reductionIcon);
232
- dom.utils.addClass(e, 'active');
233
- });
234
- } else {
235
- // frame
236
- wysiwygFrame.style.cssText = this.wysiwygOriginCssText.replace(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/, '') + (isCodeView ? 'display: none;' : '');
237
-
238
- // code wrapper
239
- codeWrapper.style.cssText = this.codeWrapperOriginCssText.replace(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/, '') + `display: ${!isCodeView ? 'none' : 'flex'} !important;`;
240
-
241
- // code
242
- code.style.cssText = this.codeOriginCssText;
243
- if (codeNumbers) codeNumbers.style.cssText = this.codeNumberOriginCssText;
244
-
245
- // toolbar, editor area
246
- toolbar.style.cssText = this.toolbarOriginCssText;
247
- editorArea.style.cssText = this.editorAreaOriginCssText;
248
- topArea.style.cssText = this._originCssText;
249
- if (arrow) arrow.style.cssText = this.arrowOriginCssText;
250
- this._d.body.style.overflow = this.bodyOverflow;
251
-
252
- if (this.editor.frameOptions.get('height') === 'auto' && !this.options.get('hasCodeMirror')) this._codeViewAutoHeight(fc.get('code'), fc.get('codeNumbers'), true);
253
-
254
- if (this.toolbarParent) {
255
- this.toolbarParent.appendChild(toolbar);
256
- this.toolbarParent = null;
257
- }
258
-
259
- if (this.options.get('toolbar_sticky') > -1) {
260
- dom.utils.removeClass(toolbar, 'se-toolbar-sticky');
261
- }
262
-
263
- if (this.fullScreenSticky && !this.options.get('toolbar_container')) {
264
- this.fullScreenSticky = false;
265
- fc.get('_stickyDummy').style.display = 'block';
266
- dom.utils.addClass(toolbar, 'se-toolbar-sticky');
267
- }
268
-
269
- this.editor.isInline = this.toolbar._isInline = this.fullScreenInline;
270
- this.editor.isBalloon = this.toolbar._isBalloon = this.fullScreenBalloon;
271
- if (!fc.get('isCodeView')) {
272
- if (this.editor.isInline) this.editor.toolbar._showInline();
273
- else if (this.editor.isBalloon) this.editor.toolbar._showBalloon();
274
- }
275
-
276
- this.editor.toolbar._resetSticky();
277
- fc.get('topArea').style.marginTop = '';
278
-
279
- const expansionIcon = this.icons.expansion;
280
- this.editor.applyCommandTargets('fullScreen', (e) => {
281
- dom.utils.changeElement(e.firstElementChild, expansionIcon);
282
- dom.utils.removeClass(e, 'active');
283
- });
284
- }
285
-
286
- if (wasToolbarHidden && !fc.get('isCodeView')) this.editor.toolbar.hide();
287
-
288
- // user event
289
- this.triggerEvent('onToggleFullScreen', { frameContext: fc, is: fc.get('isFullScreen') });
290
- },
291
-
292
- /**
293
- * @this {ViewerThis}
294
- * @description Add or remove the class name of "body" so that the code block is visible
295
- * @param {boolean=} value true/false, If undefined toggle the codeView mode.
296
- */
297
- showBlocks(value) {
298
- const fc = this.editor.frameContext;
299
- if (value === undefined) value = !fc.get('isShowBlocks');
300
- fc.set('isShowBlocks', !!value);
301
-
302
- if (value) {
303
- dom.utils.addClass(fc.get('wysiwyg'), 'se-show-block');
304
- dom.utils.addClass(this.editor.commandTargets.get('showBlocks'), 'active');
305
- } else {
306
- dom.utils.removeClass(fc.get('wysiwyg'), 'se-show-block');
307
- dom.utils.removeClass(this.editor.commandTargets.get('showBlocks'), 'active');
308
- }
309
-
310
- this.editor._resourcesStateChange(fc);
311
- },
312
-
313
- /**
314
- * @private
315
- * @this {ViewerThis}
316
- * @description Set the active class to the button of the toolbar
317
- */
318
- _setButtonsActive() {
319
- const fc = this.editor.frameContext;
320
-
321
- // codeView
322
- if (fc.get('isCodeView')) {
323
- dom.utils.addClass(this.editor.commandTargets.get('codeView'), 'active');
324
- } else {
325
- dom.utils.removeClass(this.editor.commandTargets.get('codeView'), 'active');
326
- }
327
-
328
- // fullScreen
329
- if (fc.get('isFullScreen')) {
330
- const reductionIcon = this.icons.reduction;
331
- this.editor.applyCommandTargets('fullScreen', (e) => {
332
- dom.utils.changeElement(e.firstElementChild, reductionIcon);
333
- dom.utils.addClass(e, 'active');
334
- });
335
- } else {
336
- const expansionIcon = this.icons.expansion;
337
- this.editor.applyCommandTargets('fullScreen', (e) => {
338
- dom.utils.changeElement(e.firstElementChild, expansionIcon);
339
- dom.utils.removeClass(e, 'active');
340
- });
341
- }
342
-
343
- // showBlocks
344
- if (fc.get('isShowBlocks')) {
345
- dom.utils.addClass(this.editor.commandTargets.get('showBlocks'), 'active');
346
- } else {
347
- dom.utils.removeClass(this.editor.commandTargets.get('showBlocks'), 'active');
348
- }
349
- },
350
-
351
- /**
352
- * @this {ViewerThis}
353
- * @description Prints the current content of the editor.
354
- */
355
- print() {
356
- /** @type {HTMLIFrameElement} */
357
- const iframe = dom.utils.createElement('IFRAME', { style: 'display: none;' });
358
- this._d.body.appendChild(iframe);
359
-
360
- const innerPadding = this._w.getComputedStyle(this.editor.frameContext.get('wysiwyg')).padding;
361
- const contentHTML = this.options.get('printTemplate') ? this.options.get('printTemplate').replace(/\{\{\s*contents\s*\}\}/i, this.html.get()) : this.html.get();
362
- const printDocument = dom.query.getIframeDocument(iframe);
363
- const wDoc = this.editor.frameContext.get('_wd');
364
- const rtlClass = this.options.get('_rtl') ? ' se-rtl' : '';
365
- const pageCSS = /*html*/ `
366
- <style>
367
- @page {
368
- size: A4;
369
- margin: ${innerPadding};
370
- }
371
- </style>`;
372
-
373
- if (this.editor.frameOptions.get('iframe')) {
374
- const arrts = this.options.get('printClass')
375
- ? 'class="' + this.options.get('printClass') + rtlClass + '"'
376
- : this.editor.frameOptions.get('iframe_fullPage')
377
- ? dom.utils.getAttributesToString(wDoc.body, ['contenteditable'])
378
- : 'class="' + this.options.get('_editableClass') + rtlClass + '"';
379
-
380
- printDocument.write(/*html*/ `
381
- <!DOCTYPE html>
382
- <html>
383
- <head>
384
- ${wDoc.head.innerHTML}
385
- ${pageCSS}
386
- </head>
387
- <body ${arrts} style="padding: 0; padding-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0;">
388
- ${contentHTML}
389
- </body>
390
- </html>`);
391
- } else {
392
- const links = this._d.head.getElementsByTagName('link');
393
- const styles = this._d.head.getElementsByTagName('style');
394
- let linkHTML = '';
395
- for (let i = 0, len = links.length; i < len; i++) {
396
- linkHTML += links[i].outerHTML;
397
- }
398
- for (let i = 0, len = styles.length; i < len; i++) {
399
- linkHTML += styles[i].outerHTML;
400
- }
401
-
402
- printDocument.write(/*html*/ `
403
- <!DOCTYPE html>
404
- <html>
405
- <head>
406
- ${linkHTML}
407
- ${pageCSS}
408
- </head>
409
- <body class="${(this.options.get('printClass') || this.options.get('_editableClass')) + rtlClass}" style="padding: 0; padding-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0;">
410
- ${contentHTML}
411
- </body>
412
- </html>`);
413
- }
414
-
415
- this.ui.showLoading();
416
- this._w.setTimeout(() => {
417
- try {
418
- iframe.focus();
419
- // Edge, Chromium
420
- if (env.isEdge || env.isChromium || 'StyleMedia' in env._w) {
421
- try {
422
- iframe.contentWindow.document.execCommand('print', false, null);
423
- } catch (e) {
424
- console.warn('[SUNEDITOR.print.warn]', e);
425
- iframe.contentWindow.print();
426
- }
427
- } else {
428
- // Other browsers
429
- iframe.contentWindow.print();
430
- }
431
- } catch (error) {
432
- throw Error(`[SUNEDITOR.print.fail] error: ${error.message}`);
433
- } finally {
434
- this.ui.hideLoading();
435
- dom.utils.removeItem(iframe);
436
- }
437
- }, 1000);
438
- },
439
-
440
- /**
441
- * @this {ViewerThis}
442
- * @description Open the preview window.
443
- */
444
- preview() {
445
- this.menu.dropdownOff();
446
- this.menu.containerOff();
447
- this.ui._offCurrentController();
448
- this.ui._offCurrentModal();
449
-
450
- const contentHTML = this.options.get('previewTemplate') ? this.options.get('previewTemplate').replace(/\{\{\s*contents\s*\}\}/i, this.html.get({ withFrame: true })) : this.html.get({ withFrame: true });
451
- const windowObject = this._w.open('', '_blank');
452
- const wDoc = this.editor.frameContext.get('_wd');
453
- const rtlClass = this.options.get('_rtl') ? ' se-rtl' : '';
454
-
455
- if (this.editor.frameOptions.get('iframe')) {
456
- const arrts = this.options.get('printClass')
457
- ? 'class="' + this.options.get('printClass') + rtlClass + '"'
458
- : this.editor.frameOptions.get('iframe_fullPage')
459
- ? dom.utils.getAttributesToString(wDoc.body, ['contenteditable'])
460
- : 'class="' + this.options.get('_editableClass') + rtlClass + '"';
461
-
462
- windowObject.document.write(/*html*/ `<!DOCTYPE html>
463
- <html>
464
- <head>
465
- ${wDoc.head.innerHTML}
466
- <style>
467
- body {overflow:auto !important; height:auto !important;}
468
- </style>
469
- </head>
470
- <body ${arrts}>
471
- ${contentHTML}
472
- </body>
473
- </html>`);
474
- } else {
475
- const links = this._d.head.getElementsByTagName('link');
476
- const styles = this._d.head.getElementsByTagName('style');
477
- let linkHTML = '';
478
- for (let i = 0, len = links.length; i < len; i++) {
479
- linkHTML += links[i].outerHTML;
480
- }
481
- for (let i = 0, len = styles.length; i < len; i++) {
482
- linkHTML += styles[i].outerHTML;
483
- }
484
-
485
- windowObject.document.write(/*html*/ `<!DOCTYPE html>
486
- <html>
487
- <head>
488
- <meta charset="utf-8" />
489
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
490
- <title>${this.lang.preview}</title>
491
- ${linkHTML}
492
- </head>
493
- <body class="${(this.options.get('printClass') ? this.options.get('printClass') : this.options.get('_editableClass')) + rtlClass}" style="height:auto">
494
- ${contentHTML}
495
- </body>
496
- </html>`);
497
- }
498
- },
499
-
500
- /**
501
- * @private
502
- * @this {ViewerThis}
503
- * @description Resets the full-screen height of the editor.
504
- * - Updates the editor's height dynamically when in full-screen mode.
505
- */
506
- _resetFullScreenHeight() {
507
- if (this.editor.frameContext.get('isFullScreen')) {
508
- this.fullScreenInnerHeight +=
509
- this._w.innerHeight - this.context.get('toolbar.main').offsetHeight - (this.editor.frameContext.has('statusbar') ? this.editor.frameContext.get('statusbar').offsetHeight : 0) - this.fullScreenInnerHeight;
510
- this.editor.frameContext.get('wrapper').style.height = this.fullScreenInnerHeight + 'px';
511
- return true;
512
- }
513
- },
514
-
515
- /**
516
- * @private
517
- * @this {ViewerThis}
518
- * @description Run CodeMirror Editor
519
- * @param {"set"|"get"|"readonly"|"refresh"} key method key
520
- * @param {*} value CodeMirror params
521
- * @param {string|undefined} rootKey Root key
522
- */
523
- _codeMirrorEditor(key, value, rootKey) {
524
- const fo = rootKey ? this.frameRoots.get(rootKey).get('options') : this.editor.frameOptions;
525
- switch (key) {
526
- case 'set':
527
- if (fo.has('codeMirror5Editor')) {
528
- fo.get('codeMirror5Editor').getDoc().setValue(value);
529
- } else if (fo.has('codeMirror6Editor')) {
530
- fo.get('codeMirror6Editor').dispatch({
531
- changes: { from: 0, to: fo.get('codeMirror6Editor').state.doc.length, insert: value }
532
- });
533
- }
534
- break;
535
- case 'get':
536
- if (fo.has('codeMirror5Editor')) {
537
- return fo.get('codeMirror5Editor').getDoc().getValue();
538
- } else if (fo.has('codeMirror6Editor')) {
539
- return fo.get('codeMirror6Editor').state.doc.toString();
540
- }
541
- break;
542
- case 'readonly':
543
- if (fo.has('codeMirror5Editor')) {
544
- fo.get('codeMirror5Editor').setOption('readOnly', value);
545
- } else if (fo.has('codeMirror6Editor')) {
546
- if (!value) fo.get('codeMirror6Editor').contentDOM.setAttribute('contenteditable', true);
547
- else fo.get('codeMirror6Editor').contentDOM.removeAttribute('contenteditable');
548
- }
549
- break;
550
- case 'refresh':
551
- if (fo.has('codeMirror5Editor')) {
552
- fo.get('codeMirror5Editor').refresh();
553
- }
554
- break;
555
- }
556
- },
557
-
558
- /**
559
- * @private
560
- * @this {ViewerThis}
561
- * @description Set method in the code view area
562
- * @param {string} value HTML string
563
- */
564
- _setCodeView(value) {
565
- if (this.options.get('hasCodeMirror')) {
566
- this._codeMirrorEditor('set', value, null);
567
- } else {
568
- this.editor.frameContext.get('code').value = value;
569
- }
570
- },
571
-
572
- /**
573
- * @private
574
- * @this {ViewerThis}
575
- * @description Get method in the code view area
576
- */
577
- _getCodeView() {
578
- if (this.options.get('hasCodeMirror')) {
579
- return this._codeMirrorEditor('get', null, null);
580
- } else {
581
- return this.editor.frameContext.get('code').value;
582
- }
583
- },
584
-
585
- /**
586
- * @private
587
- * @this {ViewerThis}
588
- * @description Convert the data of the code view and put it in the WYSIWYG area.
589
- */
590
- _setCodeDataToEditor() {
591
- const code_html = this._getCodeView();
592
-
593
- if (this.editor.frameOptions.get('iframe_fullPage')) {
594
- const wDoc = this.editor.frameContext.get('_wd');
595
- const parseDocument = new DOMParser().parseFromString(code_html, 'text/html');
596
-
597
- if (!this.options.get('__allowedScriptTag')) {
598
- const headChildren = parseDocument.head.children;
599
- for (let i = 0, len = headChildren.length; i < len; i++) {
600
- if (/^script$/i.test(headChildren[i].tagName)) {
601
- parseDocument.head.removeChild(headChildren[i]);
602
- i--;
603
- len--;
604
- }
605
- }
606
- }
607
-
608
- let headers = parseDocument.head.innerHTML;
609
- if (!parseDocument.head.querySelector('link[rel="stylesheet"]') || (this.editor.frameOptions.get('height') === 'auto' && !parseDocument.head.querySelector('style'))) {
610
- headers += converter._setIframeStyleLinks(this.editor.frameOptions.get('iframe_cssFileName')) + converter._setAutoHeightStyle(this.editor.frameOptions.get('height'));
611
- }
612
-
613
- wDoc.head.innerHTML = headers;
614
- wDoc.body.innerHTML = this.html.clean(parseDocument.body.innerHTML, { forceFormat: true, whitelist: null, blacklist: null, _freeCodeViewMode: this.options.get('freeCodeViewMode') });
615
-
616
- const attrs = parseDocument.body.attributes;
617
- for (let i = 0, len = attrs.length; i < len; i++) {
618
- if (attrs[i].name === 'contenteditable') continue;
619
- wDoc.body.setAttribute(attrs[i].name, attrs[i].value);
620
- }
621
- if (!dom.utils.hasClass(wDoc.body, 'sun-editor-editable')) {
622
- const editableClasses = this.options.get('_editableClass').split(' ');
623
- for (let i = 0; i < editableClasses.length; i++) {
624
- dom.utils.addClass(wDoc.body, this.options.get('_editableClass')[i]);
625
- }
626
- }
627
- } else {
628
- this.editor.frameContext.get('wysiwyg').innerHTML =
629
- code_html.length > 0
630
- ? this.html.clean(code_html, { forceFormat: true, whitelist: null, blacklist: null, _freeCodeViewMode: this.options.get('freeCodeViewMode') })
631
- : '<' + this.options.get('defaultLine') + '><br></' + this.options.get('defaultLine') + '>';
632
- }
633
- },
634
-
635
- /**
636
- * @private
637
- * @this {ViewerThis}
638
- * @description Convert the data of the WYSIWYG area and put it in the code view area.
639
- */
640
- _setEditorDataToCodeView() {
641
- const codeContent = this.html._convertToCode(this.editor.frameContext.get('wysiwyg'), false);
642
- let codeValue = '';
643
-
644
- if (this.editor.frameOptions.get('iframe_fullPage')) {
645
- const attrs = dom.utils.getAttributesToString(this.editor.frameContext.get('_wd').body, null);
646
- codeValue = '<!DOCTYPE html>\n<html>\n' + this.editor.frameContext.get('_wd').head.outerHTML.replace(/>(?!\n)/g, '>\n') + '<body ' + attrs + '>\n' + codeContent + '</body>\n</html>';
647
- } else {
648
- codeValue = codeContent;
649
- }
650
-
651
- this._setCodeView(codeValue);
652
- },
653
-
654
- /**
655
- * @private
656
- * @this {ViewerThis}
657
- * @description Adjusts the height of the code view area.
658
- * - Ensures the code block auto-resizes based on its content.
659
- * @param {HTMLElement} code - Code area
660
- * @param {HTMLTextAreaElement} codeNumbers - Code numbers area
661
- * @param {boolean} isAuto - Auto height option
662
- */
663
- _codeViewAutoHeight(code, codeNumbers, isAuto) {
664
- if (isAuto) code.style.height = code.scrollHeight + 'px';
665
- this._updateLineNumbers(codeNumbers, code);
666
- },
667
-
668
- /**
669
- * @private
670
- * @this {ViewerThis}
671
- * @description Updates the line numbers for the code editor.
672
- * - Dynamically adjusts line numbers as content grows.
673
- * @param {HTMLTextAreaElement} lineNumbers - Code numbers area
674
- * @param {HTMLElement} code - Code area
675
- */
676
- _updateLineNumbers(lineNumbers, code) {
677
- if (!lineNumbers) return;
678
-
679
- const lineHeight = GetLineHeight(lineNumbers);
680
- const numberOfLinesNeeded = Math.ceil(code.scrollHeight / lineHeight);
681
-
682
- const currentLineCount = (lineNumbers.value.match(/\n/g) || []).length;
683
- if (numberOfLinesNeeded > currentLineCount) {
684
- let n = '';
685
- for (let i = currentLineCount + 1; i <= numberOfLinesNeeded; i++) {
686
- n += `${i}\n`;
687
- }
688
- lineNumbers.value += n;
689
- }
690
- },
691
-
692
- /**
693
- * @private
694
- * @this {HTMLElement} Code numbers area
695
- * @description Synchronizes scrolling of line numbers with the code editor.
696
- * - Keeps the line numbers aligned with the text.
697
- * @param {HTMLTextAreaElement} codeNumbers - Code numbers textarea
698
- */
699
- _scrollLineNumbers(codeNumbers) {
700
- codeNumbers.scrollTop = this.scrollTop;
701
- codeNumbers.scrollLeft = this.scrollLeft;
702
- },
703
-
704
- constructor: Viewer
705
- };
706
-
707
- /**
708
- * @private
709
- * @description Create line numbers for the code view area
710
- * @param {__se__FrameContext} fc - Frame context
711
- */
712
- function CreateLineNumbers(fc) {
713
- const codeNumbers = fc.get('codeNumbers');
714
- if (!codeNumbers) return;
715
-
716
- const lineHeight = GetLineHeight(codeNumbers);
717
- const numberOfLines = fc.get('code').scrollHeight / lineHeight;
718
-
719
- let n = '';
720
- for (let i = 1; i <= numberOfLines; i++) {
721
- n += `${i}\n`;
722
- }
723
-
724
- const { padding, margin } = env._w.getComputedStyle(fc.get('code'));
725
- codeNumbers.value = n;
726
- codeNumbers.style.padding = padding || '';
727
- codeNumbers.style.margin = margin || '';
728
- }
729
-
730
- /**
731
- * @private
732
- * @description Get the line height of the textarea
733
- * @param {HTMLTextAreaElement} textarea Textarea element
734
- * @returns {number}
735
- */
736
- function GetLineHeight(textarea) {
737
- const lineHeight = env._w.getComputedStyle(textarea).lineHeight;
738
- let lineHeightMatch;
739
-
740
- if (!numbers.is(lineHeight)) {
741
- const fontSize = env._w.getComputedStyle(textarea).fontSize;
742
- lineHeightMatch = numbers.get(fontSize) * 1.2;
743
- } else {
744
- lineHeightMatch = numbers.get(lineHeight);
745
- }
746
-
747
- return lineHeightMatch;
748
- }
749
-
750
- export default Viewer;
1
+ /**
2
+ * @fileoverview Viewer class
3
+ */
4
+
5
+ import CoreInjector from '../../editorInjector/_core';
6
+ import { dom, env, converter, numbers } from '../../helper';
7
+
8
+ /**
9
+ * @typedef {Omit<Viewer & Partial<__se__EditorInjector>, 'viewer'>} ViewerThis
10
+ */
11
+
12
+ /**
13
+ * @constructor
14
+ * @this {ViewerThis}
15
+ * @description Viewer(codeView, fullScreen, showBlocks) class
16
+ * @param {__se__EditorCore} editor - The root editor instance
17
+ */
18
+ function Viewer(editor) {
19
+ CoreInjector.call(this, editor);
20
+
21
+ // members
22
+ this.bodyOverflow = '';
23
+ this.editorAreaOriginCssText = '';
24
+ this.wysiwygOriginCssText = '';
25
+ this.codeWrapperOriginCssText = '';
26
+ this.codeOriginCssText = '';
27
+ this.codeNumberOriginCssText = '';
28
+ this.toolbarOriginCssText = '';
29
+ this.arrowOriginCssText = '';
30
+ this.fullScreenInnerHeight = 0;
31
+ this.fullScreenSticky = false;
32
+ this.fullScreenBalloon = false;
33
+ this.fullScreenInline = false;
34
+ this.toolbarParent = null;
35
+ }
36
+
37
+ Viewer.prototype = {
38
+ /**
39
+ * @this {ViewerThis}
40
+ * @description Changes to code view or wysiwyg view
41
+ * @param {boolean=} value true/false, If undefined toggle the codeView mode.
42
+ */
43
+ codeView(value) {
44
+ const fc = this.editor.frameContext;
45
+ if (value === undefined) value = !fc.get('isCodeView');
46
+ if (value === fc.get('isCodeView')) return;
47
+
48
+ fc.set('isCodeView', value);
49
+ this.ui._offCurrentController();
50
+ this.ui._offCurrentModal();
51
+
52
+ const codeWrapper = fc.get('codeWrapper');
53
+ const codeFrame = fc.get('code');
54
+ const wysiwygFrame = fc.get('wysiwygFrame');
55
+ const wrapper = fc.get('wrapper');
56
+
57
+ if (value) {
58
+ this._setEditorDataToCodeView();
59
+ codeWrapper.style.setProperty('display', 'flex', 'important');
60
+ wysiwygFrame.style.display = 'none';
61
+
62
+ if (fc.get('isFullScreen')) {
63
+ codeFrame.style.height = '100%';
64
+ } else if (this.editor.frameOptions.get('height') === 'auto' && !this.options.get('hasCodeMirror')) {
65
+ codeFrame.style.height = codeFrame.scrollHeight > 0 ? codeFrame.scrollHeight + 'px' : 'auto';
66
+ }
67
+
68
+ if (this.options.get('hasCodeMirror')) {
69
+ this._codeMirrorEditor('refresh', null, null);
70
+ }
71
+
72
+ if (!fc.get('isFullScreen')) {
73
+ this.editor._notHideToolbar = true;
74
+ if (this.editor.isBalloon) {
75
+ this.context.get('toolbar._arrow').style.display = 'none';
76
+ this.context.get('toolbar.main').style.left = '';
77
+ this.editor.isInline = this.toolbar._isInline = true;
78
+ this.editor.isBalloon = this.toolbar._isBalloon = false;
79
+ this.toolbar._showInline();
80
+ }
81
+ }
82
+
83
+ if (this.editor.isSubBalloon) {
84
+ this.subToolbar.hide();
85
+ }
86
+
87
+ CreateLineNumbers(fc);
88
+
89
+ this.status._range = null;
90
+ codeFrame.focus();
91
+ dom.utils.addClass(this.editor.commandTargets.get('codeView'), 'active');
92
+ dom.utils.addClass(wrapper, 'se-code-view-status');
93
+ } else {
94
+ if (!dom.check.isNonEditable(wysiwygFrame)) this._setCodeDataToEditor();
95
+ wysiwygFrame.scrollTop = 0;
96
+ codeWrapper.style.setProperty('display', 'none', 'important');
97
+ wysiwygFrame.style.display = 'block';
98
+
99
+ if (this.editor.frameOptions.get('height') === 'auto' && !this.options.get('hasCodeMirror')) fc.get('code').style.height = '0px';
100
+
101
+ if (!fc.get('isFullScreen')) {
102
+ this.editor._notHideToolbar = false;
103
+ if (/balloon/.test(this.options.get('mode'))) {
104
+ this.context.get('toolbar._arrow').style.display = '';
105
+ this.editor.isInline = this.toolbar._isInline = false;
106
+ this.editor.isBalloon = this.toolbar._isBalloon = true;
107
+ this.eventManager._hideToolbar();
108
+ }
109
+ }
110
+
111
+ this.editor._nativeFocus();
112
+ dom.utils.removeClass(this.editor.commandTargets.get('codeView'), 'active');
113
+
114
+ if (!dom.check.isNonEditable(wysiwygFrame)) {
115
+ this.history.push(false);
116
+ this.history.resetButtons(fc.get('key'), null);
117
+ }
118
+ dom.utils.removeClass(wrapper, 'se-code-view-status');
119
+ }
120
+
121
+ this.editor._checkPlaceholder(fc);
122
+ dom.utils.setDisabled(this.editor._codeViewDisabledButtons, value);
123
+
124
+ // document type
125
+ if (fc.has('documentType-use-header')) {
126
+ if (value) {
127
+ fc.get('documentTypeInner').style.display = 'none';
128
+ } else {
129
+ fc.get('documentTypeInner').style.display = '';
130
+ fc.get('documentType').reHeader();
131
+ }
132
+ }
133
+
134
+ // user event
135
+ this.triggerEvent('onToggleCodeView', { frameContext: fc, is: fc.get('isCodeView') });
136
+ },
137
+
138
+ /**
139
+ * @this {ViewerThis}
140
+ * @description Changes to full screen or default screen
141
+ * @param {boolean=} value true/false, If undefined toggle the codeView mode.
142
+ */
143
+ fullScreen(value) {
144
+ const fc = this.editor.frameContext;
145
+ if (value === undefined) value = !fc.get('isFullScreen');
146
+ if (value === fc.get('isFullScreen')) return;
147
+
148
+ fc.set('isFullScreen', value);
149
+ const topArea = fc.get('topArea');
150
+ const toolbar = this.context.get('toolbar.main');
151
+ const editorArea = fc.get('wrapper');
152
+ const wysiwygFrame = fc.get('wysiwygFrame');
153
+ const codeWrapper = fc.get('codeWrapper');
154
+ const code = fc.get('code');
155
+ const codeNumbers = fc.get('codeNumbers');
156
+ const isCodeView = this.editor.frameContext.get('isCodeView');
157
+ const arrow = this.context.get('toolbar._arrow');
158
+
159
+ this.ui._offCurrentController();
160
+ const wasToolbarHidden = toolbar.style.display === 'none' || (this.editor.isInline && !this.editor.toolbar._inlineToolbarAttr.isShow);
161
+
162
+ if (value) {
163
+ this._originCssText = topArea.style.cssText;
164
+ this.editorAreaOriginCssText = editorArea.style.cssText;
165
+ this.wysiwygOriginCssText = wysiwygFrame.style.cssText;
166
+ this.codeWrapperOriginCssText = codeWrapper.style.cssText;
167
+ this.codeOriginCssText = code.style.cssText;
168
+ this.codeNumberOriginCssText = codeNumbers?.style.cssText;
169
+ this.toolbarOriginCssText = toolbar.style.cssText;
170
+ if (arrow) this.arrowOriginCssText = arrow.style.cssText;
171
+
172
+ if (this.editor.isBalloon || this.editor.isInline) {
173
+ if (arrow) arrow.style.display = 'none';
174
+ this.fullScreenInline = this.editor.isInline;
175
+ this.fullScreenBalloon = this.editor.isBalloon;
176
+ this.editor.isInline = this.toolbar._isInline = false;
177
+ this.editor.isBalloon = this.toolbar._isBalloon = false;
178
+ }
179
+
180
+ if (this.options.get('toolbar_container')) {
181
+ this.toolbarParent = toolbar.parentElement;
182
+ fc.get('container').insertBefore(toolbar, editorArea);
183
+ }
184
+
185
+ topArea.style.position = 'fixed';
186
+ topArea.style.top = '0';
187
+ topArea.style.left = '0';
188
+ topArea.style.width = '100%';
189
+ topArea.style.maxWidth = '100%';
190
+ topArea.style.height = '100%';
191
+ topArea.style.zIndex = '2147483639';
192
+
193
+ if (fc.get('_stickyDummy').style.display !== 'none' && fc.get('_stickyDummy').style.display !== '') {
194
+ this.fullScreenSticky = true;
195
+ fc.get('_stickyDummy').style.display = 'none';
196
+ dom.utils.removeClass(toolbar, 'se-toolbar-sticky');
197
+ }
198
+
199
+ this.bodyOverflow = this._d.body.style.overflow;
200
+ this._d.body.style.overflow = 'hidden';
201
+
202
+ // frame
203
+ editorArea.style.cssText = toolbar.style.cssText = '';
204
+ wysiwygFrame.style.cssText = (wysiwygFrame.style.cssText.match(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/) || [''])[0] + this.editor.frameOptions.get('_defaultStyles').editor + (isCodeView ? 'display: none;' : '');
205
+
206
+ // code wrapper
207
+ codeWrapper.style.cssText = (codeWrapper.style.cssText.match(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/) || [''])[0] + `display: ${!isCodeView ? 'none' : 'flex'} !important;`;
208
+ codeWrapper.style.overflow = 'auto';
209
+ codeWrapper.style.height = '100%';
210
+
211
+ // code
212
+ code.style.height = '';
213
+
214
+ // toolbar, editor area
215
+ toolbar.style.width = wysiwygFrame.style.height = '100%';
216
+ toolbar.style.position = 'relative';
217
+ toolbar.style.display = 'block';
218
+
219
+ this.fullScreenInnerHeight = this._w.innerHeight - toolbar.offsetHeight;
220
+ editorArea.style.height = this.fullScreenInnerHeight - (fc.has('statusbar') ? fc.get('statusbar').offsetHeight : 0) - this.options.get('fullScreenOffset') + 'px';
221
+
222
+ if (this.editor.frameOptions.get('iframe') && this.editor.frameOptions.get('height') === 'auto') {
223
+ editorArea.style.overflow = 'auto';
224
+ this.editor._iframeAutoHeight(fc);
225
+ }
226
+
227
+ fc.get('topArea').style.marginTop = this.options.get('fullScreenOffset') + 'px';
228
+
229
+ const reductionIcon = this.icons.reduction;
230
+ this.editor.applyCommandTargets('fullScreen', (e) => {
231
+ dom.utils.changeElement(e.firstElementChild, reductionIcon);
232
+ dom.utils.addClass(e, 'active');
233
+ });
234
+ } else {
235
+ // frame
236
+ wysiwygFrame.style.cssText = this.wysiwygOriginCssText.replace(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/, '') + (isCodeView ? 'display: none;' : '');
237
+
238
+ // code wrapper
239
+ codeWrapper.style.cssText = this.codeWrapperOriginCssText.replace(/\s?display(\s+)?:(\s+)?[a-zA-Z]+;/, '') + `display: ${!isCodeView ? 'none' : 'flex'} !important;`;
240
+
241
+ // code
242
+ code.style.cssText = this.codeOriginCssText;
243
+ if (codeNumbers) codeNumbers.style.cssText = this.codeNumberOriginCssText;
244
+
245
+ // toolbar, editor area
246
+ toolbar.style.cssText = this.toolbarOriginCssText;
247
+ editorArea.style.cssText = this.editorAreaOriginCssText;
248
+ topArea.style.cssText = this._originCssText;
249
+ if (arrow) arrow.style.cssText = this.arrowOriginCssText;
250
+ this._d.body.style.overflow = this.bodyOverflow;
251
+
252
+ if (this.editor.frameOptions.get('height') === 'auto' && !this.options.get('hasCodeMirror')) this._codeViewAutoHeight(fc.get('code'), fc.get('codeNumbers'), true);
253
+
254
+ if (this.toolbarParent) {
255
+ this.toolbarParent.appendChild(toolbar);
256
+ this.toolbarParent = null;
257
+ }
258
+
259
+ if (this.options.get('toolbar_sticky') > -1) {
260
+ dom.utils.removeClass(toolbar, 'se-toolbar-sticky');
261
+ }
262
+
263
+ if (this.fullScreenSticky && !this.options.get('toolbar_container')) {
264
+ this.fullScreenSticky = false;
265
+ fc.get('_stickyDummy').style.display = 'block';
266
+ dom.utils.addClass(toolbar, 'se-toolbar-sticky');
267
+ }
268
+
269
+ this.editor.isInline = this.toolbar._isInline = this.fullScreenInline;
270
+ this.editor.isBalloon = this.toolbar._isBalloon = this.fullScreenBalloon;
271
+ if (!fc.get('isCodeView')) {
272
+ if (this.editor.isInline) this.editor.toolbar._showInline();
273
+ else if (this.editor.isBalloon) this.editor.toolbar._showBalloon();
274
+ }
275
+
276
+ this.editor.toolbar._resetSticky();
277
+ fc.get('topArea').style.marginTop = '';
278
+
279
+ const expansionIcon = this.icons.expansion;
280
+ this.editor.applyCommandTargets('fullScreen', (e) => {
281
+ dom.utils.changeElement(e.firstElementChild, expansionIcon);
282
+ dom.utils.removeClass(e, 'active');
283
+ });
284
+ }
285
+
286
+ if (wasToolbarHidden && !fc.get('isCodeView')) this.editor.toolbar.hide();
287
+
288
+ // user event
289
+ this.triggerEvent('onToggleFullScreen', { frameContext: fc, is: fc.get('isFullScreen') });
290
+ },
291
+
292
+ /**
293
+ * @this {ViewerThis}
294
+ * @description Add or remove the class name of "body" so that the code block is visible
295
+ * @param {boolean=} value true/false, If undefined toggle the codeView mode.
296
+ */
297
+ showBlocks(value) {
298
+ const fc = this.editor.frameContext;
299
+ if (value === undefined) value = !fc.get('isShowBlocks');
300
+ fc.set('isShowBlocks', !!value);
301
+
302
+ if (value) {
303
+ dom.utils.addClass(fc.get('wysiwyg'), 'se-show-block');
304
+ dom.utils.addClass(this.editor.commandTargets.get('showBlocks'), 'active');
305
+ } else {
306
+ dom.utils.removeClass(fc.get('wysiwyg'), 'se-show-block');
307
+ dom.utils.removeClass(this.editor.commandTargets.get('showBlocks'), 'active');
308
+ }
309
+
310
+ this.editor._resourcesStateChange(fc);
311
+ },
312
+
313
+ /**
314
+ * @private
315
+ * @this {ViewerThis}
316
+ * @description Set the active class to the button of the toolbar
317
+ */
318
+ _setButtonsActive() {
319
+ const fc = this.editor.frameContext;
320
+
321
+ // codeView
322
+ if (fc.get('isCodeView')) {
323
+ dom.utils.addClass(this.editor.commandTargets.get('codeView'), 'active');
324
+ } else {
325
+ dom.utils.removeClass(this.editor.commandTargets.get('codeView'), 'active');
326
+ }
327
+
328
+ // fullScreen
329
+ if (fc.get('isFullScreen')) {
330
+ const reductionIcon = this.icons.reduction;
331
+ this.editor.applyCommandTargets('fullScreen', (e) => {
332
+ dom.utils.changeElement(e.firstElementChild, reductionIcon);
333
+ dom.utils.addClass(e, 'active');
334
+ });
335
+ } else {
336
+ const expansionIcon = this.icons.expansion;
337
+ this.editor.applyCommandTargets('fullScreen', (e) => {
338
+ dom.utils.changeElement(e.firstElementChild, expansionIcon);
339
+ dom.utils.removeClass(e, 'active');
340
+ });
341
+ }
342
+
343
+ // showBlocks
344
+ if (fc.get('isShowBlocks')) {
345
+ dom.utils.addClass(this.editor.commandTargets.get('showBlocks'), 'active');
346
+ } else {
347
+ dom.utils.removeClass(this.editor.commandTargets.get('showBlocks'), 'active');
348
+ }
349
+ },
350
+
351
+ /**
352
+ * @this {ViewerThis}
353
+ * @description Prints the current content of the editor.
354
+ */
355
+ print() {
356
+ /** @type {HTMLIFrameElement} */
357
+ const iframe = dom.utils.createElement('IFRAME', { style: 'display: none;' });
358
+ this._d.body.appendChild(iframe);
359
+
360
+ const innerPadding = this._w.getComputedStyle(this.editor.frameContext.get('wysiwyg')).padding;
361
+ const contentHTML = this.options.get('printTemplate') ? this.options.get('printTemplate').replace(/\{\{\s*contents\s*\}\}/i, this.html.get()) : this.html.get();
362
+ const printDocument = dom.query.getIframeDocument(iframe);
363
+ const wDoc = this.editor.frameContext.get('_wd');
364
+ const rtlClass = this.options.get('_rtl') ? ' se-rtl' : '';
365
+ const pageCSS = /*html*/ `
366
+ <style>
367
+ @page {
368
+ size: A4;
369
+ margin: ${innerPadding};
370
+ }
371
+ </style>`;
372
+
373
+ if (this.editor.frameOptions.get('iframe')) {
374
+ const arrts = this.options.get('printClass')
375
+ ? 'class="' + this.options.get('printClass') + rtlClass + '"'
376
+ : this.editor.frameOptions.get('iframe_fullPage')
377
+ ? dom.utils.getAttributesToString(wDoc.body, ['contenteditable'])
378
+ : 'class="' + this.options.get('_editableClass') + rtlClass + '"';
379
+
380
+ printDocument.write(/*html*/ `
381
+ <!DOCTYPE html>
382
+ <html>
383
+ <head>
384
+ ${wDoc.head.innerHTML}
385
+ ${pageCSS}
386
+ </head>
387
+ <body ${arrts} style="padding: 0; padding-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0;">
388
+ ${contentHTML}
389
+ </body>
390
+ </html>`);
391
+ } else {
392
+ const links = this._d.head.getElementsByTagName('link');
393
+ const styles = this._d.head.getElementsByTagName('style');
394
+ let linkHTML = '';
395
+ for (let i = 0, len = links.length; i < len; i++) {
396
+ linkHTML += links[i].outerHTML;
397
+ }
398
+ for (let i = 0, len = styles.length; i < len; i++) {
399
+ linkHTML += styles[i].outerHTML;
400
+ }
401
+
402
+ printDocument.write(/*html*/ `
403
+ <!DOCTYPE html>
404
+ <html>
405
+ <head>
406
+ ${linkHTML}
407
+ ${pageCSS}
408
+ </head>
409
+ <body class="${(this.options.get('printClass') || this.options.get('_editableClass')) + rtlClass}" style="padding: 0; padding-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0;">
410
+ ${contentHTML}
411
+ </body>
412
+ </html>`);
413
+ }
414
+
415
+ this.ui.showLoading();
416
+ this._w.setTimeout(() => {
417
+ try {
418
+ iframe.focus();
419
+ // Edge, Chromium
420
+ if (env.isEdge || env.isChromium || 'StyleMedia' in env._w) {
421
+ try {
422
+ iframe.contentWindow.document.execCommand('print', false, null);
423
+ } catch (e) {
424
+ console.warn('[SUNEDITOR.print.warn]', e);
425
+ iframe.contentWindow.print();
426
+ }
427
+ } else {
428
+ // Other browsers
429
+ iframe.contentWindow.print();
430
+ }
431
+ } catch (error) {
432
+ throw Error(`[SUNEDITOR.print.fail] error: ${error.message}`);
433
+ } finally {
434
+ this.ui.hideLoading();
435
+ dom.utils.removeItem(iframe);
436
+ }
437
+ }, 1000);
438
+ },
439
+
440
+ /**
441
+ * @this {ViewerThis}
442
+ * @description Open the preview window.
443
+ */
444
+ preview() {
445
+ this.menu.dropdownOff();
446
+ this.menu.containerOff();
447
+ this.ui._offCurrentController();
448
+ this.ui._offCurrentModal();
449
+
450
+ const contentHTML = this.options.get('previewTemplate') ? this.options.get('previewTemplate').replace(/\{\{\s*contents\s*\}\}/i, this.html.get({ withFrame: true })) : this.html.get({ withFrame: true });
451
+ const windowObject = this._w.open('', '_blank');
452
+ const wDoc = this.editor.frameContext.get('_wd');
453
+ const rtlClass = this.options.get('_rtl') ? ' se-rtl' : '';
454
+
455
+ if (this.editor.frameOptions.get('iframe')) {
456
+ const arrts = this.options.get('printClass')
457
+ ? 'class="' + this.options.get('printClass') + rtlClass + '"'
458
+ : this.editor.frameOptions.get('iframe_fullPage')
459
+ ? dom.utils.getAttributesToString(wDoc.body, ['contenteditable'])
460
+ : 'class="' + this.options.get('_editableClass') + rtlClass + '"';
461
+
462
+ windowObject.document.write(/*html*/ `<!DOCTYPE html>
463
+ <html>
464
+ <head>
465
+ ${wDoc.head.innerHTML}
466
+ <style>
467
+ body {overflow:auto !important; height:auto !important;}
468
+ </style>
469
+ </head>
470
+ <body ${arrts}>
471
+ ${contentHTML}
472
+ </body>
473
+ </html>`);
474
+ } else {
475
+ const links = this._d.head.getElementsByTagName('link');
476
+ const styles = this._d.head.getElementsByTagName('style');
477
+ let linkHTML = '';
478
+ for (let i = 0, len = links.length; i < len; i++) {
479
+ linkHTML += links[i].outerHTML;
480
+ }
481
+ for (let i = 0, len = styles.length; i < len; i++) {
482
+ linkHTML += styles[i].outerHTML;
483
+ }
484
+
485
+ windowObject.document.write(/*html*/ `<!DOCTYPE html>
486
+ <html>
487
+ <head>
488
+ <meta charset="utf-8" />
489
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
490
+ <title>${this.lang.preview}</title>
491
+ ${linkHTML}
492
+ </head>
493
+ <body class="${(this.options.get('printClass') ? this.options.get('printClass') : this.options.get('_editableClass')) + rtlClass}" style="height:auto">
494
+ ${contentHTML}
495
+ </body>
496
+ </html>`);
497
+ }
498
+ },
499
+
500
+ /**
501
+ * @private
502
+ * @this {ViewerThis}
503
+ * @description Resets the full-screen height of the editor.
504
+ * - Updates the editor's height dynamically when in full-screen mode.
505
+ */
506
+ _resetFullScreenHeight() {
507
+ if (this.editor.frameContext.get('isFullScreen')) {
508
+ this.fullScreenInnerHeight +=
509
+ this._w.innerHeight - this.context.get('toolbar.main').offsetHeight - (this.editor.frameContext.has('statusbar') ? this.editor.frameContext.get('statusbar').offsetHeight : 0) - this.fullScreenInnerHeight;
510
+ this.editor.frameContext.get('wrapper').style.height = this.fullScreenInnerHeight + 'px';
511
+ return true;
512
+ }
513
+ },
514
+
515
+ /**
516
+ * @private
517
+ * @this {ViewerThis}
518
+ * @description Run CodeMirror Editor
519
+ * @param {"set"|"get"|"readonly"|"refresh"} key method key
520
+ * @param {*} value CodeMirror params
521
+ * @param {string|undefined} rootKey Root key
522
+ */
523
+ _codeMirrorEditor(key, value, rootKey) {
524
+ const fo = rootKey ? this.frameRoots.get(rootKey).get('options') : this.editor.frameOptions;
525
+ switch (key) {
526
+ case 'set':
527
+ if (fo.has('codeMirror5Editor')) {
528
+ fo.get('codeMirror5Editor').getDoc().setValue(value);
529
+ } else if (fo.has('codeMirror6Editor')) {
530
+ fo.get('codeMirror6Editor').dispatch({
531
+ changes: { from: 0, to: fo.get('codeMirror6Editor').state.doc.length, insert: value }
532
+ });
533
+ }
534
+ break;
535
+ case 'get':
536
+ if (fo.has('codeMirror5Editor')) {
537
+ return fo.get('codeMirror5Editor').getDoc().getValue();
538
+ } else if (fo.has('codeMirror6Editor')) {
539
+ return fo.get('codeMirror6Editor').state.doc.toString();
540
+ }
541
+ break;
542
+ case 'readonly':
543
+ if (fo.has('codeMirror5Editor')) {
544
+ fo.get('codeMirror5Editor').setOption('readOnly', value);
545
+ } else if (fo.has('codeMirror6Editor')) {
546
+ if (!value) fo.get('codeMirror6Editor').contentDOM.setAttribute('contenteditable', true);
547
+ else fo.get('codeMirror6Editor').contentDOM.removeAttribute('contenteditable');
548
+ }
549
+ break;
550
+ case 'refresh':
551
+ if (fo.has('codeMirror5Editor')) {
552
+ fo.get('codeMirror5Editor').refresh();
553
+ }
554
+ break;
555
+ }
556
+ },
557
+
558
+ /**
559
+ * @private
560
+ * @this {ViewerThis}
561
+ * @description Set method in the code view area
562
+ * @param {string} value HTML string
563
+ */
564
+ _setCodeView(value) {
565
+ if (this.options.get('hasCodeMirror')) {
566
+ this._codeMirrorEditor('set', value, null);
567
+ } else {
568
+ this.editor.frameContext.get('code').value = value;
569
+ }
570
+ },
571
+
572
+ /**
573
+ * @private
574
+ * @this {ViewerThis}
575
+ * @description Get method in the code view area
576
+ */
577
+ _getCodeView() {
578
+ if (this.options.get('hasCodeMirror')) {
579
+ return this._codeMirrorEditor('get', null, null);
580
+ } else {
581
+ return this.editor.frameContext.get('code').value;
582
+ }
583
+ },
584
+
585
+ /**
586
+ * @private
587
+ * @this {ViewerThis}
588
+ * @description Convert the data of the code view and put it in the WYSIWYG area.
589
+ */
590
+ _setCodeDataToEditor() {
591
+ const code_html = this._getCodeView();
592
+
593
+ if (this.editor.frameOptions.get('iframe_fullPage')) {
594
+ const wDoc = this.editor.frameContext.get('_wd');
595
+ const parseDocument = new DOMParser().parseFromString(code_html, 'text/html');
596
+
597
+ if (!this.options.get('__allowedScriptTag')) {
598
+ const headChildren = parseDocument.head.children;
599
+ for (let i = 0, len = headChildren.length; i < len; i++) {
600
+ if (/^script$/i.test(headChildren[i].tagName)) {
601
+ parseDocument.head.removeChild(headChildren[i]);
602
+ i--;
603
+ len--;
604
+ }
605
+ }
606
+ }
607
+
608
+ let headers = parseDocument.head.innerHTML;
609
+ if (!parseDocument.head.querySelector('link[rel="stylesheet"]') || (this.editor.frameOptions.get('height') === 'auto' && !parseDocument.head.querySelector('style'))) {
610
+ headers += converter._setIframeStyleLinks(this.editor.frameOptions.get('iframe_cssFileName')) + converter._setAutoHeightStyle(this.editor.frameOptions.get('height'));
611
+ }
612
+
613
+ wDoc.head.innerHTML = headers;
614
+ wDoc.body.innerHTML = this.html.clean(parseDocument.body.innerHTML, { forceFormat: true, whitelist: null, blacklist: null, _freeCodeViewMode: this.options.get('freeCodeViewMode') });
615
+
616
+ const attrs = parseDocument.body.attributes;
617
+ for (let i = 0, len = attrs.length; i < len; i++) {
618
+ if (attrs[i].name === 'contenteditable') continue;
619
+ wDoc.body.setAttribute(attrs[i].name, attrs[i].value);
620
+ }
621
+ if (!dom.utils.hasClass(wDoc.body, 'sun-editor-editable')) {
622
+ const editableClasses = this.options.get('_editableClass').split(' ');
623
+ for (let i = 0; i < editableClasses.length; i++) {
624
+ dom.utils.addClass(wDoc.body, this.options.get('_editableClass')[i]);
625
+ }
626
+ }
627
+ } else {
628
+ this.editor.frameContext.get('wysiwyg').innerHTML =
629
+ code_html.length > 0
630
+ ? this.html.clean(code_html, { forceFormat: true, whitelist: null, blacklist: null, _freeCodeViewMode: this.options.get('freeCodeViewMode') })
631
+ : '<' + this.options.get('defaultLine') + '><br></' + this.options.get('defaultLine') + '>';
632
+ }
633
+ },
634
+
635
+ /**
636
+ * @private
637
+ * @this {ViewerThis}
638
+ * @description Convert the data of the WYSIWYG area and put it in the code view area.
639
+ */
640
+ _setEditorDataToCodeView() {
641
+ const codeContent = this.html._convertToCode(this.editor.frameContext.get('wysiwyg'), false);
642
+ let codeValue = '';
643
+
644
+ if (this.editor.frameOptions.get('iframe_fullPage')) {
645
+ const attrs = dom.utils.getAttributesToString(this.editor.frameContext.get('_wd').body, null);
646
+ codeValue = '<!DOCTYPE html>\n<html>\n' + this.editor.frameContext.get('_wd').head.outerHTML.replace(/>(?!\n)/g, '>\n') + '<body ' + attrs + '>\n' + codeContent + '</body>\n</html>';
647
+ } else {
648
+ codeValue = codeContent;
649
+ }
650
+
651
+ this._setCodeView(codeValue);
652
+ },
653
+
654
+ /**
655
+ * @private
656
+ * @this {ViewerThis}
657
+ * @description Adjusts the height of the code view area.
658
+ * - Ensures the code block auto-resizes based on its content.
659
+ * @param {HTMLElement} code - Code area
660
+ * @param {HTMLTextAreaElement} codeNumbers - Code numbers area
661
+ * @param {boolean} isAuto - Auto height option
662
+ */
663
+ _codeViewAutoHeight(code, codeNumbers, isAuto) {
664
+ if (isAuto) code.style.height = code.scrollHeight + 'px';
665
+ this._updateLineNumbers(codeNumbers, code);
666
+ },
667
+
668
+ /**
669
+ * @private
670
+ * @this {ViewerThis}
671
+ * @description Updates the line numbers for the code editor.
672
+ * - Dynamically adjusts line numbers as content grows.
673
+ * @param {HTMLTextAreaElement} lineNumbers - Code numbers area
674
+ * @param {HTMLElement} code - Code area
675
+ */
676
+ _updateLineNumbers(lineNumbers, code) {
677
+ if (!lineNumbers) return;
678
+
679
+ const lineHeight = GetLineHeight(lineNumbers);
680
+ const numberOfLinesNeeded = Math.ceil(code.scrollHeight / lineHeight);
681
+
682
+ const currentLineCount = (lineNumbers.value.match(/\n/g) || []).length;
683
+ if (numberOfLinesNeeded > currentLineCount) {
684
+ let n = '';
685
+ for (let i = currentLineCount + 1; i <= numberOfLinesNeeded; i++) {
686
+ n += `${i}\n`;
687
+ }
688
+ lineNumbers.value += n;
689
+ }
690
+ },
691
+
692
+ /**
693
+ * @private
694
+ * @this {HTMLElement} Code numbers area
695
+ * @description Synchronizes scrolling of line numbers with the code editor.
696
+ * - Keeps the line numbers aligned with the text.
697
+ * @param {HTMLTextAreaElement} codeNumbers - Code numbers textarea
698
+ */
699
+ _scrollLineNumbers(codeNumbers) {
700
+ codeNumbers.scrollTop = this.scrollTop;
701
+ codeNumbers.scrollLeft = this.scrollLeft;
702
+ },
703
+
704
+ constructor: Viewer
705
+ };
706
+
707
+ /**
708
+ * @private
709
+ * @description Create line numbers for the code view area
710
+ * @param {__se__FrameContext} fc - Frame context
711
+ */
712
+ function CreateLineNumbers(fc) {
713
+ const codeNumbers = fc.get('codeNumbers');
714
+ if (!codeNumbers) return;
715
+
716
+ const lineHeight = GetLineHeight(codeNumbers);
717
+ const numberOfLines = fc.get('code').scrollHeight / lineHeight;
718
+
719
+ let n = '';
720
+ for (let i = 1; i <= numberOfLines; i++) {
721
+ n += `${i}\n`;
722
+ }
723
+
724
+ const { padding, margin } = env._w.getComputedStyle(fc.get('code'));
725
+ codeNumbers.value = n;
726
+ codeNumbers.style.padding = padding || '';
727
+ codeNumbers.style.margin = margin || '';
728
+ }
729
+
730
+ /**
731
+ * @private
732
+ * @description Get the line height of the textarea
733
+ * @param {HTMLTextAreaElement} textarea Textarea element
734
+ * @returns {number}
735
+ */
736
+ function GetLineHeight(textarea) {
737
+ const lineHeight = env._w.getComputedStyle(textarea).lineHeight;
738
+ let lineHeightMatch;
739
+
740
+ if (!numbers.is(lineHeight)) {
741
+ const fontSize = env._w.getComputedStyle(textarea).fontSize;
742
+ lineHeightMatch = numbers.get(fontSize) * 1.2;
743
+ } else {
744
+ lineHeightMatch = numbers.get(lineHeight);
745
+ }
746
+
747
+ return lineHeightMatch;
748
+ }
749
+
750
+ export default Viewer;