suneditor 3.0.0-alpha.9 → 3.0.0-beta.2

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 (315) hide show
  1. package/CONTRIBUTING.md +170 -22
  2. package/{LICENSE.txt → LICENSE} +9 -9
  3. package/README.md +168 -30
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +47 -21
  7. package/src/assets/design/color.css +121 -0
  8. package/src/assets/design/index.css +3 -0
  9. package/src/assets/design/size.css +35 -0
  10. package/src/assets/design/typography.css +37 -0
  11. package/src/assets/icons/defaultIcons.js +232 -0
  12. package/src/assets/suneditor-contents.css +181 -46
  13. package/src/assets/suneditor.css +1403 -650
  14. package/src/core/base/eventHandlers/handler_toolbar.js +35 -14
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +23 -4
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +49 -10
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +422 -224
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +83 -36
  19. package/src/core/base/eventManager.js +520 -179
  20. package/src/core/base/history.js +95 -41
  21. package/src/core/class/char.js +26 -11
  22. package/src/core/class/component.js +345 -137
  23. package/src/core/class/format.js +683 -519
  24. package/src/core/class/html.js +485 -305
  25. package/src/core/class/menu.js +133 -47
  26. package/src/core/class/nodeTransform.js +90 -71
  27. package/src/core/class/offset.js +408 -92
  28. package/src/core/class/selection.js +216 -106
  29. package/src/core/class/shortcuts.js +68 -8
  30. package/src/core/class/toolbar.js +106 -116
  31. package/src/core/class/ui.js +422 -0
  32. package/src/core/class/viewer.js +178 -74
  33. package/src/core/editor.js +496 -389
  34. package/src/core/section/actives.js +123 -27
  35. package/src/core/section/constructor.js +615 -206
  36. package/src/core/section/context.js +28 -23
  37. package/src/core/section/documentType.js +561 -0
  38. package/src/editorInjector/_classes.js +19 -5
  39. package/src/editorInjector/_core.js +71 -7
  40. package/src/editorInjector/index.js +63 -1
  41. package/src/events.js +622 -0
  42. package/src/helper/clipboard.js +59 -0
  43. package/src/helper/converter.js +202 -26
  44. package/src/helper/dom/domCheck.js +304 -0
  45. package/src/helper/dom/domQuery.js +669 -0
  46. package/src/helper/dom/domUtils.js +557 -0
  47. package/src/helper/dom/index.js +12 -0
  48. package/src/helper/env.js +46 -56
  49. package/src/helper/index.js +10 -4
  50. package/src/helper/keyCodeMap.js +183 -0
  51. package/src/helper/numbers.js +12 -8
  52. package/src/helper/unicode.js +9 -5
  53. package/src/langs/ckb.js +74 -4
  54. package/src/langs/cs.js +72 -2
  55. package/src/langs/da.js +73 -3
  56. package/src/langs/de.js +73 -4
  57. package/src/langs/en.js +23 -3
  58. package/src/langs/es.js +73 -4
  59. package/src/langs/fa.js +75 -3
  60. package/src/langs/fr.js +73 -3
  61. package/src/langs/he.js +73 -4
  62. package/src/langs/hu.js +230 -0
  63. package/src/langs/index.js +7 -3
  64. package/src/langs/it.js +70 -1
  65. package/src/langs/ja.js +72 -4
  66. package/src/langs/km.js +230 -0
  67. package/src/langs/ko.js +22 -2
  68. package/src/langs/lv.js +74 -5
  69. package/src/langs/nl.js +73 -4
  70. package/src/langs/pl.js +73 -4
  71. package/src/langs/pt_br.js +70 -1
  72. package/src/langs/ro.js +74 -5
  73. package/src/langs/ru.js +73 -4
  74. package/src/langs/se.js +73 -4
  75. package/src/langs/tr.js +73 -1
  76. package/src/langs/{ua.js → uk.js} +75 -6
  77. package/src/langs/ur.js +77 -8
  78. package/src/langs/zh_cn.js +74 -5
  79. package/src/modules/ApiManager.js +77 -54
  80. package/src/modules/Browser.js +667 -0
  81. package/src/modules/ColorPicker.js +162 -102
  82. package/src/modules/Controller.js +273 -142
  83. package/src/modules/Figure.js +925 -484
  84. package/src/modules/FileManager.js +121 -69
  85. package/src/modules/HueSlider.js +113 -61
  86. package/src/modules/Modal.js +291 -122
  87. package/src/modules/ModalAnchorEditor.js +383 -234
  88. package/src/modules/SelectMenu.js +270 -168
  89. package/src/modules/_DragHandle.js +2 -1
  90. package/src/modules/index.js +3 -3
  91. package/src/plugins/browser/audioGallery.js +83 -0
  92. package/src/plugins/browser/fileBrowser.js +103 -0
  93. package/src/plugins/browser/fileGallery.js +83 -0
  94. package/src/plugins/browser/imageGallery.js +81 -0
  95. package/src/plugins/browser/videoGallery.js +103 -0
  96. package/src/plugins/command/blockquote.js +40 -27
  97. package/src/plugins/command/exportPDF.js +134 -0
  98. package/src/plugins/command/fileUpload.js +229 -162
  99. package/src/plugins/command/list_bulleted.js +83 -47
  100. package/src/plugins/command/list_numbered.js +83 -47
  101. package/src/plugins/dropdown/align.js +66 -54
  102. package/src/plugins/dropdown/backgroundColor.js +63 -49
  103. package/src/plugins/dropdown/font.js +71 -47
  104. package/src/plugins/dropdown/fontColor.js +63 -48
  105. package/src/plugins/dropdown/formatBlock.js +70 -33
  106. package/src/plugins/dropdown/hr.js +92 -51
  107. package/src/plugins/dropdown/layout.js +37 -26
  108. package/src/plugins/dropdown/lineHeight.js +54 -38
  109. package/src/plugins/dropdown/list.js +60 -45
  110. package/src/plugins/dropdown/paragraphStyle.js +51 -30
  111. package/src/plugins/dropdown/table.js +2003 -813
  112. package/src/plugins/dropdown/template.js +38 -26
  113. package/src/plugins/dropdown/textStyle.js +43 -31
  114. package/src/plugins/field/mention.js +147 -86
  115. package/src/plugins/index.js +32 -6
  116. package/src/plugins/input/fontSize.js +161 -108
  117. package/src/plugins/input/pageNavigator.js +70 -0
  118. package/src/plugins/modal/audio.js +358 -173
  119. package/src/plugins/modal/drawing.js +531 -0
  120. package/src/plugins/modal/embed.js +886 -0
  121. package/src/plugins/modal/image.js +674 -362
  122. package/src/plugins/modal/link.js +100 -71
  123. package/src/plugins/modal/math.js +367 -167
  124. package/src/plugins/modal/video.js +691 -335
  125. package/src/plugins/popup/anchor.js +222 -0
  126. package/src/suneditor.js +50 -13
  127. package/src/themes/dark.css +122 -0
  128. package/src/typedef.js +130 -0
  129. package/types/assets/icons/defaultIcons.d.ts +153 -0
  130. package/types/core/base/eventHandlers/handler_toolbar.d.ts +41 -0
  131. package/types/core/base/eventHandlers/handler_ww_clipboard.d.ts +40 -0
  132. package/types/core/base/eventHandlers/handler_ww_dragDrop.d.ts +35 -0
  133. package/types/core/base/eventHandlers/handler_ww_key_input.d.ts +45 -0
  134. package/types/core/base/eventHandlers/handler_ww_mouse.d.ts +39 -0
  135. package/types/core/base/eventManager.d.ts +385 -0
  136. package/types/core/base/history.d.ts +81 -0
  137. package/types/core/class/char.d.ts +60 -0
  138. package/types/core/class/component.d.ts +212 -0
  139. package/types/core/class/format.d.ts +616 -0
  140. package/types/core/class/html.d.ts +422 -0
  141. package/types/core/class/menu.d.ts +126 -0
  142. package/types/core/class/nodeTransform.d.ts +93 -0
  143. package/types/core/class/offset.d.ts +522 -0
  144. package/types/core/class/selection.d.ts +188 -0
  145. package/types/core/class/shortcuts.d.ts +142 -0
  146. package/types/core/class/toolbar.d.ts +189 -0
  147. package/types/core/class/ui.d.ts +164 -0
  148. package/types/core/class/viewer.d.ts +140 -0
  149. package/types/core/editor.d.ts +610 -0
  150. package/types/core/section/actives.d.ts +46 -0
  151. package/types/core/section/constructor.d.ts +777 -0
  152. package/types/core/section/context.d.ts +45 -0
  153. package/types/core/section/documentType.d.ts +178 -0
  154. package/types/editorInjector/_classes.d.ts +41 -0
  155. package/types/editorInjector/_core.d.ts +92 -0
  156. package/types/editorInjector/index.d.ts +71 -0
  157. package/types/events.d.ts +273 -0
  158. package/types/helper/clipboard.d.ts +12 -0
  159. package/types/helper/converter.d.ts +197 -0
  160. package/types/helper/dom/domCheck.d.ts +189 -0
  161. package/types/helper/dom/domQuery.d.ts +223 -0
  162. package/types/helper/dom/domUtils.d.ts +226 -0
  163. package/types/helper/dom/index.d.ts +9 -0
  164. package/types/helper/env.d.ts +132 -0
  165. package/types/helper/index.d.ts +174 -0
  166. package/types/helper/keyCodeMap.d.ts +110 -0
  167. package/types/helper/numbers.d.ts +46 -0
  168. package/types/helper/unicode.d.ts +28 -0
  169. package/types/index.d.ts +120 -0
  170. package/{typings/Lang.d.ts → types/langs/_Lang.d.ts} +173 -103
  171. package/types/langs/ckb.d.ts +3 -0
  172. package/types/langs/cs.d.ts +3 -0
  173. package/types/langs/da.d.ts +3 -0
  174. package/types/langs/de.d.ts +3 -0
  175. package/types/langs/en.d.ts +3 -0
  176. package/types/langs/es.d.ts +3 -0
  177. package/types/langs/fa.d.ts +3 -0
  178. package/types/langs/fr.d.ts +3 -0
  179. package/types/langs/he.d.ts +3 -0
  180. package/types/langs/hu.d.ts +3 -0
  181. package/types/langs/index.d.ts +54 -0
  182. package/types/langs/it.d.ts +3 -0
  183. package/types/langs/ja.d.ts +3 -0
  184. package/types/langs/km.d.ts +3 -0
  185. package/types/langs/ko.d.ts +3 -0
  186. package/types/langs/lv.d.ts +3 -0
  187. package/types/langs/nl.d.ts +3 -0
  188. package/types/langs/pl.d.ts +3 -0
  189. package/types/langs/pt_br.d.ts +3 -0
  190. package/types/langs/ro.d.ts +3 -0
  191. package/types/langs/ru.d.ts +3 -0
  192. package/types/langs/se.d.ts +3 -0
  193. package/types/langs/tr.d.ts +3 -0
  194. package/types/langs/uk.d.ts +3 -0
  195. package/types/langs/ur.d.ts +3 -0
  196. package/types/langs/zh_cn.d.ts +3 -0
  197. package/types/modules/ApiManager.d.ts +125 -0
  198. package/types/modules/Browser.d.ts +326 -0
  199. package/types/modules/ColorPicker.d.ts +131 -0
  200. package/types/modules/Controller.d.ts +251 -0
  201. package/types/modules/Figure.d.ts +517 -0
  202. package/types/modules/FileManager.d.ts +202 -0
  203. package/types/modules/HueSlider.d.ts +136 -0
  204. package/types/modules/Modal.d.ts +111 -0
  205. package/types/modules/ModalAnchorEditor.d.ts +236 -0
  206. package/types/modules/SelectMenu.d.ts +194 -0
  207. package/types/modules/_DragHandle.d.ts +7 -0
  208. package/types/modules/index.d.ts +26 -0
  209. package/types/plugins/browser/audioGallery.d.ts +55 -0
  210. package/types/plugins/browser/fileBrowser.d.ts +64 -0
  211. package/types/plugins/browser/fileGallery.d.ts +55 -0
  212. package/types/plugins/browser/imageGallery.d.ts +51 -0
  213. package/types/plugins/browser/videoGallery.d.ts +57 -0
  214. package/types/plugins/command/blockquote.d.ts +28 -0
  215. package/types/plugins/command/exportPDF.d.ts +46 -0
  216. package/types/plugins/command/fileUpload.d.ts +156 -0
  217. package/types/plugins/command/list_bulleted.d.ts +46 -0
  218. package/types/plugins/command/list_numbered.d.ts +46 -0
  219. package/types/plugins/dropdown/align.d.ts +60 -0
  220. package/types/plugins/dropdown/backgroundColor.d.ts +63 -0
  221. package/types/plugins/dropdown/font.d.ts +54 -0
  222. package/types/plugins/dropdown/fontColor.d.ts +63 -0
  223. package/types/plugins/dropdown/formatBlock.d.ts +54 -0
  224. package/types/plugins/dropdown/hr.d.ts +71 -0
  225. package/types/plugins/dropdown/layout.d.ts +40 -0
  226. package/types/plugins/dropdown/lineHeight.d.ts +50 -0
  227. package/types/plugins/dropdown/list.d.ts +39 -0
  228. package/types/plugins/dropdown/paragraphStyle.d.ts +54 -0
  229. package/types/plugins/dropdown/table.d.ts +627 -0
  230. package/types/plugins/dropdown/template.d.ts +40 -0
  231. package/types/plugins/dropdown/textStyle.d.ts +41 -0
  232. package/types/plugins/field/mention.d.ts +102 -0
  233. package/types/plugins/index.d.ts +107 -0
  234. package/types/plugins/input/fontSize.d.ts +170 -0
  235. package/types/plugins/input/pageNavigator.d.ts +28 -0
  236. package/types/plugins/modal/audio.d.ts +269 -0
  237. package/types/plugins/modal/drawing.d.ts +246 -0
  238. package/types/plugins/modal/embed.d.ts +387 -0
  239. package/types/plugins/modal/image.d.ts +451 -0
  240. package/types/plugins/modal/link.d.ts +128 -0
  241. package/types/plugins/modal/math.d.ts +193 -0
  242. package/types/plugins/modal/video.d.ts +485 -0
  243. package/types/plugins/popup/anchor.d.ts +56 -0
  244. package/types/suneditor.d.ts +51 -0
  245. package/types/typedef.d.ts +233 -0
  246. package/.eslintignore +0 -7
  247. package/.eslintrc.json +0 -64
  248. package/src/assets/icons/_default.js +0 -194
  249. package/src/core/base/events.js +0 -320
  250. package/src/core/class/notice.js +0 -42
  251. package/src/helper/domUtils.js +0 -1177
  252. package/src/modules/FileBrowser.js +0 -271
  253. package/src/plugins/command/exportPdf.js +0 -168
  254. package/src/plugins/fileBrowser/imageGallery.js +0 -81
  255. package/src/themes/test.css +0 -61
  256. package/typings/CommandPlugin.d.ts +0 -8
  257. package/typings/DialogPlugin.d.ts +0 -20
  258. package/typings/FileBrowserPlugin.d.ts +0 -30
  259. package/typings/Module.d.ts +0 -15
  260. package/typings/Plugin.d.ts +0 -42
  261. package/typings/SubmenuPlugin.d.ts +0 -8
  262. package/typings/_classes.d.ts +0 -17
  263. package/typings/_colorPicker.d.ts +0 -60
  264. package/typings/_core.d.ts +0 -55
  265. package/typings/align.d.ts +0 -5
  266. package/typings/audio.d.ts +0 -5
  267. package/typings/backgroundColor.d.ts +0 -5
  268. package/typings/blockquote.d.ts +0 -5
  269. package/typings/char.d.ts +0 -39
  270. package/typings/component.d.ts +0 -38
  271. package/typings/context.d.ts +0 -39
  272. package/typings/converter.d.ts +0 -33
  273. package/typings/dialog.d.ts +0 -28
  274. package/typings/domUtils.d.ts +0 -361
  275. package/typings/editor.d.ts +0 -7
  276. package/typings/editor.ts +0 -542
  277. package/typings/env.d.ts +0 -70
  278. package/typings/eventManager.d.ts +0 -37
  279. package/typings/events.d.ts +0 -262
  280. package/typings/fileBrowser.d.ts +0 -42
  281. package/typings/fileManager.d.ts +0 -67
  282. package/typings/font.d.ts +0 -5
  283. package/typings/fontColor.d.ts +0 -5
  284. package/typings/fontSize.d.ts +0 -5
  285. package/typings/format.d.ts +0 -191
  286. package/typings/formatBlock.d.ts +0 -5
  287. package/typings/history.d.ts +0 -48
  288. package/typings/horizontalRule.d.ts +0 -5
  289. package/typings/image.d.ts +0 -5
  290. package/typings/imageGallery.d.ts +0 -5
  291. package/typings/index.d.ts +0 -21
  292. package/typings/index.modules.d.ts +0 -11
  293. package/typings/index.plugins.d.ts +0 -58
  294. package/typings/lineHeight.d.ts +0 -5
  295. package/typings/link.d.ts +0 -5
  296. package/typings/list.d.ts +0 -5
  297. package/typings/math.d.ts +0 -5
  298. package/typings/mediaContainer.d.ts +0 -25
  299. package/typings/mention.d.ts +0 -5
  300. package/typings/node.d.ts +0 -57
  301. package/typings/notice.d.ts +0 -16
  302. package/typings/numbers.d.ts +0 -29
  303. package/typings/offset.d.ts +0 -24
  304. package/typings/options.d.ts +0 -589
  305. package/typings/paragraphStyle.d.ts +0 -5
  306. package/typings/resizing.d.ts +0 -141
  307. package/typings/selection.d.ts +0 -94
  308. package/typings/shortcuts.d.ts +0 -13
  309. package/typings/suneditor.d.ts +0 -9
  310. package/typings/table.d.ts +0 -5
  311. package/typings/template.d.ts +0 -5
  312. package/typings/textStyle.d.ts +0 -5
  313. package/typings/toolbar.d.ts +0 -32
  314. package/typings/unicode.d.ts +0 -25
  315. package/typings/video.d.ts +0 -5
