suneditor 3.0.0-alpha.2 → 3.0.0-alpha.20

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