diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts index a675665ac..81fb96a93 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts @@ -16,6 +16,7 @@ import { } from "lexical/nodes/common"; import {$selectSingleNode} from "../../utils/selection"; import {SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; +import * as url from "node:url"; export type MediaNodeTag = 'iframe' | 'embed' | 'object' | 'video' | 'audio'; export type MediaNodeSource = { @@ -343,11 +344,55 @@ export function $createMediaNodeFromHtml(html: string): MediaNode | null { return domElementToNode(tag as MediaNodeTag, el); } +interface UrlPattern { + readonly regex: RegExp; + readonly w: number; + readonly h: number; + readonly url: string; +} + +/** + * These patterns originate from the tinymce/tinymce project. + * https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts + * License: MIT Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc. + * License Link: https://github.com/tinymce/tinymce/blob/584a150679669859a528828e5d2910a083b1d911/LICENSE.TXT + */ +const urlPatterns: UrlPattern[] = [ + { + regex: /.*?youtu\.be\/([\w\-_\?&=.]+)/i, + w: 560, h: 314, + url: 'https://www.youtube.com/embed/$1', + }, + { + regex: /.*youtube\.com(.+)v=([^&]+)(&([a-z0-9&=\-_]+))?.*/i, + w: 560, h: 314, + url: 'https://www.youtube.com/embed/$2?$4', + }, + { + regex: /.*youtube.com\/embed\/([a-z0-9\?&=\-_]+).*/i, + w: 560, h: 314, + url: 'https://www.youtube.com/embed/$1', + }, +]; + const videoExtensions = ['mp4', 'mpeg', 'm4v', 'm4p', 'mov']; const audioExtensions = ['3gp', 'aac', 'flac', 'mp3', 'm4a', 'ogg', 'wav', 'webm']; const iframeExtensions = ['html', 'htm', 'php', 'asp', 'aspx', '']; export function $createMediaNodeFromSrc(src: string): MediaNode { + + for (const pattern of urlPatterns) { + const match = src.match(pattern.regex); + if (match) { + const newSrc = src.replace(pattern.regex, pattern.url); + const node = new MediaNode('iframe'); + node.setSrc(newSrc); + node.setHeight(pattern.h); + node.setWidth(pattern.w); + return node; + } + } + let nodeTag: MediaNodeTag = 'iframe'; const srcEnd = src.split('?')[0].split('/').pop() || ''; const srcEndSplit = srcEnd.split('.'); @@ -360,7 +405,9 @@ export function $createMediaNodeFromSrc(src: string): MediaNode { nodeTag = 'embed'; } - return new MediaNode(nodeTag); + const node = new MediaNode(nodeTag); + node.setSrc(src); + return node; } export function $isMediaNode(node: LexicalNode | null | undefined): node is MediaNode { diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index 1d42ba3e4..94ae0e144 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -10,7 +10,6 @@ ## Secondary Todo -- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts) - Deep check of translation coverage ## Bugs diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts index 6612c0dc4..63df4fea8 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts @@ -32,7 +32,7 @@ import { } from "../../../utils/selection"; import {$isDiagramNode, $openDrawingEditorForNode, showDiagramManagerForInsert} from "../../../utils/diagrams"; import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images"; -import {$showDetailsForm, $showImageForm, $showLinkForm} from "../forms/objects"; +import {$showDetailsForm, $showImageForm, $showLinkForm, $showMediaForm} from "../forms/objects"; import {formatCodeBlock} from "../../../utils/formats"; export const link: EditorButtonDefinition = { @@ -168,24 +168,11 @@ export const media: EditorButtonDefinition = { label: 'Insert/edit Media', icon: mediaIcon, action(context: EditorUiContext) { - const mediaModal = context.manager.createModal('media'); - context.editor.getEditorState().read(() => { const selection = $getSelection(); const selectedNode = $getNodeFromSelection(selection, $isMediaNode) as MediaNode | null; - let formDefaults = {}; - if (selectedNode) { - const nodeAttrs = selectedNode.getAttributes(); - formDefaults = { - src: nodeAttrs.src || nodeAttrs.data || '', - width: nodeAttrs.width, - height: nodeAttrs.height, - embed: '', - } - } - - mediaModal.show(formDefaults); + $showMediaForm(selectedNode, context); }); }, isActive(selection: BaseSelection | null): boolean { diff --git a/resources/js/wysiwyg/ui/defaults/forms/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts index 21d333c3a..0effdc171 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/objects.ts @@ -186,6 +186,23 @@ export const link: EditorFormDefinition = { ], }; +export function $showMediaForm(media: MediaNode|null, context: EditorUiContext): void { + const mediaModal = context.manager.createModal('media'); + + let formDefaults = {}; + if (media) { + const nodeAttrs = media.getAttributes(); + formDefaults = { + src: nodeAttrs.src || nodeAttrs.data || '', + width: nodeAttrs.width, + height: nodeAttrs.height, + embed: '', + } + } + + mediaModal.show(formDefaults); +} + export const media: EditorFormDefinition = { submitText: 'Save', async action(formData, context: EditorUiContext) { @@ -215,12 +232,19 @@ export const media: EditorFormDefinition = { const height = (formData.get('height') || '').toString().trim(); const width = (formData.get('width') || '').toString().trim(); - const updateNode = selectedNode || $createMediaNodeFromSrc(src); - updateNode.setSrc(src); - updateNode.setWidthAndHeight(width, height); - if (!selectedNode) { - $insertNodes([updateNode]); + // Update existing + if (selectedNode) { + selectedNode.setSrc(src); + selectedNode.setWidthAndHeight(width, height); + return; } + + // Insert new + const node = $createMediaNodeFromSrc(src); + if (width || height) { + node.setWidthAndHeight(width, height); + } + $insertNodes([node]); }); return true;