@@ -1,194 +1,253 @@
1
1
  import EditorInjector from '../../editorInjector';
2
2
  import { Modal, Figure, FileManager, ModalAnchorEditor } from '../../modules';
3
- import { domUtils, numbers, env } from '../../helper';
4
- import { CreateTooltipInner } from '../../core/section/constructor';
3
+ import { dom, numbers, env, keyCodeMap } from '../../helper';
5
4
  const { NO_EVENT } = env;
6
5
 
7
- const Image_ = function (editor, pluginOptions) {
8
- // plugin bisic properties
9
- EditorInjector.call(this, editor);
10
- this.title = this.lang.image;
11
- this.icon = 'image';
12
-
13
- // define plugin options
14
- this.pluginOptions = {
15
- canResize: pluginOptions.canResize === undefined ? true : pluginOptions.canResize,
16
- showHeightInput: pluginOptions.showHeightInput === undefined ? true : !!pluginOptions.showHeightInput,
17
- defaultWidth: !pluginOptions.defaultWidth ? 'auto' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
18
- defaultHeight: !pluginOptions.defaultHeight ? 'auto' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
19
- percentageOnlySize: !!pluginOptions.percentageOnlySize,
20
- createFileInput: pluginOptions.createFileInput === undefined ? true : pluginOptions.createFileInput,
21
- createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
22
- uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
23
- uploadHeaders: pluginOptions.uploadHeaders || null,
24
- uploadSizeLimit: /\d+/.test(pluginOptions.uploadSizeLimit) ? numbers.get(pluginOptions.uploadSizeLimit, 0) : null,
25
- uploadSingleSizeLimit: /\d+/.test(pluginOptions.uploadSingleSizeLimit) ? numbers.get(pluginOptions.uploadSingleSizeLimit, 0) : null,
26
- allowMultiple: !!pluginOptions.allowMultiple,
27
- acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'image/*' : pluginOptions.acceptedFormats.trim() || 'image/*'
28
- // useFormatType: pluginOptions.useFormatType ?? false,
29
- // defaultFormatType: ['block', 'inline'].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : 'block'
30
- };
31
-
32
- // create HTML
33
- const sizeUnit = this.pluginOptions.percentageOnlySize ? '%' : 'px';
34
- const modalEl = CreateHTML_modal(editor, this.pluginOptions);
35
- const figureControls =
36
- pluginOptions.controls || !this.pluginOptions.canResize
37
- ? [['mirror_h', 'mirror_v', 'align', 'caption', 'revert', 'edit', 'remove']]
38
- : [
39
- ['resize_auto,100,75,50', 'rotate_l', 'rotate_r', 'mirror_h', 'mirror_v'],
40
- ['edit', 'align', 'caption', 'revert', 'remove']
41
- ];
42
-
43
- // show align
44
- this.alignForm = modalEl.querySelector('.se-figure-align');
45
- if (!figureControls.some((subArray) => subArray.includes('align'))) this.alignForm.style.display = 'none';
46
-
47
- // modules
48
- const Link = this.plugins.link ? this.plugins.link.pluginOptions : {};
49
- this.anchor = new ModalAnchorEditor(this, modalEl, {
50
- textToDisplay: false,
51
- title: true,
52
- openNewWindow: Link.openNewWindow,
53
- relList: Link.relList,
54
- defaultRel: Link.defaultRel,
55
- noAutoPrefix: Link.noAutoPrefix,
56
- enableFileUpload: pluginOptions.linkEnableFileUpload
57
- });
58
- this.modal = new Modal(this, modalEl);
59
- this.figure = new Figure(this, figureControls, {
60
- sizeUnit: sizeUnit
61
- });
62
- this.fileManager = new FileManager(this, {
63
- query: 'img',
64
- loadHandler: this.events.onImageLoad,
65
- eventHandler: this.events.onImageAction
66
- });
67
-
68
- // members
69
- this.fileModalWrapper = modalEl.querySelector('.se-flex-input-wrapper');
70
- this.imgInputFile = modalEl.querySelector('.__se__file_input');
71
- this.imgUrlFile = modalEl.querySelector('._se_image_url');
72
- this.focusElement = this.imgInputFile || this.imgUrlFile;
73
- this.altText = modalEl.querySelector('._se_image_alt');
74
- this.captionCheckEl = modalEl.querySelector('._se_image_check_caption');
75
- this.previewSrc = modalEl.querySelector('._se_tab_content_image .se-link-preview');
76
- this.sizeUnit = sizeUnit;
77
- this.proportion = {};
78
- this.inputX = {};
79
- this.inputY = {};
80
- this._linkElement = null;
81
- this._linkValue = '';
82
- this._align = 'none';
83
- this._svgDefaultSize = '30%';
84
- this._base64RenderIndex = 0;
85
- this._element = null;
86
- this._cover = null;
87
- this._container = null;
88
- this._caption = null;
89
- this._ratio = {
90
- w: 1,
91
- h: 1
92
- };
93
- this._origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
94
- this._origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
95
- this._resizing = this.pluginOptions.canResize;
96
- this._onlyPercentage = this.pluginOptions.percentageOnlySize;
97
- this._nonResizing = !this._resizing || !this.pluginOptions.showHeightInput || this._onlyPercentage;
98
-
99
- // init
100
- this.eventManager.addEvent(modalEl.querySelector('.se-modal-tabs'), 'click', this._openTab.bind(this));
101
- if (this.imgInputFile) this.eventManager.addEvent(modalEl.querySelector('.se-file-remove'), 'click', RemoveSelectedFiles.bind(this));
102
- if (this.imgUrlFile) this.eventManager.addEvent(this.imgUrlFile, 'input', OnLinkPreview.bind(this));
103
- if (this.imgInputFile && this.imgUrlFile) this.eventManager.addEvent(this.imgInputFile, 'change', OnfileInputChange.bind(this));
104
-
105
- const imageGalleryButton = modalEl.querySelector('.__se__gallery');
106
- if (imageGalleryButton) this.eventManager.addEvent(imageGalleryButton, 'click', OpenGallery.bind(this));
107
-
108
- if (this._resizing) {
109
- this.proportion = modalEl.querySelector('._se_image_check_proportion');
110
- this.inputX = modalEl.querySelector('._se_image_size_x');
111
- this.inputY = modalEl.querySelector('._se_image_size_y');
112
- this.inputX.value = this.pluginOptions.defaultWidth;
113
- this.inputY.value = this.pluginOptions.defaultHeight;
114
-
115
- const ratioChange = OnChangeRatio.bind(this);
116
- this.eventManager.addEvent(this.inputX, 'keyup', OnInputSize.bind(this, 'x'));
117
- this.eventManager.addEvent(this.inputY, 'keyup', OnInputSize.bind(this, 'y'));
118
- this.eventManager.addEvent(this.inputX, 'change', ratioChange);
119
- this.eventManager.addEvent(this.inputY, 'change', ratioChange);
120
- this.eventManager.addEvent(this.proportion, 'change', ratioChange);
121
- this.eventManager.addEvent(modalEl.querySelector('.se-modal-btn-revert'), 'click', OnClickRevert.bind(this));
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;
122
53
  }
123
54
 
124
- if (this.pluginOptions.useFormatType) {
125
- this.as = this.pluginOptions.defaultFormatType;
126
- this.asBlock = modalEl.querySelector('[data-command="asBlock"]');
127
- this.asInline = modalEl.querySelector('[data-command="asInline"]');
128
- this.eventManager.addEvent([this.asBlock, this.asInline], 'click', OnClickAsButton.bind(this));
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
+ ['align', 'caption', 'edit', '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
+ }
129
186
  }
130
- };
131
-
132
- Image_.key = 'image';
133
- Image_.type = 'modal';
134
- Image_.component = function (node) {
135
- node = domUtils.isFigure(node) ? node.firstElementChild : node;
136
- return /^IMG$/i.test(node?.nodeName) ? node : domUtils.isAnchor(node) && /^IMG$/i.test(node?.firstElementChild?.nodeName) ? node?.firstElementChild : null;
137
- };
138
- Image_.className = '';
139
- Image_.prototype = {
187
+
140
188
  /**
141
- * @override type = "modal"
189
+ * @editorMethod Modules.Modal
190
+ * @description Executes the method that is called when a "Modal" module's is opened.
142
191
  */
143
192
  open() {
144
193
  this.modal.open();
145
- },
194
+ }
146
195
 
