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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/CONTRIBUTING.md +170 -22
  2. package/{LICENSE.txt → LICENSE} +9 -9
  3. package/README.md +168 -30
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +47 -21
  7. package/src/assets/design/color.css +121 -0
  8. package/src/assets/design/index.css +3 -0
  9. package/src/assets/design/size.css +35 -0
  10. package/src/assets/design/typography.css +37 -0
  11. package/src/assets/icons/defaultIcons.js +232 -0
  12. package/src/assets/suneditor-contents.css +181 -46
  13. package/src/assets/suneditor.css +1403 -650
  14. package/src/core/base/eventHandlers/handler_toolbar.js +35 -14
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +23 -4
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +49 -10
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +422 -224
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +83 -36
  19. package/src/core/base/eventManager.js +520 -179
  20. package/src/core/base/history.js +95 -41
  21. package/src/core/class/char.js +26 -11
  22. package/src/core/class/component.js +345 -137
  23. package/src/core/class/format.js +683 -519
  24. package/src/core/class/html.js +485 -305
  25. package/src/core/class/menu.js +133 -47
  26. package/src/core/class/nodeTransform.js +90 -71
  27. package/src/core/class/offset.js +408 -92
  28. package/src/core/class/selection.js +216 -106
  29. package/src/core/class/shortcuts.js +68 -8
  30. package/src/core/class/toolbar.js +106 -116
  31. package/src/core/class/ui.js +422 -0
  32. package/src/core/class/viewer.js +178 -74
  33. package/src/core/editor.js +496 -389
  34. package/src/core/section/actives.js +123 -27
  35. package/src/core/section/constructor.js +615 -206
  36. package/src/core/section/context.js +28 -23
  37. package/src/core/section/documentType.js +561 -0
  38. package/src/editorInjector/_classes.js +19 -5
  39. package/src/editorInjector/_core.js +71 -7
  40. package/src/editorInjector/index.js +63 -1
  41. package/src/events.js +622 -0
  42. package/src/helper/clipboard.js +59 -0
  43. package/src/helper/converter.js +202 -26
  44. package/src/helper/dom/domCheck.js +304 -0
  45. package/src/helper/dom/domQuery.js +669 -0
  46. package/src/helper/dom/domUtils.js +557 -0
  47. package/src/helper/dom/index.js +12 -0
  48. package/src/helper/env.js +46 -56
  49. package/src/helper/index.js +10 -4
  50. package/src/helper/keyCodeMap.js +183 -0
  51. package/src/helper/numbers.js +12 -8
  52. package/src/helper/unicode.js +9 -5
  53. package/src/langs/ckb.js +74 -4
  54. package/src/langs/cs.js +72 -2
  55. package/src/langs/da.js +73 -3
  56. package/src/langs/de.js +73 -4
  57. package/src/langs/en.js +23 -3
  58. package/src/langs/es.js +73 -4
  59. package/src/langs/fa.js +75 -3
  60. package/src/langs/fr.js +73 -3
  61. package/src/langs/he.js +73 -4
  62. package/src/langs/hu.js +230 -0
  63. package/src/langs/index.js +7 -3
  64. package/src/langs/it.js +70 -1
  65. package/src/langs/ja.js +72 -4
  66. package/src/langs/km.js +230 -0
  67. package/src/langs/ko.js +22 -2
  68. package/src/langs/lv.js +74 -5
  69. package/src/langs/nl.js +73 -4
  70. package/src/langs/pl.js +73 -4
  71. package/src/langs/pt_br.js +70 -1
  72. package/src/langs/ro.js +74 -5
  73. package/src/langs/ru.js +73 -4
  74. package/src/langs/se.js +73 -4
  75. package/src/langs/tr.js +73 -1
  76. package/src/langs/{ua.js → uk.js} +75 -6
  77. package/src/langs/ur.js +77 -8
  78. package/src/langs/zh_cn.js +74 -5
  79. package/src/modules/ApiManager.js +77 -54
  80. package/src/modules/Browser.js +667 -0
  81. package/src/modules/ColorPicker.js +162 -102
  82. package/src/modules/Controller.js +273 -142
  83. package/src/modules/Figure.js +925 -484
  84. package/src/modules/FileManager.js +121 -69
  85. package/src/modules/HueSlider.js +113 -61
  86. package/src/modules/Modal.js +291 -122
  87. package/src/modules/ModalAnchorEditor.js +383 -234
  88. package/src/modules/SelectMenu.js +270 -168
  89. package/src/modules/_DragHandle.js +2 -1
  90. package/src/modules/index.js +3 -3
  91. package/src/plugins/browser/audioGallery.js +83 -0
  92. package/src/plugins/browser/fileBrowser.js +103 -0
  93. package/src/plugins/browser/fileGallery.js +83 -0
  94. package/src/plugins/browser/imageGallery.js +81 -0
  95. package/src/plugins/browser/videoGallery.js +103 -0
  96. package/src/plugins/command/blockquote.js +40 -27
  97. package/src/plugins/command/exportPDF.js +134 -0
  98. package/src/plugins/command/fileUpload.js +229 -162
  99. package/src/plugins/command/list_bulleted.js +83 -47
  100. package/src/plugins/command/list_numbered.js +83 -47
  101. package/src/plugins/dropdown/align.js +66 -54
  102. package/src/plugins/dropdown/backgroundColor.js +63 -49
  103. package/src/plugins/dropdown/font.js +71 -47
  104. package/src/plugins/dropdown/fontColor.js +63 -48
  105. package/src/plugins/dropdown/formatBlock.js +70 -33
  106. package/src/plugins/dropdown/hr.js +92 -51
  107. package/src/plugins/dropdown/layout.js +37 -26
  108. package/src/plugins/dropdown/lineHeight.js +54 -38
  109. package/src/plugins/dropdown/list.js +60 -45
  110. package/src/plugins/dropdown/paragraphStyle.js +51 -30
  111. package/src/plugins/dropdown/table.js +2003 -813
  112. package/src/plugins/dropdown/template.js +38 -26
  113. package/src/plugins/dropdown/textStyle.js +43 -31
  114. package/src/plugins/field/mention.js +147 -86
  115. package/src/plugins/index.js +32 -6
  116. package/src/plugins/input/fontSize.js +161 -108
  117. package/src/plugins/input/pageNavigator.js +70 -0
  118. package/src/plugins/modal/audio.js +358 -173
  119. package/src/plugins/modal/drawing.js +531 -0
  120. package/src/plugins/modal/embed.js +886 -0
  121. package/src/plugins/modal/image.js +674 -362
  122. package/src/plugins/modal/link.js +100 -71
  123. package/src/plugins/modal/math.js +367 -167
  124. package/src/plugins/modal/video.js +691 -335
  125. package/src/plugins/popup/anchor.js +222 -0
  126. package/src/suneditor.js +50 -13
  127. package/src/themes/dark.css +122 -0
  128. package/src/typedef.js +130 -0
  129. package/types/assets/icons/defaultIcons.d.ts +153 -0
  130. package/types/core/base/eventHandlers/handler_toolbar.d.ts +41 -0
  131. package/types/core/base/eventHandlers/handler_ww_clipboard.d.ts +40 -0
  132. package/types/core/base/eventHandlers/handler_ww_dragDrop.d.ts +35 -0
  133. package/types/core/base/eventHandlers/handler_ww_key_input.d.ts +45 -0
  134. package/types/core/base/eventHandlers/handler_ww_mouse.d.ts +39 -0
  135. package/types/core/base/eventManager.d.ts +385 -0
  136. package/types/core/base/history.d.ts +81 -0
  137. package/types/core/class/char.d.ts +60 -0
  138. package/types/core/class/component.d.ts +212 -0
  139. package/types/core/class/format.d.ts +616 -0
  140. package/types/core/class/html.d.ts +422 -0
  141. package/types/core/class/menu.d.ts +126 -0
  142. package/types/core/class/nodeTransform.d.ts +93 -0
  143. package/types/core/class/offset.d.ts +522 -0
  144. package/types/core/class/selection.d.ts +188 -0
  145. package/types/core/class/shortcuts.d.ts +142 -0
  146. package/types/core/class/toolbar.d.ts +189 -0
  147. package/types/core/class/ui.d.ts +164 -0
  148. package/types/core/class/viewer.d.ts +140 -0
  149. package/types/core/editor.d.ts +610 -0
  150. package/types/core/section/actives.d.ts +46 -0
  151. package/types/core/section/constructor.d.ts +777 -0
  152. package/types/core/section/context.d.ts +45 -0
  153. package/types/core/section/documentType.d.ts +178 -0
  154. package/types/editorInjector/_classes.d.ts +41 -0
  155. package/types/editorInjector/_core.d.ts +92 -0
  156. package/types/editorInjector/index.d.ts +71 -0
  157. package/types/events.d.ts +273 -0
  158. package/types/helper/clipboard.d.ts +12 -0
  159. package/types/helper/converter.d.ts +197 -0
  160. package/types/helper/dom/domCheck.d.ts +189 -0
  161. package/types/helper/dom/domQuery.d.ts +223 -0
  162. package/types/helper/dom/domUtils.d.ts +226 -0
  163. package/types/helper/dom/index.d.ts +9 -0
  164. package/types/helper/env.d.ts +132 -0
  165. package/types/helper/index.d.ts +174 -0
  166. package/types/helper/keyCodeMap.d.ts +110 -0
  167. package/types/helper/numbers.d.ts +46 -0
  168. package/types/helper/unicode.d.ts +28 -0
  169. package/types/index.d.ts +120 -0
  170. package/{typings/Lang.d.ts → types/langs/_Lang.d.ts} +173 -103
  171. package/types/langs/ckb.d.ts +3 -0
  172. package/types/langs/cs.d.ts +3 -0
  173. package/types/langs/da.d.ts +3 -0
  174. package/types/langs/de.d.ts +3 -0
  175. package/types/langs/en.d.ts +3 -0
  176. package/types/langs/es.d.ts +3 -0
  177. package/types/langs/fa.d.ts +3 -0
  178. package/types/langs/fr.d.ts +3 -0
  179. package/types/langs/he.d.ts +3 -0
  180. package/types/langs/hu.d.ts +3 -0
  181. package/types/langs/index.d.ts +54 -0
  182. package/types/langs/it.d.ts +3 -0
  183. package/types/langs/ja.d.ts +3 -0
  184. package/types/langs/km.d.ts +3 -0
  185. package/types/langs/ko.d.ts +3 -0
  186. package/types/langs/lv.d.ts +3 -0
  187. package/types/langs/nl.d.ts +3 -0
  188. package/types/langs/pl.d.ts +3 -0
  189. package/types/langs/pt_br.d.ts +3 -0
  190. package/types/langs/ro.d.ts +3 -0
  191. package/types/langs/ru.d.ts +3 -0
  192. package/types/langs/se.d.ts +3 -0
  193. package/types/langs/tr.d.ts +3 -0
  194. package/types/langs/uk.d.ts +3 -0
  195. package/types/langs/ur.d.ts +3 -0
  196. package/types/langs/zh_cn.d.ts +3 -0
  197. package/types/modules/ApiManager.d.ts +125 -0
  198. package/types/modules/Browser.d.ts +326 -0
  199. package/types/modules/ColorPicker.d.ts +131 -0
  200. package/types/modules/Controller.d.ts +251 -0
  201. package/types/modules/Figure.d.ts +517 -0
  202. package/types/modules/FileManager.d.ts +202 -0
  203. package/types/modules/HueSlider.d.ts +136 -0
  204. package/types/modules/Modal.d.ts +111 -0
  205. package/types/modules/ModalAnchorEditor.d.ts +236 -0
  206. package/types/modules/SelectMenu.d.ts +194 -0
  207. package/types/modules/_DragHandle.d.ts +7 -0
  208. package/types/modules/index.d.ts +26 -0
  209. package/types/plugins/browser/audioGallery.d.ts +55 -0
  210. package/types/plugins/browser/fileBrowser.d.ts +64 -0
  211. package/types/plugins/browser/fileGallery.d.ts +55 -0
  212. package/types/plugins/browser/imageGallery.d.ts +51 -0
  213. package/types/plugins/browser/videoGallery.d.ts +57 -0
  214. package/types/plugins/command/blockquote.d.ts +28 -0
  215. package/types/plugins/command/exportPDF.d.ts +46 -0
  216. package/types/plugins/command/fileUpload.d.ts +156 -0
  217. package/types/plugins/command/list_bulleted.d.ts +46 -0
  218. package/types/plugins/command/list_numbered.d.ts +46 -0
  219. package/types/plugins/dropdown/align.d.ts +60 -0
  220. package/types/plugins/dropdown/backgroundColor.d.ts +63 -0
  221. package/types/plugins/dropdown/font.d.ts +54 -0
  222. package/types/plugins/dropdown/fontColor.d.ts +63 -0
  223. package/types/plugins/dropdown/formatBlock.d.ts +54 -0
  224. package/types/plugins/dropdown/hr.d.ts +71 -0
  225. package/types/plugins/dropdown/layout.d.ts +40 -0
  226. package/types/plugins/dropdown/lineHeight.d.ts +50 -0
  227. package/types/plugins/dropdown/list.d.ts +39 -0
  228. package/types/plugins/dropdown/paragraphStyle.d.ts +54 -0
  229. package/types/plugins/dropdown/table.d.ts +627 -0
  230. package/types/plugins/dropdown/template.d.ts +40 -0
  231. package/types/plugins/dropdown/textStyle.d.ts +41 -0
  232. package/types/plugins/field/mention.d.ts +102 -0
  233. package/types/plugins/index.d.ts +107 -0
  234. package/types/plugins/input/fontSize.d.ts +170 -0
  235. package/types/plugins/input/pageNavigator.d.ts +28 -0
  236. package/types/plugins/modal/audio.d.ts +269 -0
  237. package/types/plugins/modal/drawing.d.ts +246 -0
  238. package/types/plugins/modal/embed.d.ts +387 -0
  239. package/types/plugins/modal/image.d.ts +451 -0
  240. package/types/plugins/modal/link.d.ts +128 -0
  241. package/types/plugins/modal/math.d.ts +193 -0
  242. package/types/plugins/modal/video.d.ts +485 -0
  243. package/types/plugins/popup/anchor.d.ts +56 -0
  244. package/types/suneditor.d.ts +51 -0
  245. package/types/typedef.d.ts +233 -0
  246. package/.eslintignore +0 -7
  247. package/.eslintrc.json +0 -64
  248. package/src/assets/icons/_default.js +0 -194
  249. package/src/core/base/events.js +0 -320
  250. package/src/core/class/notice.js +0 -42
  251. package/src/helper/domUtils.js +0 -1177
  252. package/src/modules/FileBrowser.js +0 -271
  253. package/src/plugins/command/exportPdf.js +0 -168
  254. package/src/plugins/fileBrowser/imageGallery.js +0 -81
  255. package/src/themes/test.css +0 -61
  256. package/typings/CommandPlugin.d.ts +0 -8
  257. package/typings/DialogPlugin.d.ts +0 -20
  258. package/typings/FileBrowserPlugin.d.ts +0 -30
  259. package/typings/Module.d.ts +0 -15
  260. package/typings/Plugin.d.ts +0 -42
  261. package/typings/SubmenuPlugin.d.ts +0 -8
  262. package/typings/_classes.d.ts +0 -17
  263. package/typings/_colorPicker.d.ts +0 -60
  264. package/typings/_core.d.ts +0 -55
  265. package/typings/align.d.ts +0 -5
  266. package/typings/audio.d.ts +0 -5
  267. package/typings/backgroundColor.d.ts +0 -5
  268. package/typings/blockquote.d.ts +0 -5
  269. package/typings/char.d.ts +0 -39
  270. package/typings/component.d.ts +0 -38
  271. package/typings/context.d.ts +0 -39
  272. package/typings/converter.d.ts +0 -33
  273. package/typings/dialog.d.ts +0 -28
  274. package/typings/domUtils.d.ts +0 -361
  275. package/typings/editor.d.ts +0 -7
  276. package/typings/editor.ts +0 -542
  277. package/typings/env.d.ts +0 -70
  278. package/typings/eventManager.d.ts +0 -37
  279. package/typings/events.d.ts +0 -262
  280. package/typings/fileBrowser.d.ts +0 -42
  281. package/typings/fileManager.d.ts +0 -67
  282. package/typings/font.d.ts +0 -5
  283. package/typings/fontColor.d.ts +0 -5
  284. package/typings/fontSize.d.ts +0 -5
  285. package/typings/format.d.ts +0 -191
  286. package/typings/formatBlock.d.ts +0 -5
  287. package/typings/history.d.ts +0 -48
  288. package/typings/horizontalRule.d.ts +0 -5
  289. package/typings/image.d.ts +0 -5
  290. package/typings/imageGallery.d.ts +0 -5
  291. package/typings/index.d.ts +0 -21
  292. package/typings/index.modules.d.ts +0 -11
  293. package/typings/index.plugins.d.ts +0 -58
  294. package/typings/lineHeight.d.ts +0 -5
  295. package/typings/link.d.ts +0 -5
  296. package/typings/list.d.ts +0 -5
  297. package/typings/math.d.ts +0 -5
  298. package/typings/mediaContainer.d.ts +0 -25
  299. package/typings/mention.d.ts +0 -5
  300. package/typings/node.d.ts +0 -57
  301. package/typings/notice.d.ts +0 -16
  302. package/typings/numbers.d.ts +0 -29
  303. package/typings/offset.d.ts +0 -24
  304. package/typings/options.d.ts +0 -589
  305. package/typings/paragraphStyle.d.ts +0 -5
  306. package/typings/resizing.d.ts +0 -141
  307. package/typings/selection.d.ts +0 -94
  308. package/typings/shortcuts.d.ts +0 -13
  309. package/typings/suneditor.d.ts +0 -9
  310. package/typings/table.d.ts +0 -5
  311. package/typings/template.d.ts +0 -5
  312. package/typings/textStyle.d.ts +0 -5
  313. package/typings/toolbar.d.ts +0 -32
  314. package/typings/unicode.d.ts +0 -25
  315. package/typings/video.d.ts +0 -5
