import { createBlockConfig, createBlockSpec } from "../../schema/index.js"; import { defaultProps, parseDefaultProps } from "../defaultProps.js"; import { parseFigureElement } from "../File/helpers/parse/parseFigureElement.js"; import { createResizableFileBlockWrapper } from "../File/helpers/render/createResizableFileBlockWrapper.js"; import { createFigureWithCaption } from "../File/helpers/toExternalHTML/createFigureWithCaption.js"; import { createLinkWithCaption } from "../File/helpers/toExternalHTML/createLinkWithCaption.js"; import { parseVideoElement } from "./parseVideoElement.js"; export const FILE_VIDEO_ICON_SVG = ''; export interface VideoOptions { icon?: string; } export type VideoBlockConfig = ReturnType; export const createVideoBlockConfig = createBlockConfig( (_ctx: VideoOptions) => ({ type: "video" as const, propSchema: { textAlignment: defaultProps.textAlignment, backgroundColor: defaultProps.backgroundColor, name: { default: "" as const }, url: { default: "" as const }, caption: { default: "" as const }, showPreview: { default: true }, previewWidth: { default: undefined, type: "number" as const }, }, content: "none" as const, }), ); export const videoParse = (_config: VideoOptions) => (element: HTMLElement) => { if (element.tagName === "VIDEO") { // Ignore if parent figure has already been parsed. if (element.closest("figure")) { return undefined; } const { backgroundColor } = parseDefaultProps(element); return { ...parseVideoElement(element as HTMLVideoElement), backgroundColor, }; } if (element.tagName === "FIGURE") { const parsedFigure = parseFigureElement(element, "video"); if (!parsedFigure) { return undefined; } const { targetElement, caption } = parsedFigure; const { backgroundColor } = parseDefaultProps(element); return { ...parseVideoElement(targetElement as HTMLVideoElement), backgroundColor, caption, }; } return undefined; }; export const createVideoBlockSpec = createBlockSpec( createVideoBlockConfig, (config) => ({ meta: { fileBlockAccept: ["video/*"], }, parse: videoParse(config), render(block, editor) { const icon = document.createElement("div"); icon.innerHTML = config.icon ?? FILE_VIDEO_ICON_SVG; const videoWrapper = document.createElement("div"); videoWrapper.className = "bn-visual-media-wrapper"; const video = document.createElement("video"); video.className = "bn-visual-media"; if (editor.resolveFileUrl) { editor.resolveFileUrl(block.props.url).then((downloadUrl) => { video.src = downloadUrl; }); } else { video.src = block.props.url; } video.controls = true; video.contentEditable = "false"; video.draggable = false; if (block.props.previewWidth) { video.width = block.props.previewWidth; } videoWrapper.appendChild(video); return createResizableFileBlockWrapper( block, editor, { dom: videoWrapper }, videoWrapper, icon.firstElementChild as HTMLElement, ); }, toExternalHTML(block) { if (!block.props.url) { return { dom: document.createElement("video"), }; } let video; if (block.props.showPreview) { video = document.createElement("video"); video.src = block.props.url; if (block.props.previewWidth) { video.width = block.props.previewWidth; } } else { video = document.createElement("a"); video.href = block.props.url; video.textContent = block.props.name || block.props.url; } if (block.props.caption) { if (block.props.showPreview) { return createFigureWithCaption(video, block.props.caption); } else { return createLinkWithCaption(video, block.props.caption); } } return { dom: video, }; }, runsBefore: ["file"], }), );