From: Dan Brown Date: Sun, 15 Jun 2025 19:00:28 +0000 (+0100) Subject: Lexical: Media form improvements X-Git-Tag: v25.05.1~1^2~4^2 X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/refs/pull/5653/head?ds=sidebyside Lexical: Media form improvements - Allowed re-editing of existing embed HTML code. - Handled "src" form field when video is using child source tags. --- diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts index 4f4b1d82a..a7acc4ad3 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts @@ -14,7 +14,6 @@ import { setCommonBlockPropsFromElement, updateElementWithCommonBlockProps } from "lexical/nodes/common"; -import {$selectSingleNode} from "../../utils/selection"; import {SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; export type MediaNodeTag = 'iframe' | 'embed' | 'object' | 'video' | 'audio'; @@ -141,16 +140,26 @@ export class MediaNode extends ElementNode { getSources(): MediaNodeSource[] { const self = this.getLatest(); - return self.__sources; + return self.__sources.map(s => Object.assign({}, s)) } setSrc(src: string): void { const attrs = this.getAttributes(); + const sources = this.getSources(); + if (this.__tag ==='object') { attrs.data = src; + } if (this.__tag === 'video' && sources.length > 0) { + sources[0].src = src; + delete attrs.src; + if (sources.length > 1) { + sources.splice(1, sources.length - 1); + } + this.setSources(sources); } else { attrs.src = src; } + this.setAttributes(attrs); } diff --git a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts index c55ae669e..b142e95a0 100644 --- a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts +++ b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts @@ -28,4 +28,19 @@ describe('LexicalMediaNode', () => { }); }); + test('setSrc on video uses sources if existing', () => { + const {editor} = createTestContext(); + editor.updateAndCommit(() => { + const mediaMode = $createMediaNode('video'); + mediaMode.setAttributes({src: 'z'}); + mediaMode.setSources([{src: 'a', type: 'video'}, {src: 'b', type: 'video'}]); + + mediaMode.setSrc('c'); + + expect(mediaMode.getAttributes().src).toBeUndefined(); + expect(mediaMode.getSources()).toHaveLength(1); + expect(mediaMode.getSources()[0].src).toBe('c'); + }); + }); + }); \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/defaults/forms/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts index 0effdc171..cdf464cb4 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/objects.ts @@ -192,11 +192,17 @@ export function $showMediaForm(media: MediaNode|null, context: EditorUiContext): let formDefaults = {}; if (media) { const nodeAttrs = media.getAttributes(); + const nodeDOM = media.exportDOM(context.editor).element; + const nodeHtml = (nodeDOM instanceof HTMLElement) ? nodeDOM.outerHTML : ''; + formDefaults = { - src: nodeAttrs.src || nodeAttrs.data || '', + src: nodeAttrs.src || nodeAttrs.data || media.getSources()[0]?.src || '', width: nodeAttrs.width, height: nodeAttrs.height, - embed: '', + embed: nodeHtml, + + // This is used so we can check for edits against the embed field on submit + embed_check: nodeHtml, } } @@ -214,7 +220,8 @@ export const media: EditorFormDefinition = { })); const embedCode = (formData.get('embed') || '').toString().trim(); - if (embedCode) { + const embedCheck = (formData.get('embed_check') || '').toString().trim(); + if (embedCode && embedCode !== embedCheck) { context.editor.update(() => { const node = $createMediaNodeFromHtml(embedCode); if (selectedNode && node) { @@ -236,6 +243,7 @@ export const media: EditorFormDefinition = { if (selectedNode) { selectedNode.setSrc(src); selectedNode.setWidthAndHeight(width, height); + context.manager.triggerFutureStateRefresh(); return; } @@ -281,6 +289,11 @@ export const media: EditorFormDefinition = { name: 'embed', type: 'textarea', }, + { + label: '', + name: 'embed_check', + type: 'hidden', + }, ], } ]) diff --git a/resources/js/wysiwyg/ui/framework/forms.ts b/resources/js/wysiwyg/ui/framework/forms.ts index 08edb214e..b12d9f692 100644 --- a/resources/js/wysiwyg/ui/framework/forms.ts +++ b/resources/js/wysiwyg/ui/framework/forms.ts @@ -11,7 +11,7 @@ import {el} from "../../utils/dom"; export interface EditorFormFieldDefinition { label: string; name: string; - type: 'text' | 'select' | 'textarea' | 'checkbox'; + type: 'text' | 'select' | 'textarea' | 'checkbox' | 'hidden'; } export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition { @@ -67,6 +67,9 @@ export class EditorFormField extends EditorUiElement { input = el('textarea', {id, name: this.definition.name, class: 'editor-form-field-input'}); } else if (this.definition.type === 'checkbox') { input = el('input', {id, name: this.definition.name, type: 'checkbox', class: 'editor-form-field-input-checkbox', value: 'true'}); + } else if (this.definition.type === 'hidden') { + input = el('input', {id, name: this.definition.name, type: 'hidden'}); + return el('div', {hidden: 'true'}, [input]); } else { input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'}); }