suneditor 3.0.0-rc.4 → 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 +4 -3
- 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 +10 -6
- 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/char.js +11 -0
- package/src/core/logic/dom/format.js +22 -0
- package/src/core/logic/dom/html.js +126 -11
- package/src/core/logic/dom/nodeTransform.js +13 -0
- package/src/core/logic/dom/offset.js +100 -37
- package/src/core/logic/dom/selection.js +54 -22
- 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 +180 -39
- package/src/core/section/constructor.js +61 -20
- package/src/core/section/documentType.js +2 -2
- package/src/events.js +12 -0
- package/src/helper/clipboard.js +1 -1
- package/src/helper/converter.js +15 -0
- package/src/helper/dom/domQuery.js +12 -0
- package/src/helper/dom/domUtils.js +26 -14
- package/src/helper/index.js +3 -0
- package/src/helper/markdown.js +876 -0
- package/src/interfaces/plugins.js +7 -5
- 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/Browser.js +31 -1
- package/src/modules/contract/ColorPicker.js +6 -0
- package/src/modules/contract/Controller.js +77 -39
- package/src/modules/contract/Figure.js +57 -0
- package/src/modules/contract/Modal.js +6 -0
- package/src/modules/manager/ApiManager.js +53 -4
- package/src/modules/manager/FileManager.js +18 -1
- package/src/modules/ui/ModalAnchorEditor.js +35 -2
- package/src/modules/ui/SelectMenu.js +44 -12
- package/src/plugins/browser/fileBrowser.js +5 -2
- package/src/plugins/command/codeBlock.js +324 -0
- package/src/plugins/command/exportPDF.js +15 -3
- package/src/plugins/command/fileUpload.js +4 -1
- package/src/plugins/dropdown/backgroundColor.js +5 -1
- package/src/plugins/dropdown/blockStyle.js +8 -2
- package/src/plugins/dropdown/fontColor.js +5 -1
- package/src/plugins/dropdown/hr.js +6 -0
- package/src/plugins/dropdown/layout.js +4 -1
- package/src/plugins/dropdown/lineHeight.js +3 -0
- package/src/plugins/dropdown/paragraphStyle.js +5 -5
- package/src/plugins/dropdown/table/index.js +4 -1
- 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/dropdown/template.js +3 -0
- package/src/plugins/dropdown/textStyle.js +5 -1
- package/src/plugins/field/mention.js +5 -1
- package/src/plugins/index.js +3 -0
- package/src/plugins/input/fontSize.js +10 -3
- package/src/plugins/modal/audio.js +7 -3
- package/src/plugins/modal/embed.js +23 -20
- package/src/plugins/modal/image/index.js +5 -1
- package/src/plugins/modal/math.js +7 -2
- package/src/plugins/modal/video/index.js +21 -4
- 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/assets/suneditor.css.d.ts +1 -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/char.d.ts +11 -0
- package/types/core/logic/dom/format.d.ts +22 -0
- package/types/core/logic/dom/html.d.ts +16 -0
- package/types/core/logic/dom/nodeTransform.d.ts +13 -0
- package/types/core/logic/dom/offset.d.ts +23 -2
- package/types/core/logic/dom/selection.d.ts +9 -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 +362 -79
- package/types/events.d.ts +11 -0
- package/types/helper/converter.d.ts +15 -0
- package/types/helper/dom/domQuery.d.ts +12 -0
- package/types/helper/dom/domUtils.d.ts +23 -2
- package/types/helper/index.d.ts +5 -0
- package/types/helper/markdown.d.ts +27 -0
- package/types/interfaces/plugins.d.ts +7 -5
- package/types/langs/_Lang.d.ts +9 -0
- package/types/modules/contract/Browser.d.ts +36 -2
- package/types/modules/contract/ColorPicker.d.ts +6 -0
- package/types/modules/contract/Controller.d.ts +35 -1
- package/types/modules/contract/Figure.d.ts +57 -0
- package/types/modules/contract/Modal.d.ts +6 -0
- package/types/modules/manager/ApiManager.d.ts +26 -0
- package/types/modules/manager/FileManager.d.ts +17 -0
- package/types/modules/ui/ModalAnchorEditor.d.ts +41 -4
- package/types/modules/ui/SelectMenu.d.ts +40 -2
- package/types/plugins/browser/fileBrowser.d.ts +10 -4
- package/types/plugins/command/codeBlock.d.ts +53 -0
- package/types/plugins/command/fileUpload.d.ts +8 -2
- package/types/plugins/dropdown/backgroundColor.d.ts +10 -2
- package/types/plugins/dropdown/blockStyle.d.ts +14 -2
- package/types/plugins/dropdown/fontColor.d.ts +10 -2
- package/types/plugins/dropdown/hr.d.ts +12 -0
- package/types/plugins/dropdown/layout.d.ts +8 -2
- package/types/plugins/dropdown/lineHeight.d.ts +6 -0
- package/types/plugins/dropdown/paragraphStyle.d.ts +14 -3
- package/types/plugins/dropdown/table/index.d.ts +9 -3
- package/types/plugins/dropdown/template.d.ts +6 -0
- package/types/plugins/dropdown/textStyle.d.ts +10 -2
- package/types/plugins/field/mention.d.ts +10 -2
- package/types/plugins/index.d.ts +3 -0
- package/types/plugins/input/fontSize.d.ts +18 -4
- package/types/plugins/modal/audio.d.ts +14 -6
- package/types/plugins/modal/embed.d.ts +44 -38
- package/types/plugins/modal/image/index.d.ts +9 -1
- package/types/plugins/modal/link.d.ts +6 -2
- package/types/plugins/modal/math.d.ts +23 -5
- package/types/plugins/modal/video/index.d.ts +49 -9
- package/types/typedef.d.ts +5 -2
package/src/langs/tr.js
CHANGED
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
link_modal_url: "Bağlantı URL'si",
|
|
127
127
|
link_modal_relAttribute: 'Rel niteliği',
|
|
128
128
|
list: 'Liste',
|
|
129
|
+
markdownView: 'Markdown görünümü',
|
|
129
130
|
math: 'Matematik',
|
|
130
131
|
math_modal_fontSizeLabel: 'Yazı Tipi Boyutu',
|
|
131
132
|
math_modal_inputLabel: 'Matematiksel Simgeler',
|
|
@@ -187,6 +188,7 @@
|
|
|
187
188
|
tableProperties: 'Tablo özellikleri',
|
|
188
189
|
tags: 'Etiketler',
|
|
189
190
|
tag_blockquote: 'Alıntı',
|
|
191
|
+
codeBlock: 'Kod Bloğu',
|
|
190
192
|
tag_div: 'Normal (DIV)',
|
|
191
193
|
tag_h: 'Başlık',
|
|
192
194
|
tag_p: 'Paragraf',
|
|
@@ -205,6 +207,13 @@
|
|
|
205
207
|
video_modal_title: 'Video Ekle',
|
|
206
208
|
video_modal_url: "Medya Ekleme URL'si (YouTube/Vimeo)",
|
|
207
209
|
width: 'Genişlik',
|
|
210
|
+
codeLanguage: 'Dil',
|
|
211
|
+
codeLanguage_none: 'Hiçbiri',
|
|
212
|
+
finder_matchCase: 'Kibrit Kutusu',
|
|
213
|
+
finder_wholeWord: 'Tüm Kelime',
|
|
214
|
+
finder_regex: 'Düzenli İfade',
|
|
215
|
+
finder_prev: 'Önceki Maç',
|
|
216
|
+
finder_next: 'Sonraki Maç',
|
|
208
217
|
message_copy_success: 'Panoya kopyalandı',
|
|
209
218
|
message_copy_fail: 'Kopyalama başarısız oldu. Lütfen manuel olarak kopyalayın.',
|
|
210
219
|
};
|
package/src/langs/uk.js
CHANGED
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
link_modal_url: 'Посилання',
|
|
127
127
|
link_modal_relAttribute: 'Атрибут rel',
|
|
128
128
|
list: 'Список',
|
|
129
|
+
markdownView: 'Перегляд Markdown',
|
|
129
130
|
math: 'Формула',
|
|
130
131
|
math_modal_fontSizeLabel: 'Розмір шрифту',
|
|
131
132
|
math_modal_inputLabel: 'Математична запис',
|
|
@@ -187,6 +188,7 @@
|
|
|
187
188
|
tableProperties: 'Властивості таблиці',
|
|
188
189
|
tags: 'Теги',
|
|
189
190
|
tag_blockquote: 'Цитата',
|
|
191
|
+
codeBlock: 'Блок коду',
|
|
190
192
|
tag_div: 'Базовий',
|
|
191
193
|
tag_h: 'Заголовок',
|
|
192
194
|
tag_p: 'Абзац',
|
|
@@ -205,6 +207,13 @@
|
|
|
205
207
|
video_modal_title: 'Вставити відео',
|
|
206
208
|
video_modal_url: 'Посилання на відео, Youtube, Vimeo',
|
|
207
209
|
width: 'Ширина',
|
|
210
|
+
codeLanguage: 'Мова',
|
|
211
|
+
codeLanguage_none: 'Жоден',
|
|
212
|
+
finder_matchCase: 'Зіставте регістр',
|
|
213
|
+
finder_wholeWord: 'Ціле слово',
|
|
214
|
+
finder_regex: 'Регулярний вираз',
|
|
215
|
+
finder_prev: 'Попередній матч',
|
|
216
|
+
finder_next: 'Наступний матч',
|
|
208
217
|
message_copy_success: 'Скопійовано в буфер обміну',
|
|
209
218
|
message_copy_fail: 'Не вдалося скопіювати. Будь ласка, скопіюйте вручну.',
|
|
210
219
|
};
|
package/src/langs/ur.js
CHANGED
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
link_modal_url: 'لنک کرنے کے لیے URL',
|
|
127
127
|
link_modal_relAttribute: 'Rel وصف',
|
|
128
128
|
list: 'فہرست',
|
|
129
|
+
markdownView: 'مارک ڈاؤن منظر',
|
|
129
130
|
math: 'ریاضی',
|
|
130
131
|
math_modal_fontSizeLabel: 'حرف کا سائز',
|
|
131
132
|
math_modal_inputLabel: 'ریاضیاتی اشارے',
|
|
@@ -187,6 +188,7 @@
|
|
|
187
188
|
tableProperties: 'ٹیبل کی خصوصیات',
|
|
188
189
|
tags: 'ٹیگز',
|
|
189
190
|
tag_blockquote: 'اقتباس',
|
|
191
|
+
codeBlock: 'کوڈ بلاک',
|
|
190
192
|
tag_div: 'عام (div)',
|
|
191
193
|
tag_h: 'ہیڈر',
|
|
192
194
|
tag_p: 'پیراگراف',
|
|
@@ -205,6 +207,13 @@
|
|
|
205
207
|
video_modal_title: 'ویڈیو داخل کریں',
|
|
206
208
|
video_modal_url: 'ذرائع ابلاغ کا یو آر ایل، یوٹیوب/ویمیو',
|
|
207
209
|
width: 'چوڑائی',
|
|
210
|
+
codeLanguage: 'زبان',
|
|
211
|
+
codeLanguage_none: 'کوئی نہیں۔',
|
|
212
|
+
finder_matchCase: 'میچ کیس',
|
|
213
|
+
finder_wholeWord: 'پورا کلام',
|
|
214
|
+
finder_regex: 'باقاعدہ اظہار',
|
|
215
|
+
finder_prev: 'پچھلا میچ',
|
|
216
|
+
finder_next: 'اگلا میچ',
|
|
208
217
|
message_copy_success: 'کلپ بورڈ میں کاپی ہو گیا',
|
|
209
218
|
message_copy_fail: 'کاپی ناکام۔ براہ کرم دستی طور پر کاپی کریں۔',
|
|
210
219
|
};
|
package/src/langs/zh_cn.js
CHANGED
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
link_modal_url: '网址',
|
|
127
127
|
link_modal_relAttribute: 'Rel 属性',
|
|
128
128
|
list: '列表',
|
|
129
|
+
markdownView: 'Markdown视图',
|
|
129
130
|
math: '数学',
|
|
130
131
|
math_modal_fontSizeLabel: '字号',
|
|
131
132
|
math_modal_inputLabel: '数学符号',
|
|
@@ -187,6 +188,7 @@
|
|
|
187
188
|
tableProperties: '表格属性',
|
|
188
189
|
tags: '标签',
|
|
189
190
|
tag_blockquote: '引用',
|
|
191
|
+
codeBlock: '代码块',
|
|
190
192
|
tag_div: '正文 (DIV)',
|
|
191
193
|
tag_h: '标题',
|
|
192
194
|
tag_p: '段落',
|
|
@@ -205,6 +207,13 @@
|
|
|
205
207
|
video_modal_title: '插入视频',
|
|
206
208
|
video_modal_url: '嵌入网址, Youtube,Vimeo',
|
|
207
209
|
width: '宽度',
|
|
210
|
+
codeLanguage: '语言',
|
|
211
|
+
codeLanguage_none: '没有任何',
|
|
212
|
+
finder_matchCase: '火柴盒',
|
|
213
|
+
finder_wholeWord: '整个单词',
|
|
214
|
+
finder_regex: '正则表达式',
|
|
215
|
+
finder_prev: '上一场比赛',
|
|
216
|
+
finder_next: '下一场比赛',
|
|
208
217
|
message_copy_success: '已复制到剪贴板',
|
|
209
218
|
message_copy_fail: '复制失败,请手动复制。',
|
|
210
219
|
};
|
|
@@ -29,7 +29,11 @@ import ApiManager from '../manager/ApiManager';
|
|
|
29
29
|
* @property {string} [searchUrl] - File server search url. Optional. Can be overridden in browser.
|
|
30
30
|
* @property {Object<string, string>} [searchUrlHeader] - File server search http header. Optional. Can be overridden in browser.
|
|
31
31
|
* @property {string} [listClass] - Class name of list div. Required. Can be overridden in browser.
|
|
32
|
-
* @property {(item: BrowserFile) => string} [drawItemHandler] - Function that
|
|
32
|
+
* @property {(item: BrowserFile) => string} [drawItemHandler] - Function that returns HTML string for rendering each file item. Required. Can be overridden in browser.
|
|
33
|
+
* ```js
|
|
34
|
+
* // drawItemHandler
|
|
35
|
+
* (item) => `<div><img src="${item.thumbnail}"><span>${item.name}</span></div>`
|
|
36
|
+
* ```
|
|
33
37
|
* @property {Array<*>} [props] - `props` argument to `drawItemHandler` function. Optional. Can be overridden in browser.
|
|
34
38
|
* @property {number} [columnSize] - Number of `div.se-file-item-column` to be created.
|
|
35
39
|
* - Optional. Can be overridden in browser. Default: 4.
|
|
@@ -54,6 +58,17 @@ class Browser {
|
|
|
54
58
|
* @param {*} host The instance object that called the constructor.
|
|
55
59
|
* @param {SunEditor.Deps} $ Kernel dependencies
|
|
56
60
|
* @param {BrowserParams} params Browser options
|
|
61
|
+
* @example
|
|
62
|
+
* // Inside a PluginBrowser constructor:
|
|
63
|
+
* this.browser = new Browser(this, this.$, {
|
|
64
|
+
* title: this.$.lang.imageGallery,
|
|
65
|
+
* data: pluginOptions.data,
|
|
66
|
+
* url: pluginOptions.url,
|
|
67
|
+
* headers: pluginOptions.headers,
|
|
68
|
+
* selectorHandler: this.#OnSelect.bind(this),
|
|
69
|
+
* columnSize: 4,
|
|
70
|
+
* className: 'se-image-gallery',
|
|
71
|
+
* });
|
|
57
72
|
*/
|
|
58
73
|
constructor(host, $, params) {
|
|
59
74
|
this.#$ = $;
|
|
@@ -136,6 +151,16 @@ class Browser {
|
|
|
136
151
|
* @param {string} [params.title] - File browser window title. If not, use `this.title`.
|
|
137
152
|
* @param {string} [params.url] - File server url. If not, use `this.url`.
|
|
138
153
|
* @param {Object<string, string>} [params.urlHeader] - File server http header. If not, use `this.urlHeader`.
|
|
154
|
+
* @example
|
|
155
|
+
* // Open with default settings (configured at construction):
|
|
156
|
+
* this.browser.open();
|
|
157
|
+
*
|
|
158
|
+
* // Open with runtime overrides:
|
|
159
|
+
* this.browser.open({
|
|
160
|
+
* title: 'Select a video',
|
|
161
|
+
* url: '/api/videos',
|
|
162
|
+
* urlHeader: { Authorization: 'Bearer token' },
|
|
163
|
+
* });
|
|
139
164
|
*/
|
|
140
165
|
open(params = {}) {
|
|
141
166
|
this.#addGlobalEvent();
|
|
@@ -199,6 +224,11 @@ class Browser {
|
|
|
199
224
|
* @description Filter items by tag
|
|
200
225
|
* @param {Array<BrowserFile>} items - Items to filter
|
|
201
226
|
* @returns {Array<BrowserFile>}
|
|
227
|
+
* @example
|
|
228
|
+
* // Filter items by currently selected tags:
|
|
229
|
+
* browser.selectedTags = ['photo', 'landscape'];
|
|
230
|
+
* const filtered = browser.tagfilter(items);
|
|
231
|
+
* // Returns only items whose `tag` array includes 'photo' or 'landscape'
|
|
202
232
|
*/
|
|
203
233
|
tagfilter(items) {
|
|
204
234
|
const selectedTags = this.selectedTags;
|
|
@@ -143,6 +143,12 @@ class ColorPicker {
|
|
|
143
143
|
* @param {?(current: Node) => boolean} [stopCondition] - A function used to stop traversing parent nodes while finding the color.
|
|
144
144
|
* - When this function returns `true`, the traversal ends at that node.
|
|
145
145
|
* - e.g., `(node) => this.format.isLine(node)` stops at line-level elements like <p>, <div>.
|
|
146
|
+
* @example
|
|
147
|
+
* // Initialize with a selected node and stop traversal at line-level elements
|
|
148
|
+
* this.colorPicker.init(this.$.selection.getNode(), target, (current) => this.$.format.isLine(current));
|
|
149
|
+
*
|
|
150
|
+
* // Initialize with a color string directly (e.g., from a table cell style)
|
|
151
|
+
* this.colorPicker.init(color?.value || '', button);
|
|
146
152
|
*/
|
|
147
153
|
init(nodeOrColor, target, stopCondition) {
|
|
148
154
|
this.targetButton = target;
|
|
@@ -6,6 +6,7 @@ const INDEX_00 = '2147483646';
|
|
|
6
6
|
const INDEX_0 = '2147483645';
|
|
7
7
|
const INDEX_S_1 = '2147483642';
|
|
8
8
|
const INDEX_1 = '2147483641';
|
|
9
|
+
const ADD_OFFSET_VALUE = { left: 0, right: 0, top: 0 };
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Controller information object
|
|
@@ -46,11 +47,10 @@ class Controller {
|
|
|
46
47
|
#initMethod;
|
|
47
48
|
#globalEventHandlers;
|
|
48
49
|
|
|
49
|
-
#addOffset =
|
|
50
|
+
#addOffset = ADD_OFFSET_VALUE;
|
|
50
51
|
#reserveIndex = false;
|
|
51
52
|
#preventClose = false;
|
|
52
|
-
#
|
|
53
|
-
#shadowRootEventListener = null;
|
|
53
|
+
#bindShadowRootEvent = null;
|
|
54
54
|
#bindClose_key = null;
|
|
55
55
|
#bindClose_mouse = null;
|
|
56
56
|
|
|
@@ -124,13 +124,26 @@ class Controller {
|
|
|
124
124
|
* @param {Node} [positionTarget] Position target element
|
|
125
125
|
* @param {Object} [params={}] params
|
|
126
126
|
* @param {boolean} [params.isWWTarget] If the controller is in the WYSIWYG area, set it to `true`.
|
|
127
|
+
* @param {boolean} [params.passive] If `true`, opens the controller visually without affecting editor state
|
|
128
|
+
* - (`_preventBlur`, `controlActive`, `onControllerContext`, `opendControllers`).
|
|
129
|
+
* - Used for lightweight, non-intrusive display such as hover-triggered UI (e.g., codeLang selector on `<pre>` hover).
|
|
130
|
+
* - Automatically set to `true` when opened during component hover selection (`ON_OVER_COMPONENT`).
|
|
127
131
|
* @param {() => void} [params.initMethod] Method to be called when the controller is closed.
|
|
128
132
|
* @param {boolean} [params.disabled] If `true`, When the `controller` is opened, buttons without the `se-component-enabled` class are disabled. (default: `this.disabled`)
|
|
129
|
-
* @param {{left?: number, top?: number}} [params.addOffset] Additional offset values
|
|
133
|
+
* @param {{left?: number, right?:number, top?: number}} [params.addOffset] Additional offset values
|
|
134
|
+
* @example
|
|
135
|
+
* // Open controller on a target element with default options
|
|
136
|
+
* this.controller.open(target);
|
|
137
|
+
*
|
|
138
|
+
* // Open with explicit options and additional offset
|
|
139
|
+
* this.controller.open(target, null, { isWWTarget: false, initMethod: null, addOffset: null });
|
|
140
|
+
*
|
|
141
|
+
* // Open on a Range target (e.g., text selection)
|
|
142
|
+
* this.controller.open(this.$.selection.getRange());
|
|
130
143
|
*/
|
|
131
|
-
open(target, positionTarget, { isWWTarget, initMethod, disabled, addOffset } = {}) {
|
|
144
|
+
open(target, positionTarget, { isWWTarget, passive, initMethod, disabled, addOffset } = {}) {
|
|
132
145
|
if (_DragHandle.get('__overInfo') === ON_OVER_COMPONENT) {
|
|
133
|
-
|
|
146
|
+
passive = true;
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
if (!target) {
|
|
@@ -142,35 +155,38 @@ class Controller {
|
|
|
142
155
|
this.form.removeAttribute('data-se-hidden-by-children');
|
|
143
156
|
this.#__hiddenByParents__.clear();
|
|
144
157
|
|
|
145
|
-
if (
|
|
146
|
-
|
|
158
|
+
if (!passive) {
|
|
159
|
+
if (this.#$.store.mode.isBalloon) this.#$.toolbar.hide();
|
|
160
|
+
else if (this.#$.store.mode.isSubBalloon) this.#$.subToolbar.hide();
|
|
147
161
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
if (!this.#$.store.get('hasFocus')) {
|
|
163
|
+
if (disabled ?? this.disabled) {
|
|
164
|
+
this.#$.ui.setControllerOnDisabledButtons(true);
|
|
165
|
+
} else {
|
|
166
|
+
this.#$.ui.setControllerOnDisabledButtons(false);
|
|
167
|
+
}
|
|
153
168
|
}
|
|
154
169
|
}
|
|
155
170
|
|
|
156
171
|
this.currentPositionTarget = positionTarget || target;
|
|
157
172
|
this.isWWTarget = isWWTarget ?? this.isWWTarget;
|
|
158
173
|
if (typeof initMethod === 'function') this.#initMethod = initMethod;
|
|
159
|
-
this.#$.ui.currentControllerName = this.kind;
|
|
174
|
+
if (!passive) this.#$.ui.currentControllerName = this.kind;
|
|
160
175
|
|
|
161
|
-
this.#addOffset = { left: 0, top: 0 };
|
|
162
|
-
if (addOffset) this.#addOffset = { ...this.#addOffset, ...addOffset };
|
|
176
|
+
this.#addOffset = { left: 0, right: 0, top: 0, ...addOffset };
|
|
163
177
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (this.parentsHide) {
|
|
170
|
-
this.parentsForm.forEach((e) => {
|
|
171
|
-
e.style.display = 'none';
|
|
172
|
-
e.setAttribute('data-se-hidden-by-children', '1');
|
|
178
|
+
if (!passive) {
|
|
179
|
+
const parents = this.isOutsideForm ? this.parentsForm : [];
|
|
180
|
+
this.#$.ui.opendControllers?.forEach((e) => {
|
|
181
|
+
if (!parents.includes(e.form)) e.form.style.zIndex = INDEX_1;
|
|
173
182
|
});
|
|
183
|
+
|
|
184
|
+
if (this.parentsHide) {
|
|
185
|
+
this.parentsForm.forEach((e) => {
|
|
186
|
+
e.style.display = 'none';
|
|
187
|
+
e.setAttribute('data-se-hidden-by-children', '1');
|
|
188
|
+
});
|
|
189
|
+
}
|
|
174
190
|
}
|
|
175
191
|
|
|
176
192
|
this.#addGlobalEvent();
|
|
@@ -180,7 +196,7 @@ class Controller {
|
|
|
180
196
|
|
|
181
197
|
const isRangeTarget = this.#$.instanceCheck.isRange(target);
|
|
182
198
|
this.currentTarget = /** @type {HTMLElement} */ (isRangeTarget ? null : target);
|
|
183
|
-
this.#controllerOn(this.form, target, isRangeTarget);
|
|
199
|
+
this.#controllerOn(this.form, target, isRangeTarget, passive);
|
|
184
200
|
_w.setTimeout(() => _DragHandle.set('__overInfo', false), 0);
|
|
185
201
|
}
|
|
186
202
|
|
|
@@ -188,6 +204,12 @@ class Controller {
|
|
|
188
204
|
* @description Close a modal plugin
|
|
189
205
|
* - The plugin's `init` method is called.
|
|
190
206
|
* @param {boolean} [force] If `true`, parent controllers are forcibly closed.
|
|
207
|
+
* @example
|
|
208
|
+
* // Close the controller (skips if not open or preventClose is set)
|
|
209
|
+
* this.controller.close();
|
|
210
|
+
*
|
|
211
|
+
* // Force close, also closing parent controllers in the hierarchy
|
|
212
|
+
* this.controller.close(true);
|
|
191
213
|
*/
|
|
192
214
|
close(force) {
|
|
193
215
|
if (!force && (!this.isOpen || this.#preventClose)) return;
|
|
@@ -201,7 +223,7 @@ class Controller {
|
|
|
201
223
|
this.isOpen = false;
|
|
202
224
|
this.#preventClose = false;
|
|
203
225
|
this.__offset = {};
|
|
204
|
-
this.#addOffset =
|
|
226
|
+
this.#addOffset = ADD_OFFSET_VALUE;
|
|
205
227
|
|
|
206
228
|
this.#removeGlobalEvent();
|
|
207
229
|
|
|
@@ -240,6 +262,12 @@ class Controller {
|
|
|
240
262
|
/**
|
|
241
263
|
* @description Sets whether the element (form) should be brought to the top based on `z-index`.
|
|
242
264
|
* @param {boolean} value - `true`: `'2147483646'`, `false`: `'2147483645'`.
|
|
265
|
+
* @example
|
|
266
|
+
* // Bring controller to the highest z-index layer (2147483646)
|
|
267
|
+
* this.controller_cell.bringToTop(true);
|
|
268
|
+
*
|
|
269
|
+
* // Restore to the default top z-index (2147483645)
|
|
270
|
+
* this.controller_cell.bringToTop(false);
|
|
243
271
|
*/
|
|
244
272
|
bringToTop(value) {
|
|
245
273
|
this.toTop = value;
|
|
@@ -249,6 +277,12 @@ class Controller {
|
|
|
249
277
|
/**
|
|
250
278
|
* @description Reset controller position
|
|
251
279
|
* @param {Node} [target]
|
|
280
|
+
* @example
|
|
281
|
+
* // Reposition using a new target element
|
|
282
|
+
* this.controller_cell.resetPosition(tdElement);
|
|
283
|
+
*
|
|
284
|
+
* // Reposition using the previously set target
|
|
285
|
+
* this.controller.resetPosition();
|
|
252
286
|
*/
|
|
253
287
|
resetPosition(target) {
|
|
254
288
|
this.#setControllerPosition(this.form, target || this.currentPositionTarget, true);
|
|
@@ -320,8 +354,9 @@ class Controller {
|
|
|
320
354
|
* @param {HTMLFormElement} form Controller element
|
|
321
355
|
* @param {Node|Range} target Controller target element
|
|
322
356
|
* @param {boolean} isRangeTarget If the target is a `Range`, set it to `true`.
|
|
357
|
+
* @param {boolean} [passive=false] If `true`, opens without affecting editor state (_preventBlur, controlActive, etc.)
|
|
323
358
|
*/
|
|
324
|
-
async #controllerOn(form, target, isRangeTarget) {
|
|
359
|
+
async #controllerOn(form, target, isRangeTarget, passive) {
|
|
325
360
|
/** @type {ControllerInfo} */
|
|
326
361
|
const info = {
|
|
327
362
|
position: this.position,
|
|
@@ -336,20 +371,21 @@ class Controller {
|
|
|
336
371
|
|
|
337
372
|
form.style.display = 'block';
|
|
338
373
|
if (this.#$.contextProvider.shadowRoot) {
|
|
339
|
-
this.#
|
|
340
|
-
this.#shadowRootEventListener = (e) => e.stopPropagation();
|
|
341
|
-
form.addEventListener('mousedown', this.#shadowRootEventListener);
|
|
374
|
+
this.#bindShadowRootEvent = this.#$.eventManager.addEvent(form, 'mousedown', (e) => e.stopPropagation());
|
|
342
375
|
}
|
|
343
376
|
|
|
344
|
-
|
|
377
|
+
if (!passive) {
|
|
378
|
+
this.#$.ui.onControllerContext();
|
|
379
|
+
this.#$.store.set('controlActive', true);
|
|
380
|
+
}
|
|
345
381
|
|
|
346
382
|
if (!this.isOpen) {
|
|
347
383
|
this.#$.ui.opendControllers.push(info);
|
|
348
384
|
}
|
|
349
385
|
|
|
350
|
-
this.isOpen = true;
|
|
351
386
|
this.#$.store.set('_preventBlur', true);
|
|
352
|
-
|
|
387
|
+
|
|
388
|
+
this.isOpen = true;
|
|
353
389
|
|
|
354
390
|
this.host.controllerOn?.(form, target);
|
|
355
391
|
this.#$.eventManager.triggerEvent('onShowController', { caller: this.kind, frameContext: this.#$.frameContext, info });
|
|
@@ -374,10 +410,7 @@ class Controller {
|
|
|
374
410
|
_w.setTimeout(() => {
|
|
375
411
|
this.#$.store.set('controlActive', false);
|
|
376
412
|
}, 0);
|
|
377
|
-
|
|
378
|
-
this.#shadowRootEventForm.removeEventListener('mousedown', this.#shadowRootEventListener);
|
|
379
|
-
this.#shadowRootEventForm = this.#shadowRootEventListener = null;
|
|
380
|
-
}
|
|
413
|
+
this.#bindShadowRootEvent &&= this.#$.eventManager.removeEvent(this.#bindShadowRootEvent);
|
|
381
414
|
}
|
|
382
415
|
|
|
383
416
|
/**
|
|
@@ -473,7 +506,7 @@ class Controller {
|
|
|
473
506
|
|
|
474
507
|
/**
|
|
475
508
|
* @description Checks if the given target is within a form or controller.
|
|
476
|
-
* @param {
|
|
509
|
+
* @param {Element} target The target element.
|
|
477
510
|
* @returns {boolean} `true` if the target is inside a form or controller.
|
|
478
511
|
*/
|
|
479
512
|
#checkForm(target) {
|
|
@@ -505,6 +538,11 @@ class Controller {
|
|
|
505
538
|
e.stopPropagation();
|
|
506
539
|
e.preventDefault();
|
|
507
540
|
|
|
541
|
+
if (target.getAttribute('data-command') === 'close') {
|
|
542
|
+
this.close();
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
508
546
|
this.host.controllerAction(target);
|
|
509
547
|
}
|
|
510
548
|
|
|
@@ -233,6 +233,12 @@ class Figure {
|
|
|
233
233
|
* @param {Node} element Target element
|
|
234
234
|
* @param {string} [className] Class name of container (fixed: `se-component`)
|
|
235
235
|
* @returns {FigureInfo} {target, container, cover, inlineCover, caption}
|
|
236
|
+
* @example
|
|
237
|
+
* const imgEl = document.createElement('IMG');
|
|
238
|
+
* imgEl.src = imageUrl;
|
|
239
|
+
* const figureInfo = Figure.CreateContainer(imgEl, 'se-image-container');
|
|
240
|
+
* // figureInfo.container → <div class="se-component se-image-container">
|
|
241
|
+
* // figureInfo.cover → <figure> wrapping the imgEl
|
|
236
242
|
*/
|
|
237
243
|
static CreateContainer(element, className) {
|
|
238
244
|
dom.utils.createElement('DIV', { class: 'se-component' + (className ? ' ' + className : '') }, dom.utils.createElement('FIGURE', null, element));
|
|
@@ -244,6 +250,12 @@ class Figure {
|
|
|
244
250
|
* @param {Node} element Target element
|
|
245
251
|
* @param {string} [className] Class name of container (fixed: `se-component` `se-inline-component`)
|
|
246
252
|
* @returns {FigureInfo} {target, container, cover, inlineCover, caption}
|
|
253
|
+
* @example
|
|
254
|
+
* const imgEl = document.createElement('IMG');
|
|
255
|
+
* imgEl.src = imageUrl;
|
|
256
|
+
* const figureInfo = Figure.CreateInlineContainer(imgEl, 'se-image-container');
|
|
257
|
+
* // figureInfo.container → <span class="se-component se-inline-component se-image-container">
|
|
258
|
+
* // figureInfo.inlineCover → same as container (inline mode)
|
|
247
259
|
*/
|
|
248
260
|
static CreateInlineContainer(element, className) {
|
|
249
261
|
dom.utils.createElement('SPAN', { class: 'se-component se-inline-component' + (className ? ' ' + className : '') }, element);
|
|
@@ -288,6 +300,13 @@ class Figure {
|
|
|
288
300
|
* @param {string|number} h Height size
|
|
289
301
|
* @param {?string} [defaultSizeUnit="px"] Default size unit (default: `"px"`)
|
|
290
302
|
* @return {{w: number, h: number}}
|
|
303
|
+
* @example
|
|
304
|
+
* const ratio = Figure.GetRatio(200, 100, 'px');
|
|
305
|
+
* // ratio → { w: 2, h: 0.5 }
|
|
306
|
+
*
|
|
307
|
+
* // Used with proportion-locked resizing
|
|
308
|
+
* const ratio = Figure.GetRatio(inputX.value, inputY.value, sizeUnit);
|
|
309
|
+
* const adjusted = Figure.CalcRatio(newWidth, newHeight, sizeUnit, ratio);
|
|
291
310
|
*/
|
|
292
311
|
static GetRatio(w, h, defaultSizeUnit) {
|
|
293
312
|
let rw = 0,
|
|
@@ -316,6 +335,11 @@ class Figure {
|
|
|
316
335
|
* @param {string} defaultSizeUnit Default size unit (default: `"px"`)
|
|
317
336
|
* @param {?{w: number, h: number}} [ratio] Ratio size (Figure.GetRatio)
|
|
318
337
|
* @return {{w: string|number, h: string|number}}
|
|
338
|
+
* @example
|
|
339
|
+
* const ratio = Figure.GetRatio(200, 100, 'px');
|
|
340
|
+
* // When width changes, recalculate height to maintain aspect ratio
|
|
341
|
+
* const result = Figure.CalcRatio(inputX.value, inputY.value, 'px', ratio);
|
|
342
|
+
* inputY.value = result.h; // adjusted height preserving ratio
|
|
319
343
|
*/
|
|
320
344
|
static CalcRatio(w, h, defaultSizeUnit, ratio) {
|
|
321
345
|
if (ratio?.w && ratio?.h && /\d+/.test(w + '') && /\d+/.test(h + '')) {
|
|
@@ -366,6 +390,19 @@ class Figure {
|
|
|
366
390
|
* @param {boolean} [params.figureTarget=false] If `true`, the target is a figure element
|
|
367
391
|
* @param {boolean} [params.infoOnly=false] If `true`, returns only the figure target info without opening the controller
|
|
368
392
|
* @returns {FigureTargetInfo|undefined} figure target info
|
|
393
|
+
* @example
|
|
394
|
+
* // Open controller with full UI (resize handles, size info, border)
|
|
395
|
+
* const info = this.figure.open(imgElement, {
|
|
396
|
+
* nonResizing: false, nonSizeInfo: false, nonBorder: false,
|
|
397
|
+
* figureTarget: false, infoOnly: false
|
|
398
|
+
* });
|
|
399
|
+
*
|
|
400
|
+
* // Get figure info without opening the controller UI
|
|
401
|
+
* const info = this.figure.open(oFrame, {
|
|
402
|
+
* nonResizing: false, nonSizeInfo: false, nonBorder: false,
|
|
403
|
+
* figureTarget: false, infoOnly: true
|
|
404
|
+
* });
|
|
405
|
+
* // info.width, info.height, info.ratio are available
|
|
369
406
|
*/
|
|
370
407
|
open(targetNode, { nonResizing, nonSizeInfo, nonBorder, figureTarget, infoOnly }) {
|
|
371
408
|
if (!targetNode) {
|
|
@@ -683,6 +720,13 @@ class Figure {
|
|
|
683
720
|
* @param {?Node} targetNode Target element
|
|
684
721
|
* @param {"block"|"inline"} formatStyle Format style
|
|
685
722
|
* @returns {HTMLElement} New target element after conversion
|
|
723
|
+
* @example
|
|
724
|
+
* // Convert a block image to inline format
|
|
725
|
+
* const newImgEl = this.figure.convertAsFormat(imgElement, 'inline');
|
|
726
|
+
* // newImgEl is a cloned element inside a new inline container
|
|
727
|
+
*
|
|
728
|
+
* // Convert an inline image back to block format
|
|
729
|
+
* const newImgEl = this.figure.convertAsFormat(imgElement, 'block');
|
|
686
730
|
*/
|
|
687
731
|
convertAsFormat(targetNode, formatStyle) {
|
|
688
732
|
targetNode ||= this._element;
|
|
@@ -849,6 +893,13 @@ class Figure {
|
|
|
849
893
|
* @param {Node} originEl - The original element of the figure component.
|
|
850
894
|
* @param {Node} anchorCover - The anchor cover element of the figure component.
|
|
851
895
|
* @param {import('../manager/FileManager').default} [fileManagerInst=null] - FileManager module instance, if used.
|
|
896
|
+
* @example
|
|
897
|
+
* // Insert a new image figure, replacing the original element in the DOM
|
|
898
|
+
* const figureInfo = Figure.CreateContainer(imgElement, 'se-image-container');
|
|
899
|
+
* this.figure.retainFigureFormat(figureInfo.container, this.#element, null, this.fileManager);
|
|
900
|
+
*
|
|
901
|
+
* // Replace with anchor cover (e.g., image wrapped in a link)
|
|
902
|
+
* this.figure.retainFigureFormat(container, this.#element, anchorEl, this.fileManager);
|
|
852
903
|
*/
|
|
853
904
|
retainFigureFormat(container, originEl, anchorCover, fileManagerInst) {
|
|
854
905
|
const isInline = this.#$.component.isInline(container);
|
|
@@ -910,6 +961,12 @@ class Figure {
|
|
|
910
961
|
* @param {?string|number} width Element's width size
|
|
911
962
|
* @param {?string|number} height Element's height size
|
|
912
963
|
* @param {?number} deg rotate value
|
|
964
|
+
* @example
|
|
965
|
+
* // Rotate element 90 degrees clockwise
|
|
966
|
+
* this.figure.setTransform(imgElement, 100, 50, 90);
|
|
967
|
+
*
|
|
968
|
+
* // Apply size without additional rotation (deg=0 preserves current rotation)
|
|
969
|
+
* this.figure.setTransform(oFrame, width, height, 0);
|
|
913
970
|
*/
|
|
914
971
|
setTransform(node, width, height, deg) {
|
|
915
972
|
try {
|
|
@@ -80,6 +80,12 @@ class Modal {
|
|
|
80
80
|
* - acceptedFormats: `"image/*, video/*, audio/*"`, etc.
|
|
81
81
|
* - allowMultiple: `true` or `false`
|
|
82
82
|
* @returns {string} HTML string
|
|
83
|
+
* @example
|
|
84
|
+
* // Inside a plugin's modal HTML template:
|
|
85
|
+
* const html = Modal.CreateFileInput(
|
|
86
|
+
* { icons, lang },
|
|
87
|
+
* { acceptedFormats: 'image/*', allowMultiple: true }
|
|
88
|
+
* );
|
|
83
89
|
*/
|
|
84
90
|
static CreateFileInput({ icons, lang }, { acceptedFormats, allowMultiple }) {
|
|
85
91
|
return /*html*/ `
|
|
@@ -51,6 +51,20 @@ class ApiManager {
|
|
|
51
51
|
/**
|
|
52
52
|
* @description Call API
|
|
53
53
|
* @param {ApiManagerParams} params
|
|
54
|
+
* @example
|
|
55
|
+
* // POST with FormData and callbacks
|
|
56
|
+
* apiManager.call({
|
|
57
|
+
* method: 'POST', url: '/upload', headers: { 'x-custom': 'value' },
|
|
58
|
+
* data: formData,
|
|
59
|
+
* callBack: (xhr) => console.log(xhr.responseText),
|
|
60
|
+
* errorCallBack: (res, xhr) => res.errorMessage || 'Upload failed'
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* // GET request with minimal params (uses constructor defaults for omitted options)
|
|
64
|
+
* apiManager.call({
|
|
65
|
+
* method: 'GET', url: '/api/files',
|
|
66
|
+
* callBack: (xhr) => JSON.parse(xhr.responseText)
|
|
67
|
+
* });
|
|
54
68
|
*/
|
|
55
69
|
call({ method, url, headers, data, callBack, errorCallBack, responseType }) {
|
|
56
70
|
this.cancel();
|
|
@@ -90,6 +104,18 @@ class ApiManager {
|
|
|
90
104
|
* @param {*} [params.data] - API data
|
|
91
105
|
* @param {XMLHttpRequestResponseType} [params.responseType] - XMLHttpRequest.responseType
|
|
92
106
|
* @returns {Promise<XMLHttpRequest>}
|
|
107
|
+
* @example
|
|
108
|
+
* // POST FormData and await the response
|
|
109
|
+
* const xhr = await apiManager.asyncCall({
|
|
110
|
+
* method: 'POST', url: '/upload',
|
|
111
|
+
* headers: { 'x-api-key': 'key' }, data: formData
|
|
112
|
+
* });
|
|
113
|
+
* const result = JSON.parse(xhr.responseText);
|
|
114
|
+
*
|
|
115
|
+
* // Send JSON data (uses constructor defaults for method/url)
|
|
116
|
+
* const xhr = await apiManager.asyncCall({
|
|
117
|
+
* data: JSON.stringify({ fileName: 'doc.pdf', htmlContent })
|
|
118
|
+
* });
|
|
93
119
|
*/
|
|
94
120
|
asyncCall({ method, url, headers, data, responseType }) {
|
|
95
121
|
this.cancel();
|
|
@@ -118,9 +144,9 @@ class ApiManager {
|
|
|
118
144
|
this.#$.ui.hideLoading();
|
|
119
145
|
}
|
|
120
146
|
} else {
|
|
147
|
+
console.error(`[SUNEDITOR.ApiManager[${this.kind}].upload.serverException]`, xhr);
|
|
121
148
|
try {
|
|
122
|
-
|
|
123
|
-
reject(res);
|
|
149
|
+
reject(_parseErrorResponse(xhr));
|
|
124
150
|
} finally {
|
|
125
151
|
this.#$.ui.hideLoading();
|
|
126
152
|
}
|
|
@@ -177,12 +203,12 @@ class ApiManager {
|
|
|
177
203
|
// exception
|
|
178
204
|
console.error(`[SUNEDITOR.ApiManager[${this.kind}].upload.serverException]`, xmlHttp);
|
|
179
205
|
try {
|
|
180
|
-
const res =
|
|
206
|
+
const res = _parseErrorResponse(xmlHttp);
|
|
181
207
|
let message = '';
|
|
182
208
|
if (typeof errorCallBack === 'function') {
|
|
183
209
|
message = await errorCallBack(res, xmlHttp);
|
|
184
210
|
}
|
|
185
|
-
const err = `[SUNEDITOR.ApiManager[${this.kind}].upload.serverException] status: ${xmlHttp.status}, response: ${message || res.errorMessage ||
|
|
211
|
+
const err = `[SUNEDITOR.ApiManager[${this.kind}].upload.serverException] status: ${xmlHttp.status}, response: ${message || res.errorMessage || (typeof res === 'string' ? res : JSON.stringify(res))}`;
|
|
186
212
|
this.#$.ui.alertOpen(err, 'error');
|
|
187
213
|
} catch (error) {
|
|
188
214
|
throw Error(`[SUNEDITOR.ApiManager[${this.kind}].upload.errorCallBack.fail] ${error.message}`);
|
|
@@ -194,4 +220,27 @@ class ApiManager {
|
|
|
194
220
|
}
|
|
195
221
|
}
|
|
196
222
|
|
|
223
|
+
/**
|
|
224
|
+
* @description Parses error response from XMLHttpRequest.
|
|
225
|
+
* Safely handles non-text responseTypes (blob, arraybuffer, etc.) where accessing responseText throws.
|
|
226
|
+
* @param {XMLHttpRequest} xhr
|
|
227
|
+
* @returns {Object|string} Parsed JSON object, raw text, or status string as fallback
|
|
228
|
+
*/
|
|
229
|
+
function _parseErrorResponse(xhr) {
|
|
230
|
+
let text;
|
|
231
|
+
try {
|
|
232
|
+
text = xhr.responseText;
|
|
233
|
+
} catch {
|
|
234
|
+
return `status ${xhr.status}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!text) return `status ${xhr.status}`;
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
return JSON.parse(text);
|
|
241
|
+
} catch {
|
|
242
|
+
return text;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
197
246
|
export default ApiManager;
|