import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import {
BlockFromConfig,
createBlockConfig,
createBlockSpec,
} from "../../schema/index.js";
import { defaultProps, parseDefaultProps } from "../defaultProps.js";
import { parseFigureElement } from "../File/helpers/parse/parseFigureElement.js";
import { createFileBlockWrapper } from "../File/helpers/render/createFileBlockWrapper.js";
import { createFigureWithCaption } from "../File/helpers/toExternalHTML/createFigureWithCaption.js";
import { createLinkWithCaption } from "../File/helpers/toExternalHTML/createLinkWithCaption.js";
import { parseAudioElement } from "./parseAudioElement.js";
export const FILE_AUDIO_ICON_SVG =
'';
export interface AudioOptions {
icon?: string;
}
export type AudioBlockConfig = ReturnType;
export const createAudioBlockConfig = createBlockConfig(
(_ctx: AudioOptions) =>
({
type: "audio" as const,
propSchema: {
backgroundColor: defaultProps.backgroundColor,
// File name.
name: {
default: "" as const,
},
// File url.
url: {
default: "" as const,
},
// File caption.
caption: {
default: "" as const,
},
showPreview: {
default: true,
},
},
content: "none",
}) as const,
);
export const audioParse =
(_config: AudioOptions = {}) =>
(element: HTMLElement) => {
if (element.tagName === "AUDIO") {
// Ignore if parent figure has already been parsed.
if (element.closest("figure")) {
return undefined;
}
const { backgroundColor } = parseDefaultProps(element);
return {
...parseAudioElement(element as HTMLAudioElement),
backgroundColor,
};
}
if (element.tagName === "FIGURE") {
const parsedFigure = parseFigureElement(element, "audio");
if (!parsedFigure) {
return undefined;
}
const { targetElement, caption } = parsedFigure;
const { backgroundColor } = parseDefaultProps(element);
return {
...parseAudioElement(targetElement as HTMLAudioElement),
backgroundColor,
caption,
};
}
return undefined;
};
export const audioRender =
(config: AudioOptions = {}) =>
(
block: BlockFromConfig, any, any>,
editor: BlockNoteEditor<
Record<"audio", ReturnType>,
any,
any
>,
) => {
const icon = document.createElement("div");
icon.innerHTML = config.icon ?? FILE_AUDIO_ICON_SVG;
const audio = document.createElement("audio");
audio.className = "bn-audio";
if (editor.resolveFileUrl) {
editor.resolveFileUrl(block.props.url).then((downloadUrl) => {
audio.src = downloadUrl;
});
} else {
audio.src = block.props.url;
}
audio.controls = true;
audio.contentEditable = "false";
audio.draggable = false;
return createFileBlockWrapper(
block,
editor,
{ dom: audio },
icon.firstElementChild as HTMLElement,
);
};
export const audioToExternalHTML =
(_config: AudioOptions = {}) =>
(
block: BlockFromConfig, any, any>,
_editor: BlockNoteEditor<
Record<"audio", ReturnType>,
any,
any
>,
) => {
if (!block.props.url) {
return {
dom: document.createElement("audio"),
};
}
let audio;
if (block.props.showPreview) {
audio = document.createElement("audio");
audio.src = block.props.url;
} else {
audio = document.createElement("a");
audio.href = block.props.url;
audio.textContent = block.props.name || block.props.url;
}
if (block.props.caption) {
if (block.props.showPreview) {
return createFigureWithCaption(audio, block.props.caption);
} else {
return createLinkWithCaption(audio, block.props.caption);
}
}
return {
dom: audio,
};
};
export const createAudioBlockSpec = createBlockSpec(
createAudioBlockConfig,
(config) => ({
meta: {
fileBlockAccept: ["audio/*"],
},
parse: audioParse(config),
render: audioRender(config),
toExternalHTML: audioToExternalHTML(config),
runsBefore: ["file"],
}),
);