suneditor 3.0.0-beta.3 → 3.0.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/CONTRIBUTING.md +8 -8
  2. package/README.md +44 -49
  3. package/dist/suneditor.min.css +1 -1
  4. package/dist/suneditor.min.js +1 -1
  5. package/package.json +95 -53
  6. package/src/assets/design/color.css +2 -2
  7. package/src/assets/design/size.css +2 -0
  8. package/src/assets/icons/defaultIcons.js +16 -1
  9. package/src/assets/suneditor-contents.css +9 -8
  10. package/src/assets/suneditor.css +29 -26
  11. package/src/core/{section → base}/actives.js +20 -12
  12. package/src/core/base/history.js +4 -4
  13. package/src/core/class/char.js +10 -10
  14. package/src/core/class/component.js +146 -57
  15. package/src/core/class/format.js +94 -2458
  16. package/src/core/class/html.js +187 -129
  17. package/src/core/class/inline.js +1853 -0
  18. package/src/core/class/listFormat.js +582 -0
  19. package/src/core/class/menu.js +14 -3
  20. package/src/core/class/nodeTransform.js +9 -14
  21. package/src/core/class/offset.js +162 -197
  22. package/src/core/class/selection.js +137 -34
  23. package/src/core/class/toolbar.js +73 -52
  24. package/src/core/class/ui.js +11 -11
  25. package/src/core/class/viewer.js +56 -55
  26. package/src/core/config/context.js +122 -0
  27. package/src/core/config/frameContext.js +204 -0
  28. package/src/core/config/options.js +639 -0
  29. package/src/core/editor.js +181 -108
  30. package/src/core/event/actions/index.js +229 -0
  31. package/src/core/event/effects/common.registry.js +60 -0
  32. package/src/core/event/effects/keydown.registry.js +551 -0
  33. package/src/core/event/effects/ruleHelpers.js +145 -0
  34. package/src/core/{base → event}/eventManager.js +119 -201
  35. package/src/core/event/executor.js +21 -0
  36. package/src/core/{base/eventHandlers → event/handlers}/handler_toolbar.js +4 -4
  37. package/src/core/{base/eventHandlers → event/handlers}/handler_ww_dragDrop.js +2 -2
  38. package/src/core/event/handlers/handler_ww_input.js +77 -0
  39. package/src/core/event/handlers/handler_ww_key.js +228 -0
  40. package/src/core/{base/eventHandlers → event/handlers}/handler_ww_mouse.js +3 -3
  41. package/src/core/event/ports.js +211 -0
  42. package/src/core/event/reducers/keydown.reducer.js +89 -0
  43. package/src/core/event/rules/keydown.rule.arrow.js +54 -0
  44. package/src/core/event/rules/keydown.rule.backspace.js +202 -0
  45. package/src/core/event/rules/keydown.rule.delete.js +126 -0
  46. package/src/core/event/rules/keydown.rule.enter.js +144 -0
  47. package/src/core/event/rules/keydown.rule.tab.js +29 -0
  48. package/src/core/section/constructor.js +79 -388
  49. package/src/core/section/documentType.js +47 -26
  50. package/src/core/util/instanceCheck.js +59 -0
  51. package/src/editorInjector/_classes.js +4 -0
  52. package/src/editorInjector/_core.js +17 -7
  53. package/src/editorInjector/index.js +10 -2
  54. package/src/events.js +6 -0
  55. package/src/helper/clipboard.js +24 -10
  56. package/src/helper/converter.js +17 -12
  57. package/src/helper/dom/domCheck.js +22 -3
  58. package/src/helper/dom/domQuery.js +91 -45
  59. package/src/helper/dom/domUtils.js +93 -19
  60. package/src/helper/dom/index.js +4 -0
  61. package/src/helper/env.js +11 -7
  62. package/src/helper/keyCodeMap.js +4 -3
  63. package/src/langs/ckb.js +1 -1
  64. package/src/langs/cs.js +1 -1
  65. package/src/langs/da.js +1 -1
  66. package/src/langs/de.js +1 -1
  67. package/src/langs/en.js +1 -1
  68. package/src/langs/es.js +1 -1
  69. package/src/langs/fa.js +1 -1
  70. package/src/langs/fr.js +1 -1
  71. package/src/langs/he.js +1 -1
  72. package/src/langs/hu.js +1 -1
  73. package/src/langs/it.js +1 -1
  74. package/src/langs/ja.js +1 -1
  75. package/src/langs/km.js +1 -1
  76. package/src/langs/ko.js +1 -1
  77. package/src/langs/lv.js +1 -1
  78. package/src/langs/nl.js +1 -1
  79. package/src/langs/pl.js +1 -1
  80. package/src/langs/pt_br.js +10 -10
  81. package/src/langs/ro.js +1 -1
  82. package/src/langs/ru.js +1 -1
  83. package/src/langs/se.js +1 -1
  84. package/src/langs/tr.js +1 -1
  85. package/src/langs/uk.js +1 -1
  86. package/src/langs/ur.js +1 -1
  87. package/src/langs/zh_cn.js +1 -1
  88. package/src/modules/ApiManager.js +25 -18
  89. package/src/modules/Browser.js +52 -61
  90. package/src/modules/ColorPicker.js +37 -38
  91. package/src/modules/Controller.js +85 -79
  92. package/src/modules/Figure.js +275 -187
  93. package/src/modules/FileManager.js +86 -92
  94. package/src/modules/HueSlider.js +67 -35
  95. package/src/modules/Modal.js +84 -77
  96. package/src/modules/ModalAnchorEditor.js +62 -79
  97. package/src/modules/SelectMenu.js +89 -86
  98. package/src/plugins/browser/audioGallery.js +9 -5
  99. package/src/plugins/browser/fileBrowser.js +10 -6
  100. package/src/plugins/browser/fileGallery.js +9 -5
  101. package/src/plugins/browser/imageGallery.js +9 -5
  102. package/src/plugins/browser/videoGallery.js +11 -6
  103. package/src/plugins/command/blockquote.js +1 -0
  104. package/src/plugins/command/exportPDF.js +11 -8
  105. package/src/plugins/command/fileUpload.js +41 -29
  106. package/src/plugins/command/list_bulleted.js +2 -1
  107. package/src/plugins/command/list_numbered.js +2 -1
  108. package/src/plugins/dropdown/align.js +8 -2
  109. package/src/plugins/dropdown/backgroundColor.js +19 -11
  110. package/src/plugins/dropdown/font.js +15 -9
  111. package/src/plugins/dropdown/fontColor.js +19 -11
  112. package/src/plugins/dropdown/formatBlock.js +7 -2
  113. package/src/plugins/dropdown/hr.js +7 -3
  114. package/src/plugins/dropdown/layout.js +6 -2
  115. package/src/plugins/dropdown/lineHeight.js +8 -3
  116. package/src/plugins/dropdown/list.js +2 -1
  117. package/src/plugins/dropdown/paragraphStyle.js +15 -11
  118. package/src/plugins/dropdown/{table.js → table/index.js} +514 -362
  119. package/src/plugins/dropdown/template.js +6 -2
  120. package/src/plugins/dropdown/textStyle.js +7 -3
  121. package/src/plugins/field/mention.js +33 -27
  122. package/src/plugins/input/fontSize.js +44 -37
  123. package/src/plugins/input/pageNavigator.js +3 -2
  124. package/src/plugins/modal/audio.js +90 -85
  125. package/src/plugins/modal/drawing.js +58 -66
  126. package/src/plugins/modal/embed.js +193 -180
  127. package/src/plugins/modal/image.js +441 -439
  128. package/src/plugins/modal/link.js +31 -8
  129. package/src/plugins/modal/math.js +23 -22
  130. package/src/plugins/modal/video.js +233 -230
  131. package/src/plugins/popup/anchor.js +24 -18
  132. package/src/suneditor.js +69 -24
  133. package/src/typedef.js +42 -19
  134. package/types/assets/icons/defaultIcons.d.ts +8 -0
  135. package/types/core/class/char.d.ts +1 -1
  136. package/types/core/class/component.d.ts +29 -7
  137. package/types/core/class/format.d.ts +4 -354
  138. package/types/core/class/html.d.ts +13 -4
  139. package/types/core/class/inline.d.ts +263 -0
  140. package/types/core/class/listFormat.d.ts +135 -0
  141. package/types/core/class/menu.d.ts +10 -2
  142. package/types/core/class/offset.d.ts +24 -26
  143. package/types/core/class/selection.d.ts +2 -0
  144. package/types/core/class/toolbar.d.ts +24 -11
  145. package/types/core/class/ui.d.ts +1 -1
  146. package/types/core/class/viewer.d.ts +1 -1
  147. package/types/core/config/context.d.ts +157 -0
  148. package/types/core/config/frameContext.d.ts +367 -0
  149. package/types/core/config/options.d.ts +1119 -0
  150. package/types/core/editor.d.ts +101 -66
  151. package/types/core/event/actions/index.d.ts +47 -0
  152. package/types/core/event/effects/common.registry.d.ts +50 -0
  153. package/types/core/event/effects/keydown.registry.d.ts +73 -0
  154. package/types/core/event/effects/ruleHelpers.d.ts +31 -0
  155. package/types/core/{base → event}/eventManager.d.ts +15 -46
  156. package/types/core/event/executor.d.ts +6 -0
  157. package/types/core/event/handlers/handler_ww_input.d.ts +41 -0
  158. package/types/core/{base/eventHandlers/handler_ww_key_input.d.ts → event/handlers/handler_ww_key.d.ts} +4 -6
  159. package/types/core/event/ports.d.ts +255 -0
  160. package/types/core/event/reducers/keydown.reducer.d.ts +75 -0
  161. package/types/core/event/rules/keydown.rule.arrow.d.ts +8 -0
  162. package/types/core/event/rules/keydown.rule.backspace.d.ts +9 -0
  163. package/types/core/event/rules/keydown.rule.delete.d.ts +9 -0
  164. package/types/core/event/rules/keydown.rule.enter.d.ts +9 -0
  165. package/types/core/event/rules/keydown.rule.tab.d.ts +9 -0
  166. package/types/core/section/constructor.d.ts +101 -631
  167. package/types/core/section/documentType.d.ts +14 -4
  168. package/types/core/util/instanceCheck.d.ts +50 -0
  169. package/types/editorInjector/_classes.d.ts +4 -0
  170. package/types/editorInjector/_core.d.ts +17 -7
  171. package/types/editorInjector/index.d.ts +10 -2
  172. package/types/events.d.ts +1 -0
  173. package/types/helper/clipboard.d.ts +2 -2
  174. package/types/helper/converter.d.ts +6 -9
  175. package/types/helper/dom/domCheck.d.ts +7 -0
  176. package/types/helper/dom/domQuery.d.ts +19 -8
  177. package/types/helper/dom/domUtils.d.ts +24 -2
  178. package/types/helper/dom/index.d.ts +86 -1
  179. package/types/helper/env.d.ts +6 -1
  180. package/types/helper/index.d.ts +7 -1
  181. package/types/helper/keyCodeMap.d.ts +3 -3
  182. package/types/index.d.ts +23 -117
  183. package/types/langs/index.d.ts +2 -2
  184. package/types/modules/ApiManager.d.ts +1 -8
  185. package/types/modules/Browser.d.ts +4 -62
  186. package/types/modules/ColorPicker.d.ts +4 -21
  187. package/types/modules/Controller.d.ts +8 -64
  188. package/types/modules/Figure.d.ts +54 -50
  189. package/types/modules/FileManager.d.ts +1 -13
  190. package/types/modules/HueSlider.d.ts +13 -3
  191. package/types/modules/Modal.d.ts +0 -43
  192. package/types/modules/ModalAnchorEditor.d.ts +0 -73
  193. package/types/modules/SelectMenu.d.ts +0 -85
  194. package/types/modules/index.d.ts +3 -3
  195. package/types/plugins/browser/audioGallery.d.ts +29 -18
  196. package/types/plugins/browser/fileBrowser.d.ts +38 -27
  197. package/types/plugins/browser/fileGallery.d.ts +29 -18
  198. package/types/plugins/browser/imageGallery.d.ts +24 -16
  199. package/types/plugins/browser/videoGallery.d.ts +29 -18
  200. package/types/plugins/command/blockquote.d.ts +1 -0
  201. package/types/plugins/command/exportPDF.d.ts +18 -18
  202. package/types/plugins/command/fileUpload.d.ts +65 -45
  203. package/types/plugins/command/list_bulleted.d.ts +1 -0
  204. package/types/plugins/command/list_numbered.d.ts +1 -0
  205. package/types/plugins/dropdown/align.d.ts +13 -8
  206. package/types/plugins/dropdown/backgroundColor.d.ts +30 -19
  207. package/types/plugins/dropdown/font.d.ts +13 -12
  208. package/types/plugins/dropdown/fontColor.d.ts +30 -19
  209. package/types/plugins/dropdown/formatBlock.d.ts +13 -8
  210. package/types/plugins/dropdown/hr.d.ts +15 -11
  211. package/types/plugins/dropdown/layout.d.ts +15 -11
  212. package/types/plugins/dropdown/lineHeight.d.ts +16 -11
  213. package/types/plugins/dropdown/list.d.ts +1 -0
  214. package/types/plugins/dropdown/paragraphStyle.d.ts +31 -27
  215. package/types/plugins/dropdown/table/index.d.ts +582 -0
  216. package/types/plugins/dropdown/table.d.ts +41 -86
  217. package/types/plugins/dropdown/template.d.ts +15 -11
  218. package/types/plugins/dropdown/textStyle.d.ts +19 -11
  219. package/types/plugins/field/mention.d.ts +58 -56
  220. package/types/plugins/index.d.ts +38 -38
  221. package/types/plugins/input/fontSize.d.ts +46 -50
  222. package/types/plugins/modal/audio.d.ts +26 -56
  223. package/types/plugins/modal/drawing.d.ts +0 -85
  224. package/types/plugins/modal/embed.d.ts +15 -79
  225. package/types/plugins/modal/image.d.ts +24 -136
  226. package/types/plugins/modal/link.d.ts +34 -15
  227. package/types/plugins/modal/math.d.ts +0 -16
  228. package/types/plugins/modal/video.d.ts +17 -86
  229. package/types/plugins/popup/anchor.d.ts +1 -8
  230. package/types/suneditor.d.ts +70 -19
  231. package/types/typedef.d.ts +60 -46
  232. package/src/core/base/eventHandlers/handler_ww_key_input.js +0 -1200
  233. package/src/core/section/context.js +0 -102
  234. package/types/core/section/context.d.ts +0 -45
  235. package/types/langs/_Lang.d.ts +0 -194
  236. /package/src/core/{base/eventHandlers → event/handlers}/handler_ww_clipboard.js +0 -0
  237. /package/types/core/{section → base}/actives.d.ts +0 -0
  238. /package/types/core/{base/eventHandlers → event/handlers}/handler_toolbar.d.ts +0 -0
  239. /package/types/core/{base/eventHandlers → event/handlers}/handler_ww_clipboard.d.ts +0 -0
  240. /package/types/core/{base/eventHandlers → event/handlers}/handler_ww_dragDrop.d.ts +0 -0
  241. /package/types/core/{base/eventHandlers → event/handlers}/handler_ww_mouse.d.ts +0 -0
