@blocknote/core 0.13.2 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/dist/blocknote.js +5282 -2785
  2. package/dist/blocknote.js.map +1 -1
  3. package/dist/blocknote.umd.cjs +7 -7
  4. package/dist/blocknote.umd.cjs.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/dist/webpack-stats.json +1 -1
  7. package/package.json +2 -2
  8. package/src/api/exporters/html/__snapshots__/file/basic/external.html +1 -0
  9. package/src/api/exporters/html/__snapshots__/file/basic/internal.html +1 -0
  10. package/src/api/exporters/html/__snapshots__/file/button/external.html +1 -0
  11. package/src/api/exporters/html/__snapshots__/file/button/internal.html +1 -0
  12. package/src/api/exporters/html/__snapshots__/file/nested/external.html +1 -0
  13. package/src/api/exporters/html/__snapshots__/file/nested/internal.html +1 -0
  14. package/src/api/exporters/html/__snapshots__/file/noCaption/external.html +1 -0
  15. package/src/api/exporters/html/__snapshots__/file/noCaption/internal.html +1 -0
  16. package/src/api/exporters/html/__snapshots__/file/noName/external.html +1 -0
  17. package/src/api/exporters/html/__snapshots__/file/noName/internal.html +1 -0
  18. package/src/api/exporters/html/__snapshots__/image/basic/external.html +1 -1
  19. package/src/api/exporters/html/__snapshots__/image/basic/internal.html +1 -1
  20. package/src/api/exporters/html/__snapshots__/image/button/external.html +1 -1
  21. package/src/api/exporters/html/__snapshots__/image/button/internal.html +1 -1
  22. package/src/api/exporters/html/__snapshots__/image/nested/external.html +1 -1
  23. package/src/api/exporters/html/__snapshots__/image/nested/internal.html +1 -1
  24. package/src/api/exporters/html/__snapshots__/image/noCaption/external.html +1 -0
  25. package/src/api/exporters/html/__snapshots__/image/noCaption/internal.html +1 -0
  26. package/src/api/exporters/html/__snapshots__/image/noName/external.html +1 -0
  27. package/src/api/exporters/html/__snapshots__/image/noName/internal.html +1 -0
  28. package/src/api/exporters/html/__snapshots__/image/noPreview/external.html +1 -0
  29. package/src/api/exporters/html/__snapshots__/image/noPreview/internal.html +1 -0
  30. package/src/api/exporters/html/__snapshots__/simpleFile/basic/external.html +1 -0
  31. package/src/api/exporters/html/__snapshots__/simpleFile/basic/internal.html +1 -0
  32. package/src/api/exporters/html/__snapshots__/simpleFile/button/external.html +1 -0
  33. package/src/api/exporters/html/__snapshots__/simpleFile/button/internal.html +1 -0
  34. package/src/api/exporters/html/__snapshots__/simpleFile/nested/external.html +1 -0
  35. package/src/api/exporters/html/__snapshots__/simpleFile/nested/internal.html +1 -0
  36. package/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html +1 -1
  37. package/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html +1 -1
  38. package/src/api/exporters/html/__snapshots__/simpleImage/button/external.html +1 -1
  39. package/src/api/exporters/html/__snapshots__/simpleImage/button/internal.html +1 -1
  40. package/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html +1 -1
  41. package/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html +1 -1
  42. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html +1 -0
  43. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/internal.html +1 -0
  44. package/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html +1 -0
  45. package/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html +1 -0
  46. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html +1 -0
  47. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html +1 -0
  48. package/src/api/exporters/markdown/__snapshots__/file/basic/markdown.md +3 -0
  49. package/src/api/exporters/markdown/__snapshots__/file/button/markdown.md +1 -0
  50. package/src/api/exporters/markdown/__snapshots__/file/nested/markdown.md +7 -0
  51. package/src/api/exporters/markdown/__snapshots__/file/noCaption/markdown.md +1 -0
  52. package/src/api/exporters/markdown/__snapshots__/file/noName/markdown.md +3 -0
  53. package/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md +1 -1
  54. package/src/api/exporters/markdown/__snapshots__/image/button/markdown.md +1 -1
  55. package/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md +2 -2
  56. package/src/api/exporters/markdown/__snapshots__/image/noCaption/markdown.md +1 -0
  57. package/src/api/exporters/markdown/__snapshots__/image/noName/markdown.md +3 -0
  58. package/src/api/exporters/markdown/__snapshots__/image/noPreview/markdown.md +3 -0
  59. package/src/api/exporters/markdown/__snapshots__/simpleFile/basic/markdown.md +3 -0
  60. package/src/api/exporters/markdown/__snapshots__/simpleFile/button/markdown.md +1 -0
  61. package/src/api/exporters/markdown/__snapshots__/simpleFile/nested/markdown.md +7 -0
  62. package/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md +3 -1
  63. package/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md +1 -0
  64. package/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md +6 -2
  65. package/src/api/exporters/markdown/__snapshots__/simpleImage/noCaption/markdown.md +1 -0
  66. package/src/api/exporters/markdown/__snapshots__/simpleImage/noName/markdown.md +3 -0
  67. package/src/api/exporters/markdown/__snapshots__/simpleImage/noPreview/markdown.md +3 -0
  68. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +212 -4
  69. package/src/api/parsers/html/__snapshots__/paste/parse-basic-block-types.json +3 -1
  70. package/src/api/parsers/html/__snapshots__/paste/parse-fake-image-caption.json +3 -1
  71. package/src/api/testUtil/cases/customBlocks.ts +79 -33
  72. package/src/api/testUtil/cases/customInlineContent.ts +1 -1
  73. package/src/api/testUtil/cases/customStyles.ts +1 -1
  74. package/src/api/testUtil/cases/defaultSchema.ts +114 -4
  75. package/src/blocks/AudioBlockContent/AudioBlockContent.ts +162 -0
  76. package/src/blocks/AudioBlockContent/audioBlockHelpers.ts +5 -0
  77. package/src/blocks/FileBlockContent/FileBlockContent.ts +121 -0
  78. package/src/blocks/FileBlockContent/fileBlockHelpers.ts +377 -0
  79. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +135 -356
  80. package/src/blocks/ImageBlockContent/imageBlockHelpers.ts +6 -0
  81. package/src/blocks/VideoBlockContent/VideoBlockContent.ts +182 -0
  82. package/src/blocks/VideoBlockContent/videoBlockHelpers.ts +6 -0
  83. package/src/blocks/defaultBlockTypeGuards.ts +53 -1
  84. package/src/blocks/defaultBlocks.ts +8 -2
  85. package/src/editor/Block.css +67 -27
  86. package/src/editor/BlockNoteEditor.ts +14 -10
  87. package/src/editor/BlockNoteSchema.ts +12 -3
  88. package/src/extensions/{ImagePanel/ImageToolbarPlugin.ts → FilePanel/FilePanelPlugin.ts} +22 -25
  89. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +14 -2
  90. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +59 -2
  91. package/src/i18n/locales/en.ts +102 -11
  92. package/src/i18n/locales/fr.ts +104 -11
  93. package/src/i18n/locales/index.ts +8 -2
  94. package/src/i18n/locales/is.ts +288 -0
  95. package/src/i18n/locales/ja.ts +300 -0
  96. package/src/i18n/locales/ko.ts +292 -0
  97. package/src/i18n/locales/nl.ts +101 -8
  98. package/src/i18n/locales/pl.ts +280 -0
  99. package/src/i18n/locales/pt.ts +281 -0
  100. package/src/i18n/locales/vi.ts +281 -0
  101. package/src/i18n/locales/zh.ts +107 -8
  102. package/src/index.ts +9 -2
  103. package/src/pm-nodes/BlockContainer.ts +2 -2
  104. package/src/schema/blocks/createSpec.ts +1 -0
  105. package/src/schema/blocks/internal.ts +10 -0
  106. package/src/schema/blocks/types.ts +41 -5
  107. package/src/util/string.ts +12 -0
  108. package/types/src/api/testUtil/cases/customBlocks.d.ts +228 -42
  109. package/types/src/api/testUtil/cases/customInlineContent.d.ts +178 -4
  110. package/types/src/api/testUtil/cases/customStyles.d.ts +178 -4
  111. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +104 -0
  112. package/types/src/blocks/AudioBlockContent/audioBlockHelpers.d.ts +3 -0
  113. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +96 -0
  114. package/types/src/blocks/FileBlockContent/fileBlockHelpers.d.ts +30 -0
  115. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +53 -14
  116. package/types/src/blocks/ImageBlockContent/imageBlockHelpers.d.ts +4 -0
  117. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +132 -0
  118. package/types/src/blocks/VideoBlockContent/videoBlockHelpers.d.ts +4 -0
  119. package/types/src/blocks/defaultBlockTypeGuards.d.ts +6 -1
  120. package/types/src/blocks/defaultBlocks.d.ts +356 -8
  121. package/types/src/editor/BlockNoteEditor.d.ts +5 -5
  122. package/types/src/extensions/{ImagePanel/ImageToolbarPlugin.d.ts → FilePanel/FilePanelPlugin.d.ts} +9 -12
  123. package/types/src/i18n/locales/en.d.ts +49 -7
  124. package/types/src/i18n/locales/fr.d.ts +2 -184
  125. package/types/src/i18n/locales/index.d.ts +7 -1
  126. package/types/src/i18n/locales/is.d.ts +2 -0
  127. package/types/src/i18n/locales/ja.d.ts +2 -0
  128. package/types/src/i18n/locales/ko.d.ts +2 -0
  129. package/types/src/i18n/locales/pl.d.ts +2 -0
  130. package/types/src/i18n/locales/pt.d.ts +2 -0
  131. package/types/src/i18n/locales/vi.d.ts +2 -0
  132. package/types/src/index.d.ts +8 -2
  133. package/types/src/schema/blocks/internal.d.ts +1 -1
  134. package/types/src/schema/blocks/types.d.ts +26 -1
  135. package/types/src/util/string.d.ts +1 -0
  136. /package/src/blocks/{ImageBlockContent → FileBlockContent}/uploadToTmpFilesDotOrg_DEV_ONLY.ts +0 -0
  137. /package/types/src/blocks/{ImageBlockContent → FileBlockContent}/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +0 -0