147
196
  /**
148
- * @override Figure
197
+ * @editorMethod Modules.Controller(Figure)
198
+ * @description Executes the method that is called when a target component is edited.
149
199
  */
150
200
  edit() {
151
201
  this.modal.open();
152
- },
202
+ }
153
203
 
154
204
  /**
155
- * @override modal
156
- * @param {boolean} isUpdate open state is update
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)."
157
208
  */
158
209
  on(isUpdate) {
159
210
  if (!isUpdate) {
160
- this.inputX.value = this._origin_w = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
161
- this.inputY.value = this._origin_h = this.pluginOptions.defaultHeight === 'auto' ? '' : this.pluginOptions.defaultHeight;
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
+ }
162
215
  if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.setAttribute('multiple', 'multiple');
163
216
  } else {
164
217
  if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.removeAttribute('multiple');
165
218
  }
166
219
 
167
220
  this.anchor.on(isUpdate);
168
- },
221
+ }
169
222
 
170
223
  /**
171
- * @description On paste or drop
172
- * @param {*} params { frameContext, event, file }
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
173
231
  */
174
- onPastAndDrop({ file }) {
232
+ onFilePasteAndDrop({ file }) {
175
233
  if (!/^image/.test(file.type)) return;
176
234
 
177
235
  this.submitFile([file]);
178
236
  this.editor.focus();
179
237
 
180
238
  return false;
181
- },
239
+ }
182
240
 
