Skip to content

Commit

Permalink
Merge pull request #751 from TypeCellOS/file-blocks-separate-blocks
Browse files Browse the repository at this point in the history
File blocks separate blocks
  • Loading branch information
YousefED committed May 20, 2024
2 parents fda09b8 + d67ab4e commit 0289c9e
Show file tree
Hide file tree
Showing 51 changed files with 1,553 additions and 1,340 deletions.
13 changes: 8 additions & 5 deletions examples/01-basic/testing/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import {
uploadToTmpFilesDotOrg_DEV_ONLY,
} from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
createReactFileBlock,
defaultReactFileExtensions,
ReactAudioBlock,
ReactImageBlock,
ReactVideoBlock,
useCreateBlockNote,
} from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";

const schema = BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
file: createReactFileBlock(defaultReactFileExtensions),
image: ReactImageBlock,
video: ReactVideoBlock,
audio: ReactAudioBlock,
},
});

Expand Down
32 changes: 17 additions & 15 deletions packages/core/src/api/testUtil/cases/customBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ import { defaultProps } from "../../../blocks/defaultProps";
import { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
import { BlockNoteSchema } from "../../../editor/BlockNoteSchema";
import { createBlockSpec } from "../../../schema";
import { fileRender } from "../../../blocks/FileBlockContent/fileBlockImplementation";
import { filePropSchema } from "../../../blocks/FileBlockContent/fileBlockConfig";
import {
imagePropSchema,
imageRender,
} from "../../../blocks/ImageBlockContent/ImageBlockContent";

// This is a modified version of the default file block that does not implement
// This is a modified version of the default image block that does not implement
// a `toExternalHTML` function. It's used to test if the custom serializer by
// default serializes custom blocks using their `render` function.
const SimpleFile = createBlockSpec(
const SimpleImage = createBlockSpec(
{
type: "simpleFile",
propSchema: filePropSchema,
type: "simpleImage",
propSchema: imagePropSchema,
content: "none",
},
{
render: (block, editor) => fileRender(block as any, editor as any),
render: (block, editor) => imageRender(block as any, editor as any),
}
);

Expand Down Expand Up @@ -77,7 +79,7 @@ const SimpleCustomParagraph = createBlockSpec(
const schema = BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
simpleFile: SimpleFile,
simpleImage: SimpleImage,
customParagraph: CustomParagraph,
simpleCustomParagraph: SimpleCustomParagraph,
},
Expand All @@ -97,18 +99,18 @@ export const customBlocksTestCases: EditorTestCases<
},
documents: [
{
name: "simpleFile/button",
name: "simpleImage/button",
blocks: [
{
type: "simpleFile",
type: "simpleImage",
},
],
},
{
name: "simpleFile/basic",
name: "simpleImage/basic",
blocks: [
{
type: "simpleFile",
type: "simpleImage",
props: {
url: "exampleURL",
caption: "Caption",
Expand All @@ -118,18 +120,18 @@ export const customBlocksTestCases: EditorTestCases<
],
},
{
name: "simpleFile/nested",
name: "simpleImage/nested",
blocks: [
{
type: "simpleFile",
type: "simpleImage",
props: {
url: "exampleURL",
caption: "Caption",
previewWidth: 256,
},
children: [
{
type: "simpleFile",
type: "simpleImage",
props: {
url: "exampleURL",
caption: "Caption",
Expand Down
14 changes: 4 additions & 10 deletions packages/core/src/api/testUtil/cases/defaultSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,16 @@ export const defaultSchemaTestCases: EditorTestCases<
name: "image/button",
blocks: [
{
type: "file",
props: {
fileType: "image",
},
type: "image",
},
],
},
{
name: "image/basic",
blocks: [
{
type: "file",
type: "image",
props: {
fileType: "image",
url: "exampleURL",
caption: "Caption",
previewWidth: 256,
Expand All @@ -168,18 +164,16 @@ export const defaultSchemaTestCases: EditorTestCases<
name: "image/nested",
blocks: [
{
type: "file",
type: "image",
props: {
fileType: "image",
url: "exampleURL",
caption: "Caption",
previewWidth: 256,
},
children: [
{
type: "file",
type: "image",
props: {
fileType: "image",
url: "exampleURL",
caption: "Caption",
previewWidth: 256,
Expand Down
148 changes: 148 additions & 0 deletions packages/core/src/blocks/AudioBlockContent/AudioBlockContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import {
BlockFromConfig,
createBlockSpec,
FileBlockConfig,
Props,
PropSchema,
} from "../../schema";
import { defaultProps } from "../defaultProps";

import {
createAddFileButton,
createDefaultFilePreview,
createFigureWithCaption,
createFileAndCaptionWrapper,
parseFigureElement,
} from "../FileBlockContent/fileBlockHelpers";
import { parseAudioElement } from "./audioBlockHelpers";

export const audioPropSchema = {
backgroundColor: defaultProps.backgroundColor,
// File name.
name: {
default: "" as const,
},
// File url.
url: {
default: "" as const,
},
// File caption.
caption: {
default: "" as const,
},

showPreview: {
default: true,
},
} satisfies PropSchema;

export const audioBlockConfig = {
type: "audio" as const,
propSchema: audioPropSchema,
content: "none",
isFileBlock: true,
} satisfies FileBlockConfig;

export const audioRender = (
block: BlockFromConfig<typeof audioBlockConfig, any, any>,
editor: BlockNoteEditor<any, any, any>
) => {
const wrapper = document.createElement("div");
wrapper.className = "bn-file-block-content-wrapper";

if (block.props.url === "") {
const fileBlockAudioIcon = document.createElement("div");
fileBlockAudioIcon.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 16.0001H5.88889L11.1834 20.3319C11.2727 20.405 11.3846 20.4449 11.5 20.4449C11.7761 20.4449 12 20.2211 12 19.9449V4.05519C12 3.93977 11.9601 3.8279 11.887 3.73857C11.7121 3.52485 11.3971 3.49335 11.1834 3.66821L5.88889 8.00007H2C1.44772 8.00007 1 8.44778 1 9.00007V15.0001C1 15.5524 1.44772 16.0001 2 16.0001ZM23 12C23 15.292 21.5539 18.2463 19.2622 20.2622L17.8445 18.8444C19.7758 17.1937 21 14.7398 21 12C21 9.26016 19.7758 6.80629 17.8445 5.15557L19.2622 3.73779C21.5539 5.75368 23 8.70795 23 12ZM18 12C18 10.0883 17.106 8.38548 15.7133 7.28673L14.2842 8.71584C15.3213 9.43855 16 10.64 16 12C16 13.36 15.3213 14.5614 14.2842 15.2841L15.7133 16.7132C17.106 15.6145 18 13.9116 18 12Z"></path></svg>';
const addAudioButton = createAddFileButton(
block,
editor,
"Add audio",
fileBlockAudioIcon.firstElementChild as HTMLElement
);
wrapper.appendChild(addAudioButton.dom);

return {
dom: wrapper,
destroy: () => {
addAudioButton?.destroy?.();
},
};
} else if (!block.props.showPreview) {
const file = createDefaultFilePreview(block).dom;
const element = createFileAndCaptionWrapper(block, file);

return {
dom: element.dom,
};
} else {
const audio = document.createElement("audio");
audio.className = "bn-audio";
audio.src = block.props.url;
audio.controls = true;
audio.contentEditable = "false";
audio.draggable = false;

const element = createFileAndCaptionWrapper(block, audio);
wrapper.appendChild(element.dom);

return {
dom: wrapper,
};
}
};

export const audioParse = (
element: HTMLElement
): Partial<Props<typeof audioBlockConfig.propSchema>> | undefined => {
if (element.tagName === "AUDIO") {
return parseAudioElement(element as HTMLAudioElement);
}

if (element.tagName === "FIGURE") {
const parsedFigure = parseFigureElement(element, "audio");
if (!parsedFigure) {
return undefined;
}

const { targetElement, caption } = parsedFigure;

return {
...parseAudioElement(targetElement as HTMLAudioElement),
caption,
};
}

return undefined;
};

export const audioToExternalHTML = (
block: BlockFromConfig<typeof audioBlockConfig, any, any>
) => {
if (!block.props.url) {
const div = document.createElement("p");
div.innerHTML = "Add audio";

return {
dom: div,
};
}

const audio = document.createElement("audio");
audio.src = block.props.url;

if (block.props.caption) {
return createFigureWithCaption(audio, block.props.caption);
}

return {
dom: audio,
};
};

export const AudioBlock = createBlockSpec(audioBlockConfig, {
render: audioRender,
parse: audioParse,
toExternalHTML: audioToExternalHTML,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const parseAudioElement = (audioElement: HTMLAudioElement) => {
const url = audioElement.src || undefined;

return { url };
};
Loading

0 comments on commit 0289c9e

Please sign in to comment.