@@ -0,0 +1,182 @@
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
+ createLinkWithCaption,
17
+ createResizeHandlesWrapper,
18
+ parseFigureElement,
19
+ } from "../FileBlockContent/fileBlockHelpers";
20
+ import { parseVideoElement } from "./videoBlockHelpers";
21
+
22
+ export const videoPropSchema = {
23
+ textAlignment: defaultProps.textAlignment,
24
+ backgroundColor: defaultProps.backgroundColor,
25
+ // File name.
26
+ name: {
27
+ default: "" as const,
28
+ },
29
+ // File url.
30
+ url: {
31
+ default: "" as const,
32
+ },
33
+ // File caption.
34
+ caption: {
35
+ default: "" as const,
36
+ },
37
+
38
+ showPreview: {
39
+ default: true,
40
+ },
41
+ // File preview width in px.
42
+ previewWidth: {
43
+ default: 512,
44
+ },
45
+ } satisfies PropSchema;
46
+
47
+ export const videoBlockConfig = {
48
+ type: "video" as const,
49
+ propSchema: videoPropSchema,
50
+ content: "none",
51
+ isFileBlock: true,
52
+ isFileBlockPlaceholder: (block: any) => !block.props.url,
53
+ fileBlockAcceptMimeTypes: ["video/*"],
54
+ } satisfies FileBlockConfig;
55
+
56
+ export const videoRender = (
57
+ block: BlockFromConfig<typeof videoBlockConfig, any, any>,
58
+ editor: BlockNoteEditor<any, any, any>
59
+ ) => {
60
+ const wrapper = document.createElement("div");
61
+ wrapper.className = "bn-file-block-content-wrapper";
62
+
63
+ if (block.props.url === "") {
64
+ const fileBlockVideoIcon = document.createElement("div");
65
+ fileBlockVideoIcon.innerHTML =
66
+ '<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>';
67
+ const addVideoButton = createAddFileButton(
68
+ block,
69
+ editor,
70
+ editor.dictionary.file_blocks.video.add_button_text,
71
+ fileBlockVideoIcon.firstElementChild as HTMLElement
72
+ );
73
+ wrapper.appendChild(addVideoButton.dom);
74
+
75
+ return {
76
+ dom: wrapper,
77
+ destroy: () => {
78
+ addVideoButton?.destroy?.();
79
+ },
80
+ };
81
+ } else if (!block.props.showPreview) {
82
+ const file = createDefaultFilePreview(block).dom;
83
+ const element = createFileAndCaptionWrapper(block, file);
84
+
85
+ return {
86
+ dom: element.dom,
87
+ };
88
+ } else {
89
+ const video = document.createElement("video");
90
+ video.className = "bn-visual-media";
91
+ video.src = block.props.url;
92
+ video.controls = true;
93
+ video.contentEditable = "false";
94
+ video.draggable = false;
95
+ video.width = Math.min(
96
+ block.props.previewWidth,
97
+ editor.domElement.firstElementChild!.clientWidth
98
+ );
99
+
100
+ const file = createResizeHandlesWrapper(
101
+ block,
102
+ editor,
103
+ video,
104
+ () => video.width,
105
+ (width) => (video.width = width)
106
+ );
107
+
108
+ const element = createFileAndCaptionWrapper(block, file.dom);
109
+ wrapper.appendChild(element.dom);
110
+
111
+ return {
112
+ dom: wrapper,
113
+ destroy: file.destroy,
114
+ };
115
+ }
116
+ };
117
+
118
+ export const videoParse = (
119
+ element: HTMLElement
120
+ ): Partial<Props<typeof videoBlockConfig.propSchema>> | undefined => {
121
+ if (element.tagName === "VIDEO") {
122
+ return parseVideoElement(element as HTMLVideoElement);
123
+ }
124
+
125
+ if (element.tagName === "FIGURE") {
126
+ const parsedFigure = parseFigureElement(element, "video");
127
+ if (!parsedFigure) {
128
+ return undefined;
129
+ }
130
+
131
+ const { targetElement, caption } = parsedFigure;
132
+
133
+ return {
134
+ ...parseVideoElement(targetElement as HTMLVideoElement),
135
+ caption,
136
+ };
137
+ }
138
+
139
+ return undefined;
140
+ };
141
+
142
+ export const videoToExternalHTML = (
143
+ block: BlockFromConfig<typeof videoBlockConfig, any, any>
144
+ ) => {
145
+ if (!block.props.url) {
146
+ const div = document.createElement("p");
147
+ div.textContent = "Add video";
148
+
149
+ return {
150
+ dom: div,
151
+ };
152
+ }
153
+
154
+ let video;
155
+ if (block.props.showPreview) {
156
+ video = document.createElement("video");
157
+ video.src = block.props.url;
158
+ video.width = block.props.previewWidth;
159
+ } else {
160
+ video = document.createElement("a");
161
+ video.href = block.props.url;
162
+ video.textContent = block.props.name || block.props.url;
163
+ }
164
+
165
+ if (block.props.caption) {
166
+ if (block.props.showPreview) {
167
+ return createFigureWithCaption(video, block.props.caption);
168
+ } else {
169
+ return createLinkWithCaption(video, block.props.caption);
170
+ }
171
+ }
172
+
173
+ return {
174
+ dom: video,
175
+ };
176
+ };
177
+
178
+ export const VideoBlock = createBlockSpec(videoBlockConfig, {
179
+ render: videoRender,
180
+ parse: videoParse,
181
+ toExternalHTML: videoToExternalHTML,
182
+ });
@@ -0,0 +1,6 @@
1
+ export const parseVideoElement = (videoElement: HTMLVideoElement) => {
2
+ const url = videoElement.src || undefined;
3
+ const previewWidth = videoElement.width || undefined;
4
+
5
+ return { url, previewWidth };
6
+ };
@@ -1,5 +1,11 @@
1
1
  import type { BlockNoteEditor } from "../editor/BlockNoteEditor";