@@ -31,6 +31,13 @@ const { NO_EVENT } = env;
31
31
  * @property {boolean} [keepFormatType=false] - Whether to retain the chosen format type after image insertion.
32
32
  * @property {boolean} [linkEnableFileUpload] - Whether to enable file uploads for linked images.
33
33
  * @property {FigureControls_image} [controls] - Figure controls.
34
+ * @property {__se__ComponentInsertBehaviorType} [insertBehavior] - Component insertion behavior for selection and cursor placement. [default: options.get('componentInsertBehavior')]
35
+ * - For inline components: places the cursor near the inserted component or selects it if no nearby range is available.
36
+ * - For block components: executes behavior based on `selectMode`:
37
+ * - `auto`: Move cursor to the next line if possible, otherwise select the component.
38
+ * - `select`: Always select the inserted component.
39
+ * - `line`: Move cursor to the next line if possible, or create a new line and move there.
40
+ * - `none`: Do nothing.
34
41
  */
35
42
 
36
43
  /**
@@ -48,10 +55,26 @@ class Image_ extends EditorInjector {
48
55
  * @returns {Element|null} Returns a node if the node is a valid component.
49
56
  */
50
57
  static component(node) {
51
- const compNode = dom.check.isFigure(node) || (/^span$/i.test(node.nodeName) && dom.utils.hasClass(node, 'se-component')) ? node.firstElementChild : node;
58
+ const compNode = dom.check.isFigure(node) || (/^span$/i.test(node.nodeName) && dom.check.isComponentContainer(node)) ? node.firstElementChild : node;
52
59
  return /^IMG$/i.test(compNode?.nodeName) ? compNode : dom.check.isAnchor(compNode) && /^IMG$/i.test(compNode?.firstElementChild?.nodeName) ? compNode?.firstElementChild : null;
53
60
  }
54
61
 
