suneditor 3.0.0-rc.5 → 3.0.0

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 (118) hide show
  1. package/README.md +3 -2
  2. package/dist/suneditor-contents.min.css +1 -1
  3. package/dist/suneditor.min.css +1 -1
  4. package/dist/suneditor.min.js +1 -1
  5. package/package.json +2 -3
  6. package/src/assets/design/color.css +14 -2
  7. package/src/assets/design/typography.css +5 -0
  8. package/src/assets/icons/defaultIcons.js +22 -4
  9. package/src/assets/suneditor-contents.css +1 -1
  10. package/src/assets/suneditor.css +312 -18
  11. package/src/core/config/eventManager.js +6 -9
  12. package/src/core/editor.js +1 -1
  13. package/src/core/event/actions/index.js +5 -0
  14. package/src/core/event/effects/keydown.registry.js +25 -0
  15. package/src/core/event/eventOrchestrator.js +69 -2
  16. package/src/core/event/handlers/handler_ww_mouse.js +1 -0
  17. package/src/core/event/rules/keydown.rule.backspace.js +9 -1
  18. package/src/core/kernel/coreKernel.js +4 -0
  19. package/src/core/kernel/store.js +2 -0
  20. package/src/core/logic/dom/html.js +110 -11
  21. package/src/core/logic/dom/offset.js +89 -35
  22. package/src/core/logic/dom/selection.js +46 -19
  23. package/src/core/logic/panel/finder.js +982 -0
  24. package/src/core/logic/panel/menu.js +8 -6
  25. package/src/core/logic/panel/toolbar.js +112 -19
  26. package/src/core/logic/panel/viewer.js +214 -43
  27. package/src/core/logic/shell/_commandExecutor.js +7 -1
  28. package/src/core/logic/shell/commandDispatcher.js +1 -1
  29. package/src/core/logic/shell/component.js +5 -7
  30. package/src/core/logic/shell/history.js +24 -0
  31. package/src/core/logic/shell/shortcuts.js +3 -3
  32. package/src/core/logic/shell/ui.js +25 -26
  33. package/src/core/schema/frameContext.js +15 -1
  34. package/src/core/schema/options.js +75 -16
  35. package/src/core/section/constructor.js +61 -20
  36. package/src/core/section/documentType.js +1 -1
  37. package/src/events.js +12 -0
  38. package/src/helper/clipboard.js +1 -1
  39. package/src/helper/dom/domUtils.js +5 -14
  40. package/src/helper/index.js +3 -0
  41. package/src/helper/markdown.js +876 -0
  42. package/src/langs/ckb.js +9 -0
  43. package/src/langs/cs.js +9 -0
  44. package/src/langs/da.js +9 -0
  45. package/src/langs/de.js +9 -0
  46. package/src/langs/en.js +9 -0
  47. package/src/langs/es.js +9 -0
  48. package/src/langs/fa.js +9 -0
  49. package/src/langs/fr.js +9 -0
  50. package/src/langs/he.js +9 -0
  51. package/src/langs/hu.js +9 -0
  52. package/src/langs/it.js +9 -0
  53. package/src/langs/ja.js +9 -0
  54. package/src/langs/km.js +9 -0
  55. package/src/langs/ko.js +9 -0
  56. package/src/langs/lv.js +9 -0
  57. package/src/langs/nl.js +9 -0
  58. package/src/langs/pl.js +9 -0
  59. package/src/langs/pt_br.js +9 -0
  60. package/src/langs/ro.js +9 -0
  61. package/src/langs/ru.js +9 -0
  62. package/src/langs/se.js +9 -0
  63. package/src/langs/tr.js +9 -0
  64. package/src/langs/uk.js +9 -0
  65. package/src/langs/ur.js +9 -0
  66. package/src/langs/zh_cn.js +9 -0
  67. package/src/modules/contract/Controller.js +50 -39
  68. package/src/modules/manager/ApiManager.js +27 -4
  69. package/src/modules/manager/FileManager.js +1 -1
  70. package/src/modules/ui/SelectMenu.js +22 -11
  71. package/src/plugins/command/codeBlock.js +324 -0
  72. package/src/plugins/command/exportPDF.js +15 -3
  73. package/src/plugins/dropdown/blockStyle.js +1 -1
  74. package/src/plugins/dropdown/paragraphStyle.js +1 -2
  75. package/src/plugins/dropdown/table/render/table.html.js +1 -1
  76. package/src/plugins/dropdown/table/services/table.grid.js +16 -8
  77. package/src/plugins/dropdown/table/services/table.style.js +5 -9
  78. package/src/plugins/index.js +3 -0
  79. package/src/plugins/input/fontSize.js +4 -2
  80. package/src/plugins/modal/audio.js +2 -1
  81. package/src/plugins/modal/image/index.js +2 -1
  82. package/src/plugins/modal/math.js +2 -1
  83. package/src/plugins/modal/video/index.js +2 -1
  84. package/src/themes/cobalt.css +13 -4
  85. package/src/themes/cream.css +11 -2
  86. package/src/themes/dark.css +13 -4
  87. package/src/themes/midnight.css +13 -4
  88. package/src/typedef.js +4 -4
  89. package/types/assets/icons/defaultIcons.d.ts +12 -1
  90. package/types/core/config/eventManager.d.ts +6 -8
  91. package/types/core/event/actions/index.d.ts +1 -0
  92. package/types/core/event/effects/keydown.registry.d.ts +2 -0
  93. package/types/core/event/eventOrchestrator.d.ts +2 -1
  94. package/types/core/kernel/coreKernel.d.ts +5 -0
  95. package/types/core/kernel/store.d.ts +5 -0
  96. package/types/core/logic/dom/offset.d.ts +16 -3
  97. package/types/core/logic/dom/selection.d.ts +3 -3
  98. package/types/core/logic/panel/finder.d.ts +83 -0
  99. package/types/core/logic/panel/toolbar.d.ts +14 -1
  100. package/types/core/logic/panel/viewer.d.ts +22 -2
  101. package/types/core/logic/shell/shortcuts.d.ts +1 -1
  102. package/types/core/schema/frameContext.d.ts +22 -0
  103. package/types/core/schema/options.d.ts +153 -31
  104. package/types/events.d.ts +11 -0
  105. package/types/helper/dom/domUtils.d.ts +2 -2
  106. package/types/helper/index.d.ts +5 -0
  107. package/types/helper/markdown.d.ts +27 -0
  108. package/types/langs/_Lang.d.ts +9 -0
  109. package/types/modules/contract/Controller.d.ts +8 -1
  110. package/types/modules/ui/SelectMenu.d.ts +12 -0
  111. package/types/plugins/command/codeBlock.d.ts +53 -0
  112. package/types/plugins/index.d.ts +3 -0
  113. package/types/plugins/input/fontSize.d.ts +6 -2
  114. package/types/plugins/modal/audio.d.ts +4 -2
  115. package/types/plugins/modal/image/index.d.ts +3 -1
  116. package/types/plugins/modal/math.d.ts +3 -1
  117. package/types/plugins/modal/video/index.d.ts +3 -1
  118. package/types/typedef.d.ts +5 -2