2
- import { BlockFromConfig, InlineContentSchema, StyleSchema } from "../schema";
2
+ import {
3
+ BlockFromConfig,
4
+ BlockSchema,
5
+ FileBlockConfig,
6
+ InlineContentSchema,
7
+ StyleSchema,
8
+ } from "../schema";
3
9
  import { Block, DefaultBlockSchema, defaultBlockSchema } from "./defaultBlocks";
4
10
  import { defaultProps } from "./defaultProps";
5
11
 
@@ -33,6 +39,52 @@ export function checkBlockIsDefaultType<
33
39
  );
34
40
  }
35
41
 
42
+ export function checkBlockIsFileBlock<
43
+ B extends BlockSchema,
44
+ I extends InlineContentSchema,
45
+ S extends StyleSchema
46
+ >(
47
+ block: Block<any, I, S>,
48
+ editor: BlockNoteEditor<B, I, S>
49
+ ): block is BlockFromConfig<FileBlockConfig, I, S> {
50
+ return (
51
+ (block.type in editor.schema.blockSchema &&
52
+ editor.schema.blockSchema[block.type].isFileBlock) ||
53
+ false
54
+ );
55
+ }
56
+
57
+ export function checkBlockIsFileBlockWithPreview<
58
+ B extends BlockSchema,
59
+ I extends InlineContentSchema,
60
+ S extends StyleSchema
61
+ >(
62
+ block: Block<any, I, S>,
63
+ editor: BlockNoteEditor<B, I, S>
64
+ ): block is BlockFromConfig<
65
+ FileBlockConfig & {
66
+ propSchema: Required<FileBlockConfig["propSchema"]>;
67
+ },
68
+ I,
69
+ S
70
+ > {
71
+ return (
72
+ (block.type in editor.schema.blockSchema &&
73
+ editor.schema.blockSchema[block.type].isFileBlock &&
74
+ "showPreview" in editor.schema.blockSchema[block.type].propSchema) ||
75
+ false
76
+ );
77
+ }
78
+
79
+ export function checkBlockIsFileBlockWithPlaceholder<
80
+ B extends BlockSchema,
81
+ I extends InlineContentSchema,
82
+ S extends StyleSchema
83
+ >(block: Block<B, I, S>, editor: BlockNoteEditor<B, I, S>) {
84
+ const config = editor.schema.blockSchema[block.type];
85
+ return config.isFileBlock && config.isFileBlockPlaceholder(block);
86
+ }
87
+
36
88
  export function checkBlockTypeHasDefaultProp<
