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.
- package/CONTRIBUTING.md +170 -22
- package/{LICENSE.txt → LICENSE} +9 -9
- package/README.md +168 -30
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +47 -21
- package/src/assets/design/color.css +121 -0
- package/src/assets/design/index.css +3 -0
- package/src/assets/design/size.css +35 -0
- package/src/assets/design/typography.css +37 -0
- package/src/assets/icons/defaultIcons.js +232 -0
- package/src/assets/suneditor-contents.css +181 -46
- package/src/assets/suneditor.css +1403 -650
- package/src/core/base/eventHandlers/handler_toolbar.js +35 -14
- package/src/core/base/eventHandlers/handler_ww_clipboard.js +23 -4
- package/src/core/base/eventHandlers/handler_ww_dragDrop.js +49 -10
- package/src/core/base/eventHandlers/handler_ww_key_input.js +422 -224
- package/src/core/base/eventHandlers/handler_ww_mouse.js +83 -36
- package/src/core/base/eventManager.js +520 -179
- package/src/core/base/history.js +95 -41
- package/src/core/class/char.js +26 -11
- package/src/core/class/component.js +345 -137
- package/src/core/class/format.js +683 -519
- package/src/core/class/html.js +485 -305
- package/src/core/class/menu.js +133 -47
- package/src/core/class/nodeTransform.js +90 -71
- package/src/core/class/offset.js +408 -92
- package/src/core/class/selection.js +216 -106
- package/src/core/class/shortcuts.js +68 -8
- package/src/core/class/toolbar.js +106 -116
- package/src/core/class/ui.js +422 -0
- package/src/core/class/viewer.js +178 -74
- package/src/core/editor.js +496 -389
- package/src/core/section/actives.js +123 -27
- package/src/core/section/constructor.js +615 -206
- package/src/core/section/context.js +28 -23
- package/src/core/section/documentType.js +561 -0
- package/src/editorInjector/_classes.js +19 -5
- package/src/editorInjector/_core.js +71 -7
- package/src/editorInjector/index.js +63 -1
- package/src/events.js +622 -0
- package/src/helper/clipboard.js +59 -0
- package/src/helper/converter.js +202 -26
- package/src/helper/dom/domCheck.js +304 -0
- package/src/helper/dom/domQuery.js +669 -0
- package/src/helper/dom/domUtils.js +557 -0
- package/src/helper/dom/index.js +12 -0
- package/src/helper/env.js +46 -56
- package/src/helper/index.js +10 -4
- package/src/helper/keyCodeMap.js +183 -0
- package/src/helper/numbers.js +12 -8
- package/src/helper/unicode.js +9 -5
- package/src/langs/ckb.js +74 -4
- package/src/langs/cs.js +72 -2
- package/src/langs/da.js +73 -3
- package/src/langs/de.js +73 -4
- package/src/langs/en.js +23 -3
- package/src/langs/es.js +73 -4
- package/src/langs/fa.js +75 -3
- package/src/langs/fr.js +73 -3
- package/src/langs/he.js +73 -4
- package/src/langs/hu.js +230 -0
- package/src/langs/index.js +7 -3
- package/src/langs/it.js +70 -1
- package/src/langs/ja.js +72 -4
- package/src/langs/km.js +230 -0
- package/src/langs/ko.js +22 -2
- package/src/langs/lv.js +74 -5
- package/src/langs/nl.js +73 -4
- package/src/langs/pl.js +73 -4
- package/src/langs/pt_br.js +70 -1
- package/src/langs/ro.js +74 -5
- package/src/langs/ru.js +73 -4
- package/src/langs/se.js +73 -4
- package/src/langs/tr.js +73 -1
- package/src/langs/{ua.js → uk.js} +75 -6
- package/src/langs/ur.js +77 -8
- package/src/langs/zh_cn.js +74 -5
- package/src/modules/ApiManager.js +77 -54
- package/src/modules/Browser.js +667 -0
- package/src/modules/ColorPicker.js +162 -102
- package/src/modules/Controller.js +273 -142
- package/src/modules/Figure.js +925 -484
- package/src/modules/FileManager.js +121 -69
- package/src/modules/HueSlider.js +113 -61
- package/src/modules/Modal.js +291 -122
- package/src/modules/ModalAnchorEditor.js +383 -234
- package/src/modules/SelectMenu.js +270 -168
- package/src/modules/_DragHandle.js +2 -1
- package/src/modules/index.js +3 -3
- package/src/plugins/browser/audioGallery.js +83 -0
- package/src/plugins/browser/fileBrowser.js +103 -0
- package/src/plugins/browser/fileGallery.js +83 -0
- package/src/plugins/browser/imageGallery.js +81 -0
- package/src/plugins/browser/videoGallery.js +103 -0
- package/src/plugins/command/blockquote.js +40 -27
- package/src/plugins/command/exportPDF.js +134 -0
- package/src/plugins/command/fileUpload.js +229 -162
- package/src/plugins/command/list_bulleted.js +83 -47
- package/src/plugins/command/list_numbered.js +83 -47
- package/src/plugins/dropdown/align.js +66 -54
- package/src/plugins/dropdown/backgroundColor.js +63 -49
- package/src/plugins/dropdown/font.js +71 -47
- package/src/plugins/dropdown/fontColor.js +63 -48
- package/src/plugins/dropdown/formatBlock.js +70 -33
- package/src/plugins/dropdown/hr.js +92 -51
- package/src/plugins/dropdown/layout.js +37 -26
- package/src/plugins/dropdown/lineHeight.js +54 -38
- package/src/plugins/dropdown/list.js +60 -45
- package/src/plugins/dropdown/paragraphStyle.js +51 -30
- package/src/plugins/dropdown/table.js +2003 -813
- package/src/plugins/dropdown/template.js +38 -26
- package/src/plugins/dropdown/textStyle.js +43 -31
- package/src/plugins/field/mention.js +147 -86
- package/src/plugins/index.js +32 -6
- package/src/plugins/input/fontSize.js +161 -108
- package/src/plugins/input/pageNavigator.js +70 -0
- package/src/plugins/modal/audio.js +358 -173
- package/src/plugins/modal/drawing.js +531 -0
- package/src/plugins/modal/embed.js +886 -0
- package/src/plugins/modal/image.js +674 -362
- package/src/plugins/modal/link.js +100 -71
- package/src/plugins/modal/math.js +367 -167
- package/src/plugins/modal/video.js +691 -335
- package/src/plugins/popup/anchor.js +222 -0
- package/src/suneditor.js +50 -13
- package/src/themes/dark.css +122 -0
- package/src/typedef.js +130 -0
- package/types/assets/icons/defaultIcons.d.ts +153 -0
- package/types/core/base/eventHandlers/handler_toolbar.d.ts +41 -0
- package/types/core/base/eventHandlers/handler_ww_clipboard.d.ts +40 -0
- package/types/core/base/eventHandlers/handler_ww_dragDrop.d.ts +35 -0
- package/types/core/base/eventHandlers/handler_ww_key_input.d.ts +45 -0
- package/types/core/base/eventHandlers/handler_ww_mouse.d.ts +39 -0
- package/types/core/base/eventManager.d.ts +385 -0
- package/types/core/base/history.d.ts +81 -0
- package/types/core/class/char.d.ts +60 -0
- package/types/core/class/component.d.ts +212 -0
- package/types/core/class/format.d.ts +616 -0
- package/types/core/class/html.d.ts +422 -0
- package/types/core/class/menu.d.ts +126 -0
- package/types/core/class/nodeTransform.d.ts +93 -0
- package/types/core/class/offset.d.ts +522 -0
- package/types/core/class/selection.d.ts +188 -0
- package/types/core/class/shortcuts.d.ts +142 -0
- package/types/core/class/toolbar.d.ts +189 -0
- package/types/core/class/ui.d.ts +164 -0
- package/types/core/class/viewer.d.ts +140 -0
- package/types/core/editor.d.ts +610 -0
- package/types/core/section/actives.d.ts +46 -0
- package/types/core/section/constructor.d.ts +777 -0
- package/types/core/section/context.d.ts +45 -0
- package/types/core/section/documentType.d.ts +178 -0
- package/types/editorInjector/_classes.d.ts +41 -0
- package/types/editorInjector/_core.d.ts +92 -0
- package/types/editorInjector/index.d.ts +71 -0
- package/types/events.d.ts +273 -0
- package/types/helper/clipboard.d.ts +12 -0
- package/types/helper/converter.d.ts +197 -0
- package/types/helper/dom/domCheck.d.ts +189 -0
- package/types/helper/dom/domQuery.d.ts +223 -0
- package/types/helper/dom/domUtils.d.ts +226 -0
- package/types/helper/dom/index.d.ts +9 -0
- package/types/helper/env.d.ts +132 -0
- package/types/helper/index.d.ts +174 -0
- package/types/helper/keyCodeMap.d.ts +110 -0
- package/types/helper/numbers.d.ts +46 -0
- package/types/helper/unicode.d.ts +28 -0
- package/types/index.d.ts +120 -0
- package/{typings/Lang.d.ts → types/langs/_Lang.d.ts} +173 -103
- package/types/langs/ckb.d.ts +3 -0
- package/types/langs/cs.d.ts +3 -0
- package/types/langs/da.d.ts +3 -0
- package/types/langs/de.d.ts +3 -0
- package/types/langs/en.d.ts +3 -0
- package/types/langs/es.d.ts +3 -0
- package/types/langs/fa.d.ts +3 -0
- package/types/langs/fr.d.ts +3 -0
- package/types/langs/he.d.ts +3 -0
- package/types/langs/hu.d.ts +3 -0
- package/types/langs/index.d.ts +54 -0
- package/types/langs/it.d.ts +3 -0
- package/types/langs/ja.d.ts +3 -0
- package/types/langs/km.d.ts +3 -0
- package/types/langs/ko.d.ts +3 -0
- package/types/langs/lv.d.ts +3 -0
- package/types/langs/nl.d.ts +3 -0
- package/types/langs/pl.d.ts +3 -0
- package/types/langs/pt_br.d.ts +3 -0
- package/types/langs/ro.d.ts +3 -0
- package/types/langs/ru.d.ts +3 -0
- package/types/langs/se.d.ts +3 -0
- package/types/langs/tr.d.ts +3 -0
- package/types/langs/uk.d.ts +3 -0
- package/types/langs/ur.d.ts +3 -0
- package/types/langs/zh_cn.d.ts +3 -0
- package/types/modules/ApiManager.d.ts +125 -0
- package/types/modules/Browser.d.ts +326 -0
- package/types/modules/ColorPicker.d.ts +131 -0
- package/types/modules/Controller.d.ts +251 -0
- package/types/modules/Figure.d.ts +517 -0
- package/types/modules/FileManager.d.ts +202 -0
- package/types/modules/HueSlider.d.ts +136 -0
- package/types/modules/Modal.d.ts +111 -0
- package/types/modules/ModalAnchorEditor.d.ts +236 -0
- package/types/modules/SelectMenu.d.ts +194 -0
- package/types/modules/_DragHandle.d.ts +7 -0
- package/types/modules/index.d.ts +26 -0
- package/types/plugins/browser/audioGallery.d.ts +55 -0
- package/types/plugins/browser/fileBrowser.d.ts +64 -0
- package/types/plugins/browser/fileGallery.d.ts +55 -0
- package/types/plugins/browser/imageGallery.d.ts +51 -0
- package/types/plugins/browser/videoGallery.d.ts +57 -0
- package/types/plugins/command/blockquote.d.ts +28 -0
- package/types/plugins/command/exportPDF.d.ts +46 -0
- package/types/plugins/command/fileUpload.d.ts +156 -0
- package/types/plugins/command/list_bulleted.d.ts +46 -0
- package/types/plugins/command/list_numbered.d.ts +46 -0
- package/types/plugins/dropdown/align.d.ts +60 -0
- package/types/plugins/dropdown/backgroundColor.d.ts +63 -0
- package/types/plugins/dropdown/font.d.ts +54 -0
- package/types/plugins/dropdown/fontColor.d.ts +63 -0
- package/types/plugins/dropdown/formatBlock.d.ts +54 -0
- package/types/plugins/dropdown/hr.d.ts +71 -0
- package/types/plugins/dropdown/layout.d.ts +40 -0
- package/types/plugins/dropdown/lineHeight.d.ts +50 -0
- package/types/plugins/dropdown/list.d.ts +39 -0
- package/types/plugins/dropdown/paragraphStyle.d.ts +54 -0
- package/types/plugins/dropdown/table.d.ts +627 -0
- package/types/plugins/dropdown/template.d.ts +40 -0
- package/types/plugins/dropdown/textStyle.d.ts +41 -0
- package/types/plugins/field/mention.d.ts +102 -0
- package/types/plugins/index.d.ts +107 -0
- package/types/plugins/input/fontSize.d.ts +170 -0
- package/types/plugins/input/pageNavigator.d.ts +28 -0
- package/types/plugins/modal/audio.d.ts +269 -0
- package/types/plugins/modal/drawing.d.ts +246 -0
- package/types/plugins/modal/embed.d.ts +387 -0
- package/types/plugins/modal/image.d.ts +451 -0
- package/types/plugins/modal/link.d.ts +128 -0
- package/types/plugins/modal/math.d.ts +193 -0
- package/types/plugins/modal/video.d.ts +485 -0
- package/types/plugins/popup/anchor.d.ts +56 -0
- package/types/suneditor.d.ts +51 -0
- package/types/typedef.d.ts +233 -0
- package/.eslintignore +0 -7
- package/.eslintrc.json +0 -64
- package/src/assets/icons/_default.js +0 -194
- package/src/core/base/events.js +0 -320
- package/src/core/class/notice.js +0 -42
- package/src/helper/domUtils.js +0 -1177
- package/src/modules/FileBrowser.js +0 -271
- package/src/plugins/command/exportPdf.js +0 -168
- package/src/plugins/fileBrowser/imageGallery.js +0 -81
- package/src/themes/test.css +0 -61
- package/typings/CommandPlugin.d.ts +0 -8
- package/typings/DialogPlugin.d.ts +0 -20
- package/typings/FileBrowserPlugin.d.ts +0 -30
- package/typings/Module.d.ts +0 -15
- package/typings/Plugin.d.ts +0 -42
- package/typings/SubmenuPlugin.d.ts +0 -8
- package/typings/_classes.d.ts +0 -17
- package/typings/_colorPicker.d.ts +0 -60
- package/typings/_core.d.ts +0 -55
- package/typings/align.d.ts +0 -5
- package/typings/audio.d.ts +0 -5
- package/typings/backgroundColor.d.ts +0 -5
- package/typings/blockquote.d.ts +0 -5
- package/typings/char.d.ts +0 -39
- package/typings/component.d.ts +0 -38
- package/typings/context.d.ts +0 -39
- package/typings/converter.d.ts +0 -33
- package/typings/dialog.d.ts +0 -28
- package/typings/domUtils.d.ts +0 -361
- package/typings/editor.d.ts +0 -7
- package/typings/editor.ts +0 -542
- package/typings/env.d.ts +0 -70
- package/typings/eventManager.d.ts +0 -37
- package/typings/events.d.ts +0 -262
- package/typings/fileBrowser.d.ts +0 -42
- package/typings/fileManager.d.ts +0 -67
- package/typings/font.d.ts +0 -5
- package/typings/fontColor.d.ts +0 -5
- package/typings/fontSize.d.ts +0 -5
- package/typings/format.d.ts +0 -191
- package/typings/formatBlock.d.ts +0 -5
- package/typings/history.d.ts +0 -48
- package/typings/horizontalRule.d.ts +0 -5
- package/typings/image.d.ts +0 -5
- package/typings/imageGallery.d.ts +0 -5
- package/typings/index.d.ts +0 -21
- package/typings/index.modules.d.ts +0 -11
- package/typings/index.plugins.d.ts +0 -58
- package/typings/lineHeight.d.ts +0 -5
- package/typings/link.d.ts +0 -5
- package/typings/list.d.ts +0 -5
- package/typings/math.d.ts +0 -5
- package/typings/mediaContainer.d.ts +0 -25
- package/typings/mention.d.ts +0 -5
- package/typings/node.d.ts +0 -57
- package/typings/notice.d.ts +0 -16
- package/typings/numbers.d.ts +0 -29
- package/typings/offset.d.ts +0 -24
- package/typings/options.d.ts +0 -589
- package/typings/paragraphStyle.d.ts +0 -5
- package/typings/resizing.d.ts +0 -141
- package/typings/selection.d.ts +0 -94
- package/typings/shortcuts.d.ts +0 -13
- package/typings/suneditor.d.ts +0 -9
- package/typings/table.d.ts +0 -5
- package/typings/template.d.ts +0 -5
- package/typings/textStyle.d.ts +0 -5
- package/typings/toolbar.d.ts +0 -32
- package/typings/unicode.d.ts +0 -25
- package/typings/video.d.ts +0 -5
|
@@ -1,188 +1,282 @@
|
|
|
1
1
|
import EditorInjector from '../../editorInjector';
|
|
2
2
|
import { Modal, Figure, FileManager } from '../../modules';
|
|
3
|
-
import {
|
|
3
|
+
import { dom, numbers, env, converter, keyCodeMap } from '../../helper';
|
|
4
4
|
const { NO_EVENT } = env;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../../events').VideoInfo} VideoInfo_video
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('../../modules/Figure').FigureControls} FigureControls_video
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} VideoPluginOptions
|
|
16
|
+
* @property {boolean} [canResize=true] - Whether the video element can be resized.
|
|
17
|
+
* @property {boolean} [showHeightInput=true] - Whether to display the height input field.
|
|
18
|
+
* @property {string} [defaultWidth] - The default width of the video element. If a number is provided, "px" will be appended.
|
|
19
|
+
* @property {string} [defaultHeight] - The default height of the video element. If a number is provided, "px" will be appended.
|
|
20
|
+
* @property {boolean} [percentageOnlySize=false] - Whether to allow only percentage-based sizing.
|
|
21
|
+
* @property {boolean} [createFileInput=false] - Whether to create a file input element for video uploads.
|
|
22
|
+
* @property {boolean} [createUrlInput=true] - Whether to create a URL input element for video embedding.
|
|
23
|
+
* @property {string} [uploadUrl] - The URL endpoint for video file uploads.
|
|
24
|
+
* @property {Object<string, string>} [uploadHeaders] - Additional headers to include in the video upload request.
|
|
25
|
+
* @property {number} [uploadSizeLimit] - The total upload size limit for videos in bytes.
|
|
26
|
+
* @property {number} [uploadSingleSizeLimit] - The single file upload size limit for videos in bytes.
|
|
27
|
+
* @property {boolean} [allowMultiple=false] - Whether multiple video uploads are allowed.
|
|
28
|
+
* @property {string} [acceptedFormats="video/*"] - Accepted file formats for video uploads.
|
|
29
|
+
* @property {number} [defaultRatio=0.5625] - The default aspect ratio for the video (e.g., 16:9 is 0.5625).
|
|
30
|
+
* @property {boolean} [showRatioOption=true] - Whether to display the ratio option in the modal.
|
|
31
|
+
* @property {Array} [ratioOptions] - Custom ratio options for video resizing.
|
|
32
|
+
* @property {Object<string, string>} [videoTagAttributes] - Additional attributes to set on the video tag.
|
|
33
|
+
* @property {Object<string, string>} [iframeTagAttributes] - Additional attributes to set on the iframe tag.
|
|
34
|
+
* @property {string} [query_youtube=""] - Additional query parameters for YouTube embedding.
|
|
35
|
+
* @property {string} [query_vimeo=""] - Additional query parameters for Vimeo embedding.
|
|
36
|
+
* @property {Object<string, {pattern: RegExp, action: (url: string) => string, tag: string}>} [embedQuery] - Custom query objects for additional embedding services.
|
|
37
|
+
* @property {Array<RegExp>} [urlPatterns] - Additional URL patterns for video embedding.
|
|
38
|
+
* @property {Array<string>} [extensions] - Additional file extensions to be recognized for video uploads.
|
|
39
|
+
* @property {FigureControls_video} [controls] - Figure controls.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @class
|
|
44
|
+
* @description Video plugin.
|
|
45
|
+
* - This plugin provides video embedding functionality within the editor.
|
|
46
|
+
* - It also supports embedding from popular video services
|
|
47
|
+
*/
|
|
48
|
+
class Video extends EditorInjector {
|
|
49
|
+
static key = 'video';
|
|
50
|
+
static type = 'modal';
|
|
51
|
+
static className = '';
|
|
52
|
+
/**
|
|
53
|
+
* @this {Video}
|
|
54
|
+
* @param {HTMLElement} node - The node to check.
|
|
55
|
+
* @returns {HTMLElement|null} Returns a node if the node is a valid component.
|
|
56
|
+
*/
|
|
57
|
+
static component(node) {
|
|
58
|
+
if (/^(VIDEO)$/i.test(node?.nodeName)) {
|
|
59
|
+
return node;
|
|
60
|
+
} else if (/^(IFRAME)$/i.test(node?.nodeName)) {
|
|
61
|
+
return this.checkContentType(/** @type {HTMLIFrameElement} */ (node).src) ? node : null;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
33
65
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @constructor
|
|
68
|
+
* @param {__se__EditorCore} editor - The root editor instance
|
|
69
|
+
* @param {VideoPluginOptions} pluginOptions
|
|
70
|
+
*/
|
|
71
|
+
constructor(editor, pluginOptions) {
|
|
72
|
+
// plugin bisic properties
|
|
73
|
+
super(editor);
|
|
74
|
+
this.title = this.lang.video;
|
|
75
|
+
this.icon = 'video';
|
|
76
|
+
|
|
77
|
+
// define plugin options
|
|
78
|
+
this.pluginOptions = {
|
|
79
|
+
canResize: pluginOptions.canResize === undefined ? true : pluginOptions.canResize,
|
|
80
|
+
showHeightInput: pluginOptions.showHeightInput === undefined ? true : !!pluginOptions.showHeightInput,
|
|
81
|
+
defaultWidth: !pluginOptions.defaultWidth || !numbers.get(pluginOptions.defaultWidth, 0) ? '' : numbers.is(pluginOptions.defaultWidth) ? pluginOptions.defaultWidth + 'px' : pluginOptions.defaultWidth,
|
|
82
|
+
defaultHeight: !pluginOptions.defaultHeight || !numbers.get(pluginOptions.defaultHeight, 0) ? '' : numbers.is(pluginOptions.defaultHeight) ? pluginOptions.defaultHeight + 'px' : pluginOptions.defaultHeight,
|
|
83
|
+
percentageOnlySize: !!pluginOptions.percentageOnlySize,
|
|
84
|
+
createFileInput: !!pluginOptions.createFileInput,
|
|
85
|
+
createUrlInput: pluginOptions.createUrlInput === undefined || !pluginOptions.createFileInput ? true : pluginOptions.createUrlInput,
|
|
86
|
+
uploadUrl: typeof pluginOptions.uploadUrl === 'string' ? pluginOptions.uploadUrl : null,
|
|
87
|
+
uploadHeaders: pluginOptions.uploadHeaders || null,
|
|
88
|
+
uploadSizeLimit: numbers.get(pluginOptions.uploadSizeLimit, 0),
|
|
89
|
+
uploadSingleSizeLimit: numbers.get(pluginOptions.uploadSingleSizeLimit, 0),
|
|
90
|
+
allowMultiple: !!pluginOptions.allowMultiple,
|
|
91
|
+
acceptedFormats: typeof pluginOptions.acceptedFormats !== 'string' || pluginOptions.acceptedFormats.trim() === '*' ? 'video/*' : pluginOptions.acceptedFormats.trim() || 'video/*',
|
|
92
|
+
defaultRatio: numbers.get(pluginOptions.defaultRatio, 4) || 0.5625,
|
|
93
|
+
showRatioOption: pluginOptions.showRatioOption === undefined ? true : !!pluginOptions.showRatioOption,
|
|
94
|
+
ratioOptions: !pluginOptions.ratioOptions ? null : pluginOptions.ratioOptions,
|
|
95
|
+
videoTagAttributes: pluginOptions.videoTagAttributes || null,
|
|
96
|
+
iframeTagAttributes: pluginOptions.iframeTagAttributes || null,
|
|
97
|
+
query_youtube: pluginOptions.query_youtube || '',
|
|
98
|
+
query_vimeo: pluginOptions.query_vimeo || ''
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// create HTML
|
|
102
|
+
const sizeUnit = this.pluginOptions.percentageOnlySize ? '%' : 'px';
|
|
103
|
+
const modalEl = CreateHTML_modal(editor, this.pluginOptions);
|
|
104
|
+
const figureControls = pluginOptions.controls || !this.pluginOptions.canResize ? [['align', 'edit', 'copy', 'remove']] : [['resize_auto,75,50', 'align', 'edit', 'revert', 'copy', 'remove']];
|
|
105
|
+
|
|
106
|
+
// show align
|
|
107
|
+
if (!figureControls.some((subArray) => subArray.includes('align'))) modalEl.alignForm.style.display = 'none';
|
|
108
|
+
|
|
109
|
+
// modules
|
|
110
|
+
const defaultRatio = this.pluginOptions.defaultRatio * 100 + '%';
|
|
111
|
+
this.modal = new Modal(this, modalEl.html);
|
|
112
|
+
this.figure = new Figure(this, figureControls, { sizeUnit: sizeUnit, autoRatio: { current: defaultRatio, default: defaultRatio } });
|
|
113
|
+
this.fileManager = new FileManager(this, {
|
|
114
|
+
query: 'iframe, video',
|
|
115
|
+
loadHandler: this.events.onVideoLoad,
|
|
116
|
+
eventHandler: this.events.onVideoAction
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// members
|
|
120
|
+
this.fileModalWrapper = modalEl.fileModalWrapper;
|
|
121
|
+
this.videoInputFile = modalEl.videoInputFile;
|
|
122
|
+
this.videoUrlFile = modalEl.videoUrlFile;
|
|
123
|
+
this.focusElement = this.videoUrlFile || this.videoInputFile;
|
|
124
|
+
this.previewSrc = modalEl.previewSrc;
|
|
125
|
+
this._linkValue = '';
|
|
126
|
+
this._align = 'none';
|
|
127
|
+
this._frameRatio = defaultRatio;
|
|
128
|
+
this._defaultRatio = defaultRatio;
|
|
129
|
+
this._defaultSizeX = '100%';
|
|
130
|
+
this._defaultSizeY = this.pluginOptions.defaultRatio * 100 + '%';
|
|
131
|
+
this.sizeUnit = sizeUnit;
|
|
132
|
+
this.proportion = null;
|
|
133
|
+
this.frameRatioOption = null;
|
|
134
|
+
this.inputX = null;
|
|
135
|
+
this.inputY = null;
|
|
136
|
+
this._element = null;
|
|
137
|
+
this._cover = null;
|
|
138
|
+
this._container = null;
|
|
139
|
+
this._ratio = { w: 1, h: 1 };
|
|
140
|
+
this._origin_w = this.pluginOptions.defaultWidth === '100%' ? '' : this.pluginOptions.defaultWidth;
|
|
141
|
+
this._origin_h = this.pluginOptions.defaultHeight === defaultRatio ? '' : this.pluginOptions.defaultHeight;
|
|
142
|
+
this._resizing = this.pluginOptions.canResize;
|
|
143
|
+
this._onlyPercentage = this.pluginOptions.percentageOnlySize;
|
|
144
|
+
this._nonResizing = !this._resizing || !this.pluginOptions.showHeightInput || this._onlyPercentage;
|
|
145
|
+
this.query = {
|
|
146
|
+
youtube: {
|
|
147
|
+
pattern: /youtu\.?be/i,
|
|
148
|
+
action: (url) => {
|
|
149
|
+
url = this.convertUrlYoutube(url);
|
|
150
|
+
return converter.addUrlQuery(url, this.pluginOptions.query_youtube);
|
|
151
|
+
},
|
|
152
|
+
tag: 'iframe'
|
|
153
|
+
},
|
|
154
|
+
vimeo: {
|
|
155
|
+
pattern: /vimeo\.com/i,
|
|
156
|
+
action: (url) => {
|
|
157
|
+
url = this.convertUrlVimeo(url);
|
|
158
|
+
return converter.addUrlQuery(url, this.pluginOptions.query_vimeo);
|
|
159
|
+
},
|
|
160
|
+
tag: 'iframe'
|
|
161
|
+
},
|
|
162
|
+
...pluginOptions.embedQuery
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const urlPatterns = [];
|
|
166
|
+
for (const key in this.query) {
|
|
167
|
+
urlPatterns.push(this.query[key].pattern);
|
|
168
|
+
}
|
|
169
|
+
this.extensions = ['.mp4', '.avi', '.mov', '.webm', '.flv', '.mkv', '.m4v', '.ogv'].concat(this.pluginOptions.extensions || []);
|
|
170
|
+
this.urlPatterns = urlPatterns
|
|
171
|
+
.concat([
|
|
172
|
+
/youtu\.?be/,
|
|
173
|
+
/vimeo\.com\//,
|
|
174
|
+
/dailymotion\.com\/video\//,
|
|
175
|
+
/facebook\.com\/.+\/videos\//,
|
|
176
|
+
/facebook\.com\/watch\/\?v=/,
|
|
177
|
+
/twitter\.com\/.+\/status\//,
|
|
178
|
+
/twitch\.tv\/videos\//,
|
|
179
|
+
/twitch\.tv\/[^/]+$/,
|
|
180
|
+
/tiktok\.com\/@[^/]+\/video\//,
|
|
181
|
+
/instagram\.com\/p\//,
|
|
182
|
+
/instagram\.com\/tv\//,
|
|
183
|
+
/instagram\.com\/reel\//,
|
|
184
|
+
/linkedin\.com\/posts\//,
|
|
185
|
+
/\.(wistia\.com|wi\.st)\/(medias|embed)\//,
|
|
186
|
+
/loom\.com\/share\//
|
|
187
|
+
])
|
|
188
|
+
.concat(pluginOptions.urlPatterns || []);
|
|
189
|
+
|
|
190
|
+
const galleryButton = modalEl.galleryButton;
|
|
191
|
+
if (galleryButton) this.eventManager.addEvent(galleryButton, 'click', this.#OpenGallery.bind(this));
|
|
192
|
+
|
|
193
|
+
// init
|
|
194
|
+
if (this.videoInputFile) this.eventManager.addEvent(modalEl.fileRemoveBtn, 'click', this.#RemoveSelectedFiles.bind(this));
|
|
195
|
+
if (this.videoUrlFile) this.eventManager.addEvent(this.videoUrlFile, 'input', this.#OnLinkPreview.bind(this));
|
|
196
|
+
if (this.videoInputFile && this.videoUrlFile) this.eventManager.addEvent(this.videoInputFile, 'change', this.#OnfileInputChange.bind(this));
|
|
97
197
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
Video.key = 'video';
|
|
123
|
-
Video.type = 'modal';
|
|
124
|
-
Video.className = '';
|
|
125
|
-
Video.component = function (node) {
|
|
126
|
-
if (/^(VIDEO)$/i.test(node?.nodeName)) {
|
|
127
|
-
return node;
|
|
128
|
-
} else if (/^(IFRAME)$/i.test(node?.nodeName)) {
|
|
129
|
-
return this.findProcessUrl(node.src) ? node : null;
|
|
130
|
-
}
|
|
131
|
-
return null;
|
|
132
|
-
};
|
|
133
|
-
Video.prototype = {
|
|
134
|
-
/**
|
|
135
|
-
* @override type = "modal"
|
|
198
|
+
if (this._resizing) {
|
|
199
|
+
this.proportion = modalEl.proportion;
|
|
200
|
+
this.frameRatioOption = modalEl.frameRatioOption;
|
|
201
|
+
this.inputX = modalEl.inputX;
|
|
202
|
+
this.inputY = modalEl.inputY;
|
|
203
|
+
this.inputX.value = this.pluginOptions.defaultWidth;
|
|
204
|
+
this.inputY.value = this.pluginOptions.defaultHeight;
|
|
205
|
+
|
|
206
|
+
const ratioChange = this.#OnChangeRatio.bind(this);
|
|
207
|
+
this.eventManager.addEvent(this.inputX, 'keyup', this.#OnInputSize.bind(this, 'x'));
|
|
208
|
+
this.eventManager.addEvent(this.inputY, 'keyup', this.#OnInputSize.bind(this, 'y'));
|
|
209
|
+
this.eventManager.addEvent(this.inputX, 'change', ratioChange);
|
|
210
|
+
this.eventManager.addEvent(this.inputY, 'change', ratioChange);
|
|
211
|
+
this.eventManager.addEvent(this.proportion, 'change', ratioChange);
|
|
212
|
+
this.eventManager.addEvent(this.frameRatioOption, 'change', this.#SetRatio.bind(this));
|
|
213
|
+
this.eventManager.addEvent(modalEl.revertBtn, 'click', this.#OnClickRevert.bind(this));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @editorMethod Modules.Modal
|
|
219
|
+
* @description Executes the method that is called when a "Modal" module's is opened.
|
|
136
220
|
*/
|
|
137
221
|
open() {
|
|
138
222
|
this.modal.open();
|
|
139
|
-
}
|
|
223
|
+
}
|
|
140
224
|
|
|
141
225
|
/**
|
|
142
|
-
* @
|
|
226
|
+
* @editorMethod Modules.Controller(Figure)
|
|
227
|
+
* @description Executes the method that is called when a target component is edited.
|
|
143
228
|
*/
|
|
144
229
|
edit() {
|
|
145
230
|
this.modal.open();
|
|
146
|
-
}
|
|
231
|
+
}
|
|
147
232
|
|
|
148
233
|
/**
|
|
149
|
-
* @
|
|
150
|
-
* @
|
|
234
|
+
* @editorMethod Modules.Modal
|
|
235
|
+
* @description Executes the method that is called when a plugin's modal is opened.
|
|
236
|
+
* @param {boolean} isUpdate "Indicates whether the modal is for editing an existing component (true) or registering a new one (false)."
|
|
151
237
|
*/
|
|
152
238
|
on(isUpdate) {
|
|
153
239
|
if (!isUpdate) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
240
|
+
if (this._resizing) {
|
|
241
|
+
this.inputX.value = this._origin_w = this.pluginOptions.defaultWidth === this._defaultSizeX ? '' : this.pluginOptions.defaultWidth;
|
|
242
|
+
this.inputY.value = this._origin_h = this.pluginOptions.defaultHeight === this._defaultSizeY ? '' : this.pluginOptions.defaultHeight;
|
|
243
|
+
this.proportion.disabled = true;
|
|
244
|
+
}
|
|
157
245
|
if (this.videoInputFile && this.pluginOptions.allowMultiple) this.videoInputFile.setAttribute('multiple', 'multiple');
|
|
158
246
|
} else {
|
|
159
247
|
if (this.videoInputFile && this.pluginOptions.allowMultiple) this.videoInputFile.removeAttribute('multiple');
|
|
160
248
|
}
|
|
161
249
|
|
|
162
250
|
if (this._resizing) {
|
|
163
|
-
this.
|
|
251
|
+
this._setRatioSelect(this._origin_h || this._defaultRatio);
|
|
164
252
|
}
|
|
165
|
-
}
|
|
253
|
+
}
|
|
166
254
|
|
|
167
255
|
/**
|
|
168
|
-
* @
|
|
169
|
-
* @
|
|
256
|
+
* @editorMethod Editor.EventManager
|
|
257
|
+
* @description Executes the event function of "paste" or "drop".
|
|
258
|
+
* @param {Object} params { frameContext, event, file }
|
|
259
|
+
* @param {__se__FrameContext} params.frameContext Frame context
|
|
260
|
+
* @param {ClipboardEvent} params.event Event object
|
|
261
|
+
* @param {File} params.file File object
|
|
262
|
+
* @returns {boolean} - If return false, the file upload will be canceled
|
|
170
263
|
*/
|
|
171
|
-
|
|
264
|
+
onFilePasteAndDrop({ file }) {
|
|
172
265
|
if (!/^video/.test(file.type)) return;
|
|
173
266
|
|
|
174
267
|
this.submitFile([file]);
|
|
175
268
|
this.editor.focus();
|
|
176
269
|
|
|
177
270
|
return false;
|
|
178
|
-
}
|
|
271
|
+
}
|
|
179
272
|
|
|
180
273
|
/**
|
|
181
|
-
* @
|
|
182
|
-
* @
|
|
274
|
+
* @editorMethod Modules.Modal
|
|
275
|
+
* @description This function is called when a form within a modal window is "submit".
|
|
276
|
+
* @returns {Promise<boolean>} Success / failure
|
|
183
277
|
*/
|
|
184
278
|
async modalAction() {
|
|
185
|
-
this._align = this.modal.form.querySelector('input[name="suneditor_video_radio"]:checked').value;
|
|
279
|
+
this._align = /** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_video_radio"]:checked')).value;
|
|
186
280
|
|
|
187
281
|
let result = false;
|
|
188
282
|
if (this.videoInputFile && this.videoInputFile.files.length > 0) {
|
|
@@ -191,43 +285,56 @@ Video.prototype = {
|
|
|
191
285
|
result = await this.submitURL(this._linkValue);
|
|
192
286
|
}
|
|
193
287
|
|
|
194
|
-
if (result) this._w.setTimeout(this.component.select.bind(this.component, this._element,
|
|
288
|
+
if (result) this._w.setTimeout(this.component.select.bind(this.component, this._element, Video.key), 0);
|
|
195
289
|
|
|
196
290
|
return result;
|
|
197
|
-
}
|
|
291
|
+
}
|
|
198
292
|
|
|
199
293
|
/**
|
|
200
|
-
* @
|
|
294
|
+
* @editorMethod Editor.core
|
|
295
|
+
* @description This method is used to validate and preserve the format of the component within the editor.
|
|
296
|
+
* - It ensures that the structure and attributes of the element are maintained and secure.
|
|
297
|
+
* - The method checks if the element is already wrapped in a valid container and updates its attributes if necessary.
|
|
298
|
+
* - If the element isn't properly contained, a new container is created to retain the format.
|
|
299
|
+
* @returns {{query: string, method: (element: HTMLIFrameElement|HTMLVideoElement) => void}} The format retention object containing the query and method to process the element.
|
|
300
|
+
* - query: The selector query to identify the relevant elements (in this case, 'audio').
|
|
301
|
+
* - method:The function to execute on the element to validate and preserve its format.
|
|
302
|
+
* - The function takes the element as an argument, checks if it is contained correctly, and applies necessary adjustments.
|
|
201
303
|
*/
|
|
202
304
|
retainFormat() {
|
|
203
305
|
return {
|
|
204
306
|
query: 'iframe, video',
|
|
205
|
-
method: (element) => {
|
|
307
|
+
method: async (element) => {
|
|
308
|
+
if (/^(iframe)$/i.test(element?.nodeName)) {
|
|
309
|
+
if (!this.checkContentType(element.src)) return;
|
|
310
|
+
}
|
|
311
|
+
|
|
206
312
|
const figureInfo = Figure.GetContainer(element);
|
|
207
313
|
if (figureInfo && figureInfo.container && figureInfo.cover) return;
|
|
208
314
|
|
|
209
|
-
this.
|
|
315
|
+
this._ready(element);
|
|
210
316
|
const line = this.format.getLine(element);
|
|
211
317
|
if (line) this._align = line.style.textAlign || line.style.float;
|
|
212
318
|
|
|
213
319
|
this._update(element);
|
|
214
320
|
}
|
|
215
321
|
};
|
|
216
|
-
}
|
|
322
|
+
}
|
|
217
323
|
|
|
218
324
|
/**
|
|
219
|
-
* @
|
|
325
|
+
* @editorMethod Modules.Modal
|
|
326
|
+
* @description This function is called before the modal window is opened, but before it is closed.
|
|
220
327
|
*/
|
|
221
328
|
init() {
|
|
222
329
|
Modal.OnChangeFile(this.fileModalWrapper, []);
|
|
223
330
|
if (this.videoInputFile) this.videoInputFile.value = '';
|
|
224
331
|
if (this.videoUrlFile) this._linkValue = this.previewSrc.textContent = this.videoUrlFile.value = '';
|
|
225
332
|
if (this.videoInputFile && this.videoUrlFile) {
|
|
226
|
-
this.videoUrlFile.
|
|
333
|
+
this.videoUrlFile.disabled = false;
|
|
227
334
|
this.previewSrc.style.textDecoration = '';
|
|
228
335
|
}
|
|
229
336
|
|
|
230
|
-
this.modal.form.querySelector('input[name="suneditor_video_radio"][value="none"]').checked = true;
|
|
337
|
+
/** @type {HTMLInputElement} */ (this.modal.form.querySelector('input[name="suneditor_video_radio"][value="none"]')).checked = true;
|
|
231
338
|
this._ratio = { w: 1, h: 1 };
|
|
232
339
|
this._nonResizing = false;
|
|
233
340
|
|
|
@@ -236,24 +343,27 @@ Video.prototype = {
|
|
|
236
343
|
this.inputY.value = this.pluginOptions.defaultHeight === this._defaultSizeY ? '' : this.pluginOptions.defaultHeight;
|
|
237
344
|
this.proportion.checked = false;
|
|
238
345
|
this.proportion.disabled = true;
|
|
239
|
-
this.
|
|
346
|
+
this._setRatioSelect(this._defaultRatio);
|
|
240
347
|
}
|
|
241
|
-
}
|
|
348
|
+
}
|
|
242
349
|
|
|
243
350
|
/**
|
|
244
|
-
* @
|
|
245
|
-
* @description
|
|
246
|
-
* @param {
|
|
351
|
+
* @editorMethod Editor.component
|
|
352
|
+
* @description Executes the method that is called when a component of a plugin is selected.
|
|
353
|
+
* @param {HTMLIFrameElement|HTMLVideoElement} target Target component element
|
|
247
354
|
*/
|
|
248
|
-
select(
|
|
249
|
-
this.
|
|
250
|
-
}
|
|
355
|
+
select(target) {
|
|
356
|
+
this._ready(target);
|
|
357
|
+
}
|
|
251
358
|
|
|
252
359
|
/**
|
|
253
|
-
* @
|
|
254
|
-
* @
|
|
360
|
+
* @private
|
|
361
|
+
* @description Prepares the component for selection.
|
|
362
|
+
* - Ensures that the controller is properly positioned and initialized.
|
|
363
|
+
* - Prevents duplicate event handling if the component is already selected.
|
|
364
|
+
* @param {HTMLIFrameElement|HTMLVideoElement} target - The selected element.
|
|
255
365
|
*/
|
|
256
|
-
|
|
366
|
+
_ready(target) {
|
|
257
367
|
if (!target) return;
|
|
258
368
|
const figureInfo = this.figure.open(target, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: false });
|
|
259
369
|
|
|
@@ -263,14 +373,17 @@ Video.prototype = {
|
|
|
263
373
|
this._align = figureInfo.align;
|
|
264
374
|
target.style.float = '';
|
|
265
375
|
|
|
266
|
-
this._origin_w = figureInfo.width || figureInfo.originWidth || figureInfo.w || '';
|
|
267
|
-
this._origin_h = figureInfo.height || figureInfo.originHeight || figureInfo.h || '';
|
|
376
|
+
this._origin_w = String(figureInfo.width || figureInfo.originWidth || figureInfo.w || '');
|
|
377
|
+
this._origin_h = String(figureInfo.height || figureInfo.originHeight || figureInfo.h || '');
|
|
268
378
|
|
|
269
379
|
let w = figureInfo.width || figureInfo.w || this._origin_w || '';
|
|
270
380
|
const h = figureInfo.height || figureInfo.h || this._origin_h || '';
|
|
271
381
|
|
|
272
|
-
if (this.videoUrlFile) this._linkValue = this.previewSrc.textContent = this.videoUrlFile.value = this._element.src ||
|
|
273
|
-
|
|
382
|
+
if (this.videoUrlFile) this._linkValue = this.previewSrc.textContent = this.videoUrlFile.value = this._element.src || this._element.querySelector('source')?.src || '';
|
|
383
|
+
|
|
384
|
+
/** @type {HTMLInputElement} */
|
|
385
|
+
const activeAlgin = this.modal.form.querySelector('input[name="suneditor_video_radio"][value="' + this._align + '"]') || this.modal.form.querySelector('input[name="suneditor_video_radio"][value="none"]');
|
|
386
|
+
activeAlgin.checked = true;
|
|
274
387
|
|
|
275
388
|
if (!this._resizing) return;
|
|
276
389
|
|
|
@@ -279,14 +392,14 @@ Video.prototype = {
|
|
|
279
392
|
w = numbers.get(w, 2);
|
|
280
393
|
if (w > 100) w = 100;
|
|
281
394
|
}
|
|
282
|
-
this.inputX.value = w === 'auto' ? '' : w;
|
|
395
|
+
this.inputX.value = String(w === 'auto' ? '' : w);
|
|
283
396
|
|
|
284
397
|
if (!this._onlyPercentage) {
|
|
285
398
|
const infoH = percentageRotation ? '' : figureInfo.height;
|
|
286
|
-
this.inputY.value = infoH === 'auto' ? '' : infoH;
|
|
399
|
+
this.inputY.value = String(infoH === 'auto' ? '' : infoH);
|
|
287
400
|
}
|
|
288
401
|
|
|
289
|
-
if (!this.
|
|
402
|
+
if (!this._setRatioSelect(h)) this.inputY.value = String(this._onlyPercentage ? numbers.get(h, 2) : h);
|
|
290
403
|
|
|
291
404
|
this.proportion.checked = true;
|
|
292
405
|
this.inputX.disabled = percentageRotation ? true : false;
|
|
@@ -294,21 +407,24 @@ Video.prototype = {
|
|
|
294
407
|
this.proportion.disabled = percentageRotation ? true : false;
|
|
295
408
|
|
|
296
409
|
this._ratio = this.proportion.checked ? figureInfo.ratio : { w: 1, h: 1 };
|
|
297
|
-
}
|
|
410
|
+
}
|
|
298
411
|
|
|
299
412
|
/**
|
|
300
|
-
* @
|
|
413
|
+
* @editorMethod Editor.Component
|
|
414
|
+
* @description Method to delete a component of a plugin, called by the "FileManager", "Controller" module.
|
|
415
|
+
* @param {HTMLElement} target Target element
|
|
416
|
+
* @returns {Promise<void>}
|
|
301
417
|
*/
|
|
302
|
-
async destroy(
|
|
303
|
-
const targetEl =
|
|
304
|
-
const container =
|
|
418
|
+
async destroy(target) {
|
|
419
|
+
const targetEl = target || this._element;
|
|
420
|
+
const container = dom.query.getParentElement(targetEl, Figure.is) || targetEl;
|
|
305
421
|
const focusEl = container.previousElementSibling || container.nextElementSibling;
|
|
306
422
|
const emptyDiv = container.parentNode;
|
|
307
423
|
|
|
308
|
-
const message = await this.triggerEvent('onVideoDeleteBefore', {
|
|
424
|
+
const message = await this.triggerEvent('onVideoDeleteBefore', { element: targetEl, container, align: this._align, url: this._linkValue });
|
|
309
425
|
if (message === false) return;
|
|
310
426
|
|
|
311
|
-
|
|
427
|
+
dom.utils.removeItem(container);
|
|
312
428
|
this.init();
|
|
313
429
|
|
|
314
430
|
if (emptyDiv !== this.editor.frameContext.get('wysiwyg')) {
|
|
@@ -324,8 +440,28 @@ Video.prototype = {
|
|
|
324
440
|
// focus
|
|
325
441
|
this.editor.focusEdge(focusEl);
|
|
326
442
|
this.history.push(false);
|
|
327
|
-
}
|
|
443
|
+
}
|
|
328
444
|
|
|
445
|
+
/**
|
|
446
|
+
* @description Checks if the given URL matches any of the defined URL patterns.
|
|
447
|
+
* @param {string} url - The URL to check.
|
|
448
|
+
* @returns {boolean} True if the URL matches a known pattern; otherwise, false.
|
|
449
|
+
*/
|
|
450
|
+
checkContentType(url) {
|
|
451
|
+
url = url?.toLowerCase() || '';
|
|
452
|
+
if (this.extensions.some((ext) => url.endsWith(ext)) || this.urlPatterns.some((pattern) => pattern.test(url))) {
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @description Finds and processes the URL for video by matching it against known service patterns.
|
|
461
|
+
* @param {string} url - The original URL.
|
|
462
|
+
* @returns {{origin: string, url: string, tag: string}|null} An object containing the original URL, the processed URL, and the tag type (e.g., 'iframe'),
|
|
463
|
+
* or null if no matching pattern is found.
|
|
464
|
+
*/
|
|
329
465
|
findProcessUrl(url) {
|
|
330
466
|
const query = this.query;
|
|
331
467
|
for (const key in query) {
|
|
@@ -340,8 +476,14 @@ Video.prototype = {
|
|
|
340
476
|
}
|
|
341
477
|
|
|
342
478
|
return null;
|
|
343
|
-
}
|
|
479
|
+
}
|
|
344
480
|
|
|
481
|
+
/**
|
|
482
|
+
* @description Converts a YouTube URL into an embeddable URL.
|
|
483
|
+
* - If the URL does not start with "http", it prepends "https://". It also replaces "watch?v=" with the embed path.
|
|
484
|
+
* @param {string} url - The original YouTube URL.
|
|
485
|
+
* @returns {string} The converted YouTube embed URL.
|
|
486
|
+
*/
|
|
345
487
|
convertUrlYoutube(url) {
|
|
346
488
|
if (!/^http/.test(url)) url = 'https://' + url;
|
|
347
489
|
url = url.replace('watch?v=', '');
|
|
@@ -349,26 +491,53 @@ Video.prototype = {
|
|
|
349
491
|
url = url.replace(url.match(/\/\/.+\//)[0], '//www.youtube.com/embed/').replace('&', '?&');
|
|
350
492
|
}
|
|
351
493
|
return url;
|
|
352
|
-
}
|
|
494
|
+
}
|
|
353
495
|
|
|
496
|
+
/**
|
|
497
|
+
* @description Converts a Vimeo URL into an embeddable URL.
|
|
498
|
+
* - Removes any trailing slash and extracts the video ID from the URL.
|
|
499
|
+
* @param {string} url - The original Vimeo URL.
|
|
500
|
+
* @returns {string} The converted Vimeo embed URL.
|
|
501
|
+
*/
|
|
354
502
|
convertUrlVimeo(url) {
|
|
355
503
|
if (url.endsWith('/')) {
|
|
356
504
|
url = url.slice(0, -1);
|
|
357
505
|
}
|
|
358
506
|
url = 'https://player.vimeo.com/video/' + url.slice(url.lastIndexOf('/') + 1);
|
|
359
507
|
return url;
|
|
360
|
-
}
|
|
508
|
+
}
|
|
361
509
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
510
|
+
/**
|
|
511
|
+
* @description Adds query parameters to a URL.
|
|
512
|
+
* - If the URL already contains a query string, the provided query is appended with an "&".
|
|
513
|
+
* @param {string} url - The original URL.
|
|
514
|
+
* @param {string} query - The query string to append.
|
|
515
|
+
* @returns {string} The URL with the appended query parameters.
|
|
516
|
+
*/
|
|
517
|
+
addQuery(url, query) {
|
|
518
|
+
if (query.length > 0) {
|
|
519
|
+
if (/\?/.test(url)) {
|
|
520
|
+
const splitUrl = url.split('?');
|
|
521
|
+
url = splitUrl[0] + '?' + query + '&' + splitUrl[1];
|
|
522
|
+
} else {
|
|
523
|
+
url += '?' + query;
|
|
524
|
+
}
|
|
368
525
|
}
|
|
369
|
-
|
|
370
|
-
}
|
|
526
|
+
return url;
|
|
527
|
+
}
|
|
371
528
|
|
|
529
|
+
/**
|
|
530
|
+
* @description Creates or updates a video embed component.
|
|
531
|
+
* - When updating, it replaces the existing element if necessary and applies the new source, size, and alignment.
|
|
532
|
+
* - When creating, it wraps the provided element in a figure container.
|
|
533
|
+
* @param {HTMLIFrameElement|HTMLVideoElement} oFrame - The existing video element (for update) or a newly created one.
|
|
534
|
+
* @param {string} src - The source URL for the video.
|
|
535
|
+
* @param {string} width - The desired width for the video element.
|
|
536
|
+
* @param {string} height - The desired height for the video element.
|
|
537
|
+
* @param {string} align - The alignment to apply to the video element (e.g., 'left', 'center', 'right').
|
|
538
|
+
* @param {boolean} isUpdate - Indicates whether this is an update to an existing component (true) or a new creation (false).
|
|
539
|
+
* @param {{name: string, size: number}} file - File metadata associated with the video
|
|
540
|
+
*/
|
|
372
541
|
create(oFrame, src, width, height, align, isUpdate, file) {
|
|
373
542
|
let cover = null;
|
|
374
543
|
let container = null;
|
|
@@ -381,19 +550,19 @@ Video.prototype = {
|
|
|
381
550
|
if (/^iframe$/i.test(processUrl?.tag) && !/^iframe$/i.test(oFrame.nodeName)) {
|
|
382
551
|
const newTag = this.createIframeTag();
|
|
383
552
|
newTag.src = src;
|
|
384
|
-
oFrame.
|
|
553
|
+
oFrame.replaceWith(newTag);
|
|
385
554
|
this._element = oFrame = newTag;
|
|
386
555
|
} else if (/^video$/i.test(processUrl?.tag) && !/^video$/i.test(oFrame.nodeName)) {
|
|
387
556
|
const newTag = this.createVideoTag();
|
|
388
557
|
newTag.src = src;
|
|
389
|
-
oFrame.
|
|
558
|
+
oFrame.replaceWith(newTag);
|
|
390
559
|
this._element = oFrame = newTag;
|
|
391
560
|
} else {
|
|
392
561
|
oFrame.src = src;
|
|
393
562
|
}
|
|
394
563
|
}
|
|
395
564
|
container = this._container;
|
|
396
|
-
cover =
|
|
565
|
+
cover = dom.query.getParentElement(oFrame, 'FIGURE');
|
|
397
566
|
} else {
|
|
398
567
|
/** create */
|
|
399
568
|
oFrame.src = src;
|
|
@@ -410,14 +579,14 @@ Video.prototype = {
|
|
|
410
579
|
this.figure.open(oFrame, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
|
|
411
580
|
|
|
412
581
|
width = width || this._defaultSizeX;
|
|
413
|
-
height = height || this.
|
|
582
|
+
height = height || this._frameRatio;
|
|
414
583
|
const size = this.figure.getSize(oFrame);
|
|
415
584
|
const inputUpdate = size.w !== width || size.h !== height;
|
|
416
585
|
const changeSize = !isUpdate || inputUpdate;
|
|
417
586
|
|
|
418
587
|
// set size
|
|
419
588
|
if (changeSize) {
|
|
420
|
-
this.
|
|
589
|
+
this._applySize(width, height);
|
|
421
590
|
}
|
|
422
591
|
|
|
423
592
|
// align
|
|
@@ -425,11 +594,10 @@ Video.prototype = {
|
|
|
425
594
|
|
|
426
595
|
// select figure
|
|
427
596
|
// oFrame.onload = OnloadVideo.bind(this, oFrame);
|
|
428
|
-
|
|
429
597
|
this.fileManager.setFileData(oFrame, file);
|
|
430
598
|
|
|
431
599
|
if (!isUpdate) {
|
|
432
|
-
this.component.insert(container, false, true);
|
|
600
|
+
this.component.insert(container, { skipCharCount: false, skipSelection: true, skipHistory: false });
|
|
433
601
|
if (!this.options.get('componentAutoSelect')) {
|
|
434
602
|
const line = this.format.addLine(container, null);
|
|
435
603
|
if (line) this.selection.setRange(line, 0, line, 0);
|
|
@@ -439,36 +607,86 @@ Video.prototype = {
|
|
|
439
607
|
|
|
440
608
|
if (this._resizing && changeSize && this.figure.isVertical) this.figure.setTransform(oFrame, width, height, 0);
|
|
441
609
|
this.history.push(false);
|
|
442
|
-
}
|
|
610
|
+
}
|
|
443
611
|
|
|
444
|
-
|
|
445
|
-
|
|
612
|
+
/**
|
|
613
|
+
* @description Creates a new iframe element for video embedding.
|
|
614
|
+
* - Applies any additional properties provided and sets the necessary attributes for embedding.
|
|
615
|
+
* @param {Object<string, string>} [props] - An optional object containing properties to assign to the iframe.
|
|
616
|
+
* @returns {HTMLIFrameElement} The newly created iframe element.
|
|
617
|
+
*/
|
|
618
|
+
createIframeTag(props) {
|
|
619
|
+
/** @type {HTMLIFrameElement} */
|
|
620
|
+
const iframeTag = dom.utils.createElement('IFRAME');
|
|
621
|
+
if (props) {
|
|
622
|
+
for (const key in props) {
|
|
623
|
+
iframeTag[key] = props[key];
|
|
624
|
+
}
|
|
625
|
+
}
|
|
446
626
|
this._setIframeAttrs(iframeTag);
|
|
447
627
|
return iframeTag;
|
|
448
|
-
}
|
|
628
|
+
}
|
|
449
629
|
|
|
450
|
-
|
|
451
|
-
|
|
630
|
+
/**
|
|
631
|
+
* @description Creates a new video element for video embedding.
|
|
632
|
+
* - Applies any additional properties provided and sets the necessary attributes.
|
|
633
|
+
* @param {Object<string, string>} [props] - An optional object containing properties to assign to the video element.
|
|
634
|
+
* @returns {HTMLVideoElement} The newly created video element.
|
|
635
|
+
*/
|
|
636
|
+
createVideoTag(props) {
|
|
637
|
+
/** @type {HTMLVideoElement} */
|
|
638
|
+
const videoTag = dom.utils.createElement('VIDEO');
|
|
639
|
+
if (props) {
|
|
640
|
+
for (const key in props) {
|
|
641
|
+
videoTag[key] = props[key];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
452
644
|
this._setTagAttrs(videoTag);
|
|
453
645
|
return videoTag;
|
|
454
|
-
}
|
|
646
|
+
}
|
|
455
647
|
|
|
648
|
+
/**
|
|
649
|
+
* @private
|
|
650
|
+
* @description Sets the size of the video element.
|
|
651
|
+
* @param {string|number} w - The width of the video.
|
|
652
|
+
* @param {string|number} h - The height of the video.
|
|
653
|
+
*/
|
|
654
|
+
_applySize(w, h) {
|
|
655
|
+
if (!w) w = this.inputX?.value || this.pluginOptions.defaultWidth;
|
|
656
|
+
if (!h) h = this.inputY?.value || this.pluginOptions.defaultHeight;
|
|
657
|
+
if (this._onlyPercentage) {
|
|
658
|
+
if (!w) w = '100%';
|
|
659
|
+
else if (/%$/.test(w + '')) w += '%';
|
|
660
|
+
}
|
|
661
|
+
this.figure.setSize(w, h);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* @private
|
|
666
|
+
* @description Retrieves video information including size and alignment.
|
|
667
|
+
* @returns {*} Video information object.
|
|
668
|
+
*/
|
|
456
669
|
_getInfo() {
|
|
457
670
|
return {
|
|
458
|
-
inputWidth: this.inputX
|
|
459
|
-
inputHeight: this.inputY
|
|
671
|
+
inputWidth: this.inputX?.value || '',
|
|
672
|
+
inputHeight: this.inputY?.value || '',
|
|
460
673
|
align: this._align,
|
|
461
674
|
isUpdate: this.modal.isUpdate,
|
|
462
675
|
element: this._element
|
|
463
676
|
};
|
|
464
|
-
}
|
|
677
|
+
}
|
|
465
678
|
|
|
679
|
+
/**
|
|
680
|
+
* @description Create an "video" component using the provided files.
|
|
681
|
+
* @param {FileList|File[]} fileList File object list
|
|
682
|
+
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
683
|
+
*/
|
|
466
684
|
async submitFile(fileList) {
|
|
467
685
|
if (fileList.length === 0) return;
|
|
468
686
|
|
|
469
687
|
let fileSize = 0;
|
|
470
688
|
const files = [];
|
|
471
|
-
const slngleSizeLimit = this.uploadSingleSizeLimit;
|
|
689
|
+
const slngleSizeLimit = this.pluginOptions.uploadSingleSizeLimit;
|
|
472
690
|
for (let i = 0, len = fileList.length, f, s; i < len; i++) {
|
|
473
691
|
f = fileList[i];
|
|
474
692
|
if (!/video/i.test(f.type)) continue;
|
|
@@ -483,7 +701,7 @@ Video.prototype = {
|
|
|
483
701
|
file: f
|
|
484
702
|
});
|
|
485
703
|
|
|
486
|
-
this.
|
|
704
|
+
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
487
705
|
|
|
488
706
|
return false;
|
|
489
707
|
}
|
|
@@ -498,7 +716,7 @@ Video.prototype = {
|
|
|
498
716
|
const err = '[SUNEDITOR.videoUpload.fail] Size of uploadable total videos: ' + limitSize / 1000 + 'KB';
|
|
499
717
|
const message = await this.triggerEvent('onVideoUploadError', { error: err, limitSize, currentSize, uploadSize: fileSize });
|
|
500
718
|
|
|
501
|
-
this.
|
|
719
|
+
this.ui.alertOpen(message === NO_EVENT ? err : message || err, 'error');
|
|
502
720
|
|
|
503
721
|
return false;
|
|
504
722
|
}
|
|
@@ -513,9 +731,11 @@ Video.prototype = {
|
|
|
513
731
|
infos = newInfos || infos;
|
|
514
732
|
this._serverUpload(infos, infos.files);
|
|
515
733
|
}.bind(this, videoInfo);
|
|
734
|
+
// se-ts-ignore
|
|
735
|
+
this._serverUpload;
|
|
516
736
|
|
|
517
737
|
const result = await this.triggerEvent('onVideoUploadBefore', {
|
|
518
|
-
|
|
738
|
+
info: videoInfo,
|
|
519
739
|
handler
|
|
520
740
|
});
|
|
521
741
|
|
|
@@ -524,8 +744,13 @@ Video.prototype = {
|
|
|
524
744
|
if (result !== null && typeof result === 'object') handler(result);
|
|
525
745
|
|
|
526
746
|
if (result === true || result === NO_EVENT) handler(null);
|
|
527
|
-
}
|
|
747
|
+
}
|
|
528
748
|
|
|
749
|
+
/**
|
|
750
|
+
* @description Create an "video" component using the provided url.
|
|
751
|
+
* @param {string} url File url
|
|
752
|
+
* @returns {Promise<boolean>} If return false, the file upload will be canceled
|
|
753
|
+
*/
|
|
529
754
|
async submitURL(url) {
|
|
530
755
|
if (!url) url = this._linkValue;
|
|
531
756
|
if (!url) return false;
|
|
@@ -551,7 +776,7 @@ Video.prototype = {
|
|
|
551
776
|
}.bind(this, videoInfo);
|
|
552
777
|
|
|
553
778
|
const result = await this.triggerEvent('onVideoUploadBefore', {
|
|
554
|
-
|
|
779
|
+
info: videoInfo,
|
|
555
780
|
handler
|
|
556
781
|
});
|
|
557
782
|
|
|
@@ -562,87 +787,110 @@ Video.prototype = {
|
|
|
562
787
|
if (result === true || result === NO_EVENT) handler(null);
|
|
563
788
|
|
|
564
789
|
return true;
|
|
565
|
-
}
|
|
790
|
+
}
|
|
566
791
|
|
|
792
|
+
/**
|
|
793
|
+
* @private
|
|
794
|
+
* @description Updates the video component within the editor.
|
|
795
|
+
* @param {HTMLIFrameElement|HTMLVideoElement} oFrame - The video element to update.
|
|
796
|
+
*/
|
|
567
797
|
_update(oFrame) {
|
|
568
798
|
if (!oFrame) return;
|
|
569
799
|
|
|
570
800
|
if (/^video$/i.test(oFrame.nodeName)) {
|
|
571
|
-
this._setTagAttrs(oFrame);
|
|
801
|
+
this._setTagAttrs(/** @type {HTMLVideoElement} */ (oFrame));
|
|
572
802
|
} else if (/^iframe$/i.test(oFrame.nodeName)) {
|
|
573
|
-
this._setIframeAttrs(oFrame);
|
|
803
|
+
this._setIframeAttrs(/** @type {HTMLIFrameElement} */ (oFrame));
|
|
574
804
|
}
|
|
575
805
|
|
|
576
|
-
let existElement = this.format.isBlock(oFrame.parentNode) ||
|
|
806
|
+
let existElement = this.format.isBlock(oFrame.parentNode) || dom.check.isWysiwygFrame(oFrame.parentNode) ? oFrame : this.format.getLine(oFrame) || oFrame;
|
|
577
807
|
|
|
578
808
|
const prevFrame = oFrame;
|
|
579
|
-
|
|
580
|
-
const figure = Figure.CreateContainer(
|
|
809
|
+
const cloneFrame = /** @type {HTMLIFrameElement|HTMLVideoElement} */ (oFrame.cloneNode(true));
|
|
810
|
+
const figure = Figure.CreateContainer(cloneFrame, 'se-video-container');
|
|
581
811
|
const container = figure.container;
|
|
582
812
|
|
|
583
813
|
const figcaption = existElement.querySelector('figcaption');
|
|
584
814
|
let caption = null;
|
|
585
815
|
if (figcaption) {
|
|
586
|
-
caption =
|
|
816
|
+
caption = dom.utils.createElement('DIV');
|
|
587
817
|
caption.innerHTML = figcaption.innerHTML;
|
|
588
|
-
|
|
818
|
+
dom.utils.removeItem(figcaption);
|
|
589
819
|
}
|
|
590
820
|
|
|
591
821
|
// size
|
|
592
|
-
this.figure.open(
|
|
593
|
-
const size = (
|
|
594
|
-
this.
|
|
822
|
+
this.figure.open(cloneFrame, { nonResizing: this._nonResizing, nonSizeInfo: false, nonBorder: false, figureTarget: false, __fileManagerInfo: true });
|
|
823
|
+
const size = (cloneFrame.getAttribute('data-se-size') || ',').split(',');
|
|
824
|
+
this._applySize(size[0] || prevFrame.style.width || prevFrame.width || '', size[1] || prevFrame.style.height || prevFrame.height || '');
|
|
595
825
|
|
|
596
826
|
// align
|
|
597
827
|
const format = this.format.getLine(prevFrame);
|
|
598
828
|
if (format) this._align = format.style.textAlign || format.style.float;
|
|
599
|
-
this.figure.setAlign(
|
|
829
|
+
this.figure.setAlign(cloneFrame, this._align);
|
|
600
830
|
|
|
601
|
-
if (
|
|
602
|
-
prevFrame.
|
|
603
|
-
} else if (
|
|
604
|
-
const refer =
|
|
831
|
+
if (dom.query.getParentElement(prevFrame, dom.check.isExcludeFormat)) {
|
|
832
|
+
prevFrame.replaceWith(container);
|
|
833
|
+
} else if (dom.check.isListCell(existElement)) {
|
|
834
|
+
const refer = dom.query.getParentElement(prevFrame, (current) => current.parentNode === existElement);
|
|
605
835
|
existElement.insertBefore(container, refer);
|
|
606
|
-
|
|
836
|
+
dom.utils.removeItem(prevFrame);
|
|
607
837
|
this.nodeTransform.removeEmptyNode(refer, null, true);
|
|
608
|
-
} else if (this.format.
|
|
609
|
-
const refer =
|
|
838
|
+
} else if (this.format.isLine(existElement)) {
|
|
839
|
+
const refer = dom.query.getParentElement(prevFrame, (current) => current.parentNode === existElement);
|
|
610
840
|
existElement = this.nodeTransform.split(existElement, refer);
|
|
611
841
|
existElement.parentNode.insertBefore(container, existElement);
|
|
612
|
-
|
|
842
|
+
dom.utils.removeItem(prevFrame);
|
|
613
843
|
this.nodeTransform.removeEmptyNode(existElement, null, true);
|
|
614
844
|
} else {
|
|
615
|
-
existElement.
|
|
845
|
+
/** @type {Element} */ (existElement).replaceWith(container);
|
|
616
846
|
}
|
|
617
847
|
|
|
618
848
|
if (caption) existElement.parentNode.insertBefore(caption, container.nextElementSibling);
|
|
619
849
|
|
|
620
|
-
return
|
|
621
|
-
}
|
|
850
|
+
return cloneFrame;
|
|
851
|
+
}
|
|
622
852
|
|
|
853
|
+
/**
|
|
854
|
+
* @private
|
|
855
|
+
* @description Registers the uploaded video in the editor.
|
|
856
|
+
* @param {VideoInfo_video} info - Video information object.
|
|
857
|
+
* @param {Object<string, *>} response - Server response containing video data.
|
|
858
|
+
*/
|
|
623
859
|
_register(info, response) {
|
|
624
860
|
const fileList = response.result;
|
|
625
861
|
const videoTag = this.createVideoTag();
|
|
626
862
|
|
|
627
863
|
for (let i = 0, len = fileList.length; i < len; i++) {
|
|
628
|
-
|
|
864
|
+
const ctag = info.isUpdate ? info.element : /** @type {HTMLIFrameElement|HTMLVideoElement} */ (videoTag.cloneNode(false));
|
|
865
|
+
this.create(ctag, fileList[i].url, info.inputWidth, info.inputHeight, info.align, info.isUpdate, {
|
|
629
866
|
name: fileList[i].name,
|
|
630
867
|
size: fileList[i].size
|
|
631
868
|
});
|
|
632
869
|
}
|
|
633
|
-
}
|
|
870
|
+
}
|
|
634
871
|
|
|
872
|
+
/**
|
|
873
|
+
* @private
|
|
874
|
+
* @description Uploads a video to the server using an external upload handler.
|
|
875
|
+
* @param {VideoInfo_video} info - Video information object.
|
|
876
|
+
* @param {FileList} files - The video files to upload.
|
|
877
|
+
*/
|
|
635
878
|
_serverUpload(info, files) {
|
|
636
879
|
if (!files) return;
|
|
637
880
|
|
|
638
881
|
const videoUploadUrl = this.pluginOptions.uploadUrl;
|
|
639
882
|
if (typeof videoUploadUrl === 'string' && videoUploadUrl.length > 0) {
|
|
640
|
-
this.fileManager.upload(videoUploadUrl, this.pluginOptions.uploadHeaders, files, UploadCallBack.bind(this, info), this._error.bind(this));
|
|
883
|
+
this.fileManager.upload(videoUploadUrl, this.pluginOptions.uploadHeaders, files, this.#UploadCallBack.bind(this, info), this._error.bind(this));
|
|
641
884
|
}
|
|
642
|
-
}
|
|
885
|
+
}
|
|
643
886
|
|
|
887
|
+
/**
|
|
888
|
+
* @private
|
|
889
|
+
* @description Sets attributes for the video tag.
|
|
890
|
+
* @param {HTMLVideoElement} element - The video element.
|
|
891
|
+
*/
|
|
644
892
|
_setTagAttrs(element) {
|
|
645
|
-
element.setAttribute('controls', true);
|
|
893
|
+
element.setAttribute('controls', 'true');
|
|
646
894
|
|
|
647
895
|
const attrs = this.pluginOptions.videoTagAttributes;
|
|
648
896
|
if (!attrs) return;
|
|
@@ -650,8 +898,13 @@ Video.prototype = {
|
|
|
650
898
|
for (const key in attrs) {
|
|
651
899
|
element.setAttribute(key, attrs[key]);
|
|
652
900
|
}
|
|
653
|
-
}
|
|
901
|
+
}
|
|
654
902
|
|
|
903
|
+
/**
|
|
904
|
+
* @private
|
|
905
|
+
* @description Sets attributes for the iframe tag.
|
|
906
|
+
* @param {HTMLIFrameElement} element - The iframe element.
|
|
907
|
+
*/
|
|
655
908
|
_setIframeAttrs(element) {
|
|
656
909
|
element.frameBorder = '0';
|
|
657
910
|
element.allowFullscreen = true;
|
|
@@ -662,130 +915,206 @@ Video.prototype = {
|
|
|
662
915
|
for (const key in attrs) {
|
|
663
916
|
element.setAttribute(key, attrs[key]);
|
|
664
917
|
}
|
|
665
|
-
}
|
|
918
|
+
}
|
|
666
919
|
|
|
667
|
-
|
|
920
|
+
/**
|
|
921
|
+
* @private
|
|
922
|
+
* @description Selects a ratio option in the ratio dropdown.
|
|
923
|
+
* @param {string|number} value - The selected ratio value.
|
|
924
|
+
* @returns {boolean} Returns true if a ratio was selected.
|
|
925
|
+
*/
|
|
926
|
+
_setRatioSelect(value) {
|
|
668
927
|
let ratioSelected = false;
|
|
669
|
-
const ratioOption = this.
|
|
928
|
+
const ratioOption = this.frameRatioOption.options;
|
|
670
929
|
|
|
671
|
-
if (/%$/.test(value) || this._onlyPercentage) value = numbers.get(value, 2) / 100 + '';
|
|
672
|
-
else if (!numbers.is(value) || value
|
|
930
|
+
if (/%$/.test(value + '') || this._onlyPercentage) value = numbers.get(value, 2) / 100 + '';
|
|
931
|
+
else if (!numbers.is(value) || Number(value) >= 1) value = '';
|
|
673
932
|
|
|
674
933
|
this.inputY.placeholder = '';
|
|
675
934
|
for (let i = 0, len = ratioOption.length; i < len; i++) {
|
|
676
935
|
if (ratioOption[i].value === value) {
|
|
677
936
|
ratioSelected = ratioOption[i].selected = true;
|
|
678
|
-
this.inputY.placeholder = !value ? '' : value * 100 + '%';
|
|
937
|
+
this.inputY.placeholder = !value ? '' : Number(value) * 100 + '%';
|
|
679
938
|
} else ratioOption[i].selected = false;
|
|
680
939
|
}
|
|
681
940
|
|
|
682
941
|
return ratioSelected;
|
|
683
|
-
}
|
|
942
|
+
}
|
|
684
943
|
|
|
944
|
+
/**
|
|
945
|
+
* @private
|
|
946
|
+
* @description Handles video upload errors.
|
|
947
|
+
* @param {Object<string, *>} response - The error response object.
|
|
948
|
+
* @returns {Promise<void>}
|
|
949
|
+
*/
|
|
685
950
|
async _error(response) {
|
|
686
951
|
const message = await this.triggerEvent('onVideoUploadError', { error: response });
|
|
687
952
|
const err = message === NO_EVENT ? response.errorMessage : message || response.errorMessage;
|
|
688
|
-
this.
|
|
953
|
+
this.ui.alertOpen(err, 'error');
|
|
689
954
|
console.error('[SUNEDITOR.plugin.video.error]', message);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
constructor: Video
|
|
693
|
-
};
|
|
955
|
+
}
|
|
694
956
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
957
|
+
/**
|
|
958
|
+
* @description Handles the callback function for video upload completion.
|
|
959
|
+
* @param {VideoInfo_video} info - Video information.
|
|
960
|
+
* @param {XMLHttpRequest} xmlHttp - The XMLHttpRequest object.
|
|
961
|
+
*/
|
|
962
|
+
async #UploadCallBack(info, xmlHttp) {
|
|
963
|
+
if ((await this.triggerEvent('videoUploadHandler', { xmlHttp, info })) === NO_EVENT) {
|
|
964
|
+
const response = JSON.parse(xmlHttp.responseText);
|
|
965
|
+
if (response.errorMessage) {
|
|
966
|
+
this._error(response);
|
|
967
|
+
} else {
|
|
968
|
+
this._register(info, response);
|
|
969
|
+
}
|
|
702
970
|
}
|
|
703
971
|
}
|
|
704
|
-
}
|
|
705
972
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
this.
|
|
973
|
+
/**
|
|
974
|
+
* @description Removes selected files from the file input.
|
|
975
|
+
*/
|
|
976
|
+
#RemoveSelectedFiles() {
|
|
977
|
+
this.videoInputFile.value = '';
|
|
978
|
+
if (this.videoUrlFile) {
|
|
979
|
+
this.videoUrlFile.disabled = false;
|
|
980
|
+
this.previewSrc.style.textDecoration = '';
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// inputFile check
|
|
984
|
+
Modal.OnChangeFile(this.fileModalWrapper, []);
|
|
711
985
|
}
|
|
712
986
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
987
|
+
/**
|
|
988
|
+
* @description Handles link preview input changes.
|
|
989
|
+
* @param {InputEvent} e - Event object
|
|
990
|
+
*/
|
|
991
|
+
#OnLinkPreview(e) {
|
|
992
|
+
/** @type {HTMLInputElement} */
|
|
993
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
994
|
+
const value = eventTarget.value.trim();
|
|
995
|
+
if (/^<iframe.*\/iframe>$/.test(value)) {
|
|
996
|
+
this._linkValue = value;
|
|
997
|
+
this.previewSrc.textContent = '<IFrame :src=".."></IFrame>';
|
|
998
|
+
} else {
|
|
999
|
+
this._linkValue = this.previewSrc.textContent = !value
|
|
1000
|
+
? ''
|
|
1001
|
+
: this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
|
|
1002
|
+
? this.options.get('defaultUrlProtocol') + value
|
|
1003
|
+
: !value.includes('://')
|
|
1004
|
+
? '/' + value
|
|
1005
|
+
: value;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
716
1008
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
this.
|
|
722
|
-
} else {
|
|
723
|
-
this._linkValue = this.previewSrc.textContent = !value
|
|
724
|
-
? ''
|
|
725
|
-
: this.options.get('defaultUrlProtocol') && !value.includes('://') && value.indexOf('#') !== 0
|
|
726
|
-
? this.options.get('defaultUrlProtocol') + value
|
|
727
|
-
: !value.includes('://')
|
|
728
|
-
? '/' + value
|
|
729
|
-
: value;
|
|
1009
|
+
/**
|
|
1010
|
+
* @description Opens the video gallery.
|
|
1011
|
+
*/
|
|
1012
|
+
#OpenGallery() {
|
|
1013
|
+
this.plugins.videoGallery.open(this.#SetUrlInput.bind(this));
|
|
730
1014
|
}
|
|
731
|
-
}
|
|
732
1015
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
this.videoUrlFile.
|
|
739
|
-
this.
|
|
1016
|
+
/**
|
|
1017
|
+
* @description Sets the URL input value when selecting from the gallery.
|
|
1018
|
+
* @param {HTMLInputElement} target - The selected video element.
|
|
1019
|
+
*/
|
|
1020
|
+
#SetUrlInput(target) {
|
|
1021
|
+
this._linkValue = this.previewSrc.textContent = this.videoUrlFile.value = target.getAttribute('data-command') || target.src;
|
|
1022
|
+
this.videoUrlFile.focus();
|
|
740
1023
|
}
|
|
741
1024
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1025
|
+
/**
|
|
1026
|
+
* @param {InputEvent} e - Event object
|
|
1027
|
+
*/
|
|
1028
|
+
#OnfileInputChange(e) {
|
|
1029
|
+
if (!this.videoInputFile.value) {
|
|
1030
|
+
this.videoUrlFile.disabled = false;
|
|
1031
|
+
this.previewSrc.style.textDecoration = '';
|
|
1032
|
+
} else {
|
|
1033
|
+
this.videoUrlFile.disabled = true;
|
|
1034
|
+
this.previewSrc.style.textDecoration = 'line-through';
|
|
1035
|
+
}
|
|
745
1036
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
this.inputX.value = this._origin_w;
|
|
751
|
-
this.inputY.value = this._origin_h;
|
|
1037
|
+
// inputFile check
|
|
1038
|
+
/** @type {HTMLInputElement} */
|
|
1039
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
1040
|
+
Modal.OnChangeFile(this.fileModalWrapper, eventTarget.files);
|
|
752
1041
|
}
|
|
753
|
-
}
|
|
754
1042
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1043
|
+
#OnClickRevert() {
|
|
1044
|
+
if (this._onlyPercentage) {
|
|
1045
|
+
this.inputX.value = Number(this._origin_w) > 100 ? '100' : this._origin_w;
|
|
1046
|
+
} else {
|
|
1047
|
+
this.inputX.value = this._origin_w;
|
|
1048
|
+
this.inputY.value = this._origin_h;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
761
1051
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1052
|
+
/**
|
|
1053
|
+
* @param {InputEvent} e - Event object
|
|
1054
|
+
*/
|
|
1055
|
+
#SetRatio(e) {
|
|
1056
|
+
/** @type {HTMLSelectElement} */
|
|
1057
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
1058
|
+
const value = eventTarget.options[eventTarget.selectedIndex].value;
|
|
1059
|
+
this._defaultSizeY = this.figure.autoRatio.current = this._frameRatio = !value ? this._defaultSizeY : Number(value) * 100 + '%';
|
|
1060
|
+
this.inputY.placeholder = !value ? '' : Number(value) * 100 + '%';
|
|
1061
|
+
this.inputY.value = '';
|
|
1062
|
+
}
|
|
765
1063
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
e.preventDefault();
|
|
769
|
-
return;
|
|
1064
|
+
#OnChangeRatio() {
|
|
1065
|
+
this._ratio = this.proportion.checked ? Figure.GetRatio(this.inputX.value, this.inputY.value, this.sizeUnit) : { w: 1, h: 1 };
|
|
770
1066
|
}
|
|
771
1067
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1068
|
+
/**
|
|
1069
|
+
* @param {"x"|"y"} xy - x or y
|
|
1070
|
+
* @param {KeyboardEvent} e - Event object
|
|
1071
|
+
*/
|
|
1072
|
+
#OnInputSize(xy, e) {
|
|
1073
|
+
if (keyCodeMap.isSpace(e.code)) {
|
|
1074
|
+
e.preventDefault();
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/** @type {HTMLInputElement} */
|
|
1079
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
1080
|
+
if (xy === 'x' && this._onlyPercentage && Number(eventTarget.value) > 100) {
|
|
1081
|
+
eventTarget.value = '100';
|
|
1082
|
+
} else if (this.proportion.checked) {
|
|
1083
|
+
const ratioSize = Figure.CalcRatio(this.inputX.value, this.inputY.value, this.sizeUnit, this._ratio);
|
|
1084
|
+
if (xy === 'x') {
|
|
1085
|
+
this.inputY.value = String(ratioSize.h);
|
|
1086
|
+
} else {
|
|
1087
|
+
this.inputX.value = String(ratioSize.w);
|
|
1088
|
+
}
|
|
780
1089
|
}
|
|
781
|
-
}
|
|
782
1090
|
|
|
783
|
-
|
|
784
|
-
|
|
1091
|
+
if (xy === 'y') {
|
|
1092
|
+
this._setRatioSelect(eventTarget.value || this._defaultRatio);
|
|
1093
|
+
}
|
|
785
1094
|
}
|
|
786
1095
|
}
|
|
787
1096
|
|
|
788
|
-
|
|
1097
|
+
/**
|
|
1098
|
+
* @typedef {object} ModalReturns_video
|
|
1099
|
+
* @property {HTMLElement} html
|
|
1100
|
+
* @property {HTMLElement} alignForm
|
|
1101
|
+
* @property {HTMLElement} fileModalWrapper
|
|
1102
|
+
* @property {HTMLInputElement} videoInputFile
|
|
1103
|
+
* @property {HTMLInputElement} videoUrlFile
|
|
1104
|
+
* @property {HTMLElement} previewSrc
|
|
1105
|
+
* @property {HTMLButtonElement} galleryButton
|
|
1106
|
+
* @property {HTMLInputElement} proportion
|
|
1107
|
+
* @property {HTMLSelectElement} frameRatioOption
|
|
1108
|
+
* @property {HTMLInputElement} inputX
|
|
1109
|
+
* @property {HTMLInputElement} inputY
|
|
1110
|
+
* @property {HTMLButtonElement} revertBtn
|
|
1111
|
+
* @property {HTMLButtonElement} fileRemoveBtn
|
|
1112
|
+
*
|
|
1113
|
+
* @param {__se__EditorCore} editor
|
|
1114
|
+
* @param {*} pluginOptions
|
|
1115
|
+
* @returns {ModalReturns_video}
|
|
1116
|
+
*/
|
|
1117
|
+
function CreateHTML_modal({ lang, icons, plugins }, pluginOptions) {
|
|
789
1118
|
let html = /*html*/ `
|
|
790
1119
|
<form method="post" enctype="multipart/form-data">
|
|
791
1120
|
<div class="se-modal-header">
|
|
@@ -808,7 +1137,17 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
|
|
|
808
1137
|
html += /*html*/ `
|
|
809
1138
|
<div class="se-modal-form">
|
|
810
1139
|
<label>${lang.video_modal_url}</label>
|
|
811
|
-
<
|
|
1140
|
+
<div class="se-modal-form-files">
|
|
1141
|
+
<input class="se-input-form se-input-url" type="text" data-focus />
|
|
1142
|
+
${
|
|
1143
|
+
plugins.videoGallery
|
|
1144
|
+
? `<button type="button" class="se-btn se-tooltip se-modal-files-edge-button __se__gallery" aria-label="${lang.videoGallery}">
|
|
1145
|
+
${icons.video_gallery}
|
|
1146
|
+
${dom.utils.createTooltipInner(lang.videoGallery)}
|
|
1147
|
+
</button>`
|
|
1148
|
+
: ''
|
|
1149
|
+
}
|
|
1150
|
+
</div>
|
|
812
1151
|
<pre class="se-link-preview"></pre>
|
|
813
1152
|
</div>`;
|
|
814
1153
|
}
|
|
@@ -817,7 +1156,8 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
|
|
|
817
1156
|
const ratioList = pluginOptions.ratioOptions || [
|
|
818
1157
|
{ name: '16:9', value: 0.5625 },
|
|
819
1158
|
{ name: '4:3', value: 0.75 },
|
|
820
|
-
{ name: '21:9', value: 0.4285 }
|
|
1159
|
+
{ name: '21:9', value: 0.4285 },
|
|
1160
|
+
{ name: '9:16', value: 1.78 }
|
|
821
1161
|
];
|
|
822
1162
|
const ratio = pluginOptions.defaultRatio;
|
|
823
1163
|
const onlyPercentage = pluginOptions.percentageOnlySize;
|
|
@@ -833,11 +1173,11 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
|
|
|
833
1173
|
<label class="size-h"${heightDisplay}>${lang.height}</label>
|
|
834
1174
|
<label class="size-h"${ratioDisplay}>(${lang.ratio})</label>
|
|
835
1175
|
</div>
|
|
836
|
-
<input class="se-input-control
|
|
1176
|
+
<input class="se-input-control _se_size_x" placeholder="100%"${onlyPercentage ? ' type="number" min="1"' : 'type="text"'}${onlyPercentage ? ' max="100"' : ''}/>
|
|
837
1177
|
<label class="se-modal-size-x"${onlyWidthDisplay}>${onlyPercentage ? '%' : 'x'}</label>
|
|
838
|
-
<input class="se-input-control
|
|
1178
|
+
<input class="se-input-control _se_size_y" placeholder="${pluginOptions.defaultRatio * 100}%"
|
|
839
1179
|
${onlyPercentage ? ' type="number" min="1"' : 'type="text"'}${onlyPercentage ? ' max="100"' : ''}${heightDisplay}/>
|
|
840
|
-
<select class="se-input-select se-
|
|
1180
|
+
<select class="se-input-select se-modal-ratio" title="${lang.ratio}" aria-label="${lang.ratio}"${ratioDisplay}>
|
|
841
1181
|
${!heightDisplay ? '<option value=""> - </option>' : ''}
|
|
842
1182
|
${ratioList.map((ratioOption) => `<option value="${ratioOption.value}"${ratio.toString() === ratioOption.value.toString() ? ' selected' : ''}>${ratioOption.name}</option>`).join('')}
|
|
843
1183
|
</select>
|
|
@@ -845,7 +1185,7 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
|
|
|
845
1185
|
</div>
|
|
846
1186
|
<div class="se-modal-form se-modal-form-footer"${onlyPercentDisplay}${onlyWidthDisplay}>
|
|
847
1187
|
<label>
|
|
848
|
-
<input type="checkbox" class="se-modal-btn-check
|
|
1188
|
+
<input type="checkbox" class="se-modal-btn-check _se_check_proportion" />
|
|
849
1189
|
<span>${lang.proportion}</span>
|
|
850
1190
|
</label>
|
|
851
1191
|
</div>`;
|
|
@@ -864,7 +1204,23 @@ function CreateHTML_modal({ lang, icons }, pluginOptions) {
|
|
|
864
1204
|
</div>
|
|
865
1205
|
</form>`;
|
|
866
1206
|
|
|
867
|
-
|
|
1207
|
+
const content = dom.utils.createElement('DIV', { class: 'se-modal-content' }, html);
|
|
1208
|
+
|
|
1209
|
+
return {
|
|
1210
|
+
html: content,
|
|
1211
|
+
alignForm: content.querySelector('.se-figure-align'),
|
|
1212
|
+
fileModalWrapper: content.querySelector('.se-flex-input-wrapper'),
|
|
1213
|
+
videoInputFile: content.querySelector('.__se__file_input'),
|
|
1214
|
+
videoUrlFile: content.querySelector('.se-input-url'),
|
|
1215
|
+
previewSrc: content.querySelector('.se-link-preview'),
|
|
1216
|
+
galleryButton: content.querySelector('.__se__gallery'),
|
|
1217
|
+
proportion: content.querySelector('._se_check_proportion'),
|
|
1218
|
+
frameRatioOption: content.querySelector('.se-modal-ratio'),
|
|
1219
|
+
inputX: content.querySelector('._se_size_x'),
|
|
1220
|
+
inputY: content.querySelector('._se_size_y'),
|
|
1221
|
+
revertBtn: content.querySelector('.se-modal-btn-revert'),
|
|
1222
|
+
fileRemoveBtn: content.querySelector('.se-file-remove')
|
|
1223
|
+
};
|
|
868
1224
|
}
|
|
869
1225
|
|
|
870
1226
|
export default Video;
|