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.
- package/README.md +65 -57
- package/dist/suneditor-contents.min.css +1 -0
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +110 -61
- package/src/assets/design/color.css +36 -17
- package/src/assets/design/size.css +2 -0
- package/src/assets/icons/defaultIcons.js +17 -2
- package/src/assets/suneditor-contents.css +51 -16
- package/src/assets/suneditor.css +116 -43
- package/src/core/config/contextProvider.js +288 -0
- package/src/core/config/eventManager.js +188 -0
- package/src/core/config/instanceCheck.js +59 -0
- package/src/core/config/optionProvider.js +452 -0
- package/src/core/editor.js +166 -1637
- package/src/core/event/actions/index.js +229 -0
- package/src/core/event/effects/common.registry.js +74 -0
- package/src/core/event/effects/keydown.registry.js +573 -0
- package/src/core/event/effects/ruleHelpers.js +148 -0
- package/src/core/event/eventOrchestrator.js +944 -0
- package/src/core/event/executor.js +27 -0
- package/src/core/{base/eventHandlers → event/handlers}/handler_toolbar.js +27 -28
- package/src/core/{base/eventHandlers → event/handlers}/handler_ww_clipboard.js +10 -8
- package/src/core/{base/eventHandlers → event/handlers}/handler_ww_dragDrop.js +22 -23
- package/src/core/event/handlers/handler_ww_input.js +75 -0
- package/src/core/event/handlers/handler_ww_key.js +228 -0
- package/src/core/event/handlers/handler_ww_mouse.js +166 -0
- package/src/core/event/ports.js +211 -0
- package/src/core/event/reducers/keydown.reducer.js +97 -0
- package/src/core/event/rules/keydown.rule.arrow.js +63 -0
- package/src/core/event/rules/keydown.rule.backspace.js +208 -0
- package/src/core/event/rules/keydown.rule.delete.js +132 -0
- package/src/core/event/rules/keydown.rule.enter.js +150 -0
- package/src/core/event/rules/keydown.rule.tab.js +35 -0
- package/src/core/event/support/defaultLineManager.js +136 -0
- package/src/core/event/support/selectionState.js +204 -0
- package/src/core/kernel/coreKernel.js +320 -0
- package/src/core/kernel/kernelInjector.js +19 -0
- package/src/core/kernel/store.js +173 -0
- package/src/core/{class → logic/dom}/char.js +42 -45
- package/src/core/logic/dom/format.js +1075 -0
- package/src/core/{class → logic/dom}/html.js +743 -624
- package/src/core/logic/dom/inline.js +1847 -0
- package/src/core/logic/dom/listFormat.js +601 -0
- package/src/core/{class → logic/dom}/nodeTransform.js +92 -72
- package/src/core/{class → logic/dom}/offset.js +254 -317
- package/src/core/logic/dom/selection.js +754 -0
- package/src/core/logic/panel/menu.js +389 -0
- package/src/core/logic/panel/toolbar.js +449 -0
- package/src/core/logic/panel/viewer.js +761 -0
- package/src/core/logic/shell/_commandExecutor.js +380 -0
- package/src/core/logic/shell/commandDispatcher.js +241 -0
- package/src/core/logic/shell/component.js +970 -0
- package/src/core/logic/shell/focusManager.js +110 -0
- package/src/core/{base → logic/shell}/history.js +110 -60
- package/src/core/logic/shell/pluginManager.js +363 -0
- package/src/core/logic/shell/shortcuts.js +130 -0
- package/src/core/logic/shell/ui.js +904 -0
- package/src/core/schema/context.js +66 -0
- package/src/core/schema/frameContext.js +160 -0
- package/src/core/schema/options.js +628 -0
- package/src/core/section/constructor.js +194 -500
- package/src/core/section/documentType.js +297 -222
- package/src/events.js +808 -543
- package/src/helper/clipboard.js +27 -16
- package/src/helper/converter.js +100 -78
- package/src/helper/dom/domCheck.js +56 -30
- package/src/helper/dom/domQuery.js +159 -89
- package/src/helper/dom/domUtils.js +114 -49
- package/src/helper/dom/index.js +5 -1
- package/src/helper/env.js +26 -26
- package/src/helper/index.js +1 -1
- package/src/helper/keyCodeMap.js +25 -28
- package/src/helper/numbers.js +4 -8
- package/src/helper/unicode.js +4 -8
- package/src/hooks/base.js +307 -0
- package/src/hooks/params.js +130 -0
- package/src/interfaces/contracts.js +227 -0
- package/src/interfaces/index.js +7 -0
- package/src/interfaces/plugins.js +239 -0
- package/src/langs/ckb.js +4 -4
- package/src/langs/cs.js +4 -4
- package/src/langs/da.js +4 -4
- package/src/langs/de.js +4 -4
- package/src/langs/en.js +4 -4
- package/src/langs/es.js +4 -4
- package/src/langs/fa.js +4 -4
- package/src/langs/fr.js +4 -4
- package/src/langs/he.js +4 -4
- package/src/langs/hu.js +4 -4
- package/src/langs/it.js +4 -4
- package/src/langs/ja.js +4 -4
- package/src/langs/km.js +4 -4
- package/src/langs/ko.js +4 -4
- package/src/langs/lv.js +4 -4
- package/src/langs/nl.js +4 -4
- package/src/langs/pl.js +4 -4
- package/src/langs/pt_br.js +13 -13
- package/src/langs/ro.js +4 -4
- package/src/langs/ru.js +4 -4
- package/src/langs/se.js +4 -4
- package/src/langs/tr.js +4 -4
- package/src/langs/uk.js +4 -4
- package/src/langs/ur.js +4 -4
- package/src/langs/zh_cn.js +4 -4
- package/src/modules/{Browser.js → contract/Browser.js} +119 -128
- package/src/modules/{ColorPicker.js → contract/ColorPicker.js} +132 -142
- package/src/modules/contract/Controller.js +589 -0
- package/src/modules/{Figure.js → contract/Figure.js} +591 -411
- package/src/modules/{HueSlider.js → contract/HueSlider.js} +125 -86
- package/src/modules/contract/Modal.js +357 -0
- package/src/modules/contract/index.js +9 -0
- package/src/modules/manager/ApiManager.js +197 -0
- package/src/modules/{FileManager.js → manager/FileManager.js} +128 -160
- package/src/modules/manager/index.js +5 -0
- package/src/modules/{ModalAnchorEditor.js → ui/ModalAnchorEditor.js} +108 -138
- package/src/modules/{SelectMenu.js → ui/SelectMenu.js} +119 -120
- package/src/modules/{_DragHandle.js → ui/_DragHandle.js} +1 -1
- package/src/modules/ui/index.js +6 -0
- package/src/plugins/browser/audioGallery.js +23 -26
- package/src/plugins/browser/fileBrowser.js +25 -28
- package/src/plugins/browser/fileGallery.js +20 -23
- package/src/plugins/browser/imageGallery.js +24 -23
- package/src/plugins/browser/videoGallery.js +27 -29
- package/src/plugins/command/blockquote.js +11 -17
- package/src/plugins/command/exportPDF.js +26 -26
- package/src/plugins/command/fileUpload.js +138 -133
- package/src/plugins/command/list_bulleted.js +48 -44
- package/src/plugins/command/list_numbered.js +48 -44
- package/src/plugins/dropdown/align.js +64 -50
- package/src/plugins/dropdown/backgroundColor.js +34 -35
- package/src/plugins/dropdown/{formatBlock.js → blockStyle.js} +43 -37
- package/src/plugins/dropdown/font.js +50 -36
- package/src/plugins/dropdown/fontColor.js +34 -35
- package/src/plugins/dropdown/hr.js +55 -50
- package/src/plugins/dropdown/layout.js +20 -15
- package/src/plugins/dropdown/lineHeight.js +46 -30
- package/src/plugins/dropdown/list.js +32 -33
- package/src/plugins/dropdown/paragraphStyle.js +40 -34
- package/src/plugins/dropdown/table/index.js +915 -0
- package/src/plugins/dropdown/table/render/table.html.js +308 -0
- package/src/plugins/dropdown/table/render/table.menu.js +121 -0
- package/src/plugins/dropdown/table/services/table.cell.js +465 -0
- package/src/plugins/dropdown/table/services/table.clipboard.js +414 -0
- package/src/plugins/dropdown/table/services/table.grid.js +504 -0
- package/src/plugins/dropdown/table/services/table.resize.js +463 -0
- package/src/plugins/dropdown/table/services/table.selection.js +466 -0
- package/src/plugins/dropdown/table/services/table.style.js +844 -0
- package/src/plugins/dropdown/table/shared/table.constants.js +109 -0
- package/src/plugins/dropdown/table/shared/table.utils.js +219 -0
- package/src/plugins/dropdown/template.js +20 -15
- package/src/plugins/dropdown/textStyle.js +28 -22
- package/src/plugins/field/mention.js +54 -49
- package/src/plugins/index.js +5 -5
- package/src/plugins/input/fontSize.js +100 -97
- package/src/plugins/input/pageNavigator.js +13 -10
- package/src/plugins/modal/audio.js +208 -219
- package/src/plugins/modal/drawing.js +99 -104
- package/src/plugins/modal/embed.js +323 -312
- package/src/plugins/modal/image/index.js +942 -0
- package/src/plugins/modal/image/render/image.html.js +150 -0
- package/src/plugins/modal/image/services/image.size.js +198 -0
- package/src/plugins/modal/image/services/image.upload.js +216 -0
- package/src/plugins/modal/image/shared/image.constants.js +20 -0
- package/src/plugins/modal/link.js +74 -54
- package/src/plugins/modal/math.js +126 -119
- package/src/plugins/modal/video/index.js +858 -0
- package/src/plugins/modal/video/render/video.html.js +131 -0
- package/src/plugins/modal/video/services/video.size.js +281 -0
- package/src/plugins/modal/video/services/video.upload.js +92 -0
- package/src/plugins/popup/anchor.js +57 -49
- package/src/suneditor.js +73 -61
- package/src/themes/cobalt.css +155 -0
- package/src/themes/dark.css +143 -120
- package/src/typedef.js +214 -63
- package/types/assets/icons/defaultIcons.d.ts +8 -0
- package/types/assets/suneditor-contents.css.d.ts +1 -0
- package/types/assets/suneditor.css.d.ts +1 -0
- package/types/core/config/contextProvider.d.ts +148 -0
- package/types/core/config/eventManager.d.ts +68 -0
- package/types/core/config/instanceCheck.d.ts +33 -0
- package/types/core/config/optionProvider.d.ts +147 -0
- package/types/core/editor.d.ts +27 -586
- package/types/core/event/actions/index.d.ts +50 -0
- package/types/core/event/effects/common.registry.d.ts +56 -0
- package/types/core/event/effects/keydown.registry.d.ts +80 -0
- package/types/core/event/effects/ruleHelpers.d.ts +36 -0
- package/types/core/event/eventOrchestrator.d.ts +191 -0
- package/types/core/event/executor.d.ts +13 -0
- package/types/core/event/handlers/handler_toolbar.d.ts +38 -0
- package/types/core/event/handlers/handler_ww_clipboard.d.ts +36 -0
- package/types/core/event/handlers/handler_ww_dragDrop.d.ts +26 -0
- package/types/core/event/handlers/handler_ww_input.d.ts +38 -0
- package/types/core/event/handlers/handler_ww_key.d.ts +40 -0
- package/types/core/event/handlers/handler_ww_mouse.d.ts +47 -0
- package/types/core/event/ports.d.ts +256 -0
- package/types/core/event/reducers/keydown.reducer.d.ts +84 -0
- package/types/core/event/rules/keydown.rule.arrow.d.ts +19 -0
- package/types/core/event/rules/keydown.rule.backspace.d.ts +18 -0
- package/types/core/event/rules/keydown.rule.delete.d.ts +18 -0
- package/types/core/event/rules/keydown.rule.enter.d.ts +18 -0
- package/types/core/event/rules/keydown.rule.tab.d.ts +18 -0
- package/types/core/event/support/defaultLineManager.d.ts +22 -0
- package/types/core/event/support/selectionState.d.ts +29 -0
- package/types/core/kernel/coreKernel.d.ts +219 -0
- package/types/core/kernel/kernelInjector.d.ts +16 -0
- package/types/core/kernel/store.d.ts +170 -0
- package/types/core/logic/dom/char.d.ts +46 -0
- package/types/core/logic/dom/format.d.ts +234 -0
- package/types/core/logic/dom/html.d.ts +290 -0
- package/types/core/logic/dom/inline.d.ts +93 -0
- package/types/core/logic/dom/listFormat.d.ts +101 -0
- package/types/core/logic/dom/nodeTransform.d.ts +110 -0
- package/types/core/logic/dom/offset.d.ts +335 -0
- package/types/core/logic/dom/selection.d.ts +165 -0
- package/types/core/logic/panel/menu.d.ts +93 -0
- package/types/core/logic/panel/toolbar.d.ts +128 -0
- package/types/core/logic/panel/viewer.d.ts +89 -0
- package/types/core/logic/shell/_commandExecutor.d.ts +18 -0
- package/types/core/logic/shell/commandDispatcher.d.ts +65 -0
- package/types/core/logic/shell/component.d.ts +182 -0
- package/types/core/logic/shell/focusManager.d.ts +31 -0
- package/types/core/{base → logic/shell}/history.d.ts +13 -12
- package/types/core/logic/shell/pluginManager.d.ts +115 -0
- package/types/core/logic/shell/shortcuts.d.ts +131 -0
- package/types/core/logic/shell/ui.d.ts +261 -0
- package/types/core/schema/context.d.ts +104 -0
- package/types/core/schema/frameContext.d.ts +320 -0
- package/types/core/schema/options.d.ts +1241 -0
- package/types/core/section/constructor.d.ts +117 -652
- package/types/core/section/documentType.d.ts +43 -61
- package/types/events.d.ts +796 -65
- package/types/helper/clipboard.d.ts +5 -4
- package/types/helper/converter.d.ts +55 -43
- package/types/helper/dom/domCheck.d.ts +27 -19
- package/types/helper/dom/domQuery.d.ts +76 -57
- package/types/helper/dom/domUtils.d.ts +62 -39
- package/types/helper/dom/index.d.ts +87 -1
- package/types/helper/env.d.ts +16 -13
- package/types/helper/index.d.ts +8 -2
- package/types/helper/keyCodeMap.d.ts +24 -23
- package/types/helper/numbers.d.ts +4 -6
- package/types/helper/unicode.d.ts +4 -3
- package/types/hooks/base.d.ts +239 -0
- package/types/hooks/params.d.ts +65 -0
- package/types/index.d.ts +20 -117
- package/types/interfaces/contracts.d.ts +183 -0
- package/types/interfaces/index.d.ts +3 -0
- package/types/interfaces/plugins.d.ts +168 -0
- package/types/langs/_Lang.d.ts +2 -2
- package/types/langs/index.d.ts +2 -2
- package/types/modules/contract/Browser.d.ts +262 -0
- package/types/modules/contract/ColorPicker.d.ts +99 -0
- package/types/modules/contract/Controller.d.ts +204 -0
- package/types/modules/contract/Figure.d.ts +529 -0
- package/types/modules/{HueSlider.d.ts → contract/HueSlider.d.ts} +39 -28
- package/types/modules/contract/Modal.d.ts +62 -0
- package/types/modules/contract/index.d.ts +7 -0
- package/types/modules/manager/ApiManager.d.ts +106 -0
- package/types/modules/manager/FileManager.d.ts +124 -0
- package/types/modules/manager/index.d.ts +3 -0
- package/types/modules/ui/ModalAnchorEditor.d.ts +152 -0
- package/types/modules/ui/SelectMenu.d.ts +107 -0
- package/types/modules/{_DragHandle.d.ts → ui/_DragHandle.d.ts} +1 -0
- package/types/modules/ui/index.d.ts +4 -0
- package/types/plugins/browser/audioGallery.d.ts +33 -41
- package/types/plugins/browser/fileBrowser.d.ts +42 -50
- package/types/plugins/browser/fileGallery.d.ts +33 -41
- package/types/plugins/browser/imageGallery.d.ts +30 -37
- package/types/plugins/browser/videoGallery.d.ts +33 -41
- package/types/plugins/command/blockquote.d.ts +4 -21
- package/types/plugins/command/exportPDF.d.ts +23 -33
- package/types/plugins/command/fileUpload.d.ts +80 -100
- package/types/plugins/command/list_bulleted.d.ts +9 -35
- package/types/plugins/command/list_numbered.d.ts +9 -35
- package/types/plugins/dropdown/align.d.ts +23 -46
- package/types/plugins/dropdown/backgroundColor.d.ts +35 -53
- package/types/plugins/dropdown/blockStyle.d.ts +45 -0
- package/types/plugins/dropdown/font.d.ts +18 -41
- package/types/plugins/dropdown/fontColor.d.ts +35 -53
- package/types/plugins/dropdown/hr.d.ts +26 -52
- package/types/plugins/dropdown/layout.d.ts +19 -25
- package/types/plugins/dropdown/lineHeight.d.ts +21 -39
- package/types/plugins/dropdown/list.d.ts +6 -34
- package/types/plugins/dropdown/paragraphStyle.d.ts +34 -45
- package/types/plugins/dropdown/table/index.d.ts +158 -0
- package/types/plugins/dropdown/table/render/table.html.d.ts +71 -0
- package/types/plugins/dropdown/table/render/table.menu.d.ts +59 -0
- package/types/plugins/dropdown/table/services/table.cell.d.ts +76 -0
- package/types/plugins/dropdown/table/services/table.clipboard.d.ts +26 -0
- package/types/plugins/dropdown/table/services/table.grid.d.ts +77 -0
- package/types/plugins/dropdown/table/services/table.resize.d.ts +72 -0
- package/types/plugins/dropdown/table/services/table.selection.d.ts +59 -0
- package/types/plugins/dropdown/table/services/table.style.d.ts +162 -0
- package/types/plugins/dropdown/table/shared/table.constants.d.ts +134 -0
- package/types/plugins/dropdown/table/shared/table.utils.d.ts +91 -0
- package/types/plugins/dropdown/template.d.ts +19 -25
- package/types/plugins/dropdown/textStyle.d.ts +23 -30
- package/types/plugins/field/mention.d.ts +66 -72
- package/types/plugins/index.d.ts +41 -40
- package/types/plugins/input/fontSize.d.ts +57 -96
- package/types/plugins/input/pageNavigator.d.ts +5 -8
- package/types/plugins/modal/audio.d.ts +60 -153
- package/types/plugins/modal/drawing.d.ts +16 -118
- package/types/plugins/modal/embed.d.ts +46 -166
- package/types/plugins/modal/image/index.d.ts +281 -0
- package/types/plugins/modal/image/render/image.html.d.ts +45 -0
- package/types/plugins/modal/image/services/image.size.d.ts +55 -0
- package/types/plugins/modal/image/services/image.upload.d.ts +24 -0
- package/types/plugins/modal/image/shared/image.constants.d.ts +17 -0
- package/types/plugins/modal/link.d.ts +46 -66
- package/types/plugins/modal/math.d.ts +17 -86
- package/types/plugins/modal/{video.d.ts → video/index.d.ts} +89 -221
- package/types/plugins/modal/video/render/video.html.d.ts +37 -0
- package/types/plugins/modal/video/services/video.size.d.ts +74 -0
- package/types/plugins/modal/video/services/video.upload.d.ts +19 -0
- package/types/plugins/popup/anchor.d.ts +8 -38
- package/types/suneditor.d.ts +55 -24
- package/types/typedef.d.ts +344 -228
- package/CONTRIBUTING.md +0 -186
- package/src/core/base/eventHandlers/handler_ww_key_input.js +0 -1200
- package/src/core/base/eventHandlers/handler_ww_mouse.js +0 -194
- package/src/core/base/eventManager.js +0 -1523
- package/src/core/class/component.js +0 -856
- package/src/core/class/format.js +0 -3433
- package/src/core/class/menu.js +0 -346
- package/src/core/class/selection.js +0 -610
- package/src/core/class/shortcuts.js +0 -98
- package/src/core/class/toolbar.js +0 -431
- package/src/core/class/ui.js +0 -424
- package/src/core/class/viewer.js +0 -750
- package/src/core/section/actives.js +0 -266
- package/src/core/section/context.js +0 -102
- package/src/editorInjector/_classes.js +0 -36
- package/src/editorInjector/_core.js +0 -87
- package/src/editorInjector/index.js +0 -73
- package/src/modules/ApiManager.js +0 -191
- package/src/modules/Controller.js +0 -474
- package/src/modules/Modal.js +0 -346
- package/src/modules/index.js +0 -14
- package/src/plugins/dropdown/table.js +0 -4034
- package/src/plugins/modal/image.js +0 -1376
- package/src/plugins/modal/video.js +0 -1226
- package/types/core/base/eventHandlers/handler_toolbar.d.ts +0 -41
- package/types/core/base/eventHandlers/handler_ww_clipboard.d.ts +0 -40
- package/types/core/base/eventHandlers/handler_ww_dragDrop.d.ts +0 -35
- package/types/core/base/eventHandlers/handler_ww_key_input.d.ts +0 -45
- package/types/core/base/eventHandlers/handler_ww_mouse.d.ts +0 -39
- package/types/core/base/eventManager.d.ts +0 -401
- package/types/core/class/char.d.ts +0 -61
- package/types/core/class/component.d.ts +0 -213
- package/types/core/class/format.d.ts +0 -623
- package/types/core/class/html.d.ts +0 -430
- package/types/core/class/menu.d.ts +0 -126
- package/types/core/class/nodeTransform.d.ts +0 -93
- package/types/core/class/offset.d.ts +0 -522
- package/types/core/class/selection.d.ts +0 -188
- package/types/core/class/shortcuts.d.ts +0 -142
- package/types/core/class/toolbar.d.ts +0 -189
- package/types/core/class/ui.d.ts +0 -164
- package/types/core/class/viewer.d.ts +0 -140
- package/types/core/section/actives.d.ts +0 -46
- package/types/core/section/context.d.ts +0 -45
- package/types/editorInjector/_classes.d.ts +0 -41
- package/types/editorInjector/_core.d.ts +0 -87
- package/types/editorInjector/index.d.ts +0 -69
- package/types/modules/ApiManager.d.ts +0 -125
- package/types/modules/Browser.d.ts +0 -326
- package/types/modules/ColorPicker.d.ts +0 -135
- package/types/modules/Controller.d.ts +0 -251
- package/types/modules/Figure.d.ts +0 -517
- package/types/modules/FileManager.d.ts +0 -202
- package/types/modules/Modal.d.ts +0 -111
- package/types/modules/ModalAnchorEditor.d.ts +0 -236
- package/types/modules/SelectMenu.d.ts +0 -194
- package/types/modules/index.d.ts +0 -26
- package/types/plugins/dropdown/formatBlock.d.ts +0 -55
- package/types/plugins/dropdown/table.d.ts +0 -627
- package/types/plugins/modal/image.d.ts +0 -451
- /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;
|