37
89
  Prop extends keyof typeof defaultProps,
38
90
  I extends InlineContentSchema,
@@ -19,19 +19,25 @@ import {
19
19
  getInlineContentSchemaFromSpecs,
20
20
  getStyleSchemaFromSpecs,
21
21
  } from "../schema";
22
+ import { FileBlock } from "./FileBlockContent/FileBlockContent";
23
+ import { ImageBlock } from "./ImageBlockContent/ImageBlockContent";
22
24
  import { Heading } from "./HeadingBlockContent/HeadingBlockContent";
23
- import { Image } from "./ImageBlockContent/ImageBlockContent";
24
25
  import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent";
25
26
  import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent";
26
27
  import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent";
27
28
  import { Table } from "./TableBlockContent/TableBlockContent";
29
+ import { VideoBlock } from "./VideoBlockContent/VideoBlockContent";
30
+ import { AudioBlock } from "./AudioBlockContent/AudioBlockContent";
28
31
 
29
32
  export const defaultBlockSpecs = {
30
33
  paragraph: Paragraph,
31
34
  heading: Heading,
32
35
  bulletListItem: BulletListItem,
33
36
  numberedListItem: NumberedListItem,
34
- image: Image,
37
+ file: FileBlock,
38
+ image: ImageBlock,
39
+ video: VideoBlock,
40
+ audio: AudioBlock,
35
41
  table: Table,
36
42
  } satisfies BlockSpecs;