62
+ #produceIndex;
63
+ #linkElement;
64
+ #linkValue;
65
+ #align;
66
+ #svgDefaultSize;
67
+ #element;
68
+ #cover;
69
+ #container;
70
+ #caption;
71
+ #ratio;
72
+ #origin_w;
73
+ #origin_h;
74
+ #resizing;
75
+ #onlyPercentage;
76
+ #nonResizing;
77
+
55
78
  /**
56
79
  * @constructor
57
80
  * @param {__se__EditorCore} editor - The root editor instance
@@ -79,7 +102,8 @@ class Image_ extends EditorInjector {
79
102
  acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'image/*' : pluginOptions.acceptedFormats.trim() || 'image/*',
80
103
  useFormatType: pluginOptions.useFormatType ?? true,
81
104
  defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : 'block',
82
- keepFormatType: pluginOptions.keepFormatType ?? false
105
+ keepFormatType: pluginOptions.keepFormatType ?? false,
106
+ insertBehavior: pluginOptions.insertBehavior
83
107
  };
84
108
 
85
109
  // create HTML
@@ -87,12 +111,13 @@ class Image_ extends EditorInjector {
87
111
  const modalEl = CreateHTML_modal(editor, this.pluginOptions);
88
112
  const ctrlAs = this.pluginOptions.useFormatType ? 'as' : '';
89
113
  const figureControls =
90
- pluginOptions.controls || !this.pluginOptions.canResize
114
+ pluginOptions.controls ||
115
+ (!this.pluginOptions.canResize
91
116
  ? [[ctrlAs, 'mirror_h', 'mirror_v', 'align', 'caption', 'edit', 'revert', 'copy', 'remove']]
92
117
  : [
93
118
  [ctrlAs, 'resize_auto,100,75,50', 'rotate_l', 'rotate_r', 'mirror_h', 'mirror_v'],
94
- ['align', 'caption', 'edit', 'revert', 'copy', 'remove']
95
- ];
119
+ ['edit', 'align', 'caption', 'revert', 'copy', 'remove']
120
+ ]);
96
121
 
97
122
  // show align
98
123
  this.alignForm = modalEl.alignForm;
@@ -101,13 +126,9 @@ class Image_ extends EditorInjector {
101
126
  // modules
102
127
  const Link = this.plugins.link ? this.plugins.link.pluginOptions : {};
103
128
  this.anchor = new ModalAnchorEditor(this, modalEl.html, {
129
+ ...Link,
104
130
  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
131
+ title: true
111
132
  });
112
133
  this.modal = new Modal(this, modalEl.html);
113
134
  this.figure = new Figure(this, figureControls, {
@@ -133,24 +154,23 @@ class Image_ extends EditorInjector {
133
154
  this.proportion = null;
134
155
  this.inputX = null;
135
156
  this.inputY = null;
136
- this._linkElement = null;
137
- this._linkValue = '';
138
- this._align = 'none';
139
- this._svgDefaultSize = '30%';
140
157
  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;
158
+
159
+ this.#produceIndex = 0;
160
+ this.#linkElement = null;
161
+ this.#linkValue = '';
162
+ this.#align = 'none';
163
+ this.#svgDefaultSize = '30%';
164
+ this.#element = null;
165
+ this.#cover = null;
166
+ this.#container = null;
167
+ this.#caption = null;
168
+ this.#ratio = { w: 0, h: 0 };
169
+ this.#origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
170
+ this.#origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
171
+ this.#resizing = this.pluginOptions.canResize;
172
+ this.#onlyPercentage = this.pluginOptions.percentageOnlySize;
173
+ this.#nonResizing = !this.#resizing || !this.pluginOptions.showHeightInput || this.#onlyPercentage;
154
174
 
155
175
  // init
156
176
  this.eventManager.addEvent(modalEl.tabs, 'click', this.#OpenTab.bind(this));
@@ -161,7 +181,7 @@ class Image_ extends EditorInjector {
161
181
  const galleryButton = modalEl.galleryButton;
162
182
  if (galleryButton) this.eventManager.addEvent(galleryButton, 'click', this.#OpenGallery.bind(this));
163
183
 
164
- if (this._resizing) {
184
+ if (this.#resizing) {
165
185
  this.proportion = modalEl.proportion;
166
186
  this.inputX = modalEl.inputX;
167
187
  this.inputY = modalEl.inputY;
@@ -190,6 +210,7 @@ class Image_ extends EditorInjector {
190
210
  * @description Executes the method that is called when a "Modal" module's is opened.
191
211
  */
192
212
  open() {
213
+ this.#produceIndex = 0;
193
214
  this.modal.open();
194
215
  }
195
216
 
