@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
160 lines (139 loc) • 4.55 kB
text/typescript
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
BlockFromConfig,
createBlockSpec,
FileBlockConfig,
Props,
PropSchema,
} from "../../schema/index.js";
import { defaultProps } from "../defaultProps.js";
import { parseFigureElement } from "../FileBlockContent/helpers/parse/parseFigureElement.js";
import { createFigureWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createFigureWithCaption.js";
import { createLinkWithCaption } from "../FileBlockContent/helpers/toExternalHTML/createLinkWithCaption.js";
import { createResizableFileBlockWrapper } from "../FileBlockContent/helpers/render/createResizableFileBlockWrapper.js";
import { parseVideoElement } from "./parseVideoElement.js";
export const FILE_VIDEO_ICON_SVG =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 3.9934C2 3.44476 2.45531 3 2.9918 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934ZM8 5V19H16V5H8ZM4 5V7H6V5H4ZM18 5V7H20V5H18ZM4 9V11H6V9H4ZM18 9V11H20V9H18ZM4 13V15H6V13H4ZM18 13V15H20V13H18ZM4 17V19H6V17H4ZM18 17V19H20V17H18Z"></path></svg>';
export const videoPropSchema = {
textAlignment: defaultProps.textAlignment,
backgroundColor: defaultProps.backgroundColor,
// File name.
name: {
default: "" as const,
},
// File url.
url: {
default: "" as const,
},
// File caption.
caption: {
default: "" as const,
},
showPreview: {
default: true,
},
// File preview width in px.
previewWidth: {
default: undefined,
type: "number",
},
} satisfies PropSchema;
export const videoBlockConfig = {
type: "video" as const,
propSchema: videoPropSchema,
content: "none",
isFileBlock: true,
fileBlockAccept: ["video/*"],
} satisfies FileBlockConfig;
export const videoRender = (
block: BlockFromConfig<typeof videoBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>,
) => {
const icon = document.createElement("div");
icon.innerHTML = 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;
video.width = block.props.previewWidth;
videoWrapper.appendChild(video);
return createResizableFileBlockWrapper(
block,
editor,
{ dom: videoWrapper },
videoWrapper,
editor.dictionary.file_blocks.video.add_button_text,
icon.firstElementChild as HTMLElement,
);
};
export const videoParse = (
element: HTMLElement,
): Partial<Props<typeof videoBlockConfig.propSchema>> | undefined => {
if (element.tagName === "VIDEO") {
// Ignore if parent figure has already been parsed.
if (element.closest("figure")) {
return undefined;
}
return parseVideoElement(element as HTMLVideoElement);
}
if (element.tagName === "FIGURE") {
const parsedFigure = parseFigureElement(element, "video");
if (!parsedFigure) {
return undefined;
}
const { targetElement, caption } = parsedFigure;
return {
...parseVideoElement(targetElement as HTMLVideoElement),
caption,
};
}
return undefined;
};
export const videoToExternalHTML = (
block: BlockFromConfig<typeof videoBlockConfig, any, any>,
) => {
if (!block.props.url) {
const div = document.createElement("p");
div.textContent = "Add video";
return {
dom: div,
};
}
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,
};
};
export const VideoBlock = createBlockSpec(videoBlockConfig, {
render: videoRender,
parse: videoParse,
toExternalHTML: videoToExternalHTML,
});