183
241
  /**
184
- * @override modal
185
- * @returns {boolean | undefined}
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
186
245
  */
187
246
  async modalAction() {
188
- this._align = this.modal.form.querySelector('input[name="suneditor_image_radio"]:checked').value;
247
+ this._align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"]:checked')).value;
189
248
 
190
249
  if (this.modal.isUpdate) {
191
- this._update(this.inputX.value, this.inputY.value);
250
+ this._update(this.inputX?.value, this.inputY?.value);
192
251
  this.history.push(false);
193
252
  }
194
253
 
@@ -199,10 +258,18 @@ Image_.prototype = {
199
258
  }
200
259
 
201
260
  return false;
202
- },
261
+ }
203
262
 
204
263
  /**
205
- * @override core
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.
206
273
  */
207
274
  retainFormat() {
208
275
  return {
@@ -211,33 +278,34 @@ Image_.prototype = {
211
278
  const figureInfo = Figure.GetContainer(element);
212
279
  if (figureInfo && figureInfo.container && figureInfo.cover) return;
213
280
 
214
- this.ready(element);
281
+ this._ready(element);
215
282
  this._fileCheck(this._origin_w, this._origin_h);
216
283
  }
217
284
  };
218
- },
285
+ }
219
286
 
220
287
  /**
221
- * @override modal
288
+ * @editorMethod Modules.Modal
289
+ * @description This function is called before the modal window is opened, but before it is closed.
222
290
  */
223
291
  init() {
224
292
  Modal.OnChangeFile(this.fileModalWrapper, []);
225
293
  if (this.imgInputFile) this.imgInputFile.value = '';
226
294
  if (this.imgUrlFile) this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = '';
227
295
  if (this.imgInputFile && this.imgUrlFile) {
228
- this.imgUrlFile.removeAttribute('disabled');
296
+ this.imgUrlFile.disabled = false;
229
297
  this.previewSrc.style.textDecoration = '';
230
298
  }
231
299
 
232
300
  this.altText.value = '';
233
- this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]').checked = true;
301
+ /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]')).checked = true;
234
302
  this.captionCheckEl.checked = false;
235
303
  this._element = null;
236
304
  this._ratio = {
237
305
  w: 1,
238
306
  h: 1
239
307
  };
240
- this._openTab('init');
308
+ this.#OpenTab('init');
241
309
 
242
310
  if (this._resizing) {
243
311
  this.inputX.value = this.pluginOptions.defaultWidth === 'auto' ? '' : this.pluginOptions.defaultWidth;
@@ -246,28 +314,32 @@ Image_.prototype = {
246
314
  }
247
315
 
248
316
  if (this.pluginOptions.useFormatType) {
249
- this._activeAsInline(this.pluginOptions.defaultFormatType === 'inline');
317
+ this._activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === 'inline');
250
318
  }
251
319
 
252
320
  this.anchor.init();
253
- },
321
+ }
254
322
 
255
323
  /**
256
- * @override component, fileManager
257
- * @description Called when a container is selected.
258
- * @param {Element} element Target element
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
259
327
  */
260
- select(element) {
261
- this.ready(element);
262
- },
328
+ select(target) {
329
+ this._ready(target);
330
+ }
263
331
 
264
332
  /**
265
- * @override fileManager, figure
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.
266
338
  */
267
- ready(target) {
339
+ _ready(target) {
268
340
  if (!target) return;
269
341
  const figureInfo = this.figure.open(target, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: false });
270
- this.anchor.set(domUtils.isAnchor(target.parentNode) ? target.parentNode : null);
342
+ this.anchor.set(dom.check.isAnchor(target.parentNode) ? target.parentNode : null);
271
343
 
272
344
  this._linkElement = this.anchor.currentTarget;
273
345
  this._element = target;
@@ -277,13 +349,15 @@ Image_.prototype = {
277
349
  this._align = figureInfo.align;
278
350
  target.style.float = '';
279
351
 
280
- this._origin_w = figureInfo.originWidth || figureInfo.w || '';
281
- this._origin_h = figureInfo.originHeight || figureInfo.h || '';
352
+ this._origin_w = String(figureInfo.originWidth || figureInfo.w || '');
353
+ this._origin_h = String(figureInfo.originHeight || figureInfo.h || '');
282
354
  this.altText.value = this._element.alt;
283
355
 
284
356
  if (this.imgUrlFile) this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = this._element.src;
285
357
 
286
- (this.modal.form.querySelector('input[name="suneditor_image_radio"][value="' + this._align + '"]') || this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]')).checked = true;
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;
287
361
  this.captionCheckEl.checked = !!this._caption;
288
362
 
289
363
  if (!this._resizing) return;
@@ -294,11 +368,11 @@ Image_.prototype = {
294
368
  w = numbers.get(w, 2);
295
369
  if (w > 100) w = 100;
296
370
  }
297
- this.inputX.value = w === 'auto' ? '' : w;
371
+ this.inputX.value = String(w === 'auto' ? '' : w);
298
372
 
299
373
  if (!this._onlyPercentage) {
300
374
  const h = percentageRotation ? '' : figureInfo.height;
301
- this.inputY.value = h === 'auto' ? '' : h;
375
+ this.inputY.value = String(h === 'auto' ? '' : h);
302
376
  }
303
377
 
304
378
  this.proportion.checked = true;
@@ -316,21 +390,24 @@ Image_.prototype = {
316
390
  if (this.pluginOptions.useFormatType) {
317
391
  this._activeAsInline(this.component.isInline(figureInfo.container));
318
392
  }
319
- },
393
+ }
320
394
 
321
395
  /**
322
- * @override fileManager
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>}
323
400
  */
324
- async destroy(element) {
325
- const targetEl = element || this._element;
326
- const container = domUtils.getParentElement(targetEl, Figure.__is) || targetEl;
401
+ async destroy(target) {
402
+ const targetEl = target || this._element;
403
+ const container = dom.query.getParentElement(targetEl, Figure.is) || targetEl;
327
404
  const focusEl = container.previousElementSibling || container.nextElementSibling;
328
405
  const emptyDiv = container.parentNode;
329
406
 
330
- const message = await this.triggerEvent('onImageDeleteBefore', { target: targetEl, container, align: this._align, alt: this.altText.value, url: this._linkValue });
407
+ const message = await this.triggerEvent('onImageDeleteBefore', { element: targetEl, container, align: this._align, alt: this.altText.value, url: this._linkValue });
331
408
  if (message === false) return;
332
409
 
333
- domUtils.removeItem(container);
410
+ dom.utils.removeItem(container);
334
411
  this.init();
335
412
 
336
413
  if (emptyDiv !== this.editor.frameContext.get('wysiwyg')) {
@@ -346,46 +423,61 @@ Image_.prototype = {
346
423
  // focus
347
424
  this.editor.focusEdge(focusEl);
348
425
  this.history.push(false);
349
- },
426
+ }
350
427
 
428
+ /**
429
+ * @private
430
+ * @description Retrieves the current image information.
431
+ * @returns {*} - The image data.
432
+ */
351
433
  _getInfo() {
352
434
  return {
353
435
  element: this._element,
354
436
  anchor: this.anchor.create(true),
355
- inputWidth: this.inputX.value,
356
- inputHeight: this.inputY.value,
437
+ inputWidth: this.inputX?.value || '',
438
+ inputHeight: this.inputY?.value || '',
357
439
  align: this._align,
358
440
  isUpdate: this.modal.isUpdate,
359
441
  alt: this.altText.value
360
442
  };
361
- },
443
+ }
362
444
 
445
+ /**
446
+ * @private
447
+ * @description Toggles between block and inline image format.
448
+ * @param {boolean} isInline - Whether the image should be inline.
449
+ */
363
450
  _activeAsInline(isInline) {
364
- const ctrlAlignBtn = this.figure.controller.form.querySelector('[data-command="onalign"]');
365
-
366
451
  if (isInline) {
367
- domUtils.addClass(this.asInline, 'on');
368
- domUtils.removeClass(this.asBlock, 'on');
452
+ dom.utils.addClass(this.asInline, 'on');
453
+ dom.utils.removeClass(this.asBlock, 'on');
369
454
  this.as = 'inline';
370
455
  // buttns
371
456
  if (this.alignForm) this.alignForm.style.display = 'none';
372
- if (ctrlAlignBtn) ctrlAlignBtn.style.display = 'none';
457
+ // caption
458
+ if (this.captionEl) this.captionEl.style.display = 'none';
373
459
  } else {
374
- domUtils.addClass(this.asBlock, 'on');
375
- domUtils.removeClass(this.asInline, 'on');
460
+ dom.utils.addClass(this.asBlock, 'on');
461
+ dom.utils.removeClass(this.asInline, 'on');
376
462
  this.as = 'block';
377
463
  // buttns
378
464
  if (this.alignForm) this.alignForm.style.display = '';
379
- if (ctrlAlignBtn) ctrlAlignBtn.style.display = '';
465
+ // caption
466
+ if (this.captionEl) this.captionEl.style.display = '';
380
467
  }
381
- },
468
+ }
382
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
+ */
383
475
  async submitFile(fileList) {
384
476
  if (fileList.length === 0) return false;
385
477
 
386
478
  let fileSize = 0;
387
479
  const files = [];
388
- const slngleSizeLimit = this.uploadSingleSizeLimit;
480
+ const slngleSizeLimit = this.pluginOptions.uploadSingleSizeLimit;
389
481
  for (let i = 0, len = fileList.length, f, s; i < len; i++) {
390
482
  f = fileList[i];
391
483
  if (!/image/i.test(f.type)) continue;
@@ -400,7 +492,7 @@ Image_.prototype = {
400
492
  file: f
401
493
  });
402
494
 
403
- this.notice.open(message === NO_EVENT ? err : message || err);
495
+ this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
404
496
 
405
497
  return false;
406
498
  }
@@ -420,7 +512,7 @@ Image_.prototype = {
420
512
  uploadSize: fileSize
421
513
  });
