@blocknote/core 0.13.2 → 0.13.4

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 (163) hide show
  1. package/dist/blocknote.js +5730 -2891
  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__/lists/basic/external.html +1 -0
  31. package/src/api/exporters/html/__snapshots__/lists/basic/internal.html +1 -0
  32. package/src/api/exporters/html/__snapshots__/lists/nested/external.html +1 -0
  33. package/src/api/exporters/html/__snapshots__/lists/nested/internal.html +1 -0
  34. package/src/api/exporters/html/__snapshots__/simpleFile/basic/external.html +1 -0
  35. package/src/api/exporters/html/__snapshots__/simpleFile/basic/internal.html +1 -0
  36. package/src/api/exporters/html/__snapshots__/simpleFile/button/external.html +1 -0
  37. package/src/api/exporters/html/__snapshots__/simpleFile/button/internal.html +1 -0
  38. package/src/api/exporters/html/__snapshots__/simpleFile/nested/external.html +1 -0
  39. package/src/api/exporters/html/__snapshots__/simpleFile/nested/internal.html +1 -0
  40. package/src/api/exporters/html/__snapshots__/simpleImage/basic/external.html +1 -1
  41. package/src/api/exporters/html/__snapshots__/simpleImage/basic/internal.html +1 -1
  42. package/src/api/exporters/html/__snapshots__/simpleImage/button/external.html +1 -1
  43. package/src/api/exporters/html/__snapshots__/simpleImage/button/internal.html +1 -1
  44. package/src/api/exporters/html/__snapshots__/simpleImage/nested/external.html +1 -1
  45. package/src/api/exporters/html/__snapshots__/simpleImage/nested/internal.html +1 -1
  46. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/external.html +1 -0
  47. package/src/api/exporters/html/__snapshots__/simpleImage/noCaption/internal.html +1 -0
  48. package/src/api/exporters/html/__snapshots__/simpleImage/noName/external.html +1 -0
  49. package/src/api/exporters/html/__snapshots__/simpleImage/noName/internal.html +1 -0
  50. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/external.html +1 -0
  51. package/src/api/exporters/html/__snapshots__/simpleImage/noPreview/internal.html +1 -0
  52. package/src/api/exporters/html/externalHTMLExporter.ts +4 -3
  53. package/src/api/exporters/html/util/simplifyBlocksRehypePlugin.ts +1 -1
  54. package/src/api/exporters/markdown/__snapshots__/file/basic/markdown.md +3 -0
  55. package/src/api/exporters/markdown/__snapshots__/file/button/markdown.md +1 -0
  56. package/src/api/exporters/markdown/__snapshots__/file/nested/markdown.md +7 -0
  57. package/src/api/exporters/markdown/__snapshots__/file/noCaption/markdown.md +1 -0
  58. package/src/api/exporters/markdown/__snapshots__/file/noName/markdown.md +3 -0
  59. package/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md +1 -1
  60. package/src/api/exporters/markdown/__snapshots__/image/button/markdown.md +1 -1
  61. package/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md +2 -2
  62. package/src/api/exporters/markdown/__snapshots__/image/noCaption/markdown.md +1 -0
  63. package/src/api/exporters/markdown/__snapshots__/image/noName/markdown.md +3 -0
  64. package/src/api/exporters/markdown/__snapshots__/image/noPreview/markdown.md +3 -0
  65. package/src/api/exporters/markdown/__snapshots__/lists/basic/markdown.md +8 -0
  66. package/src/api/exporters/markdown/__snapshots__/lists/nested/markdown.md +10 -0
  67. package/src/api/exporters/markdown/__snapshots__/simpleFile/basic/markdown.md +3 -0
  68. package/src/api/exporters/markdown/__snapshots__/simpleFile/button/markdown.md +1 -0
  69. package/src/api/exporters/markdown/__snapshots__/simpleFile/nested/markdown.md +7 -0
  70. package/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md +3 -1
  71. package/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md +1 -0
  72. package/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md +6 -2
  73. package/src/api/exporters/markdown/__snapshots__/simpleImage/noCaption/markdown.md +1 -0
  74. package/src/api/exporters/markdown/__snapshots__/simpleImage/noName/markdown.md +3 -0
  75. package/src/api/exporters/markdown/__snapshots__/simpleImage/noPreview/markdown.md +3 -0
  76. package/src/api/exporters/markdown/markdownExporter.ts +2 -0
  77. package/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts +42 -0
  78. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +262 -4
  79. package/src/api/parsers/html/__snapshots__/paste/list-test.json +74 -2
  80. package/src/api/parsers/html/__snapshots__/paste/parse-basic-block-types.json +3 -1
  81. package/src/api/parsers/html/__snapshots__/paste/parse-fake-image-caption.json +3 -1
  82. package/src/api/parsers/html/__snapshots__/paste/parse-mixed-nested-lists.json +135 -10
  83. package/src/api/parsers/html/__snapshots__/paste/parse-nested-lists-with-paragraphs.json +132 -7
  84. package/src/api/parsers/html/__snapshots__/paste/parse-nested-lists.json +111 -3
  85. package/src/api/parsers/html/parseHTML.test.ts +166 -95
  86. package/src/api/testUtil/cases/customBlocks.ts +82 -33
  87. package/src/api/testUtil/cases/customInlineContent.ts +1 -1
  88. package/src/api/testUtil/cases/customStyles.ts +1 -1
  89. package/src/api/testUtil/cases/defaultSchema.ts +185 -4
  90. package/src/blocks/AudioBlockContent/AudioBlockContent.ts +163 -0
  91. package/src/blocks/AudioBlockContent/audioBlockHelpers.ts +5 -0
  92. package/src/blocks/FileBlockContent/FileBlockContent.ts +120 -0
  93. package/src/blocks/FileBlockContent/fileBlockHelpers.ts +377 -0
  94. package/src/blocks/ImageBlockContent/ImageBlockContent.ts +134 -354
  95. package/src/blocks/ImageBlockContent/imageBlockHelpers.ts +6 -0
  96. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +3 -0
  97. package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +266 -0
  98. package/src/blocks/ListItemBlockContent/ListItemKeyboardShortcuts.ts +2 -1
  99. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +1 -0
  100. package/src/blocks/VideoBlockContent/VideoBlockContent.ts +181 -0
  101. package/src/blocks/VideoBlockContent/videoBlockHelpers.ts +6 -0
  102. package/src/blocks/defaultBlockTypeGuards.ts +53 -1
  103. package/src/blocks/defaultBlocks.ts +11 -2
  104. package/src/editor/Block.css +89 -27
  105. package/src/editor/BlockNoteEditor.ts +24 -10
  106. package/src/editor/BlockNoteSchema.ts +12 -3
  107. package/src/editor/transformPasted.ts +2 -1
  108. package/src/extensions/{ImagePanel/ImageToolbarPlugin.ts → FilePanel/FilePanelPlugin.ts} +22 -25
  109. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +14 -2
  110. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +72 -2
  111. package/src/extensions/TableHandles/TableHandlesPlugin.ts +27 -27
  112. package/src/extensions/TextAlignment/TextAlignmentExtension.ts +7 -1
  113. package/src/i18n/locales/en.ts +117 -11
  114. package/src/i18n/locales/fr.ts +118 -11
  115. package/src/i18n/locales/index.ts +8 -2
  116. package/src/i18n/locales/is.ts +295 -0
  117. package/src/i18n/locales/ja.ts +323 -0
  118. package/src/i18n/locales/ko.ts +307 -0
  119. package/src/i18n/locales/nl.ts +108 -8
  120. package/src/i18n/locales/pl.ts +287 -0
  121. package/src/i18n/locales/pt.ts +295 -0
  122. package/src/i18n/locales/vi.ts +295 -0
  123. package/src/i18n/locales/zh.ts +123 -8
  124. package/src/index.ts +9 -2
  125. package/src/pm-nodes/BlockContainer.ts +18 -6
  126. package/src/schema/blocks/createSpec.ts +1 -0
  127. package/src/schema/blocks/internal.ts +10 -0
  128. package/src/schema/blocks/types.ts +40 -5
  129. package/src/util/string.ts +12 -0
  130. package/types/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.d.ts +7 -0
  131. package/types/src/api/testUtil/cases/customBlocks.d.ts +272 -54
  132. package/types/src/api/testUtil/cases/customInlineContent.d.ts +222 -16
  133. package/types/src/api/testUtil/cases/customStyles.d.ts +222 -16
  134. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +101 -0
  135. package/types/src/blocks/AudioBlockContent/audioBlockHelpers.d.ts +3 -0
  136. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +93 -0
  137. package/types/src/blocks/FileBlockContent/fileBlockHelpers.d.ts +30 -0
  138. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +50 -14
  139. package/types/src/blocks/ImageBlockContent/imageBlockHelpers.d.ts +4 -0
  140. package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +55 -0
  141. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +129 -0
  142. package/types/src/blocks/VideoBlockContent/videoBlockHelpers.d.ts +4 -0
  143. package/types/src/blocks/defaultBlockTypeGuards.d.ts +6 -1
  144. package/types/src/blocks/defaultBlocks.d.ts +444 -32
  145. package/types/src/editor/BlockNoteEditor.d.ts +12 -5
  146. package/types/src/extensions/{ImagePanel/ImageToolbarPlugin.d.ts → FilePanel/FilePanelPlugin.d.ts} +9 -12
  147. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +1 -1
  148. package/types/src/i18n/locales/en.d.ts +56 -7
  149. package/types/src/i18n/locales/fr.d.ts +2 -184
  150. package/types/src/i18n/locales/index.d.ts +7 -1
  151. package/types/src/i18n/locales/is.d.ts +2 -0
  152. package/types/src/i18n/locales/ja.d.ts +2 -0
  153. package/types/src/i18n/locales/ko.d.ts +2 -0
  154. package/types/src/i18n/locales/pl.d.ts +2 -0
  155. package/types/src/i18n/locales/pt.d.ts +2 -0
  156. package/types/src/i18n/locales/vi.d.ts +2 -0
  157. package/types/src/index.d.ts +8 -2
  158. package/types/src/pm-nodes/BlockContainer.d.ts +1 -1
  159. package/types/src/schema/blocks/internal.d.ts +1 -1
  160. package/types/src/schema/blocks/types.d.ts +25 -1
  161. package/types/src/util/string.d.ts +1 -0
  162. /package/src/blocks/{ImageBlockContent → FileBlockContent}/uploadToTmpFilesDotOrg_DEV_ONLY.ts +0 -0
  163. /package/types/src/blocks/{ImageBlockContent → FileBlockContent}/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +0 -0