@@ -208,9 +229,9 @@ class Image_ extends EditorInjector {
208
229
  */
209
230
  on(isUpdate) {
210
231
  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;
232
+ if (this.#resizing) {
233
+ this.inputX.value = this.#origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
234
+ this.inputY.value = this.#origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
214
235
  }
215
236
  if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.setAttribute('multiple', 'multiple');
216
237
  } else {
@@ -244,17 +265,17 @@ class Image_ extends EditorInjector {
244
265
  * @returns {Promise<boolean>} Success or failure
245
266
  */
246
267
  async modalAction() {
247
- this._align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"]:checked')).value;
268
+ this.#align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"]:checked')).value;
248
269
 
249
270
  if (this.modal.isUpdate) {
250
- this._update(this.inputX?.value, this.inputY?.value);
271
+ this.#fixTagStructure(this.inputX?.value, this.inputY?.value);
251
272
  this.history.push(false);
252
273
  }
253
274
 
254
275
  if (this.imgInputFile && this.imgInputFile.files.length > 0) {
255
276
  return await this.submitFile(this.imgInputFile.files);
256
- } else if (this.imgUrlFile && this._linkValue.length > 0) {
257
- return await this.submitURL(this._linkValue);
277
+ } else if (this.imgUrlFile && this.#linkValue.length > 0) {
278
+ return await this.submitURL(this.#linkValue);
258
279
  }
259
280
 
260
281
  return false;
@@ -276,10 +297,10 @@ class Image_ extends EditorInjector {
276
297
  query: 'img',
277
298
  method: (element) => {
278
299
  const figureInfo = Figure.GetContainer(element);
279
- if (figureInfo && figureInfo.container && figureInfo.cover) return;
300
+ if (figureInfo && figureInfo.container && (figureInfo.cover || figureInfo.inlineCover)) return;
280
301
 
281
- this._ready(element);
282
- this._fileCheck(this._origin_w, this._origin_h);
302
+ const { w, h } = this.#ready(element, true);
303
+ this.#fileCheck(w, h);
283
304
  }
284
305
  };
285
306
  }
@@ -291,7 +312,7 @@ class Image_ extends EditorInjector {
291
312
  init() {
292
313
  Modal.OnChangeFile(this.fileModalWrapper, []);
293
314
  if (this.imgInputFile) this.imgInputFile.value = '';
294
- if (this.imgUrlFile) this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = '';
315
+ if (this.imgUrlFile) this.#linkValue = this.previewSrc.textContent = this.imgUrlFile.value = '';
295
316
  if (this.imgInputFile && this.imgUrlFile) {
296
317
  this.imgUrlFile.disabled = false;
297
318
  this.previewSrc.style.textDecoration = '';
@@ -300,21 +321,21 @@ class Image_ extends EditorInjector {
300
321
  this.altText.value = '';
301
322
  /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]')).checked = true;
302
323
  this.captionCheckEl.checked = false;
303
- this._element = null;
304
- this._ratio = {
305
- w: 1,
306
- h: 1
324
+ this.#element = null;
325
+ this.#ratio = {
326
+ w: 0,
327
+ h: 0
307
328
  };
308
329
  this.#OpenTab('init');
309
330
 
310
- if (this._resizing) {
331
+ if (this.#resizing) {
311
332
  this.inputX.value = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
312
333
  this.inputY.value = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
313
334
  this.proportion.checked = true;
314
335
  }
315
336
 
316
337
  if (this.pluginOptions.useFormatType) {
317
- this._activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
338
+ this.#activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
318
339
  }
319
340
 
320
341
  this.anchor.init();
@@ -326,70 +347,7 @@ class Image_ extends EditorInjector {
326
347
  * @param {HTMLElement} target Target component element
327
348
  */
328
349
  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
- }
350
+ this.#ready(target);
393
351
  }
394
352
 
395
353
  /**
@@ -399,18 +357,18 @@ class Image_ extends EditorInjector {
399
357
  * @returns {Promise<void>}
400
358
  */
401
359
  async destroy(target) {
402
- const targetEl = target || this._element;
360
+ const targetEl = target || this.#element;
403
361
  const container = dom.query.getParentElement(targetEl, Figure.is) || targetEl;
404
362
  const focusEl = container.previousElementSibling || container.nextElementSibling;
405
363
  const emptyDiv = container.parentNode;
406
364
 
407
- const message = await this.triggerEvent('onImageDeleteBefore', { element: targetEl, container, align: this._align, alt: this.altText.value, url: this._linkValue });
365
+ const message = await this.triggerEvent('onImageDeleteBefore', { element: targetEl, container, align: this.#align, alt: this.altText.value, url: this.#linkValue });
408
366
  if (message === false) return;
409
367
 
410
368
  dom.utils.removeItem(container);
411
369
  this.init();
412
370
 
413
- if (emptyDiv !== this.editor.frameContext.get('wysiwyg')) {
371
+ if (emptyDiv !== this.frameContext.get('wysiwyg')) {
414
372
  this.nodeTransform.removeAllParents(
415
373
  emptyDiv,
416
374
  function (current) {
@@ -425,48 +383,6 @@ class Image_ extends EditorInjector {
425
383
  this.history.push(false);
426
384
  }
427
385
 
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
386
  /**
471
387
  * @description Create an "image" component using the provided files.
472
388
  * @param {FileList|File[]} fileList File object list
@@ -483,7 +399,7 @@ class Image_ extends EditorInjector {
483
399
  if (!/image/i.test(f.type)) continue;
484
400
 
485
401
  s = f.size;
486
- if (slngleSizeLimit && slngleSizeLimit > s) {
402
+ if (slngleSizeLimit > 0 && s > slngleSizeLimit) {
487
403
  const err = '[SUNEDITOR.imageUpload.fail] Size of uploadable single file: ' + slngleSizeLimit / 1000 + 'KB';
488
404
  const message = await this.triggerEvent('onImageUploadError', {
489
405
  error: err,
@@ -517,13 +433,11 @@ class Image_ extends EditorInjector {
517
433
  return false;
518
434
  }
519
435
 
520
- const imgInfo = { files, ...this._getInfo() };
521
- const handler = function (infos, newInfos) {
436
+ const imgInfo = { files, ...this.#getInfo() };
437
+ const handler = function (uploadCallback, infos, newInfos) {
522
438
  infos = newInfos || infos;
523
- this._serverUpload(infos, infos.files);
524
- }.bind(this, imgInfo);
525
- // se-ts-ignore
526
- this._serverUpload;
439
+ uploadCallback(infos, infos.files);
440
+ }.bind(this, this.#serverUpload.bind(this), imgInfo);
527
441
 
528
442
  const result = await this.triggerEvent('onImageUploadBefore', {
529
443
  info: imgInfo,
@@ -543,22 +457,19 @@ class Image_ extends EditorInjector {
543
457
  * @returns {Promise<boolean>} If return false, the file upload will be canceled
544
458
  */
545
459
  async submitURL(url) {
546
- if (!url) url = this._linkValue;
547
- if (!url) return false;
460
+ if (!(url ||= this.#linkValue)) return false;
548
461
 
549
462
  const file = { name: url.split('/').pop(), size: 0 };
550
463
  const imgInfo = {
551
464
  url,
552
465
  files: file,
553
- ...this._getInfo()
466
+ ...this.#getInfo()
554
467
  };
555
468
 
556
- const handler = function (infos, newInfos) {
469
+ const handler = function (uploadCallback, infos, newInfos) {
557
470
  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);
471
+ uploadCallback(infos);
472
+ }.bind(this, this.#urlUpload.bind(this), imgInfo);
562
473
 
563
474
  const result = await this.triggerEvent('onImageUploadBefore', {
564
475
  info: imgInfo,
@@ -575,18 +486,204 @@ class Image_ extends EditorInjector {
575
486
  }
576
487
 
577
488
  /**
578
- * @private
489
+ * @description Creates a new image component, wraps it in a figure container with an optional anchor,
490
+ * - applies size and alignment settings, and inserts it into the editor.
491
+ * @param {string} src - The URL of the image to be inserted.
492
+ * @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
493
+ * @param {string} width - The width value to be applied to the image.
494
+ * @param {string} height - The height value to be applied to the image.
495
+ * @param {string} align - The alignment setting for the image (e.g., 'left', 'center', 'right').
496
+ * @param {{name: string, size: number}} file - File metadata associated with the image
497
+ * @param {string} alt - The alternative text for the image.
498
+ * @param {boolean} isLast - Indicates whether this is the last file in the batch (used for scroll and insert actions).
499
+ */
500
+ create(src, anchor, width, height, align, file, alt, isLast) {
501
+ /** @type {HTMLImageElement} */
502
+ const oImg = dom.utils.createElement('IMG');
503
+ oImg.src = src;
504
+ oImg.alt = alt;
505
+ anchor = this.#setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
506
+
507
+ const figureInfo = Figure.CreateContainer(anchor, 'se-image-container');
508
+ const cover = figureInfo.cover;
509
+ const container = figureInfo.container;
510
+
511
+ // caption
512
+ if (this.captionCheckEl.checked) {
513
+ this.#caption = Figure.CreateCaption(cover, this.lang.caption);
514
+ }
515
+
516
+ this.#element = oImg;
517
+ this.#cover = cover;
518
+ this.#container = container;
519
+ this.figure.open(oImg, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
520
+
521
+ // set size
522
+ this.#applySize(width, height);
523
+
524
+ // align
525
+ this.figure.setAlign(oImg, align);
526
+
527
+ this.fileManager.setFileData(oImg, file);
528
+
529
+ this.#produceIndex++;
530
+ oImg.onload = this.#OnloadImg.bind(this, oImg, this.#svgDefaultSize, container);
531
+ this.component.insert(container, { scrollTo: isLast ? true : false, insertBehavior: isLast ? null : 'line' });
532
+ }
533
+
534
+ /**
535
+ * @description Creates a new inline image component, wraps it in an inline figure container with an optional anchor,
536
+ * - applies size settings, and inserts it into the editor.
537
+ * @param {string} src - The URL of the image to be inserted.
538
+ * @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
539
+ * @param {string} width - The width value to be applied to the image.
540
+ * @param {string} height - The height value to be applied to the image.
541
+ * @param {{name: string, size: number}} file - File metadata associated with the image
542
+ * @param {string} alt - The alternative text for the image.
543
+ * @param {boolean} isLast - Indicates whether this is the last file in the batch (used for scroll and insert actions).
544
+ */
545
+ createInline(src, anchor, width, height, file, alt, isLast) {
546
+ /** @type {HTMLImageElement} */
547
+ const oImg = dom.utils.createElement('IMG');
548
+ oImg.src = src;
549
+ oImg.alt = alt;
550
+ anchor = this.#setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
551
+
552
+ const figureInfo = Figure.CreateInlineContainer(anchor, 'se-image-container');
553
+ const container = figureInfo.container;
554
+
555
+ this.#element = oImg;
556
+ this.#container = container;
557
+ this.figure.open(oImg, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
558
+
559
+ // set size
560
+ this.#applySize(width, height);
561
+
562
+ this.fileManager.setFileData(oImg, file);
563
+
564
+ this.#produceIndex++;
565
+ oImg.onload = this.#OnloadImg.bind(this, oImg, this.#svgDefaultSize, container);
566
+ this.component.insert(container, { scrollTo: isLast ? true : false, insertBehavior: isLast ? null : 'line' });
567
+ }
568
+
569
+ /**
570
+ * @description Prepares the component for selection.
571
+ * - Ensures that the controller is properly positioned and initialized.
572
+ * - Prevents duplicate event handling if the component is already selected.
573
+ * @param {HTMLElement} target - The selected element.
574
+ * @param {boolean} [infoOnly=false] - If true, only retrieves information without opening the controller.
575
+ * @returns {{w: string, h: string}} - The width and height of the component.
576
+ */
577
+ #ready(target, infoOnly = false) {
578
+ if (!target) return;
579
+ const figureInfo = this.figure.open(target, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly });
580
+ this.anchor.set(dom.check.isAnchor(target.parentNode) ? target.parentNode : null);
581
+
582
+ this.#linkElement = this.anchor.currentTarget;
583
+ this.#element = target;
584
+ this.#cover = figureInfo.cover;
585
+ this.#container = figureInfo.container;
586
+ this.#caption = figureInfo.caption;
587
+ this.#align = figureInfo.align;
588
+ target.style.float = '';
589
+
590
+ this.#origin_w = String(figureInfo.originWidth || figureInfo.w || '');
591
+ this.#origin_h = String(figureInfo.originHeight || figureInfo.h || '');
592
+ this.altText.value = this.#element.alt;
593
+
594
+ if (this.imgUrlFile) this.#linkValue = this.previewSrc.textContent = this.imgUrlFile.value = this.#element.src;
595
+
596
+ /** @type {HTMLInputElement} */
597
+ 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"]');
598
+ activeAlign.checked = true;
599
+ this.captionCheckEl.checked = !!this.#caption;
600
+
601
+ const { dw, dh } = this.figure.getSize(target);
602
+
603
+ if (!this.#resizing) return { w: dw, h: dh };
604
+
605
+ this.inputX.value = dw === 'auto' ? '' : dw;
606
+ this.inputY.value = dh === 'auto' ? '' : dh;
607
+
608
+ const percentageRotation = this.#onlyPercentage && this.figure.isVertical;
609
+ this.proportion.checked = true;
610
+ this.inputX.disabled = percentageRotation ? true : false;
611
+ this.inputY.disabled = percentageRotation ? true : false;
612
+ this.proportion.disabled = percentageRotation ? true : false;
613
+
614
+ this.#ratio = this.proportion.checked
615
+ ? figureInfo.ratio
616
+ : {
617
+ w: 0,
618
+ h: 0
619
+ };
620
+
621
+ if (this.pluginOptions.useFormatType) {
622
+ this.#activeAsInline(this.component.isInline(figureInfo.container));
623
+ }
624
+
625
+ return { w: dw, h: dh };
626
+ }
627
+
628
+ /**
629
+ * @description Retrieves the current image information.
630
+ * @returns {*} - The image data.
631
+ */
632
+ #getInfo() {
633
+ return {
634
+ element: this.#element,
635
+ anchor: this.anchor.create(true),
636
+ inputWidth: this.inputX?.value || '',
637
+ inputHeight: this.inputY?.value || '',
638
+ align: this.#align,
639
+ isUpdate: this.modal.isUpdate,
640
+ alt: this.altText.value
641
+ };
642
+ }
643
+
644
+ /**
645
+ * @description Toggles between block and inline image format.
646
+ * @param {boolean} isInline - Whether the image should be inline.
647
+ */
648
+ #activeAsInline(isInline) {
649
+ if (isInline) {
650
+ dom.utils.addClass(this.asInline, 'on');
651
+ dom.utils.removeClass(this.asBlock, 'on');
652
+ this.as = 'inline';
653
+ // buttns
654
+ if (this.alignForm) this.alignForm.style.display = 'none';
655
+ // caption
656
+ if (this.captionEl) this.captionEl.style.display = 'none';
657
+ } else {
658
+ dom.utils.addClass(this.asBlock, 'on');
659
+ dom.utils.removeClass(this.asInline, 'on');
660
+ this.as = 'block';
661
+ // buttns
662
+ if (this.alignForm) this.alignForm.style.display = '';
663
+ // caption
664
+ if (this.captionEl) this.captionEl.style.display = '';
665
+ }
666
+ }
667
+
668
+ /**
579
669
  * @description Updates the selected image size, alt text, and caption.
580
670
  * @param {string} width - New image width.
581
671
  * @param {string} height - New image height.
582
672
  */
583
- _update(width, height) {
584
- if (!width) width = this.inputX?.value || 'auto';
585
- if (!height) height = this.inputY?.value || 'auto';
673
+ #fixTagStructure(width, height) {
674
+ width ||= this.inputX?.value || 'auto';
675
+ height ||= this.inputY?.value || 'auto';
676
+
677
+ let imageEl = this.#element;
678
+
679
+ // as (block | inline)
680
+ if ((this.as === 'block' && !this.#cover) || (this.as === 'inline' && this.#cover)) {
681
+ imageEl = this.figure.convertAsFormat(imageEl, this.as);
682
+ }
586
683
 
587
- let imageEl = this._element;
588
- const cover = this._cover;
589
- const container = this._container === this._cover ? null : this._container;
684
+ // --- update image ---
685
+ const cover = this.#cover;
686
+ const container = this.#container === this.#cover ? null : this.#container;
590
687
 
591
688
  // check size
592
689
  let changeSize;
@@ -604,14 +701,14 @@ class Image_ extends EditorInjector {
604
701
  // caption
605
702
  let modifiedCaption = false;
606
703
  if (this.captionCheckEl.checked) {
607
- if (!this._caption) {
608
- this._caption = Figure.CreateCaption(cover, this.lang.caption);
704
+ if (!this.#caption) {
705
+ this.#caption = Figure.CreateCaption(cover, this.lang.caption);
609
706
  modifiedCaption = true;
610
707
  }
611
708
  } else {
612
- if (this._caption) {
613
- dom.utils.removeItem(this._caption);
614
- this._caption = null;
709
+ if (this.#caption) {
710
+ dom.utils.removeItem(this.#caption);
711
+ this.#caption = null;
615
712
  modifiedCaption = true;
616
713
  }
617
714
  }
@@ -620,42 +717,42 @@ class Image_ extends EditorInjector {
620
717
  let isNewAnchor = false;
621
718
  const anchor = this.anchor.create(true);
622
719
  if (anchor) {
623
- if (this._linkElement !== anchor || !container.contains(anchor)) {
624
- this._linkElement = anchor.cloneNode(false);
625
- cover.insertBefore(this._setAnchor(imageEl, this._linkElement), this._caption);
720
+ if (this.#linkElement !== anchor || !container.contains(anchor)) {
721
+ this.#linkElement = anchor.cloneNode(false);
722
+ cover.insertBefore(this.#setAnchor(imageEl, this.#linkElement), this.#caption);
626
723
  isNewAnchor = true;
627
724
  }
628
- } else if (this._linkElement !== null) {
629
- if (cover.contains(this._linkElement)) {
725
+ } else if (this.#linkElement !== null) {
726
+ if (cover.contains(this.#linkElement)) {
630
727
  const newEl = imageEl.cloneNode(true);
631
- cover.removeChild(this._linkElement);
632
- cover.insertBefore(newEl, this._caption);
728
+ cover.removeChild(this.#linkElement);
729
+ cover.insertBefore(newEl, this.#caption);
633
730
  imageEl = newEl;
634
731
  }
635
732
  }
636
733
 
637
- // size
638
- if (this._resizing && changeSize) {
639
- this._applySize(width, height);
640
- }
641
-
642
734
  if (isNewAnchor) {
643
735
  dom.utils.removeItem(anchor);
644
736
  }
645
737
 
738
+ // size
739
+ if (this.#resizing && changeSize) {
740
+ this.#applySize(width, height);
741
+ }
742
+
646
743
  // transform
647
- if (modifiedCaption || (!this._onlyPercentage && changeSize)) {
744
+ if (modifiedCaption || (!this.#onlyPercentage && changeSize)) {
648
745
  if (/\d+/.test(imageEl.style.height) || (this.figure.isVertical && this.captionCheckEl.checked)) {
649
746
  if (/auto|%$/.test(width) || /auto|%$/.test(height)) {
650
747
  this.figure.deleteTransform(imageEl);
651
- } else {
748
+ } else if (!this.#resizing || !changeSize || !this.figure.isVertical) {
652
749
  this.figure.setTransform(imageEl, width, height, 0);
653
750
  }
654
751
  }
655
752
  }
656
753
 
657
754
  // align
658
- this.figure.setAlign(imageEl, this._align);
755
+ this.figure.setAlign(imageEl, this.#align);
659
756
 
660
757
  // select
661
758
  imageEl.onload = () => {
@@ -664,42 +761,31 @@ class Image_ extends EditorInjector {
664
761
  }
665
762
 
666
763
  /**
667
- * @private
668
764
  * @description Validates the image size and applies necessary transformations.
669
765
  * @param {string} width - The width of the image.
670
766
  * @param {string} height - The height of the image.
671
767
  */
672
- _fileCheck(width, height) {
673
- if (!width) width = this.inputX?.value || 'auto';
674
- if (!height) height = this.inputY?.value || 'auto';
768
+ #fileCheck(width, height) {
769
+ width ||= this.inputX?.value || 'auto';
770
+ height ||= this.inputY?.value || 'auto';
675
771
 
676
- let imageEl = this._element;
677
- let cover = this._cover;
772
+ let imageEl = this.#element;
773
+ let cover = this.#cover;
678
774
  let inlineCover = null;
679
- let container = this._container === this._cover ? null : this._container;
775
+ let container = this.#container === this.#cover ? null : this.#container;
680
776
  let isNewContainer = false;
681
777
 
682
778
  if (!cover || !container) {
683
779
  isNewContainer = true;
684
- imageEl = this._element.cloneNode(true);
780
+ imageEl = this.#element.cloneNode(true);
685
781
  const figureInfo =
686
- this.pluginOptions.useFormatType && width !== 'auto' && (/^span$/i.test(this._element.parentElement?.nodeName) || this.format.isLine(this._element.parentElement))
782
+ this.pluginOptions.useFormatType && width !== 'auto' && (/^span$/i.test(this.#element.parentElement?.nodeName) || this.format.isLine(this.#element.parentElement))
687
783
  ? Figure.CreateInlineContainer(imageEl, 'se-image-container')
688
784
  : Figure.CreateContainer(imageEl, 'se-image-container');
689
785
  cover = figureInfo.cover;
690
786
  container = figureInfo.container;
691
787
  inlineCover = figureInfo.inlineCover;
692
- this.figure.open(imageEl, { nonResizing: true, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
693
- }
694
-
695
- // check size
696
- let changeSize;
697
- const x = numbers.is(width) ? width + this.sizeUnit : width;
698
- const y = numbers.is(height) ? height + this.sizeUnit : height;
699
- if (/%$/.test(imageEl.style.width)) {
700
- changeSize = x !== container.style.width || y !== container.style.height;
701
- } else {
702
- changeSize = x !== imageEl.style.width || y !== imageEl.style.height;
788
+ this.figure.open(imageEl, { nonResizing: true, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
703
789
  }
704
790
 
705
791
  // alt
@@ -709,14 +795,14 @@ class Image_ extends EditorInjector {
709
795
  let modifiedCaption = false;
710
796
  if (!inlineCover) {
711
797
  if (this.captionCheckEl.checked) {
712
- if (!this._caption || isNewContainer) {
713
- this._caption = Figure.CreateCaption(cover, this.lang.caption);
798
+ if (!this.#caption || isNewContainer) {
799
+ this.#caption = Figure.CreateCaption(cover, this.lang.caption);
714
800
  modifiedCaption = true;
715
801
  }
716
802
  } else {
717
- if (this._caption) {
718
- dom.utils.removeItem(this._caption);
719
- this._caption = null;
803
+ if (this.#caption) {
804
+ dom.utils.removeItem(this.#caption);
805
+ this.#caption = null;
720
806
  modifiedCaption = true;
721
807
  }
722
808
  }
@@ -726,46 +812,48 @@ class Image_ extends EditorInjector {
726
812
  let isNewAnchor = null;
727
813
  const anchor = this.anchor.create(true);
728
814
  if (anchor) {
729
- if (this._linkElement !== anchor || (isNewContainer && !container.contains(anchor))) {
730
- this._linkElement = anchor.cloneNode(false);
731
- cover.insertBefore(this._setAnchor(imageEl, this._linkElement), this._caption);
732
- isNewAnchor = this._element;
815
+ if (this.#linkElement !== anchor || (isNewContainer && !container.contains(anchor))) {
816
+ this.#linkElement = anchor.cloneNode(false);
817
+ cover.insertBefore(this.#setAnchor(imageEl, this.#linkElement), this.#caption);
818
+ isNewAnchor = this.#element;
733
819
  }
734
- } else if (this._linkElement !== null) {
735
- if (cover.contains(this._linkElement)) {
820
+ } else if (this.#linkElement !== null) {
821
+ if (cover.contains(this.#linkElement)) {
736
822
  const newEl = imageEl.cloneNode(true);
737
- cover.removeChild(this._linkElement);
738
- cover.insertBefore(newEl, this._caption);
823
+ cover.removeChild(this.#linkElement);
824
+ cover.insertBefore(newEl, this.#caption);
739
825
  imageEl = newEl;
740
826
  }
741
827
  }
742
828
 
743
829
  if (isNewContainer) {
744
- imageEl = this._element;
745
- this.figure.retainFigureFormat(container, this._element, isNewAnchor ? anchor : null, this.fileManager);
746
- this._element = imageEl = container.querySelector('img');
747
- this._cover = cover;
748
- this._container = container;
830
+ imageEl = this.#element;
831
+ this.figure.retainFigureFormat(container, this.#element, isNewAnchor ? anchor : null, this.fileManager);
832
+ this.#element = imageEl = container.querySelector('img');
833
+ this.#cover = cover;
834
+ this.#container = container;
749
835
  }
750
836
 
751
837
  // size
752
- if (this._resizing && changeSize) {
753
- this._applySize(width, height);
754
- }
838
+ imageEl.style.width = '';
839
+ imageEl.style.height = '';
840
+ imageEl.removeAttribute('width');
841
+ imageEl.removeAttribute('height');
842
+ this.#applySize(width, height);
755
843
 
756
844
  if (isNewAnchor) {
757
845
  if (!isNewContainer) {
758
846
  dom.utils.removeItem(anchor);
759
847
  } else {
760
848
  dom.utils.removeItem(isNewAnchor);
761
- if (dom.query.getListChildren(anchor, (current) => /IMG/i.test(current.tagName)).length === 0) {
849
+ if (dom.query.getListChildren(anchor, (current) => /IMG/i.test(current.tagName), null).length === 0) {
762
850
  dom.utils.removeItem(anchor);
763
851
  }
764
852
  }
765
853
  }
766
854
 
767
855
  // transform
768
- if (modifiedCaption || (!this._onlyPercentage && changeSize)) {
856
+ if (modifiedCaption || !this.#onlyPercentage) {
769
857
  if (/\d+/.test(imageEl.style.height) || (this.figure.isVertical && this.captionCheckEl.checked)) {
770
858
  if (/auto|%$/.test(width) || /auto|%$/.test(height)) {
771
859
  this.figure.deleteTransform(imageEl);
@@ -776,56 +864,10 @@ class Image_ extends EditorInjector {
776
864
  }
777
865
 
778
866
  // align
779
- this.figure.setAlign(imageEl, this._align);
867
+ this.figure.setAlign(imageEl, this.#align);
780
868
  }
781
869
 
782
870
  /**
783
- * @description Opens a specific tab inside the modal.
784
- * @param {MouseEvent|string} e - The event object or tab name.
785
- * @returns {boolean} - Whether the tab was successfully opened.
786
- */
787
- #OpenTab(e) {
788
- const modalForm = this.modal.form;
789
- const targetElement = typeof e === 'string' ? modalForm.querySelector('._se_tab_link') : dom.query.getEventTarget(e);
790
-
791
- if (!/^BUTTON$/i.test(targetElement.tagName)) {
792
- return false;
793
- }
794
-
795
- // Declare all variables
796
- const tabName = targetElement.getAttribute('data-tab-link');
797
- let i;
798
-
799
- // Get all elements with class="tabcontent" and hide them
800
- const tabContent = /** @type {HTMLCollectionOf<HTMLElement>}*/ (modalForm.getElementsByClassName('_se_tab_content'));
801
- for (i = 0; i < tabContent.length; i++) {
802
- tabContent[i].style.display = 'none';
803
- }
804
-
805
- // Get all elements with class="tablinks" and remove the class "active"
806
- const tabLinks = modalForm.getElementsByClassName('_se_tab_link');
807
- for (i = 0; i < tabLinks.length; i++) {
808
- dom.utils.removeClass(tabLinks[i], 'active');
809
- }
810
-
811
- // Show the current tab, and add an "active" class to the button that opened the tab
812
- /** @type {HTMLElement}*/ (modalForm.querySelector('._se_tab_content_' + tabName)).style.display = 'block';
813
- dom.utils.addClass(targetElement, 'active');
814
-
815
- // focus
816
- if (e !== 'init') {
817
- if (tabName === 'image') {
818
- this.focusElement.focus();
819
- } else if (tabName === 'url') {
820
- this.anchor.urlInput.focus();
821
- }
822
- }
823
-
824
- return false;
825
- }
826
-
827
- /**
828
- * @private
829
871
  * @description Creates a new image component based on provided parameters.
830
872
  * @param {string} src - The image source URL.
831
873
  * @param {?Node} anchor - Optional anchor wrapping the image.
@@ -834,25 +876,26 @@ class Image_ extends EditorInjector {
834
876
  * @param {string} align - Image alignment.
835
877
  * @param {{name: string, size: number}} file - File metadata.
836
878
  * @param {string} alt - Alternative text.
879
+ * @param {boolean} isLast - Indicates if this is the last image in a batch (for scroll and insert behavior).
837
880
  */
838
- _produce(src, anchor, width, height, align, file, alt) {
881
+ #produce(src, anchor, width, height, align, file, alt, isLast) {
839
882
  if (this.as !== 'inline') {
840
- this.create(src, anchor, width, height, align, file, alt);
883
+ this.create(src, anchor, width, height, align, file, alt, isLast);
841
884
  } else {
842
- this.createInline(src, anchor, width, height, file, alt);
885
+ this.createInline(src, anchor, width, height, file, alt, isLast);
843
886
  }
844
887
  }
845
888
 
846
889
  /**
847
- * @private
848
890
  * @description Applies the specified width and height to the image.
849
891
  * @param {string} w - Image width.
850
892
  * @param {string} h - Image height.
851
893
  */
852
- _applySize(w, h) {
853
- if (!w) w = this.inputX?.value || this.pluginOptions.defaultWidth;
854
- if (!h) h = this.inputY?.value || this.pluginOptions.defaultHeight;
855
- if (this._onlyPercentage) {
894
+ #applySize(w, h) {
895
+ w ||= this.inputX?.value || this.pluginOptions.defaultWidth;
896
+ h ||= this.inputY?.value || this.pluginOptions.defaultHeight;
897
+
898
+ if (this.#onlyPercentage) {
856
899
  if (!w) w = '100%';
857
900
  else if (/%$/.test(w)) w += '%';
858
901
  }
@@ -860,102 +903,24 @@ class Image_ extends EditorInjector {
860
903
  }
861
904
 
862
905
  /**
863
- * @description Creates a new image component, wraps it in a figure container with an optional anchor,
864
- * - applies size and alignment settings, and inserts it into the editor.
865
- * @param {string} src - The URL of the image to be inserted.
866
- * @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
867
- * @param {string} width - The width value to be applied to the image.
868
- * @param {string} height - The height value to be applied to the image.
869
- * @param {string} align - The alignment setting for the image (e.g., 'left', 'center', 'right').
870
- * @param {{name: string, size: number}} file - File metadata associated with the image
871
- * @param {string} alt - The alternative text for the image.
872
- */
873
- create(src, anchor, width, height, align, file, alt) {
874
- /** @type {HTMLImageElement} */
875
- const oImg = dom.utils.createElement('IMG');
876
- oImg.src = src;
877
- oImg.alt = alt;
878
- anchor = this._setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
879
-
880
- const figureInfo = Figure.CreateContainer(anchor, 'se-image-container');
881
- const cover = figureInfo.cover;
882
- const container = figureInfo.container;
883
-
884
- // caption
885
- if (this.captionCheckEl.checked) {
886
- this._caption = Figure.CreateCaption(cover, this.lang.caption);
887
- }
888
-
889
- this._element = oImg;
890
- this._cover = cover;
891
- this._container = container;
892
- this.figure.open(oImg, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
893
-
894
- // set size
895
- this._applySize(width, height);
896
-
897
- // align
898
- this.figure.setAlign(oImg, align);
899
-
900
- this.fileManager.setFileData(oImg, file);
901
-
902
- oImg.onload = this.#OnloadImg.bind(this, oImg, this._svgDefaultSize, container);
903
- this.component.insert(container, { skipCharCount: false, skipSelection: !this.options.get('componentAutoSelect'), skipHistory: false });
904
- }
905
-
906
- /**
907
- * @description Creates a new inline image component, wraps it in an inline figure container with an optional anchor,
908
- * - applies size settings, and inserts it into the editor.
909
- * @param {string} src - The URL of the image to be inserted.
910
- * @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
911
- * @param {string} width - The width value to be applied to the image.
912
- * @param {string} height - The height value to be applied to the image.
913
- * @param {{name: string, size: number}} file - File metadata associated with the image
914
- * @param {string} alt - The alternative text for the image.
915
- */
916
- createInline(src, anchor, width, height, file, alt) {
917
- /** @type {HTMLImageElement} */
918
- const oImg = dom.utils.createElement('IMG');
919
- oImg.src = src;
920
- oImg.alt = alt;
921
- anchor = this._setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
922
-
923
- const figureInfo = Figure.CreateInlineContainer(anchor, 'se-image-container');
924
- const container = figureInfo.container;
925
-
926
- this._element = oImg;
927
- this._container = container;
928
- this.figure.open(oImg, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
929
-
930
- // set size
931
- this._applySize(width, height);
932
-
933
- this.fileManager.setFileData(oImg, file);
934
-
935
- oImg.onload = this.#OnloadImg.bind(this, oImg, this._svgDefaultSize, container);
936
- this.component.insert(container, { skipCharCount: false, skipSelection: true, skipHistory: false });
937
- }
938
-
939
- /**
940
- * @private
941
906
  * @description Updates the image source URL.
942
907
  * @param {string} src - The new image source.
943
908
  * @param {HTMLImageElement} element - The image element.
944
909
  * @param {{ name: string, size: number }} file - File metadata.
945
910
  */
946
- _updateSrc(src, element, file) {
911
+ #updateSrc(src, element, file) {
947
912
  element.src = src;
948
913
  this.fileManager.setFileData(element, file);
949
914
  this.component.select(element, Image_.key);
950
915
  }
951
916
 
952
917
  /**
953
- * @private
954
918
  * @description Registers the uploaded image and inserts it into the editor.
955
919
  * @param {ImageInfo_image} info - Image info.
956
920
  * @param {Object<string, *>} response - Server response data.
957
921
  */
958
- _register(info, response) {
922
+ #register(info, response) {
923
+ this.#produceIndex = 0;
959
924
  const fileList = response.result;
960
925
 
961
926
  for (let i = 0, len = fileList.length, file; i < len; i++) {
@@ -964,34 +929,44 @@ class Image_ extends EditorInjector {
964
929
  size: fileList[i].size
965
930
  };
966
931
  if (info.isUpdate) {
967
- this._updateSrc(fileList[i].url, info.element, file);
932
+ this.#updateSrc(fileList[i].url, info.element, file);
968
933
  break;
969
934
  } else {
970
- this._produce(fileList[i].url, info.anchor, info.inputWidth, info.inputHeight, info.align, file, info.alt);
935
+ this.#produce(fileList[i].url, info.anchor, info.inputWidth, info.inputHeight, info.align, file, info.alt, i === len - 1);
971
936
  }
972
937
  }
973
938
  }
974
939
 
975
940
  /**
976
- * @private
977
941
  * @description Uploads the image to the server.
978
942
  * @param {ImageInfo_image} info - Image upload info.
979
943
  * @param {FileList} files - List of image files.
980
944
  */
981
- _serverUpload(info, files) {
945
+ #serverUpload(info, files) {
982
946
  if (!files) return;
983
947
 
984
948
  // server upload
985
949
  const imageUploadUrl = this.pluginOptions.uploadUrl;
986
950
  if (typeof imageUploadUrl === 'string' && imageUploadUrl.length > 0) {
987
- this.fileManager.upload(imageUploadUrl, this.pluginOptions.uploadHeaders, files, this.#UploadCallBack.bind(this, info), this._error.bind(this));
951
+ this.fileManager.upload(imageUploadUrl, this.pluginOptions.uploadHeaders, files, this.#UploadCallBack.bind(this, info), this.#error.bind(this));
988
952
  } else {
989
- this._setBase64(files, info.anchor, info.inputWidth, info.inputHeight, info.align, info.alt, info.isUpdate);
953
+ this.#setBase64(files, info.anchor, info.inputWidth, info.inputHeight, info.align, info.alt, info.isUpdate);
990
954
  }
991
955
  }
992
956
 
993
957
  /**
994
- * @private
958
+ * @description Handles image upload via URL.
959
+ * @param {*} info - Image information.
960
+ */
961
+ #urlUpload(info) {
962
+ this.#produceIndex = 0;
963
+ const infoUrl = info.url;
964
+
965
+ if (this.modal.isUpdate) this.#updateSrc(infoUrl, info.element, info.files);
966
+ else this.#produce(infoUrl, info.anchor, info.inputWidth, info.inputHeight, info.align, info.files, info.alt, true);
967
+ }
968
+
969
+ /**
995
970
  * @description Converts an image file to Base64 and inserts it into the editor.
996
971
  * @param {FileList|File[]} files - List of image files.
997
972
  * @param {?Node} anchor - Optional anchor wrapping the image.
@@ -1001,7 +976,7 @@ class Image_ extends EditorInjector {
1001
976
  * @param {string} alt - Alternative text.
1002
977
  * @param {boolean} isUpdate - Whether the image is being updated.
1003
978
  */
1004
- _setBase64(files, anchor, width, height, align, alt, isUpdate) {
979
+ #setBase64(files, anchor, width, height, align, alt, isUpdate) {
1005
980
  try {
1006
981
  const filesLen = this.modal.isUpdate ? 1 : files.length;
1007
982
 
@@ -1014,28 +989,26 @@ class Image_ extends EditorInjector {
1014
989
  this._base64RenderIndex = filesLen;
1015
990
  const filesStack = new Array(filesLen);
1016
991
 
1017
- if (this._resizing) {
992
+ if (this.#resizing) {
1018
993
  this.inputX.value = width;
1019
994
  this.inputY.value = height;
1020
995
  }
1021
996
 
1022
- for (let i = 0, reader, file; i < filesLen; i++) {
997
+ for (let i = 0, renderFunc = this.#onRenderBase64.bind(this), reader, file; i < filesLen; i++) {
1023
998
  reader = new FileReader();
1024
999
  file = files[i];
1025
1000
 
1026
- reader.onload = function (on_reader, update, updateElement, on_file, index) {
1001
+ reader.onload = function (loadCallback, on_reader, update, updateElement, on_file, index) {
1027
1002
  filesStack[index] = {
1028
1003
  result: on_reader.result,
1029
1004
  file: on_file
1030
1005
  };
1031
1006
 
1032
1007
  if (--this._base64RenderIndex === 0) {
1033
- this._onRenderBase64(update, filesStack, updateElement, anchor, width, height, align, alt);
1008
+ loadCallback(update, filesStack, updateElement, anchor, width, height, align, alt);
1034
1009
  this.ui.hideLoading();
1035
1010
  }
1036
- }.bind(this, reader, isUpdate, this._element, file, i);
1037
- // se-ts-ignore
1038
- this._onRenderBase64;
1011
+ }.bind(this, renderFunc, reader, isUpdate, this.#element, file, i);
1039
1012
 
1040
1013
  reader.readAsDataURL(file);
1041
1014
  }
@@ -1046,7 +1019,6 @@ class Image_ extends EditorInjector {
1046
1019
  }
1047
1020
 
1048
1021
  /**
1049
- * @private
1050
1022
  * @description Inserts an image using a Base64-encoded string.
1051
1023
  * @param {boolean} update - Whether the image is being updated.
1052
1024
  * @param {Array<{result: string, file: { name: string, size: number }}>} filesStack - Stack of Base64-encoded files.
@@ -1059,24 +1031,25 @@ class Image_ extends EditorInjector {
1059
1031
  * @param {string} align - Image alignment.
1060
1032
  * @param {string} alt - Alternative text.
1061
1033
  */
1062
- _onRenderBase64(update, filesStack, updateElement, anchor, width, height, align, alt) {
1034
+ #onRenderBase64(update, filesStack, updateElement, anchor, width, height, align, alt) {
1035
+ this.#produceIndex = 0;
1036
+
1063
1037
  for (let i = 0, len = filesStack.length; i < len; i++) {
1064
1038
  if (update) {
1065
- this._updateSrc(filesStack[i].result, updateElement, filesStack[i].file);
1039
+ this.#updateSrc(filesStack[i].result, updateElement, filesStack[i].file);
1066
1040
  } else {
1067
- this._produce(filesStack[i].result, anchor, width, height, align, filesStack[i].file, alt);
1041
+ this.#produce(filesStack[i].result, anchor, width, height, align, filesStack[i].file, alt, i === len - 1);
1068
1042
  }
1069
1043
  }
1070
1044
  }
1071
1045
 
1072
1046
  /**
1073
- * @private
1074
1047
  * @description Wraps an image element with an anchor if provided.
1075
1048
  * @param {Node} imgTag - The image element to be wrapped.
1076
1049
  * @param {?Node} anchor - The anchor element to wrap around the image. If null, returns the image itself.
1077
1050
  * @returns {Node} - The wrapped image inside the anchor or the original image element.
1078
1051
  */
1079
- _setAnchor(imgTag, anchor) {
1052
+ #setAnchor(imgTag, anchor) {
1080
1053
  if (anchor) {
1081
1054
  anchor.appendChild(imgTag);
1082
1055
  return anchor;
@@ -1086,12 +1059,11 @@ class Image_ extends EditorInjector {
1086
1059
  }
1087
1060
 
1088
1061
  /**
1089
- * @private
1090
1062
  * @description Handles errors during image upload and displays appropriate messages.
1091
1063
  * @param {Object<string, *>} response - The error response from the server.
1092
1064
  * @returns {Promise<void>}
1093
1065
  */
1094
- async _error(response) {
1066
+ async #error(response) {
1095
1067
  const message = await this.triggerEvent('onImageUploadError', { error: response });
1096
1068
  const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
1097
1069
  this.ui.alertOpen(err, 'error');
@@ -1107,13 +1079,58 @@ class Image_ extends EditorInjector {
1107
1079
  if ((await this.triggerEvent('imageUploadHandler', { xmlHttp, info })) === NO_EVENT) {
1108
1080
  const response = JSON.parse(xmlHttp.responseText);
1109
1081
  if (response.errorMessage) {
1110
- this._error(response);
1082
+ this.#error(response);
1111
1083
  } else {
1112
- this._register(info, response);
1084
+ this.#register(info, response);
1113
1085
  }
1114
1086
  }
1115
1087
  }
1116
1088
 
1089
+ /**
1090
+ * @description Opens a specific tab inside the modal.
1091
+ * @param {MouseEvent|string} e - The event object or tab name.
1092
+ * @returns {boolean} - Whether the tab was successfully opened.
1093
+ */
1094
+ #OpenTab(e) {
1095
+ const modalForm = this.modal.form;
1096
+ const targetElement = typeof e === 'string' ? modalForm.querySelector('._se_tab_link') : dom.query.getEventTarget(e);
1097
+
1098
+ if (!/^BUTTON$/i.test(targetElement.tagName)) {
1099
+ return false;
1100
+ }
1101
+
1102
+ // Declare all variables
1103
+ const tabName = targetElement.getAttribute('data-tab-link');
1104
+ let i;
1105
+
1106
+ // Get all elements with class="tabcontent" and hide them
1107
+ const tabContent = /** @type {HTMLCollectionOf<HTMLElement>}*/ (modalForm.getElementsByClassName('_se_tab_content'));
1108
+ for (i = 0; i < tabContent.length; i++) {
1109
+ tabContent[i].style.display = 'none';
1110
+ }
1111
+
1112
+ // Get all elements with class="tablinks" and remove the class "active"
1113
+ const tabLinks = modalForm.getElementsByClassName('_se_tab_link');
1114
+ for (i = 0; i < tabLinks.length; i++) {
1115
+ dom.utils.removeClass(tabLinks[i], 'active');
1116
+ }
1117
+
1118
+ // Show the current tab, and add an "active" class to the button that opened the tab
1119
+ /** @type {HTMLElement}*/ (modalForm.querySelector('._se_tab_content_' + tabName)).style.display = 'block';
1120
+ dom.utils.addClass(targetElement, 'active');
1121
+
1122
+ // focus
1123
+ if (e !== 'init') {
1124
+ if (tabName === 'image') {
1125
+ this.focusElement.focus();
1126
+ } else if (tabName === 'url') {
1127
+ this.anchor.urlInput.focus();
1128
+ }
1129
+ }
1130
+
1131
+ return false;
1132
+ }
1133
+
1117
1134
  #RemoveSelectedFiles() {
1118
1135
  this.imgInputFile.value = '';
1119
1136
  if (this.imgUrlFile) {
@@ -1131,10 +1148,10 @@ class Image_ extends EditorInjector {
1131
1148
  return;
1132
1149
  }
1133
1150
 
1134
- if (xy === 'x' && this._onlyPercentage && e.target.value > 100) {
1151
+ if (xy === 'x' && this.#onlyPercentage && e.target.value > 100) {
1135
1152
  e.target.value = 100;
1136
1153
  } else if (this.proportion.checked) {
1137
- const ratioSize = Figure.CalcRatio(this.inputX.value, this.inputY.value, this.sizeUnit, this._ratio);
1154
+ const ratioSize = Figure.CalcRatio(this.inputX.value, this.inputY.value, this.sizeUnit, this.#ratio);
1138
1155
  if (xy === 'x') {
1139
1156
  this.inputY.value = String(ratioSize.h);
1140
1157
  } else {
@@ -1144,36 +1161,31 @@ class Image_ extends EditorInjector {
1144
1161
  }
1145
1162
 
1146
1163
  #OnChangeRatio() {
1147
- this._ratio = this.proportion.checked
1148
- ? Figure.GetRatio(this.inputX.value, this.inputY.value, this.sizeUnit)
1149
- : {
1150
- w: 1,
1151
- h: 1
1152
- };
1164
+ this.#ratio = this.proportion.checked ? Figure.GetRatio(this.inputX.value, this.inputY.value, this.sizeUnit) : { w: 0, h: 0 };
1153
1165
  }
1154
1166
 
1155
1167
  #OnClickRevert() {
1156
- if (this._onlyPercentage) {
1157
- this.inputX.value = Number(this._origin_w) > 100 ? '100' : this._origin_w;
1168
+ if (this.#onlyPercentage) {
1169
+ this.inputX.value = Number(this.#origin_w) > 100 ? '100' : this.#origin_w;
1158
1170
  } else {
1159
- this.inputX.value = this._origin_w;
1160
- this.inputY.value = this._origin_h;
1171
+ this.inputX.value = this.#origin_w;
1172
+ this.inputY.value = this.#origin_h;
1161
1173
  }
1162
1174
  }
1163
1175
 
1164
1176
  #OnClickAsButton({ target }) {
1165
- this._activeAsInline(target.getAttribute('data-command') === 'asInline');
1177
+ this.#activeAsInline(target.getAttribute('data-command') === 'asInline');
1166
1178
  }
1167
1179
 
1168
1180
  #OnLinkPreview(e) {
1169
1181
  const value = e.target.value.trim();
1170
- this._linkValue = this.previewSrc.textContent = !value
1182
+ this.#linkValue = this.previewSrc.textContent = !value
1171
1183
  ? ''
1172
1184
  : this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
1173
- ? this.options.get('defaultUrlProtocol') + value
1174
- : !value.includes('://')
1175
- ? '/' + value
1176
- : value;
1185
+ ? this.options.get('defaultUrlProtocol') + value
1186
+ : !value.includes('://')
1187
+ ? '/' + value
1188
+ : value;
1177
1189
  }
1178
1190
 
1179
1191
  #OnfileInputChange({ target }) {
@@ -1195,33 +1207,23 @@ class Image_ extends EditorInjector {
1195
1207
 
1196
1208
  #SetUrlInput(target) {
1197
1209
  this.altText.value = target.getAttribute('data-value') || target.alt;
1198
- this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.getAttribute('data-command') || target.src;
1210
+ this.#linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.getAttribute('data-command') || target.src;
1199
1211
  this.imgUrlFile.focus();
1200
1212
  }
1201
1213
 
1202
1214
  #OnloadImg(oImg, _svgDefaultSize, container) {
1215
+ this.#produceIndex--;
1216
+ delete oImg.onload;
1217
+
1203
1218
  // svg exception handling
1204
- if (oImg.offsetWidth === 0) this._applySize(_svgDefaultSize, '');
1205
- if (this.options.get('componentAutoSelect')) {
1206
- this.component.select(oImg, Image_.key);
1207
- } else {
1208
- if (!this.component.isInline(container)) {
1209
- const line = this.format.addLine(container, null);
1210
- if (line) this.selection.setRange(line, 0, line, 0);
1211
- } else {
1212
- const r = this.selection.getNearRange(container);
1213
- if (r) {
1214
- this.selection.setRange(r.container, r.offset, r.container, r.offset);
1215
- } else {
1216
- this.component.select(oImg, Image_.key);
1217
- }
1218
- }
1219
- }
1219
+ if (oImg.offsetWidth === 0) this.#applySize(_svgDefaultSize, '');
1220
1220
 
1221
- this.editor._iframeAutoHeight(this.editor.frameContext);
1222
- this.history.push(false);
1221
+ if (this.#produceIndex === 0) {
1222
+ this.component.applyInsertBehavior(container, null, this.pluginOptions.insertBehavior || this.options.get('componentInsertBehavior'));
1223
1223
 
1224
- delete oImg.onload;
1224
+ this.editor._iframeAutoHeight(this.frameContext);
1225
+ this.history.push(false);
1226
+ }
1225
1227
  }
1226
1228
  }
1227
1229