From c8f6b7e0d655562aa143ad7c3e82c560b376e74b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 27 Jul 2024 17:25:30 +0100 Subject: [PATCH] Lexical: Got media node core work & form done --- resources/js/wysiwyg/nodes/media.ts | 23 +++++++++- resources/js/wysiwyg/todo.md | 3 +- .../wysiwyg/ui/defaults/form-definitions.ts | 43 +++++++++++++------ resources/js/wysiwyg/ui/framework/forms.ts | 2 +- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/resources/js/wysiwyg/nodes/media.ts b/resources/js/wysiwyg/nodes/media.ts index e0c1b3141..751f420fa 100644 --- a/resources/js/wysiwyg/nodes/media.ts +++ b/resources/js/wysiwyg/nodes/media.ts @@ -30,7 +30,7 @@ const attributeAllowList = [ function filterAttributes(attributes: Record): Record { const filtered: Record = {}; - for (const key in Object.keys(attributes)) { + for (const key of Object.keys(attributes)) { if (attributeAllowList.includes(key)) { filtered[key] = attributes[key]; } @@ -170,7 +170,7 @@ export class MediaNode extends ElementNode { exportJSON(): SerializedMediaNode { return { ...super.exportJSON(), - type: 'callout', + type: 'media', version: 1, tag: this.__tag, attributes: this.__attributes, @@ -206,6 +206,25 @@ export function $createMediaNodeFromHtml(html: string): MediaNode | null { return domElementToNode(tag as MediaNodeTag, el); } +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 { + let nodeTag: MediaNodeTag = 'iframe'; + const srcEnd = src.split('?')[0].split('/').pop() || ''; + const extension = (srcEnd.split('.').pop() || '').toLowerCase(); + if (videoExtensions.includes(extension)) { + nodeTag = 'video'; + } else if (audioExtensions.includes(extension)) { + nodeTag = 'audio'; + } else if (extension && !iframeExtensions.includes(extension)) { + nodeTag = 'embed'; + } + + return new MediaNode(nodeTag); +} + export function $isMediaNode(node: LexicalNode | null | undefined) { return node instanceof MediaNode; } diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index cd36f359e..2125aa258 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -2,7 +2,8 @@ ## In progress -- Finish initial media node & form integration +- Update forms to allow panels (Media) + - Will be used for table forms also. ## Main Todo diff --git a/resources/js/wysiwyg/ui/defaults/form-definitions.ts b/resources/js/wysiwyg/ui/defaults/form-definitions.ts index e0459b5c5..a2242c338 100644 --- a/resources/js/wysiwyg/ui/defaults/form-definitions.ts +++ b/resources/js/wysiwyg/ui/defaults/form-definitions.ts @@ -1,15 +1,17 @@ import {EditorFormDefinition, EditorSelectFormFieldDefinition} from "../framework/forms"; import {EditorUiContext} from "../framework/core"; import {$createLinkNode} from "@lexical/link"; -import {$createTextNode, $getSelection} from "lexical"; +import {$createTextNode, $getSelection, LexicalNode} from "lexical"; import {$createImageNode} from "../../nodes/image"; import {setEditorContentFromHtml} from "../../actions"; -import {$createMediaNodeFromHtml} from "../../nodes/media"; +import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "../../nodes/media"; +import {$getNodeFromSelection} from "../../helpers"; +import {$insertNodeToNearestRoot} from "@lexical/utils"; export const link: EditorFormDefinition = { submitText: 'Apply', - action(formData, context: EditorUiContext) { + async action(formData, context: EditorUiContext) { context.editor.update(() => { const selection = $getSelection(); @@ -54,7 +56,7 @@ export const link: EditorFormDefinition = { export const image: EditorFormDefinition = { submitText: 'Apply', - action(formData, context: EditorUiContext) { + async action(formData, context: EditorUiContext) { context.editor.update(() => { const selection = $getSelection(); const imageNode = $createImageNode(formData.get('src')?.toString() || '', { @@ -92,25 +94,40 @@ export const image: EditorFormDefinition = { export const media: EditorFormDefinition = { submitText: 'Save', - action(formData, context: EditorUiContext) { - - // TODO - Get media from selection + async action(formData, context: EditorUiContext) { + const selectedNode: MediaNode|null = await (new Promise((res, rej) => { + context.editor.getEditorState().read(() => { + const node = $getNodeFromSelection($getSelection(), $isMediaNode); + res(node as MediaNode|null); + }); + })); const embedCode = (formData.get('embed') || '').toString().trim(); if (embedCode) { context.editor.update(() => { const node = $createMediaNodeFromHtml(embedCode); - // TODO - Replace existing or insert new + if (selectedNode && node) { + selectedNode.replace(node) + } else if (node) { + $insertNodeToNearestRoot(node); + } }); return true; } - const src = (formData.get('src') || '').toString().trim(); - const height = (formData.get('height') || '').toString().trim(); - const width = (formData.get('width') || '').toString().trim(); + context.editor.update(() => { + const src = (formData.get('src') || '').toString().trim(); + const height = (formData.get('height') || '').toString().trim(); + const width = (formData.get('width') || '').toString().trim(); - // TODO - Update existing or insert new + const updateNode = selectedNode || $createMediaNodeFromSrc(src); + updateNode.setSrc(src); + updateNode.setWidthAndHeight(width, height); + if (!selectedNode) { + $insertNodeToNearestRoot(updateNode); + } + }); return true; }, @@ -141,7 +158,7 @@ export const media: EditorFormDefinition = { export const source: EditorFormDefinition = { submitText: 'Save', - action(formData, context: EditorUiContext) { + async action(formData, context: EditorUiContext) { setEditorContentFromHtml(context.editor, formData.get('source')?.toString() || ''); return true; }, diff --git a/resources/js/wysiwyg/ui/framework/forms.ts b/resources/js/wysiwyg/ui/framework/forms.ts index 4fee787d3..b641f993b 100644 --- a/resources/js/wysiwyg/ui/framework/forms.ts +++ b/resources/js/wysiwyg/ui/framework/forms.ts @@ -14,7 +14,7 @@ export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefiniti export interface EditorFormDefinition { submitText: string; - action: (formData: FormData, context: EditorUiContext) => boolean; + action: (formData: FormData, context: EditorUiContext) => Promise; fields: EditorFormFieldDefinition[]; }