Skip to content

Commit 0289c9e

Browse files
authored
Merge pull request #751 from TypeCellOS/file-blocks-separate-blocks
File blocks separate blocks
2 parents fda09b8 + d67ab4e commit 0289c9e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1553
-1340
lines changed

examples/01-basic/testing/App.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ import {
44
uploadToTmpFilesDotOrg_DEV_ONLY,
55
} from "@blocknote/core";
66
import "@blocknote/core/fonts/inter.css";
7+
import { BlockNoteView } from "@blocknote/mantine";
8+
import "@blocknote/mantine/style.css";
79
import {
8-
createReactFileBlock,
9-
defaultReactFileExtensions,
10+
ReactAudioBlock,
11+
ReactImageBlock,
12+
ReactVideoBlock,
1013
useCreateBlockNote,
1114
} from "@blocknote/react";
12-
import { BlockNoteView } from "@blocknote/mantine";
13-
import "@blocknote/mantine/style.css";
1415

1516
const schema = BlockNoteSchema.create({
1617
blockSpecs: {
1718
...defaultBlockSpecs,
18-
file: createReactFileBlock(defaultReactFileExtensions),
19+
image: ReactImageBlock,
20+
video: ReactVideoBlock,
21+
audio: ReactAudioBlock,
1922
},
2023
});
2124

