suneditor 3.0.0-beta.9 → 3.0.0-rc.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 (380) hide show
  1. package/README.md +65 -57
  2. package/dist/suneditor-contents.min.css +1 -0
  3. package/dist/suneditor.min.css +1 -1
  4. package/dist/suneditor.min.js +1 -1
  5. package/package.json +110 -61
  6. package/src/assets/design/color.css +36 -17
  7. package/src/assets/design/size.css +2 -0
  8. package/src/assets/icons/defaultIcons.js +17 -2
  9. package/src/assets/suneditor-contents.css +51 -16
  10. package/src/assets/suneditor.css +116 -43
  11. package/src/core/config/contextProvider.js +288 -0
  12. package/src/core/config/eventManager.js +188 -0
  13. package/src/core/config/instanceCheck.js +59 -0
  14. package/src/core/config/optionProvider.js +452 -0
  15. package/src/core/editor.js +166 -1637
  16. package/src/core/event/actions/index.js +229 -0
  17. package/src/core/event/effects/common.registry.js +74 -0
  18. package/src/core/event/effects/keydown.registry.js +573 -0
  19. package/src/core/event/effects/ruleHelpers.js +148 -0
  20. package/src/core/event/eventOrchestrator.js +944 -0
  21. package/src/core/event/executor.js +27 -0
  22. package/src/core/{base/eventHandlers → event/handlers}/handler_toolbar.js +27 -28
  23. package/src/core/{base/eventHandlers → event/handlers}/handler_ww_clipboard.js +10 -8
  24. package/src/core/{base/eventHandlers → event/handlers}/handler_ww_dragDrop.js +22 -23
  25. package/src/core/event/handlers/handler_ww_input.js +75 -0
  26. package/src/core/event/handlers/handler_ww_key.js +228 -0
  27. package/src/core/event/handlers/handler_ww_mouse.js +166 -0
  28. package/src/core/event/ports.js +211 -0
  29. package/src/core/event/reducers/keydown.reducer.js +97 -0
  30. package/src/core/event/rules/keydown.rule.arrow.js +63 -0
  31. package/src/core/event/rules/keydown.rule.backspace.js +208 -0
  32. package/src/core/event/rules/keydown.rule.delete.js +132 -0
  33. package/src/core/event/rules/keydown.rule.enter.js +150 -0
  34. package/src/core/event/rules/keydown.rule.tab.js +35 -0
  35. package/src/core/event/support/defaultLineManager.js +136 -0
  36. package/src/core/event/support/selectionState.js +204 -0
  37. package/src/core/kernel/coreKernel.js +320 -0
  38. package/src/core/kernel/kernelInjector.js +19 -0
  39. package/src/core/kernel/store.js +173 -0
  40. package/src/core/{class → logic/dom}/char.js +42 -45
  41. package/src/core/logic/dom/format.js +1075 -0
  42. package/src/core/{class → logic/dom}/html.js +743 -624
  43. package/src/core/logic/dom/inline.js +1847 -0
  44. package/src/core/logic/dom/listFormat.js +601 -0
  45. package/src/core/{class → logic/dom}/nodeTransform.js +92 -72
  46. package/src/core/{class → logic/dom}/offset.js +254 -317
  47. package/src/core/logic/dom/selection.js +754 -0
  48. package/src/core/logic/panel/menu.js +389 -0
  49. package/src/core/logic/panel/toolbar.js +449 -0
  50. package/src/core/logic/panel/viewer.js +761 -0
  51. package/src/core/logic/shell/_commandExecutor.js +380 -0
  52. package/src/core/logic/shell/commandDispatcher.js +241 -0
  53. package/src/core/logic/shell/component.js +970 -0
  54. package/src/core/logic/shell/focusManager.js +110 -0
  55. package/src/core/{base → logic/shell}/history.js +110 -60
  56. package/src/core/logic/shell/pluginManager.js +363 -0
  57. package/src/core/logic/shell/shortcuts.js +130 -0
  58. package/src/core/logic/shell/ui.js +904 -0
  59. package/src/core/schema/context.js +66 -0
  60. package/src/core/schema/frameContext.js +160 -0
  61. package/src/core/schema/options.js +628 -0
  62. package/src/core/section/constructor.js +194 -500
  63. package/src/core/section/documentType.js +297 -222
  64. package/src/events.js +808 -543
  65. package/src/helper/clipboard.js +27 -16
  66. package/src/helper/converter.js +100 -78
  67. package/src/helper/dom/domCheck.js +56 -30
  68. package/src/helper/dom/domQuery.js +159 -89
  69. package/src/helper/dom/domUtils.js +114 -49
  70. package/src/helper/dom/index.js +5 -1
  71. package/src/helper/env.js +26 -26
  72. package/src/helper/index.js +1 -1
  73. package/src/helper/keyCodeMap.js +25 -28
  74. package/src/helper/numbers.js +4 -8
  75. package/src/helper/unicode.js +4 -8
  76. package/src/hooks/base.js +307 -0
  77. package/src/hooks/params.js +130 -0
  78. package/src/interfaces/contracts.js +227 -0
  79. package/src/interfaces/index.js +7 -0
  80. package/src/interfaces/plugins.js +239 -0
  81. package/src/langs/ckb.js +4 -4
  82. package/src/langs/cs.js +4 -4
  83. package/src/langs/da.js +4 -4
  84. package/src/langs/de.js +4 -4
  85. package/src/langs/en.js +4 -4
  86. package/src/langs/es.js +4 -4
  87. package/src/langs/fa.js +4 -4
  88. package/src/langs/fr.js +4 -4
  89. package/src/langs/he.js +4 -4
  90. package/src/langs/hu.js +4 -4
  91. package/src/langs/it.js +4 -4
  92. package/src/langs/ja.js +4 -4
  93. package/src/langs/km.js +4 -4
  94. package/src/langs/ko.js +4 -4
  95. package/src/langs/lv.js +4 -4
  96. package/src/langs/nl.js +4 -4
  97. package/src/langs/pl.js +4 -4
  98. package/src/langs/pt_br.js +13 -13
  99. package/src/langs/ro.js +4 -4
  100. package/src/langs/ru.js +4 -4
  101. package/src/langs/se.js +4 -4
  102. package/src/langs/tr.js +4 -4
  103. package/src/langs/uk.js +4 -4
  104. package/src/langs/ur.js +4 -4
  105. package/src/langs/zh_cn.js +4 -4
  106. package/src/modules/{Browser.js → contract/Browser.js} +119 -128
  107. package/src/modules/{ColorPicker.js → contract/ColorPicker.js} +132 -142
  108. package/src/modules/contract/Controller.js +589 -0
  109. package/src/modules/{Figure.js → contract/Figure.js} +591 -411
  110. package/src/modules/{HueSlider.js → contract/HueSlider.js} +125 -86
  111. package/src/modules/contract/Modal.js +357 -0
  112. package/src/modules/contract/index.js +9 -0
  113. package/src/modules/manager/ApiManager.js +197 -0
  114. package/src/modules/{FileManager.js → manager/FileManager.js} +128 -160
  115. package/src/modules/manager/index.js +5 -0
  116. package/src/modules/{ModalAnchorEditor.js → ui/ModalAnchorEditor.js} +108 -138
  117. package/src/modules/{SelectMenu.js → ui/SelectMenu.js} +119 -120
  118. package/src/modules/{_DragHandle.js → ui/_DragHandle.js} +1 -1
  119. package/src/modules/ui/index.js +6 -0
  120. package/src/plugins/browser/audioGallery.js +23 -26
  121. package/src/plugins/browser/fileBrowser.js +25 -28
  122. package/src/plugins/browser/fileGallery.js +20 -23
  123. package/src/plugins/browser/imageGallery.js +24 -23
  124. package/src/plugins/browser/videoGallery.js +27 -29
  125. package/src/plugins/command/blockquote.js +11 -17
  126. package/src/plugins/command/exportPDF.js +26 -26
  127. package/src/plugins/command/fileUpload.js +138 -133
  128. package/src/plugins/command/list_bulleted.js +48 -44
  129. package/src/plugins/command/list_numbered.js +48 -44
  130. package/src/plugins/dropdown/align.js +64 -50
  131. package/src/plugins/dropdown/backgroundColor.js +34 -35
  132. package/src/plugins/dropdown/{formatBlock.js → blockStyle.js} +43 -37
  133. package/src/plugins/dropdown/font.js +50 -36
  134. package/src/plugins/dropdown/fontColor.js +34 -35
  135. package/src/plugins/dropdown/hr.js +55 -50
  136. package/src/plugins/dropdown/layout.js +20 -15
  137. package/src/plugins/dropdown/lineHeight.js +46 -30
  138. package/src/plugins/dropdown/list.js +32 -33
  139. package/src/plugins/dropdown/paragraphStyle.js +40 -34
  140. package/src/plugins/dropdown/table/index.js +915 -0
  141. package/src/plugins/dropdown/table/render/table.html.js +308 -0
  142. package/src/plugins/dropdown/table/render/table.menu.js +121 -0
  143. package/src/plugins/dropdown/table/services/table.cell.js +465 -0
  144. package/src/plugins/dropdown/table/services/table.clipboard.js +414 -0
  145. package/src/plugins/dropdown/table/services/table.grid.js +504 -0
  146. package/src/plugins/dropdown/table/services/table.resize.js +463 -0
  147. package/src/plugins/dropdown/table/services/table.selection.js +466 -0
  148. package/src/plugins/dropdown/table/services/table.style.js +844 -0
  149. package/src/plugins/dropdown/table/shared/table.constants.js +109 -0
  150. package/src/plugins/dropdown/table/shared/table.utils.js +219 -0
  151. package/src/plugins/dropdown/template.js +20 -15
  152. package/src/plugins/dropdown/textStyle.js +28 -22
  153. package/src/plugins/field/mention.js +54 -49
  154. package/src/plugins/index.js +5 -5
  155. package/src/plugins/input/fontSize.js +100 -97
  156. package/src/plugins/input/pageNavigator.js +13 -10
  157. package/src/plugins/modal/audio.js +208 -219
  158. package/src/plugins/modal/drawing.js +99 -104
  159. package/src/plugins/modal/embed.js +323 -312
  160. package/src/plugins/modal/image/index.js +942 -0
  161. package/src/plugins/modal/image/render/image.html.js +150 -0
  162. package/src/plugins/modal/image/services/image.size.js +198 -0
  163. package/src/plugins/modal/image/services/image.upload.js +216 -0
  164. package/src/plugins/modal/image/shared/image.constants.js +20 -0
  165. package/src/plugins/modal/link.js +74 -54
  166. package/src/plugins/modal/math.js +126 -119
  167. package/src/plugins/modal/video/index.js +858 -0
  168. package/src/plugins/modal/video/render/video.html.js +131 -0
  169. package/src/plugins/modal/video/services/video.size.js +281 -0
  170. package/src/plugins/modal/video/services/video.upload.js +92 -0
  171. package/src/plugins/popup/anchor.js +57 -49
  172. package/src/suneditor.js +73 -61
  173. package/src/themes/cobalt.css +155 -0
  174. package/src/themes/dark.css +143 -120
  175. package/src/typedef.js +214 -63
  176. package/types/assets/icons/defaultIcons.d.ts +8 -0
  177. package/types/assets/suneditor-contents.css.d.ts +1 -0
  178. package/types/assets/suneditor.css.d.ts +1 -0
  179. package/types/core/config/contextProvider.d.ts +148 -0
  180. package/types/core/config/eventManager.d.ts +68 -0
  181. package/types/core/config/instanceCheck.d.ts +33 -0
  182. package/types/core/config/optionProvider.d.ts +147 -0
  183. package/types/core/editor.d.ts +27 -586
  184. package/types/core/event/actions/index.d.ts +50 -0
  185. package/types/core/event/effects/common.registry.d.ts +56 -0
  186. package/types/core/event/effects/keydown.registry.d.ts +80 -0
  187. package/types/core/event/effects/ruleHelpers.d.ts +36 -0
  188. package/types/core/event/eventOrchestrator.d.ts +191 -0
  189. package/types/core/event/executor.d.ts +13 -0
  190. package/types/core/event/handlers/handler_toolbar.d.ts +38 -0
  191. package/types/core/event/handlers/handler_ww_clipboard.d.ts +36 -0
  192. package/types/core/event/handlers/handler_ww_dragDrop.d.ts +26 -0
  193. package/types/core/event/handlers/handler_ww_input.d.ts +38 -0
  194. package/types/core/event/handlers/handler_ww_key.d.ts +40 -0
  195. package/types/core/event/handlers/handler_ww_mouse.d.ts +47 -0
  196. package/types/core/event/ports.d.ts +256 -0
  197. package/types/core/event/reducers/keydown.reducer.d.ts +84 -0
  198. package/types/core/event/rules/keydown.rule.arrow.d.ts +19 -0
  199. package/types/core/event/rules/keydown.rule.backspace.d.ts +18 -0
  200. package/types/core/event/rules/keydown.rule.delete.d.ts +18 -0
  201. package/types/core/event/rules/keydown.rule.enter.d.ts +18 -0
  202. package/types/core/event/rules/keydown.rule.tab.d.ts +18 -0
  203. package/types/core/event/support/defaultLineManager.d.ts +22 -0
  204. package/types/core/event/support/selectionState.d.ts +29 -0
  205. package/types/core/kernel/coreKernel.d.ts +219 -0
  206. package/types/core/kernel/kernelInjector.d.ts +16 -0
  207. package/types/core/kernel/store.d.ts +170 -0
  208. package/types/core/logic/dom/char.d.ts +46 -0
  209. package/types/core/logic/dom/format.d.ts +234 -0
  210. package/types/core/logic/dom/html.d.ts +290 -0
  211. package/types/core/logic/dom/inline.d.ts +93 -0
  212. package/types/core/logic/dom/listFormat.d.ts +101 -0
  213. package/types/core/logic/dom/nodeTransform.d.ts +110 -0
  214. package/types/core/logic/dom/offset.d.ts +335 -0
  215. package/types/core/logic/dom/selection.d.ts +165 -0
  216. package/types/core/logic/panel/menu.d.ts +93 -0
  217. package/types/core/logic/panel/toolbar.d.ts +128 -0
  218. package/types/core/logic/panel/viewer.d.ts +89 -0
  219. package/types/core/logic/shell/_commandExecutor.d.ts +18 -0
  220. package/types/core/logic/shell/commandDispatcher.d.ts +65 -0
  221. package/types/core/logic/shell/component.d.ts +182 -0
  222. package/types/core/logic/shell/focusManager.d.ts +31 -0
  223. package/types/core/{base → logic/shell}/history.d.ts +13 -12
  224. package/types/core/logic/shell/pluginManager.d.ts +115 -0
  225. package/types/core/logic/shell/shortcuts.d.ts +131 -0
  226. package/types/core/logic/shell/ui.d.ts +261 -0
  227. package/types/core/schema/context.d.ts +104 -0
  228. package/types/core/schema/frameContext.d.ts +320 -0
  229. package/types/core/schema/options.d.ts +1241 -0
  230. package/types/core/section/constructor.d.ts +117 -652
  231. package/types/core/section/documentType.d.ts +43 -61
  232. package/types/events.d.ts +796 -65
  233. package/types/helper/clipboard.d.ts +5 -4
  234. package/types/helper/converter.d.ts +55 -43
  235. package/types/helper/dom/domCheck.d.ts +27 -19
  236. package/types/helper/dom/domQuery.d.ts +76 -57
  237. package/types/helper/dom/domUtils.d.ts +62 -39
  238. package/types/helper/dom/index.d.ts +87 -1
  239. package/types/helper/env.d.ts +16 -13
  240. package/types/helper/index.d.ts +8 -2
  241. package/types/helper/keyCodeMap.d.ts +24 -23
  242. package/types/helper/numbers.d.ts +4 -6
  243. package/types/helper/unicode.d.ts +4 -3
  244. package/types/hooks/base.d.ts +239 -0
  245. package/types/hooks/params.d.ts +65 -0
  246. package/types/index.d.ts +20 -117
  247. package/types/interfaces/contracts.d.ts +183 -0
  248. package/types/interfaces/index.d.ts +3 -0
  249. package/types/interfaces/plugins.d.ts +168 -0
  250. package/types/langs/_Lang.d.ts +2 -2
  251. package/types/langs/index.d.ts +2 -2
  252. package/types/modules/contract/Browser.d.ts +262 -0
  253. package/types/modules/contract/ColorPicker.d.ts +99 -0
  254. package/types/modules/contract/Controller.d.ts +204 -0
  255. package/types/modules/contract/Figure.d.ts +529 -0
  256. package/types/modules/{HueSlider.d.ts → contract/HueSlider.d.ts} +39 -28
  257. package/types/modules/contract/Modal.d.ts +62 -0
  258. package/types/modules/contract/index.d.ts +7 -0
  259. package/types/modules/manager/ApiManager.d.ts +106 -0
  260. package/types/modules/manager/FileManager.d.ts +124 -0
  261. package/types/modules/manager/index.d.ts +3 -0
  262. package/types/modules/ui/ModalAnchorEditor.d.ts +152 -0
  263. package/types/modules/ui/SelectMenu.d.ts +107 -0
  264. package/types/modules/{_DragHandle.d.ts → ui/_DragHandle.d.ts} +1 -0
  265. package/types/modules/ui/index.d.ts +4 -0
  266. package/types/plugins/browser/audioGallery.d.ts +33 -41
  267. package/types/plugins/browser/fileBrowser.d.ts +42 -50
  268. package/types/plugins/browser/fileGallery.d.ts +33 -41
  269. package/types/plugins/browser/imageGallery.d.ts +30 -37
  270. package/types/plugins/browser/videoGallery.d.ts +33 -41
  271. package/types/plugins/command/blockquote.d.ts +4 -21
  272. package/types/plugins/command/exportPDF.d.ts +23 -33
  273. package/types/plugins/command/fileUpload.d.ts +80 -100
  274. package/types/plugins/command/list_bulleted.d.ts +9 -35
  275. package/types/plugins/command/list_numbered.d.ts +9 -35
  276. package/types/plugins/dropdown/align.d.ts +23 -46
  277. package/types/plugins/dropdown/backgroundColor.d.ts +35 -53
  278. package/types/plugins/dropdown/blockStyle.d.ts +45 -0
  279. package/types/plugins/dropdown/font.d.ts +18 -41
  280. package/types/plugins/dropdown/fontColor.d.ts +35 -53
  281. package/types/plugins/dropdown/hr.d.ts +26 -52
  282. package/types/plugins/dropdown/layout.d.ts +19 -25
  283. package/types/plugins/dropdown/lineHeight.d.ts +21 -39
  284. package/types/plugins/dropdown/list.d.ts +6 -34
  285. package/types/plugins/dropdown/paragraphStyle.d.ts +34 -45
  286. package/types/plugins/dropdown/table/index.d.ts +158 -0
  287. package/types/plugins/dropdown/table/render/table.html.d.ts +71 -0
  288. package/types/plugins/dropdown/table/render/table.menu.d.ts +59 -0
  289. package/types/plugins/dropdown/table/services/table.cell.d.ts +76 -0
  290. package/types/plugins/dropdown/table/services/table.clipboard.d.ts +26 -0
  291. package/types/plugins/dropdown/table/services/table.grid.d.ts +77 -0
  292. package/types/plugins/dropdown/table/services/table.resize.d.ts +72 -0
  293. package/types/plugins/dropdown/table/services/table.selection.d.ts +59 -0
  294. package/types/plugins/dropdown/table/services/table.style.d.ts +162 -0
  295. package/types/plugins/dropdown/table/shared/table.constants.d.ts +134 -0
  296. package/types/plugins/dropdown/table/shared/table.utils.d.ts +91 -0
  297. package/types/plugins/dropdown/template.d.ts +19 -25
  298. package/types/plugins/dropdown/textStyle.d.ts +23 -30
  299. package/types/plugins/field/mention.d.ts +66 -72
  300. package/types/plugins/index.d.ts +41 -40
  301. package/types/plugins/input/fontSize.d.ts +57 -96
  302. package/types/plugins/input/pageNavigator.d.ts +5 -8
  303. package/types/plugins/modal/audio.d.ts +60 -153
  304. package/types/plugins/modal/drawing.d.ts +16 -118
  305. package/types/plugins/modal/embed.d.ts +46 -166
  306. package/types/plugins/modal/image/index.d.ts +281 -0
  307. package/types/plugins/modal/image/render/image.html.d.ts +45 -0
  308. package/types/plugins/modal/image/services/image.size.d.ts +55 -0
  309. package/types/plugins/modal/image/services/image.upload.d.ts +24 -0
  310. package/types/plugins/modal/image/shared/image.constants.d.ts +17 -0
  311. package/types/plugins/modal/link.d.ts +46 -66
  312. package/types/plugins/modal/math.d.ts +17 -86
  313. package/types/plugins/modal/{video.d.ts → video/index.d.ts} +89 -221
  314. package/types/plugins/modal/video/render/video.html.d.ts +37 -0
  315. package/types/plugins/modal/video/services/video.size.d.ts +74 -0
  316. package/types/plugins/modal/video/services/video.upload.d.ts +19 -0
  317. package/types/plugins/popup/anchor.d.ts +8 -38
  318. package/types/suneditor.d.ts +55 -24
  319. package/types/typedef.d.ts +344 -228
  320. package/CONTRIBUTING.md +0 -186
  321. package/src/core/base/eventHandlers/handler_ww_key_input.js +0 -1200
  322. package/src/core/base/eventHandlers/handler_ww_mouse.js +0 -194
  323. package/src/core/base/eventManager.js +0 -1523
  324. package/src/core/class/component.js +0 -856
  325. package/src/core/class/format.js +0 -3433
  326. package/src/core/class/menu.js +0 -346
  327. package/src/core/class/selection.js +0 -610
  328. package/src/core/class/shortcuts.js +0 -98
  329. package/src/core/class/toolbar.js +0 -431
  330. package/src/core/class/ui.js +0 -424
  331. package/src/core/class/viewer.js +0 -750
  332. package/src/core/section/actives.js +0 -266
  333. package/src/core/section/context.js +0 -102
  334. package/src/editorInjector/_classes.js +0 -36
  335. package/src/editorInjector/_core.js +0 -87
  336. package/src/editorInjector/index.js +0 -73
  337. package/src/modules/ApiManager.js +0 -191
  338. package/src/modules/Controller.js +0 -474
  339. package/src/modules/Modal.js +0 -346
  340. package/src/modules/index.js +0 -14
  341. package/src/plugins/dropdown/table.js +0 -4034
  342. package/src/plugins/modal/image.js +0 -1376
  343. package/src/plugins/modal/video.js +0 -1226
  344. package/types/core/base/eventHandlers/handler_toolbar.d.ts +0 -41
  345. package/types/core/base/eventHandlers/handler_ww_clipboard.d.ts +0 -40
  346. package/types/core/base/eventHandlers/handler_ww_dragDrop.d.ts +0 -35
  347. package/types/core/base/eventHandlers/handler_ww_key_input.d.ts +0 -45
  348. package/types/core/base/eventHandlers/handler_ww_mouse.d.ts +0 -39
  349. package/types/core/base/eventManager.d.ts +0 -401
  350. package/types/core/class/char.d.ts +0 -61
  351. package/types/core/class/component.d.ts +0 -213
  352. package/types/core/class/format.d.ts +0 -623
  353. package/types/core/class/html.d.ts +0 -430
  354. package/types/core/class/menu.d.ts +0 -126
  355. package/types/core/class/nodeTransform.d.ts +0 -93
  356. package/types/core/class/offset.d.ts +0 -522
  357. package/types/core/class/selection.d.ts +0 -188
  358. package/types/core/class/shortcuts.d.ts +0 -142
  359. package/types/core/class/toolbar.d.ts +0 -189
  360. package/types/core/class/ui.d.ts +0 -164
  361. package/types/core/class/viewer.d.ts +0 -140
  362. package/types/core/section/actives.d.ts +0 -46
  363. package/types/core/section/context.d.ts +0 -45
  364. package/types/editorInjector/_classes.d.ts +0 -41
  365. package/types/editorInjector/_core.d.ts +0 -87
  366. package/types/editorInjector/index.d.ts +0 -69
  367. package/types/modules/ApiManager.d.ts +0 -125
  368. package/types/modules/Browser.d.ts +0 -326
  369. package/types/modules/ColorPicker.d.ts +0 -135
  370. package/types/modules/Controller.d.ts +0 -251
  371. package/types/modules/Figure.d.ts +0 -517
  372. package/types/modules/FileManager.d.ts +0 -202
  373. package/types/modules/Modal.d.ts +0 -111
  374. package/types/modules/ModalAnchorEditor.d.ts +0 -236
  375. package/types/modules/SelectMenu.d.ts +0 -194
  376. package/types/modules/index.d.ts +0 -26
  377. package/types/plugins/dropdown/formatBlock.d.ts +0 -55
  378. package/types/plugins/dropdown/table.d.ts +0 -627
  379. package/types/plugins/modal/image.d.ts +0 -451
  380. /package/{LICENSE → LICENSE.txt} +0 -0
