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,1376 +1,1377 @@
|
|
|
1
|
-
import EditorInjector from '../../editorInjector';
|
|
2
|
-
import { Modal, Figure, FileManager, ModalAnchorEditor } from '../../modules';
|
|
3
|
-
import { dom, numbers, env, keyCodeMap } from '../../helper';
|
|
4
|
-
const { NO_EVENT } = env;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {import('../../events').ImageInfo} ImageInfo_image
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {import('../../modules/Figure').FigureControls} FigureControls_image
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {Object} ImagePluginOptions
|
|
16
|
-
* @property {boolean} [canResize=true] - Whether the image element can be resized.
|
|
17
|
-
* @property {boolean} [showHeightInput=true] - Whether to display the height input field.
|
|
18
|
-
* @property {string} [defaultWidth="auto"] - The default width of the image. If a number is provided, "px" will be appended.
|
|
19
|
-
* @property {string} [defaultHeight="auto"] - The default height of the image. If a number is provided, "px" will be appended.
|
|
20
|
-
* @property {boolean} [percentageOnlySize=false] - Whether to allow only percentage-based sizing.
|
|
21
|
-
* @property {boolean} [createFileInput=true] - Whether to create a file input element for image uploads.
|
|
22
|
-
* @property {boolean} [createUrlInput=true] - Whether to create a URL input element for image insertion.
|
|
23
|
-
* @property {string} [uploadUrl] - The URL endpoint for image file uploads.
|
|
24
|
-
* @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the file upload request.
|
|
25
|
-
* @property {number} [uploadSizeLimit] - The total upload size limit in bytes.
|
|
26
|
-
* @property {number} [uploadSingleSizeLimit] - The single file upload size limit in bytes.
|
|
27
|
-
* @property {boolean} [allowMultiple=false] - Whether multiple image uploads are allowed.
|
|
28
|
-
* @property {string} [acceptedFormats="image/*"] - The accepted file formats for image uploads.
|
|
29
|
-
* @property {boolean} [useFormatType=true] - Whether to enable format type selection (block or inline).
|
|
30
|
-
* @property {string} [defaultFormatType="block"] - The default image format type ("block" or "inline").
|
|
31
|
-
* @property {boolean} [keepFormatType=false] - Whether to retain the chosen format type after image insertion.
|
|
32
|
-
* @property {boolean} [linkEnableFileUpload] - Whether to enable file uploads for linked images.
|
|
33
|
-
* @property {FigureControls_image} [controls] - Figure controls.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @class
|
|
38
|
-
* @description Image plugin.
|
|
39
|
-
* - This plugin provides image insertion functionality within the editor, supporting both file upload and URL input.
|
|
40
|
-
*/
|
|
41
|
-
class Image_ extends EditorInjector {
|
|
42
|
-
static key = 'image';
|
|
43
|
-
static type = 'modal';
|
|
44
|
-
static className = '';
|
|
45
|
-
/**
|
|
46
|
-
* @this {Image_}
|
|
47
|
-
* @param {Element} node - The node to check.
|
|
48
|
-
* @returns {Element|null} Returns a node if the node is a valid component.
|
|
49
|
-
*/
|
|
50
|
-
static component(node) {
|
|
51
|
-
const compNode = dom.check.isFigure(node) || (/^span$/i.test(node.nodeName) && dom.utils.hasClass(node, 'se-component')) ? node.firstElementChild : node;
|
|
52
|
-
return /^IMG$/i.test(compNode?.nodeName) ? compNode : dom.check.isAnchor(compNode) && /^IMG$/i.test(compNode?.firstElementChild?.nodeName) ? compNode?.firstElementChild : null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @constructor
|
|
57
|
-
* @param {__se__EditorCore} editor - The root editor instance
|
|
58
|
-
* @param {ImagePluginOptions} pluginOptions
|
|
59
|
-
*/
|
|
60
|
-
constructor(editor, pluginOptions) {
|
|
61
|
-
// plugin bisic properties
|
|
62
|
-
super(editor);
|
|
63
|
-
this.title = this.lang.image;
|
|
64
|
-
this.icon = 'image';
|
|
65
|
-
|
|
66
|
-
this.pluginOptions = {
|
|
67
|
-
canResize: pluginOptions.canResize === undefined ? true : pluginOptions.canResize,
|
|
68
|
-
showHeightInput: pluginOptions.showHeightInput === undefined ? true : !!pluginOptions.showHeightInput,
|
|
69
|
-
defaultWidth: !pluginOptions.defaultWidth ? 'auto' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
|
|
70
|
-
defaultHeight: !pluginOptions.defaultHeight ? 'auto' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
|
|
71
|
-
percentageOnlySize: !!pluginOptions.percentageOnlySize,
|
|
72
|
-
createFileInput: pluginOptions.createFileInput === undefined ? true : pluginOptions.createFileInput,
|
|
73
|
-
createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
|
|
74
|
-
uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
|
|
75
|
-
uploadHeaders: pluginOptions.uploadHeaders || null,
|
|
76
|
-
uploadSizeLimit: numbers.get(pluginOptions.uploadSizeLimit, 0),
|
|
77
|
-
uploadSingleSizeLimit: numbers.get(pluginOptions.uploadSingleSizeLimit, 0),
|
|
78
|
-
allowMultiple: !!pluginOptions.allowMultiple,
|
|
79
|
-
acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'image/*' : pluginOptions.acceptedFormats.trim() || 'image/*',
|
|
80
|
-
useFormatType: pluginOptions.useFormatType ?? true,
|
|
81
|
-
defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : 'block',
|
|
82
|
-
keepFormatType: pluginOptions.keepFormatType ?? false
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// create HTML
|
|
86
|
-
const sizeUnit = this.pluginOptions.percentageOnlySize ? '%' : 'px';
|
|
87
|
-
const modalEl = CreateHTML_modal(editor, this.pluginOptions);
|
|
88
|
-
const ctrlAs = this.pluginOptions.useFormatType ? 'as' : '';
|
|
89
|
-
const figureControls =
|
|
90
|
-
pluginOptions.controls || !this.pluginOptions.canResize
|
|
91
|
-
? [[ctrlAs, 'mirror_h', 'mirror_v', 'align', 'caption', 'edit', 'revert', 'copy', 'remove']]
|
|
92
|
-
: [
|
|
93
|
-
[ctrlAs, 'resize_auto,100,75,50', 'rotate_l', 'rotate_r', 'mirror_h', 'mirror_v'],
|
|
94
|
-
['
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
// show align
|
|
98
|
-
this.alignForm = modalEl.alignForm;
|
|
99
|
-
if (!figureControls.some((subArray) => subArray.includes('align'))) this.alignForm.style.display = 'none';
|
|
100
|
-
|
|
101
|
-
// modules
|
|
102
|
-
const Link = this.plugins.link ? this.plugins.link.pluginOptions : {};
|
|
103
|
-
this.anchor = new ModalAnchorEditor(this, modalEl.html, {
|
|
104
|
-
textToDisplay: false,
|
|
105
|
-
title: true,
|
|
106
|
-
openNewWindow: Link.openNewWindow,
|
|
107
|
-
relList: Link.relList,
|
|
108
|
-
defaultRel: Link.defaultRel,
|
|
109
|
-
noAutoPrefix: Link.noAutoPrefix,
|
|
110
|
-
enableFileUpload: pluginOptions.linkEnableFileUpload
|
|
111
|
-
});
|
|
112
|
-
this.modal = new Modal(this, modalEl.html);
|
|
113
|
-
this.figure = new Figure(this, figureControls, {
|
|
114
|
-
sizeUnit: sizeUnit
|
|
115
|
-
});
|
|
116
|
-
this.fileManager = new FileManager(this, {
|
|
117
|
-
query: 'img',
|
|
118
|
-
loadHandler: this.events.onImageLoad,
|
|
119
|
-
eventHandler: this.events.onImageAction
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// members
|
|
123
|
-
this.fileModalWrapper = modalEl.fileModalWrapper;
|
|
124
|
-
this.imgInputFile = modalEl.imgInputFile;
|
|
125
|
-
this.imgUrlFile = modalEl.imgUrlFile;
|
|
126
|
-
this.focusElement = this.imgInputFile || this.imgUrlFile;
|
|
127
|
-
this.altText = modalEl.altText;
|
|
128
|
-
this.captionCheckEl = modalEl.captionCheckEl;
|
|
129
|
-
this.captionEl = this.captionCheckEl?.parentElement;
|
|
130
|
-
this.previewSrc = modalEl.previewSrc;
|
|
131
|
-
this.sizeUnit = sizeUnit;
|
|
132
|
-
this.as = 'block';
|
|
133
|
-
this.proportion = null;
|
|
134
|
-
this.inputX = null;
|
|
135
|
-
this.inputY = null;
|
|
136
|
-
this._linkElement = null;
|
|
137
|
-
this._linkValue = '';
|
|
138
|
-
this._align = 'none';
|
|
139
|
-
this._svgDefaultSize = '30%';
|
|
140
|
-
this._base64RenderIndex = 0;
|
|
141
|
-
this._element = null;
|
|
142
|
-
this._cover = null;
|
|
143
|
-
this._container = null;
|
|
144
|
-
this._caption = null;
|
|
145
|
-
this._ratio = {
|
|
146
|
-
w: 1,
|
|
147
|
-
h: 1
|
|
148
|
-
};
|
|
149
|
-
this._origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
|
|
150
|
-
this._origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
|
|
151
|
-
this._resizing = this.pluginOptions.canResize;
|
|
152
|
-
this._onlyPercentage = this.pluginOptions.percentageOnlySize;
|
|
153
|
-
this._nonResizing = !this._resizing || !this.pluginOptions.showHeightInput || this._onlyPercentage;
|
|
154
|
-
|
|
155
|
-
// init
|
|
156
|
-
this.eventManager.addEvent(modalEl.tabs, 'click', this.#OpenTab.bind(this));
|
|
157
|
-
if (this.imgInputFile) this.eventManager.addEvent(modalEl.fileRemoveBtn, 'click', this.#RemoveSelectedFiles.bind(this));
|
|
158
|
-
if (this.imgUrlFile) this.eventManager.addEvent(this.imgUrlFile, 'input', this.#OnLinkPreview.bind(this));
|
|
159
|
-
if (this.imgInputFile && this.imgUrlFile) this.eventManager.addEvent(this.imgInputFile, 'change', this.#OnfileInputChange.bind(this));
|
|
160
|
-
|
|
161
|
-
const galleryButton = modalEl.galleryButton;
|
|
162
|
-
if (galleryButton) this.eventManager.addEvent(galleryButton, 'click', this.#OpenGallery.bind(this));
|
|
163
|
-
|
|
164
|
-
if (this._resizing) {
|
|
165
|
-
this.proportion = modalEl.proportion;
|
|
166
|
-
this.inputX = modalEl.inputX;
|
|
167
|
-
this.inputY = modalEl.inputY;
|
|
168
|
-
this.inputX.value = this.pluginOptions.defaultWidth;
|
|
169
|
-
this.inputY.value = this.pluginOptions.defaultHeight;
|
|
170
|
-
|
|
171
|
-
const ratioChange = this.#OnChangeRatio.bind(this);
|
|
172
|
-
this.eventManager.addEvent(this.inputX, 'keyup', this.#OnInputSize.bind(this, 'x'));
|
|
173
|
-
this.eventManager.addEvent(this.inputY, 'keyup', this.#OnInputSize.bind(this, 'y'));
|
|
174
|
-
this.eventManager.addEvent(this.inputX, 'change', ratioChange);
|
|
175
|
-
this.eventManager.addEvent(this.inputY, 'change', ratioChange);
|
|
176
|
-
this.eventManager.addEvent(this.proportion, 'change', ratioChange);
|
|
177
|
-
this.eventManager.addEvent(modalEl.revertBtn, 'click', this.#OnClickRevert.bind(this));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (this.pluginOptions.useFormatType) {
|
|
181
|
-
this.as = this.pluginOptions.defaultFormatType;
|
|
182
|
-
this.asBlock = modalEl.asBlock;
|
|
183
|
-
this.asInline = modalEl.asInline;
|
|
184
|
-
this.eventManager.addEvent([this.asBlock, this.asInline], 'click', this.#OnClickAsButton.bind(this));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* @editorMethod Modules.Modal
|
|
190
|
-
* @description Executes the method that is called when a "Modal" module's is opened.
|
|
191
|
-
*/
|
|
192
|
-
open() {
|
|
193
|
-
this.modal.open();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* @editorMethod Modules.Controller(Figure)
|
|
198
|
-
* @description Executes the method that is called when a target component is edited.
|
|
199
|
-
*/
|
|
200
|
-
edit() {
|
|
201
|
-
this.modal.open();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* @editorMethod Modules.Modal
|
|
206
|
-
* @description Executes the method that is called when a plugin's modal is opened.
|
|
207
|
-
* @param {boolean} isUpdate "Indicates whether the modal is for editing an existing component (true) or registering a new one (false)."
|
|
208
|
-
*/
|
|
209
|
-
on(isUpdate) {
|
|
210
|
-
if (!isUpdate) {
|
|
211
|
-
if (this._resizing) {
|
|
212
|
-
this.inputX.value = this._origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
|
|
213
|
-
this.inputY.value = this._origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
|
|
214
|
-
}
|
|
215
|
-
if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.setAttribute('multiple', 'multiple');
|
|
216
|
-
} else {
|
|
217
|
-
if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.removeAttribute('multiple');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
this.anchor.on(isUpdate);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* @editorMethod Editor.EventManager
|
|
225
|
-
* @description Executes the event function of "paste" or "drop".
|
|
226
|
-
* @param {Object} params { frameContext, event, file }
|
|
227
|
-
* @param {__se__FrameContext} params.frameContext Frame context
|
|
228
|
-
* @param {ClipboardEvent} params.event Event object
|
|
229
|
-
* @param {File} params.file File object
|
|
230
|
-
* @returns {boolean} - If return false, the file upload will be canceled
|
|
231
|
-
*/
|
|
232
|
-
onFilePasteAndDrop({ file }) {
|
|
233
|
-
if (!/^image/.test(file.type)) return;
|
|
234
|
-
|
|
235
|
-
this.submitFile([file]);
|
|
236
|
-
this.editor.focus();
|
|
237
|
-
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* @editorMethod Modules.Modal
|
|
243
|
-
* @description This function is called when a form within a modal window is "submit".
|
|
244
|
-
* @returns {Promise<boolean>} Success or failure
|
|
245
|
-
*/
|
|
246
|
-
async modalAction() {
|
|
247
|
-
this._align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"]:checked')).value;
|
|
248
|
-
|
|
249
|
-
if (this.modal.isUpdate) {
|
|
250
|
-
this._update(this.inputX?.value, this.inputY?.value);
|
|
251
|
-
this.history.push(false);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (this.imgInputFile && this.imgInputFile.files.length > 0) {
|
|
255
|
-
return await this.submitFile(this.imgInputFile.files);
|
|
256
|
-
} else if (this.imgUrlFile && this._linkValue.length > 0) {
|
|
257
|
-
return await this.submitURL(this._linkValue);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* @editorMethod Editor.core
|
|
265
|
-
* @description This method is used to validate and preserve the format of the component within the editor.
|
|
266
|
-
* - It ensures that the structure and attributes of the element are maintained and secure.
|
|
267
|
-
* - The method checks if the element is already wrapped in a valid container and updates its attributes if necessary.
|
|
268
|
-
* - If the element isn't properly contained, a new container is created to retain the format.
|
|
269
|
-
* @returns {{query: string, method: (element: HTMLImageElement) => void}} The format retention object containing the query and method to process the element.
|
|
270
|
-
* - query: The selector query to identify the relevant elements (in this case, 'audio').
|
|
271
|
-
* - method:The function to execute on the element to validate and preserve its format.
|
|
272
|
-
* - The function takes the element as an argument, checks if it is contained correctly, and applies necessary adjustments.
|
|
273
|
-
*/
|
|
274
|
-
retainFormat() {
|
|
275
|
-
return {
|
|
276
|
-
query: 'img',
|
|
277
|
-
method: (element) => {
|
|
278
|
-
const figureInfo = Figure.GetContainer(element);
|
|
279
|
-
if (figureInfo && figureInfo.container && figureInfo.cover) return;
|
|
280
|
-
|
|
281
|
-
this._ready(element);
|
|
282
|
-
this._fileCheck(this._origin_w, this._origin_h);
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* @editorMethod Modules.Modal
|
|
289
|
-
* @description This function is called before the modal window is opened, but before it is closed.
|
|
290
|
-
*/
|
|
291
|
-
init() {
|
|
292
|
-
Modal.OnChangeFile(this.fileModalWrapper, []);
|
|
293
|
-
if (this.imgInputFile) this.imgInputFile.value = '';
|
|
294
|
-
if (this.imgUrlFile) this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = '';
|
|
295
|
-
if (this.imgInputFile && this.imgUrlFile) {
|
|
296
|
-
this.imgUrlFile.disabled = false;
|
|
297
|
-
this.previewSrc.style.textDecoration = '';
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
this.altText.value = '';
|
|
301
|
-
/** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]')).checked = true;
|
|
302
|
-
this.captionCheckEl.checked = false;
|
|
303
|
-
this._element = null;
|
|
304
|
-
this._ratio = {
|
|
305
|
-
w: 1,
|
|
306
|
-
h: 1
|
|
307
|
-
};
|
|
308
|
-
this.#OpenTab('init');
|
|
309
|
-
|
|
310
|
-
if (this._resizing) {
|
|
311
|
-
this.inputX.value = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
|
|
312
|
-
this.inputY.value = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
|
|
313
|
-
this.proportion.checked = true;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (this.pluginOptions.useFormatType) {
|
|
317
|
-
this._activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
this.anchor.init();
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* @editorMethod Editor.Component
|
|
325
|
-
* @description Executes the method that is called when a component of a plugin is selected.
|
|
326
|
-
* @param {HTMLElement} target Target component element
|
|
327
|
-
*/
|
|
328
|
-
select(target) {
|
|
329
|
-
this._ready(target);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* @private
|
|
334
|
-
* @description Prepares the component for selection.
|
|
335
|
-
* - Ensures that the controller is properly positioned and initialized.
|
|
336
|
-
* - Prevents duplicate event handling if the component is already selected.
|
|
337
|
-
* @param {HTMLElement} target - The selected element.
|
|
338
|
-
*/
|
|
339
|
-
_ready(target) {
|
|
340
|
-
if (!target) return;
|
|
341
|
-
const figureInfo = this.figure.open(target, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: false });
|
|
342
|
-
this.anchor.set(dom.check.isAnchor(target.parentNode) ? target.parentNode : null);
|
|
343
|
-
|
|
344
|
-
this._linkElement = this.anchor.currentTarget;
|
|
345
|
-
this._element = target;
|
|
346
|
-
this._cover = figureInfo.cover;
|
|
347
|
-
this._container = figureInfo.container;
|
|
348
|
-
this._caption = figureInfo.caption;
|
|
349
|
-
this._align = figureInfo.align;
|
|
350
|
-
target.style.float = '';
|
|
351
|
-
|
|
352
|
-
this._origin_w = String(figureInfo.originWidth || figureInfo.w || '');
|
|
353
|
-
this._origin_h = String(figureInfo.originHeight || figureInfo.h || '');
|
|
354
|
-
this.altText.value = this._element.alt;
|
|
355
|
-
|
|
356
|
-
if (this.imgUrlFile) this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = this._element.src;
|
|
357
|
-
|
|
358
|
-
/** @type {HTMLInputElement} */
|
|
359
|
-
const activeAlign = this.modal.form.querySelector('input[name="suneditor_image_radio"][value="' + this._align + '"]') || this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]');
|
|
360
|
-
activeAlign.checked = true;
|
|
361
|
-
this.captionCheckEl.checked = !!this._caption;
|
|
362
|
-
|
|
363
|
-
if (!this._resizing) return;
|
|
364
|
-
|
|
365
|
-
const percentageRotation = this._onlyPercentage && this.figure.isVertical;
|
|
366
|
-
let w = percentageRotation ? '' : figureInfo.width;
|
|
367
|
-
if (this._onlyPercentage) {
|
|
368
|
-
w = numbers.get(w, 2);
|
|
369
|
-
if (w > 100) w = 100;
|
|
370
|
-
}
|
|
371
|
-
this.inputX.value = String(w === 'auto' ? '' : w);
|
|
372
|
-
|
|
373
|
-
if (!this._onlyPercentage) {
|
|
374
|
-
const h = percentageRotation ? '' : figureInfo.height;
|
|
375
|
-
this.inputY.value = String(h === 'auto' ? '' : h);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
this.proportion.checked = true;
|
|
379
|
-
this.inputX.disabled = percentageRotation ? true : false;
|
|
380
|
-
this.inputY.disabled = percentageRotation ? true : false;
|
|
381
|
-
this.proportion.disabled = percentageRotation ? true : false;
|
|
382
|
-
|
|
383
|
-
this._ratio = this.proportion.checked
|
|
384
|
-
? figureInfo.ratio
|
|
385
|
-
: {
|
|
386
|
-
w: 1,
|
|
387
|
-
h: 1
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
if (this.pluginOptions.useFormatType) {
|
|
391
|
-
this._activeAsInline(this.component.isInline(figureInfo.container));
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* @editorMethod Editor.Component
|
|
397
|
-
* @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
|
|
398
|
-
* @param {HTMLElement} target Target element
|
|
399
|
-
* @returns {Promise<void>}
|
|
400
|
-
*/
|
|
401
|
-
async destroy(target) {
|
|
402
|
-
const targetEl = target || this._element;
|
|
403
|
-
const container = dom.query.getParentElement(targetEl, Figure.is) || targetEl;
|
|
404
|
-
const focusEl = container.previousElementSibling || container.nextElementSibling;
|
|
405
|
-
const emptyDiv = container.parentNode;
|
|
406
|
-
|
|
407
|
-
const message = await this.triggerEvent('onImageDeleteBefore', { element: targetEl, container, align: this._align, alt: this.altText.value, url: this._linkValue });
|
|
408
|
-
if (message === false) return;
|
|
409
|
-
|
|
410
|
-
dom.utils.removeItem(container);
|
|
411
|
-
this.init();
|
|
412
|
-
|
|
413
|
-
if (emptyDiv !== this.editor.frameContext.get('wysiwyg')) {
|
|
414
|
-
this.nodeTransform.removeAllParents(
|
|
415
|
-
emptyDiv,
|
|
416
|
-
function (current) {
|
|
417
|
-
return current.childNodes.length === 0;
|
|
418
|
-
},
|
|
419
|
-
null
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// focus
|
|
424
|
-
this.editor.focusEdge(focusEl);
|
|
425
|
-
this.history.push(false);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* @private
|
|
430
|
-
* @description Retrieves the current image information.
|
|
431
|
-
* @returns {*} - The image data.
|
|
432
|
-
*/
|
|
433
|
-
_getInfo() {
|
|
434
|
-
return {
|
|
435
|
-
element: this._element,
|
|
436
|
-
anchor: this.anchor.create(true),
|
|
437
|
-
inputWidth: this.inputX?.value || '',
|
|
438
|
-
inputHeight: this.inputY?.value || '',
|
|
439
|
-
align: this._align,
|
|
440
|
-
isUpdate: this.modal.isUpdate,
|
|
441
|
-
alt: this.altText.value
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* @private
|
|
447
|
-
* @description Toggles between block and inline image format.
|
|
448
|
-
* @param {boolean} isInline - Whether the image should be inline.
|
|
449
|
-
*/
|
|
450
|
-
_activeAsInline(isInline) {
|
|
451
|
-
if (isInline) {
|
|
452
|
-
dom.utils.addClass(this.asInline, 'on');
|
|
453
|
-
dom.utils.removeClass(this.asBlock, 'on');
|
|
454
|
-
this.as = 'inline';
|
|
455
|
-
// buttns
|
|
456
|
-
if (this.alignForm) this.alignForm.style.display = 'none';
|
|
457
|
-
// caption
|
|
458
|
-
if (this.captionEl) this.captionEl.style.display = 'none';
|
|
459
|
-
} else {
|
|
460
|
-
dom.utils.addClass(this.asBlock, 'on');
|
|
461
|
-
dom.utils.removeClass(this.asInline, 'on');
|
|
462
|
-
this.as = 'block';
|
|
463
|
-
// buttns
|
|
464
|
-
if (this.alignForm) this.alignForm.style.display = '';
|
|
465
|
-
// caption
|
|
466
|
-
if (this.captionEl) this.captionEl.style.display = '';
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* @description Create an "image" component using the provided files.
|
|
472
|
-
* @param {FileList|File[]} fileList File object list
|
|
473
|
-
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
474
|
-
*/
|
|
475
|
-
async submitFile(fileList) {
|
|
476
|
-
if (fileList.length === 0) return false;
|
|
477
|
-
|
|
478
|
-
let fileSize = 0;
|
|
479
|
-
const files = [];
|
|
480
|
-
const slngleSizeLimit = this.pluginOptions.uploadSingleSizeLimit;
|
|
481
|
-
for (let i = 0, len = fileList.length, f, s; i < len; i++) {
|
|
482
|
-
f = fileList[i];
|
|
483
|
-
if (!/image/i.test(f.type)) continue;
|
|
484
|
-
|
|
485
|
-
s = f.size;
|
|
486
|
-
if (slngleSizeLimit && slngleSizeLimit > s) {
|
|
487
|
-
const err = '[SUNEDITOR.imageUpload.fail] Size of uploadable single file: ' + slngleSizeLimit / 1000 + 'KB';
|
|
488
|
-
const message = await this.triggerEvent('onImageUploadError', {
|
|
489
|
-
error: err,
|
|
490
|
-
limitSize: slngleSizeLimit,
|
|
491
|
-
uploadSize: s,
|
|
492
|
-
file: f
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
496
|
-
|
|
497
|
-
return false;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
files.push(f);
|
|
501
|
-
fileSize += s;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const limitSize = this.pluginOptions.uploadSizeLimit;
|
|
505
|
-
const currentSize = this.fileManager.getSize();
|
|
506
|
-
if (limitSize > 0 && fileSize + currentSize > limitSize) {
|
|
507
|
-
const err = '[SUNEDITOR.imageUpload.fail] Size of uploadable total images: ' + limitSize / 1000 + 'KB';
|
|
508
|
-
const message = await this.triggerEvent('onImageUploadError', {
|
|
509
|
-
error: err,
|
|
510
|
-
limitSize,
|
|
511
|
-
currentSize,
|
|
512
|
-
uploadSize: fileSize
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
516
|
-
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const imgInfo = { files, ...this._getInfo() };
|
|
521
|
-
const handler = function (infos, newInfos) {
|
|
522
|
-
infos = newInfos || infos;
|
|
523
|
-
this._serverUpload(infos, infos.files);
|
|
524
|
-
}.bind(this, imgInfo);
|
|
525
|
-
// se-ts-ignore
|
|
526
|
-
this._serverUpload;
|
|
527
|
-
|
|
528
|
-
const result = await this.triggerEvent('onImageUploadBefore', {
|
|
529
|
-
info: imgInfo,
|
|
530
|
-
handler
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
if (result === undefined) return true;
|
|
534
|
-
if (result === false) return false;
|
|
535
|
-
if (result !== null && typeof result === 'object') handler(result);
|
|
536
|
-
|
|
537
|
-
if (result === true || result === NO_EVENT) handler(null);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* @description Create an "image" component using the provided url.
|
|
542
|
-
* @param {string} url File url
|
|
543
|
-
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
544
|
-
*/
|
|
545
|
-
async submitURL(url) {
|
|
546
|
-
if (!url) url = this._linkValue;
|
|
547
|
-
if (!url) return false;
|
|
548
|
-
|
|
549
|
-
const file = { name: url.split('/').pop(), size: 0 };
|
|
550
|
-
const imgInfo = {
|
|
551
|
-
url,
|
|
552
|
-
files: file,
|
|
553
|
-
...this._getInfo()
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
const handler = function (infos, newInfos) {
|
|
557
|
-
infos = newInfos || infos;
|
|
558
|
-
const infoUrl = infos.url;
|
|
559
|
-
if (this.modal.isUpdate) this._updateSrc(infoUrl, infos.element, infos.files);
|
|
560
|
-
else this._produce(infoUrl, infos.anchor, infos.inputWidth, infos.inputHeight, infos.align, infos.files, infos.alt);
|
|
561
|
-
}.bind(this, imgInfo);
|
|
562
|
-
|
|
563
|
-
const result = await this.triggerEvent('onImageUploadBefore', {
|
|
564
|
-
info: imgInfo,
|
|
565
|
-
handler
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
if (result === undefined) return true;
|
|
569
|
-
if (result === false) return false;
|
|
570
|
-
if (result !== null && typeof result === 'object') handler(result);
|
|
571
|
-
|
|
572
|
-
if (result === true || result === NO_EVENT) handler(null);
|
|
573
|
-
|
|
574
|
-
return true;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* @private
|
|
579
|
-
* @description Updates the selected image size, alt text, and caption.
|
|
580
|
-
* @param {string} width - New image width.
|
|
581
|
-
* @param {string} height - New image height.
|
|
582
|
-
*/
|
|
583
|
-
_update(width, height) {
|
|
584
|
-
if (!width) width = this.inputX?.value || 'auto';
|
|
585
|
-
if (!height) height = this.inputY?.value || 'auto';
|
|
586
|
-
|
|
587
|
-
let imageEl = this._element;
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
imageEl.
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
cover.insertBefore(
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (!
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
this._linkElement
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
cover.
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
this.
|
|
746
|
-
this.
|
|
747
|
-
this.
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
* @
|
|
785
|
-
* @
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
* @
|
|
830
|
-
* @
|
|
831
|
-
* @param {
|
|
832
|
-
* @param {
|
|
833
|
-
* @param {string}
|
|
834
|
-
* @param {string}
|
|
835
|
-
* @param {
|
|
836
|
-
* @param {string}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
* @
|
|
849
|
-
* @
|
|
850
|
-
* @param {string}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if (!
|
|
855
|
-
if (this.
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
* @param {
|
|
867
|
-
* @param {
|
|
868
|
-
* @param {string}
|
|
869
|
-
* @param {string}
|
|
870
|
-
* @param {
|
|
871
|
-
* @param {string}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
oImg
|
|
877
|
-
oImg.
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
this.
|
|
891
|
-
this.
|
|
892
|
-
this.
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
this.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
* @param {
|
|
911
|
-
* @param {
|
|
912
|
-
* @param {string}
|
|
913
|
-
* @param {
|
|
914
|
-
* @param {string}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
oImg
|
|
920
|
-
oImg.
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
this.
|
|
928
|
-
this.
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
this.
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
* @
|
|
942
|
-
* @
|
|
943
|
-
* @param {
|
|
944
|
-
* @param {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
this.
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
* @
|
|
955
|
-
* @
|
|
956
|
-
* @param {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
* @
|
|
978
|
-
* @
|
|
979
|
-
* @param {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
* @
|
|
996
|
-
* @
|
|
997
|
-
* @param {
|
|
998
|
-
* @param {
|
|
999
|
-
* @param {string}
|
|
1000
|
-
* @param {string}
|
|
1001
|
-
* @param {string}
|
|
1002
|
-
* @param {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
this.
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
this.
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
* @
|
|
1051
|
-
* @
|
|
1052
|
-
* @param {
|
|
1053
|
-
*
|
|
1054
|
-
* -
|
|
1055
|
-
*
|
|
1056
|
-
* @param {
|
|
1057
|
-
* @param {
|
|
1058
|
-
* @param {string}
|
|
1059
|
-
* @param {string}
|
|
1060
|
-
* @param {string}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
* @
|
|
1075
|
-
* @
|
|
1076
|
-
* @param {
|
|
1077
|
-
* @
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
* @
|
|
1091
|
-
* @
|
|
1092
|
-
* @
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
* @
|
|
1104
|
-
* @param {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
this.
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
this.
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
this.
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
this.
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
this.
|
|
1199
|
-
this.imgUrlFile.
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (this.
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
this.
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
* @
|
|
1231
|
-
* @property {HTMLElement}
|
|
1232
|
-
* @property {HTMLElement}
|
|
1233
|
-
* @property {
|
|
1234
|
-
* @property {HTMLInputElement}
|
|
1235
|
-
* @property {HTMLInputElement}
|
|
1236
|
-
* @property {HTMLInputElement}
|
|
1237
|
-
* @property {
|
|
1238
|
-
* @property {HTMLElement}
|
|
1239
|
-
* @property {
|
|
1240
|
-
* @property {
|
|
1241
|
-
* @property {HTMLInputElement}
|
|
1242
|
-
* @property {HTMLInputElement}
|
|
1243
|
-
* @property {
|
|
1244
|
-
* @property {HTMLButtonElement}
|
|
1245
|
-
* @property {HTMLButtonElement}
|
|
1246
|
-
* @property {HTMLButtonElement}
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
* @param {
|
|
1250
|
-
* @
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
<
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
${
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
<label class="
|
|
1287
|
-
<label class="size-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
<
|
|
1291
|
-
<
|
|
1292
|
-
<
|
|
1293
|
-
<
|
|
1294
|
-
|
|
1295
|
-
${
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
${
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
${
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
<
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
<button type="button" class="_se_tab_link" data-tab-link="
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
${
|
|
1329
|
-
|
|
1330
|
-
<div
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
${
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
<label><input type="radio" name="suneditor_image_radio" class="se-modal-btn-radio" value="
|
|
1346
|
-
<label><input type="radio" name="suneditor_image_radio" class="se-modal-btn-radio" value="
|
|
1347
|
-
<label><input type="radio" name="suneditor_image_radio" class="se-modal-btn-radio" value="
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1
|
+
import EditorInjector from '../../editorInjector';
|
|
2
|
+
import { Modal, Figure, FileManager, ModalAnchorEditor } from '../../modules';
|
|
3
|
+
import { dom, numbers, env, keyCodeMap } from '../../helper';
|
|
4
|
+
const { NO_EVENT } = env;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../../events').ImageInfo} ImageInfo_image
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('../../modules/Figure').FigureControls} FigureControls_image
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} ImagePluginOptions
|
|
16
|
+
* @property {boolean} [canResize=true] - Whether the image element can be resized.
|
|
17
|
+
* @property {boolean} [showHeightInput=true] - Whether to display the height input field.
|
|
18
|
+
* @property {string} [defaultWidth="auto"] - The default width of the image. If a number is provided, "px" will be appended.
|
|
19
|
+
* @property {string} [defaultHeight="auto"] - The default height of the image. If a number is provided, "px" will be appended.
|
|
20
|
+
* @property {boolean} [percentageOnlySize=false] - Whether to allow only percentage-based sizing.
|
|
21
|
+
* @property {boolean} [createFileInput=true] - Whether to create a file input element for image uploads.
|
|
22
|
+
* @property {boolean} [createUrlInput=true] - Whether to create a URL input element for image insertion.
|
|
23
|
+
* @property {string} [uploadUrl] - The URL endpoint for image file uploads.
|
|
24
|
+
* @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the file upload request.
|
|
25
|
+
* @property {number} [uploadSizeLimit] - The total upload size limit in bytes.
|
|
26
|
+
* @property {number} [uploadSingleSizeLimit] - The single file upload size limit in bytes.
|
|
27
|
+
* @property {boolean} [allowMultiple=false] - Whether multiple image uploads are allowed.
|
|
28
|
+
* @property {string} [acceptedFormats="image/*"] - The accepted file formats for image uploads.
|
|
29
|
+
* @property {boolean} [useFormatType=true] - Whether to enable format type selection (block or inline).
|
|
30
|
+
* @property {string} [defaultFormatType="block"] - The default image format type ("block" or "inline").
|
|
31
|
+
* @property {boolean} [keepFormatType=false] - Whether to retain the chosen format type after image insertion.
|
|
32
|
+
* @property {boolean} [linkEnableFileUpload] - Whether to enable file uploads for linked images.
|
|
33
|
+
* @property {FigureControls_image} [controls] - Figure controls.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @class
|
|
38
|
+
* @description Image plugin.
|
|
39
|
+
* - This plugin provides image insertion functionality within the editor, supporting both file upload and URL input.
|
|
40
|
+
*/
|
|
41
|
+
class Image_ extends EditorInjector {
|
|
42
|
+
static key = 'image';
|
|
43
|
+
static type = 'modal';
|
|
44
|
+
static className = '';
|
|
45
|
+
/**
|
|
46
|
+
* @this {Image_}
|
|
47
|
+
* @param {Element} node - The node to check.
|
|
48
|
+
* @returns {Element|null} Returns a node if the node is a valid component.
|
|
49
|
+
*/
|
|
50
|
+
static component(node) {
|
|
51
|
+
const compNode = dom.check.isFigure(node) || (/^span$/i.test(node.nodeName) && dom.utils.hasClass(node, 'se-component')) ? node.firstElementChild : node;
|
|
52
|
+
return /^IMG$/i.test(compNode?.nodeName) ? compNode : dom.check.isAnchor(compNode) && /^IMG$/i.test(compNode?.firstElementChild?.nodeName) ? compNode?.firstElementChild : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @constructor
|
|
57
|
+
* @param {__se__EditorCore} editor - The root editor instance
|
|
58
|
+
* @param {ImagePluginOptions} pluginOptions
|
|
59
|
+
*/
|
|
60
|
+
constructor(editor, pluginOptions) {
|
|
61
|
+
// plugin bisic properties
|
|
62
|
+
super(editor);
|
|
63
|
+
this.title = this.lang.image;
|
|
64
|
+
this.icon = 'image';
|
|
65
|
+
|
|
66
|
+
this.pluginOptions = {
|
|
67
|
+
canResize: pluginOptions.canResize === undefined ? true : pluginOptions.canResize,
|
|
68
|
+
showHeightInput: pluginOptions.showHeightInput === undefined ? true : !!pluginOptions.showHeightInput,
|
|
69
|
+
defaultWidth: !pluginOptions.defaultWidth ? 'auto' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
|
|
70
|
+
defaultHeight: !pluginOptions.defaultHeight ? 'auto' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
|
|
71
|
+
percentageOnlySize: !!pluginOptions.percentageOnlySize,
|
|
72
|
+
createFileInput: pluginOptions.createFileInput === undefined ? true : pluginOptions.createFileInput,
|
|
73
|
+
createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
|
|
74
|
+
uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
|
|
75
|
+
uploadHeaders: pluginOptions.uploadHeaders || null,
|
|
76
|
+
uploadSizeLimit: numbers.get(pluginOptions.uploadSizeLimit, 0),
|
|
77
|
+
uploadSingleSizeLimit: numbers.get(pluginOptions.uploadSingleSizeLimit, 0),
|
|
78
|
+
allowMultiple: !!pluginOptions.allowMultiple,
|
|
79
|
+
acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'image/*' : pluginOptions.acceptedFormats.trim() || 'image/*',
|
|
80
|
+
useFormatType: pluginOptions.useFormatType ?? true,
|
|
81
|
+
defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : 'block',
|
|
82
|
+
keepFormatType: pluginOptions.keepFormatType ?? false
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// create HTML
|
|
86
|
+
const sizeUnit = this.pluginOptions.percentageOnlySize ? '%' : 'px';
|
|
87
|
+
const modalEl = CreateHTML_modal(editor, this.pluginOptions);
|
|
88
|
+
const ctrlAs = this.pluginOptions.useFormatType ? 'as' : '';
|
|
89
|
+
const figureControls =
|
|
90
|
+
pluginOptions.controls || !this.pluginOptions.canResize
|
|
91
|
+
? [[ctrlAs, 'mirror_h', 'mirror_v', 'align', 'caption', 'edit', 'revert', 'copy', 'remove']]
|
|
92
|
+
: [
|
|
93
|
+
[ctrlAs, 'resize_auto,100,75,50', 'rotate_l', 'rotate_r', 'mirror_h', 'mirror_v'],
|
|
94
|
+
['edit', 'align', 'caption', 'revert', 'copy', 'remove']
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// show align
|
|
98
|
+
this.alignForm = modalEl.alignForm;
|
|
99
|
+
if (!figureControls.some((subArray) => subArray.includes('align'))) this.alignForm.style.display = 'none';
|
|
100
|
+
|
|
101
|
+
// modules
|
|
102
|
+
const Link = this.plugins.link ? this.plugins.link.pluginOptions : {};
|
|
103
|
+
this.anchor = new ModalAnchorEditor(this, modalEl.html, {
|
|
104
|
+
textToDisplay: false,
|
|
105
|
+
title: true,
|
|
106
|
+
openNewWindow: Link.openNewWindow,
|
|
107
|
+
relList: Link.relList,
|
|
108
|
+
defaultRel: Link.defaultRel,
|
|
109
|
+
noAutoPrefix: Link.noAutoPrefix,
|
|
110
|
+
enableFileUpload: pluginOptions.linkEnableFileUpload
|
|
111
|
+
});
|
|
112
|
+
this.modal = new Modal(this, modalEl.html);
|
|
113
|
+
this.figure = new Figure(this, figureControls, {
|
|
114
|
+
sizeUnit: sizeUnit
|
|
115
|
+
});
|
|
116
|
+
this.fileManager = new FileManager(this, {
|
|
117
|
+
query: 'img',
|
|
118
|
+
loadHandler: this.events.onImageLoad,
|
|
119
|
+
eventHandler: this.events.onImageAction
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// members
|
|
123
|
+
this.fileModalWrapper = modalEl.fileModalWrapper;
|
|
124
|
+
this.imgInputFile = modalEl.imgInputFile;
|
|
125
|
+
this.imgUrlFile = modalEl.imgUrlFile;
|
|
126
|
+
this.focusElement = this.imgInputFile || this.imgUrlFile;
|
|
127
|
+
this.altText = modalEl.altText;
|
|
128
|
+
this.captionCheckEl = modalEl.captionCheckEl;
|
|
129
|
+
this.captionEl = this.captionCheckEl?.parentElement;
|
|
130
|
+
this.previewSrc = modalEl.previewSrc;
|
|
131
|
+
this.sizeUnit = sizeUnit;
|
|
132
|
+
this.as = 'block';
|
|
133
|
+
this.proportion = null;
|
|
134
|
+
this.inputX = null;
|
|
135
|
+
this.inputY = null;
|
|
136
|
+
this._linkElement = null;
|
|
137
|
+
this._linkValue = '';
|
|
138
|
+
this._align = 'none';
|
|
139
|
+
this._svgDefaultSize = '30%';
|
|
140
|
+
this._base64RenderIndex = 0;
|
|
141
|
+
this._element = null;
|
|
142
|
+
this._cover = null;
|
|
143
|
+
this._container = null;
|
|
144
|
+
this._caption = null;
|
|
145
|
+
this._ratio = {
|
|
146
|
+
w: 1,
|
|
147
|
+
h: 1
|
|
148
|
+
};
|
|
149
|
+
this._origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
|
|
150
|
+
this._origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
|
|
151
|
+
this._resizing = this.pluginOptions.canResize;
|
|
152
|
+
this._onlyPercentage = this.pluginOptions.percentageOnlySize;
|
|
153
|
+
this._nonResizing = !this._resizing || !this.pluginOptions.showHeightInput || this._onlyPercentage;
|
|
154
|
+
|
|
155
|
+
// init
|
|
156
|
+
this.eventManager.addEvent(modalEl.tabs, 'click', this.#OpenTab.bind(this));
|
|
157
|
+
if (this.imgInputFile) this.eventManager.addEvent(modalEl.fileRemoveBtn, 'click', this.#RemoveSelectedFiles.bind(this));
|
|
158
|
+
if (this.imgUrlFile) this.eventManager.addEvent(this.imgUrlFile, 'input', this.#OnLinkPreview.bind(this));
|
|
159
|
+
if (this.imgInputFile && this.imgUrlFile) this.eventManager.addEvent(this.imgInputFile, 'change', this.#OnfileInputChange.bind(this));
|
|
160
|
+
|
|
161
|
+
const galleryButton = modalEl.galleryButton;
|
|
162
|
+
if (galleryButton) this.eventManager.addEvent(galleryButton, 'click', this.#OpenGallery.bind(this));
|
|
163
|
+
|
|
164
|
+
if (this._resizing) {
|
|
165
|
+
this.proportion = modalEl.proportion;
|
|
166
|
+
this.inputX = modalEl.inputX;
|
|
167
|
+
this.inputY = modalEl.inputY;
|
|
168
|
+
this.inputX.value = this.pluginOptions.defaultWidth;
|
|
169
|
+
this.inputY.value = this.pluginOptions.defaultHeight;
|
|
170
|
+
|
|
171
|
+
const ratioChange = this.#OnChangeRatio.bind(this);
|
|
172
|
+
this.eventManager.addEvent(this.inputX, 'keyup', this.#OnInputSize.bind(this, 'x'));
|
|
173
|
+
this.eventManager.addEvent(this.inputY, 'keyup', this.#OnInputSize.bind(this, 'y'));
|
|
174
|
+
this.eventManager.addEvent(this.inputX, 'change', ratioChange);
|
|
175
|
+
this.eventManager.addEvent(this.inputY, 'change', ratioChange);
|
|
176
|
+
this.eventManager.addEvent(this.proportion, 'change', ratioChange);
|
|
177
|
+
this.eventManager.addEvent(modalEl.revertBtn, 'click', this.#OnClickRevert.bind(this));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (this.pluginOptions.useFormatType) {
|
|
181
|
+
this.as = this.pluginOptions.defaultFormatType;
|
|
182
|
+
this.asBlock = modalEl.asBlock;
|
|
183
|
+
this.asInline = modalEl.asInline;
|
|
184
|
+
this.eventManager.addEvent([this.asBlock, this.asInline], 'click', this.#OnClickAsButton.bind(this));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @editorMethod Modules.Modal
|
|
190
|
+
* @description Executes the method that is called when a "Modal" module's is opened.
|
|
191
|
+
*/
|
|
192
|
+
open() {
|
|
193
|
+
this.modal.open();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @editorMethod Modules.Controller(Figure)
|
|
198
|
+
* @description Executes the method that is called when a target component is edited.
|
|
199
|
+
*/
|
|
200
|
+
edit() {
|
|
201
|
+
this.modal.open();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @editorMethod Modules.Modal
|
|
206
|
+
* @description Executes the method that is called when a plugin's modal is opened.
|
|
207
|
+
* @param {boolean} isUpdate "Indicates whether the modal is for editing an existing component (true) or registering a new one (false)."
|
|
208
|
+
*/
|
|
209
|
+
on(isUpdate) {
|
|
210
|
+
if (!isUpdate) {
|
|
211
|
+
if (this._resizing) {
|
|
212
|
+
this.inputX.value = this._origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
|
|
213
|
+
this.inputY.value = this._origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
|
|
214
|
+
}
|
|
215
|
+
if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.setAttribute('multiple', 'multiple');
|
|
216
|
+
} else {
|
|
217
|
+
if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.removeAttribute('multiple');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.anchor.on(isUpdate);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @editorMethod Editor.EventManager
|
|
225
|
+
* @description Executes the event function of "paste" or "drop".
|
|
226
|
+
* @param {Object} params { frameContext, event, file }
|
|
227
|
+
* @param {__se__FrameContext} params.frameContext Frame context
|
|
228
|
+
* @param {ClipboardEvent} params.event Event object
|
|
229
|
+
* @param {File} params.file File object
|
|
230
|
+
* @returns {boolean} - If return false, the file upload will be canceled
|
|
231
|
+
*/
|
|
232
|
+
onFilePasteAndDrop({ file }) {
|
|
233
|
+
if (!/^image/.test(file.type)) return;
|
|
234
|
+
|
|
235
|
+
this.submitFile([file]);
|
|
236
|
+
this.editor.focus();
|
|
237
|
+
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @editorMethod Modules.Modal
|
|
243
|
+
* @description This function is called when a form within a modal window is "submit".
|
|
244
|
+
* @returns {Promise<boolean>} Success or failure
|
|
245
|
+
*/
|
|
246
|
+
async modalAction() {
|
|
247
|
+
this._align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"]:checked')).value;
|
|
248
|
+
|
|
249
|
+
if (this.modal.isUpdate) {
|
|
250
|
+
this._update(this.inputX?.value, this.inputY?.value);
|
|
251
|
+
this.history.push(false);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (this.imgInputFile && this.imgInputFile.files.length > 0) {
|
|
255
|
+
return await this.submitFile(this.imgInputFile.files);
|
|
256
|
+
} else if (this.imgUrlFile && this._linkValue.length > 0) {
|
|
257
|
+
return await this.submitURL(this._linkValue);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @editorMethod Editor.core
|
|
265
|
+
* @description This method is used to validate and preserve the format of the component within the editor.
|
|
266
|
+
* - It ensures that the structure and attributes of the element are maintained and secure.
|
|
267
|
+
* - The method checks if the element is already wrapped in a valid container and updates its attributes if necessary.
|
|
268
|
+
* - If the element isn't properly contained, a new container is created to retain the format.
|
|
269
|
+
* @returns {{query: string, method: (element: HTMLImageElement) => void}} The format retention object containing the query and method to process the element.
|
|
270
|
+
* - query: The selector query to identify the relevant elements (in this case, 'audio').
|
|
271
|
+
* - method:The function to execute on the element to validate and preserve its format.
|
|
272
|
+
* - The function takes the element as an argument, checks if it is contained correctly, and applies necessary adjustments.
|
|
273
|
+
*/
|
|
274
|
+
retainFormat() {
|
|
275
|
+
return {
|
|
276
|
+
query: 'img',
|
|
277
|
+
method: (element) => {
|
|
278
|
+
const figureInfo = Figure.GetContainer(element);
|
|
279
|
+
if (figureInfo && figureInfo.container && (figureInfo.cover || figureInfo.inlineCover)) return;
|
|
280
|
+
|
|
281
|
+
this._ready(element);
|
|
282
|
+
this._fileCheck(this._origin_w, this._origin_h);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* @editorMethod Modules.Modal
|
|
289
|
+
* @description This function is called before the modal window is opened, but before it is closed.
|
|
290
|
+
*/
|
|
291
|
+
init() {
|
|
292
|
+
Modal.OnChangeFile(this.fileModalWrapper, []);
|
|
293
|
+
if (this.imgInputFile) this.imgInputFile.value = '';
|
|
294
|
+
if (this.imgUrlFile) this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = '';
|
|
295
|
+
if (this.imgInputFile && this.imgUrlFile) {
|
|
296
|
+
this.imgUrlFile.disabled = false;
|
|
297
|
+
this.previewSrc.style.textDecoration = '';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.altText.value = '';
|
|
301
|
+
/** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]')).checked = true;
|
|
302
|
+
this.captionCheckEl.checked = false;
|
|
303
|
+
this._element = null;
|
|
304
|
+
this._ratio = {
|
|
305
|
+
w: 1,
|
|
306
|
+
h: 1
|
|
307
|
+
};
|
|
308
|
+
this.#OpenTab('init');
|
|
309
|
+
|
|
310
|
+
if (this._resizing) {
|
|
311
|
+
this.inputX.value = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
|
|
312
|
+
this.inputY.value = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
|
|
313
|
+
this.proportion.checked = true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (this.pluginOptions.useFormatType) {
|
|
317
|
+
this._activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.anchor.init();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @editorMethod Editor.Component
|
|
325
|
+
* @description Executes the method that is called when a component of a plugin is selected.
|
|
326
|
+
* @param {HTMLElement} target Target component element
|
|
327
|
+
*/
|
|
328
|
+
select(target) {
|
|
329
|
+
this._ready(target);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @private
|
|
334
|
+
* @description Prepares the component for selection.
|
|
335
|
+
* - Ensures that the controller is properly positioned and initialized.
|
|
336
|
+
* - Prevents duplicate event handling if the component is already selected.
|
|
337
|
+
* @param {HTMLElement} target - The selected element.
|
|
338
|
+
*/
|
|
339
|
+
_ready(target) {
|
|
340
|
+
if (!target) return;
|
|
341
|
+
const figureInfo = this.figure.open(target, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: false });
|
|
342
|
+
this.anchor.set(dom.check.isAnchor(target.parentNode) ? target.parentNode : null);
|
|
343
|
+
|
|
344
|
+
this._linkElement = this.anchor.currentTarget;
|
|
345
|
+
this._element = target;
|
|
346
|
+
this._cover = figureInfo.cover;
|
|
347
|
+
this._container = figureInfo.container;
|
|
348
|
+
this._caption = figureInfo.caption;
|
|
349
|
+
this._align = figureInfo.align;
|
|
350
|
+
target.style.float = '';
|
|
351
|
+
|
|
352
|
+
this._origin_w = String(figureInfo.originWidth || figureInfo.w || '');
|
|
353
|
+
this._origin_h = String(figureInfo.originHeight || figureInfo.h || '');
|
|
354
|
+
this.altText.value = this._element.alt;
|
|
355
|
+
|
|
356
|
+
if (this.imgUrlFile) this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = this._element.src;
|
|
357
|
+
|
|
358
|
+
/** @type {HTMLInputElement} */
|
|
359
|
+
const activeAlign = this.modal.form.querySelector('input[name="suneditor_image_radio"][value="' + this._align + '"]') || this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]');
|
|
360
|
+
activeAlign.checked = true;
|
|
361
|
+
this.captionCheckEl.checked = !!this._caption;
|
|
362
|
+
|
|
363
|
+
if (!this._resizing) return;
|
|
364
|
+
|
|
365
|
+
const percentageRotation = this._onlyPercentage && this.figure.isVertical;
|
|
366
|
+
let w = percentageRotation ? '' : figureInfo.width;
|
|
367
|
+
if (this._onlyPercentage) {
|
|
368
|
+
w = numbers.get(w, 2);
|
|
369
|
+
if (w > 100) w = 100;
|
|
370
|
+
}
|
|
371
|
+
this.inputX.value = String(w === 'auto' ? '' : w);
|
|
372
|
+
|
|
373
|
+
if (!this._onlyPercentage) {
|
|
374
|
+
const h = percentageRotation ? '' : figureInfo.height;
|
|
375
|
+
this.inputY.value = String(h === 'auto' ? '' : h);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.proportion.checked = true;
|
|
379
|
+
this.inputX.disabled = percentageRotation ? true : false;
|
|
380
|
+
this.inputY.disabled = percentageRotation ? true : false;
|
|
381
|
+
this.proportion.disabled = percentageRotation ? true : false;
|
|
382
|
+
|
|
383
|
+
this._ratio = this.proportion.checked
|
|
384
|
+
? figureInfo.ratio
|
|
385
|
+
: {
|
|
386
|
+
w: 1,
|
|
387
|
+
h: 1
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
if (this.pluginOptions.useFormatType) {
|
|
391
|
+
this._activeAsInline(this.component.isInline(figureInfo.container));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @editorMethod Editor.Component
|
|
397
|
+
* @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
|
|
398
|
+
* @param {HTMLElement} target Target element
|
|
399
|
+
* @returns {Promise<void>}
|
|
400
|
+
*/
|
|
401
|
+
async destroy(target) {
|
|
402
|
+
const targetEl = target || this._element;
|
|
403
|
+
const container = dom.query.getParentElement(targetEl, Figure.is) || targetEl;
|
|
404
|
+
const focusEl = container.previousElementSibling || container.nextElementSibling;
|
|
405
|
+
const emptyDiv = container.parentNode;
|
|
406
|
+
|
|
407
|
+
const message = await this.triggerEvent('onImageDeleteBefore', { element: targetEl, container, align: this._align, alt: this.altText.value, url: this._linkValue });
|
|
408
|
+
if (message === false) return;
|
|
409
|
+
|
|
410
|
+
dom.utils.removeItem(container);
|
|
411
|
+
this.init();
|
|
412
|
+
|
|
413
|
+
if (emptyDiv !== this.editor.frameContext.get('wysiwyg')) {
|
|
414
|
+
this.nodeTransform.removeAllParents(
|
|
415
|
+
emptyDiv,
|
|
416
|
+
function (current) {
|
|
417
|
+
return current.childNodes.length === 0;
|
|
418
|
+
},
|
|
419
|
+
null
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// focus
|
|
424
|
+
this.editor.focusEdge(focusEl);
|
|
425
|
+
this.history.push(false);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* @private
|
|
430
|
+
* @description Retrieves the current image information.
|
|
431
|
+
* @returns {*} - The image data.
|
|
432
|
+
*/
|
|
433
|
+
_getInfo() {
|
|
434
|
+
return {
|
|
435
|
+
element: this._element,
|
|
436
|
+
anchor: this.anchor.create(true),
|
|
437
|
+
inputWidth: this.inputX?.value || '',
|
|
438
|
+
inputHeight: this.inputY?.value || '',
|
|
439
|
+
align: this._align,
|
|
440
|
+
isUpdate: this.modal.isUpdate,
|
|
441
|
+
alt: this.altText.value
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @private
|
|
447
|
+
* @description Toggles between block and inline image format.
|
|
448
|
+
* @param {boolean} isInline - Whether the image should be inline.
|
|
449
|
+
*/
|
|
450
|
+
_activeAsInline(isInline) {
|
|
451
|
+
if (isInline) {
|
|
452
|
+
dom.utils.addClass(this.asInline, 'on');
|
|
453
|
+
dom.utils.removeClass(this.asBlock, 'on');
|
|
454
|
+
this.as = 'inline';
|
|
455
|
+
// buttns
|
|
456
|
+
if (this.alignForm) this.alignForm.style.display = 'none';
|
|
457
|
+
// caption
|
|
458
|
+
if (this.captionEl) this.captionEl.style.display = 'none';
|
|
459
|
+
} else {
|
|
460
|
+
dom.utils.addClass(this.asBlock, 'on');
|
|
461
|
+
dom.utils.removeClass(this.asInline, 'on');
|
|
462
|
+
this.as = 'block';
|
|
463
|
+
// buttns
|
|
464
|
+
if (this.alignForm) this.alignForm.style.display = '';
|
|
465
|
+
// caption
|
|
466
|
+
if (this.captionEl) this.captionEl.style.display = '';
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @description Create an "image" component using the provided files.
|
|
472
|
+
* @param {FileList|File[]} fileList File object list
|
|
473
|
+
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
474
|
+
*/
|
|
475
|
+
async submitFile(fileList) {
|
|
476
|
+
if (fileList.length === 0) return false;
|
|
477
|
+
|
|
478
|
+
let fileSize = 0;
|
|
479
|
+
const files = [];
|
|
480
|
+
const slngleSizeLimit = this.pluginOptions.uploadSingleSizeLimit;
|
|
481
|
+
for (let i = 0, len = fileList.length, f, s; i < len; i++) {
|
|
482
|
+
f = fileList[i];
|
|
483
|
+
if (!/image/i.test(f.type)) continue;
|
|
484
|
+
|
|
485
|
+
s = f.size;
|
|
486
|
+
if (slngleSizeLimit && slngleSizeLimit > s) {
|
|
487
|
+
const err = '[SUNEDITOR.imageUpload.fail] Size of uploadable single file: ' + slngleSizeLimit / 1000 + 'KB';
|
|
488
|
+
const message = await this.triggerEvent('onImageUploadError', {
|
|
489
|
+
error: err,
|
|
490
|
+
limitSize: slngleSizeLimit,
|
|
491
|
+
uploadSize: s,
|
|
492
|
+
file: f
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
496
|
+
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
files.push(f);
|
|
501
|
+
fileSize += s;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const limitSize = this.pluginOptions.uploadSizeLimit;
|
|
505
|
+
const currentSize = this.fileManager.getSize();
|
|
506
|
+
if (limitSize > 0 && fileSize + currentSize > limitSize) {
|
|
507
|
+
const err = '[SUNEDITOR.imageUpload.fail] Size of uploadable total images: ' + limitSize / 1000 + 'KB';
|
|
508
|
+
const message = await this.triggerEvent('onImageUploadError', {
|
|
509
|
+
error: err,
|
|
510
|
+
limitSize,
|
|
511
|
+
currentSize,
|
|
512
|
+
uploadSize: fileSize
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
516
|
+
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const imgInfo = { files, ...this._getInfo() };
|
|
521
|
+
const handler = function (infos, newInfos) {
|
|
522
|
+
infos = newInfos || infos;
|
|
523
|
+
this._serverUpload(infos, infos.files);
|
|
524
|
+
}.bind(this, imgInfo);
|
|
525
|
+
// se-ts-ignore
|
|
526
|
+
this._serverUpload;
|
|
527
|
+
|
|
528
|
+
const result = await this.triggerEvent('onImageUploadBefore', {
|
|
529
|
+
info: imgInfo,
|
|
530
|
+
handler
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
if (result === undefined) return true;
|
|
534
|
+
if (result === false) return false;
|
|
535
|
+
if (result !== null && typeof result === 'object') handler(result);
|
|
536
|
+
|
|
537
|
+
if (result === true || result === NO_EVENT) handler(null);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* @description Create an "image" component using the provided url.
|
|
542
|
+
* @param {string} url File url
|
|
543
|
+
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
544
|
+
*/
|
|
545
|
+
async submitURL(url) {
|
|
546
|
+
if (!url) url = this._linkValue;
|
|
547
|
+
if (!url) return false;
|
|
548
|
+
|
|
549
|
+
const file = { name: url.split('/').pop(), size: 0 };
|
|
550
|
+
const imgInfo = {
|
|
551
|
+
url,
|
|
552
|
+
files: file,
|
|
553
|
+
...this._getInfo()
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const handler = function (infos, newInfos) {
|
|
557
|
+
infos = newInfos || infos;
|
|
558
|
+
const infoUrl = infos.url;
|
|
559
|
+
if (this.modal.isUpdate) this._updateSrc(infoUrl, infos.element, infos.files);
|
|
560
|
+
else this._produce(infoUrl, infos.anchor, infos.inputWidth, infos.inputHeight, infos.align, infos.files, infos.alt);
|
|
561
|
+
}.bind(this, imgInfo);
|
|
562
|
+
|
|
563
|
+
const result = await this.triggerEvent('onImageUploadBefore', {
|
|
564
|
+
info: imgInfo,
|
|
565
|
+
handler
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
if (result === undefined) return true;
|
|
569
|
+
if (result === false) return false;
|
|
570
|
+
if (result !== null && typeof result === 'object') handler(result);
|
|
571
|
+
|
|
572
|
+
if (result === true || result === NO_EVENT) handler(null);
|
|
573
|
+
|
|
574
|
+
return true;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* @private
|
|
579
|
+
* @description Updates the selected image size, alt text, and caption.
|
|
580
|
+
* @param {string} width - New image width.
|
|
581
|
+
* @param {string} height - New image height.
|
|
582
|
+
*/
|
|
583
|
+
_update(width, height) {
|
|
584
|
+
if (!width) width = this.inputX?.value || 'auto';
|
|
585
|
+
if (!height) height = this.inputY?.value || 'auto';
|
|
586
|
+
|
|
587
|
+
let imageEl = this._element;
|
|
588
|
+
|
|
589
|
+
// as (block | inline)
|
|
590
|
+
if ((this.as === 'block' && !this._cover) || (this.as === 'inline' && this._cover)) {
|
|
591
|
+
imageEl = this.figure.convertAsFormat(imageEl, this.as);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// --- update image ---
|
|
595
|
+
const cover = this._cover;
|
|
596
|
+
const container = this._container === this._cover ? null : this._container;
|
|
597
|
+
|
|
598
|
+
// check size
|
|
599
|
+
let changeSize;
|
|
600
|
+
const x = numbers.is(width) ? width + this.sizeUnit : width;
|
|
601
|
+
const y = numbers.is(height) ? height + this.sizeUnit : height;
|
|
602
|
+
if (/%$/.test(imageEl.style.width)) {
|
|
603
|
+
changeSize = x !== container.style.width || y !== container.style.height;
|
|
604
|
+
} else {
|
|
605
|
+
changeSize = x !== imageEl.style.width || y !== imageEl.style.height;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// alt
|
|
609
|
+
imageEl.alt = this.altText.value;
|
|
610
|
+
|
|
611
|
+
// caption
|
|
612
|
+
let modifiedCaption = false;
|
|
613
|
+
if (this.captionCheckEl.checked) {
|
|
614
|
+
if (!this._caption) {
|
|
615
|
+
this._caption = Figure.CreateCaption(cover, this.lang.caption);
|
|
616
|
+
modifiedCaption = true;
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
if (this._caption) {
|
|
620
|
+
dom.utils.removeItem(this._caption);
|
|
621
|
+
this._caption = null;
|
|
622
|
+
modifiedCaption = true;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// link
|
|
627
|
+
let isNewAnchor = false;
|
|
628
|
+
const anchor = this.anchor.create(true);
|
|
629
|
+
if (anchor) {
|
|
630
|
+
if (this._linkElement !== anchor || !container.contains(anchor)) {
|
|
631
|
+
this._linkElement = anchor.cloneNode(false);
|
|
632
|
+
cover.insertBefore(this._setAnchor(imageEl, this._linkElement), this._caption);
|
|
633
|
+
isNewAnchor = true;
|
|
634
|
+
}
|
|
635
|
+
} else if (this._linkElement !== null) {
|
|
636
|
+
if (cover.contains(this._linkElement)) {
|
|
637
|
+
const newEl = imageEl.cloneNode(true);
|
|
638
|
+
cover.removeChild(this._linkElement);
|
|
639
|
+
cover.insertBefore(newEl, this._caption);
|
|
640
|
+
imageEl = newEl;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// size
|
|
645
|
+
if (this._resizing && changeSize) {
|
|
646
|
+
this._applySize(width, height);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (isNewAnchor) {
|
|
650
|
+
dom.utils.removeItem(anchor);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// transform
|
|
654
|
+
if (modifiedCaption || (!this._onlyPercentage && changeSize)) {
|
|
655
|
+
if (/\d+/.test(imageEl.style.height) || (this.figure.isVertical && this.captionCheckEl.checked)) {
|
|
656
|
+
if (/auto|%$/.test(width) || /auto|%$/.test(height)) {
|
|
657
|
+
this.figure.deleteTransform(imageEl);
|
|
658
|
+
} else {
|
|
659
|
+
this.figure.setTransform(imageEl, width, height, 0);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// align
|
|
665
|
+
this.figure.setAlign(imageEl, this._align);
|
|
666
|
+
|
|
667
|
+
// select
|
|
668
|
+
imageEl.onload = () => {
|
|
669
|
+
this.select(imageEl);
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
this._ready(imageEl);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* @private
|
|
677
|
+
* @description Validates the image size and applies necessary transformations.
|
|
678
|
+
* @param {string} width - The width of the image.
|
|
679
|
+
* @param {string} height - The height of the image.
|
|
680
|
+
*/
|
|
681
|
+
_fileCheck(width, height) {
|
|
682
|
+
if (!width) width = this.inputX?.value || 'auto';
|
|
683
|
+
if (!height) height = this.inputY?.value || 'auto';
|
|
684
|
+
|
|
685
|
+
let imageEl = this._element;
|
|
686
|
+
let cover = this._cover;
|
|
687
|
+
let inlineCover = null;
|
|
688
|
+
let container = this._container === this._cover ? null : this._container;
|
|
689
|
+
let isNewContainer = false;
|
|
690
|
+
|
|
691
|
+
if (!cover || !container) {
|
|
692
|
+
isNewContainer = true;
|
|
693
|
+
imageEl = this._element.cloneNode(true);
|
|
694
|
+
const figureInfo =
|
|
695
|
+
this.pluginOptions.useFormatType && width !== 'auto' && (/^span$/i.test(this._element.parentElement?.nodeName) || this.format.isLine(this._element.parentElement))
|
|
696
|
+
? Figure.CreateInlineContainer(imageEl, 'se-image-container')
|
|
697
|
+
: Figure.CreateContainer(imageEl, 'se-image-container');
|
|
698
|
+
cover = figureInfo.cover;
|
|
699
|
+
container = figureInfo.container;
|
|
700
|
+
inlineCover = figureInfo.inlineCover;
|
|
701
|
+
this.figure.open(imageEl, { nonResizing: true, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// alt
|
|
705
|
+
imageEl.alt = this.altText.value;
|
|
706
|
+
|
|
707
|
+
// caption
|
|
708
|
+
let modifiedCaption = false;
|
|
709
|
+
if (!inlineCover) {
|
|
710
|
+
if (this.captionCheckEl.checked) {
|
|
711
|
+
if (!this._caption || isNewContainer) {
|
|
712
|
+
this._caption = Figure.CreateCaption(cover, this.lang.caption);
|
|
713
|
+
modifiedCaption = true;
|
|
714
|
+
}
|
|
715
|
+
} else {
|
|
716
|
+
if (this._caption) {
|
|
717
|
+
dom.utils.removeItem(this._caption);
|
|
718
|
+
this._caption = null;
|
|
719
|
+
modifiedCaption = true;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// link
|
|
725
|
+
let isNewAnchor = null;
|
|
726
|
+
const anchor = this.anchor.create(true);
|
|
727
|
+
if (anchor) {
|
|
728
|
+
if (this._linkElement !== anchor || (isNewContainer && !container.contains(anchor))) {
|
|
729
|
+
this._linkElement = anchor.cloneNode(false);
|
|
730
|
+
cover.insertBefore(this._setAnchor(imageEl, this._linkElement), this._caption);
|
|
731
|
+
isNewAnchor = this._element;
|
|
732
|
+
}
|
|
733
|
+
} else if (this._linkElement !== null) {
|
|
734
|
+
if (cover.contains(this._linkElement)) {
|
|
735
|
+
const newEl = imageEl.cloneNode(true);
|
|
736
|
+
cover.removeChild(this._linkElement);
|
|
737
|
+
cover.insertBefore(newEl, this._caption);
|
|
738
|
+
imageEl = newEl;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (isNewContainer) {
|
|
743
|
+
imageEl = this._element;
|
|
744
|
+
this.figure.retainFigureFormat(container, this._element, isNewAnchor ? anchor : null, this.fileManager);
|
|
745
|
+
this._element = imageEl = container.querySelector('img');
|
|
746
|
+
this._cover = cover;
|
|
747
|
+
this._container = container;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// size
|
|
751
|
+
imageEl.style.width = '';
|
|
752
|
+
imageEl.style.height = '';
|
|
753
|
+
imageEl.removeAttribute('width');
|
|
754
|
+
imageEl.removeAttribute('height');
|
|
755
|
+
this._applySize(width, height);
|
|
756
|
+
|
|
757
|
+
if (isNewAnchor) {
|
|
758
|
+
if (!isNewContainer) {
|
|
759
|
+
dom.utils.removeItem(anchor);
|
|
760
|
+
} else {
|
|
761
|
+
dom.utils.removeItem(isNewAnchor);
|
|
762
|
+
if (dom.query.getListChildren(anchor, (current) => /IMG/i.test(current.tagName)).length === 0) {
|
|
763
|
+
dom.utils.removeItem(anchor);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// transform
|
|
769
|
+
if (modifiedCaption || !this._onlyPercentage) {
|
|
770
|
+
if (/\d+/.test(imageEl.style.height) || (this.figure.isVertical && this.captionCheckEl.checked)) {
|
|
771
|
+
if (/auto|%$/.test(width) || /auto|%$/.test(height)) {
|
|
772
|
+
this.figure.deleteTransform(imageEl);
|
|
773
|
+
} else {
|
|
774
|
+
this.figure.setTransform(imageEl, width, height, 0);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// align
|
|
780
|
+
this.figure.setAlign(imageEl, this._align);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @description Opens a specific tab inside the modal.
|
|
785
|
+
* @param {MouseEvent|string} e - The event object or tab name.
|
|
786
|
+
* @returns {boolean} - Whether the tab was successfully opened.
|
|
787
|
+
*/
|
|
788
|
+
#OpenTab(e) {
|
|
789
|
+
const modalForm = this.modal.form;
|
|
790
|
+
const targetElement = typeof e === 'string' ? modalForm.querySelector('._se_tab_link') : dom.query.getEventTarget(e);
|
|
791
|
+
|
|
792
|
+
if (!/^BUTTON$/i.test(targetElement.tagName)) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Declare all variables
|
|
797
|
+
const tabName = targetElement.getAttribute('data-tab-link');
|
|
798
|
+
let i;
|
|
799
|
+
|
|
800
|
+
// Get all elements with class="tabcontent" and hide them
|
|
801
|
+
const tabContent = /** @type {HTMLCollectionOf<HTMLElement>}*/ (modalForm.getElementsByClassName('_se_tab_content'));
|
|
802
|
+
for (i = 0; i < tabContent.length; i++) {
|
|
803
|
+
tabContent[i].style.display = 'none';
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Get all elements with class="tablinks" and remove the class "active"
|
|
807
|
+
const tabLinks = modalForm.getElementsByClassName('_se_tab_link');
|
|
808
|
+
for (i = 0; i < tabLinks.length; i++) {
|
|
809
|
+
dom.utils.removeClass(tabLinks[i], 'active');
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Show the current tab, and add an "active" class to the button that opened the tab
|
|
813
|
+
/** @type {HTMLElement}*/ (modalForm.querySelector('._se_tab_content_' + tabName)).style.display = 'block';
|
|
814
|
+
dom.utils.addClass(targetElement, 'active');
|
|
815
|
+
|
|
816
|
+
// focus
|
|
817
|
+
if (e !== 'init') {
|
|
818
|
+
if (tabName === 'image') {
|
|
819
|
+
this.focusElement.focus();
|
|
820
|
+
} else if (tabName === 'url') {
|
|
821
|
+
this.anchor.urlInput.focus();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* @private
|
|
830
|
+
* @description Creates a new image component based on provided parameters.
|
|
831
|
+
* @param {string} src - The image source URL.
|
|
832
|
+
* @param {?Node} anchor - Optional anchor wrapping the image.
|
|
833
|
+
* @param {string} width - Image width.
|
|
834
|
+
* @param {string} height - Image height.
|
|
835
|
+
* @param {string} align - Image alignment.
|
|
836
|
+
* @param {{name: string, size: number}} file - File metadata.
|
|
837
|
+
* @param {string} alt - Alternative text.
|
|
838
|
+
*/
|
|
839
|
+
_produce(src, anchor, width, height, align, file, alt) {
|
|
840
|
+
if (this.as !== 'inline') {
|
|
841
|
+
this.create(src, anchor, width, height, align, file, alt);
|
|
842
|
+
} else {
|
|
843
|
+
this.createInline(src, anchor, width, height, file, alt);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* @private
|
|
849
|
+
* @description Applies the specified width and height to the image.
|
|
850
|
+
* @param {string} w - Image width.
|
|
851
|
+
* @param {string} h - Image height.
|
|
852
|
+
*/
|
|
853
|
+
_applySize(w, h) {
|
|
854
|
+
if (!w) w = this.inputX?.value || this.pluginOptions.defaultWidth;
|
|
855
|
+
if (!h) h = this.inputY?.value || this.pluginOptions.defaultHeight;
|
|
856
|
+
if (this._onlyPercentage) {
|
|
857
|
+
if (!w) w = '100%';
|
|
858
|
+
else if (/%$/.test(w)) w += '%';
|
|
859
|
+
}
|
|
860
|
+
this.figure.setSize(w, h);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* @description Creates a new image component, wraps it in a figure container with an optional anchor,
|
|
865
|
+
* - applies size and alignment settings, and inserts it into the editor.
|
|
866
|
+
* @param {string} src - The URL of the image to be inserted.
|
|
867
|
+
* @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
|
|
868
|
+
* @param {string} width - The width value to be applied to the image.
|
|
869
|
+
* @param {string} height - The height value to be applied to the image.
|
|
870
|
+
* @param {string} align - The alignment setting for the image (e.g., 'left', 'center', 'right').
|
|
871
|
+
* @param {{name: string, size: number}} file - File metadata associated with the image
|
|
872
|
+
* @param {string} alt - The alternative text for the image.
|
|
873
|
+
*/
|
|
874
|
+
create(src, anchor, width, height, align, file, alt) {
|
|
875
|
+
/** @type {HTMLImageElement} */
|
|
876
|
+
const oImg = dom.utils.createElement('IMG');
|
|
877
|
+
oImg.src = src;
|
|
878
|
+
oImg.alt = alt;
|
|
879
|
+
anchor = this._setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
|
|
880
|
+
|
|
881
|
+
const figureInfo = Figure.CreateContainer(anchor, 'se-image-container');
|
|
882
|
+
const cover = figureInfo.cover;
|
|
883
|
+
const container = figureInfo.container;
|
|
884
|
+
|
|
885
|
+
// caption
|
|
886
|
+
if (this.captionCheckEl.checked) {
|
|
887
|
+
this._caption = Figure.CreateCaption(cover, this.lang.caption);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
this._element = oImg;
|
|
891
|
+
this._cover = cover;
|
|
892
|
+
this._container = container;
|
|
893
|
+
this.figure.open(oImg, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
|
|
894
|
+
|
|
895
|
+
// set size
|
|
896
|
+
this._applySize(width, height);
|
|
897
|
+
|
|
898
|
+
// align
|
|
899
|
+
this.figure.setAlign(oImg, align);
|
|
900
|
+
|
|
901
|
+
this.fileManager.setFileData(oImg, file);
|
|
902
|
+
|
|
903
|
+
oImg.onload = this.#OnloadImg.bind(this, oImg, this._svgDefaultSize, container);
|
|
904
|
+
this.component.insert(container, { skipCharCount: false, skipSelection: !this.options.get('componentAutoSelect'), skipHistory: false });
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* @description Creates a new inline image component, wraps it in an inline figure container with an optional anchor,
|
|
909
|
+
* - applies size settings, and inserts it into the editor.
|
|
910
|
+
* @param {string} src - The URL of the image to be inserted.
|
|
911
|
+
* @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
|
|
912
|
+
* @param {string} width - The width value to be applied to the image.
|
|
913
|
+
* @param {string} height - The height value to be applied to the image.
|
|
914
|
+
* @param {{name: string, size: number}} file - File metadata associated with the image
|
|
915
|
+
* @param {string} alt - The alternative text for the image.
|
|
916
|
+
*/
|
|
917
|
+
createInline(src, anchor, width, height, file, alt) {
|
|
918
|
+
/** @type {HTMLImageElement} */
|
|
919
|
+
const oImg = dom.utils.createElement('IMG');
|
|
920
|
+
oImg.src = src;
|
|
921
|
+
oImg.alt = alt;
|
|
922
|
+
anchor = this._setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
|
|
923
|
+
|
|
924
|
+
const figureInfo = Figure.CreateInlineContainer(anchor, 'se-image-container');
|
|
925
|
+
const container = figureInfo.container;
|
|
926
|
+
|
|
927
|
+
this._element = oImg;
|
|
928
|
+
this._container = container;
|
|
929
|
+
this.figure.open(oImg, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
|
|
930
|
+
|
|
931
|
+
// set size
|
|
932
|
+
this._applySize(width, height);
|
|
933
|
+
|
|
934
|
+
this.fileManager.setFileData(oImg, file);
|
|
935
|
+
|
|
936
|
+
oImg.onload = this.#OnloadImg.bind(this, oImg, this._svgDefaultSize, container);
|
|
937
|
+
this.component.insert(container, { skipCharCount: false, skipSelection: true, skipHistory: false });
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* @private
|
|
942
|
+
* @description Updates the image source URL.
|
|
943
|
+
* @param {string} src - The new image source.
|
|
944
|
+
* @param {HTMLImageElement} element - The image element.
|
|
945
|
+
* @param {{ name: string, size: number }} file - File metadata.
|
|
946
|
+
*/
|
|
947
|
+
_updateSrc(src, element, file) {
|
|
948
|
+
element.src = src;
|
|
949
|
+
this.fileManager.setFileData(element, file);
|
|
950
|
+
this.component.select(element, Image_.key);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* @private
|
|
955
|
+
* @description Registers the uploaded image and inserts it into the editor.
|
|
956
|
+
* @param {ImageInfo_image} info - Image info.
|
|
957
|
+
* @param {Object<string, *>} response - Server response data.
|
|
958
|
+
*/
|
|
959
|
+
_register(info, response) {
|
|
960
|
+
const fileList = response.result;
|
|
961
|
+
|
|
962
|
+
for (let i = 0, len = fileList.length, file; i < len; i++) {
|
|
963
|
+
file = {
|
|
964
|
+
name: fileList[i].name,
|
|
965
|
+
size: fileList[i].size
|
|
966
|
+
};
|
|
967
|
+
if (info.isUpdate) {
|
|
968
|
+
this._updateSrc(fileList[i].url, info.element, file);
|
|
969
|
+
break;
|
|
970
|
+
} else {
|
|
971
|
+
this._produce(fileList[i].url, info.anchor, info.inputWidth, info.inputHeight, info.align, file, info.alt);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* @private
|
|
978
|
+
* @description Uploads the image to the server.
|
|
979
|
+
* @param {ImageInfo_image} info - Image upload info.
|
|
980
|
+
* @param {FileList} files - List of image files.
|
|
981
|
+
*/
|
|
982
|
+
_serverUpload(info, files) {
|
|
983
|
+
if (!files) return;
|
|
984
|
+
|
|
985
|
+
// server upload
|
|
986
|
+
const imageUploadUrl = this.pluginOptions.uploadUrl;
|
|
987
|
+
if (typeof imageUploadUrl === 'string' && imageUploadUrl.length > 0) {
|
|
988
|
+
this.fileManager.upload(imageUploadUrl, this.pluginOptions.uploadHeaders, files, this.#UploadCallBack.bind(this, info), this._error.bind(this));
|
|
989
|
+
} else {
|
|
990
|
+
this._setBase64(files, info.anchor, info.inputWidth, info.inputHeight, info.align, info.alt, info.isUpdate);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* @private
|
|
996
|
+
* @description Converts an image file to Base64 and inserts it into the editor.
|
|
997
|
+
* @param {FileList|File[]} files - List of image files.
|
|
998
|
+
* @param {?Node} anchor - Optional anchor wrapping the image.
|
|
999
|
+
* @param {string} width - Image width.
|
|
1000
|
+
* @param {string} height - Image height.
|
|
1001
|
+
* @param {string} align - Image alignment.
|
|
1002
|
+
* @param {string} alt - Alternative text.
|
|
1003
|
+
* @param {boolean} isUpdate - Whether the image is being updated.
|
|
1004
|
+
*/
|
|
1005
|
+
_setBase64(files, anchor, width, height, align, alt, isUpdate) {
|
|
1006
|
+
try {
|
|
1007
|
+
const filesLen = this.modal.isUpdate ? 1 : files.length;
|
|
1008
|
+
|
|
1009
|
+
if (filesLen === 0) {
|
|
1010
|
+
this.ui.hideLoading();
|
|
1011
|
+
console.warn('[SUNEDITOR.image.base64.fail] cause : No applicable files');
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
this._base64RenderIndex = filesLen;
|
|
1016
|
+
const filesStack = new Array(filesLen);
|
|
1017
|
+
|
|
1018
|
+
if (this._resizing) {
|
|
1019
|
+
this.inputX.value = width;
|
|
1020
|
+
this.inputY.value = height;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
for (let i = 0, reader, file; i < filesLen; i++) {
|
|
1024
|
+
reader = new FileReader();
|
|
1025
|
+
file = files[i];
|
|
1026
|
+
|
|
1027
|
+
reader.onload = function (on_reader, update, updateElement, on_file, index) {
|
|
1028
|
+
filesStack[index] = {
|
|
1029
|
+
result: on_reader.result,
|
|
1030
|
+
file: on_file
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
if (--this._base64RenderIndex === 0) {
|
|
1034
|
+
this._onRenderBase64(update, filesStack, updateElement, anchor, width, height, align, alt);
|
|
1035
|
+
this.ui.hideLoading();
|
|
1036
|
+
}
|
|
1037
|
+
}.bind(this, reader, isUpdate, this._element, file, i);
|
|
1038
|
+
// se-ts-ignore
|
|
1039
|
+
this._onRenderBase64;
|
|
1040
|
+
|
|
1041
|
+
reader.readAsDataURL(file);
|
|
1042
|
+
}
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
this.ui.hideLoading();
|
|
1045
|
+
throw Error(`[SUNEDITOR.plugins.image._setBase64.fail] ${error.message}`);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* @private
|
|
1051
|
+
* @description Inserts an image using a Base64-encoded string.
|
|
1052
|
+
* @param {boolean} update - Whether the image is being updated.
|
|
1053
|
+
* @param {Array<{result: string, file: { name: string, size: number }}>} filesStack - Stack of Base64-encoded files.
|
|
1054
|
+
* - result: Image url or Base64-encoded string
|
|
1055
|
+
* - file: File metadata ({ name: string, size: number })
|
|
1056
|
+
* @param {HTMLImageElement} updateElement - The image element being updated.
|
|
1057
|
+
* @param {?HTMLAnchorElement} anchor - Optional anchor wrapping the image.
|
|
1058
|
+
* @param {string} width - Image width.
|
|
1059
|
+
* @param {string} height - Image height.
|
|
1060
|
+
* @param {string} align - Image alignment.
|
|
1061
|
+
* @param {string} alt - Alternative text.
|
|
1062
|
+
*/
|
|
1063
|
+
_onRenderBase64(update, filesStack, updateElement, anchor, width, height, align, alt) {
|
|
1064
|
+
for (let i = 0, len = filesStack.length; i < len; i++) {
|
|
1065
|
+
if (update) {
|
|
1066
|
+
this._updateSrc(filesStack[i].result, updateElement, filesStack[i].file);
|
|
1067
|
+
} else {
|
|
1068
|
+
this._produce(filesStack[i].result, anchor, width, height, align, filesStack[i].file, alt);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* @private
|
|
1075
|
+
* @description Wraps an image element with an anchor if provided.
|
|
1076
|
+
* @param {Node} imgTag - The image element to be wrapped.
|
|
1077
|
+
* @param {?Node} anchor - The anchor element to wrap around the image. If null, returns the image itself.
|
|
1078
|
+
* @returns {Node} - The wrapped image inside the anchor or the original image element.
|
|
1079
|
+
*/
|
|
1080
|
+
_setAnchor(imgTag, anchor) {
|
|
1081
|
+
if (anchor) {
|
|
1082
|
+
anchor.appendChild(imgTag);
|
|
1083
|
+
return anchor;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
return imgTag;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* @private
|
|
1091
|
+
* @description Handles errors during image upload and displays appropriate messages.
|
|
1092
|
+
* @param {Object<string, *>} response - The error response from the server.
|
|
1093
|
+
* @returns {Promise<void>}
|
|
1094
|
+
*/
|
|
1095
|
+
async _error(response) {
|
|
1096
|
+
const message = await this.triggerEvent('onImageUploadError', { error: response });
|
|
1097
|
+
const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
|
|
1098
|
+
this.ui.alertOpen(err, 'error');
|
|
1099
|
+
console.error('[SUNEDITOR.plugin.image.error]', err);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* @description Handles the callback function for image upload completion.
|
|
1104
|
+
* @param {ImageInfo_image} info - Image information.
|
|
1105
|
+
* @param {XMLHttpRequest} xmlHttp - The XMLHttpRequest object.
|
|
1106
|
+
*/
|
|
1107
|
+
async #UploadCallBack(info, xmlHttp) {
|
|
1108
|
+
if ((await this.triggerEvent('imageUploadHandler', { xmlHttp, info })) === NO_EVENT) {
|
|
1109
|
+
const response = JSON.parse(xmlHttp.responseText);
|
|
1110
|
+
if (response.errorMessage) {
|
|
1111
|
+
this._error(response);
|
|
1112
|
+
} else {
|
|
1113
|
+
this._register(info, response);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
#RemoveSelectedFiles() {
|
|
1119
|
+
this.imgInputFile.value = '';
|
|
1120
|
+
if (this.imgUrlFile) {
|
|
1121
|
+
this.imgUrlFile.disabled = false;
|
|
1122
|
+
this.previewSrc.style.textDecoration = '';
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// inputFile check
|
|
1126
|
+
Modal.OnChangeFile(this.fileModalWrapper, []);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
#OnInputSize(xy, e) {
|
|
1130
|
+
if (keyCodeMap.isSpace(e.code)) {
|
|
1131
|
+
e.preventDefault();
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (xy === 'x' && this._onlyPercentage && e.target.value > 100) {
|
|
1136
|
+
e.target.value = 100;
|
|
1137
|
+
} else if (this.proportion.checked) {
|
|
1138
|
+
const ratioSize = Figure.CalcRatio(this.inputX.value, this.inputY.value, this.sizeUnit, this._ratio);
|
|
1139
|
+
if (xy === 'x') {
|
|
1140
|
+
this.inputY.value = String(ratioSize.h);
|
|
1141
|
+
} else {
|
|
1142
|
+
this.inputX.value = String(ratioSize.w);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
#OnChangeRatio() {
|
|
1148
|
+
this._ratio = this.proportion.checked
|
|
1149
|
+
? Figure.GetRatio(this.inputX.value, this.inputY.value, this.sizeUnit)
|
|
1150
|
+
: {
|
|
1151
|
+
w: 1,
|
|
1152
|
+
h: 1
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
#OnClickRevert() {
|
|
1157
|
+
if (this._onlyPercentage) {
|
|
1158
|
+
this.inputX.value = Number(this._origin_w) > 100 ? '100' : this._origin_w;
|
|
1159
|
+
} else {
|
|
1160
|
+
this.inputX.value = this._origin_w;
|
|
1161
|
+
this.inputY.value = this._origin_h;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
#OnClickAsButton({ target }) {
|
|
1166
|
+
this._activeAsInline(target.getAttribute('data-command') === 'asInline');
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
#OnLinkPreview(e) {
|
|
1170
|
+
const value = e.target.value.trim();
|
|
1171
|
+
this._linkValue = this.previewSrc.textContent = !value
|
|
1172
|
+
? ''
|
|
1173
|
+
: this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
|
|
1174
|
+
? this.options.get('defaultUrlProtocol') + value
|
|
1175
|
+
: !value.includes('://')
|
|
1176
|
+
? '/' + value
|
|
1177
|
+
: value;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
#OnfileInputChange({ target }) {
|
|
1181
|
+
if (!this.imgInputFile.value) {
|
|
1182
|
+
this.imgUrlFile.disabled = false;
|
|
1183
|
+
this.previewSrc.style.textDecoration = '';
|
|
1184
|
+
} else {
|
|
1185
|
+
this.imgUrlFile.disabled = true;
|
|
1186
|
+
this.previewSrc.style.textDecoration = 'line-through';
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// inputFile check
|
|
1190
|
+
Modal.OnChangeFile(this.fileModalWrapper, target.files);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
#OpenGallery() {
|
|
1194
|
+
this.plugins.imageGallery.open(this.#SetUrlInput.bind(this));
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
#SetUrlInput(target) {
|
|
1198
|
+
this.altText.value = target.getAttribute('data-value') || target.alt;
|
|
1199
|
+
this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.getAttribute('data-command') || target.src;
|
|
1200
|
+
this.imgUrlFile.focus();
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
#OnloadImg(oImg, _svgDefaultSize, container) {
|
|
1204
|
+
// svg exception handling
|
|
1205
|
+
if (oImg.offsetWidth === 0) this._applySize(_svgDefaultSize, '');
|
|
1206
|
+
if (this.options.get('componentAutoSelect')) {
|
|
1207
|
+
this.component.select(oImg, Image_.key);
|
|
1208
|
+
} else {
|
|
1209
|
+
if (!this.component.isInline(container)) {
|
|
1210
|
+
const line = this.format.addLine(container, null);
|
|
1211
|
+
if (line) this.selection.setRange(line, 0, line, 0);
|
|
1212
|
+
} else {
|
|
1213
|
+
const r = this.selection.getNearRange(container);
|
|
1214
|
+
if (r) {
|
|
1215
|
+
this.selection.setRange(r.container, r.offset, r.container, r.offset);
|
|
1216
|
+
} else {
|
|
1217
|
+
this.component.select(oImg, Image_.key);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
this.editor._iframeAutoHeight(this.editor.frameContext);
|
|
1223
|
+
this.history.push(false);
|
|
1224
|
+
|
|
1225
|
+
delete oImg.onload;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* @typedef {Object} ModalReturns_image
|
|
1231
|
+
* @property {HTMLElement} html
|
|
1232
|
+
* @property {HTMLElement} alignForm
|
|
1233
|
+
* @property {HTMLElement} fileModalWrapper
|
|
1234
|
+
* @property {HTMLInputElement} imgInputFile
|
|
1235
|
+
* @property {HTMLInputElement} imgUrlFile
|
|
1236
|
+
* @property {HTMLInputElement} altText
|
|
1237
|
+
* @property {HTMLInputElement} captionCheckEl
|
|
1238
|
+
* @property {HTMLElement} previewSrc
|
|
1239
|
+
* @property {HTMLElement} tabs
|
|
1240
|
+
* @property {HTMLButtonElement} galleryButton
|
|
1241
|
+
* @property {HTMLInputElement} proportion
|
|
1242
|
+
* @property {HTMLInputElement} inputX
|
|
1243
|
+
* @property {HTMLInputElement} inputY
|
|
1244
|
+
* @property {HTMLButtonElement} revertBtn
|
|
1245
|
+
* @property {HTMLButtonElement} asBlock
|
|
1246
|
+
* @property {HTMLButtonElement} asInline
|
|
1247
|
+
* @property {HTMLButtonElement} fileRemoveBtn
|
|
1248
|
+
*
|
|
1249
|
+
* @param {__se__EditorCore} editor
|
|
1250
|
+
* @param {*} pluginOptions
|
|
1251
|
+
* @returns {ModalReturns_image}
|
|
1252
|
+
*/
|
|
1253
|
+
function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
|
|
1254
|
+
const createFileInputHtml = !pluginOptions.createFileInput
|
|
1255
|
+
? ''
|
|
1256
|
+
: /*html*/ `
|
|
1257
|
+
<div class="se-modal-form">
|
|
1258
|
+
<label>${lang.image_modal_file}</label>
|
|
1259
|
+
${Modal.CreateFileInput({ icons, lang }, pluginOptions)}
|
|
1260
|
+
</div>`;
|
|
1261
|
+
|
|
1262
|
+
const createUrlInputHtml = !pluginOptions.createUrlInput
|
|
1263
|
+
? ''
|
|
1264
|
+
: /*html*/ `
|
|
1265
|
+
<div class="se-modal-form">
|
|
1266
|
+
<label>${lang.image_modal_url}</label>
|
|
1267
|
+
<div class="se-modal-form-files">
|
|
1268
|
+
<input class="se-input-form se-input-url" data-focus type="text" />
|
|
1269
|
+
${
|
|
1270
|
+
plugins.imageGallery
|
|
1271
|
+
? `<button type="button" class="se-btn se-tooltip se-modal-files-edge-button __se__gallery" aria-label="${lang.imageGallery}">
|
|
1272
|
+
${icons.image_gallery}
|
|
1273
|
+
${dom.utils.createTooltipInner(lang.imageGallery)}
|
|
1274
|
+
</button>`
|
|
1275
|
+
: ''
|
|
1276
|
+
}
|
|
1277
|
+
</div>
|
|
1278
|
+
<pre class="se-link-preview"></pre>
|
|
1279
|
+
</div>`;
|
|
1280
|
+
|
|
1281
|
+
const canResizeHtml = !pluginOptions.canResize
|
|
1282
|
+
? ''
|
|
1283
|
+
: /*html*/ `
|
|
1284
|
+
<div class="se-modal-form">
|
|
1285
|
+
<div class="se-modal-size-text">
|
|
1286
|
+
<label class="size-w">${lang.width}</label>
|
|
1287
|
+
<label class="se-modal-size-x"> </label>
|
|
1288
|
+
<label class="size-h">${lang.height}</label>
|
|
1289
|
+
</div>
|
|
1290
|
+
<input class="se-input-control _se_size_x" placeholder="auto" type="text" />
|
|
1291
|
+
<label class="se-modal-size-x">x</label>
|
|
1292
|
+
<input type="text" class="se-input-control _se_size_y" placeholder="auto" />
|
|
1293
|
+
<label><input type="checkbox" class="se-modal-btn-check _se_check_proportion" checked/> ${lang.proportion}</label>
|
|
1294
|
+
<button type="button" aria-label="${lang.revert}" class="se-btn se-tooltip se-modal-btn-revert">
|
|
1295
|
+
${icons.revert}
|
|
1296
|
+
${dom.utils.createTooltipInner(lang.revert)}
|
|
1297
|
+
</button>
|
|
1298
|
+
</div>`;
|
|
1299
|
+
|
|
1300
|
+
const useFormatTypeHtml = !pluginOptions.useFormatType
|
|
1301
|
+
? ''
|
|
1302
|
+
: /*html*/ `
|
|
1303
|
+
<div class="se-modal-form">
|
|
1304
|
+
<div class="se-modal-flex-form">
|
|
1305
|
+
<button type="button" data-command="asBlock" class="se-btn se-tooltip" aria-label="${lang.inlineStyle}">
|
|
1306
|
+
${icons.as_block}
|
|
1307
|
+
${dom.utils.createTooltipInner(lang.blockStyle)}
|
|
1308
|
+
</button>
|
|
1309
|
+
<button type="button" data-command="asInline" class="se-btn se-tooltip" aria-label="${lang.inlineStyle}">
|
|
1310
|
+
${icons.as_inline}
|
|
1311
|
+
${dom.utils.createTooltipInner(lang.inlineStyle)}
|
|
1312
|
+
</button>
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>`;
|
|
1315
|
+
|
|
1316
|
+
const html = /*html*/ `
|
|
1317
|
+
<div class="se-modal-header">
|
|
1318
|
+
<button type="button" data-command="close" class="se-btn se-close-btn close" title="${lang.close}" aria-label="${lang.close}">${icons.cancel}</button>
|
|
1319
|
+
<span class="se-modal-title">${lang.image_modal_title}</span>
|
|
1320
|
+
</div>
|
|
1321
|
+
<div class="se-modal-tabs">
|
|
1322
|
+
<button type="button" class="_se_tab_link active" data-tab-link="image">${lang.image}</button>
|
|
1323
|
+
<button type="button" class="_se_tab_link" data-tab-link="url">${lang.link}</button>
|
|
1324
|
+
</div>
|
|
1325
|
+
<form method="post" enctype="multipart/form-data">
|
|
1326
|
+
<div class="_se_tab_content _se_tab_content_image">
|
|
1327
|
+
<div class="se-modal-body">
|
|
1328
|
+
${createFileInputHtml}
|
|
1329
|
+
${createUrlInputHtml}
|
|
1330
|
+
<div style="border-bottom: 1px dashed #ccc;"></div>
|
|
1331
|
+
<div class="se-modal-form">
|
|
1332
|
+
<label>${lang.image_modal_altText}</label><input class="se-input-form _se_image_alt" type="text" />
|
|
1333
|
+
</div>
|
|
1334
|
+
${canResizeHtml}
|
|
1335
|
+
${useFormatTypeHtml}
|
|
1336
|
+
<div class="se-modal-form se-modal-form-footer">
|
|
1337
|
+
<label><input type="checkbox" class="se-modal-btn-check _se_image_check_caption" /> ${lang.caption}</label>
|
|
1338
|
+
</div>
|
|
1339
|
+
</div>
|
|
1340
|
+
</div>
|
|
1341
|
+
<div class="se-anchor-editor _se_tab_content _se_tab_content_url" style="display: none;">
|
|
1342
|
+
</div>
|
|
1343
|
+
<div class="se-modal-footer">
|
|
1344
|
+
<div class="se-figure-align">
|
|
1345
|
+
<label><input type="radio" name="suneditor_image_radio" class="se-modal-btn-radio" value="none" checked>${lang.basic}</label>
|
|
1346
|
+
<label><input type="radio" name="suneditor_image_radio" class="se-modal-btn-radio" value="left">${lang.left}</label>
|
|
1347
|
+
<label><input type="radio" name="suneditor_image_radio" class="se-modal-btn-radio" value="center">${lang.center}</label>
|
|
1348
|
+
<label><input type="radio" name="suneditor_image_radio" class="se-modal-btn-radio" value="right">${lang.right}</label>
|
|
1349
|
+
</div>
|
|
1350
|
+
<button type="submit" class="se-btn-primary" title="${lang.submitButton}" aria-label="${lang.submitButton}"><span>${lang.submitButton}</span></button>
|
|
1351
|
+
</div>
|
|
1352
|
+
</form>`;
|
|
1353
|
+
|
|
1354
|
+
const content = dom.utils.createElement('DIV', { class: 'se-modal-content' }, html);
|
|
1355
|
+
|
|
1356
|
+
return {
|
|
1357
|
+
html: content,
|
|
1358
|
+
alignForm: content.querySelector('.se-figure-align'),
|
|
1359
|
+
fileModalWrapper: content.querySelector('.se-flex-input-wrapper'),
|
|
1360
|
+
imgInputFile: content.querySelector('.__se__file_input'),
|
|
1361
|
+
imgUrlFile: content.querySelector('.se-input-url'),
|
|
1362
|
+
altText: content.querySelector('._se_image_alt'),
|
|
1363
|
+
captionCheckEl: content.querySelector('._se_image_check_caption'),
|
|
1364
|
+
previewSrc: content.querySelector('._se_tab_content_image .se-link-preview'),
|
|
1365
|
+
tabs: content.querySelector('.se-modal-tabs'),
|
|
1366
|
+
galleryButton: content.querySelector('.__se__gallery'),
|
|
1367
|
+
proportion: content.querySelector('._se_check_proportion'),
|
|
1368
|
+
inputX: content.querySelector('._se_size_x'),
|
|
1369
|
+
inputY: content.querySelector('._se_size_y'),
|
|
1370
|
+
revertBtn: content.querySelector('.se-modal-btn-revert'),
|
|
1371
|
+
asBlock: content.querySelector('[data-command="asBlock"]'),
|
|
1372
|
+
asInline: content.querySelector('[data-command="asInline"]'),
|
|
1373
|
+
fileRemoveBtn: content.querySelector('.se-file-remove')
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
export default Image_;
|