@@ -0,0 +1,266 @@
1
+ import { InputRule } from "@tiptap/core";
2
+ import {
3
+ PropSchema,
4
+ createBlockSpecFromStronglyTypedTiptapNode,
5
+ createStronglyTypedTiptapNode,
6
+ } from "../../../schema";
7
+ import { createDefaultBlockDOMOutputSpec } from "../../defaultBlockHelpers";
8
+ import { defaultProps } from "../../defaultProps";
9
+ import { handleEnter } from "../ListItemKeyboardShortcuts";
10
+ import { getCurrentBlockContentType } from "../../../api/getCurrentBlockContentType";
11
+
12
+ export const checkListItemPropSchema = {
13
+ ...defaultProps,
14
+ checked: {
15
+ default: false,
16
+ },
17
+ } satisfies PropSchema;
18
+
19
+ const checkListItemBlockContent = createStronglyTypedTiptapNode({
20
+ name: "checkListItem",
21
+ content: "inline*",
22
+ group: "blockContent",
23
+ addAttributes() {
24
+ return {
25
+ checked: {
26
+ default: false,
27
+ // instead of "checked" attributes, use "data-checked"
28
+ parseHTML: (element) =>
29
+ element.getAttribute("data-checked") === "true" || undefined,
30
+ renderHTML: (attributes) => {
31
+ return attributes.checked
32
+ ? {
33
+ "data-checked": (attributes.checked as boolean).toString(),
34
+ }
35
+ : {};
36
+ },
37
+ },
38
+ };
39
+ },
40
+
41
+ addInputRules() {
42
+ return [
43
+ // Creates a checklist when starting with "[]" or "[X]".
44
+ new InputRule({
45
+ find: new RegExp(`\\[\\s*\\]\\s$`),
46
+ handler: ({ state, chain, range }) => {
47
+ if (getCurrentBlockContentType(this.editor) !== "inline*") {
48
+ return;
49
+ }
50
+
51
+ chain()
52
+ .BNUpdateBlock(state.selection.from, {
53
+ type: "checkListItem",
54
+ props: {
55
+ checked: false as any,
56
+ },
57
+ })
58
+ // Removes the characters used to set the list.
59
+ .deleteRange({ from: range.from, to: range.to });
60
+ },
61
+ }),
62
+ new InputRule({
63
+ find: new RegExp(`\\[[Xx]\\]\\s$`),
64
+ handler: ({ state, chain, range }) => {
65
+ if (getCurrentBlockContentType(this.editor) !== "inline*") {
66
+ return;
67
+ }
68
+
69
+ chain()
70
+ .BNUpdateBlock(state.selection.from, {
71
+ type: "checkListItem",
72
+ props: {
73
+ checked: true as any,
74
+ },
75
+ })
76
+ // Removes the characters used to set the list.
77
+ .deleteRange({ from: range.from, to: range.to });
78
+ },
79
+ }),
80
+ ];
81
+ },
82
+
83
+ addKeyboardShortcuts() {
84
+ return {
85
+ Enter: () => handleEnter(this.editor),
86
+ "Mod-Shift-9": () => {
87
+ if (getCurrentBlockContentType(this.editor) !== "inline*") {
88
+ return true;
89
+ }
90
+
91
+ return this.editor.commands.BNUpdateBlock(
92
+ this.editor.state.selection.anchor,
93
+ {
94
+ type: "checkListItem",
95
+ props: {},
96
+ }
97
+ );
98
+ },
99
+ };
100
+ },
101
+
102
+ parseHTML() {
103
+ return [
104
+ {
105
+ tag: "div[data-content-type=" + this.name + "]", // TODO: remove if we can't come up with test case that needs this
106
+ },
107
+ // Checkbox only.
108
+ {
109
+ tag: "input",
110
+ getAttrs: (element) => {
111
+ if (typeof element === "string") {
112
+ return false;
113
+ }
114
+
115
+ if ((element as HTMLInputElement).type === "checkbox") {
116
+ return { checked: (element as HTMLInputElement).checked };
117
+ }
118
+
119
+ return false;
120
+ },
121
+ node: "checkListItem",
122
+ },
123
+ // Container element for checkbox + label.
124
+ {
125
+ tag: "li",
126
+ getAttrs: (element) => {
127
+ if (typeof element === "string") {
128
+ return false;
129
+ }
130
+
131
+ const parent = element.parentElement;
132
+
133
+ if (parent === null) {
134
+ return false;
135
+ }
136
+
137
+ if (
138
+ parent.tagName === "UL" ||
139
+ (parent.tagName === "DIV" && parent.parentElement!.tagName === "UL")
140
+ ) {
141
+ const checkbox =
142
+ (element.querySelector(
143
+ "input[type=checkbox]"
144
+ ) as HTMLInputElement) || null;
145
+
146
+ if (checkbox === null) {
147
+ return false;
148
+ }
149
+
150
+ return { checked: checkbox.checked };
151
+ }
152
+
153
+ return false;
154
+ },
155
+ node: "checkListItem",
156
+ },
157
+ ];
158
+ },
159
+
160
+ // Since there is no HTML checklist element, there isn't really any
161
+ // standardization for what checklists should look like in the DOM. GDocs'
162
+ // and Notion's aren't cross compatible, for example. This implementation
163
+ // has a semantically correct DOM structure (though missing a label for the
164
+ // checkbox) which is also converted correctly to Markdown by remark.
165
+ renderHTML({ node, HTMLAttributes }) {
166
+ const checkbox = document.createElement("input");
167
+ checkbox.type = "checkbox";
168
+ checkbox.checked = node.attrs.checked;
169
+ if (node.attrs.checked) {
170
+ checkbox.setAttribute("checked", "");
171
+ }
172
+
173
+ const { dom, contentDOM } = createDefaultBlockDOMOutputSpec(
174
+ this.name,
175
+ "p",
176
+ {
177
+ ...(this.options.domAttributes?.blockContent || {}),
178
+ ...HTMLAttributes,
179
+ },
180
+ this.options.domAttributes?.inlineContent || {}
181
+ );
182
+
183
+ dom.insertBefore(checkbox, contentDOM);
184
+
185
+ return { dom, contentDOM };
186
+ },
187
+
188
+ // Need to render node view since the checkbox needs to be able to update the
189
+ // node. This is only possible with a node view as it exposes `getPos`.
190
+ addNodeView() {
191
+ return ({ node, getPos, editor, HTMLAttributes }) => {
192
+ // Need to wrap certain elements in a div or keyboard navigation gets
193
+ // confused.
194
+ const wrapper = document.createElement("div");
195
+ const checkboxWrapper = document.createElement("div");
196
+ checkboxWrapper.contentEditable = "false";
197
+
198
+ const checkbox = document.createElement("input");
199
+ checkbox.type = "checkbox";
200
+ checkbox.checked = node.attrs.checked;
201
+ if (node.attrs.checked) {
202
+ checkbox.setAttribute("checked", "");
203
+ }
204
+
205
+ const changeHandler = () => {
206
+ if (!editor.isEditable) {
207
+ // This seems like the most effective way of blocking the checkbox
208
+ // from being toggled, as event.preventDefault() does not stop it for
209
+ // "click" or "change" events.
210
+ checkbox.checked = !checkbox.checked;
211
+ return;
212
+ }
213
+
214
+ if (typeof getPos !== "boolean") {
215
+ this.editor.commands.BNUpdateBlock(getPos(), {
216
+ type: "checkListItem",
217
+ props: {
218
+ checked: checkbox.checked as any,
219
+ },
220
+ });
221
+ }
222
+ };
223
+ checkbox.addEventListener("change", changeHandler);
224
+
225
+ const { dom, contentDOM } = createDefaultBlockDOMOutputSpec(
226
+ this.name,
227
+ "p",
228
+ {
229
+ ...(this.options.domAttributes?.blockContent || {}),
230
+ ...HTMLAttributes,
231
+ },
232
+ this.options.domAttributes?.inlineContent || {}
233
+ );
234
+
235
+ if (typeof getPos !== "boolean") {
236
+ // Since `node` is a blockContent node, we have to get the block ID from
237
+ // the parent blockContainer node. This means we can't add the label in
238
+ // `renderHTML` as we can't use `getPos` and therefore can't get the
239
+ // parent blockContainer node.
240
+ const blockID = this.editor.state.doc.resolve(getPos()).node().attrs.id;
241
+ const label = "label-" + blockID;
242
+ checkbox.setAttribute("aria-labelledby", label);
243
+ contentDOM.id = label;
244
+ }
245
+
246
+ dom.removeChild(contentDOM);
247
+ dom.appendChild(wrapper);
248
+ wrapper.appendChild(checkboxWrapper);
249
+ wrapper.appendChild(contentDOM);
250
+ checkboxWrapper.appendChild(checkbox);
251
+
252
+ return {
253
+ dom,
254
+ contentDOM,
255
+ destroy: () => {
256
+ checkbox.removeEventListener("change", changeHandler);
257
+ },
258
+ };
259
+ };
260
+ },
261
+ });
262
+
263
+ export const CheckListItem = createBlockSpecFromStronglyTypedTiptapNode(
264
+ checkListItemBlockContent,
265
+ checkListItemPropSchema
266
+ );
@@ -13,7 +13,8 @@ export const handleEnter = (editor: Editor) => {
13
13
  if (
14
14
  !(
15
15
  contentType.name === "bulletListItem" ||
16
- contentType.name === "numberedListItem"
16
+ contentType.name === "numberedListItem" ||
17
+ contentType.name === "checkListItem"
17
18
  ) ||
18
19
  !selectionEmpty
19
20
  ) {
@@ -18,6 +18,7 @@ const NumberedListItemBlockContent = createStronglyTypedTiptapNode({
18
18
  name: "numberedListItem",
19
19
  content: "inline*",
20
20
  group: "blockContent",
21
+ priority: 90,
21
22
  addAttributes() {
22
23
  return {
23
24
  index: {
@@ -0,0 +1,181 @@
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
+ fileBlockAcceptMimeTypes: ["video/*"],
53
+ } satisfies FileBlockConfig;
54
+
55
+ export const videoRender = (
56
+ block: BlockFromConfig<typeof videoBlockConfig, any, any>,
57
+ editor: BlockNoteEditor<any, any, any>
58
+ ) => {
59
+ const wrapper = document.createElement("div");
60
+ wrapper.className = "bn-file-block-content-wrapper";
61
+
62
+ if (block.props.url === "") {
63
+ const fileBlockVideoIcon = document.createElement("div");
64
+ fileBlockVideoIcon.innerHTML =
65
+ '<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>';
66
+ const addVideoButton = createAddFileButton(
67
+ block,
68
+ editor,
69
+ editor.dictionary.file_blocks.video.add_button_text,
70
+ fileBlockVideoIcon.firstElementChild as HTMLElement
71
+ );
72
+ wrapper.appendChild(addVideoButton.dom);
73
+
74
+ return {
75
+ dom: wrapper,
76
+ destroy: () => {
77
+ addVideoButton?.destroy?.();
78
+ },
79
+ };
80
+ } else if (!block.props.showPreview) {
81
+ const file = createDefaultFilePreview(block).dom;
82
+ const element = createFileAndCaptionWrapper(block, file);
83
+
84
+ return {
85
+ dom: element.dom,
86
+ };
87
+ } else {
88
+ const video = document.createElement("video");
89
+ video.className = "bn-visual-media";
90
+ video.src = block.props.url;
91
+ video.controls = true;
92
+ video.contentEditable = "false";
93
+ video.draggable = false;
94
+ video.width = Math.min(
95
+ block.props.previewWidth,
96
+ editor.domElement.firstElementChild!.clientWidth
97
+ );
98
+
99
+ const file = createResizeHandlesWrapper(
100
+ block,
101
+ editor,
102
+ video,
103
+ () => video.width,
104
+ (width) => (video.width = width)
105
+ );
106
+
107
+ const element = createFileAndCaptionWrapper(block, file.dom);
108
+ wrapper.appendChild(element.dom);
109
+
110
+ return {
111
+ dom: wrapper,
112
+ destroy: file.destroy,
113
+ };
114
+ }
115
+ };
116
+
117
+ export const videoParse = (
118
+ element: HTMLElement
119
+ ): Partial<Props<typeof videoBlockConfig.propSchema>> | undefined => {
120
+ if (element.tagName === "VIDEO") {
121
+ return parseVideoElement(element as HTMLVideoElement);
122
+ }
123
+
124
+ if (element.tagName === "FIGURE") {
125
+ const parsedFigure = parseFigureElement(element, "video");
126
+ if (!parsedFigure) {
127
+ return undefined;
128
+ }
129
+
130
+ const { targetElement, caption } = parsedFigure;
131
+
132
+ return {
133
+ ...parseVideoElement(targetElement as HTMLVideoElement),
134
+ caption,
135
+ };
136
+ }
137
+
138
+ return undefined;
139
+ };
140
+
141
+ export const videoToExternalHTML = (
142
+ block: BlockFromConfig<typeof videoBlockConfig, any, any>
143
+ ) => {
144
+ if (!block.props.url) {
145
+ const div = document.createElement("p");
146
+ div.textContent = "Add video";
147
+
148
+ return {
149
+ dom: div,
150
+ };
151
+ }
152
+
153
+ let video;
154
+ if (block.props.showPreview) {
155
+ video = document.createElement("video");
156
+ video.src = block.props.url;
157
+ video.width = block.props.previewWidth;
158
+ } else {
159
+ video = document.createElement("a");
160
+ video.href = block.props.url;
161
+ video.textContent = block.props.name || block.props.url;
162
+ }
163
+
164
+ if (block.props.caption) {
165
+ if (block.props.showPreview) {
166
+ return createFigureWithCaption(video, block.props.caption);
167
+ } else {
168
+ return createLinkWithCaption(video, block.props.caption);
169
+ }
170
+ }
171
+
172
+ return {
173
+ dom: video,
174
+ };
175
+ };
176
+
177
+ export const VideoBlock = createBlockSpec(videoBlockConfig, {
178
+ render: videoRender,
179
+ parse: videoParse,
180
+ toExternalHTML: videoToExternalHTML,
181
+ });
@@ -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 && !block.props.url;
86
+ }
87
+
36
88
  export function checkBlockTypeHasDefaultProp<
37
89
  Prop extends keyof typeof defaultProps,
38
90
  I extends InlineContentSchema,
@@ -19,20 +19,29 @@ import {
19
19
  getInlineContentSchemaFromSpecs,
20
20
  getStyleSchemaFromSpecs,
21
21
  } from "../schema";
22
+
22
23
  import { Heading } from "./HeadingBlockContent/HeadingBlockContent";
23
- import { Image } from "./ImageBlockContent/ImageBlockContent";
24
24
  import { BulletListItem } from "./ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent";
25
25
  import { NumberedListItem } from "./ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent";
26
+ import { CheckListItem } from "./ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent";
26
27
  import { Paragraph } from "./ParagraphBlockContent/ParagraphBlockContent";
27
28
  import { Table } from "./TableBlockContent/TableBlockContent";
29
+ import { FileBlock } from "./FileBlockContent/FileBlockContent";
30
+ import { ImageBlock } from "./ImageBlockContent/ImageBlockContent";
31
+ import { VideoBlock } from "./VideoBlockContent/VideoBlockContent";
32
+ import { AudioBlock } from "./AudioBlockContent/AudioBlockContent";
28
33
 
29
34
  export const defaultBlockSpecs = {
30
35
  paragraph: Paragraph,
31
36
  heading: Heading,
32
37
  bulletListItem: BulletListItem,
33
38
  numberedListItem: NumberedListItem,
34
- image: Image,
39
+ checkListItem: CheckListItem,
35
40
  table: Table,
41
+ file: FileBlock,
42
+ image: ImageBlock,
43
+ video: VideoBlock,
44
+ audio: AudioBlock,
36
45
  } satisfies BlockSpecs;
37
46
 
38
47
  export const defaultBlockSchema = getBlockSchemaFromSpecs(defaultBlockSpecs);