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.
- package/README.md +3 -2
- package/dist/suneditor-contents.min.css +1 -1
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +2 -3
- package/src/assets/design/color.css +14 -2
- package/src/assets/design/typography.css +5 -0
- package/src/assets/icons/defaultIcons.js +22 -4
- package/src/assets/suneditor-contents.css +1 -1
- package/src/assets/suneditor.css +312 -18
- package/src/core/config/eventManager.js +6 -9
- package/src/core/editor.js +1 -1
- package/src/core/event/actions/index.js +5 -0
- package/src/core/event/effects/keydown.registry.js +25 -0
- package/src/core/event/eventOrchestrator.js +69 -2
- package/src/core/event/handlers/handler_ww_mouse.js +1 -0
- package/src/core/event/rules/keydown.rule.backspace.js +9 -1
- package/src/core/kernel/coreKernel.js +4 -0
- package/src/core/kernel/store.js +2 -0
- package/src/core/logic/dom/html.js +110 -11
- package/src/core/logic/dom/offset.js +89 -35
- package/src/core/logic/dom/selection.js +46 -19
- package/src/core/logic/panel/finder.js +982 -0
- package/src/core/logic/panel/menu.js +8 -6
- package/src/core/logic/panel/toolbar.js +112 -19
- package/src/core/logic/panel/viewer.js +214 -43
- package/src/core/logic/shell/_commandExecutor.js +7 -1
- package/src/core/logic/shell/commandDispatcher.js +1 -1
- package/src/core/logic/shell/component.js +5 -7
- package/src/core/logic/shell/history.js +24 -0
- package/src/core/logic/shell/shortcuts.js +3 -3
- package/src/core/logic/shell/ui.js +25 -26
- package/src/core/schema/frameContext.js +15 -1
- package/src/core/schema/options.js +75 -16
- package/src/core/section/constructor.js +61 -20
- package/src/core/section/documentType.js +1 -1
- package/src/events.js +12 -0
- package/src/helper/clipboard.js +1 -1
- package/src/helper/dom/domUtils.js +5 -14
- package/src/helper/index.js +3 -0
- package/src/helper/markdown.js +876 -0
- package/src/langs/ckb.js +9 -0
- package/src/langs/cs.js +9 -0
- package/src/langs/da.js +9 -0
- package/src/langs/de.js +9 -0
- package/src/langs/en.js +9 -0
- package/src/langs/es.js +9 -0
- package/src/langs/fa.js +9 -0
- package/src/langs/fr.js +9 -0
- package/src/langs/he.js +9 -0
- package/src/langs/hu.js +9 -0
- package/src/langs/it.js +9 -0
- package/src/langs/ja.js +9 -0
- package/src/langs/km.js +9 -0
- package/src/langs/ko.js +9 -0
- package/src/langs/lv.js +9 -0
- package/src/langs/nl.js +9 -0
- package/src/langs/pl.js +9 -0
- package/src/langs/pt_br.js +9 -0
- package/src/langs/ro.js +9 -0
- package/src/langs/ru.js +9 -0
- package/src/langs/se.js +9 -0
- package/src/langs/tr.js +9 -0
- package/src/langs/uk.js +9 -0
- package/src/langs/ur.js +9 -0
- package/src/langs/zh_cn.js +9 -0
- package/src/modules/contract/Controller.js +50 -39
- package/src/modules/manager/ApiManager.js +27 -4
- package/src/modules/manager/FileManager.js +1 -1
- package/src/modules/ui/SelectMenu.js +22 -11
- package/src/plugins/command/codeBlock.js +324 -0
- package/src/plugins/command/exportPDF.js +15 -3
- package/src/plugins/dropdown/blockStyle.js +1 -1
- package/src/plugins/dropdown/paragraphStyle.js +1 -2
- package/src/plugins/dropdown/table/render/table.html.js +1 -1
- package/src/plugins/dropdown/table/services/table.grid.js +16 -8
- package/src/plugins/dropdown/table/services/table.style.js +5 -9
- package/src/plugins/index.js +3 -0
- package/src/plugins/input/fontSize.js +4 -2
- package/src/plugins/modal/audio.js +2 -1
- package/src/plugins/modal/image/index.js +2 -1
- package/src/plugins/modal/math.js +2 -1
- package/src/plugins/modal/video/index.js +2 -1
- package/src/themes/cobalt.css +13 -4
- package/src/themes/cream.css +11 -2
- package/src/themes/dark.css +13 -4
- package/src/themes/midnight.css +13 -4
- package/src/typedef.js +4 -4
- package/types/assets/icons/defaultIcons.d.ts +12 -1
- package/types/core/config/eventManager.d.ts +6 -8
- package/types/core/event/actions/index.d.ts +1 -0
- package/types/core/event/effects/keydown.registry.d.ts +2 -0
- package/types/core/event/eventOrchestrator.d.ts +2 -1
- package/types/core/kernel/coreKernel.d.ts +5 -0
- package/types/core/kernel/store.d.ts +5 -0
- package/types/core/logic/dom/offset.d.ts +16 -3
- package/types/core/logic/dom/selection.d.ts +3 -3
- package/types/core/logic/panel/finder.d.ts +83 -0
- package/types/core/logic/panel/toolbar.d.ts +14 -1
- package/types/core/logic/panel/viewer.d.ts +22 -2
- package/types/core/logic/shell/shortcuts.d.ts +1 -1
- package/types/core/schema/frameContext.d.ts +22 -0
- package/types/core/schema/options.d.ts +153 -31
- package/types/events.d.ts +11 -0
- package/types/helper/dom/domUtils.d.ts +2 -2
- package/types/helper/index.d.ts +5 -0
- package/types/helper/markdown.d.ts +27 -0
- package/types/langs/_Lang.d.ts +9 -0
- package/types/modules/contract/Controller.d.ts +8 -1
- package/types/modules/ui/SelectMenu.d.ts +12 -0
- package/types/plugins/command/codeBlock.d.ts +53 -0
- package/types/plugins/index.d.ts +3 -0
- package/types/plugins/input/fontSize.d.ts +6 -2
- package/types/plugins/modal/audio.d.ts +4 -2
- package/types/plugins/modal/image/index.d.ts +3 -1
- package/types/plugins/modal/math.d.ts +3 -1
- package/types/plugins/modal/video/index.d.ts +3 -1
- 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"></></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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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', '
|
|
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="
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
totalW
|
|
319
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
614
|
-
hexBackColor =
|
|
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 = '';
|
package/src/plugins/index.js
CHANGED
|
@@ -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
|
|
98
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/src/themes/cobalt.css
CHANGED
|
@@ -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: #
|
|
45
|
+
--se-code-view-color: #ccc;
|
|
48
46
|
--se-main-font-color: #ccc;
|
|
49
|
-
--se-code-view-background-color: #
|
|
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;
|
package/src/themes/cream.css
CHANGED
|
@@ -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;
|
package/src/themes/dark.css
CHANGED
|
@@ -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: #
|
|
46
|
+
--se-code-view-color: #d7dae0;
|
|
49
47
|
--se-main-font-color: #d7dae0;
|
|
50
|
-
--se-code-view-background-color: #
|
|
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;
|