@@ -0,0 +1,942 @@
1
+ import { PluginModal } from '../../../interfaces';
2
+ import { Modal, Figure } from '../../../modules/contract';
3
+ import { FileManager } from '../../../modules/manager';
4
+ import { ModalAnchorEditor } from '../../../modules/ui';
5
+ import { dom, numbers, env } from '../../../helper';
6
+
7
+ import { DEFAULT_ACCEPTED_FORMATS, DEFAULT_SVG_SIZE, FORMAT_TYPE, SIZE_UNIT } from './shared/image.constants';
8
+ import { CreateHTML_modal } from './render/image.html';
9
+ import ImageSizeService from './services/image.size';
10
+ import ImageUploadService from './services/image.upload';
11
+
12
+ const { NO_EVENT } = env;
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 {'block'|'inline'} [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 {SunEditor.Module.Figure.Controls} [controls] - Figure controls.
34
+ * @property {SunEditor.ComponentInsertType} [insertBehavior] - Component insertion behavior for selection and cursor placement. [default: `options.get('componentInsertBehavior')`]
35
+ * - For inline components: places the cursor near the inserted component or selects it if no nearby range is available.
36
+ * - For block components: executes behavior based on `selectMode`:
37
+ * - `auto`: Move cursor to the next line if possible, otherwise select the component.
38
+ * - `select`: Always select the inserted component.
39
+ * - `line`: Move cursor to the next line if possible, or create a new line and move there.
40
+ * - `none`: Do nothing.
41
+ */
42
+
43
+ /**
44
+ * @typedef {Object} ImageState
45
+ * @property {string} sizeUnit - Size unit (`'px'` or `'%'`)
46
+ * @property {boolean} onlyPercentage - Whether only percentage sizing is allowed
47
+ * @property {number} produceIndex - Image production index for batch operations
48
+ */
49
+
50
+ /**
51
+ * @class
52
+ * @description Image plugin.
53
+ * - This plugin provides image insertion functionality within the editor, supporting both file upload and URL input.
54
+ */
55
+ class Image_ extends PluginModal {
56
+ static key = 'image';
57
+ static className = '';
58
+
59
+ /**
60
+ * @param {Element} node - The node to check.
61
+ * @returns {Element|null} Returns a node if the node is a valid component.
62
+ */
63
+ static component(node) {
64
+ const compNode = dom.check.isFigure(node) || (/^span$/i.test(node.nodeName) && dom.check.isComponentContainer(node)) ? node.firstElementChild : node;
65
+ return /^IMG$/i.test(compNode?.nodeName) ? compNode : dom.check.isAnchor(compNode) && /^IMG$/i.test(compNode?.firstElementChild?.nodeName) ? compNode?.firstElementChild : null;
66
+ }
67
+
68
+ #resizing;
69
+ #nonResizing;
70
+
71
+ #linkElement = null;
72
+ #linkValue = '';
73
+ #align = 'none';
74
+ #svgDefaultSize = DEFAULT_SVG_SIZE;
75
+ #element = null;
76
+ #cover = null;
77
+ #container = null;
78
+ #caption = null;
79
+
80
+ /**
81
+ * @constructor
82
+ * @param {SunEditor.Kernel} editor - The core kernel
83
+ * @param {ImagePluginOptions} pluginOptions
84
+ */
85
+ constructor(editor, pluginOptions) {
86
+ // plugin basic properties
87
+ super(editor);
88
+ this.title = this.$.lang.image;
89
+ this.icon = 'image';
90
+
91
+ this.pluginOptions = {
92
+ canResize: pluginOptions.canResize === undefined ? true : pluginOptions.canResize,
93
+ showHeightInput: pluginOptions.showHeightInput === undefined ? true : !!pluginOptions.showHeightInput,
94
+ defaultWidth: !pluginOptions.defaultWidth ? 'auto' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + SIZE_UNIT.PIXEL : pluginOptions.defaultWidth,
95
+ defaultHeight: !pluginOptions.defaultHeight ? 'auto' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + SIZE_UNIT.PIXEL : pluginOptions.defaultHeight,
96
+ percentageOnlySize: !!pluginOptions.percentageOnlySize,
97
+ createFileInput: pluginOptions.createFileInput === undefined ? true : pluginOptions.createFileInput,
98
+ createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
99
+ uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
100
+ uploadHeaders: pluginOptions.uploadHeaders || null,
101
+ uploadSizeLimit: numbers.get(pluginOptions.uploadSizeLimit, 0),
102
+ uploadSingleSizeLimit: numbers.get(pluginOptions.uploadSingleSizeLimit, 0),
103
+ allowMultiple: !!pluginOptions.allowMultiple,
104
+ acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? DEFAULT_ACCEPTED_FORMATS : pluginOptions.acceptedFormats.trim() || DEFAULT_ACCEPTED_FORMATS,
105
+ useFormatType: pluginOptions.useFormatType ?? true,
106
+ defaultFormatType: [FORMAT_TYPE.BLOCK, FORMAT_TYPE.INLINE].includes(pluginOptions.defaultFormatType) ? pluginOptions.defaultFormatType : FORMAT_TYPE.BLOCK,
107
+ keepFormatType: pluginOptions.keepFormatType ?? false,
108
+ insertBehavior: pluginOptions.insertBehavior,
109
+ };
110
+
111
+ // create HTML
112
+ const sizeUnit = this.pluginOptions.percentageOnlySize ? SIZE_UNIT.PERCENTAGE : SIZE_UNIT.PIXEL;
113
+ const modalEl = CreateHTML_modal(this.$, this.pluginOptions);
114
+ const ctrlAs = this.pluginOptions.useFormatType ? 'as' : '';
115
+ const figureControls =
116
+ pluginOptions.controls ||
117
+ (!this.pluginOptions.canResize
118
+ ? [[ctrlAs, 'mirror_h', 'mirror_v', 'align', 'caption', 'edit', 'revert', 'copy', 'remove']]
119
+ : [
120
+ [ctrlAs, 'resize_auto,100,75,50', 'rotate_l', 'rotate_r', 'mirror_h', 'mirror_v'],
121
+ ['edit', 'align', 'caption', 'revert', 'copy', 'remove'],
122
+ ]);
123
+
124
+ // show align
125
+ this.alignForm = modalEl.alignForm;
126
+ if (!figureControls.some((subArray) => subArray.includes('align'))) this.alignForm.style.display = 'none';
127
+
128
+ // modules
129
+ const Link = this.$.plugins.link ? this.$.plugins.link.pluginOptions : {};
130
+ this.anchor = new ModalAnchorEditor(this.$, modalEl.html, {
131
+ ...Link,
132
+ textToDisplay: false,
133
+ title: true,
134
+ });
135
+
136
+ this.modal = new Modal(this, this.$, modalEl.html);
137
+
138
+ this.figure = new Figure(this, this.$, figureControls, {
139
+ sizeUnit: sizeUnit,
140
+ });
141
+
142
+ this.fileManager = new FileManager(this, this.$, {
143
+ query: 'img',
144
+ loadEventName: 'onImageLoad',
145
+ actionEventName: 'onImageAction',
146
+ });
147
+
148
+ // members
149
+ /** @type {ImageState} */
150
+ this.state = {
151
+ sizeUnit: sizeUnit,
152
+ onlyPercentage: this.pluginOptions.percentageOnlySize,
153
+ produceIndex: 0,
154
+ };
155
+
156
+ this.fileModalWrapper = modalEl.fileModalWrapper;
157
+ this.imgInputFile = modalEl.imgInputFile;
158
+ this.imgUrlFile = modalEl.imgUrlFile;
159
+ this.focusElement = this.imgInputFile || this.imgUrlFile;
160
+ this.altText = modalEl.altText;
161
+ this.captionCheckEl = modalEl.captionCheckEl;
162
+ this.captionEl = this.captionCheckEl?.parentElement;
163
+ this.previewSrc = modalEl.previewSrc;
164
+
165
+ this.as = FORMAT_TYPE.BLOCK;
166
+ this.#resizing = this.pluginOptions.canResize;
167
+ this.#nonResizing = !this.#resizing || !this.pluginOptions.showHeightInput || this.pluginOptions.percentageOnlySize;
168
+
169
+ this.sizeService = new ImageSizeService(this, modalEl);
170
+ this.uploadService = new ImageUploadService(this);
171
+
172
+ // init
173
+ this.$.eventManager.addEvent(modalEl.tabs, 'click', this.#OpenTab.bind(this));
174
+ if (this.imgInputFile) this.$.eventManager.addEvent(modalEl.fileRemoveBtn, 'click', this.#RemoveSelectedFiles.bind(this));
175
+ if (this.imgUrlFile) this.$.eventManager.addEvent(this.imgUrlFile, 'input', this.#OnLinkPreview.bind(this));
176
+ if (this.imgInputFile && this.imgUrlFile) this.$.eventManager.addEvent(this.imgInputFile, 'change', this.#OnfileInputChange.bind(this));
177
+
178
+ const galleryButton = modalEl.galleryButton;
179
+ if (galleryButton) this.$.eventManager.addEvent(galleryButton, 'click', this.#OpenGallery.bind(this));
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
+ }
187
+ }
188
+
189
+ /**
190
+ * @template {keyof ImageState} K
191
+ * @param {K} key
192
+ * @param {ImageState[K]} value
193
+ */
194
+ setState(key, value) {
195
+ this.state[key] = value;
196
+ }
197
+
198
+ /**
199
+ * @override
200
+ * @type {PluginModal['open']}
201
+ */
202
+ open() {
203
+ this.state.produceIndex = 0;
204
+ this.modal.open();
205
+ }
206
+
207
+ /**
208
+ * @hook Editor.Core
209
+ * @type {SunEditor.Hook.Core.RetainFormat}
210
+ */
211
+ retainFormat() {
212
+ return {
213
+ query: 'img',
214
+ /** @param {HTMLImageElement} element */
215
+ method: (element) => {
216
+ const figureInfo = Figure.GetContainer(element);
217
+ if (figureInfo && figureInfo.container && (figureInfo.cover || figureInfo.inlineCover)) return;
218
+
219
+ const { w, h } = this.#ready(element, true);
220
+ this.#fileCheck(w, h);
221
+ },
222
+ };
223
+ }
224
+
225
+ /**
226
+ * @hook Editor.EventManager
227
+ * @type {SunEditor.Hook.Event.OnFilePasteAndDrop}
228
+ */
229
+ onFilePasteAndDrop({ file }) {
230
+ if (!/^image/.test(file.type)) return;
231
+
232
+ this.submitFile([file]);
233
+ this.$.focusManager.focus();
234
+ }
235
+
236
+ /**
237
+ * @hook Modules.Modal
238
+ * @type {SunEditor.Hook.Modal.On}
239
+ */
240
+ modalOn(isUpdate) {
241
+ if (!isUpdate) {
242
+ this.sizeService.on();
243
+ if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.setAttribute('multiple', 'multiple');
244
+ } else {
245
+ if (this.imgInputFile && this.pluginOptions.allowMultiple) this.imgInputFile.removeAttribute('multiple');
246
+ }
247
+
248
+ this.anchor.on(isUpdate);
249
+ }
250
+
251
+ /**
252
+ * @hook Modules.Modal
253
+ * @type {SunEditor.Hook.Modal.Action}
254
+ */
255
+ async modalAction() {
256
+ this.#align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"]:checked')).value;
257
+
258
+ if (this.modal.isUpdate) {
259
+ this.#fixTagStructure();
260
+ this.$.history.push(false);
261
+ }
262
+
263
+ if (this.imgInputFile && this.imgInputFile.files.length > 0) {
264
+ return await this.submitFile(this.imgInputFile.files);
265
+ } else if (this.imgUrlFile && this.#linkValue.length > 0) {
266
+ return await this.submitURL(this.#linkValue);
267
+ }
268
+
269
+ return false;
270
+ }
271
+
272
+ /**
273
+ * @hook Modules.Modal
274
+ * @type {SunEditor.Hook.Modal.Init}
275
+ */
276
+ modalInit() {
277
+ Modal.OnChangeFile(this.fileModalWrapper, []);
278
+ if (this.imgInputFile) this.imgInputFile.value = '';
279
+ if (this.imgUrlFile) this.#linkValue = this.previewSrc.textContent = this.imgUrlFile.value = '';
280
+ if (this.imgInputFile && this.imgUrlFile) {
281
+ this.imgUrlFile.disabled = false;
282
+ this.previewSrc.style.textDecoration = '';
283
+ }
284
+
285
+ this.altText.value = '';
286
+ /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_image_radio"][value="none"]')).checked = true;
287
+ this.captionCheckEl.checked = false;
288
+ this.#element = null;
289
+ this.#OpenTab('init');
290
+
291
+ this.sizeService.init();
292
+
293
+ if (this.pluginOptions.useFormatType) {
294
+ this.#activeAsInline((this.pluginOptions.keepFormatType ? this.as : this.pluginOptions.defaultFormatType) === FORMAT_TYPE.INLINE);
295
+ }
296
+
297
+ this.anchor.init();
298
+ }
299
+
300
+ /**
301
+ * @hook Editor.Component
302
+ * @type {SunEditor.Hook.Component.Select}
303
+ */
304
+ componentSelect(target) {
305
+ this.#ready(target);
306
+ }
307
+
308
+ /**
309
+ * @hook Editor.Component
310
+ * @type {SunEditor.Hook.Component.Edit}
311
+ */
312
+ componentEdit() {
313
+ this.modal.open();
314
+ }
315
+
316
+ /**
317
+ * @hook Editor.Component
318
+ * @type {SunEditor.Hook.Component.Destroy}
319
+ */
320
+ async componentDestroy(target) {
321
+ const targetEl = target || this.#element;
322
+ const container = dom.query.getParentElement(targetEl, Figure.is) || targetEl;
323
+ const focusEl = container.previousElementSibling || container.nextElementSibling;
324
+ const emptyDiv = container.parentNode;
325
+
326
+ const message = await this.$.eventManager.triggerEvent('onImageDeleteBefore', { element: targetEl, container, align: this.#align, alt: this.altText.value, url: this.#linkValue });
327
+ if (message === false) return;
328
+
329
+ dom.utils.removeItem(container);
330
+ this.modalInit();
331
+
332
+ if (emptyDiv !== this.$.frameContext.get('wysiwyg')) {
333
+ this.$.nodeTransform.removeAllParents(
334
+ emptyDiv,
335
+ function (current) {
336
+ return current.childNodes.length === 0;
337
+ },
338
+ null,
339
+ );
340
+ }
341
+
342
+ // focus
343
+ this.$.focusManager.focusEdge(focusEl);
344
+ this.$.history.push(false);
345
+ }
346
+
347
+ /**
348
+ * @description Create an `image` component using the provided files.
349
+ * @param {FileList|File[]} fileList File object list
350
+ * @returns {Promise<boolean>} If return `false`, the file upload will be canceled
351
+ */
352
+ async submitFile(fileList) {
353
+ if (fileList.length === 0) return false;
354
+
355
+ let fileSize = 0;
356
+ const files = [];
357
+ const singleSizeLimit = this.pluginOptions.uploadSingleSizeLimit;
358
+ for (let i = 0, len = fileList.length, f, s; i < len; i++) {
359
+ f = fileList[i];
360
+ if (!/image/i.test(f.type)) continue;
361
+
362
+ s = f.size;
363
+ if (singleSizeLimit > 0 && s > singleSizeLimit) {
364
+ const err = '[SUNEDITOR.imageUpload.fail] Size of uploadable single file: ' + singleSizeLimit / 1000 + 'KB';
365
+ const message = await this.$.eventManager.triggerEvent('onImageUploadError', {
366
+ error: err,
367
+ limitSize: singleSizeLimit,
368
+ uploadSize: s,
369
+ file: f,
370
+ });
371
+
372
+ this.$.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
373
+
374
+ return false;
375
+ }
376
+
377
+ files.push(f);
378
+ fileSize += s;
379
+ }
380
+
381
+ const limitSize = this.pluginOptions.uploadSizeLimit;
382
+ const currentSize = this.fileManager.getSize();
383
+ if (limitSize > 0 && fileSize + currentSize > limitSize) {
384
+ const err = '[SUNEDITOR.imageUpload.fail] Size of uploadable total images: ' + limitSize / 1000 + 'KB';
385
+ const message = await this.$.eventManager.triggerEvent('onImageUploadError', {
386
+ error: err,
387
+ limitSize,
388
+ currentSize,
389
+ uploadSize: fileSize,
390
+ });
391
+
392
+ this.$.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
393
+
394
+ return false;
395
+ }
396
+
397
+ const imgInfo = { files, ...this.#getInfo() };
398
+ const handler = function (uploadCallback, infos, newInfos) {
399
+ infos = newInfos || infos;
400
+ uploadCallback(infos);
401
+ }.bind(this, this.uploadService.serverUpload.bind(this.uploadService), imgInfo);
402
+
403
+ const result = await this.$.eventManager.triggerEvent('onImageUploadBefore', {
404
+ info: imgInfo,
405
+ handler,
406
+ });
407
+
408
+ if (result === undefined) return true;
409
+ if (result === false) return false;
410
+ if (result !== null && typeof result === 'object') handler(result);
411
+
412
+ if (result === true || result === NO_EVENT) handler(null);
413
+ }
414
+
415
+ /**
416
+ * @description Create an `image` component using the provided url.
417
+ * @param {string} url File url
418
+ * @returns {Promise<boolean>} If return `false`, the file upload will be canceled
419
+ */
420
+ async submitURL(url) {
421
+ if (!(url ||= this.#linkValue)) return false;
422
+
423
+ const file = { name: url.split('/').pop(), size: 0 };
424
+ const imgInfo = {
425
+ url,
426
+ files: file,
427
+ ...this.#getInfo(),
428
+ };
429
+
430
+ const handler = function (uploadCallback, infos, newInfos) {
431
+ infos = newInfos || infos;
432
+ uploadCallback(infos);
433
+ }.bind(this, this.uploadService.urlUpload.bind(this.uploadService), imgInfo);
434
+
435
+ const result = await this.$.eventManager.triggerEvent('onImageUploadBefore', {
436
+ info: imgInfo,
437
+ handler,
438
+ });
439
+
440
+ if (result === undefined) return true;
441
+ if (result === false) return false;
442
+ if (result !== null && typeof result === 'object') handler(result);
443
+
444
+ if (result === true || result === NO_EVENT) handler(null);
445
+
446
+ return true;
447
+ }
448
+
449
+ /**
450
+ * @description Creates a new image component, wraps it in a figure container with an optional anchor,
451
+ * - applies size and alignment settings, and inserts it into the editor.
452
+ * @param {string} src - The URL of the image to be inserted.
453
+ * @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
454
+ * @param {string} width - The width value to be applied to the image.
455
+ * @param {string} height - The height value to be applied to the image.
456
+ * @param {string} align - The alignment setting for the image (e.g., 'left', 'center', 'right').
457
+ * @param {{name: string, size: number}} file - File metadata associated with the image
458
+ * @param {string} alt - The alternative text for the image.
459
+ * @param {boolean} isLast - Indicates whether this is the last file in the batch (used for scroll and insert actions).
460
+ */
461
+ create(src, anchor, width, height, align, file, alt, isLast) {
462
+ /** @type {HTMLImageElement} */
463
+ const oImg = dom.utils.createElement('IMG');
464
+ oImg.src = src;
465
+ oImg.alt = alt;
466
+ anchor = this.#setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
467
+
468
+ const figureInfo = Figure.CreateContainer(anchor, 'se-image-container');
469
+ const cover = figureInfo.cover;
470
+ const container = figureInfo.container;
471
+
472
+ // caption
473
+ if (this.captionCheckEl.checked) {
474
+ this.#caption = Figure.CreateCaption(cover, this.$.lang.caption);
475
+ }
476
+
477
+ this.#element = oImg;
478
+ this.#cover = cover;
479
+ this.#container = container;
480
+ this.figure.open(oImg, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
481
+
482
+ // set size
483
+ this.sizeService.applySize(width, height);
484
+
485
+ // align
486
+ this.figure.setAlign(oImg, align);
487
+
488
+ this.fileManager.setFileData(oImg, file);
489
+
490
+ this.setState('produceIndex', this.state.produceIndex + 1);
491
+ oImg.onload = this.#OnloadImg.bind(this, oImg, this.#svgDefaultSize, container);
492
+ this.$.component.insert(container, { scrollTo: isLast ? true : false, insertBehavior: isLast ? null : 'line' });
493
+ }
494
+
495
+ /**
496
+ * @description Creates a new inline image component, wraps it in an inline figure container with an optional anchor,
497
+ * - applies size settings, and inserts it into the editor.
498
+ * @param {string} src - The URL of the image to be inserted.
499
+ * @param {?Node} anchor - An optional anchor element to wrap the image. If provided, a clone is used.
500
+ * @param {string} width - The width value to be applied to the image.
501
+ * @param {string} height - The height value to be applied to the image.
502
+ * @param {{name: string, size: number}} file - File metadata associated with the image
503
+ * @param {string} alt - The alternative text for the image.
504
+ * @param {boolean} isLast - Indicates whether this is the last file in the batch (used for scroll and insert actions).
505
+ */
506
+ createInline(src, anchor, width, height, file, alt, isLast) {
507
+ /** @type {HTMLImageElement} */
508
+ const oImg = dom.utils.createElement('IMG');
509
+ oImg.src = src;
510
+ oImg.alt = alt;
511
+ anchor = this.#setAnchor(oImg, anchor ? anchor.cloneNode(false) : null);
512
+
513
+ const figureInfo = Figure.CreateInlineContainer(anchor, 'se-image-container');
514
+ const container = figureInfo.container;
515
+
516
+ this.#element = oImg;
517
+ this.#container = container;
518
+ this.figure.open(oImg, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
519
+
520
+ // set size
521
+ this.sizeService.applySize(width, height);
522
+
523
+ this.fileManager.setFileData(oImg, file);
524
+
525
+ this.setState('produceIndex', this.state.produceIndex + 1);
526
+ oImg.onload = this.#OnloadImg.bind(this, oImg, this.#svgDefaultSize, container);
527
+ this.$.component.insert(container, { scrollTo: isLast ? true : false, insertBehavior: isLast ? null : 'line' });
528
+ }
529
+
530
+ /**
531
+ * @description Prepares the component for selection.
532
+ * - Ensures that the controller is properly positioned and initialized.
533
+ * - Prevents duplicate event handling if the component is already selected.
534
+ * @param {HTMLElement} target - The selected element.
535
+ * @param {boolean} [infoOnly=false] - If `true`, only retrieves information without opening the controller.
536
+ * @returns {{w: string, h: string}} - The width and height of the component.
537
+ */
538
+ #ready(target, infoOnly = false) {
539
+ if (!target) return;
540
+ const figureInfo = this.figure.open(target, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly });
541
+ this.anchor.set(dom.check.isAnchor(target.parentNode) ? target.parentNode : null);
542
+
543
+ this.#linkElement = this.anchor.currentTarget;
544
+ this.#element = target;
545
+ this.#cover = figureInfo.cover;
546
+ this.#container = figureInfo.container;
547
+ this.#caption = figureInfo.caption;
548
+ this.#align = figureInfo.align;
549
+ target.style.float = '';
550
+
551
+ this.sizeService.setOriginSize(String(figureInfo.originWidth || figureInfo.w || ''), String(figureInfo.originHeight || figureInfo.h || ''));
552
+ this.altText.value = this.#element.alt;
553
+
554
+ if (this.imgUrlFile) this.#linkValue = this.previewSrc.textContent = this.imgUrlFile.value = this.#element.src;
555
+
556
+ /** @type {HTMLInputElement} */
557
+ 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"]');
558
+ activeAlign.checked = true;
559
+ this.captionCheckEl.checked = !!this.#caption;
560
+
561
+ const { dw, dh } = this.figure.getSize(target);
562
+
563
+ if (!this.#resizing) return { w: dw, h: dh };
564
+
565
+ this.sizeService.ready(figureInfo, dw, dh);
566
+
567
+ if (this.pluginOptions.useFormatType) {
568
+ this.#activeAsInline(this.$.component.isInline(figureInfo.container));
569
+ }
570
+
571
+ return { w: dw, h: dh };
572
+ }
573
+
574
+ /**
575
+ * @description Retrieves the current image information.
576
+ * @returns {*} - The image data.
577
+ */
578
+ #getInfo() {
579
+ const { w, h } = this.sizeService.getInputSize();
580
+ return {
581
+ element: this.#element,
582
+ anchor: this.anchor.create(true),
583
+ inputWidth: w,
584
+ inputHeight: h,
585
+ align: this.#align,
586
+ isUpdate: this.modal.isUpdate,
587
+ alt: this.altText.value,
588
+ };
589
+ }
590
+
591
+ /**
592
+ * @description Toggles between `block` and `inline` image format.
593
+ * @param {boolean} isInline - Whether the image should be `inline`.
594
+ */
595
+ #activeAsInline(isInline) {
596
+ if (isInline) {
597
+ dom.utils.addClass(this.asInline, 'on');
598
+ dom.utils.removeClass(this.asBlock, 'on');
599
+ this.as = FORMAT_TYPE.INLINE;
600
+ // buttns
601
+ if (this.alignForm) this.alignForm.style.display = 'none';
602
+ // caption
603
+ if (this.captionEl) this.captionEl.style.display = 'none';
604
+ } else {
605
+ dom.utils.addClass(this.asBlock, 'on');
606
+ dom.utils.removeClass(this.asInline, 'on');
607
+ this.as = FORMAT_TYPE.BLOCK;
608
+ // buttns
609
+ if (this.alignForm) this.alignForm.style.display = '';
610
+ // caption
611
+ if (this.captionEl) this.captionEl.style.display = '';
612
+ }
613
+ }
614
+
615
+ /**
616
+ * @description Updates the selected image size, alt text, and caption.
617
+ */
618
+ #fixTagStructure() {
619
+ const { w, h } = this.sizeService.getInputSize();
620
+ const width = w || 'auto';
621
+ const height = h || 'auto';
622
+
623
+ let imageEl = this.#element;
624
+
625
+ // as (block | inline)
626
+ if ((this.as === FORMAT_TYPE.BLOCK && !this.#cover) || (this.as === FORMAT_TYPE.INLINE && this.#cover)) {
627
+ imageEl = this.figure.convertAsFormat(imageEl, this.as);
628
+ }
629
+
630
+ // --- update image ---
631
+ const cover = this.#cover;
632
+ const container = this.#container === this.#cover ? null : this.#container;
633
+
634
+ // check size
635
+ let changeSize;
636
+ const x = numbers.is(width) ? width + this.state.sizeUnit : width;
637
+ const y = numbers.is(height) ? height + this.state.sizeUnit : height;
638
+ if (/%$/.test(imageEl.style.width)) {
639
+ changeSize = x !== container.style.width || y !== container.style.height;
640
+ } else {
641
+ changeSize = x !== imageEl.style.width || y !== imageEl.style.height;
642
+ }
643
+
644
+ // alt
645
+ imageEl.alt = this.altText.value;
646
+
647
+ // caption
648
+ let modifiedCaption = false;
649
+ if (this.captionCheckEl.checked) {
650
+ if (!this.#caption) {
651
+ this.#caption = Figure.CreateCaption(cover, this.$.lang.caption);
652
+ modifiedCaption = true;
653
+ }
654
+ } else {
655
+ if (this.#caption) {
656
+ dom.utils.removeItem(this.#caption);
657
+ this.#caption = null;
658
+ modifiedCaption = true;
659
+ }
660
+ }
661
+
662
+ // link
663
+ let isNewAnchor = false;
664
+ const anchor = this.anchor.create(true);
665
+ if (anchor) {
666
+ if (this.#linkElement !== anchor || !container.contains(anchor)) {
667
+ this.#linkElement = anchor.cloneNode(false);
668
+ cover.insertBefore(this.#setAnchor(imageEl, this.#linkElement), this.#caption);
669
+ isNewAnchor = true;
670
+ }
671
+ } else if (this.#linkElement !== null) {
672
+ if (cover.contains(this.#linkElement)) {
673
+ const newEl = imageEl.cloneNode(true);
674
+ cover.removeChild(this.#linkElement);
675
+ cover.insertBefore(newEl, this.#caption);
676
+ imageEl = newEl;
677
+ }
678
+ }
679
+
680
+ if (isNewAnchor) {
681
+ dom.utils.removeItem(anchor);
682
+ }
683
+
684
+ // size
685
+ if (this.#resizing && changeSize) {
686
+ this.sizeService.applySize(width, height);
687
+ }
688
+
689
+ // transform
690
+ if (modifiedCaption || (!this.state.onlyPercentage && changeSize)) {
691
+ if (/\d+/.test(imageEl.style.height) || (this.figure.isVertical && this.captionCheckEl.checked)) {
692
+ if (/auto|%$/.test(width) || /auto|%$/.test(height)) {
693
+ this.figure.deleteTransform(imageEl);
694
+ } else if (!this.#resizing || !changeSize || !this.figure.isVertical) {
695
+ this.figure.setTransform(imageEl, width, height, 0);
696
+ }
697
+ }
698
+ }
699
+
700
+ // align
701
+ this.figure.setAlign(imageEl, this.#align);
702
+
703
+ // select
704
+ imageEl.onload = () => {
705
+ this.componentSelect(imageEl);
706
+ };
707
+ }
708
+
709
+ /**
710
+ * @description Validates the image size and applies necessary transformations.
711
+ * @param {string} width - The width of the image.
712
+ * @param {string} height - The height of the image.
713
+ */
714
+ #fileCheck(width, height) {
715
+ const { w, h } = this.sizeService.getInputSize();
716
+ width ||= w || 'auto';
717
+ height ||= h || 'auto';
718
+
719
+ let imageEl = this.#element;
720
+ let cover = this.#cover;
721
+ let inlineCover = null;
722
+ let container = this.#container === this.#cover ? null : this.#container;
723
+ let isNewContainer = false;
724
+
725
+ if (!cover || !container) {
726
+ isNewContainer = true;
727
+ imageEl = this.#element.cloneNode(true);
728
+ const figureInfo =
729
+ this.pluginOptions.useFormatType && width !== 'auto' && (/^span$/i.test(this.#element.parentElement?.nodeName) || this.$.format.isLine(this.#element.parentElement))
730
+ ? Figure.CreateInlineContainer(imageEl, 'se-image-container')
731
+ : Figure.CreateContainer(imageEl, 'se-image-container');
732
+ cover = figureInfo.cover;
733
+ container = figureInfo.container;
734
+ inlineCover = figureInfo.inlineCover;
735
+ this.figure.open(imageEl, { nonResizing: true, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
736
+ }
737
+
738
+ // alt
739
+ imageEl.alt = this.altText.value;
740
+
741
+ // caption
742
+ let modifiedCaption = false;
743
+ if (!inlineCover) {
744
+ if (this.captionCheckEl.checked) {
745
+ if (!this.#caption || isNewContainer) {
746
+ this.#caption = Figure.CreateCaption(cover, this.$.lang.caption);
747
+ modifiedCaption = true;
748
+ }
749
+ } else {
750
+ if (this.#caption) {
751
+ dom.utils.removeItem(this.#caption);
752
+ this.#caption = null;
753
+ modifiedCaption = true;
754
+ }
755
+ }
756
+ }
757
+
758
+ // link
759
+ let isNewAnchor = null;
760
+ const anchor = this.anchor.create(true);
761
+ if (anchor) {
762
+ if (this.#linkElement !== anchor || (isNewContainer && !container.contains(anchor))) {
763
+ this.#linkElement = anchor.cloneNode(false);
764
+ cover.insertBefore(this.#setAnchor(imageEl, this.#linkElement), this.#caption);
765
+ isNewAnchor = this.#element;
766
+ }
767
+ } else if (this.#linkElement !== null) {
768
+ if (cover.contains(this.#linkElement)) {
769
+ const newEl = imageEl.cloneNode(true);
770
+ cover.removeChild(this.#linkElement);
771
+ cover.insertBefore(newEl, this.#caption);
772
+ imageEl = newEl;
773
+ }
774
+ }
775
+
776
+ if (isNewContainer) {
777
+ imageEl = this.#element;
778
+ this.figure.retainFigureFormat(container, this.#element, isNewAnchor ? anchor : null, this.fileManager);
779
+ this.#element = imageEl = container.querySelector('img');
780
+ this.#cover = cover;
781
+ this.#container = container;
782
+ }
783
+
784
+ // size
785
+ imageEl.style.width = '';
786
+ imageEl.style.height = '';
787
+ imageEl.removeAttribute('width');
788
+ imageEl.removeAttribute('height');
789
+ this.sizeService.applySize(width, height);
790
+
791
+ if (isNewAnchor) {
792
+ if (!isNewContainer) {
793
+ dom.utils.removeItem(anchor);
794
+ } else {
795
+ dom.utils.removeItem(isNewAnchor);
796
+ if (dom.query.getListChildren(anchor, (current) => /IMG/i.test(current.tagName), null).length === 0) {
797
+ dom.utils.removeItem(anchor);
798
+ }
799
+ }
800
+ }
801
+
802
+ // transform
803
+ if (modifiedCaption || !this.state.onlyPercentage) {
804
+ if (/\d+/.test(imageEl.style.height) || (this.figure.isVertical && this.captionCheckEl.checked)) {
805
+ if (/auto|%$/.test(width) || /auto|%$/.test(height)) {
806
+ this.figure.deleteTransform(imageEl);
807
+ } else {
808
+ this.figure.setTransform(imageEl, width, height, 0);
809
+ }
810
+ }
811
+ }
812
+
813
+ // align
814
+ this.figure.setAlign(imageEl, this.#align);
815
+ }
816
+
817
+ /**
818
+ * @description Wraps an image element with an anchor if provided.
819
+ * @param {Node} imgTag - The image element to be wrapped.
820
+ * @param {?Node} anchor - The anchor element to wrap around the image. If `null`, returns the image itself.
821
+ * @returns {Node} - The wrapped image inside the anchor or the original image element.
822
+ */
823
+ #setAnchor(imgTag, anchor) {
824
+ if (anchor) {
825
+ anchor.appendChild(imgTag);
826
+ return anchor;
827
+ }
828
+
829
+ return imgTag;
830
+ }
831
+
832
+ /**
833
+ * @description Opens a specific tab inside the modal.
834
+ * @param {MouseEvent|string} e - The event object or tab name.
835
+ * @returns {boolean} - Whether the tab was successfully opened.
836
+ */
837
+ #OpenTab(e) {
838
+ const modalForm = this.modal.form;
839
+ const targetElement = typeof e === 'string' ? modalForm.querySelector('._se_tab_link') : dom.query.getEventTarget(e);
840
+
841
+ if (!/^BUTTON$/i.test(targetElement.tagName)) {
842
+ return false;
843
+ }
844
+
845
+ // Declare all variables
846
+ const tabName = targetElement.getAttribute('data-tab-link');
847
+ let i;
848
+
849
+ // Get all elements with class="tabcontent" and hide them
850
+ const tabContent = /** @type {HTMLCollectionOf<HTMLElement>}*/ (modalForm.getElementsByClassName('_se_tab_content'));
851
+ for (i = 0; i < tabContent.length; i++) {
852
+ tabContent[i].style.display = 'none';
853
+ }
854
+
855
+ // Get all elements with class="tablinks" and remove the class "active"
856
+ const tabLinks = modalForm.getElementsByClassName('_se_tab_link');
857
+ for (i = 0; i < tabLinks.length; i++) {
858
+ dom.utils.removeClass(tabLinks[i], 'active');
859
+ }
860
+
861
+ // Show the current tab, and add an "active" class to the button that opened the tab
862
+ /** @type {HTMLElement}*/ (modalForm.querySelector('._se_tab_content_' + tabName)).style.display = 'block';
863
+ dom.utils.addClass(targetElement, 'active');
864
+
865
+ // focus
866
+ if (e !== 'init') {
867
+ if (tabName === 'image') {
868
+ this.focusElement.focus();
869
+ } else if (tabName === 'url') {
870
+ this.anchor.urlInput.focus();
871
+ }
872
+ }
873
+
874
+ return false;
875
+ }
876
+
877
+ #RemoveSelectedFiles() {
878
+ this.imgInputFile.value = '';
879
+ if (this.imgUrlFile) {
880
+ this.imgUrlFile.disabled = false;
881
+ this.previewSrc.style.textDecoration = '';
882
+ }
883
+
884
+ // inputFile check
885
+ Modal.OnChangeFile(this.fileModalWrapper, []);
886
+ }
887
+
888
+ #OnClickAsButton({ target }) {
889
+ this.#activeAsInline(target.getAttribute('data-command') === 'asInline');
890
+ }
891
+
892
+ #OnLinkPreview(e) {
893
+ const value = e.target.value.trim();
894
+ this.#linkValue = this.previewSrc.textContent = !value
895
+ ? ''
896
+ : this.$.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
897
+ ? this.$.options.get('defaultUrlProtocol') + value
898
+ : !value.includes('://')
899
+ ? '/' + value
900
+ : value;
901
+ }
902
+
903
+ #OnfileInputChange({ target }) {
904
+ if (!this.imgInputFile.value) {
905
+ this.imgUrlFile.disabled = false;
906
+ this.previewSrc.style.textDecoration = '';
907
+ } else {
908
+ this.imgUrlFile.disabled = true;
909
+ this.previewSrc.style.textDecoration = 'line-through';
910
+ }
911
+
912
+ // inputFile check
913
+ Modal.OnChangeFile(this.fileModalWrapper, target.files);
914
+ }
915
+
916
+ #OpenGallery() {
917
+ this.$.plugins.imageGallery.open(this.#SetUrlInput.bind(this));
918
+ }
919
+
920
+ #SetUrlInput(target) {
921
+ this.altText.value = target.getAttribute('data-value') || target.alt;
922
+ this.#linkValue = this.previewSrc.textContent = this.imgUrlFile.value = target.getAttribute('data-command') || target.src;
923
+ this.imgUrlFile.focus();
924
+ }
925
+
926
+ #OnloadImg(oImg, _svgDefaultSize, container) {
927
+ this.setState('produceIndex', this.state.produceIndex - 1);
928
+ delete oImg.onload;
929
+
930
+ // svg exception handling
931
+ if (oImg.offsetWidth === 0) this.sizeService.applySize(_svgDefaultSize, '');
932
+
933
+ if (this.state.produceIndex === 0) {
934
+ this.$.component.applyInsertBehavior(container, null, this.pluginOptions.insertBehavior || this.$.options.get('componentInsertBehavior'));
935
+
936
+ this.$.ui._iframeAutoHeight(this.$.frameContext);
937
+ this.$.history.push(false);
938
+ }
939
+ }
940
+ }
941
+
942
+ export default Image_;