We have the following extension for Tiptap that handles a video node:
import { mergeAttributes, Node, nodeInputRule } from "@tiptap/core";
export interface VideoOptions {
url: string;
width: number;
height: number;
HTMLAttributes: Record<string, any>;
}
declare module "@tiptap/core" {
interface Commands<ReturnType> {
video: {
/**
* Set a video node
* @param options.updateSelection set to true will select the newly inserted content
*/
setVideo: (
id: string,
src: string,
width?: number,
height?: number,
controls?: boolean,
controlslist?: string,
options?: { updateSelection: boolean }
) => ReturnType;
/**
* Toggle a video
*/
toggleVideo: (src: string) => ReturnType;
};
}
}
const VIDEO_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/;
export const Video = Node.create({
name: "video",
group: "block",
draggable: true,
selectable: true,
addAttributes() {
return {
src: {
default: null,
parseHTML: (el: any) => (el as HTMLSpanElement).getAttribute("src"),
renderHTML: (attrs: any) => ({ src: attrs.src }),
},
controls: {
default: true,
renderHTML: (attrs: any) => ({ controls: attrs.controls }),
parseHTML: (el: any) => {
if ((el as HTMLSpanElement).getAttribute("controls")) {
return (el as HTMLSpanElement).getAttribute("controls");
} else if ((el as HTMLSpanElement).hasAttribute("controls")) {
return true;
} else {
return false;
}
},
},
controlslist: {
default: "",
renderHTML: (attributes: any) => {
return { controlslist: attributes.controlslist };
},
parseHTML: (element: any) => element.getAttribute("controlslist"),
},
documentId: {
default: "",
renderHTML: (attributes: any) => {
return { "data-document-id": attributes.documentId };
},
parseHTML: (element: any) => element.getAttribute("data-document-id"),
},
videoResolution: {
default: "404x720",
renderHTML: (attributes: any) => {
return { "data-video-resolution": attributes.videoResolution };
},
parseHTML: (element: any) =>
element.getAttribute("data-video-resolution"),
},
width: {
renderHTML: (attributes: any) => {
return {
width: parseInt(attributes.width),
};
},
parseHTML: (element) => element.getAttribute("width"),
},
height: {
renderHTML: (attributes: any) => {
return {
height: parseInt(attributes.height),
};
},
parseHTML: (element) => element.getAttribute("height"),
},
};
},
parseHTML() {
return [
{
tag: "div.video-wrapper>video,video",
getAttrs: (el: any) => ({
src: (el as HTMLVideoElement).getAttribute("src"),
}),
},
];
},
renderHTML({ HTMLAttributes }) {
return [
"div",
{ class: "video-wrapper" },
["video", mergeAttributes(HTMLAttributes)],
];
},
addCommands() {
return {
setVideo:
(
id,
src,
width = 640,
height = 480,
controls = true,
controlslist = "nodownload",
options
) =>
({ commands, state }) => {
return commands.insertContentAt(
state.selection,
`<video
controls="${controls}"
controlslist="${controlslist}"
src="${src}"
width="${width}"
height="${height}"
data-document-id="${id}"
data-video-resolution="${width}x${height}" />`,
options
);
},
toggleVideo:
() =>
({ commands }) =>
commands.toggleNode(this.name, "paragraph"),
};
},
addInputRules() {
return [
nodeInputRule({
find: VIDEO_INPUT_REGEX,
type: this.type,
getAttributes: (match) => {
const [, , src] = match;
return { src };
},
}),
];
},
});
We want to insert a new video with the give entity:
if (asset.mime.includes("video")) {
const { id, src, width, height } = getUpdatedVideo(asset);
if (!forceInsert)
editor.chain().focus().setVideo(String(id), src, width, height);
else editor.commands.setVideo(String(id), src, width, height);
}
The setCommand
is executed, we checked it by congole.log
, but the node its never inserted on the document, thus, the document is never updated.
We added an audio extension with a similar structure, and we execute it in the same way,
Why is it failing?
We were calling the function, so we always set the log, but we were not running it:
editor
.chain()
.focus()
.setVideo(String(id), src, width, height)
.run();
The run()
is the instruction that executes the action.