Lexical: Added media src conversions

Only actuall added YT in the end.
Google had changed URL scheme, and Vimeo seems to just be something else
now, can't really browse video pages like before.
This commit is contained in:
Dan Brown 2025-01-27 14:28:27 +00:00
parent 958b537a49
commit d89a2fdb15
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
4 changed files with 79 additions and 22 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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;