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,858 @@
1
+ import { PluginModal } from '../../../interfaces';
2
+ import { Modal, Figure } from '../../../modules/contract';
3
+ import { FileManager } from '../../../modules/manager';
4
+ import { dom, numbers, env, converter } from '../../../helper';
5
+ const { _w, NO_EVENT } = env;
6
+
7
+ import VideoSizeService from './services/video.size';
8
+ import VideoUploadService from './services/video.upload';
9
+ import { CreateHTML_modal } from './render/video.html';
10
+
11
+ /**
12
+ * @typedef {Object} VideoPluginOptions
13
+ * @property {boolean} [canResize=true] - Whether the video element can be resized.
14
+ * @property {boolean} [showHeightInput=true] - Whether to display the height input field.
15
+ * @property {string} [defaultWidth] - The default width of the video element. If a number is provided, `"px"` will be appended.
16
+ * @property {string} [defaultHeight] - The default height of the video element. If a number is provided, `"px"` will be appended.
17
+ * @property {boolean} [percentageOnlySize=false] - Whether to allow only percentage-based sizing.
18
+ * @property {boolean} [createFileInput=false] - Whether to create a file input element for video uploads.
19
+ * @property {boolean} [createUrlInput=true] - Whether to create a URL input element for video embedding.
20
+ * @property {string} [uploadUrl] - The URL endpoint for video file uploads.
21
+ * @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the video upload request.
22
+ * @property {number} [uploadSizeLimit] - The total upload size limit for videos in bytes.
23
+ * @property {number} [uploadSingleSizeLimit] - The single file upload size limit for videos in bytes.
24
+ * @property {boolean} [allowMultiple=false] - Whether multiple video uploads are allowed.
25
+ * @property {string} [acceptedFormats="video/*"] - Accepted file formats for video uploads (`"video/*"`).
26
+ * @property {number} [defaultRatio=0.5625] - The default aspect ratio for the video (e.g., 16:9 is 0.5625).
27
+ * @property {boolean} [showRatioOption=true] - Whether to display the ratio option in the modal.
28
+ * @property {Array} [ratioOptions] - Custom ratio options for video resizing.
29
+ * @property {Object<string, string>} [videoTagAttributes] - Additional attributes to set on the `VIDEO` tag.
30
+ * @property {Object<string, string>} [iframeTagAttributes] - Additional attributes to set on the `IFRAME` tag.
31
+ * @property {string} [query_youtube=""] - Additional query parameters for YouTube embedding.
32
+ * @property {string} [query_vimeo=""] - Additional query parameters for Vimeo embedding.
33
+ * @property {Object<string, {pattern: RegExp, action: (url: string) => string, tag: string}>} [embedQuery] - Custom query objects for additional embedding services.
34
+ * @property {Array<RegExp>} [urlPatterns] - Additional URL patterns for video embedding.
35
+ * @property {Array<string>} [extensions] - Additional file extensions to be recognized for video uploads.
36
+ * @property {SunEditor.Module.Figure.Controls} [controls] - Figure controls.
37
+ * @property {SunEditor.ComponentInsertType} [insertBehavior] - Component insertion behavior for selection and cursor placement. [default: `options.get('componentInsertBehavior')`]
38
+ * - `auto`: Move cursor to the next line if possible, otherwise select the component.
39
+ * - `select`: Always select the inserted component.
40
+ * - `line`: Move cursor to the next line if possible, or create a new line and move there.
41
+ * - `none`: Do nothing.
42
+ */
43
+
44
+ /**
45
+ * @typedef {Object} VideoState
46
+ * @property {string} sizeUnit
47
+ * @property {boolean} onlyPercentage
48
+ * @property {string} defaultRatio
49
+ */
50
+
51
+ /**
52
+ * @class
53
+ * @description Video plugin.
54
+ * - This plugin provides video embedding functionality within the editor.
55
+ * - It also supports embedding from popular video services
56
+ */
57
+ class Video extends PluginModal {
58
+ static key = 'video';
59
+ static className = '';
60
+
61
+ /**
62
+ * @param {HTMLElement} node - The node to check.
63
+ * @returns {HTMLElement|null} Returns a node if the node is a valid component.
64
+ */
65
+ static component(node) {
66
+ if (/^(VIDEO)$/i.test(node?.nodeName)) {
67
+ return node;
68
+ } else if (/^(IFRAME)$/i.test(node?.nodeName)) {
69
+ return this.#checkContentType(/** @type {HTMLIFrameElement} */ (node).src) ? node : null;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * @description Checks if the given URL matches any of the defined URL patterns.
76
+ * @param {string} url - The URL to check.
77
+ * @returns {boolean} `true` if the URL matches a known pattern; otherwise, `false`.
78
+ */
79
+ static #checkContentType(url) {
80
+ url = url?.toLowerCase() || '';
81
+ if (this.#extensions.some((ext) => url.endsWith(ext)) || this.#urlPatterns.some((pattern) => pattern.test(url))) {
82
+ return true;
83
+ }
84
+
85
+ return false;
86
+ }
87
+
88
+ static #extensions = ['.mp4', '.avi', '.mov', '.webm', '.flv', '.mkv', '.m4v', '.ogv'];
89
+ static #urlPatterns = [
90
+ /youtu\.?be/,
91
+ /vimeo\.com\//,
92
+ /dailymotion\.com\/video\//,
93
+ /facebook\.com\/.+\/videos\//,
94
+ /facebook\.com\/watch\/\?v=/,
95
+ /twitter\.com\/.+\/status\//,
96
+ /twitch\.tv\/videos\//,
97
+ /twitch\.tv\/[^/]+$/,
98
+ /tiktok\.com\/@[^/]+\/video\//,
99
+ /instagram\.com\/p\//,
100
+ /instagram\.com\/tv\//,
101
+ /instagram\.com\/reel\//,
102
+ /linkedin\.com\/posts\//,
103
+ /\.(wistia\.com|wi\.st)\/(medias|embed)\//,
104
+ /loom\.com\/share\//,
105
+ ];
106
+
107
+ #resizing;
108
+ #nonResizing;
109
+
110
+ #linkValue = '';
111
+ #align = 'none';
112
+ #element = null;
113
+ #container = null;
114
+
115
+ /**
116
+ * @constructor
117
+ * @param {SunEditor.Kernel} editor - The core kernel
118
+ * @param {VideoPluginOptions} pluginOptions
119
+ */
120
+ constructor(editor, pluginOptions) {
121
+ // plugin basic properties
122
+ super(editor);
123
+ this.title = this.$.lang.video;
124
+ this.icon = 'video';
125
+
126
+ // define plugin options
127
+ this.pluginOptions = {
128
+ canResize: pluginOptions.canResize === undefined ? true : pluginOptions.canResize,
129
+ showHeightInput: pluginOptions.showHeightInput === undefined ? true : !!pluginOptions.showHeightInput,
130
+ defaultWidth: !pluginOptions.defaultWidth || !numbers.get(pluginOptions.defaultWidth, 0) ? '' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
131
+ defaultHeight: !pluginOptions.defaultHeight || !numbers.get(pluginOptions.defaultHeight, 0) ? '' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
132
+ percentageOnlySize: !!pluginOptions.percentageOnlySize,
133
+ createFileInput: !!pluginOptions.createFileInput,
134
+ createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
135
+ uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
136
+ uploadHeaders: pluginOptions.uploadHeaders || null,
137
+ uploadSizeLimit: numbers.get(pluginOptions.uploadSizeLimit, 0),
138
+ uploadSingleSizeLimit: numbers.get(pluginOptions.uploadSingleSizeLimit, 0),
139
+ allowMultiple: !!pluginOptions.allowMultiple,
140
+ acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'video/*' : pluginOptions.acceptedFormats.trim() || 'video/*',
141
+ defaultRatio: numbers.get(pluginOptions.defaultRatio, 4) || 0.5625,
142
+ showRatioOption: pluginOptions.showRatioOption === undefined ? true : !!pluginOptions.showRatioOption,
143
+ ratioOptions: !pluginOptions.ratioOptions ? null : pluginOptions.ratioOptions,
144
+ videoTagAttributes: pluginOptions.videoTagAttributes || null,
145
+ iframeTagAttributes: pluginOptions.iframeTagAttributes || null,
146
+ query_youtube: pluginOptions.query_youtube || '',
147
+ query_vimeo: pluginOptions.query_vimeo || '',
148
+ insertBehavior: pluginOptions.insertBehavior,
149
+ };
150
+
151
+ // create HTML
152
+ const sizeUnit = this.pluginOptions.percentageOnlySize ? '%' : 'px';
153
+ const modalEl = CreateHTML_modal(this.$, this.pluginOptions);
154
+ const figureControls = pluginOptions.controls || (!this.pluginOptions.canResize ? [['align', 'edit', 'copy', 'remove']] : [['resize_auto,75,50', 'align', 'edit', 'revert', 'copy', 'remove']]);
155
+
156
+ // show align
157
+ if (!figureControls.some((subArray) => subArray.includes('align'))) modalEl.alignForm.style.display = 'none';
158
+
159
+ // modules
160
+ const defaultRatio = this.pluginOptions.defaultRatio * 100 + '%';
161
+ this.modal = new Modal(this, this.$, modalEl.html);
162
+ this.figure = new Figure(this, this.$, figureControls, { sizeUnit: sizeUnit, autoRatio: { current: defaultRatio, default: defaultRatio } });
163
+ this.fileManager = new FileManager(this, this.$, {
164
+ query: 'iframe, video',
165
+ loadEventName: 'onVideoLoad',
166
+ actionEventName: 'onVideoAction',
167
+ });
168
+
169
+ // members
170
+ this.fileModalWrapper = modalEl.fileModalWrapper;
171
+ this.videoInputFile = modalEl.videoInputFile;
172
+ this.videoUrlFile = modalEl.videoUrlFile;
173
+ this.focusElement = this.videoUrlFile || this.videoInputFile;
174
+ this.previewSrc = modalEl.previewSrc;
175
+
176
+ this.#resizing = this.pluginOptions.canResize;
177
+ this.#nonResizing = !this.#resizing || !this.pluginOptions.showHeightInput || this.pluginOptions.percentageOnlySize;
178
+
179
+ this.query = {
180
+ youtube: {
181
+ pattern: /youtu\.?be/i,
182
+ action: (url) => {
183
+ url = this.convertUrlYoutube(url);
184
+ return converter.addUrlQuery(url, this.pluginOptions.query_youtube);
185
+ },
186
+ tag: 'iframe',
187
+ },
188
+ vimeo: {
189
+ pattern: /vimeo\.com/i,
190
+ action: (url) => {
191
+ url = this.convertUrlVimeo(url);
192
+ return converter.addUrlQuery(url, this.pluginOptions.query_vimeo);
193
+ },
194
+ tag: 'iframe',
195
+ },
196
+ ...pluginOptions.embedQuery,
197
+ };
198
+
199
+ const urlPatterns = [];
200
+ for (const key in this.query) {
201
+ urlPatterns.push(this.query[key].pattern);
202
+ }
203
+ Video.#extensions = Video.#extensions.concat(this.pluginOptions.extensions || []);
204
+ Video.#urlPatterns = Video.#urlPatterns.concat(pluginOptions.urlPatterns || []);
205
+
206
+ /** @type {VideoState} */
207
+ this.state = {
208
+ onlyPercentage: this.pluginOptions.percentageOnlySize,
209
+ sizeUnit: sizeUnit,
210
+ defaultRatio: this.pluginOptions.defaultRatio * 100 + '%',
211
+ };
212
+
213
+ this.sizeService = new VideoSizeService(this, modalEl);
214
+ this.uploadService = new VideoUploadService(this);
215
+
216
+ // init
217
+ const galleryButton = modalEl.galleryButton;
218
+ if (galleryButton) this.$.eventManager.addEvent(galleryButton, 'click', this.#OpenGallery.bind(this));
219
+
220
+ if (this.videoInputFile) this.$.eventManager.addEvent(modalEl.fileRemoveBtn, 'click', this.#RemoveSelectedFiles.bind(this));
221
+ if (this.videoUrlFile) this.$.eventManager.addEvent(this.videoUrlFile, 'input', this.#OnLinkPreview.bind(this));
222
+ if (this.videoInputFile && this.videoUrlFile) this.$.eventManager.addEvent(this.videoInputFile, 'change', this.#OnfileInputChange.bind(this));
223
+ }
224
+
225
+ /**
226
+ * @template {keyof VideoState} K
227
+ * @param {K} key
228
+ * @param {VideoState[K]} value
229
+ */
230
+ setState(key, value) {
231
+ this.state[key] = value;
232
+ }
233
+
234
+ /**
235
+ * @override
236
+ * @type {PluginModal['open']}
237
+ */
238
+ open() {
239
+ this.modal.open();
240
+ }
241
+
242
+ /**
243
+ * @hook Editor.Core
244
+ * @type {SunEditor.Hook.Core.RetainFormat}
245
+ */
246
+ retainFormat() {
247
+ return {
248
+ query: 'iframe, video',
249
+ /** @param {HTMLIFrameElement|HTMLVideoElement} element */
250
+ method: async (element) => {
251
+ if (/^(iframe)$/i.test(element?.nodeName)) {
252
+ if (!Video.#checkContentType(element.src)) return;
253
+ }
254
+
255
+ const figureInfo = Figure.GetContainer(element);
256
+ if (figureInfo && figureInfo.container && figureInfo.cover) return;
257
+
258
+ this.#ready(element, true);
259
+ const line = this.$.format.getLine(element);
260
+ if (line) this.#align = line.style.textAlign || line.style.float;
261
+
262
+ this.#fixTagStructure(element);
263
+ },
264
+ };
265
+ }
266
+
267
+ /**
268
+ * @hook Editor.EventManager
269
+ * @type {SunEditor.Hook.Event.OnFilePasteAndDrop}
270
+ */
271
+ onFilePasteAndDrop({ file }) {
272
+ if (!/^video/.test(file.type)) return;
273
+
274
+ this.submitFile([file]);
275
+ this.$.focusManager.focus();
276
+ }
277
+
278
+ /**
279
+ * @hook Modules.Modal
280
+ * @type {SunEditor.Hook.Modal.On}
281
+ */
282
+ modalOn(isUpdate) {
283
+ if (!isUpdate) {
284
+ if (this.videoInputFile && this.pluginOptions.allowMultiple) this.videoInputFile.setAttribute('multiple', 'multiple');
285
+ } else {
286
+ if (this.videoInputFile && this.pluginOptions.allowMultiple) this.videoInputFile.removeAttribute('multiple');
287
+ }
288
+
289
+ this.sizeService.on(isUpdate);
290
+ }
291
+
292
+ /**
293
+ * @hook Modules.Modal
294
+ * @type {SunEditor.Hook.Modal.Action}
295
+ */
296
+ async modalAction() {
297
+ this.#align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_video_radio"]:checked')).value;
298
+
299
+ let result = false;
300
+ if (this.videoInputFile && this.videoInputFile.files.length > 0) {
301
+ result = await this.submitFile(this.videoInputFile.files);
302
+ } else if (this.videoUrlFile && this.#linkValue.length > 0) {
303
+ result = await this.submitURL(this.#linkValue);
304
+ }
305
+
306
+ if (result) _w.setTimeout(this.$.component.select.bind(this.$.component, this.#element, Video.key), 0);
307
+
308
+ return result;
309
+ }
310
+
311
+ /**
312
+ * @hook Modules.Modal
313
+ * @type {SunEditor.Hook.Modal.Init}
314
+ */
315
+ modalInit() {
316
+ Modal.OnChangeFile(this.fileModalWrapper, []);
317
+ if (this.videoInputFile) this.videoInputFile.value = '';
318
+ if (this.videoUrlFile) this.#linkValue = this.previewSrc.textContent = this.videoUrlFile.value = '';
319
+ if (this.videoInputFile && this.videoUrlFile) {
320
+ this.videoUrlFile.disabled = false;
321
+ this.previewSrc.style.textDecoration = '';
322
+ }
323
+
324
+ /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_video_radio"][value="none"]')).checked = true;
325
+ this.#nonResizing = false;
326
+
327
+ this.sizeService.init();
328
+ }
329
+
330
+ /**
331
+ * @hook Editor.Component
332
+ * @type {SunEditor.Hook.Component.Select}
333
+ * @param {HTMLIFrameElement|HTMLVideoElement} target
334
+ */
335
+ componentSelect(target) {
336
+ this.#ready(target);
337
+ }
338
+
339
+ /**
340
+ * @hook Component
341
+ * @type {SunEditor.Hook.Component.Edit}
342
+ */
343
+ componentEdit() {
344
+ this.modal.open();
345
+ }
346
+
347
+ /**
348
+ * @hook Editor.Component
349
+ * @type {SunEditor.Hook.Component.Destroy}
350
+ */
351
+ async componentDestroy(target) {
352
+ const targetEl = target || this.#element;
353
+ const container = dom.query.getParentElement(targetEl, Figure.is) || targetEl;
354
+ const focusEl = container.previousElementSibling || container.nextElementSibling;
355
+ const emptyDiv = container.parentNode;
356
+
357
+ const message = await this.$.eventManager.triggerEvent('onVideoDeleteBefore', { element: targetEl, container, align: this.#align, url: this.#linkValue });
358
+ if (message === false) return;
359
+
360
+ dom.utils.removeItem(container);
361
+ this.modalInit();
362
+
363
+ if (emptyDiv !== this.$.frameContext.get('wysiwyg')) {
364
+ this.$.nodeTransform.removeAllParents(
365
+ emptyDiv,
366
+ function (current) {
367
+ return current.childNodes.length === 0;
368
+ },
369
+ null,
370
+ );
371
+ }
372
+
373
+ // focus
374
+ this.$.focusManager.focusEdge(focusEl);
375
+ this.$.history.push(false);
376
+ }
377
+
378
+ /**
379
+ * @description Finds and processes the URL for video by matching it against known service patterns.
380
+ * @param {string} url - The original URL.
381
+ * @returns {{origin: string, url: string, tag: string}|null} An object containing the original URL, the processed URL, and the tag type (e.g., `iframe`),
382
+ * or `null` if no matching pattern is found.
383
+ */
384
+ findProcessUrl(url) {
385
+ const query = this.query;
386
+ for (const key in query) {
387
+ const service = query[key];
388
+ if (service.pattern.test(url)) {
389
+ return {
390
+ origin: url,
391
+ url: service.action(url),
392
+ tag: service.tag,
393
+ };
394
+ }
395
+ }
396
+
397
+ return null;
398
+ }
399
+
400
+ /**
401
+ * @description Converts a YouTube URL into an embeddable URL.
402
+ * - If the URL does not start with `"http"`, it prepends `"https://"`. It also replaces `"watch?v="` with the embed path.
403
+ * @param {string} url - The original YouTube URL.
404
+ * @returns {string} The converted YouTube embed URL.
405
+ */
406
+ convertUrlYoutube(url) {
407
+ if (!/^http/.test(url)) url = 'https://' + url;
408
+ url = url.replace('watch?v=', '');
409
+ if (!/^\/\/.+\/embed\//.test(url)) {
410
+ url = url.replace(url.match(/\/\/.+\//)[0], '//www.youtube.com/embed/').replace('&', '?&');
411
+ }
412
+ return url;
413
+ }
414
+
415
+ /**
416
+ * @description Converts a Vimeo URL into an embeddable URL.
417
+ * - Removes any trailing slash and extracts the video ID from the URL.
418
+ * @param {string} url - The original Vimeo URL.
419
+ * @returns {string} The converted Vimeo embed URL.
420
+ */
421
+ convertUrlVimeo(url) {
422
+ if (url.endsWith('/')) {
423
+ url = url.slice(0, -1);
424
+ }
425
+ url = 'https://player.vimeo.com/video/' + url.slice(url.lastIndexOf('/') + 1);
426
+ return url;
427
+ }
428
+
429
+ /**
430
+ * @description Adds query parameters to a URL.
431
+ * - If the URL already contains a query string, the provided query is appended with an `"&"`.
432
+ * @param {string} url - The original URL.
433
+ * @param {string} query - The query string to append.
434
+ * @returns {string} The URL with the appended query parameters.
435
+ */
436
+ addQuery(url, query) {
437
+ if (query.length > 0) {
438
+ if (/\?/.test(url)) {
439
+ const splitUrl = url.split('?');
440
+ url = splitUrl[0] + '?' + query + '&' + splitUrl[1];
441
+ } else {
442
+ url += '?' + query;
443
+ }
444
+ }
445
+ return url;
446
+ }
447
+
448
+ /**
449
+ * @description Creates or updates a video embed component.
450
+ * - When updating, it replaces the existing element if necessary and applies the new source, size, and alignment.
451
+ * - When creating, it wraps the provided element in a figure container.
452
+ * @param {HTMLIFrameElement|HTMLVideoElement} oFrame - The existing video element (for update) or a newly created one.
453
+ * @param {string} src - The source URL for the video.
454
+ * @param {string} width - The desired width for the video element.
455
+ * @param {string} height - The desired height for the video element.
456
+ * @param {string} align - The alignment to apply to the video element (e.g., 'left', 'center', 'right').
457
+ * @param {boolean} isUpdate - Indicates whether this is an update to an existing component (true) or a new creation (false).
458
+ * @param {{name: string, size: number}} file - File metadata associated with the video
459
+ * @param {boolean} isLast - Indicates whether this is the last file in the batch (used for scroll and insert actions).
460
+ */
461
+ create(oFrame, src, width, height, align, isUpdate, file, isLast) {
462
+ let container = null;
463
+
464
+ /** update */
465
+ if (isUpdate) {
466
+ oFrame = this.#element;
467
+ if (oFrame.src !== src) {
468
+ const processUrl = this.findProcessUrl(src);
469
+ if (/^iframe$/i.test(processUrl?.tag) && !/^iframe$/i.test(oFrame.nodeName)) {
470
+ const newTag = this.createIframeTag();
471
+ newTag.src = src;
472
+ oFrame.replaceWith(newTag);
473
+ this.#element = oFrame = newTag;
474
+ } else if (/^video$/i.test(processUrl?.tag) && !/^video$/i.test(oFrame.nodeName)) {
475
+ const newTag = this.createVideoTag();
476
+ newTag.src = src;
477
+ oFrame.replaceWith(newTag);
478
+ this.#element = oFrame = newTag;
479
+ } else {
480
+ oFrame.src = src;
481
+ }
482
+ }
483
+ container = this.#container;
484
+ } else {
485
+ /** create */
486
+ oFrame.src = src;
487
+ this.#element = oFrame;
488
+ const figure = Figure.CreateContainer(oFrame, 'se-video-container');
489
+ container = figure.container;
490
+ }
491
+
492
+ /** rendering */
493
+ this.#element = oFrame;
494
+ this.#container = container;
495
+ this.figure.open(oFrame, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
496
+
497
+ // set size
498
+ const resolved = this.sizeService.resolveSize(width, height, oFrame, isUpdate);
499
+ width = resolved.width;
500
+ height = resolved.height;
501
+
502
+ // align
503
+ this.figure.setAlign(oFrame, align);
504
+
505
+ // select figure
506
+ // oFrame.onload = OnloadVideo.bind(this, oFrame);
507
+ this.fileManager.setFileData(oFrame, file);
508
+
509
+ if (!isUpdate) {
510
+ this.$.component.insert(container, { scrollTo: isLast ? true : false, insertBehavior: isLast ? this.pluginOptions.insertBehavior : 'line' });
511
+ return;
512
+ }
513
+
514
+ if (!this.#resizing || !resolved.isChanged || !this.figure.isVertical) this.figure.setTransform(oFrame, width, height, 0);
515
+ this.$.history.push(false);
516
+ }
517
+
518
+ /**
519
+ * @description Creates a new `IFRAME` element for video embedding.
520
+ * - Applies any additional properties provided and sets the necessary attributes for embedding.
521
+ * @param {Object<string, string>} [props] - An optional object containing properties to assign to the `IFRAME`.
522
+ * @returns {HTMLIFrameElement} The newly created `IFRAME` element.
523
+ */
524
+ createIframeTag(props) {
525
+ /** @type {HTMLIFrameElement} */
526
+ const iframeTag = dom.utils.createElement('IFRAME');
527
+ if (props) {
528
+ for (const key in props) {
529
+ iframeTag[key] = props[key];
530
+ }
531
+ }
532
+ this.#setIframeAttrs(iframeTag);
533
+ return iframeTag;
534
+ }
535
+
536
+ /**
537
+ * @description Creates a new `VIDEO` element for video embedding.
538
+ * - Applies any additional properties provided and sets the necessary attributes.
539
+ * @param {Object<string, string>} [props] - An optional object containing properties to assign to the `VIDEO` element.
540
+ * @returns {HTMLVideoElement} The newly created `VIDEO` element.
541
+ */
542
+ createVideoTag(props) {
543
+ /** @type {HTMLVideoElement} */
544
+ const videoTag = dom.utils.createElement('VIDEO');
545
+ if (props) {
546
+ for (const key in props) {
547
+ videoTag[key] = props[key];
548
+ }
549
+ }
550
+ this.#setTagAttrs(videoTag);
551
+ return videoTag;
552
+ }
553
+
554
+ /**
555
+ * @description Create a `video` component using the provided files.
556
+ * @param {FileList|File[]} fileList File object list
557
+ * @returns {Promise<boolean>} If return `false`, the file upload will be canceled
558
+ */
559
+ async submitFile(fileList) {
560
+ if (fileList.length === 0) return;
561
+
562
+ let fileSize = 0;
563
+ const files = [];
564
+ const singleSizeLimit = this.pluginOptions.uploadSingleSizeLimit;
565
+ for (let i = 0, len = fileList.length, f, s; i < len; i++) {
566
+ f = fileList[i];
567
+ if (!/video/i.test(f.type)) continue;
568
+
569
+ s = f.size;
570
+ if (singleSizeLimit > 0 && s > singleSizeLimit) {
571
+ const err = '[SUNEDITOR.videoUpload.fail] Size of uploadable single file: ' + singleSizeLimit / 1000 + 'KB';
572
+ const message = await this.$.eventManager.triggerEvent('onVideoUploadError', {
573
+ error: err,
574
+ limitSize: singleSizeLimit,
575
+ uploadSize: s,
576
+ file: f,
577
+ });
578
+
579
+ this.$.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
580
+
581
+ return false;
582
+ }
583
+
584
+ files.push(f);
585
+ fileSize += s;
586
+ }
587
+
588
+ const limitSize = this.pluginOptions.uploadSizeLimit;
589
+ const currentSize = this.fileManager.getSize();
590
+ if (limitSize > 0 && fileSize + currentSize > limitSize) {
591
+ const err = '[SUNEDITOR.videoUpload.fail] Size of uploadable total videos: ' + limitSize / 1000 + 'KB';
592
+ const message = await this.$.eventManager.triggerEvent('onVideoUploadError', { error: err, limitSize, currentSize, uploadSize: fileSize });
593
+
594
+ this.$.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
595
+
596
+ return false;
597
+ }
598
+
599
+ const videoInfo = {
600
+ url: null,
601
+ files,
602
+ ...this.#getInfo(),
603
+ };
604
+
605
+ const handler = function (uploadCallback, infos, newInfos) {
606
+ infos = newInfos || infos;
607
+ uploadCallback(infos, infos.files);
608
+ }.bind(this, this.uploadService.serverUpload.bind(this.uploadService), videoInfo);
609
+
610
+ const result = await this.$.eventManager.triggerEvent('onVideoUploadBefore', {
611
+ info: videoInfo,
612
+ handler,
613
+ });
614
+
615
+ if (result === undefined) return true;
616
+ if (result === false) return false;
617
+ if (result !== null && typeof result === 'object') handler(result);
618
+
619
+ if (result === true || result === NO_EVENT) handler(null);
620
+ }
621
+
622
+ /**
623
+ * @description Create a `video` component using the provided url.
624
+ * @param {string} url File url
625
+ * @returns {Promise<boolean>} If return `false`, the file upload will be canceled
626
+ */
627
+ async submitURL(url) {
628
+ if (!(url = this.#linkValue)) return false;
629
+
630
+ /** iframe source */
631
+ if (/^<iframe.*\/iframe>$/.test(url)) {
632
+ const oIframe = new DOMParser().parseFromString(url, 'text/html').querySelector('iframe');
633
+ url = oIframe.src;
634
+ if (url.length === 0) return false;
635
+ }
636
+
637
+ const processUrl = this.findProcessUrl(url);
638
+ if (processUrl) {
639
+ url = processUrl.url;
640
+ }
641
+
642
+ const file = { name: url.split('/').pop(), size: 0 };
643
+ const videoInfo = { url, files: file, ...this.#getInfo(), process: processUrl };
644
+
645
+ const handler = function (infos, newInfos) {
646
+ infos = newInfos || infos;
647
+ this.create(this[/^iframe$/i.test(infos.process?.tag) ? 'createIframeTag' : 'createVideoTag'](), infos.url, infos.inputWidth, infos.inputHeight, infos.align, infos.isUpdate, infos.files, true);
648
+ }.bind(this, videoInfo);
649
+
650
+ const result = await this.$.eventManager.triggerEvent('onVideoUploadBefore', {
651
+ info: videoInfo,
652
+ handler,
653
+ });
654
+
655
+ if (result === undefined) return true;
656
+ if (result === false) return false;
657
+ if (result !== null && typeof result === 'object') handler(result);
658
+
659
+ if (result === true || result === NO_EVENT) handler(null);
660
+
661
+ return true;
662
+ }
663
+
664
+ /**
665
+ * @description Prepares the component for selection.
666
+ * - Ensures that the controller is properly positioned and initialized.
667
+ * - Prevents duplicate event handling if the component is already selected.
668
+ * @param {HTMLIFrameElement|HTMLVideoElement} target - The selected element.
669
+ * @param {boolean} [infoOnly=false] - If `true`, only retrieves information without opening the controller.
670
+ */
671
+ #ready(target, infoOnly = false) {
672
+ if (!target) return;
673
+ const figureInfo = this.figure.open(target, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly });
674
+
675
+ this.#element = target;
676
+ this.#container = figureInfo.container;
677
+ this.#align = figureInfo.align;
678
+ target.style.float = '';
679
+
680
+ const originWidth = String(figureInfo.width || figureInfo.originWidth || figureInfo.w || '');
681
+ const originHeight = String(figureInfo.height || figureInfo.originHeight || figureInfo.h || '');
682
+ this.sizeService.setOriginSize(originWidth, originHeight);
683
+
684
+ if (this.videoUrlFile) this.#linkValue = this.previewSrc.textContent = this.videoUrlFile.value = this.#element.src || this.#element.querySelector('source')?.src || '';
685
+
686
+ /** @type {HTMLInputElement} */
687
+ const activeAlign = this.modal.form.querySelector('input[name="suneditor_video_radio"][value="' + this.#align + '"]') || this.modal.form.querySelector('input[name="suneditor_video_radio"][value="none"]');
688
+ activeAlign.checked = true;
689
+
690
+ if (!this.#resizing) return;
691
+
692
+ this.sizeService.ready(figureInfo, target);
693
+ }
694
+
695
+ /**
696
+ * @description Retrieves video information including size and alignment.
697
+ * @returns {*} Video information object.
698
+ */
699
+ #getInfo() {
700
+ const { w, h } = this.sizeService.getInputSize();
701
+ return {
702
+ inputWidth: w,
703
+ inputHeight: h,
704
+ align: this.#align,
705
+ isUpdate: this.modal.isUpdate,
706
+ element: this.#element,
707
+ };
708
+ }
709
+
710
+ /**
711
+ * @description Updates the video component within the editor.
712
+ * @param {HTMLIFrameElement|HTMLVideoElement} oFrame - The video element to update.
713
+ */
714
+ #fixTagStructure(oFrame) {
715
+ if (!oFrame) return;
716
+
717
+ const isVideoTag = /^video$/i.test(oFrame.nodeName);
718
+ if (isVideoTag) {
719
+ this.#setTagAttrs(/** @type {HTMLVideoElement} */ (oFrame));
720
+ } else if (/^iframe$/i.test(oFrame.nodeName)) {
721
+ this.#setIframeAttrs(/** @type {HTMLIFrameElement} */ (oFrame));
722
+ }
723
+
724
+ const prevFrame = oFrame;
725
+ const cloneFrame = /** @type {HTMLIFrameElement|HTMLVideoElement} */ (oFrame.cloneNode(true));
726
+ const figure = Figure.CreateContainer(cloneFrame, 'se-video-container');
727
+ const container = figure.container;
728
+
729
+ const figcaption = Figure.GetContainer(prevFrame)?.container?.querySelector('figcaption');
730
+ let caption = null;
731
+ if (figcaption) {
732
+ caption = dom.utils.createElement('figcaption');
733
+ caption.innerHTML = figcaption.innerHTML;
734
+ dom.utils.removeItem(figcaption);
735
+ figure.cover.appendChild(caption);
736
+ }
737
+
738
+ // size
739
+ this.figure.open(cloneFrame, { nonResizing: this.#nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, infoOnly: true });
740
+ const size = (cloneFrame.getAttribute('data-se-size') || ',').split(',');
741
+
742
+ const width = size[0] || prevFrame.width || '';
743
+ const height = size[1] || prevFrame.height || this.state.defaultRatio || '';
744
+ this.sizeService.applySize(width, height);
745
+
746
+ // align
747
+ const format = this.$.format.getLine(prevFrame);
748
+ if (format) this.#align = format.style.textAlign || format.style.float;
749
+ this.figure.setAlign(cloneFrame, this.#align);
750
+
751
+ this.figure.retainFigureFormat(container, this.#element, null, this.fileManager);
752
+
753
+ return cloneFrame;
754
+ }
755
+
756
+ /**
757
+ * @description Sets attributes for the `VIDEO` tag.
758
+ * @param {HTMLVideoElement} element - The `VIDEO` element.
759
+ */
760
+ #setTagAttrs(element) {
761
+ element.setAttribute('controls', 'true');
762
+
763
+ const attrs = this.pluginOptions.videoTagAttributes;
764
+ if (!attrs) return;
765
+
766
+ for (const key in attrs) {
767
+ element.setAttribute(key, attrs[key]);
768
+ }
769
+ }
770
+
771
+ /**
772
+ * @description Sets attributes for the `IFRAME` tag.
773
+ * @param {HTMLIFrameElement} element - The `IFRAME` element.
774
+ */
775
+ #setIframeAttrs(element) {
776
+ element.frameBorder = '0';
777
+ element.allowFullscreen = true;
778
+
779
+ const attrs = this.pluginOptions.iframeTagAttributes;
780
+ if (!attrs) return;
781
+
782
+ for (const key in attrs) {
783
+ element.setAttribute(key, attrs[key]);
784
+ }
785
+ }
786
+
787
+ /**
788
+ * @description Removes selected files from the file input.
789
+ */
790
+ #RemoveSelectedFiles() {
791
+ this.videoInputFile.value = '';
792
+ if (this.videoUrlFile) {
793
+ this.videoUrlFile.disabled = false;
794
+ this.previewSrc.style.textDecoration = '';
795
+ }
796
+
797
+ // inputFile check
798
+ Modal.OnChangeFile(this.fileModalWrapper, []);
799
+ }
800
+
801
+ /**
802
+ * @description Handles link preview input changes.
803
+ * @param {InputEvent} e - Event object
804
+ */
805
+ #OnLinkPreview(e) {
806
+ /** @type {HTMLInputElement} */
807
+ const eventTarget = dom.query.getEventTarget(e);
808
+ const value = eventTarget.value.trim();
809
+ if (/^<iframe.*\/iframe>$/.test(value)) {
810
+ this.#linkValue = value;
811
+ this.previewSrc.textContent = '<IFrame :src=".."></IFrame>';
812
+ } else {
813
+ this.#linkValue = this.previewSrc.textContent = !value
814
+ ? ''
815
+ : this.$.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
816
+ ? this.$.options.get('defaultUrlProtocol') + value
817
+ : !value.includes('://')
818
+ ? '/' + value
819
+ : value;
820
+ }
821
+ }
822
+
823
+ /**
824
+ * @description Opens the video gallery.
825
+ */
826
+ #OpenGallery() {
827
+ this.$.plugins.videoGallery.open(this.#SetUrlInput.bind(this));
828
+ }
829
+
830
+ /**
831
+ * @description Sets the URL input value when selecting from the gallery.
832
+ * @param {HTMLInputElement} target - The selected video element.
833
+ */
834
+ #SetUrlInput(target) {
835
+ this.#linkValue = this.previewSrc.textContent = this.videoUrlFile.value = target.getAttribute('data-command') || target.src;
836
+ this.videoUrlFile.focus();
837
+ }
838
+
839
+ /**
840
+ * @param {InputEvent} e - Event object
841
+ */
842
+ #OnfileInputChange(e) {
843
+ if (!this.videoInputFile.value) {
844
+ this.videoUrlFile.disabled = false;
845
+ this.previewSrc.style.textDecoration = '';
846
+ } else {
847
+ this.videoUrlFile.disabled = true;
848
+ this.previewSrc.style.textDecoration = 'line-through';
849
+ }
850
+
851
+ // inputFile check
852
+ /** @type {HTMLInputElement} */
853
+ const eventTarget = dom.query.getEventTarget(e);
854
+ Modal.OnChangeFile(this.fileModalWrapper, eventTarget.files);
855
+ }
856
+ }
857
+
858
+ export default Video;