suneditor 3.0.0-beta.2 → 3.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +186 -184
- package/LICENSE +21 -21
- package/README.md +157 -180
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +126 -123
- package/src/assets/design/color.css +131 -121
- package/src/assets/design/index.css +3 -3
- package/src/assets/design/size.css +37 -35
- package/src/assets/design/typography.css +37 -37
- package/src/assets/icons/defaultIcons.js +247 -232
- package/src/assets/suneditor-contents.css +779 -778
- package/src/assets/suneditor.css +43 -35
- package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
- package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
- package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
- package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
- package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
- package/src/core/base/eventManager.js +1550 -1484
- package/src/core/base/history.js +355 -355
- package/src/core/class/char.js +163 -162
- package/src/core/class/component.js +856 -842
- package/src/core/class/format.js +3433 -3422
- package/src/core/class/html.js +1927 -1890
- package/src/core/class/menu.js +357 -346
- package/src/core/class/nodeTransform.js +424 -424
- package/src/core/class/offset.js +858 -891
- package/src/core/class/selection.js +710 -620
- package/src/core/class/shortcuts.js +98 -98
- package/src/core/class/toolbar.js +438 -430
- package/src/core/class/ui.js +424 -422
- package/src/core/class/viewer.js +750 -750
- package/src/core/editor.js +1810 -1708
- package/src/core/section/actives.js +268 -241
- package/src/core/section/constructor.js +1348 -1661
- package/src/core/section/context.js +102 -102
- package/src/core/section/documentType.js +582 -561
- package/src/core/section/options.js +367 -0
- package/src/core/util/instanceCheck.js +59 -0
- package/src/editorInjector/_classes.js +36 -36
- package/src/editorInjector/_core.js +92 -92
- package/src/editorInjector/index.js +75 -75
- package/src/events.js +634 -622
- package/src/helper/clipboard.js +59 -59
- package/src/helper/converter.js +586 -564
- package/src/helper/dom/domCheck.js +304 -304
- package/src/helper/dom/domQuery.js +677 -669
- package/src/helper/dom/domUtils.js +618 -557
- package/src/helper/dom/index.js +12 -12
- package/src/helper/env.js +249 -240
- package/src/helper/index.js +25 -25
- package/src/helper/keyCodeMap.js +183 -183
- package/src/helper/numbers.js +72 -72
- package/src/helper/unicode.js +47 -47
- package/src/langs/ckb.js +231 -231
- package/src/langs/cs.js +231 -231
- package/src/langs/da.js +231 -231
- package/src/langs/de.js +231 -231
- package/src/langs/en.js +230 -230
- package/src/langs/es.js +231 -231
- package/src/langs/fa.js +231 -231
- package/src/langs/fr.js +231 -231
- package/src/langs/he.js +231 -231
- package/src/langs/hu.js +230 -230
- package/src/langs/index.js +28 -28
- package/src/langs/it.js +231 -231
- package/src/langs/ja.js +230 -230
- package/src/langs/km.js +230 -230
- package/src/langs/ko.js +230 -230
- package/src/langs/lv.js +231 -231
- package/src/langs/nl.js +231 -231
- package/src/langs/pl.js +231 -231
- package/src/langs/pt_br.js +231 -231
- package/src/langs/ro.js +231 -231
- package/src/langs/ru.js +231 -231
- package/src/langs/se.js +231 -231
- package/src/langs/tr.js +231 -231
- package/src/langs/uk.js +231 -231
- package/src/langs/ur.js +231 -231
- package/src/langs/zh_cn.js +231 -231
- package/src/modules/ApiManager.js +191 -191
- package/src/modules/Browser.js +669 -667
- package/src/modules/ColorPicker.js +364 -362
- package/src/modules/Controller.js +474 -454
- package/src/modules/Figure.js +1620 -1617
- package/src/modules/FileManager.js +359 -359
- package/src/modules/HueSlider.js +577 -565
- package/src/modules/Modal.js +346 -346
- package/src/modules/ModalAnchorEditor.js +643 -643
- package/src/modules/SelectMenu.js +549 -549
- package/src/modules/_DragHandle.js +17 -17
- package/src/modules/index.js +14 -14
- package/src/plugins/browser/audioGallery.js +83 -83
- package/src/plugins/browser/fileBrowser.js +103 -103
- package/src/plugins/browser/fileGallery.js +83 -83
- package/src/plugins/browser/imageGallery.js +81 -81
- package/src/plugins/browser/videoGallery.js +103 -103
- package/src/plugins/command/blockquote.js +61 -60
- package/src/plugins/command/exportPDF.js +134 -134
- package/src/plugins/command/fileUpload.js +456 -456
- package/src/plugins/command/list_bulleted.js +149 -148
- package/src/plugins/command/list_numbered.js +152 -151
- package/src/plugins/dropdown/align.js +157 -155
- package/src/plugins/dropdown/backgroundColor.js +108 -104
- package/src/plugins/dropdown/font.js +141 -137
- package/src/plugins/dropdown/fontColor.js +109 -105
- package/src/plugins/dropdown/formatBlock.js +170 -178
- package/src/plugins/dropdown/hr.js +152 -152
- package/src/plugins/dropdown/layout.js +83 -83
- package/src/plugins/dropdown/lineHeight.js +131 -130
- package/src/plugins/dropdown/list.js +123 -122
- package/src/plugins/dropdown/paragraphStyle.js +138 -138
- package/src/plugins/dropdown/table.js +4110 -4000
- package/src/plugins/dropdown/template.js +83 -83
- package/src/plugins/dropdown/textStyle.js +149 -149
- package/src/plugins/field/mention.js +242 -242
- package/src/plugins/index.js +120 -120
- package/src/plugins/input/fontSize.js +414 -410
- package/src/plugins/input/pageNavigator.js +71 -70
- package/src/plugins/modal/audio.js +677 -677
- package/src/plugins/modal/drawing.js +537 -531
- package/src/plugins/modal/embed.js +886 -886
- package/src/plugins/modal/image.js +1377 -1376
- package/src/plugins/modal/link.js +248 -240
- package/src/plugins/modal/math.js +563 -563
- package/src/plugins/modal/video.js +1226 -1226
- package/src/plugins/popup/anchor.js +224 -222
- package/src/suneditor.js +114 -107
- package/src/themes/dark.css +132 -122
- package/src/typedef.js +132 -130
- package/types/assets/icons/defaultIcons.d.ts +8 -0
- package/types/core/base/eventManager.d.ts +29 -4
- package/types/core/class/char.d.ts +2 -1
- package/types/core/class/component.d.ts +1 -2
- package/types/core/class/format.d.ts +8 -1
- package/types/core/class/html.d.ts +8 -0
- package/types/core/class/menu.d.ts +8 -0
- package/types/core/class/offset.d.ts +24 -26
- package/types/core/class/selection.d.ts +2 -0
- package/types/core/class/toolbar.d.ts +6 -0
- package/types/core/class/ui.d.ts +1 -1
- package/types/core/editor.d.ts +34 -12
- package/types/core/section/constructor.d.ts +5 -638
- package/types/core/section/documentType.d.ts +12 -2
- package/types/core/section/options.d.ts +740 -0
- package/types/core/util/instanceCheck.d.ts +50 -0
- package/types/editorInjector/_core.d.ts +5 -5
- package/types/editorInjector/index.d.ts +2 -2
- package/types/events.d.ts +2 -0
- package/types/helper/converter.d.ts +9 -0
- package/types/helper/dom/domQuery.d.ts +5 -5
- package/types/helper/dom/domUtils.d.ts +8 -0
- package/types/helper/env.d.ts +6 -1
- package/types/helper/index.d.ts +4 -1
- package/types/index.d.ts +122 -120
- package/types/langs/_Lang.d.ts +194 -194
- package/types/modules/ColorPicker.d.ts +5 -1
- package/types/modules/Controller.d.ts +8 -4
- package/types/modules/Figure.d.ts +2 -1
- package/types/modules/HueSlider.d.ts +4 -1
- package/types/modules/SelectMenu.d.ts +1 -1
- package/types/plugins/command/blockquote.d.ts +1 -0
- package/types/plugins/command/list_bulleted.d.ts +1 -0
- package/types/plugins/command/list_numbered.d.ts +1 -0
- package/types/plugins/dropdown/align.d.ts +1 -0
- package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
- package/types/plugins/dropdown/font.d.ts +1 -0
- package/types/plugins/dropdown/fontColor.d.ts +1 -0
- package/types/plugins/dropdown/formatBlock.d.ts +3 -2
- package/types/plugins/dropdown/lineHeight.d.ts +1 -0
- package/types/plugins/dropdown/list.d.ts +1 -0
- package/types/plugins/dropdown/table.d.ts +6 -0
- package/types/plugins/input/fontSize.d.ts +1 -0
- package/types/plugins/modal/drawing.d.ts +4 -0
- package/types/plugins/modal/link.d.ts +32 -15
- package/types/suneditor.d.ts +13 -9
- package/types/typedef.d.ts +8 -0
|
@@ -1,456 +1,456 @@
|
|
|
1
|
-
import EditorInjector from '../../editorInjector';
|
|
2
|
-
import { dom, env, numbers } from '../../helper';
|
|
3
|
-
import { FileManager, Figure, Controller } from '../../modules';
|
|
4
|
-
|
|
5
|
-
const { NO_EVENT } = env;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @class
|
|
9
|
-
* @description File upload plugin
|
|
10
|
-
*/
|
|
11
|
-
class FileUpload extends EditorInjector {
|
|
12
|
-
static key = 'fileUpload';
|
|
13
|
-
static type = 'command';
|
|
14
|
-
static className = '';
|
|
15
|
-
static options = { eventIndex: 10000 };
|
|
16
|
-
/**
|
|
17
|
-
* @this {FileUpload}
|
|
18
|
-
* @param {HTMLElement} node - The node to check.
|
|
19
|
-
* @returns {HTMLElement|null} Returns a node if the node is a valid component.
|
|
20
|
-
*/
|
|
21
|
-
static component(node) {
|
|
22
|
-
return dom.check.isAnchor(node) && node.hasAttribute('data-se-file-download') ? node : null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @constructor
|
|
27
|
-
* @param {__se__EditorCore} editor - The root editor instance
|
|
28
|
-
* @param {Object} pluginOptions - plugin options
|
|
29
|
-
* @param {string} pluginOptions.uploadUrl - server request url
|
|
30
|
-
* @param {Object<string, string>=} pluginOptions.uploadHeaders - server request headers
|
|
31
|
-
* @param {string=} pluginOptions.uploadSizeLimit - upload size limit
|
|
32
|
-
* @param {string=} pluginOptions.uploadSingleSizeLimit - upload single size limit
|
|
33
|
-
* @param {boolean=} pluginOptions.allowMultiple - allow multiple files
|
|
34
|
-
* @param {string=} pluginOptions.acceptedFormats - accepted formats
|
|
35
|
-
* @param {string=} pluginOptions.as - Whether to use the 'Box' or 'Link' conversion button
|
|
36
|
-
* @param {Array<string>} pluginOptions.controls - Additional controls to be added to the figure
|
|
37
|
-
*/
|
|
38
|
-
constructor(editor, pluginOptions) {
|
|
39
|
-
super(editor);
|
|
40
|
-
// plugin basic properties
|
|
41
|
-
this.title = this.lang.fileUpload;
|
|
42
|
-
this.icon = 'file_upload';
|
|
43
|
-
|
|
44
|
-
if (!pluginOptions.uploadUrl) console.warn('[SUNEDITOR.fileUpload.warn] "fileUpload" plugin must be have "uploadUrl" option.');
|
|
45
|
-
|
|
46
|
-
// members
|
|
47
|
-
this.uploadUrl = pluginOptions.uploadUrl;
|
|
48
|
-
this.uploadHeaders = pluginOptions.uploadHeaders;
|
|
49
|
-
this.uploadSizeLimit = numbers.get(pluginOptions.uploadSizeLimit, 0);
|
|
50
|
-
this.uploadSingleSizeLimit = numbers.get(pluginOptions.uploadSingleSizeLimit, 0);
|
|
51
|
-
this.allowMultiple = !!pluginOptions.allowMultiple;
|
|
52
|
-
this.acceptedFormats = typeof pluginOptions.acceptedFormats !== 'string' ? '*' : pluginOptions.acceptedFormats.trim() || '*';
|
|
53
|
-
this._acceptedCheck = this.acceptedFormats.split(', ');
|
|
54
|
-
this.as = pluginOptions.as || 'box';
|
|
55
|
-
this.input = dom.utils.createElement('input', { type: 'file', accept: this.acceptedFormats });
|
|
56
|
-
if (this.allowMultiple) {
|
|
57
|
-
this.input.setAttribute('multiple', 'multiple');
|
|
58
|
-
}
|
|
59
|
-
this._element = null;
|
|
60
|
-
|
|
61
|
-
// figure
|
|
62
|
-
const customItems = {
|
|
63
|
-
'custom-download': {
|
|
64
|
-
command: 'download',
|
|
65
|
-
title: this.lang.download,
|
|
66
|
-
icon: 'download',
|
|
67
|
-
action: (target) => {
|
|
68
|
-
const url = target.getAttribute('href');
|
|
69
|
-
if (url) dom.utils.createElement('A', { href: url }, null).click();
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
'custom-as': {
|
|
73
|
-
command: 'as',
|
|
74
|
-
value: 'link', // 'block' or 'link'
|
|
75
|
-
title: this.lang.asLink,
|
|
76
|
-
icon: 'reduction',
|
|
77
|
-
action: (target, value) => {
|
|
78
|
-
this.convertFormat(target, value);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const figureControls = (pluginOptions.controls || [['custom-as', 'align', 'edit', 'custom-download', 'copy', 'remove']]).map((subArray) => subArray.map((item) => (item.startsWith('custom-') ? customItems[item] : item)));
|
|
84
|
-
this.figure = new Figure(this, figureControls, {});
|
|
85
|
-
|
|
86
|
-
// file manager
|
|
87
|
-
this.fileManager = new FileManager(this, {
|
|
88
|
-
query: 'a[download][data-se-file-download]',
|
|
89
|
-
loadHandler: this.events.onFileLoad,
|
|
90
|
-
eventHandler: this.events.onFileAction
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// controller
|
|
94
|
-
if (/\bedit\b/.test(JSON.stringify(figureControls))) {
|
|
95
|
-
const controllerEl = CreateHTML_controller(this);
|
|
96
|
-
this.controller = new Controller(this, controllerEl, { position: 'bottom', disabled: true }, FileUpload.key);
|
|
97
|
-
this.editInput = controllerEl.querySelector('input');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// init
|
|
101
|
-
this.eventManager.addEvent(this.input, 'change', this.#OnChangeFile.bind(this));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* @editorMethod Editor.core
|
|
106
|
-
* @description Executes the main execution method of the plugin.
|
|
107
|
-
* - It is executed by clicking a toolbar "command" button or calling an API.
|
|
108
|
-
*/
|
|
109
|
-
action() {
|
|
110
|
-
this.editor._preventBlur = true;
|
|
111
|
-
this.input.click();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @editorMethod Editor.Component
|
|
116
|
-
* @description Executes the method that is called when a component of a plugin is selected.
|
|
117
|
-
* @param {HTMLElement} target Target component element
|
|
118
|
-
*/
|
|
119
|
-
select(target) {
|
|
120
|
-
this._element = target;
|
|
121
|
-
const asBtn = this.figure.controller.form.querySelector('[data-command="__c__as"]');
|
|
122
|
-
if (dom.check.isFigure(target.parentElement)) {
|
|
123
|
-
asBtn.innerHTML = this.icons.reduction + dom.utils.createTooltipInner(this.lang.asLink);
|
|
124
|
-
asBtn.setAttribute('data-value', 'link');
|
|
125
|
-
this.figure.open(target, { nonResizing: true, nonSizeInfo: true, nonBorder: true, figureTarget: true, __fileManagerInfo: false });
|
|
126
|
-
} else {
|
|
127
|
-
asBtn.innerHTML = this.icons.expansion + dom.utils.createTooltipInner(this.lang.asBlock);
|
|
128
|
-
asBtn.setAttribute('data-value', 'box');
|
|
129
|
-
this.figure.controllerOpen(target, { isWWTarget: true });
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @editorMethod Editor.EventManager
|
|
136
|
-
* @description Executes the event function of "paste" or "drop".
|
|
137
|
-
* @param {Object} params { frameContext, event, file }
|
|
138
|
-
* @param {__se__FrameContext} params.frameContext Frame context
|
|
139
|
-
* @param {ClipboardEvent} params.event Event object
|
|
140
|
-
* @param {File} params.file File object
|
|
141
|
-
* @returns {boolean} - If return false, the file upload will be canceled
|
|
142
|
-
*/
|
|
143
|
-
onFilePasteAndDrop({ file }) {
|
|
144
|
-
const fileType = file.type;
|
|
145
|
-
if (
|
|
146
|
-
!this._acceptedCheck.some((format) => {
|
|
147
|
-
if (format.startsWith('*')) return true;
|
|
148
|
-
if (format.startsWith(fileType)) return true;
|
|
149
|
-
})
|
|
150
|
-
) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.submitFile([file]);
|
|
155
|
-
this.editor.focus();
|
|
156
|
-
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* @editorMethod Modules.Controller
|
|
162
|
-
* @description Executes the method that is called when a target component is edited.
|
|
163
|
-
* @param {HTMLElement|Text} target Target element
|
|
164
|
-
*/
|
|
165
|
-
edit(target) {
|
|
166
|
-
this.editInput.value = target.textContent;
|
|
167
|
-
this.figure.controllerHide();
|
|
168
|
-
this.controller.open(target, null, { isWWTarget: !dom.check.isFigure(target.parentElement), initMethod: null, addOffset: null });
|
|
169
|
-
this.editInput.focus();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* @editorMethod Modules.Controller
|
|
174
|
-
* @description Executes the method that is called when a button is clicked in the "controller".
|
|
175
|
-
* @param {HTMLButtonElement} target Target button element
|
|
176
|
-
*/
|
|
177
|
-
controllerAction(target) {
|
|
178
|
-
const command = target.getAttribute('data-command');
|
|
179
|
-
if (!command) return;
|
|
180
|
-
|
|
181
|
-
if (command === 'edit') {
|
|
182
|
-
if (this.editInput.value.trim().length === 0) return;
|
|
183
|
-
this._element.textContent = this.editInput.value;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
this.controller.close();
|
|
187
|
-
this.component.select(this._element, FileUpload.key);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* @editorMethod Editor.Component
|
|
192
|
-
* @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
|
|
193
|
-
* @param {HTMLElement} target Target element
|
|
194
|
-
* @returns {Promise<void>}
|
|
195
|
-
*/
|
|
196
|
-
async destroy(target) {
|
|
197
|
-
if (!target) return;
|
|
198
|
-
|
|
199
|
-
const figure = Figure.GetContainer(target);
|
|
200
|
-
const containerTarget = dom.query.getParentElement(target, '.se-component') || target;
|
|
201
|
-
|
|
202
|
-
const message = await this.triggerEvent('onFileDeleteBefore', { element: figure.target, container: figure, url: figure.target.getAttribute('href') });
|
|
203
|
-
if (message === false) return;
|
|
204
|
-
|
|
205
|
-
const isInlineComp = this.component.isInline(containerTarget);
|
|
206
|
-
const focusEl = isInlineComp ? containerTarget.previousSibling || containerTarget.nextSibling : containerTarget.previousElementSibling || containerTarget.nextElementSibling;
|
|
207
|
-
dom.utils.removeItem(containerTarget);
|
|
208
|
-
this.ui._offCurrentController();
|
|
209
|
-
|
|
210
|
-
this.editor.focusEdge(focusEl);
|
|
211
|
-
this.history.push(false);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* @description Create an "file" component using the provided files.
|
|
216
|
-
* @param {File[]|FileList} fileList File object list
|
|
217
|
-
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
218
|
-
*/
|
|
219
|
-
async submitFile(fileList) {
|
|
220
|
-
if (fileList.length === 0) return;
|
|
221
|
-
|
|
222
|
-
let fileSize = 0;
|
|
223
|
-
const files = [];
|
|
224
|
-
const slngleSizeLimit = this.uploadSingleSizeLimit;
|
|
225
|
-
for (let i = 0, len = fileList.length, f, s; i < len; i++) {
|
|
226
|
-
f = fileList[i];
|
|
227
|
-
s = f.size;
|
|
228
|
-
if (slngleSizeLimit && slngleSizeLimit > s) {
|
|
229
|
-
const err = '[SUNEDITOR.fileUpload.fail] Size of uploadable single file: ' + slngleSizeLimit / 1000 + 'KB';
|
|
230
|
-
const message = await this.triggerEvent('onFileUploadError', {
|
|
231
|
-
error: err,
|
|
232
|
-
limitSize: slngleSizeLimit,
|
|
233
|
-
uploadSize: s,
|
|
234
|
-
file: f
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
238
|
-
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
files.push(f);
|
|
243
|
-
fileSize += s;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const limitSize = this.uploadSizeLimit;
|
|
247
|
-
const currentSize = this.fileManager.getSize();
|
|
248
|
-
if (limitSize > 0 && fileSize + currentSize > limitSize) {
|
|
249
|
-
const err = '[SUNEDITOR.fileUpload.fail] Size of uploadable total files: ' + limitSize / 1000 + 'KB';
|
|
250
|
-
const message = await this.triggerEvent('onFileUploadError', {
|
|
251
|
-
error: err,
|
|
252
|
-
limitSize,
|
|
253
|
-
currentSize,
|
|
254
|
-
uploadSize: fileSize
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
258
|
-
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const fileInfo = {
|
|
263
|
-
url: this.uploadUrl,
|
|
264
|
-
uploadHeaders: this.uploadHeaders,
|
|
265
|
-
files
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
const handler = async function (uploadCallback, infos, newInfos) {
|
|
269
|
-
infos = newInfos || infos;
|
|
270
|
-
const xmlHttp = await this.fileManager.asyncUpload(infos.url, infos.uploadHeaders, infos.files);
|
|
271
|
-
uploadCallback(xmlHttp);
|
|
272
|
-
}.bind(this, this.#_uploadCallBack.bind(this), fileInfo);
|
|
273
|
-
|
|
274
|
-
const result = await this.triggerEvent('onFileUploadBefore', {
|
|
275
|
-
info: fileInfo,
|
|
276
|
-
handler
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
if (result === undefined) return true;
|
|
280
|
-
if (result === false) return false;
|
|
281
|
-
if (result !== null && typeof result === 'object') handler(result);
|
|
282
|
-
|
|
283
|
-
if (result === true || result === NO_EVENT) handler(null);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* @description Convert format to link or block
|
|
288
|
-
* @param {HTMLElement} target Target element
|
|
289
|
-
* @param {string} value 'link' or 'block'
|
|
290
|
-
*/
|
|
291
|
-
convertFormat(target, value) {
|
|
292
|
-
if (value === 'link') {
|
|
293
|
-
this.figure.close();
|
|
294
|
-
const { container } = Figure.GetContainer(target);
|
|
295
|
-
const next = container.nextElementSibling;
|
|
296
|
-
const parent = container.parentElement;
|
|
297
|
-
|
|
298
|
-
target.removeAttribute('data-se-non-focus');
|
|
299
|
-
target.setAttribute('contenteditable', 'false');
|
|
300
|
-
dom.utils.addClass(target, 'se-component|se-inline-component');
|
|
301
|
-
|
|
302
|
-
const line = dom.utils.createElement(this.options.get('defaultLine'), null, target);
|
|
303
|
-
parent.insertBefore(line, next);
|
|
304
|
-
dom.utils.removeItem(container);
|
|
305
|
-
} else {
|
|
306
|
-
// block
|
|
307
|
-
this.selection.setRange(target, 0, target, 1);
|
|
308
|
-
const r = this.html.remove();
|
|
309
|
-
const s = this.nodeTransform.split(r.container, r.offset, 0);
|
|
310
|
-
|
|
311
|
-
if (s?.previousElementSibling && dom.check.isZeroWidth(s.previousElementSibling)) {
|
|
312
|
-
dom.utils.removeItem(s.previousElementSibling);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
target.setAttribute('data-se-non-focus', 'true');
|
|
316
|
-
target.removeAttribute('contenteditable');
|
|
317
|
-
dom.utils.removeClass(target, 'se-component|se-component-selected|se-inline-component');
|
|
318
|
-
|
|
319
|
-
const figure = Figure.CreateContainer(target, 'se-file-figure se-flex-component');
|
|
320
|
-
(s || r.container).parentElement.insertBefore(figure.container, s);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
this.history.push(false);
|
|
324
|
-
this.component.select(target, FileUpload.key);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* @description Create file element
|
|
329
|
-
* @param {string} url File URL
|
|
330
|
-
* @param {File|{name: string, size: number}} file File object
|
|
331
|
-
* @param {boolean} isLast Is last file
|
|
332
|
-
*/
|
|
333
|
-
create(url, file, isLast) {
|
|
334
|
-
const name = file.name || url;
|
|
335
|
-
const a = dom.utils.createElement(
|
|
336
|
-
'A',
|
|
337
|
-
{
|
|
338
|
-
href: url,
|
|
339
|
-
title: name,
|
|
340
|
-
download: name,
|
|
341
|
-
'data-se-file-download': '',
|
|
342
|
-
contenteditable: 'false',
|
|
343
|
-
'data-se-non-focus': 'true',
|
|
344
|
-
'data-se-non-link': 'true'
|
|
345
|
-
},
|
|
346
|
-
name
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
this.fileManager.setFileData(a, file);
|
|
350
|
-
|
|
351
|
-
if (this.as === 'link') {
|
|
352
|
-
a.className = 'se-component se-inline-component';
|
|
353
|
-
this.component.insert(a, { skipCharCount: false, skipSelection: false, skipHistory: false });
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const figure = Figure.CreateContainer(a);
|
|
358
|
-
dom.utils.addClass(figure.container, 'se-file-figure|se-flex-component');
|
|
359
|
-
|
|
360
|
-
if (!this.component.insert(figure.container, { skipCharCount: false, skipSelection: isLast ? !this.options.get('componentAutoSelect') : true, skipHistory: false })) {
|
|
361
|
-
this.editor.focus();
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (!isLast) return;
|
|
366
|
-
|
|
367
|
-
if (!this.options.get('componentAutoSelect')) {
|
|
368
|
-
const line = this.format.addLine(figure.container, null);
|
|
369
|
-
if (line) this.selection.setRange(line, 0, line, 0);
|
|
370
|
-
} else {
|
|
371
|
-
this.component.select(a, FileUpload.key);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* @private
|
|
377
|
-
* @description Processes the server response after file upload.
|
|
378
|
-
* - Registers the uploaded files in the editor.
|
|
379
|
-
* @param {Object<string, *>} response - The response object from the server.
|
|
380
|
-
*/
|
|
381
|
-
_register(response) {
|
|
382
|
-
response.result.forEach((file, i, a) => {
|
|
383
|
-
this.create(
|
|
384
|
-
file.url,
|
|
385
|
-
{
|
|
386
|
-
name: file.name,
|
|
387
|
-
size: file.size
|
|
388
|
-
},
|
|
389
|
-
i === a.length - 1
|
|
390
|
-
);
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* @private
|
|
396
|
-
* @description Handles file upload errors.
|
|
397
|
-
* - Displays an error message if the upload fails.
|
|
398
|
-
* @param {Object<string, *>} response - The error response from the server.
|
|
399
|
-
* @returns {Promise<void>}
|
|
400
|
-
*/
|
|
401
|
-
async _error(response) {
|
|
402
|
-
const message = await this.triggerEvent('onFileUploadError', { error: response });
|
|
403
|
-
if (message === false) return;
|
|
404
|
-
const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
|
|
405
|
-
this.ui.alertOpen(err, 'error');
|
|
406
|
-
console.error('[SUNEDITOR.plugin.fileUpload.error]', err);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* @description Handles the file upload completion callback.
|
|
411
|
-
* - Parses the response and registers the uploaded file.
|
|
412
|
-
* @param {XMLHttpRequest} xmlHttp - The completed XHR request.
|
|
413
|
-
*/
|
|
414
|
-
#_uploadCallBack(xmlHttp) {
|
|
415
|
-
const response = JSON.parse(xmlHttp.responseText);
|
|
416
|
-
if (response.errorMessage) {
|
|
417
|
-
this._error(response);
|
|
418
|
-
} else {
|
|
419
|
-
this._register(response);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* @description Handles the change event when a file is selected.
|
|
425
|
-
* - Triggers the file upload process.
|
|
426
|
-
* @param {InputEvent} e - The change event object.
|
|
427
|
-
*/
|
|
428
|
-
async #OnChangeFile(e) {
|
|
429
|
-
/** @type {HTMLInputElement} */
|
|
430
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
431
|
-
await this.submitFile(eventTarget.files);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function CreateHTML_controller({ lang, icons }) {
|
|
436
|
-
const html = /*html*/ `
|
|
437
|
-
<div class="se-arrow se-arrow-up"></div>
|
|
438
|
-
<form>
|
|
439
|
-
<div class="se-btn-group se-form-group">
|
|
440
|
-
<input type="text" />
|
|
441
|
-
<button type="submit" data-command="edit" class="se-btn se-tooltip se-btn-success">
|
|
442
|
-
${icons.checked}
|
|
443
|
-
<span class="se-tooltip-inner"><span class="se-tooltip-text">${lang.save}</span></span>
|
|
444
|
-
</button>
|
|
445
|
-
<button type="button" data-command="cancel" class="se-btn se-tooltip se-btn-danger">
|
|
446
|
-
${icons.cancel}
|
|
447
|
-
<span class="se-tooltip-inner"><span class="se-tooltip-text">${lang.cancel}</span></span>
|
|
448
|
-
</button>
|
|
449
|
-
</div>
|
|
450
|
-
</form>
|
|
451
|
-
`;
|
|
452
|
-
|
|
453
|
-
return dom.utils.createElement('DIV', { class: 'se-controller se-controller-simple-input' }, html);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
export default FileUpload;
|
|
1
|
+
import EditorInjector from '../../editorInjector';
|
|
2
|
+
import { dom, env, numbers } from '../../helper';
|
|
3
|
+
import { FileManager, Figure, Controller } from '../../modules';
|
|
4
|
+
|
|
5
|
+
const { NO_EVENT } = env;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @class
|
|
9
|
+
* @description File upload plugin
|
|
10
|
+
*/
|
|
11
|
+
class FileUpload extends EditorInjector {
|
|
12
|
+
static key = 'fileUpload';
|
|
13
|
+
static type = 'command';
|
|
14
|
+
static className = '';
|
|
15
|
+
static options = { eventIndex: 10000 };
|
|
16
|
+
/**
|
|
17
|
+
* @this {FileUpload}
|
|
18
|
+
* @param {HTMLElement} node - The node to check.
|
|
19
|
+
* @returns {HTMLElement|null} Returns a node if the node is a valid component.
|
|
20
|
+
*/
|
|
21
|
+
static component(node) {
|
|
22
|
+
return dom.check.isAnchor(node) && node.hasAttribute('data-se-file-download') ? node : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @constructor
|
|
27
|
+
* @param {__se__EditorCore} editor - The root editor instance
|
|
28
|
+
* @param {Object} pluginOptions - plugin options
|
|
29
|
+
* @param {string} pluginOptions.uploadUrl - server request url
|
|
30
|
+
* @param {Object<string, string>=} pluginOptions.uploadHeaders - server request headers
|
|
31
|
+
* @param {string=} pluginOptions.uploadSizeLimit - upload size limit
|
|
32
|
+
* @param {string=} pluginOptions.uploadSingleSizeLimit - upload single size limit
|
|
33
|
+
* @param {boolean=} pluginOptions.allowMultiple - allow multiple files
|
|
34
|
+
* @param {string=} pluginOptions.acceptedFormats - accepted formats
|
|
35
|
+
* @param {string=} pluginOptions.as - Whether to use the 'Box' or 'Link' conversion button
|
|
36
|
+
* @param {Array<string>} pluginOptions.controls - Additional controls to be added to the figure
|
|
37
|
+
*/
|
|
38
|
+
constructor(editor, pluginOptions) {
|
|
39
|
+
super(editor);
|
|
40
|
+
// plugin basic properties
|
|
41
|
+
this.title = this.lang.fileUpload;
|
|
42
|
+
this.icon = 'file_upload';
|
|
43
|
+
|
|
44
|
+
if (!pluginOptions.uploadUrl) console.warn('[SUNEDITOR.fileUpload.warn] "fileUpload" plugin must be have "uploadUrl" option.');
|
|
45
|
+
|
|
46
|
+
// members
|
|
47
|
+
this.uploadUrl = pluginOptions.uploadUrl;
|
|
48
|
+
this.uploadHeaders = pluginOptions.uploadHeaders;
|
|
49
|
+
this.uploadSizeLimit = numbers.get(pluginOptions.uploadSizeLimit, 0);
|
|
50
|
+
this.uploadSingleSizeLimit = numbers.get(pluginOptions.uploadSingleSizeLimit, 0);
|
|
51
|
+
this.allowMultiple = !!pluginOptions.allowMultiple;
|
|
52
|
+
this.acceptedFormats = typeof pluginOptions.acceptedFormats !== 'string' ? '*' : pluginOptions.acceptedFormats.trim() || '*';
|
|
53
|
+
this._acceptedCheck = this.acceptedFormats.split(', ');
|
|
54
|
+
this.as = pluginOptions.as || 'box';
|
|
55
|
+
this.input = dom.utils.createElement('input', { type: 'file', accept: this.acceptedFormats });
|
|
56
|
+
if (this.allowMultiple) {
|
|
57
|
+
this.input.setAttribute('multiple', 'multiple');
|
|
58
|
+
}
|
|
59
|
+
this._element = null;
|
|
60
|
+
|
|
61
|
+
// figure
|
|
62
|
+
const customItems = {
|
|
63
|
+
'custom-download': {
|
|
64
|
+
command: 'download',
|
|
65
|
+
title: this.lang.download,
|
|
66
|
+
icon: 'download',
|
|
67
|
+
action: (target) => {
|
|
68
|
+
const url = target.getAttribute('href');
|
|
69
|
+
if (url) dom.utils.createElement('A', { href: url }, null).click();
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
'custom-as': {
|
|
73
|
+
command: 'as',
|
|
74
|
+
value: 'link', // 'block' or 'link'
|
|
75
|
+
title: this.lang.asLink,
|
|
76
|
+
icon: 'reduction',
|
|
77
|
+
action: (target, value) => {
|
|
78
|
+
this.convertFormat(target, value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const figureControls = (pluginOptions.controls || [['custom-as', 'align', 'edit', 'custom-download', 'copy', 'remove']]).map((subArray) => subArray.map((item) => (item.startsWith('custom-') ? customItems[item] : item)));
|
|
84
|
+
this.figure = new Figure(this, figureControls, {});
|
|
85
|
+
|
|
86
|
+
// file manager
|
|
87
|
+
this.fileManager = new FileManager(this, {
|
|
88
|
+
query: 'a[download][data-se-file-download]',
|
|
89
|
+
loadHandler: this.events.onFileLoad,
|
|
90
|
+
eventHandler: this.events.onFileAction
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// controller
|
|
94
|
+
if (/\bedit\b/.test(JSON.stringify(figureControls))) {
|
|
95
|
+
const controllerEl = CreateHTML_controller(this);
|
|
96
|
+
this.controller = new Controller(this, controllerEl, { position: 'bottom', disabled: true }, FileUpload.key);
|
|
97
|
+
this.editInput = controllerEl.querySelector('input');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// init
|
|
101
|
+
this.eventManager.addEvent(this.input, 'change', this.#OnChangeFile.bind(this));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @editorMethod Editor.core
|
|
106
|
+
* @description Executes the main execution method of the plugin.
|
|
107
|
+
* - It is executed by clicking a toolbar "command" button or calling an API.
|
|
108
|
+
*/
|
|
109
|
+
action() {
|
|
110
|
+
this.editor._preventBlur = true;
|
|
111
|
+
this.input.click();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @editorMethod Editor.Component
|
|
116
|
+
* @description Executes the method that is called when a component of a plugin is selected.
|
|
117
|
+
* @param {HTMLElement} target Target component element
|
|
118
|
+
*/
|
|
119
|
+
select(target) {
|
|
120
|
+
this._element = target;
|
|
121
|
+
const asBtn = this.figure.controller.form.querySelector('[data-command="__c__as"]');
|
|
122
|
+
if (dom.check.isFigure(target.parentElement)) {
|
|
123
|
+
asBtn.innerHTML = this.icons.reduction + dom.utils.createTooltipInner(this.lang.asLink);
|
|
124
|
+
asBtn.setAttribute('data-value', 'link');
|
|
125
|
+
this.figure.open(target, { nonResizing: true, nonSizeInfo: true, nonBorder: true, figureTarget: true, __fileManagerInfo: false });
|
|
126
|
+
} else {
|
|
127
|
+
asBtn.innerHTML = this.icons.expansion + dom.utils.createTooltipInner(this.lang.asBlock);
|
|
128
|
+
asBtn.setAttribute('data-value', 'box');
|
|
129
|
+
this.figure.controllerOpen(target, { isWWTarget: true });
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @editorMethod Editor.EventManager
|
|
136
|
+
* @description Executes the event function of "paste" or "drop".
|
|
137
|
+
* @param {Object} params { frameContext, event, file }
|
|
138
|
+
* @param {__se__FrameContext} params.frameContext Frame context
|
|
139
|
+
* @param {ClipboardEvent} params.event Event object
|
|
140
|
+
* @param {File} params.file File object
|
|
141
|
+
* @returns {boolean} - If return false, the file upload will be canceled
|
|
142
|
+
*/
|
|
143
|
+
onFilePasteAndDrop({ file }) {
|
|
144
|
+
const fileType = file.type;
|
|
145
|
+
if (
|
|
146
|
+
!this._acceptedCheck.some((format) => {
|
|
147
|
+
if (format.startsWith('*')) return true;
|
|
148
|
+
if (format.startsWith(fileType)) return true;
|
|
149
|
+
})
|
|
150
|
+
) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.submitFile([file]);
|
|
155
|
+
this.editor.focus();
|
|
156
|
+
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @editorMethod Modules.Controller
|
|
162
|
+
* @description Executes the method that is called when a target component is edited.
|
|
163
|
+
* @param {HTMLElement|Text} target Target element
|
|
164
|
+
*/
|
|
165
|
+
edit(target) {
|
|
166
|
+
this.editInput.value = target.textContent;
|
|
167
|
+
this.figure.controllerHide();
|
|
168
|
+
this.controller.open(target, null, { isWWTarget: !dom.check.isFigure(target.parentElement), initMethod: null, addOffset: null });
|
|
169
|
+
this.editInput.focus();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @editorMethod Modules.Controller
|
|
174
|
+
* @description Executes the method that is called when a button is clicked in the "controller".
|
|
175
|
+
* @param {HTMLButtonElement} target Target button element
|
|
176
|
+
*/
|
|
177
|
+
controllerAction(target) {
|
|
178
|
+
const command = target.getAttribute('data-command');
|
|
179
|
+
if (!command) return;
|
|
180
|
+
|
|
181
|
+
if (command === 'edit') {
|
|
182
|
+
if (this.editInput.value.trim().length === 0) return;
|
|
183
|
+
this._element.textContent = this.editInput.value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.controller.close();
|
|
187
|
+
this.component.select(this._element, FileUpload.key);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @editorMethod Editor.Component
|
|
192
|
+
* @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
|
|
193
|
+
* @param {HTMLElement} target Target element
|
|
194
|
+
* @returns {Promise<void>}
|
|
195
|
+
*/
|
|
196
|
+
async destroy(target) {
|
|
197
|
+
if (!target) return;
|
|
198
|
+
|
|
199
|
+
const figure = Figure.GetContainer(target);
|
|
200
|
+
const containerTarget = dom.query.getParentElement(target, '.se-component') || target;
|
|
201
|
+
|
|
202
|
+
const message = await this.triggerEvent('onFileDeleteBefore', { element: figure.target, container: figure, url: figure.target.getAttribute('href') });
|
|
203
|
+
if (message === false) return;
|
|
204
|
+
|
|
205
|
+
const isInlineComp = this.component.isInline(containerTarget);
|
|
206
|
+
const focusEl = isInlineComp ? containerTarget.previousSibling || containerTarget.nextSibling : containerTarget.previousElementSibling || containerTarget.nextElementSibling;
|
|
207
|
+
dom.utils.removeItem(containerTarget);
|
|
208
|
+
this.ui._offCurrentController();
|
|
209
|
+
|
|
210
|
+
this.editor.focusEdge(focusEl);
|
|
211
|
+
this.history.push(false);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @description Create an "file" component using the provided files.
|
|
216
|
+
* @param {File[]|FileList} fileList File object list
|
|
217
|
+
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
218
|
+
*/
|
|
219
|
+
async submitFile(fileList) {
|
|
220
|
+
if (fileList.length === 0) return;
|
|
221
|
+
|
|
222
|
+
let fileSize = 0;
|
|
223
|
+
const files = [];
|
|
224
|
+
const slngleSizeLimit = this.uploadSingleSizeLimit;
|
|
225
|
+
for (let i = 0, len = fileList.length, f, s; i < len; i++) {
|
|
226
|
+
f = fileList[i];
|
|
227
|
+
s = f.size;
|
|
228
|
+
if (slngleSizeLimit && slngleSizeLimit > s) {
|
|
229
|
+
const err = '[SUNEDITOR.fileUpload.fail] Size of uploadable single file: ' + slngleSizeLimit / 1000 + 'KB';
|
|
230
|
+
const message = await this.triggerEvent('onFileUploadError', {
|
|
231
|
+
error: err,
|
|
232
|
+
limitSize: slngleSizeLimit,
|
|
233
|
+
uploadSize: s,
|
|
234
|
+
file: f
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
238
|
+
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
files.push(f);
|
|
243
|
+
fileSize += s;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const limitSize = this.uploadSizeLimit;
|
|
247
|
+
const currentSize = this.fileManager.getSize();
|
|
248
|
+
if (limitSize > 0 && fileSize + currentSize > limitSize) {
|
|
249
|
+
const err = '[SUNEDITOR.fileUpload.fail] Size of uploadable total files: ' + limitSize / 1000 + 'KB';
|
|
250
|
+
const message = await this.triggerEvent('onFileUploadError', {
|
|
251
|
+
error: err,
|
|
252
|
+
limitSize,
|
|
253
|
+
currentSize,
|
|
254
|
+
uploadSize: fileSize
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const fileInfo = {
|
|
263
|
+
url: this.uploadUrl,
|
|
264
|
+
uploadHeaders: this.uploadHeaders,
|
|
265
|
+
files
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const handler = async function (uploadCallback, infos, newInfos) {
|
|
269
|
+
infos = newInfos || infos;
|
|
270
|
+
const xmlHttp = await this.fileManager.asyncUpload(infos.url, infos.uploadHeaders, infos.files);
|
|
271
|
+
uploadCallback(xmlHttp);
|
|
272
|
+
}.bind(this, this.#_uploadCallBack.bind(this), fileInfo);
|
|
273
|
+
|
|
274
|
+
const result = await this.triggerEvent('onFileUploadBefore', {
|
|
275
|
+
info: fileInfo,
|
|
276
|
+
handler
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (result === undefined) return true;
|
|
280
|
+
if (result === false) return false;
|
|
281
|
+
if (result !== null && typeof result === 'object') handler(result);
|
|
282
|
+
|
|
283
|
+
if (result === true || result === NO_EVENT) handler(null);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @description Convert format to link or block
|
|
288
|
+
* @param {HTMLElement} target Target element
|
|
289
|
+
* @param {string} value 'link' or 'block'
|
|
290
|
+
*/
|
|
291
|
+
convertFormat(target, value) {
|
|
292
|
+
if (value === 'link') {
|
|
293
|
+
this.figure.close();
|
|
294
|
+
const { container } = Figure.GetContainer(target);
|
|
295
|
+
const next = container.nextElementSibling;
|
|
296
|
+
const parent = container.parentElement;
|
|
297
|
+
|
|
298
|
+
target.removeAttribute('data-se-non-focus');
|
|
299
|
+
target.setAttribute('contenteditable', 'false');
|
|
300
|
+
dom.utils.addClass(target, 'se-component|se-inline-component');
|
|
301
|
+
|
|
302
|
+
const line = dom.utils.createElement(this.options.get('defaultLine'), null, target);
|
|
303
|
+
parent.insertBefore(line, next);
|
|
304
|
+
dom.utils.removeItem(container);
|
|
305
|
+
} else {
|
|
306
|
+
// block
|
|
307
|
+
this.selection.setRange(target, 0, target, 1);
|
|
308
|
+
const r = this.html.remove();
|
|
309
|
+
const s = this.nodeTransform.split(r.container, r.offset, 0);
|
|
310
|
+
|
|
311
|
+
if (s?.previousElementSibling && dom.check.isZeroWidth(s.previousElementSibling)) {
|
|
312
|
+
dom.utils.removeItem(s.previousElementSibling);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
target.setAttribute('data-se-non-focus', 'true');
|
|
316
|
+
target.removeAttribute('contenteditable');
|
|
317
|
+
dom.utils.removeClass(target, 'se-component|se-component-selected|se-inline-component');
|
|
318
|
+
|
|
319
|
+
const figure = Figure.CreateContainer(target, 'se-file-figure se-flex-component');
|
|
320
|
+
(s || r.container).parentElement.insertBefore(figure.container, s);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this.history.push(false);
|
|
324
|
+
this.component.select(target, FileUpload.key);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @description Create file element
|
|
329
|
+
* @param {string} url File URL
|
|
330
|
+
* @param {File|{name: string, size: number}} file File object
|
|
331
|
+
* @param {boolean} isLast Is last file
|
|
332
|
+
*/
|
|
333
|
+
create(url, file, isLast) {
|
|
334
|
+
const name = file.name || url;
|
|
335
|
+
const a = dom.utils.createElement(
|
|
336
|
+
'A',
|
|
337
|
+
{
|
|
338
|
+
href: url,
|
|
339
|
+
title: name,
|
|
340
|
+
download: name,
|
|
341
|
+
'data-se-file-download': '',
|
|
342
|
+
contenteditable: 'false',
|
|
343
|
+
'data-se-non-focus': 'true',
|
|
344
|
+
'data-se-non-link': 'true'
|
|
345
|
+
},
|
|
346
|
+
name
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
this.fileManager.setFileData(a, file);
|
|
350
|
+
|
|
351
|
+
if (this.as === 'link') {
|
|
352
|
+
a.className = 'se-component se-inline-component';
|
|
353
|
+
this.component.insert(a, { skipCharCount: false, skipSelection: false, skipHistory: false });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const figure = Figure.CreateContainer(a);
|
|
358
|
+
dom.utils.addClass(figure.container, 'se-file-figure|se-flex-component');
|
|
359
|
+
|
|
360
|
+
if (!this.component.insert(figure.container, { skipCharCount: false, skipSelection: isLast ? !this.options.get('componentAutoSelect') : true, skipHistory: false })) {
|
|
361
|
+
this.editor.focus();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!isLast) return;
|
|
366
|
+
|
|
367
|
+
if (!this.options.get('componentAutoSelect')) {
|
|
368
|
+
const line = this.format.addLine(figure.container, null);
|
|
369
|
+
if (line) this.selection.setRange(line, 0, line, 0);
|
|
370
|
+
} else {
|
|
371
|
+
this.component.select(a, FileUpload.key);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @private
|
|
377
|
+
* @description Processes the server response after file upload.
|
|
378
|
+
* - Registers the uploaded files in the editor.
|
|
379
|
+
* @param {Object<string, *>} response - The response object from the server.
|
|
380
|
+
*/
|
|
381
|
+
_register(response) {
|
|
382
|
+
response.result.forEach((file, i, a) => {
|
|
383
|
+
this.create(
|
|
384
|
+
file.url,
|
|
385
|
+
{
|
|
386
|
+
name: file.name,
|
|
387
|
+
size: file.size
|
|
388
|
+
},
|
|
389
|
+
i === a.length - 1
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @private
|
|
396
|
+
* @description Handles file upload errors.
|
|
397
|
+
* - Displays an error message if the upload fails.
|
|
398
|
+
* @param {Object<string, *>} response - The error response from the server.
|
|
399
|
+
* @returns {Promise<void>}
|
|
400
|
+
*/
|
|
401
|
+
async _error(response) {
|
|
402
|
+
const message = await this.triggerEvent('onFileUploadError', { error: response });
|
|
403
|
+
if (message === false) return;
|
|
404
|
+
const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
|
|
405
|
+
this.ui.alertOpen(err, 'error');
|
|
406
|
+
console.error('[SUNEDITOR.plugin.fileUpload.error]', err);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* @description Handles the file upload completion callback.
|
|
411
|
+
* - Parses the response and registers the uploaded file.
|
|
412
|
+
* @param {XMLHttpRequest} xmlHttp - The completed XHR request.
|
|
413
|
+
*/
|
|
414
|
+
#_uploadCallBack(xmlHttp) {
|
|
415
|
+
const response = JSON.parse(xmlHttp.responseText);
|
|
416
|
+
if (response.errorMessage) {
|
|
417
|
+
this._error(response);
|
|
418
|
+
} else {
|
|
419
|
+
this._register(response);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @description Handles the change event when a file is selected.
|
|
425
|
+
* - Triggers the file upload process.
|
|
426
|
+
* @param {InputEvent} e - The change event object.
|
|
427
|
+
*/
|
|
428
|
+
async #OnChangeFile(e) {
|
|
429
|
+
/** @type {HTMLInputElement} */
|
|
430
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
431
|
+
await this.submitFile(eventTarget.files);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function CreateHTML_controller({ lang, icons }) {
|
|
436
|
+
const html = /*html*/ `
|
|
437
|
+
<div class="se-arrow se-arrow-up"></div>
|
|
438
|
+
<form>
|
|
439
|
+
<div class="se-btn-group se-form-group">
|
|
440
|
+
<input type="text" />
|
|
441
|
+
<button type="submit" data-command="edit" class="se-btn se-tooltip se-btn-success">
|
|
442
|
+
${icons.checked}
|
|
443
|
+
<span class="se-tooltip-inner"><span class="se-tooltip-text">${lang.save}</span></span>
|
|
444
|
+
</button>
|
|
445
|
+
<button type="button" data-command="cancel" class="se-btn se-tooltip se-btn-danger">
|
|
446
|
+
${icons.cancel}
|
|
447
|
+
<span class="se-tooltip-inner"><span class="se-tooltip-text">${lang.cancel}</span></span>
|
|
448
|
+
</button>
|
|
449
|
+
</div>
|
|
450
|
+
</form>
|
|
451
|
+
`;
|
|
452
|
+
|
|
453
|
+
return dom.utils.createElement('DIV', { class: 'se-controller se-controller-simple-input' }, html);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export default FileUpload;
|