37
43
 
@@ -13,15 +13,9 @@ BASIC STYLES
13
13
  flex-direction: column;
14
14
  }
15
15
 
16
- /*Ensures block content inside React node views spans editor width*/
17
- .bn-react-node-view-renderer {
18
- display: flex;
19
- flex-grow: 1;
20
- }
21
-
22
16
  .bn-block-content {
17
+ display: flex;
23
18
  padding: 3px 0;
24
- flex-grow: 1;
25
19
  transition: font-size 0.2s;
26
20
  width: 100%;
27
21
  /*
@@ -36,6 +30,13 @@ BASIC STYLES
36
30
  /*margin: 0px;*/
37
31
  }
38
32
 
33
+ .bn-block-content.ProseMirror-selectednode > *,
34
+ /* Case for node view renderers */
35
+ .ProseMirror-selectednode > .bn-block-content > * {
36
+ border-radius: 4px;
37
+ outline: 4px solid rgb(100, 160, 255);
38
+ }
39
+
39
40
  /*
40
41
  NESTED BLOCKS
41
42
  */
@@ -235,49 +236,75 @@ NESTED BLOCKS
235
236
  content: "▪";
236
237
  }
237
238
 
238
- /* IMAGES */
239
+ /* FILES */
240
+
241
+ /* Add block button & default preview */
242
+ [data-file-block] .bn-file-block-content-wrapper:has(.bn-add-file-button),
243
+ [data-file-block] .bn-file-block-content-wrapper:has(.bn-file-default-preview) {
244
+ width: 100%;
245
+ }
239
246
 
