Lexical: Got media node core work & form done

This commit is contained in:
Dan Brown 2024-07-27 17:25:30 +01:00
parent f284d31861
commit c8f6b7e0d6
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
4 changed files with 54 additions and 17 deletions

View File

@ -30,7 +30,7 @@ const attributeAllowList = [
function filterAttributes(attributes: Record<string, string>): Record<string, string> {
const filtered: Record<string, string> = {};
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;
}

View File

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

View File

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

View File

@ -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<boolean>;
fields: EditorFormFieldDefinition[];
}