@@ -1,82 +1,128 @@
1
1
  import EditorInjector from '../../editorInjector';
2
2
  import { Modal, Controller, FileManager, Figure, _DragHandle } from '../../modules';
3
- import { domUtils, numbers, env } from '../../helper';
3
+ import { dom, numbers, env } from '../../helper';
4
4
  const { NO_EVENT, ON_OVER_COMPONENT } = env;
5
5
 
6
- const Audio_ = function (editor, pluginOptions) {
7
- // plugin bisic properties
8
- EditorInjector.call(this, editor);
9
- this.title = this.lang.audio;
10
- this.icon = 'audio';
11
-
12
- // define plugin options
13
- this.pluginOptions = {
14
- defaultWidth: !pluginOptions.defaultWidth ? '' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
15
- defaultHeight: !pluginOptions.defaultHeight ? '' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
16
- createFileInput: !!pluginOptions.createFileInput,
17
- createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
18
- uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
19
- uploadHeaders: pluginOptions.uploadHeaders || null,
20
- uploadSizeLimit: /\d+/.test(pluginOptions.uploadSizeLimit) ? numbers.get(pluginOptions.uploadSizeLimit, 0) : null,
21
- uploadSingleSizeLimit: /\d+/.test(pluginOptions.uploadSingleSizeLimit) ? numbers.get(pluginOptions.uploadSingleSizeLimit, 0) : null,
22
- allowMultiple: !!pluginOptions.allowMultiple,
23
- acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'audio/*' : pluginOptions.acceptedFormats.trim() || 'audio/*',
24
- audioTagAttributes: pluginOptions.audioTagAttributes || null
25
- };
26
-
27
- // create HTML
28
- const modalEl = CreateHTML_modal(editor, this.pluginOptions);
29
- const controllerEl = CreateHTML_controller(editor);
30
-
31
- // modules
32
- this.modal = new Modal(this, modalEl);
33
- this.controller = new Controller(this, controllerEl, { position: 'bottom', disabled: true });
34
- this.fileManager = new FileManager(this, {
35
- query: 'audio',
36
- loadHandler: this.events.onAudioLoad,
37
- eventHandler: this.events.onAudioAction
38
- });
39
-
40
- // members
41
- this.figure = new Figure(this, null, {});
42
- this.fileModalWrapper = modalEl.querySelector('.se-flex-input-wrapper');
43
- this.audioInputFile = modalEl.querySelector('.__se__file_input');
44
- this.audioUrlFile = modalEl.querySelector('.se-input-url');
45
- this.preview = modalEl.querySelector('.se-link-preview');
46
- this.defaultWidth = this.pluginOptions.defaultWidth;
47
- this.defaultHeight = this.pluginOptions.defaultHeight;
48
- this.urlValue = '';
49
- this._element = null;
50
-
51
- // init
52
- if (this.audioInputFile) {
53
- this.eventManager.addEvent(modalEl.querySelector('.se-modal-files-edge-button'), 'click', RemoveSelectedFiles.bind(this.audioInputFile, this.audioUrlFile, this.preview));
6
+ /**
7
+ * @typedef {import('../../events').AudioInfo} AudioInfo_audio
8
+ */
9
+
10
+ /**
11
+ * @typedef {Object} AudioPluginOptions
12
+ * @property {string} [defaultWidth="300px"] - The default width of the audio tag (e.g., "300px").
13
+ * @property {string} [defaultHeight="150px"] - The default height of the audio tag (e.g., "150px").
14
+ * @property {boolean} [createFileInput] - Whether to create a file input element.
15
+ * @property {boolean} [createUrlInput] - Whether to create a URL input element (default is true if file input is not created).
16
+ * @property {string} [uploadUrl] - The URL to which files will be uploaded.
17
+ * @property {Object<string, string>} [uploadHeaders] - Headers to include in the file upload request.
18
+ * @property {number} [uploadSizeLimit] - The total upload size limit in bytes.
19
+ * @property {number} [uploadSingleSizeLimit] - The single file size limit in bytes.
20
+ * @property {boolean} [allowMultiple] - Whether to allow multiple file uploads.
21
+ * @property {string} [acceptedFormats="audio/*"] - Accepted file formats (default is "audio/*").
22
+ * @property {Object<string, string>} [audioTagAttributes] - Additional attributes to set on the audio tag.
23
+ */
24
+
25
+ /**
26
+ * @class
27
+ * @description Audio modal plugin.
28
+ */
29
+ class Audio_ extends EditorInjector {
30
+ static key = 'audio';
31
+ static type = 'modal';
32
+ static className = '';
33
+ /**
34
+ * @this {Audio_}
35
+ * @param {HTMLElement} node - The node to check.
36
+ * @returns {HTMLElement|null} Returns a node if the node is a valid component.
37
+ */
38
+ static component(node) {
39
+ return /^AUDIO$/i.test(node?.nodeName) ? node : null;
40
+ }
41
+
42
+ /**
43
+ * @constructor
44
+ * @param {__se__EditorCore} editor - The root editor instance
45
+ * @param {AudioPluginOptions} pluginOptions
46
+ */
47
+ constructor(editor, pluginOptions) {
48
+ // plugin bisic properties
49
+ super(editor);
50
+ this.title = this.lang.audio;
51
+ this.icon = 'audio';
52
+
53
+ // define plugin options
54
+ this.pluginOptions = {
55
+ defaultWidth: !pluginOptions.defaultWidth ? '' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
56
+ defaultHeight: !pluginOptions.defaultHeight ? '' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
57
+ createFileInput: !!pluginOptions.createFileInput,
58
+ createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
59
+ uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
60
+ uploadHeaders: pluginOptions.uploadHeaders || null,
61
+ uploadSizeLimit: numbers.get(pluginOptions.uploadSizeLimit, 0),
62
+ uploadSingleSizeLimit: numbers.get(pluginOptions.uploadSingleSizeLimit, 0),
63
+ allowMultiple: !!pluginOptions.allowMultiple,
64
+ acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'audio/*' : pluginOptions.acceptedFormats.trim() || 'audio/*',
65
+ audioTagAttributes: pluginOptions.audioTagAttributes || null
66
+ };
67
+
68
+ // create HTML
69
+ const modalEl = CreateHTML_modal(editor, this.pluginOptions);
70
+ const controllerEl = CreateHTML_controller(editor);
71
+
72
+ // modules
73
+ this.modal = new Modal(this, modalEl);
74
+ this.controller = new Controller(this, controllerEl, { position: 'bottom', disabled: true });
75
+ this.fileManager = new FileManager(this, {
76
+ query: 'audio',
77
+ loadHandler: this.events.onAudioLoad,
78
+ eventHandler: this.events.onAudioAction
79
+ });
80
+
81
+ // members
82
+ this.figure = new Figure(this, null, {});
83
+
84
+ /** @type {HTMLElement} */
85
+ this.fileModalWrapper = modalEl.querySelector('.se-flex-input-wrapper');
86
+ /** @type {HTMLInputElement} */
87
+ this.audioInputFile = modalEl.querySelector('.__se__file_input');
88
+ /** @type {HTMLInputElement} */
89
+ this.audioUrlFile = modalEl.querySelector('.se-input-url');
90
+ /** @type {HTMLElement} */
91
+ this.preview = modalEl.querySelector('.se-link-preview');
92
+ /** @type {HTMLAudioElement} */
93
+ this._element = null;
94
+
95
+ this.defaultWidth = this.pluginOptions.defaultWidth;
96
+ this.defaultHeight = this.pluginOptions.defaultHeight;
97
+ this.urlValue = '';
98
+
99
+ const galleryButton = modalEl.querySelector('.__se__gallery');
100
+ if (galleryButton) this.eventManager.addEvent(galleryButton, 'click', this.#OpenGallery.bind(this));
101
+
102
+ // init
103
+ if (this.audioInputFile) {
104
+ this.eventManager.addEvent(modalEl.querySelector('.se-modal-files-edge-button'), 'click', this.#RemoveSelectedFiles.bind(this, this.audioUrlFile, this.preview));
105
+ if (this.audioUrlFile) {
106
+ this.eventManager.addEvent(this.audioInputFile, 'change', this.#FileInputChange.bind(this));
107
+ }
108
+ }
54
109
  if (this.audioUrlFile) {
55
- this.eventManager.addEvent(this.audioInputFile, 'change', FileInputChange.bind(this));
110
+ this.eventManager.addEvent(this.audioUrlFile, 'input', this.#OnLinkPreview.bind(this));
56
111
  }
57
112
  }
58
- if (this.audioUrlFile) {
59
- this.eventManager.addEvent(this.audioUrlFile, 'input', OnLinkPreview.bind(this));
60
- }
61
- };
62
-
63
- Audio_.key = 'audio';
64
- Audio_.type = 'modal';
65
- Audio_.className = '';
66
- Audio_.component = function (node) {
67
- return /^AUDIO$/i.test(node?.nodeName) ? node : null;
68
- };
69
- Audio_.prototype = {
113
+
70
114
  /**
71
- * @override type = "modal"
115
+ * @editorMethod Modules.Modal
116
+ * @description Executes the method that is called when a "Modal" module's is opened.
72
117
  */
73
118
  open() {
74
119
  this.modal.open();
75
- },
120
+ }
76
121
 
77
122
  /**
78
- * @override modal
79
- * @param {boolean} isUpdate open state is update
123
+ * @editorMethod Modules.Modal
124
+ * @description Executes the method that is called when a plugin's modal is opened.
125
+ * @param {boolean} isUpdate "Indicates whether the modal is for editing an existing component (true) or registering a new one (false)."
80
126
  */
81
127
  on(isUpdate) {
82
128
  if (!isUpdate) {
@@ -87,63 +133,86 @@ Audio_.prototype = {
87
133
  } else {
88
134
  if (this.audioInputFile && this.pluginOptions.allowMultiple) this.audioInputFile.removeAttribute('multiple');
89
135
  }
90
- },
136
+ }
91
137
 
92
138
  /**
93
- * @description On paste or drop
94
- * @param {*} params { frameContext, event, file }
139
+ * @editorMethod Editor.EventManager
140
+ * @description Executes the event function of "paste" or "drop".
141
+ * @param {Object} params { frameContext, event, file }
142
+ * @param {__se__FrameContext} params.frameContext Frame context
143
+ * @param {ClipboardEvent} params.event Event object
144
+ * @param {File} params.file File object
145
+ * @returns {boolean} - If return false, the file upload will be canceled
95
146
  */
96
- onPastAndDrop({ file }) {
147
+ onFilePasteAndDrop({ file }) {
97
148
  if (!/^audio/.test(file.type)) return;
98
149
 
99
150
  this.submitFile([file]);
100
151
  this.editor.focus();
101
152
 
102
153
  return false;
103
- },
154
+ }
104
155
 
105
156
  /**
106
- * @override modal
107
- * @returns {boolean | undefined}
157
+ * @editorMethod Modules.Modal
158
+ * @description This function is called when a form within a modal window is "submit".
159
+ * @returns {Promise<boolean>} Success or failure
108
160
  */
109
- modalAction() {
161
+ async modalAction() {
110
162
  if (this.audioInputFile && this.audioInputFile?.files.length > 0) {
111
- return this.submitFile(this.audioInputFile.files);
163
+ return await this.submitFile(this.audioInputFile.files);
112
164
  } else if (this.audioUrlFile && this.urlValue.length > 0) {
113
- return this.submitURL(this.urlValue);
165
+ return await this.submitURL(this.urlValue);
114
166
  }
115
167
  return false;
116
- },
168
+ }
117
169
 
118
170
  /**
119
- * @override modal
171
+ * @editorMethod Modules.Modal
172
+ * @description This function is called before the modal window is opened, but before it is closed.
120
173
  */
121
174
  init() {
122
175
  Modal.OnChangeFile(this.fileModalWrapper, []);
123
176
  if (this.audioInputFile) this.audioInputFile.value = '';
124
177
  if (this.audioUrlFile) this.urlValue = this.preview.textContent = this.audioUrlFile.value = '';
125
178
  if (this.audioInputFile && this.audioUrlFile) {
126
- this.audioUrlFile.removeAttribute('disabled');
179
+ this.audioUrlFile.disabled = false;
127
180
  this.preview.style.textDecoration = '';
128
181
  }
129
- },
182
+ }
130
183
 
131
184
  /**
132
- * @override controller
133
- * @param {Element} target Target button element
134
- * @returns
185
+ * @editorMethod Modules.Controller
186
+ * @description Executes the method that is called when a button is clicked in the "controller".
187
+ * @param {HTMLButtonElement} target Target button element
135
188
  */
136
189
  controllerAction(target) {
137
- if (/update/.test(target.getAttribute('data-command'))) {
138
- if (this.audioUrlFile) this.urlValue = this.preview.textContent = this.audioUrlFile.value = this._element.src;
139
- this.open();
140
- } else {
141
- this.destroy();
190
+ switch (target.getAttribute('data-command')) {
191
+ case 'update':
192
+ if (this.audioUrlFile) this.urlValue = this.preview.textContent = this.audioUrlFile.value = this._element.src;
193
+ this.open();
194
+ break;
195
+ case 'copy': {
196
+ const figure = Figure.GetContainer(this._element);
197
+ this.component.copy(figure.container);
198
+ break;
199
+ }
200
+ case 'delete':
201
+ this.destroy();
202
+ break;
142
203
  }
143
- },
204
+ }
144
205
 
145
206
  /**
146
- * @override core
207
+ * @editorMethod Editor.core
208
+ * @description This method is used to validate and preserve the format of the component within the editor.
209
+ * - It ensures that the structure and attributes of the element are maintained and secure.
210
+ * - The method checks if the element is already wrapped in a valid container and updates its attributes if necessary.
211
+ * - If the element isn't properly contained, a new container is created to retain the format.
212
+ * @returns {{query: string, method: (element: HTMLAudioElement) => void}} The format retention object containing the query and method to process the element.
213
+ * - query: The selector query to identify the relevant elements (in this case, 'audio').
214
+ * - method:The function to execute on the element to validate and preserve its format.
215
+ * - The function takes the element as an argument, checks if it is contained correctly, and applies necessary adjustments.
147
216
  */
148
217
  retainFormat() {
149
218
  return {
@@ -154,44 +223,51 @@ Audio_.prototype = {
154
223
 
155
224
  this._setTagAttrs(element);
156
225
  const figure = Figure.CreateContainer(element.cloneNode(true), 'se-flex-component');
157
- this.figure._retainFigureFormat(figure.container, element, null);
226
+ this.figure.retainFigureFormat(figure.container, element, null, this.fileManager);
158
227
  }
159
228
  };
160
- },
229
+ }
161
230
 
162
231
  /**
163
- * @override component, fileManager
164
- * @description Called when a container is selected.
165
- * @param {Element} element Target element
232
+ * @editorMethod Editor.Component
233
+ * @description Executes the method that is called when a component of a plugin is selected.
234
+ * @param {HTMLElement} target Target component element
166
235
  */
167
- select(element) {
168
- this.figure.open(element, { nonResizing: true, nonSizeInfo: true, nonBorder: true, figureTarget: true, __fileManagerInfo: false });
169
- this.ready(element);
170
- },
236
+ select(target) {
237
+ this.figure.open(target, { nonResizing: true, nonSizeInfo: true, nonBorder: true, figureTarget: true, __fileManagerInfo: false });
238
+ this._ready(target);
239
+ }
171
240
 
172
241
  /**
173
- * @override fileManager
242
+ * @private
243
+ * @description Prepares the component for selection.
244
+ * - Ensures that the controller is properly positioned and initialized.
245
+ * - Prevents duplicate event handling if the component is already selected.
246
+ * @param {HTMLElement} target - The selected element.
174
247
  */
175
- ready(target) {
248
+ _ready(target) {
176
249
  if (_DragHandle.get('__overInfo') === ON_OVER_COMPONENT) return;
177
- this._element = target;
250
+ this._element = /** @type {HTMLAudioElement} */ (target);
178
251
  this.controller.open(target, null, { isWWTarget: false, addOffset: null });
179
- },
252
+ }
180
253
 
181
254
  /**
182
- * @override fileManager
255
+ * @editorMethod Editor.Component
256
+ * @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
257
+ * @param {HTMLElement=} target Target element, if null current selected element
258
+ * @returns {Promise<void>}
183
259
  */
184
- async destroy(element) {
185
- element = element || this._element;
260
+ async destroy(target) {
261
+ const element = target || this._element;
186
262
  const figure = Figure.GetContainer(element);
187
263
  const container = figure.container || element;
188
264
  const focusEl = container.previousElementSibling || container.nextElementSibling;
189
265
 
190
- const message = await this.triggerEvent('onAudioDeleteBefore', { target: element, container: figure, url: element.getAttribute('src') });
266
+ const message = await this.triggerEvent('onAudioDeleteBefore', { element: element, container: figure, url: element.getAttribute('src') });
191
267
  if (message === false) return;
192
268
 
193
269
  const emptyDiv = container.parentNode;
194
- domUtils.removeItem(container);
270
+ dom.utils.removeItem(container);
195
271
  this.init();
196
272
  this.controller.close();
197
273
 
@@ -208,8 +284,15 @@ Audio_.prototype = {
208
284
  // focus
209
285
  this.editor.focusEdge(focusEl);
210
286
  this.history.push(false);
211
- },
287
+ }
212
288
 
289
+ /**
290
+ * @private
291
+ * @description Registers uploaded audio files and creates the corresponding audio elements.
292
+ * - Iterates through the uploaded files and inserts them into the editor.
293
+ * @param {AudioInfo_audio} info - Upload metadata, including `isUpdate` flag and `element`.
294
+ * @param {Object<string, *>} response - Server response containing uploaded file details.
295
+ */
213
296
  _register(info, response) {
214
297
  const fileList = response.result;
215
298
 
@@ -220,14 +303,19 @@ Audio_.prototype = {
220
303
  file = { name: fileList[i].name, size: fileList[i].size };
221
304
  this._createComp(oAudio, fileList[i].url, file, info.isUpdate);
222
305
  }
223
- },
306
+ }
224
307
 
308
+ /**
309
+ * @description Create an "audio" component using the provided files.
310
+ * @param {FileList|File[]} fileList File object list
311
+ * @returns {Promise<boolean>} If return false, the file upload will be canceled
312
+ */
225
313
  async submitFile(fileList) {
226
314
  if (fileList.length === 0) return false;
227
315
 
228
316
  let fileSize = 0;
229
317
  const files = [];
230
- const slngleSizeLimit = this.uploadSingleSizeLimit;
318
+ const slngleSizeLimit = this.pluginOptions.uploadSingleSizeLimit;
231
319
  for (let i = 0, len = fileList.length, f, s; i < len; i++) {
232
320
  f = fileList[i];
233
321
  if (!/audio/i.test(f.type)) continue;
@@ -242,7 +330,7 @@ Audio_.prototype = {
242
330
  file: f
243
331
  });
244
332
 
245
- this.notice.open(message === NO_EVENT ? err : message || err);
333
+ this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
246
334
 
247
335
  return false;
248
336
  }
@@ -256,7 +344,7 @@ Audio_.prototype = {
256
344
  const err = '[SUNEDITOR.audioUpload.fail] Size of uploadable total audios: ' + limitSize / 1000 + 'KB';
257
345
  const message = await this.triggerEvent('onAudioUploadError', { error: err, limitSize, currentSize: this.fileManager.getSize(), uploadSize: fileSize });
258
346
 
259
- this.notice.open(message === NO_EVENT ? err : message || err);
347
+ this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
260
348
 
261
349
  return false;
262
350
  }
@@ -273,7 +361,7 @@ Audio_.prototype = {
273
361
  }.bind(this, audioInfo);
274
362
 
275
363
  const result = await this.triggerEvent('onAudioUploadBefore', {
276
- ...audioInfo,
364
+ info: audioInfo,
277
365
  handler
278
366
  });
279
367
 
@@ -284,8 +372,13 @@ Audio_.prototype = {
284
372
  if (result === true || result === NO_EVENT) handler(null);
285
373
 
286
374
  return true;
287
- },
375
+ }
288
376
 
377
+ /**
378
+ * @description Create an "audio" component using the provided url.
379
+ * @param {string} url File url
380
+ * @returns {Promise<boolean>}
381
+ */
289
382
  async submitURL(url) {
290
383
  if (url.length === 0) return false;
291
384
 
@@ -303,7 +396,7 @@ Audio_.prototype = {
303
396
  }.bind(this, audioInfo);
304
397
 
305
398
  const result = await this.triggerEvent('onAudioUploadBefore', {
306
- ...audioInfo,
399
+ info: audioInfo,
307
400
  handler
308
401
  });
309
402
 
@@ -314,15 +407,25 @@ Audio_.prototype = {
314
407
  if (result === true || result === NO_EVENT) handler(null);
315
408
 
316
409
  return true;
317
- },
410
+ }
318
411
 
412
+ /**
413
+ * @private
414
+ * @description Creates or updates an audio component within the editor.
415
+ * - If `isUpdate` is `true`, updates the existing element's `src`.
416
+ * - Otherwise, inserts a new audio component with the given file.
417
+ * @param {HTMLAudioElement} element - The target audio element.
418
+ * @param {string} src - The source URL of the audio file.
419
+ * @param {{name: string, size: number}} file - The file metadata (name, size).
420
+ * @param {boolean} isUpdate - Whether to update an existing element.
421
+ */
319
422
  _createComp(element, src, file, isUpdate) {
320
423
  // create new tag
321
424
  if (!isUpdate) {
322
425
  this.fileManager.setFileData(element, file);
323
426
  element.src = src;
324
427
  const figure = Figure.CreateContainer(element, 'se-flex-component');
325
- if (!this.component.insert(figure.container, false, !this.options.get('componentAutoSelect'))) {
428
+ if (!this.component.insert(figure.container, { skipCharCount: false, skipSelection: !this.options.get('componentAutoSelect'), skipHistory: false })) {
326
429
  this.editor.focus();
327
430
  return;
328
431
  }
@@ -335,26 +438,39 @@ Audio_.prototype = {
335
438
  this.fileManager.setFileData(element, file);
336
439
  if (element && element.src !== src) {
337
440
  element.src = src;
338
- this.component.select(element, Audio_.key, false);
441
+ this.component.select(element, Audio_.key);
339
442
  } else {
340
- this.component.select(element, Audio_.key, false);
443
+ this.component.select(element, Audio_.key);
341
444
  return;
342
445
  }
343
446
  }
344
447
 
345
448
  if (isUpdate) this.history.push(false);
346
- },
449
+ }
347
450
 
451
+ /**
452
+ * @private
453
+ * @description Creates a new `<audio>` element with default attributes.
454
+ * - Applies width, height, and additional attributes from plugin options.
455
+ * @returns {HTMLAudioElement} - The newly created `<audio>` element.
456
+ */
348
457
  _createAudioTag() {
349
458
  const w = this.defaultWidth;
350
459
  const h = this.defaultHeight;
351
- const oAudio = domUtils.createElement('AUDIO', { style: (w ? 'width:' + w + '; ' : '') + (h ? 'height:' + h + ';' : '') });
460
+ /** @type {HTMLAudioElement} */
461
+ const oAudio = dom.utils.createElement('AUDIO', { style: (w ? 'width:' + w + '; ' : '') + (h ? 'height:' + h + ';' : '') });
352
462
  this._setTagAttrs(oAudio);
353
463
  return oAudio;
354
- },
464
+ }
355
465
 
466
+ /**
467
+ * @private
468
+ * @description Sets attributes on an audio element based on plugin options.
469
+ * - Adds the `controls` attribute and applies any custom attributes.
470
+ * @param {HTMLElement} element - The `<audio>` element to modify.
471
+ */
356
472
  _setTagAttrs(element) {
357
- element.setAttribute('controls', true);
473
+ element.setAttribute('controls', 'true');
358
474
 
359
475
  const attrs = this.pluginOptions.audioTagAttributes;
360
476
  if (!attrs) return;
@@ -362,74 +478,127 @@ Audio_.prototype = {
362
478
  for (const key in attrs) {
363
479
  element.setAttribute(key, attrs[key]);
364
480
  }
365
- },
481
+ }
366
482
 
483
+ /**
484
+ * @private
485
+ * @description Uploads audio files to the server.
486
+ * - Sends a request to the configured upload URL and processes the response.
487
+ * @param {AudioInfo_audio} info - Upload metadata, including `files` and `isUpdate`.
488
+ * @param {FileList|File[]} files - The files to be uploaded.
489
+ */
367
490
  _serverUpload(info, files) {
368
491
  if (!files) return;
369
492
 
370
493
  const uploadFiles = this.modal.isUpdate ? [files[0]] : files;
371
- this.fileManager.upload(this.pluginOptions.uploadUrl, this.pluginOptions.uploadHeaders, uploadFiles, UploadCallBack.bind(this, info), this._error.bind(this));
372
- },
494
+ this.fileManager.upload(this.pluginOptions.uploadUrl, this.pluginOptions.uploadHeaders, uploadFiles, this.#UploadCallBack.bind(this, info), this._error.bind(this));
495
+ }
373
496
 
497
+ /**
498
+ * @private
499
+ * @description Handles errors that occur during the audio upload process.
500
+ * - Triggers the `onAudioUploadError` event to allow custom handling of errors.
501
+ * - Displays an error message in the editor's UI.
502
+ * - Logs the error to the console for debugging.
503
+ * @param {Object<string, *>} response - The error response object from the server or upload process.
504
+ * @returns {Promise<void>}
505
+ */
374
506
  async _error(response) {
375
507
  const message = await this.triggerEvent('onAudioUploadError', { error: response });
376
508
  const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
377
- this.notice.open(err);
509
+ this.ui.alertOpen(err, 'error');
378
510
  console.error('[SUNEDITOR.plugin.audio.error]', err);
379
- },
380
-
381
- constructor: Audio_
382
- };
511
+ }
383
512
 
384
- async function UploadCallBack(info, xmlHttp) {
385
- if ((await this.triggerEvent('audioUploadHandler', { xmlHttp, info })) === NO_EVENT) {
386
- const response = JSON.parse(xmlHttp.responseText);
387
- if (response.errorMessage) {
388
- this._error(response);
389
- } else {
390
- this._register(info, response);
513
+ /**
514
+ * @description Handles the server response after a file upload.
515
+ * - If the upload is successful, registers the uploaded audio.
516
+ * - If an error occurs, triggers an error event.
517
+ * @param {AudioInfo_audio} info - Upload metadata.
518
+ * @param {XMLHttpRequest} xmlHttp - The completed XHR request.
519
+ */
520
+ async #UploadCallBack(info, xmlHttp) {
521
+ if ((await this.triggerEvent('audioUploadHandler', { xmlHttp, info })) === NO_EVENT) {
522
+ const response = JSON.parse(xmlHttp.responseText);
523
+ if (response.errorMessage) {
524
+ this._error(response);
525
+ } else {
526
+ this._register(info, response);
527
+ }
391
528
  }
392
529
  }
393
- }
394
530
 
395
- function OnLinkPreview(e) {
396
- const value = e.target.value.trim();
397
- this.urlValue = this.preview.textContent = !value
398
- ? ''
399
- : this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
400
- ? this.options.get('defaultUrlProtocol') + value
401
- : !value.includes('://')
402
- ? '/' + value
403
- : value;
404
- }
531
+ /**
532
+ * @description Updates the preview text for the entered audio URL.
533
+ * - Formats the URL correctly based on the editor’s settings.
534
+ * @param {InputEvent} e - The input event triggered when the user types a URL.
535
+ */
536
+ #OnLinkPreview(e) {
537
+ /** @type {HTMLInputElement} */
538
+ const target = dom.query.getEventTarget(e);
539
+ const value = target.value.trim();
540
+ this.urlValue = this.preview.textContent = !value
541
+ ? ''
542
+ : this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
543
+ ? this.options.get('defaultUrlProtocol') + value
544
+ : !value.includes('://')
545
+ ? '/' + value
546
+ : value;
547
+ }
405
548
 
406
- // Disable url input when uploading files
407
- function RemoveSelectedFiles(urlInput, preview) {
408
- this.value = '';
409
- if (urlInput) {
410
- urlInput.removeAttribute('disabled');
411
- preview.style.textDecoration = '';
549
+ /**
550
+ * @description Opens the audio gallery plugin, if available.
551
+ * - Calls a function to populate the URL input with the selected audio file.
552
+ */
553
+ #OpenGallery() {
554
+ this.plugins.audioGallery.open(this.#SetUrlInput.bind(this));
412
555
  }
413
556
 
414
- // inputFile check
415
- Modal.OnChangeFile(this.fileModalWrapper, []);
416
- }
557
+ /**
558
+ * @param {HTMLInputElement} target - The target element.
559
+ */
560
+ #SetUrlInput(target) {
561
+ this.urlValue = this.preview.textContent = this.audioUrlFile.value = target.getAttribute('data-command') || target.src;
562
+ this.audioUrlFile.focus();
563
+ }
417
564
 
418
- // Disable url input when uploading files
419
- function FileInputChange({ target }) {
420
- if (!this.audioInputFile.value) {
421
- this.audioUrlFile.removeAttribute('disabled');
422
- this.preview.style.textDecoration = '';
423
- } else {
424
- this.audioUrlFile.setAttribute('disabled', true);
425
- this.preview.style.textDecoration = 'line-through';
565
+ /**
566
+ * @description Clears the selected file input and re-enables the URL input.
567
+ * - Ensures that only one input method (file or URL) is used at a time.
568
+ * @param {HTMLInputElement} urlInput - The URL input field.
569
+ * @param {HTMLElement} preview - The preview text element.
570
+ */
571
+ #RemoveSelectedFiles(urlInput, preview) {
572
+ this.audioInputFile.value = '';
573
+ if (urlInput) {
574
+ urlInput.disabled = false;
575
+ preview.style.textDecoration = '';
576
+ }
577
+
578
+ // inputFile check
579
+ Modal.OnChangeFile(this.fileModalWrapper, []);
426
580
  }
427
581
 
428
- // inputFile check
429
- Modal.OnChangeFile(this.fileModalWrapper, target.files);
582
+ /**
583
+ * @param {InputEvent} e - Event object
584
+ */
585
+ #FileInputChange(e) {
586
+ /** @type {HTMLInputElement} */
587
+ const target = dom.query.getEventTarget(e);
588
+ if (!this.audioInputFile.value) {
589
+ this.audioUrlFile.disabled = false;
590
+ this.preview.style.textDecoration = '';
591
+ } else {
592
+ this.audioUrlFile.disabled = true;
593
+ this.preview.style.textDecoration = 'line-through';
594
+ }
595
+
596
+ // inputFile check
597
+ Modal.OnChangeFile(this.fileModalWrapper, target.files);
598
+ }
430
599
  }