packages/core/src/api/testUtil/cases/customBlocks.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@ import { defaultProps } from "../../../blocks/defaultProps";
1010
import { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
1111
import { BlockNoteSchema } from "../../../editor/BlockNoteSchema";
1212
import { createBlockSpec } from "../../../schema";
13-
import { fileRender } from "../../../blocks/FileBlockContent/fileBlockImplementation";
14-
import { filePropSchema } from "../../../blocks/FileBlockContent/fileBlockConfig";
13+
import {
14+
imagePropSchema,
15+
imageRender,
16+
} from "../../../blocks/ImageBlockContent/ImageBlockContent";
1517

16-
// This is a modified version of the default file block that does not implement
18+
// This is a modified version of the default image block that does not implement
1719
// a `toExternalHTML` function. It's used to test if the custom serializer by
1820
// default serializes custom blocks using their `render` function.
19-
const SimpleFile = createBlockSpec(
21+
const SimpleImage = createBlockSpec(
2022
{
21-
type: "simpleFile",
22-
propSchema: filePropSchema,
23+
type: "simpleImage",
24+
propSchema: imagePropSchema,
2325
content: "none",
2426
},
2527
{
26-
render: (block, editor) => fileRender(block as any, editor as any),
28+
render: (block, editor) => imageRender(block as any, editor as any),
2729
}
2830
);
2931

@@ -77,7 +79,7 @@ const SimpleCustomParagraph = createBlockSpec(
7779
const schema = BlockNoteSchema.create({
7880
blockSpecs: {
7981
...defaultBlockSpecs,
80-
simpleFile: SimpleFile,
82+
simpleImage: SimpleImage,
8183
customParagraph: CustomParagraph,
8284
simpleCustomParagraph: SimpleCustomParagraph,
8385
},
@@ -97,18 +99,18 @@ export const customBlocksTestCases: EditorTestCases<
9799
},
98100
documents: [
99101
{
100-
name: "simpleFile/button",
102+
name: "simpleImage/button",
101103
blocks: [
102104
{
103-
type: "simpleFile",
105+
type: "simpleImage",
104106
},
105107
],
106108
},
107109
{
108-
name: "simpleFile/basic",
110+
name: "simpleImage/basic",
109111
blocks: [
110112
{
111-
type: "simpleFile",
113+
type: "simpleImage",
112114
props: {
113115
url: "exampleURL",
114116
caption: "Caption",
@@ -118,18 +120,18 @@ export const customBlocksTestCases: EditorTestCases<
118120
],
119121
},
120122
{
121-
name: "simpleFile/nested",
123+
name: "simpleImage/nested",
122124
blocks: [
123125
{
124-
type: "simpleFile",
126+
type: "simpleImage",
125127
props: {
126128
url: "exampleURL",
127129
caption: "Caption",
128130
previewWidth: 256,
129131
},
130132
children: [
131133
{
132-
type: "simpleFile",
134+
type: "simpleImage",
133135
props: {
134136
url: "exampleURL",
135137
caption: "Caption",

packages/core/src/api/testUtil/cases/defaultSchema.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,16 @@ export const defaultSchemaTestCases: EditorTestCases<
143143
name: "image/button",
144144
blocks: [
145145
{
146-
type: "file",
147-
props: {
148-
fileType: "image",
149-
},
146+
type: "image",
150147
},
151148
],
152149
},
153150
{
154151
name: "image/basic",
155152
blocks: [
156153
{
157-
type: "file",
154+
type: "image",
158155
props: {
159-
fileType: "image",
160156
url: "exampleURL",
161157
caption: "Caption",
162158
previewWidth: 256,
@@ -168,18 +164,16 @@ export const defaultSchemaTestCases: EditorTestCases<
168164
name: "image/nested",
169165
blocks: [
170166
{
171-
type: "file",
167+
type: "image",
172168
props: {
173-
fileType: "image",
174169
url: "exampleURL",
175170
caption: "Caption",
176171
previewWidth: 256,
177172
},
178173
children: [
179174
{
180-
type: "file",
175+
type: "image",
181176
props: {
182-
fileType: "image",
183177
url: "exampleURL",
184178
caption: "Caption",
185179
previewWidth: 256,
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
2+
import {
3+
BlockFromConfig,
4+
createBlockSpec,
5+
FileBlockConfig,
6+
Props,
7+
PropSchema,
8+
} from "../../schema";
9+
import { defaultProps } from "../defaultProps";
10+
11+
import {
12+
createAddFileButton,
13+
createDefaultFilePreview,
14+
createFigureWithCaption,
15+
createFileAndCaptionWrapper,
16+
parseFigureElement,
17+
} from "../FileBlockContent/fileBlockHelpers";
18+
import { parseAudioElement } from "./audioBlockHelpers";
19+
20+
export const audioPropSchema = {
21+
backgroundColor: defaultProps.backgroundColor,
22+
// File name.
23+
name: {
24+
default: "" as const,
25+
},
26+
// File url.
27+
url: {
28+
default: "" as const,
29+
},
30+
// File caption.
31+
caption: {
32+
default: "" as const,
33+
},
34+
35+
showPreview: {
36+
default: true,
37+
},
38+
} satisfies PropSchema;
39+
40+
export const audioBlockConfig = {
41+
type: "audio" as const,
42+
propSchema: audioPropSchema,
43+
content: "none",
44+
isFileBlock: true,
45+
} satisfies FileBlockConfig;
46+
47+
export const audioRender = (
48+
block: BlockFromConfig<typeof audioBlockConfig, any, any>,
49+
editor: BlockNoteEditor<any, any, any>
50+
) => {
51+
const wrapper = document.createElement("div");
52+
wrapper.className = "bn-file-block-content-wrapper";
53+
54+
if (block.props.url === "") {
55+
const fileBlockAudioIcon = document.createElement("div");
56+
fileBlockAudioIcon.innerHTML =
57+
'<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>';
58+
const addAudioButton = createAddFileButton(
59+
block,
60+
editor,
61+
"Add audio",
62+
fileBlockAudioIcon.firstElementChild as HTMLElement
63+
);
64+
wrapper.appendChild(addAudioButton.dom);
65+
66+
return {
67+
dom: wrapper,
68+
destroy: () => {
69+
addAudioButton?.destroy?.();
70+
},
71+
};
72+
} else if (!block.props.showPreview) {
73+
const file = createDefaultFilePreview(block).dom;
74+
const element = createFileAndCaptionWrapper(block, file);
75+
76+
return {
77+
dom: element.dom,
78+
};
79+
} else {
80+
const audio = document.createElement("audio");
81+
audio.className = "bn-audio";
82+
audio.src = block.props.url;
83+
audio.controls = true;
84+
audio.contentEditable = "false";
85+
audio.draggable = false;
86+
87+
const element = createFileAndCaptionWrapper(block, audio);
88+
wrapper.appendChild(element.dom);
89+
90+
return {
91+
dom: wrapper,
92+
};
93+
}
94+
};
95+
96+
export const audioParse = (
97+
element: HTMLElement
98+
): Partial<Props<typeof audioBlockConfig.propSchema>> | undefined => {
99+
if (element.tagName === "AUDIO") {
100+
return parseAudioElement(element as HTMLAudioElement);
101+
}
102+
103+
if (element.tagName === "FIGURE") {
104+
const parsedFigure = parseFigureElement(element, "audio");
105+
if (!parsedFigure) {
106+
return undefined;
107+
}
108+
109+
const { targetElement, caption } = parsedFigure;
110+
111+
return {
112+
...parseAudioElement(targetElement as HTMLAudioElement),
113+
caption,
114+
};
115+
}
116+
117+
return undefined;
118+
};
119+
120+
export const audioToExternalHTML = (
121+
block: BlockFromConfig<typeof audioBlockConfig, any, any>
122+
) => {
123+
if (!block.props.url) {
124+
const div = document.createElement("p");
125+
div.innerHTML = "Add audio";
126+
127+
return {
128+
dom: div,
129+
};
130+
}
131+
132+
const audio = document.createElement("audio");
133+
audio.src = block.props.url;
134+
135+
if (block.props.caption) {
136+
return createFigureWithCaption(audio, block.props.caption);
137+
}
138+
139+
return {
140+
dom: audio,
141+
};
142+
};
143+
144+
export const AudioBlock = createBlockSpec(audioBlockConfig, {
145+
render: audioRender,
146+
parse: audioParse,
147+
toExternalHTML: audioToExternalHTML,
148+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const parseAudioElement = (audioElement: HTMLAudioElement) => {
2+
const url = audioElement.src || undefined;
3+
4+
return { url };
5+
};

0 commit comments

Comments
 (0)