240
- [data-content-type="image"] .bn-image-block-content-wrapper {
247
+ [data-file-block] .bn-file-block-content-wrapper {
248
+ cursor: pointer;
241
249
  display: flex;
242
250
  flex-direction: column;
243
- justify-content: center;
251
+ justify-content: stretch;
244
252
  user-select: none;
245
- width: 100%;
246
253
  }
247
254
 
248
- [data-content-type="image"] .bn-add-image-button {
249
- display: flex;
250
- flex-direction: row;
255
+ [data-file-block] .bn-add-file-button {
251
256
  align-items: center;
252
- gap: 8px;
253
- background-color: whitesmoke;
257
+ background-color: rgb(242, 241, 238);
254
258
  border-radius: 4px;
259
+ color: rgb(125, 121, 122);
255
260
  cursor: pointer;
261
+ display: flex;
262
+ flex-direction: row;
263
+ gap: 10px;
256
264
  padding: 12px;
257
265
  width: 100%;
258
266
  }
259
267
 
260
- [data-content-type="image"] .bn-add-image-button:hover {
261
- background-color: gainsboro;
268
+ [data-file-block] .bn-add-file-button:hover {
269
+ background-color: rgb(225, 225, 225);
262
270
  }
263
271
 
264
- [data-content-type="image"] .bn-add-image-button-icon {
265
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H4V19L13.2923 9.70649C13.6828 9.31595 14.3159 9.31591 14.7065 9.70641L20 15.0104V5ZM2 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 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11Z'%3E%3C/path%3E%3C/svg%3E");
272
+ [data-file-block] .bn-add-file-button-icon {
266
273
  width: 24px;
267
274
  height: 24px;
268
275
  }
269
276
 
270
- [data-content-type="image"] .bn-add-image-button-text {
271
- color: black;
277
+ [data-file-block] .bn-add-file-button .bn-add-file-button-text {
278
+ font-size: 0.9rem;
272
279
  }
273
280
 
274
- [data-content-type="image"] .bn-image-and-caption-wrapper {
281
+ [data-file-block] .bn-file-and-caption-wrapper {
275
282
  display: flex;
276
283
  flex-direction: column;
277
284
  border-radius: 4px;
278
285
  }
279
286
 
280
- [data-content-type="image"] .bn-image-wrapper {
287
+ [data-file-block] .bn-file-default-preview {
288
+ align-items: center;
289
+ border-radius: 4px;
290
+ display: flex;
291
+ flex-direction: row;
292
+ gap: 4px;
293
+ padding: 4px;
294
+ width: 100%;
295
+ }
296
+
297
+ [data-file-block] .bn-file-default-preview:hover,
298
+ .ProseMirror-selectednode .bn-file-default-preview {
299
+ background-color: rgb(225, 225, 225);
300
+ }
301
+
302
+ [data-file-block] .bn-file-default-preview-icon {
303
+ width: 24px;
304
+ height: 24px;
305
+ }
306
+
307
+ [data-file-block] .bn-visual-media-wrapper {
281
308
  display: flex;
282
309
  flex-direction: row;
283
310
  align-items: center;
@@ -285,12 +312,12 @@ NESTED BLOCKS
285
312
  width: fit-content;
286
313
  }
287
314
 
288
- [data-content-type="image"] .bn-image {
315
+ [data-file-block] .bn-visual-media {
289
316
  border-radius: 4px;
290
317
  max-width: 100%;
291
318
  }
292
319
 
293
- [data-content-type="image"] .bn-image-resize-handle {
320
+ [data-file-block] .bn-visual-media-resize-handle {
294
321
  position: absolute;
295
322
  width: 8px;
296
323
  height: 30px;
@@ -300,8 +327,17 @@ NESTED BLOCKS
300
327
  cursor: ew-resize;
301
328
  }
302
329
 
303
- [data-content-type="image"] .caption {
330
+ [data-content-type="audio"] > .bn-file-block-content-wrapper, .bn-audio {
331
+ width: 100%;
332
+ }
333
+
334
+ [data-file-block] .bn-file-caption {
304
335
  font-size: 0.8em;
336
+ padding-block: 4px;
337
+ }
338
+
339
+ [data-file-block] .bn-file-caption:empty {
340
+ padding-block: 0;
305
341
  }
306
342
 
307
343
  /* PLACEHOLDERS*/
@@ -392,17 +428,21 @@ NESTED BLOCKS
392
428
 
393
429
  /* TEXT ALIGNMENT */
394
430
  [data-text-alignment="left"] {
431
+ justify-content: flex-start;
395
432
  text-align: left;
396
433
  }
397
434
 
398
435
  [data-text-alignment="center"] {
436
+ justify-content: center;
399
437
  text-align: center;
400
438
  }
401
439
 
402
440
  [data-text-alignment="right"] {
441
+ justify-content: flex-end;
403
442
  text-align: right;
404
443
  }
405
444
 
406
445
  [data-text-alignment="justify"] {
446
+ justify-content: flex-start;
407
447
  text-align: justify;
408
448
  }
@@ -26,8 +26,8 @@ import {
26
26
  DefaultStyleSchema,
27
27
  PartialBlock,
28
28
  } from "../blocks/defaultBlocks";
29
+ import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin";
29
30
  import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin";
30
- import { ImagePanelProsemirrorPlugin } from "../extensions/ImagePanel/ImageToolbarPlugin";
31
31
  import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin";
32
32
  import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin";
33
33
  import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin";
@@ -113,9 +113,9 @@ export type BlockNoteEditorOptions<
113
113
  /**
114
114
  * A custom function to handle file uploads.
115
115
  * @param file The file that should be uploaded.
116
- * @returns The URL of the uploaded file.
116
+ * @returns The URL of the uploaded file OR an object containing props that should be set on the file block (such as an id)
117
117
  */
118
- uploadFile: (file: File) => Promise<string>;
118
+ uploadFile: (file: File) => Promise<string | Record<string, any>>;
119
119
 
120
120
  /**
121
121
  * When enabled, allows for collaboration between multiple users.
@@ -187,13 +187,19 @@ export class BlockNoteEditor<
187
187
  ISchema,
188
188
  SSchema
189
189
  >;
190
- public readonly imagePanel?: ImagePanelProsemirrorPlugin<ISchema, SSchema>;
190
+ public readonly filePanel?: FilePanelProsemirrorPlugin<
191
+ BSchema,
192
+ ISchema,
193
+ SSchema
194
+ >;
191
195
  public readonly tableHandles?: TableHandlesProsemirrorPlugin<
192
196
  ISchema,
193
197
  SSchema
194
198
  >;
195
199
 
196
- public readonly uploadFile: ((file: File) => Promise<string>) | undefined;
200
+ public readonly uploadFile:
201
+ | ((file: File) => Promise<string | Record<string, any>>)
202
+ | undefined;
197
203
 
198
204
  public static create<
199
205
  BSchema extends BlockSchema = DefaultBlockSchema,
@@ -254,10 +260,8 @@ export class BlockNoteEditor<
254
260
  this.linkToolbar = new LinkToolbarProsemirrorPlugin(this);
255
261
  this.sideMenu = new SideMenuProsemirrorPlugin(this);
256
262
  this.suggestionMenus = new SuggestionMenuProseMirrorPlugin(this);
257
- if (checkDefaultBlockTypeInSchema("image", this)) {
258
- // Type guards only work on `const`s? Not working for `this`
259
- this.imagePanel = new ImagePanelProsemirrorPlugin(this as any);
260
- }
263
+ this.filePanel = new FilePanelProsemirrorPlugin(this as any);
264
+
261
265
  if (checkDefaultBlockTypeInSchema("table", this)) {
262
266
  this.tableHandles = new TableHandlesProsemirrorPlugin(this as any);
263
267
  }
@@ -282,7 +286,7 @@ export class BlockNoteEditor<
282
286
  this.linkToolbar.plugin,
283
287
  this.sideMenu.plugin,
284
288
  this.suggestionMenus.plugin,
285
- ...(this.imagePanel ? [this.imagePanel.plugin] : []),
289
+ ...(this.filePanel ? [this.filePanel.plugin] : []),
286
290
  ...(this.tableHandles ? [this.tableHandles.plugin] : []),
287
291
  PlaceholderPlugin(this, newOptions.placeholders),
288
292
  ];
@@ -23,6 +23,15 @@ import type {
23
23
  } from "../schema/blocks/types";
24
24
  import type { BlockNoteEditor } from "./BlockNoteEditor";
25
25
 
26
+ function removeUndefined<T extends Record<string, any> | undefined>(obj: T): T {
27
+ if (!obj) {
28
+ return obj;
29
+ }
30
+ return Object.fromEntries(
31
+ Object.entries(obj).filter(([, value]) => value !== undefined)
32
+ ) as T;
33
+ }
34
+
26
35
  export class BlockNoteSchema<
27
36
  BSchema extends BlockSchema,
28
37
  ISchema extends InlineContentSchema,
@@ -84,10 +93,10 @@ export class BlockNoteSchema<
84
93
  inlineContentSpecs?: InlineContentSpecs;
85
94
  styleSpecs?: StyleSpecs;
86
95
  }) {
87
- this.blockSpecs = opts?.blockSpecs || defaultBlockSpecs;
96
+ this.blockSpecs = removeUndefined(opts?.blockSpecs) || defaultBlockSpecs;
88
97
  this.inlineContentSpecs =
89
- opts?.inlineContentSpecs || defaultInlineContentSpecs;
90
- this.styleSpecs = opts?.styleSpecs || defaultStyleSpecs;
98
+ removeUndefined(opts?.inlineContentSpecs) || defaultInlineContentSpecs;
99
+ this.styleSpecs = removeUndefined(opts?.styleSpecs) || defaultStyleSpecs;
91
100
 
92
101
  this.blockSchema = getBlockSchemaFromSpecs(this.blockSpecs) as any;
93
102
  this.inlineContentSchema = getInlineContentSchemaFromSpecs(
@@ -1,30 +1,29 @@
1
1
  import { EditorState, Plugin, PluginKey, PluginView } from "prosemirror-state";
2
2
  import { EditorView } from "prosemirror-view";
3
3
 
4
- import { DefaultBlockSchema } from "../../blocks/defaultBlocks";
5
4
  import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
6
5
  import { UiElementPosition } from "../../extensions-shared/UiElementPosition";
7
6
  import type {
8
7
  BlockFromConfig,
8
+ BlockSchema,
9
+ FileBlockConfig,
9
10
  InlineContentSchema,
10
11
  StyleSchema,
11
12
  } from "../../schema";
12
13
  import { EventEmitter } from "../../util/EventEmitter";
13
14
 
14
- export type ImagePanelState<
15
+ export type FilePanelState<
15
16
  I extends InlineContentSchema,
16
17
  S extends StyleSchema
17
18
  > = UiElementPosition & {
18
19
  // TODO: This typing is not quite right (children should be from BSchema)
19
- block: BlockFromConfig<DefaultBlockSchema["image"], I, S>;
20
+ block: BlockFromConfig<FileBlockConfig, I, S>;
20
21
  };
21
22
 
22
- export class ImagePanelView<
23
- I extends InlineContentSchema,
24
- S extends StyleSchema
25
- > implements PluginView
23
+ export class FilePanelView<I extends InlineContentSchema, S extends StyleSchema>
24
+ implements PluginView
26
25
  {
27
- public state?: ImagePanelState<I, S>;
26
+ public state?: FilePanelState<I, S>;
28
27
  public emitUpdate: () => void;
29
28
 
30
29
  public prevWasEditable: boolean | null = null;
@@ -32,11 +31,11 @@ export class ImagePanelView<
32
31
  constructor(
33
32
  private readonly pluginKey: PluginKey,
34
33
  private readonly pmView: EditorView,
35
- emitUpdate: (state: ImagePanelState<I, S>) => void
34
+ emitUpdate: (state: FilePanelState<I, S>) => void
36
35
  ) {
37
36
  this.emitUpdate = () => {
38
37
  if (!this.state) {
39
- throw new Error("Attempting to update uninitialized image panel");
38
+ throw new Error("Attempting to update uninitialized file panel");
40
39
  }
41
40
 
42
41
  emitUpdate(this.state);
@@ -77,7 +76,7 @@ export class ImagePanelView<
77
76
 
78
77
  update(view: EditorView, prevState: EditorState) {
79
78
  const pluginState: {
80
- block: BlockFromConfig<DefaultBlockSchema["image"], I, S>;
79
+ block: BlockFromConfig<FileBlockConfig, I, S>;
81
80
  } = this.pluginKey.getState(view.state);
82
81
 
83
82
  if (!this.state?.show && pluginState.block) {
@@ -124,27 +123,26 @@ export class ImagePanelView<
124
123
  }
125
124
  }
126
125
 
127
- const imagePanelPluginKey = new PluginKey("ImagePanelPlugin");
126
+ const filePanelPluginKey = new PluginKey("FilePanelPlugin");
128
127
 
129
- export class ImagePanelProsemirrorPlugin<
128
+ export class FilePanelProsemirrorPlugin<
129
+ B extends BlockSchema,
130
130
  I extends InlineContentSchema,
131
131
  S extends StyleSchema
132
132
  > extends EventEmitter<any> {
133
- private view: ImagePanelView<I, S> | undefined;
133
+ private view: FilePanelView<I, S> | undefined;
134
134
  public readonly plugin: Plugin;
135
135
 
136
- constructor(
137
- _editor: BlockNoteEditor<{ image: DefaultBlockSchema["image"] }, I, S>
138
- ) {
136
+ constructor(_editor: BlockNoteEditor<B, I, S>) {
139
137
  super();
140
138
  this.plugin = new Plugin<{
141
- block: BlockFromConfig<DefaultBlockSchema["image"], I, S> | undefined;
139
+ block: BlockFromConfig<FileBlockConfig, I, S> | undefined;
142
140
  }>({
143
- key: imagePanelPluginKey,
141
+ key: filePanelPluginKey,
144
142
  view: (editorView) => {
145
- this.view = new ImagePanelView(
143
+ this.view = new FilePanelView(
146
144
  // editor,
147
- imagePanelPluginKey,
145
+ filePanelPluginKey,
148
146
  editorView,
149
147
  (state) => {
150
148
  this.emit("update", state);
@@ -168,9 +166,8 @@ export class ImagePanelProsemirrorPlugin<
168
166
  };
169
167
  },
170
168
  apply: (transaction) => {
171
- const block:
172
- | BlockFromConfig<DefaultBlockSchema["image"], I, S>
173
- | undefined = transaction.getMeta(imagePanelPluginKey)?.block;
169
+ const block: BlockFromConfig<FileBlockConfig, I, S> | undefined =
170
+ transaction.getMeta(filePanelPluginKey)?.block;
174
171
 
175
172
  return {
176
173
  block,
@@ -184,7 +181,7 @@ export class ImagePanelProsemirrorPlugin<
184
181
  return this.view?.state?.show || false;
185
182
  }
186
183
 
187
- public onUpdate(callback: (state: ImagePanelState<I, S>) => void) {
184
+ public onUpdate(callback: (state: FilePanelState<I, S>) => void) {
188
185
  return this.on("update", callback);
189
186
  }
190
187