422
514
 
423
- this.notice.open(message === NO_EVENT ? err : message || err);
515
+ this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
424
516
 
425
517
  return false;
426
518
  }
@@ -430,9 +522,11 @@ Image_.prototype = {
430
522
  infos = newInfos || infos;
431
523
  this._serverUpload(infos, infos.files);
432
524
  }.bind(this, imgInfo);
525
+ // se-ts-ignore
526
+ this._serverUpload;
433
527
 
434
528
  const result = await this.triggerEvent('onImageUploadBefore', {
435
- ...imgInfo,
529
+ info: imgInfo,
436
530
  handler
437
531
  });
438
532
 
@@ -441,8 +535,13 @@ Image_.prototype = {
441
535
  if (result !== null && typeof result === 'object') handler(result);
442
536
 
443
537
  if (result === true || result === NO_EVENT) handler(null);
444
- },
538
+ }
445
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
+ */
446
545
  async submitURL(url) {
447
546
  if (!url) url = this._linkValue;
448
547
  if (!url) return false;
@@ -458,11 +557,11 @@ Image_.prototype = {
458
557
  infos = newInfos || infos;
459
558
  const infoUrl = infos.url;
460
559
  if (this.modal.isUpdate) this._updateSrc(infoUrl, infos.element, infos.files);
461
- else this.create(infoUrl, infos.anchor, infos.inputWidth, infos.inputHeight, infos.align, infos.files, infos.alt);
560
+ else this._produce(infoUrl, infos.anchor, infos.inputWidth, infos.inputHeight, infos.align, infos.files, infos.alt);
462
561
  }.bind(this, imgInfo);
463
562
 
464
563
  const result = await this.triggerEvent('onImageUploadBefore', {
465
- ...imgInfo,
564
+ info: imgInfo,
466
565
  handler
467
566
  });
468
567
 
@@ -473,11 +572,17 @@ Image_.prototype = {
473
572
  if (result === true || result === NO_EVENT) handler(null);
474
573
 
475
574
  return true;
476
- },
575
+ }
477
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
+ */
478
583
  _update(width, height) {
479
- if (!width) width = this.inputX.value || 'auto';
480
- if (!height) height = this.inputY.value || 'auto';
584
+ if (!width) width = this.inputX?.value || 'auto';
585
+ if (!height) height = this.inputY?.value || 'auto';
481
586
 
482
587
  let imageEl = this._element;
483
588
  const cover = this._cover;
@@ -505,7 +610,7 @@ Image_.prototype = {
505
610
  }
506
611
  } else {
507
612
  if (this._caption) {
508
- domUtils.removeItem(this._caption);
613
+ dom.utils.removeItem(this._caption);
509
614
  this._caption = null;
510
615
  modifiedCaption = true;
511
616
  }
@@ -531,11 +636,11 @@ Image_.prototype = {
531
636
 
532
637
  // size
533
638
  if (this._resizing && changeSize) {
534
- this.applySize(width, height);
639
+ this._applySize(width, height);
535
640
  }
536
641
 
537
642
  if (isNewAnchor) {
538
- domUtils.removeItem(anchor);
643
+ dom.utils.removeItem(anchor);
539
644
  }
540
645
 
541
646
  // transform
@@ -556,23 +661,34 @@ Image_.prototype = {
556
661
  imageEl.onload = () => {
557
662
  this.select(imageEl);
558
663
  };
559
- },
664
+ }
560
665
 
666
+ /**
667
+ * @private
668
+ * @description Validates the image size and applies necessary transformations.
669
+ * @param {string} width - The width of the image.
670
+ * @param {string} height - The height of the image.
671
+ */
561
672
  _fileCheck(width, height) {
562
- if (!width) width = this.inputX.value || 'auto';
563
- if (!height) height = this.inputY.value || 'auto';
673
+ if (!width) width = this.inputX?.value || 'auto';
674
+ if (!height) height = this.inputY?.value || 'auto';
564
675
 
565
676
  let imageEl = this._element;
566
677
  let cover = this._cover;
678
+ let inlineCover = null;
567
679
  let container = this._container === this._cover ? null : this._container;
568
680
  let isNewContainer = false;
569
681
 
570
682
  if (!cover || !container) {
571
683
  isNewContainer = true;
572
684
  imageEl = this._element.cloneNode(true);
573
- const figureInfo = Figure.CreateContainer(imageEl, 'se-image-container');
685
+ const figureInfo =
686
+ this.pluginOptions.useFormatType && width !== 'auto' && (/^span$/i.test(this._element.parentElement?.nodeName) || this.format.isLine(this._element.parentElement))
687
+ ? Figure.CreateInlineContainer(imageEl, 'se-image-container')
688
+ : Figure.CreateContainer(imageEl, 'se-image-container');
574
689
  cover = figureInfo.cover;
575
690
  container = figureInfo.container;
691
+ inlineCover = figureInfo.inlineCover;
576
692
  this.figure.open(imageEl, { nonResizing: true, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
577
693
  }
578
694
 
@@ -591,16 +707,18 @@ Image_.prototype = {
591
707
 
592
708
  // caption
593
709
  let modifiedCaption = false;
594
- if (this.captionCheckEl.checked) {
595
- if (!this._caption || isNewContainer) {
596
- this._caption = Figure.CreateCaption(cover, this.lang.caption);
597
- modifiedCaption = true;
598
- }
599
- } else {
600
- if (this._caption) {
601
- domUtils.removeItem(this._caption);
602
- this._caption = null;
603
- modifiedCaption = true;
710
+ if (!inlineCover) {
711
+ if (this.captionCheckEl.checked) {
712
+ if (!this._caption || isNewContainer) {
713
+ this._caption = Figure.CreateCaption(cover, this.lang.caption);
714
+ modifiedCaption = true;
715
+ }
716
+ } else {
717
+ if (this._caption) {
718
+ dom.utils.removeItem(this._caption);
719
+ this._caption = null;
720
+ modifiedCaption = true;
721
+ }
604
722
  }
605
723
  }
606
724
 
@@ -624,7 +742,7 @@ Image_.prototype = {
624
742
 
625
743
  if (isNewContainer) {
626
744
  imageEl = this._element;
627
- this.figure._retainFigureFormat(container, this._element, isNewAnchor ? anchor : null);
745
+ this.figure.retainFigureFormat(container, this._element, isNewAnchor ? anchor : null, this.fileManager);
628
746
  this._element = imageEl = container.querySelector('img');
629
747
  this._cover = cover;
630
748
  this._container = container;
@@ -632,16 +750,16 @@ Image_.prototype = {
632
750
 
633
751
  // size
634
752
  if (this._resizing && changeSize) {
635
- this.applySize(width, height);
753
+ this._applySize(width, height);
636
754
  }
637
755
 
638
756
  if (isNewAnchor) {
639
757
  if (!isNewContainer) {
640
- domUtils.removeItem(anchor);
758
+ dom.utils.removeItem(anchor);
641
759
  } else {
642
- domUtils.removeItem(isNewAnchor);
643
- if (domUtils.getListChildren(anchor, (current) => /IMG/i.test(current.tagName)).length === 0) {
644
- domUtils.removeItem(anchor);
760
+ dom.utils.removeItem(isNewAnchor);
761
+ if (dom.query.getListChildren(anchor, (current) => /IMG/i.test(current.tagName)).length === 0) {
762
+ dom.utils.removeItem(anchor);
645
763
  }
646
764
  }
647
765
  }
@@ -659,11 +777,16 @@ Image_.prototype = {
659
777
 
660
778
  // align
661
779
  this.figure.setAlign(imageEl, this._align);
662
- },
780
+ }
663
781
 
664
- _openTab(e) {
782
+ /**
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) {
665
788
  const modalForm = this.modal.form;
666
- const targetElement = e === 'init' ? modalForm.querySelector('._se_tab_link') : e.target;
789
+ const targetElement = typeof e === 'string' ? modalForm.querySelector('._se_tab_link') : dom.query.getEventTarget(e);
667
790
 
668
791
  if (!/^BUTTON$/i.test(targetElement.tagName)) {
669
792
  return false;
@@ -674,7 +797,7 @@ Image_.prototype = {
674
797
  let i;
675
798
 
676
799
  // Get all elements with class="tabcontent" and hide them
677
- const tabContent = modalForm.getElementsByClassName('_se_tab_content');
800
+ const tabContent = /** @type {HTMLCollectionOf<HTMLElement>}*/ (modalForm.getElementsByClassName('_se_tab_content'));
678
801
  for (i = 0; i < tabContent.length; i++) {
679
802
  tabContent[i].style.display = 'none';
680
803
  }
@@ -682,12 +805,12 @@ Image_.prototype = {
682
805
  // Get all elements with class="tablinks" and remove the class "active"
683
806
  const tabLinks = modalForm.getElementsByClassName('_se_tab_link');
684
807
  for (i = 0; i < tabLinks.length; i++) {
685
- domUtils.removeClass(tabLinks[i], 'active');
808
+ dom.utils.removeClass(tabLinks[i], 'active');
686
809
  }
687
810
 
688
811
  // Show the current tab, and add an "active" class to the button that opened the tab
689
- modalForm.querySelector('._se_tab_content_' + tabName).style.display = 'block';
690
- domUtils.addClass(targetElement, 'active');
812
+ /** @type {HTMLElement}*/ (modalForm.querySelector('._se_tab_content_' + tabName)).style.display = 'block';
813
+ dom.utils.addClass(targetElement, 'active');
691
814
 
692
815
  // focus
693
816
  if (e !== 'init') {
@@ -699,20 +822,57 @@ Image_.prototype = {
699
822
  }
700
823
 
701
824
  return false;
702
- },
825
+ }
703
826
 
704
- applySize(w, h) {
705
- if (!w) w = this.inputX.value || this.pluginOptions.defaultWidth;
706
- if (!h) h = this.inputY.value || this.pluginOptions.defaultHeight;
827
+ /**
828
+ * @private
829
+ * @description Creates a new image component based on provided parameters.
830
+ * @param {string} src - The image source URL.
831
+ * @param {?Node} anchor - Optional anchor wrapping the image.
832
+ * @param {string} width - Image width.
833
+ * @param {string} height - Image height.
834
+ * @param {string} align - Image alignment.
835
+ * @param {{name: string, size: number}} file - File metadata.
836
+ * @param {string} alt - Alternative text.
837
+ */
838
+ _produce(src, anchor, width, height, align, file, alt) {
839
+ if (this.as !== 'inline') {
840
+ this.create(src, anchor, width, height, align, file, alt);
841
+ } else {
842
+ this.createInline(src, anchor, width, height, file, alt);
843
+ }
844
+ }
845
+
846
+ /**
847
+ * @private
848
+ * @description Applies the specified width and height to the image.
849
+ * @param {string} w - Image width.
850
+ * @param {string} h - Image height.
851
+ */
852
+ _applySize(w, h) {
853
+ if (!w) w = this.inputX?.value || this.pluginOptions.defaultWidth;
854
+ if (!h) h = this.inputY?.value || this.pluginOptions.defaultHeight;
707
855
  if (this._onlyPercentage) {
708
856
  if (!w) w = '100%';
709
857
  else if (/%$/.test(w)) w += '%';
710
858
  }
711
- this.figure.setSize(w, h, null);
712
- },
859
+ this.figure.setSize(w, h);
860
+ }
713
861
 
862
+ /**
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
+ */
714
873
  create(src, anchor, width, height, align, file, alt) {
715
- const oImg = domUtils.createElement('IMG');
874
+ /** @type {HTMLImageElement} */
875
+ const oImg = dom.utils.createElement('IMG');
716
876
  oImg.src = src;
717
877
  oImg.alt = alt;
718
878
  anchor = this._setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
@@ -732,23 +892,69 @@ Image_.prototype = {
732
892
  this.figure.open(oImg, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
733
893
 
734
894
  // set size
735
- this.applySize(width, height);
895
+ this._applySize(width, height);
736
896
 
737
897
  // align
738
898
  this.figure.setAlign(oImg, align);
739
899
 
740
900
  this.fileManager.setFileData(oImg, file);
741
901
 
742
- oImg.onload = OnloadImg.bind(this, oImg, this._svgDefaultSize, container);
743
- this.component.insert(container, false, true);
744
- },
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
+ }
745
938
 
939
+ /**
940
+ * @private
941
+ * @description Updates the image source URL.
942
+ * @param {string} src - The new image source.
943
+ * @param {HTMLImageElement} element - The image element.
944
+ * @param {{ name: string, size: number }} file - File metadata.
945
+ */
746
946
  _updateSrc(src, element, file) {
747
947
  element.src = src;
748
948
  this.fileManager.setFileData(element, file);
749
- this.component.select(element, Image_.key, false);
750
- },
949
+ this.component.select(element, Image_.key);
950
+ }
751
951
 
952
+ /**
953
+ * @private
954
+ * @description Registers the uploaded image and inserts it into the editor.
955
+ * @param {ImageInfo_image} info - Image info.
956
+ * @param {Object<string, *>} response - Server response data.
957
+ */
752
958
  _register(info, response) {
753
959
  const fileList = response.result;
754
960
 
@@ -761,37 +967,57 @@ Image_.prototype = {
761
967
  this._updateSrc(fileList[i].url, info.element, file);
762
968
  break;
763
969
  } else {
764
- this.create(fileList[i].url, info.anchor, info.inputWidth, info.inputHeight, info.align, file, info.alt);
970
+ this._produce(fileList[i].url, info.anchor, info.inputWidth, info.inputHeight, info.align, file, info.alt);
765
971
  }
766
972
  }
767
- },
973
+ }
768
974
 
975
+ /**
976
+ * @private
977
+ * @description Uploads the image to the server.
978
+ * @param {ImageInfo_image} info - Image upload info.
979
+ * @param {FileList} files - List of image files.
980
+ */
769
981
  _serverUpload(info, files) {
770
982
  if (!files) return;
771
983
 
772
984
  // server upload
773
985
  const imageUploadUrl = this.pluginOptions.uploadUrl;
774
986
  if (typeof imageUploadUrl === 'string' && imageUploadUrl.length > 0) {
775
- this.fileManager.upload(imageUploadUrl, this.pluginOptions.uploadHeaders, files, UploadCallBack.bind(this, info), this._error.bind(this));
987
+ this.fileManager.upload(imageUploadUrl, this.pluginOptions.uploadHeaders, files, this.#UploadCallBack.bind(this, info), this._error.bind(this));
776
988
  } else {
777
989
  this._setBase64(files, info.anchor, info.inputWidth, info.inputHeight, info.align, info.alt, info.isUpdate);
778
990
  }
779
- },
991
+ }
780
992
 
993
+ /**
994
+ * @private
995
+ * @description Converts an image file to Base64 and inserts it into the editor.
996
+ * @param {FileList|File[]} files - List of image files.
997
+ * @param {?Node} anchor - Optional anchor wrapping the image.
998
+ * @param {string} width - Image width.
999
+ * @param {string} height - Image height.
1000
+ * @param {string} align - Image alignment.
1001
+ * @param {string} alt - Alternative text.
1002
+ * @param {boolean} isUpdate - Whether the image is being updated.
1003
+ */
781
1004
  _setBase64(files, anchor, width, height, align, alt, isUpdate) {
782
1005
  try {
783
1006
  const filesLen = this.modal.isUpdate ? 1 : files.length;
784
1007
 
785
1008
  if (filesLen === 0) {
786
- this.editor.hideLoading();
1009
+ this.ui.hideLoading();
787
1010
  console.warn('[SUNEDITOR.image.base64.fail] cause : No applicable files');
788
1011
  return;
789
1012
  }
790
1013
 
791
1014
  this._base64RenderIndex = filesLen;
792
- const filesStack = [filesLen];
793
- this.inputX.value = width;
794
- this.inputY.value = height;
1015
+ const filesStack = new Array(filesLen);
1016
+
1017
+ if (this._resizing) {
1018
+ this.inputX.value = width;
1019
+ this.inputY.value = height;
1020
+ }
795
1021
 
796
1022
  for (let i = 0, reader, file; i < filesLen; i++) {
797
1023
  reader = new FileReader();
@@ -805,28 +1031,51 @@ Image_.prototype = {
805
1031
 
806
1032
  if (--this._base64RenderIndex === 0) {
807
1033
  this._onRenderBase64(update, filesStack, updateElement, anchor, width, height, align, alt);
808
- this.editor.hideLoading();
1034
+ this.ui.hideLoading();
809
1035
  }
810
1036
  }.bind(this, reader, isUpdate, this._element, file, i);
1037
+ // se-ts-ignore
1038
+ this._onRenderBase64;
811
1039
 
812
1040
  reader.readAsDataURL(file);
813
1041
  }
814
1042
  } catch (error) {
815
- this.editor.hideLoading();
1043
+ this.ui.hideLoading();
816
1044
  throw Error(`[SUNEDITOR.plugins.image._setBase64.fail] ${error.message}`);
817
1045
  }
818
- },
1046
+ }
819
1047
 
1048
+ /**
1049
+ * @private
1050
+ * @description Inserts an image using a Base64-encoded string.
1051
+ * @param {boolean} update - Whether the image is being updated.
1052
+ * @param {Array<{result: string, file: { name: string, size: number }}>} filesStack - Stack of Base64-encoded files.
1053
+ * - result: Image url or Base64-encoded string
1054
+ * - file: File metadata ({ name: string, size: number })
1055
+ * @param {HTMLImageElement} updateElement - The image element being updated.
1056
+ * @param {?HTMLAnchorElement} anchor - Optional anchor wrapping the image.
1057
+ * @param {string} width - Image width.
1058
+ * @param {string} height - Image height.
1059
+ * @param {string} align - Image alignment.
1060
+ * @param {string} alt - Alternative text.
1061
+ */
820
1062
  _onRenderBase64(update, filesStack, updateElement, anchor, width, height, align, alt) {
821
1063
  for (let i = 0, len = filesStack.length; i < len; i++) {
822
1064
  if (update) {
823
1065
  this._updateSrc(filesStack[i].result, updateElement, filesStack[i].file);
824
1066
  } else {
825
- this.create(filesStack[i].result, anchor, width, height, align, filesStack[i].file, alt);
1067
+ this._produce(filesStack[i].result, anchor, width, height, align, filesStack[i].file, alt);
826
1068
  }
827
1069
  }
828
- },
1070
+ }
829
1071
 
1072
+ /**
1073
+ * @private
1074
+ * @description Wraps an image element with an anchor if provided.
1075
+ * @param {Node} imgTag - The image element to be wrapped.
1076
+ * @param {?Node} anchor - The anchor element to wrap around the image. If null, returns the image itself.
1077
+ * @returns {Node} - The wrapped image inside the anchor or the original image element.
1078
+ */
830
1079
  _setAnchor(imgTag, anchor) {
831
1080
  if (anchor) {
832
1081
  anchor.appendChild(imgTag);
@@ -834,129 +1083,172 @@ Image_.prototype = {
834
1083
  }
835
1084
 
836
1085
  return imgTag;
837
- },
1086
+ }
838
1087
 
1088
+ /**
1089
+ * @private
1090
+ * @description Handles errors during image upload and displays appropriate messages.
1091
+ * @param {Object<string, *>} response - The error response from the server.
1092
+ * @returns {Promise<void>}
1093
+ */
839
1094
  async _error(response) {
840
1095
  const message = await this.triggerEvent('onImageUploadError', { error: response });
841
1096
  const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
842
- this.notice.open(err);
1097
+ this.ui.alertOpen(err, 'error');
843
1098
  console.error('[SUNEDITOR.plugin.image.error]', err);
844
- },
845
-
846
- constructor: Image_
847
- };
1099
+ }
848
1100
 
849
- async function UploadCallBack(info, xmlHttp) {
850
- if ((await this.triggerEvent('imageUploadHandler', { xmlHttp, info })) === NO_EVENT) {
851
- const response = JSON.parse(xmlHttp.responseText);
852
- if (response.errorMessage) {
853
- this._error(response);
854
- } else {
855
- this._register(info, response);
1101
+ /**
1102
+ * @description Handles the callback function for image upload completion.
1103
+ * @param {ImageInfo_image} info - Image information.
1104
+ * @param {XMLHttpRequest} xmlHttp - The XMLHttpRequest object.
1105
+ */
1106
+ async #UploadCallBack(info, xmlHttp) {
1107
+ if ((await this.triggerEvent('imageUploadHandler', { xmlHttp, info })) === NO_EVENT) {
1108
+ const response = JSON.parse(xmlHttp.responseText);
1109
+ if (response.errorMessage) {
1110
+ this._error(response);
1111
+ } else {
1112
+ this._register(info, response);
1113
+ }
856
1114
  }
857
1115
  }
858
- }
859
1116
 
860
- function RemoveSelectedFiles() {
861
- this.imgInputFile.value = '';
862
- if (this.imgUrlFile) {
863
- this.imgUrlFile.removeAttribute('disabled');
864
- this.previewSrc.style.textDecoration = '';
1117
+ #RemoveSelectedFiles() {
1118
+ this.imgInputFile.value = '';
1119
+ if (this.imgUrlFile) {
1120
+ this.imgUrlFile.disabled = false;
1121
+ this.previewSrc.style.textDecoration = '';
1122
+ }
1123
+
1124
+ // inputFile check
1125
+ Modal.OnChangeFile(this.fileModalWrapper, []);
865
1126
  }
866
1127
 
867
- // inputFile check
868
- Modal.OnChangeFile(this.fileModalWrapper, []);
869
- }
1128
+ #OnInputSize(xy, e) {
1129
+ if (keyCodeMap.isSpace(e.code)) {
1130
+ e.preventDefault();
1131
+ return;
1132
+ }
1133
+
1134
+ if (xy === 'x' && this._onlyPercentage && e.target.value > 100) {
1135
+ e.target.value = 100;
1136
+ } else if (this.proportion.checked) {
1137
+ const ratioSize = Figure.CalcRatio(this.inputX.value, this.inputY.value, this.sizeUnit, this._ratio);
1138
+ if (xy === 'x') {
1139
+ this.inputY.value = String(ratioSize.h);
1140
+ } else {
1141
+ this.inputX.value = String(ratioSize.w);
1142
+ }
1143
+ }
1144
+ }
870
1145
 
871
- function OnInputSize(xy, e) {
872
- if (e.keyCode === 32) {
873
- e.preventDefault();
874
- return;
1146
+ #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
+ };
875
1153
  }
876
1154
 
877
- if (xy === 'x' && this._onlyPercentage && e.target.value > 100) {
878
- e.target.value = 100;
879
- } else if (this.proportion.checked) {
880
- const ratioSize = Figure.CalcRatio(this.inputX.value, this.inputY.value, this.sizeUnit, this._ratio);
881
- if (xy === 'x') {
882
- this.inputY.value = ratioSize.h;
1155
+ #OnClickRevert() {
1156
+ if (this._onlyPercentage) {
1157
+ this.inputX.value = Number(this._origin_w) > 100 ? '100' : this._origin_w;
883
1158
  } else {
884
- this.inputX.value = ratioSize.w;
1159
+ this.inputX.value = this._origin_w;
1160
+ this.inputY.value = this._origin_h;
885
1161
  }
886
1162
  }
887
- }
888
1163
 
889
- function OnChangeRatio() {
890
- this._ratio = this.proportion.checked
891
- ? Figure.GetRatio(this.inputX.value, this.inputY.value, this.sizeUnit)
892
- : {
893
- w: 1,
894
- h: 1
895
- };
896
- }
1164
+ #OnClickAsButton({ target }) {
1165
+ this._activeAsInline(target.getAttribute('data-command') === 'asInline');
1166
+ }
897
1167
 
898
- function OnClickRevert() {
899
- if (this._onlyPercentage) {
900
- this.inputX.value = this._origin_w > 100 ? 100 : this._origin_w;
901
- } else {
902
- this.inputX.value = this._origin_w;
903
- this.inputY.value = this._origin_h;
1168
+ #OnLinkPreview(e) {
1169
+ const value = e.target.value.trim();
1170
+ this._linkValue = this.previewSrc.textContent = !value
1171
+ ? ''
1172
+ : this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
1173
+ ? this.options.get('defaultUrlProtocol') + value
1174
+ : !value.includes('://')
1175
+ ? '/' + value
1176
+ : value;
904
1177
  }
905
- }
906
1178
 
907
- function OnClickAsButton({ target }) {
908
- this._activeAsInline(target.getAttribute('data-command') === 'asInline');
909
- }
1179
+ #OnfileInputChange({ target }) {
1180
+ if (!this.imgInputFile.value) {
1181
+ this.imgUrlFile.disabled = false;
1182
+ this.previewSrc.style.textDecoration = '';
1183
+ } else {
1184
+ this.imgUrlFile.disabled = true;
1185
+ this.previewSrc.style.textDecoration = 'line-through';
1186
+ }
910
1187
 
911
- function OnLinkPreview(e) {
912
- const value = e.target.value.trim();
913
- this._linkValue = this.previewSrc.textContent = !value
914
- ? ''
915
- : this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
916
- ? this.options.get('defaultUrlProtocol') + value
917
- : !value.includes('://')
918
- ? '/' + value
919
- : value;
920
- }
1188
+ // inputFile check
1189
+ Modal.OnChangeFile(this.fileModalWrapper, target.files);
1190
+ }
921
1191
 
922
- function OnfileInputChange({ target }) {
923
- if (!this.imgInputFile.value) {
924
- this.imgUrlFile.removeAttribute('disabled');
925
- this.previewSrc.style.textDecoration = '';
926
- } else {
927
- this.imgUrlFile.setAttribute('disabled', true);
928
- this.previewSrc.style.textDecoration = 'line-through';
1192
+ #OpenGallery() {
1193
+ this.plugins.imageGallery.open(this.#SetUrlInput.bind(this));
929
1194
  }
930
1195
 
931
- // inputFile check
932
- Modal.OnChangeFile(this.fileModalWrapper, target.files);
933
- }
1196
+ #SetUrlInput(target) {
1197
+ this.altText.value = target.getAttribute('data-value') || target.alt;
1198
+ this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.getAttribute('data-command') || target.src;
1199
+ this.imgUrlFile.focus();
1200
+ }
934
1201
 
935
- function OpenGallery() {
936
- this.plugins.imageGallery.open(_setUrlInput.bind(this));
937
- }
1202
+ #OnloadImg(oImg, _svgDefaultSize, container) {
1203
+ // 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
+ }
938
1220
 
939
- function _setUrlInput(target) {
940
- this.altText.value = target.getAttribute('data-value') || target.alt;
941
- this._linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.getAttribute('data-command') || target.src;
942
- this.imgUrlFile.focus();
943
- }
1221
+ this.editor._iframeAutoHeight(this.editor.frameContext);
1222
+ this.history.push(false);
944
1223
 
945
- function OnloadImg(oImg, _svgDefaultSize, container) {
946
- // svg exception handling
947
- if (oImg.offsetWidth === 0) this.applySize(_svgDefaultSize, '');
948
- if (this.options.get('componentAutoSelect')) {
949
- this.component.select(oImg, Image_.key, false);
950
- } else {
951
- const line = this.format.addLine(container, null);
952
- if (line) this.selection.setRange(line, 0, line, 0);
1224
+ delete oImg.onload;
953
1225
  }
954
-
955
- this.editor._iframeAutoHeight(this.editor.frameContext);
956
-
957
- delete oImg.onload;
958
1226
  }
959
1227
 
1228
+ /**
1229
+ * @typedef {Object} ModalReturns_image
1230
+ * @property {HTMLElement} html
1231
+ * @property {HTMLElement} alignForm
1232
+ * @property {HTMLElement} fileModalWrapper
1233
+ * @property {HTMLInputElement} imgInputFile
1234
+ * @property {HTMLInputElement} imgUrlFile
1235
+ * @property {HTMLInputElement} altText
1236
+ * @property {HTMLInputElement} captionCheckEl
1237
+ * @property {HTMLElement} previewSrc
1238
+ * @property {HTMLElement} tabs
1239
+ * @property {HTMLButtonElement} galleryButton
1240
+ * @property {HTMLInputElement} proportion
1241
+ * @property {HTMLInputElement} inputX
1242
+ * @property {HTMLInputElement} inputY
1243
+ * @property {HTMLButtonElement} revertBtn
1244
+ * @property {HTMLButtonElement} asBlock
1245
+ * @property {HTMLButtonElement} asInline
1246
+ * @property {HTMLButtonElement} fileRemoveBtn
1247
+ *
1248
+ * @param {__se__EditorCore} editor
1249
+ * @param {*} pluginOptions
1250
+ * @returns {ModalReturns_image}
1251
+ */
960
1252
  function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
961
1253
  const createFileInputHtml = !pluginOptions.createFileInput
962
1254
  ? ''
@@ -972,12 +1264,12 @@ function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
972
1264
  <div class="se-modal-form">
973
1265
  <label>${lang.image_modal_url}</label>
974
1266
  <div class="se-modal-form-files">
975
- <input class="se-input-form se-input-url _se_image_url" data-focus type="text" />
1267
+ <input class="se-input-form se-input-url" data-focus type="text" />
976
1268
  ${
977
1269
  plugins.imageGallery
978
1270
  ? `<button type="button" class="se-btn se-tooltip se-modal-files-edge-button __se__gallery" aria-label="${lang.imageGallery}">
979
1271
  ${icons.image_gallery}
980
- ${CreateTooltipInner(lang.imageGallery)}
1272
+ ${dom.utils.createTooltipInner(lang.imageGallery)}
981
1273
  </button>`
982
1274
  : ''
983
1275
  }
@@ -994,13 +1286,13 @@ function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
994
1286
  <label class="se-modal-size-x">&nbsp;</label>
995
1287
  <label class="size-h">${lang.height}</label>
996
1288
  </div>
997
- <input class="se-input-control _se_image_size_x" placeholder="auto" type="text" />
1289
+ <input class="se-input-control _se_size_x" placeholder="auto" type="text" />
998
1290
  <label class="se-modal-size-x">x</label>
999
- <input type="text" class="se-input-control _se_image_size_y" placeholder="auto" />
1000
- <label><input type="checkbox" class="se-modal-btn-check _se_image_check_proportion" checked/>&nbsp;${lang.proportion}</label>
1291
+ <input type="text" class="se-input-control _se_size_y" placeholder="auto" />
1292
+ <label><input type="checkbox" class="se-modal-btn-check _se_check_proportion" checked/>&nbsp;${lang.proportion}</label>
1001
1293
  <button type="button" aria-label="${lang.revert}" class="se-btn se-tooltip se-modal-btn-revert">
1002
1294
  ${icons.revert}
1003
- ${CreateTooltipInner(lang.revert)}
1295
+ ${dom.utils.createTooltipInner(lang.revert)}
1004
1296
  </button>
1005
1297
  </div>`;
1006
1298
 
@@ -1010,12 +1302,12 @@ function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
1010
1302
  <div class="se-modal-form">
1011
1303
  <div class="se-modal-flex-form">
1012
1304
  <button type="button" data-command="asBlock" class="se-btn se-tooltip" aria-label="${lang.inlineStyle}">
1013
- ${icons.component_outline}
1014
- ${CreateTooltipInner(lang.blockStyle)}
1305
+ ${icons.as_block}
1306
+ ${dom.utils.createTooltipInner(lang.blockStyle)}
1015
1307
  </button>
1016
1308
  <button type="button" data-command="asInline" class="se-btn se-tooltip" aria-label="${lang.inlineStyle}">
1017
- ${icons.component_inline}
1018
- ${CreateTooltipInner(lang.inlineStyle)}
1309
+ ${icons.as_inline}
1310
+ ${dom.utils.createTooltipInner(lang.inlineStyle)}
1019
1311
  </button>
1020
1312
  </div>
1021
1313
  </div>`;
@@ -1058,7 +1350,27 @@ function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
1058
1350
  </div>
1059
1351
  </form>`;
1060
1352
 
1061
- return domUtils.createElement('DIV', { class: 'se-modal-content' }, html);
1353
+ const content = dom.utils.createElement('DIV', { class: 'se-modal-content' }, html);
1354
+
1355
+ return {
1356
+ html: content,
1357
+ alignForm: content.querySelector('.se-figure-align'),
1358
+ fileModalWrapper: content.querySelector('.se-flex-input-wrapper'),
1359
+ imgInputFile: content.querySelector('.__se__file_input'),
1360
+ imgUrlFile: content.querySelector('.se-input-url'),
1361
+ altText: content.querySelector('._se_image_alt'),
1362
+ captionCheckEl: content.querySelector('._se_image_check_caption'),
1363
+ previewSrc: content.querySelector('._se_tab_content_image .se-link-preview'),
1364
+ tabs: content.querySelector('.se-modal-tabs'),
1365
+ galleryButton: content.querySelector('.__se__gallery'),
1366
+ proportion: content.querySelector('._se_check_proportion'),
1367
+ inputX: content.querySelector('._se_size_x'),
1368
+ inputY: content.querySelector('._se_size_y'),
1369
+ revertBtn: content.querySelector('.se-modal-btn-revert'),
1370
+ asBlock: content.querySelector('[data-command="asBlock"]'),
1371
+ asInline: content.querySelector('[data-command="asInline"]'),
1372
+ fileRemoveBtn: content.querySelector('.se-file-remove')
1373
+ };
1062
1374
  }
1063
1375
 
1064
1376
  export default Image_;