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