@@ -0,0 +1,324 @@
1
+ import { PluginCommand, PluginDropdown } from '../../interfaces';
2
+ import { converter, dom } from '../../helper';
3
+ import { Controller } from '../../modules/contract';
4
+ import { SelectMenu } from '../../modules/ui';
5
+
6
+ void PluginDropdown;
7
+
8
+ const DEFAULT_LANGS = ['javascript', 'typescript', 'html', 'css', 'json', 'python', 'java', 'c', 'cpp', 'csharp', 'go', 'rust', 'ruby', 'php', 'swift', 'kotlin', 'sql', 'bash', 'markdown', 'xml', 'yaml'];
9
+
10
+ /**
11
+ * @typedef {Object} CodeBlockPluginOptions
12
+ * @property {Array<string>} [langs] - List of selectable programming languages for code blocks.
13
+ * - Defaults to 21 common languages
14
+ * - [javascript, typescript, html, css, json, python, java, c, cpp, csharp, go, rust, ruby, php, swift, kotlin, sql, bash, markdown, xml, yaml].
15
+ * - Set to empty array `[]` to disable language selection UI entirely.
16
+ * ```js
17
+ * { codeBlock: { langs: ['javascript', 'python', 'html', 'css'] } }
18
+ * ```
19
+ */
20
+
21
+ /**
22
+ * @class
23
+ * @implements {PluginDropdown}
24
+ * @description Code block plugin — toggles `<pre>` formatting with language selection.
25
+ * - Toolbar: command button (toggle `<pre>`) + optional dropdown (language list)
26
+ * - Hover UI: shows language selector on `<pre>` hover (Controller + SelectMenu)
27
+ * - I/O conversion: `<pre class="language-xxx">` ↔ `<pre><code class="language-xxx">`
28
+ */
29
+ class CodeBlock extends PluginCommand {
30
+ static key = 'codeBlock';
31
+ static className = '';
32
+
33
+ #preTag;
34
+ #langItems;
35
+ #langs;
36
+
37
+ // hover UI
38
+ #hoverButton;
39
+ #hoverSelectMenu;
40
+ #hoverController;
41
+ #hoverCurrentPre;
42
+ #mouseLeaveEvent;
43
+ #removeEventFunc;
44
+
45
+ /**
46
+ * @constructor
47
+ * @param {SunEditor.Kernel} kernel - The Kernel instance
48
+ * @param {CodeBlockPluginOptions} pluginOptions - Configuration options for the CodeBlock plugin.
49
+ */
50
+ constructor(kernel, pluginOptions) {
51
+ super(kernel);
52
+ this.title = this.$.lang.codeBlock || 'Code Block';
53
+ this.icon = 'code_block';
54
+
55
+ this.#preTag = dom.utils.createElement('PRE');
56
+ this.#langs = pluginOptions?.langs ?? DEFAULT_LANGS;
57
+
58
+ if (!this.#langs.length) return;
59
+
60
+ /**
61
+ * ──────────────────────────────────
62
+ * [[ langs select ]]
63
+ * ──────────────────────────────────
64
+ */
65
+
66
+ // ───────────────── [[toolbar dropdown type]] ─────────────────
67
+ this.afterItem = dom.utils.createElement(
68
+ 'button',
69
+ { class: 'se-btn se-tooltip se-sub-arrow-btn', 'data-command': CodeBlock.key, 'data-type': 'dropdown' },
70
+ `${this.$.icons.arrow_down}<span class="se-tooltip-inner"><span class="se-tooltip-text">${this.$.lang.codeLanguage || 'Language'}</span></span>`,
71
+ );
72
+
73
+ const menu = CreateDropdownHTML(this.$, this.#langs);
74
+ this.#langItems = menu.querySelectorAll('li button');
75
+ this.$.menu.initDropdownTarget({ key: CodeBlock.key, type: 'dropdown' }, menu);
76
+
77
+ // ───────────────── [hover UI] ─────────────────
78
+ // controller
79
+ const containerEl = dom.utils.createElement('DIV', { class: 'se-controller se-code-lang' });
80
+ this.#hoverButton = dom.utils.createElement('DIV', { class: 'se-code-lang-button' });
81
+ this.#updateHoverButtonText('');
82
+ containerEl.appendChild(this.#hoverButton);
83
+
84
+ this.#hoverController = new Controller(this, this.$, containerEl, { position: 'top', isWWTarget: true });
85
+
86
+ // mouseleave handler
87
+ this.#removeEventFunc = converter.debounce((e) => {
88
+ this.#mouseLeaveEvent = this.$.eventManager.removeEvent(this.#mouseLeaveEvent);
89
+
90
+ if (e && containerEl.contains(e.relatedTarget)) {
91
+ this.#addCtrlLeaveEvent();
92
+ } else {
93
+ this.#hideHover();
94
+ }
95
+ }, 0);
96
+
97
+ // SelectMenu
98
+ this.#hoverSelectMenu = new SelectMenu(this.$, {
99
+ position: 'bottom-right',
100
+ dir: this.$.options.get('_rtl') ? 'rtl' : 'ltr',
101
+ maxHeight: '214px',
102
+ minWidth: '132px',
103
+ closeMethod: this.#removeEventFunc,
104
+ });
105
+
106
+ this.#hoverSelectMenu.on(this.#hoverButton, this.#onHoverSelect.bind(this));
107
+ this.#buildHoverMenu('');
108
+
109
+ // selectMenu
110
+ this.$.eventManager.addEvent(this.#hoverButton, 'click', (e) => {
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ if (this.#hoverSelectMenu.isOpen) {
114
+ this.#hoverSelectMenu.close();
115
+ } else {
116
+ const currentLang = this.#getPreLang(this.#hoverCurrentPre);
117
+ this.#buildHoverMenu(currentLang);
118
+ const items = this.#hoverSelectMenu.items;
119
+ const idx = currentLang ? items.indexOf(currentLang) : 0;
120
+ this.#hoverSelectMenu.open(null, idx >= 0 ? `[data-index="${idx}"]` : null);
121
+ }
122
+ });
123
+ }
124
+
125
+ /**
126
+ * @hook Editor.EventManager
127
+ * @type {SunEditor.Hook.Event.OnMouseMove}
128
+ */
129
+ onMouseMove({ event }) {
130
+ if (!this.#hoverController) return;
131
+ const eventTarget = dom.query.getEventTarget(event);
132
+ const pre = eventTarget.closest('pre');
133
+
134
+ if (pre && !this.#isHoverOpen() && this.$.ui.opendControllers.length === 0) {
135
+ this.#showHover(pre);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * @hook Editor.EventManager
141
+ * @type {SunEditor.Hook.Event.Active}
142
+ */
143
+ active(element, target) {
144
+ if (/^PRE$/i.test(element?.nodeName)) {
145
+ dom.utils.addClass(target, 'active');
146
+ return true;
147
+ }
148
+
149
+ dom.utils.removeClass(target, 'active');
150
+ return false;
151
+ }
152
+
153
+ /**
154
+ * @override
155
+ * @type {PluginCommand['action']}
156
+ */
157
+ action(target) {
158
+ const lang = target?.getAttribute('data-value') || '';
159
+ const selNode = this.$.selection.getNode();
160
+ const currentPre = dom.query.getParentElement(selNode, (el) => /^PRE$/i.test(el.nodeName));
161
+
162
+ if (currentPre && !lang) {
163
+ // toggle off: convert <pre> to default line
164
+ this.$.format.setLine(dom.utils.createElement(this.$.options.get('defaultLine')));
165
+ } else {
166
+ // toggle on or change language
167
+ if (!currentPre) {
168
+ this.$.format.setBrLine(this.#preTag.cloneNode(false));
169
+ }
170
+
171
+ if (lang) {
172
+ const pre = dom.query.getParentElement(this.$.selection.getNode(), (el) => /^PRE$/i.test(el.nodeName));
173
+ if (pre) this.#setLang(pre, lang);
174
+ }
175
+ }
176
+
177
+ this.$.menu.dropdownOff();
178
+ this.$.focusManager.focus();
179
+ this.$.history.push(false);
180
+ }
181
+
182
+ /**
183
+ * @impl Dropdown
184
+ * @type {PluginDropdown['on']}
185
+ */
186
+ on() {
187
+ if (!this.#langItems) return;
188
+ const currentLang = this.#getPreLang(this.$.selection.getNode());
189
+
190
+ for (let i = 0, len = this.#langItems.length; i < len; i++) {
191
+ const item = this.#langItems[i];
192
+ dom.utils.toggleClass(item, 'active', item.getAttribute('data-value') === currentLang);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * @description Shows the hover language selector over the given pre element.
198
+ * @param {HTMLElement} preElement
199
+ */
200
+ #showHover(preElement) {
201
+ if (this.#hoverCurrentPre === preElement && this.#hoverController.isOpen) return;
202
+
203
+ if (this.#hoverCurrentPre && this.#hoverCurrentPre !== preElement) {
204
+ dom.utils.removeClass(this.#hoverCurrentPre, 'se-pre-code-focus');
205
+ }
206
+ this.#hoverCurrentPre = preElement;
207
+ dom.utils.addClass(preElement, 'se-pre-code-focus');
208
+
209
+ this.#hoverController.open(preElement, null, { passive: true, addOffset: { right: preElement.offsetWidth } });
210
+ this.#updateHoverButtonText(this.#getPreLang(preElement));
211
+
212
+ this.#addPreLeaveEvent();
213
+ }
214
+
215
+ #hideHover() {
216
+ if (this.#hoverSelectMenu?.isOpen) return;
217
+ this.#closeHover();
218
+ }
219
+
220
+ #closeHover() {
221
+ if (this.#hoverSelectMenu?.isOpen) this.#hoverSelectMenu.close();
222
+ dom.utils.removeClass(this.#hoverCurrentPre, 'se-pre-code-focus');
223
+ this.#hoverController.close(true);
224
+ }
225
+
226
+ /** @hook Module.Controller */
227
+ controllerClose() {
228
+ if (this.#hoverCurrentPre) {
229
+ dom.utils.removeClass(this.#hoverCurrentPre, 'se-pre-code-focus');
230
+ this.#hoverCurrentPre = null;
231
+ }
232
+ }
233
+
234
+ #onHoverSelect(langValue) {
235
+ if (!this.#hoverCurrentPre) return;
236
+ this.#setLang(this.#hoverCurrentPre, langValue);
237
+ this.#updateHoverButtonText(langValue);
238
+ this.#hoverSelectMenu.close();
239
+ this.#hideHover();
240
+ this.$.focusManager.focus();
241
+ this.$.history.push(false);
242
+ }
243
+
244
+ #addPreLeaveEvent() {
245
+ this.#mouseLeaveEvent ??= this.$.eventManager.addEvent(this.#hoverCurrentPre, 'mouseleave', this.#removeEventFunc);
246
+ }
247
+
248
+ #addCtrlLeaveEvent() {
249
+ this.#mouseLeaveEvent ??= this.$.eventManager.addEvent(this.#hoverController.form, 'mouseleave', this.#removeEventFunc);
250
+ }
251
+
252
+ #buildHoverMenu(currentLang) {
253
+ const noneLabel = this.$.lang.codeLanguage_none || 'None';
254
+ const hasExtra = currentLang && !this.#langs.includes(currentLang);
255
+ const items = hasExtra ? ['', currentLang, ...this.#langs] : ['', ...this.#langs];
256
+ const menus = hasExtra ? [noneLabel, currentLang, ...this.#langs] : [noneLabel, ...this.#langs];
257
+ this.#hoverSelectMenu.create(items, menus);
258
+ }
259
+
260
+ #updateHoverButtonText(lang) {
261
+ this.#hoverButton.innerHTML = /* html */ `<span class="se-code-lang-icon">&lt;/&gt;</span><span class="se-code-lang-text">${lang || this.$.lang.codeLanguage || 'Language'}</span>`;
262
+ }
263
+
264
+ #isHoverOpen() {
265
+ return this.#hoverSelectMenu?.isOpen || this.#hoverController?.isOpen;
266
+ }
267
+
268
+ /**
269
+ * @description Get the language from a pre element's class.
270
+ * @param {?Node} preOrChild - The pre element or a node inside it
271
+ * @returns {string}
272
+ */
273
+ #getPreLang(preOrChild) {
274
+ const pre = preOrChild?.nodeName === 'PRE' ? preOrChild : dom.query.getParentElement(preOrChild, (el) => /^PRE$/i.test(el.nodeName));
275
+ if (!pre) return '';
276
+ return /** @type {HTMLElement} */ (pre).className.match(/language-(\S+)/)?.[1] || '';
277
+ }
278
+
279
+ /**
280
+ * @description Set language class on a pre element.
281
+ * @param {HTMLElement} pre
282
+ * @param {string} lang
283
+ */
284
+ #setLang(pre, lang) {
285
+ pre.className = pre.className.replace(/\s*language-\S+/g, '').trim();
286
+ if (lang) {
287
+ dom.utils.addClass(pre, 'language-' + lang);
288
+ pre.setAttribute('data-se-lang', lang);
289
+ } else {
290
+ pre.removeAttribute('data-se-lang');
291
+ }
292
+ }
293
+
294
+ /**
295
+ * @description Cleans up resources.
296
+ */
297
+ destroy() {
298
+ if (this.#hoverCurrentPre) {
299
+ dom.utils.removeClass(this.#hoverCurrentPre, 'se-pre-code-focus');
300
+ }
301
+ this.#hoverController?.form?.parentNode?.removeChild(this.#hoverController.form);
302
+ this.#hoverCurrentPre = null;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * @param {SunEditor.Deps} $
308
+ * @param {string[]} langs
309
+ * @returns {HTMLElement}
310
+ */
311
+ function CreateDropdownHTML($, langs) {
312
+ const noneLabel = $.lang.codeLanguage_none || 'None';
313
+ let list = '<div class="se-list-inner"><ul class="se-list-basic">';
314
+
315
+ list += `<li><button type="button" class="se-btn se-btn-list" data-command="codeBlock" data-value="" title="${noneLabel}">${noneLabel}</button></li>`;
316
+ for (const lang of langs) {
317
+ list += `<li><button type="button" class="se-btn se-btn-list" data-command="codeBlock" data-value="${lang}" title="${lang}">${lang}</button></li>`;
318
+ }
319
+
320
+ list += '</ul></div>';
321
+ return dom.utils.createElement('DIV', { class: 'se-dropdown se-list-layer se-list-code-block' }, list);
322
+ }
323
+
324
+ export default CodeBlock;
@@ -63,12 +63,17 @@ class ExportPDF extends PluginCommand {
63
63
 
64
64
  try {
65
65
  const standardWW = this.$.frameContext.get('documentTypePageMirror') || this.$.frameContext.get('wysiwygFrame');
66
- const editableDiv = dom.utils.createElement('div', { class: standardWW.className }, standardWW.innerHTML);
66
+
67
+ // Strip theme class so getComputedStyle resolves default (light) colors for borders, shadows, etc.
68
+ const themeClass = (this.$.options.get('_themeClass') || '').trim();
69
+ const wwClassName = themeClass ? standardWW.className.replace(themeClass, '').trim() : standardWW.className;
70
+ const editableDiv = dom.utils.createElement('div', { class: wwClassName }, standardWW.innerHTML);
67
71
  ww = dom.utils.createElement('div', { style: `position: absolute; top: -10000px; left: -10000px; width: 21cm; columns: 21cm; height: auto;` }, editableDiv);
68
72
 
69
73
  const innerPadding = _w.getComputedStyle(standardWW).padding;
70
74
  const inlineWW = dom.utils.applyInlineStylesAll(editableDiv, true, this.$.options.get('allUsedStyles'));
71
75
  inlineWW.style.padding = inlineWW.style.paddingTop = inlineWW.style.paddingBottom = inlineWW.style.paddingLeft = inlineWW.style.paddingRight = '0';
76
+
72
77
  ww.innerHTML = `
73
78
  <style>
74
79
  @page {
@@ -109,8 +114,15 @@ class ExportPDF extends PluginCommand {
109
114
  const xhr = await this.apiManager.asyncCall({ data: JSON.stringify(data) });
110
115
 
111
116
  if (xhr.status !== 200) {
112
- const res = !xhr.responseText ? xhr : JSON.parse(xhr.responseText);
113
- throw Error(`[SUNEDITOR.plugins.exportPDF.error] ${res.errorMessage}`);
117
+ let errorMessage;
118
+
119
+ try {
120
+ errorMessage = JSON.parse(xhr.responseText).errorMessage;
121
+ } catch {
122
+ // ignore
123
+ }
124
+
125
+ throw Error(`[SUNEDITOR.plugins.exportPDF.error] ${errorMessage || xhr.statusText}`);
114
126
  }
115
127
 
116
128
  const blob = new Blob([xhr.response], { type: 'application/pdf' });
@@ -139,7 +139,7 @@ class BlockStyle extends PluginDropdown {
139
139
  * @returns {HTMLElement}
140
140
  */
141
141
  function CreateHTML({ lang }, items) {
142
- const defaultFormats = ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
142
+ const defaultFormats = ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
143
143
  const formatList = !items || items.length === 0 ? defaultFormats : items;
144
144
 
145
145
  let list = /*html*/ `
@@ -76,9 +76,8 @@ class ParagraphStyle extends PluginDropdown {
76
76
  }
77
77
 
78
78
  // change format class
79
- const toggleClass = dom.utils.hasClass(target, 'active') ? dom.utils.removeClass : dom.utils.addClass;
80
79
  for (let i = 0, len = selectedFormsts.length; i < len; i++) {
81
- toggleClass(selectedFormsts[i], value);
80
+ dom.utils.toggleClass(selectedFormsts[i], value);
82
81
  }
83
82
 
84
83
  this.$.menu.dropdownOff();
@@ -190,7 +190,7 @@ export function CreateHTML_controller_properties({ lang, icons, options }) {
190
190
  const html = /*html*/ `
191
191
  <div class="se-controller-content">
192
192
  <div class="se-controller-header">
193
- <button type="button" data-command="close_props" class="se-btn se-close-btn close" title="${lang.close}" aria-label="${lang.close}">${icons.cancel}</button>
193
+ <button type="button" data-command="close" class="se-btn se-close-btn close" title="${lang.close}" aria-label="${lang.close}">${icons.cancel}</button>
194
194
  <span class="se-controller-title">${lang.tableProperties}</span>
195
195
  </div>
196
196
  <div class="se-controller-body">
@@ -311,15 +311,23 @@ export class TableGridService {
311
311
  if (remove) {
312
312
  dom.utils.removeItem(cols[insertIndex]);
313
313
  } else {
314
- let totalW = 0;
315
- for (let i = 0, len = cols.length, w; i < len; i++) {
316
- w = numbers.get(cols[i].style.width);
317
- w -= Math.round((w * len * 0.1) / 2);
318
- totalW += w;
319
- cols[i].style.width = `${w}%`;
314
+ const isAutoLayout = !dom.utils.hasClass(this.#main._element, 'se-table-layout-fixed') && this.#main._element.style.tableLayout !== 'fixed';
315
+ const hasWidth = !isAutoLayout && Array.prototype.some.call(cols, (col) => numbers.get(col.style.width) > 0);
316
+
317
+ if (hasWidth) {
318
+ let totalW = 0;
319
+ for (let i = 0, len = cols.length, w; i < len; i++) {
320
+ w = numbers.get(cols[i].style.width);
321
+ w -= Math.round((w * len * 0.1) / 2);
322
+ totalW += w;
323
+ cols[i].style.width = `${w}%`;
324
+ }
325
+ const newCol = dom.utils.createElement('col', { style: `width:${100 - totalW}%` });
326
+ colgroup.insertBefore(newCol, cols[insertIndex]);
327
+ } else {
328
+ // auto layout or no explicit widths — add bare col, let browser distribute
329
+ colgroup.insertBefore(dom.utils.createElement('col'), cols[insertIndex] || null);
320
330
  }
321
- const newCol = dom.utils.createElement('col', { style: `width:${100 - totalW}%` });
322
- colgroup.insertBefore(newCol, cols[insertIndex]);
323
331
  }
324
332
  }
325
333
 
@@ -597,7 +597,6 @@ export class TableStyleService {
597
597
  align_v = verticalAlign;
598
598
  this._propsCache = [];
599
599
 
600
- const tempColorStyles = _w.getComputedStyle(this.#$.eventManager.__focusTemp);
601
600
  for (let i = 0, t, isBreak; (t = targets[i]); i++) {
602
601
  // eslint-disable-next-line no-shadow
603
602
  const { cssText, border, backgroundColor, color, textAlign, verticalAlign, fontWeight, textDecoration, fontStyle } = t.style;
@@ -606,16 +605,13 @@ export class TableStyleService {
606
605
 
607
606
  const { c, s, w } = this.#getBorderStyle(border);
608
607
 
609
- // colors
608
+ // use getComputedStyle to normalize any CSS color format to rgb
610
609
  let hexBackColor = backgroundColor;
611
610
  let hexColor = color;
612
- if (hexBackColor) {
613
- this.#$.eventManager.__focusTemp.style.backgroundColor = hexBackColor;
614
- hexBackColor = tempColorStyles.backgroundColor;
615
- }
616
- if (hexColor) {
617
- this.#$.eventManager.__focusTemp.style.color = hexColor;
618
- hexColor = tempColorStyles.color;
611
+ if (hexBackColor || hexColor) {
612
+ const computed = _w.getComputedStyle(t);
613
+ if (hexBackColor) hexBackColor = computed.backgroundColor;
614
+ if (hexColor) hexColor = computed.color;
619
615
  }
620
616
 
621
617
  if (b_color && cellBorder.c !== c) b_color = '';
@@ -1,5 +1,6 @@
1
1
  // command
2
2
  import blockquote from './command/blockquote';
3
+ import codeBlock from './command/codeBlock';
3
4
  import exportPDF from './command/exportPDF';
4
5
  import fileUpload from './command/fileUpload';
5
6
  import list_bulleted from './command/list_bulleted';
@@ -48,6 +49,7 @@ import anchor from './popup/anchor';
48
49
 
49
50
  export {
50
51
  blockquote,
52
+ codeBlock,
51
53
  exportPDF,
52
54
  fileUpload,
53
55
  list_bulleted,
@@ -84,6 +86,7 @@ export {
84
86
  };
85
87
  export default {
86
88
  blockquote,
89
+ codeBlock,
87
90
  exportPDF,
88
91
  fileUpload,
89
92
  list_bulleted,
@@ -94,8 +94,10 @@ const DEFAULT_UNIT_MAP = {
94
94
  * - Accepted values include: `'px'`, `'pt'`, `'em'`, `'rem'`, `'vw'`, `'vh'`, `'%'` or `'text'`.
95
95
  * - If `'text'` is used, a text-based font size list is applied.
96
96
  * @property {boolean} [showDefaultSizeLabel=true] - Determines whether the default size label is displayed in the dropdown menu.
97
- * @property {boolean} [showIncDecControls=false] - When `true`, displays increase and decrease buttons for font size adjustments.
98
- * @property {boolean} [disableInput=true] - When `true`, disables the direct font size input box.
97
+ * @property {boolean} [showIncDecControls] - When `true`, displays increase and decrease buttons for font size adjustments.
98
+ * - Defaults to `false`. Always `false` when `sizeUnit` is `'text'` (ignored).
99
+ * @property {boolean} [disableInput] - When `true`, disables the direct font size input box.
100
+ * - Defaults to `true` when `sizeUnit` is `'text'`, otherwise `false`.
99
101
  * @property {Object<string, {default: number, inc: number, min: number, max: number, list: Array<number>}>} [unitMap={}] - Override or extend the default unit mapping for font sizes.
100
102
  * Each key is a unit name (e.g., `'px'`, `'em'`). `default`: initial size, `inc`: step for inc/dec buttons, `min`/`max`: range limits, `list`: dropdown values.
101
103
  * When `sizeUnit` is `'text'`, list items use `{title: string, size: string}` instead of numbers.
@@ -10,7 +10,8 @@ const { NO_EVENT, ON_OVER_COMPONENT } = env;
10
10
  * @property {string} [defaultWidth="300px"] - The default width of the `AUDIO` tag.
11
11
  * @property {string} [defaultHeight="150px"] - The default height of the `AUDIO` tag.
12
12
  * @property {boolean} [createFileInput] - Whether to create a file input element.
13
- * @property {boolean} [createUrlInput] - Whether to create a URL input element (default is `true` if file input is not created).
13
+ * @property {boolean} [createUrlInput] - Whether to create a URL input element.
14
+ * - Defaults to `true`. Always `true` when `createFileInput` is `false`.
14
15
  * @property {string} [uploadUrl] - The URL to which files will be uploaded.
15
16
  * @property {Object<string, string>} [uploadHeaders] - Headers to include in the file upload request.
16
17
  * @property {number} [uploadSizeLimit] - The total upload size limit in bytes.
@@ -19,7 +19,8 @@ const { NO_EVENT } = env;
19
19
  * @property {string} [defaultHeight="auto"] - The default height of the image. If a number is provided, `"px"` will be appended.
20
20
  * @property {boolean} [percentageOnlySize=false] - Whether to allow only percentage-based sizing.
21
21
  * @property {boolean} [createFileInput=true] - Whether to create a file input element for image uploads.
22
- * @property {boolean} [createUrlInput=true] - Whether to create a URL input element for image insertion.
22
+ * @property {boolean} [createUrlInput] - Whether to create a URL input element for image insertion.
23
+ * - Defaults to `true`. Always `true` when `createFileInput` is `false`.
23
24
  * @property {string} [uploadUrl] - The URL endpoint for image file uploads.
24
25
  * @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the file upload request.
25
26
  * ```js
@@ -16,7 +16,8 @@ const { _w, _d } = env;
16
16
  * @property {?(...args: *) => *} [onPaste] - A callback function to handle paste events in the math input area.
17
17
  * @property {Object} [formSize={}] - An object specifying the dimensions for the math modal.
18
18
  * @property {string} [formSize.width="460px"] - The default width of the math modal.
19
- * @property {string} [formSize.height="14em"] - The default height of the math modal.
19
+ * @property {string} [formSize.height] - The default height of the math modal.
20
+ * - Defaults to `"14em"`. When `autoHeight` is `true`, defaults to `formSize.minHeight`.
20
21
  * @property {string} [formSize.maxWidth] - The maximum width of the math modal.
21
22
  * @property {string} [formSize.maxHeight] - The maximum height of the math modal.
22
23
  * @property {string} [formSize.minWidth="400px"] - The minimum width of the math modal.
@@ -16,7 +16,8 @@ import { CreateHTML_modal } from './render/video.html';
16
16
  * @property {string} [defaultHeight] - The default height of the video element. If a number is provided, `"px"` will be appended.
17
17
  * @property {boolean} [percentageOnlySize=false] - Whether to allow only percentage-based sizing.
18
18
  * @property {boolean} [createFileInput=false] - Whether to create a file input element for video uploads.
19
- * @property {boolean} [createUrlInput=true] - Whether to create a URL input element for video embedding.
19
+ * @property {boolean} [createUrlInput] - Whether to create a URL input element for video embedding.
20
+ * - Defaults to `true`. Always `true` when `createFileInput` is `false`.
20
21
  * @property {string} [uploadUrl] - The URL endpoint for video file uploads.
21
22
  * @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the video upload request.
22
23
  * @property {number} [uploadSizeLimit] - The total upload size limit for videos in bytes.
@@ -38,15 +38,19 @@
38
38
  --se-edit-outline: #444;
39
39
 
40
40
  /** --------------------------- layout - [colors] ----------- */
41
- /* main shell and typography */
42
- --se-main-font-family: Helvetica Neue;
43
41
  --se-main-out-color: #444;
44
42
  --se-main-color: #ccc;
45
43
  --se-main-color-lighter: #aaa;
46
44
  --se-main-background-color: #0f1828;
47
- --se-code-view-color: #030712;
45
+ --se-code-view-color: #ccc;
48
46
  --se-main-font-color: #ccc;
49
- --se-code-view-background-color: #ddd;
47
+ --se-code-view-background-color: #0d1520;
48
+ --se-code-view-line-color: #8899aa;
49
+ --se-code-view-line-background-color: #0f1828;
50
+ --se-markdown-view-color: #ccc;
51
+ --se-markdown-view-background-color: #0d1520;
52
+ --se-markdown-view-line-color: #8899aa;
53
+ --se-markdown-view-line-background-color: #0f1828;
50
54
  --se-main-divider-color: #555;
51
55
  --se-main-border-color: #666;
52
56
  --se-main-outline-color: #444;
@@ -54,6 +58,11 @@
54
58
  --se-statusbar-font-color: #aaa;
55
59
  --se-overlay-background-color: #111;
56
60
 
61
+ /* finder */
62
+ --se-find-match-color: rgba(255, 213, 0, 0.3);
63
+ --se-find-current-color: rgba(255, 150, 50, 0.55);
64
+ --se-find-no-match-color: rgba(255, 80, 80, 0.2);
65
+
57
66
  /* hover states */
58
67
  --se-hover-color: #162040;
59
68
  --se-hover-dark-color: #304878;
@@ -38,8 +38,6 @@
38
38
  --se-edit-outline: #c4a06e;
39
39
 
40
40
  /** --------------------------- layout - [colors] ----------- */
41
- /* main shell and typography */
42
- --se-main-font-family: Helvetica Neue;
43
41
  --se-main-out-color: #ebe4d5;
44
42
  --se-main-color: #2b2b2b;
45
43
  --se-main-color-lighter: #555555;
@@ -47,6 +45,12 @@
47
45
  --se-code-view-color: #2b2b2b;
48
46
  --se-main-font-color: #2b2b2b;
49
47
  --se-code-view-background-color: #fefcf6;
48
+ --se-code-view-line-color: #8a7e68;
49
+ --se-code-view-line-background-color: #f5f0e0;
50
+ --se-markdown-view-color: #2b2b2b;
51
+ --se-markdown-view-background-color: #fefcf6;
52
+ --se-markdown-view-line-color: #8a7e68;
53
+ --se-markdown-view-line-background-color: #f5f0e0;
50
54
  --se-main-divider-color: #d5c9b0;
51
55
  --se-main-border-color: #c4a06e;
52
56
  --se-main-outline-color: #e0d6c2;
@@ -54,6 +58,11 @@
54
58
  --se-statusbar-font-color: #666666;
55
59
  --se-overlay-background-color: rgba(0, 0, 0, 0.3);
56
60
 
61
+ /* finder */
62
+ --se-find-match-color: rgba(255, 213, 0, 0.4);
63
+ --se-find-current-color: rgba(255, 150, 50, 0.65);
64
+ --se-find-no-match-color: rgba(255, 80, 80, 0.12);
65
+
57
66
  /* hover states */
58
67
  --se-hover-color: #d6e8f8;
59
68
  --se-hover-dark-color: #6ba3d6;
@@ -39,15 +39,19 @@
39
39
  --se-edit-outline: #3b4048;
40
40
 
41
41
  /** --------------------------- layout - [colors] ----------- */
42
- /* main shell and typography */
43
- --se-main-font-family: Helvetica Neue;
44
42
  --se-main-out-color: #21252b;
45
43
  --se-main-color: #d7dae0;
46
44
  --se-main-color-lighter: #abb2bf;
47
45
  --se-main-background-color: #282c34;
48
- --se-code-view-color: #181a1f;
46
+ --se-code-view-color: #d7dae0;
49
47
  --se-main-font-color: #d7dae0;
50
- --se-code-view-background-color: #d7dae0;
48
+ --se-code-view-background-color: #1e2127;
49
+ --se-code-view-line-color: #abb2bf;
50
+ --se-code-view-line-background-color: #282c34;
51
+ --se-markdown-view-color: #d7dae0;
52
+ --se-markdown-view-background-color: #1e2127;
53
+ --se-markdown-view-line-color: #abb2bf;
54
+ --se-markdown-view-line-background-color: #282c34;
51
55
  --se-main-divider-color: #3b4048;
52
56
  --se-main-border-color: #3e4451;
53
57
  --se-main-outline-color: #2f333d;
@@ -55,6 +59,11 @@
55
59
  --se-statusbar-font-color: #9da5b4;
56
60
  --se-overlay-background-color: rgba(0, 0, 0, 0.55);
57
61
 
62
+ /* finder */
63
+ --se-find-match-color: rgba(255, 213, 0, 0.3);
64
+ --se-find-current-color: rgba(255, 150, 50, 0.55);
65
+ --se-find-no-match-color: rgba(255, 80, 80, 0.2);
66
+
58
67
  /* hover states */
59
68
  --se-hover-color: #28434c;
60
69
  --se-hover-dark-color: #365864;