431
600
 
432
- function CreateHTML_modal({ lang, icons }, pluginOptions) {
601
+ function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
433
602
  let html = /*html*/ `
434
603
  <form method="post" enctype="multipart/form-data">
435
604
  <div class="se-modal-header">
@@ -450,7 +619,17 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
450
619
  html += /*html*/ `
451
620
  <div class="se-modal-form">
452
621
  <label>${lang.audio_modal_url}</label>
453
- <input class="se-input-form se-input-url" data-focus type="text" />
622
+ <div class="se-modal-form-files">
623
+ <input class="se-input-form se-input-url" data-focus type="text" />
624
+ ${
625
+ plugins.audioGallery
626
+ ? `<button type="button" class="se-btn se-tooltip se-modal-files-edge-button __se__gallery" aria-label="${lang.audioGallery}">
627
+ ${icons.audio_gallery}
628
+ ${dom.utils.createTooltipInner(lang.audioGallery)}
629
+ </button>`
630
+ : ''
631
+ }
632
+ </div>
454
633
  <pre class="se-link-preview"></pre>
455
634
  </div>`;
456
635
  }
@@ -463,7 +642,7 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
463
642
  </div>
464
643
  </form>`;
465
644
 
466
- return domUtils.createElement('DIV', { class: 'se-modal-content' }, html);
645
+ return dom.utils.createElement('DIV', { class: 'se-modal-content' }, html);
467
646
  }
468
647
 
469
648
  function CreateHTML_controller({ lang, icons }) {
@@ -477,6 +656,12 @@ function CreateHTML_controller({ lang, icons }) {
477
656
  <span class="se-tooltip-text">${lang.edit}</span>
478
657
  </span>
479
658
  </button>
659
+ <button type="button" data-command="copy" tabindex="-1" class="se-btn se-tooltip">
660
+ ${icons.copy}
661
+ <span class="se-tooltip-inner">
662
+ <span class="se-tooltip-text">${lang.copy}</span>
663
+ </span>
664
+ </button>
480
665
  <button type="button" data-command="delete" tabindex="-1" class="se-btn se-tooltip">
481
666
  ${icons.delete}
482
667
  <span class="se-tooltip-inner">
@@ -486,7 +671,7 @@ function CreateHTML_controller({ lang, icons }) {
486
671
  </div>
487
672
  </div>`;
488
673
 
489
- return domUtils.createElement('DIV', { class: 'se-controller' }, html);
674
+ return dom.utils.createElement('DIV', { class: 'se-controller' }, html);
490
675
  }
491
676
 
492
677
  export default Audio_;