@blocknote/core 0.22.0 → 0.23.1

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 (69) hide show
  1. package/dist/blocknote.js +2416 -1808
  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/tsconfig.tsbuildinfo +1 -1
  7. package/dist/webpack-stats.json +1 -1
  8. package/package.json +2 -2
  9. package/src/api/clipboard/__snapshots__/internal/basicBlocks.html +1 -0
  10. package/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +1 -0
  11. package/src/api/clipboard/clipboardInternal.test.ts +126 -0
  12. package/src/api/exporters/html/__snapshots__/pageBreak/basic/external.html +1 -0
  13. package/src/api/exporters/html/__snapshots__/pageBreak/basic/internal.html +1 -0
  14. package/src/api/exporters/markdown/__snapshots__/pageBreak/basic/markdown.md +0 -0
  15. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +16 -0
  16. package/src/api/parsers/html/__snapshots__/parse-codeblocks.json +62 -0
  17. package/src/api/parsers/html/parseHTML.test.ts +9 -0
  18. package/src/api/testUtil/cases/defaultSchema.ts +15 -1
  19. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +32 -11
  20. package/src/blocks/HeadingBlockContent/HeadingBlockContent.ts +0 -9
  21. package/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +1 -1
  22. package/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.ts +1 -1
  23. package/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +1 -1
  24. package/src/blocks/PageBreakBlockContent/PageBreakBlockContent.ts +49 -0
  25. package/src/blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.ts +45 -0
  26. package/src/blocks/PageBreakBlockContent/schema.ts +40 -0
  27. package/src/editor/Block.css +15 -1
  28. package/src/editor/BlockNoteEditor.ts +37 -12
  29. package/src/editor/BlockNoteExtensions.ts +111 -16
  30. package/src/editor/editor.css +22 -7
  31. package/src/extensions/SideMenu/SideMenuPlugin.ts +128 -23
  32. package/src/extensions/SideMenu/dragging.ts +0 -1
  33. package/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts +1 -1
  34. package/src/i18n/locales/ar.ts +6 -0
  35. package/src/i18n/locales/de.ts +6 -0
  36. package/src/i18n/locales/en.ts +6 -0
  37. package/src/i18n/locales/es.ts +6 -0
  38. package/src/i18n/locales/fr.ts +47 -17
  39. package/src/i18n/locales/hr.ts +72 -54
  40. package/src/i18n/locales/index.ts +1 -0
  41. package/src/i18n/locales/is.ts +6 -0
  42. package/src/i18n/locales/it.ts +315 -0
  43. package/src/i18n/locales/ja.ts +6 -0
  44. package/src/i18n/locales/ko.ts +6 -0
  45. package/src/i18n/locales/nl.ts +6 -0
  46. package/src/i18n/locales/pl.ts +6 -0
  47. package/src/i18n/locales/pt.ts +6 -0
  48. package/src/i18n/locales/ru.ts +6 -0
  49. package/src/i18n/locales/vi.ts +6 -0
  50. package/src/i18n/locales/zh.ts +6 -0
  51. package/src/index.ts +3 -0
  52. package/types/src/api/testUtil/cases/defaultSchema.d.ts +2 -1
  53. package/types/src/blocks/CodeBlockContent/CodeBlockContent.d.ts +2 -0
  54. package/types/src/blocks/PageBreakBlockContent/PageBreakBlockContent.d.ts +31 -0
  55. package/types/src/blocks/PageBreakBlockContent/getPageBreakSlashMenuItems.d.ts +8 -0
  56. package/types/src/blocks/PageBreakBlockContent/schema.d.ts +86 -0
  57. package/types/src/editor/BlockNoteEditor.d.ts +18 -2
  58. package/types/src/editor/BlockNoteExtensions.d.ts +2 -0
  59. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +26 -5
  60. package/types/src/extensions/SuggestionMenu/DefaultSuggestionItem.d.ts +1 -1
  61. package/types/src/i18n/locales/de.d.ts +6 -0
  62. package/types/src/i18n/locales/en.d.ts +6 -0
  63. package/types/src/i18n/locales/es.d.ts +6 -0
  64. package/types/src/i18n/locales/hr.d.ts +6 -0
  65. package/types/src/i18n/locales/index.d.ts +1 -0
  66. package/types/src/i18n/locales/it.d.ts +245 -0
  67. package/types/src/index.d.ts +3 -0
  68. package/types/src/pm-nodes/BlockContainer.d.ts +1 -1
  69. package/types/src/pm-nodes/BlockGroup.d.ts +1 -1
@@ -0,0 +1,40 @@
1
+ import { BlockNoteSchema } from "../../editor/BlockNoteSchema.js";
2
+ import {
3
+ BlockSchema,
4
+ InlineContentSchema,
5
+ StyleSchema,
6
+ } from "../../schema/index.js";
7
+ import { PageBreak } from "./PageBreakBlockContent.js";
8
+
9
+ export const pageBreakSchema = BlockNoteSchema.create({
10
+ blockSpecs: {
11
+ pageBreak: PageBreak,
12
+ },
13
+ });
14
+
15
+ /**
16
+ * Adds page break support to the given schema.
17
+ */
18
+ export const withPageBreak = <
19
+ B extends BlockSchema,
20
+ I extends InlineContentSchema,
21
+ S extends StyleSchema
22
+ >(
23
+ schema: BlockNoteSchema<B, I, S>
24
+ ) => {
25
+ return BlockNoteSchema.create({
26
+ blockSpecs: {
27
+ ...schema.blockSpecs,
28
+ ...pageBreakSchema.blockSpecs,
29
+ },
30
+ inlineContentSpecs: schema.inlineContentSpecs,
31
+ styleSpecs: schema.styleSpecs,
32
+ }) as any as BlockNoteSchema<
33
+ // typescript needs some help here
34
+ B & {
35
+ pageBreak: typeof PageBreak.config;
36
+ },
37
+ I,
38
+ S
39
+ >;
40
+ };
@@ -308,6 +308,20 @@ NESTED BLOCKS
308
308
  transition-delay: 0.1s;
309
309
  }
310
310
 
311
+ /* PAGE BREAK */
312
+ .bn-block-content[data-content-type="pageBreak"] > div {
313
+ width: 100%;
314
+ height: 0;
315
+ border-top: dotted rgb(125, 121, 122) 2px;
316
+ margin-block: 11px;
317
+ }
318
+
319
+ @media print {
320
+ .bn-block-content[data-content-type="pageBreak"] > div {
321
+ page-break-after: always;
322
+ }
323
+ }
324
+
311
325
  /* FILES */
312
326
 
313
327
  /* Element that wraps content for all file blocks */
@@ -336,7 +350,7 @@ NESTED BLOCKS
336
350
 
337
351
  .bn-editor[contenteditable="true"] [data-file-block] .bn-add-file-button:hover,
338
352
  [data-file-block] .bn-file-name-with-icon:hover,
339
- .ProseMirror-selectednode .bn-file-name-with-icon{
353
+ .ProseMirror-selectednode .bn-file-name-with-icon {
340
354
  background-color: rgb(225, 225, 225);
341
355
  }
342
356
 
@@ -9,12 +9,6 @@ import {
9
9
  import { Node, Schema } from "prosemirror-model";
10
10
  // import "./blocknote.css";
11
11
  import * as Y from "yjs";
12
- import {
13
- getBlock,
14
- getNextBlock,
15
- getParentBlock,
16
- getPrevBlock,
17
- } from "../api/blockManipulation/getBlock/getBlock.js";
18
12
  import { insertBlocks } from "../api/blockManipulation/commands/insertBlocks/insertBlocks.js";
19
13
  import {
20
14
  moveBlocksDown,
@@ -29,15 +23,21 @@ import {
29
23
  import { removeBlocks } from "../api/blockManipulation/commands/removeBlocks/removeBlocks.js";
30
24
  import { replaceBlocks } from "../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js";
31
25
  import { updateBlock } from "../api/blockManipulation/commands/updateBlock/updateBlock.js";
32
- import { insertContentAt } from "../api/blockManipulation/insertContentAt.js";
33
26
  import {
34
- getTextCursorPosition,
35
- setTextCursorPosition,
36
- } from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js";
27
+ getBlock,
28
+ getNextBlock,
29
+ getParentBlock,
30
+ getPrevBlock,
31
+ } from "../api/blockManipulation/getBlock/getBlock.js";
32
+ import { insertContentAt } from "../api/blockManipulation/insertContentAt.js";
37
33
  import {
38
34
  getSelection,
39
35
  setSelection,
40
36
  } from "../api/blockManipulation/selections/selection.js";
37
+ import {
38
+ getTextCursorPosition,
39
+ setTextCursorPosition,
40
+ } from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js";
41
41
  import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
42
42
  import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
43
43
  import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
@@ -89,11 +89,15 @@ import { en } from "../i18n/locales/index.js";
89
89
 
90
90
  import { Plugin, Transaction } from "@tiptap/pm/state";
91
91
  import { dropCursor } from "prosemirror-dropcursor";
92
+ import { EditorView } from "prosemirror-view";
92
93
  import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js";
93
94
  import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js";
94
95
  import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js";
95
96
  import "../style.css";
96
- import { EditorView } from "prosemirror-view";
97
+
98
+ export type BlockNoteExtensionFactory = (
99
+ editor: BlockNoteEditor<any, any, any>
100
+ ) => BlockNoteExtension;
97
101
 
98
102
  export type BlockNoteExtension =
99
103
  | AnyExtension
@@ -196,6 +200,13 @@ export type BlockNoteEditorOptions<
196
200
  * Optional function to customize how cursors of users are rendered
197
201
  */
198
202
  renderCursor?: (user: any) => HTMLElement;
203
+ /**
204
+ * Optional flag to set when the user label should be shown with the default
205
+ * collaboration cursor. Setting to "always" will always show the label,
206
+ * while "activity" will only show the label when the user moves the cursor
207
+ * or types. Defaults to "activity".
208
+ */
209
+ showCursorLabels?: "always" | "activity";
199
210
  };
200
211
 
201
212
  /**
@@ -206,7 +217,7 @@ export type BlockNoteEditorOptions<
206
217
  /**
207
218
  * (experimental) add extra prosemirror plugins or tiptap extensions to the editor
208
219
  */
209
- _extensions: Record<string, BlockNoteExtension>;
220
+ _extensions: Record<string, BlockNoteExtension | BlockNoteExtensionFactory>;
210
221
 
211
222
  trailingBlock?: boolean;
212
223
 
@@ -245,6 +256,15 @@ export type BlockNoteEditorOptions<
245
256
  @default "prefer-navigate-ui"
246
257
  */
247
258
  tabBehavior: "prefer-navigate-ui" | "prefer-indent";
259
+
260
+ /**
261
+ * The detection mode for showing the side menu - "viewport" always shows the
262
+ * side menu for the block next to the mouse cursor, while "editor" only shows
263
+ * it when hovering the editor or the side menu itself.
264
+ *
265
+ * @default "viewport"
266
+ */
267
+ sideMenuDetection: "viewport" | "editor";
248
268
  };
249
269
 
250
270
  const blockNoteTipTapOptions = {
@@ -423,6 +443,7 @@ export class BlockNoteEditor<
423
443
  dropCursor: this.options.dropCursor ?? dropCursor,
424
444
  placeholders: newOptions.placeholders,
425
445
  tabBehavior: newOptions.tabBehavior,
446
+ sideMenuDetection: newOptions.sideMenuDetection || "viewport",
426
447
  });
427
448
 
428
449
  // add extensions from _tiptapOptions
@@ -432,6 +453,10 @@ export class BlockNoteEditor<
432
453
 
433
454
  // add extensions from options
434
455
  Object.entries(newOptions._extensions || {}).forEach(([key, ext]) => {
456
+ if (typeof ext === "function") {
457
+ // factory
458
+ ext = ext(this);
459
+ }
435
460
  this.extensions[key] = ext;
436
461
  });
437
462
 
@@ -1,4 +1,5 @@
1
1
  import { AnyExtension, Extension, extensions } from "@tiptap/core";
2
+ import { Awareness } from "y-protocols/awareness";
2
3
 
3
4
  import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
4
5
 
@@ -64,6 +65,7 @@ type ExtensionOptions<
64
65
  };
65
66
  provider: any;
66
67
  renderCursor?: (user: any) => HTMLElement;
68
+ showCursorLabels?: "always" | "activity";
67
69
  };
68
70
  disableExtensions: string[] | undefined;
69
71
  setIdAttribute?: boolean;
@@ -72,6 +74,7 @@ type ExtensionOptions<
72
74
  dropCursor: (opts: any) => Plugin;
73
75
  placeholders: Record<string | "default", string>;
74
76
  tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
77
+ sideMenuDetection: "viewport" | "editor";
75
78
  };
76
79
 
77
80
  /**
@@ -97,7 +100,10 @@ export const getBlockNoteExtensions = <
97
100
  opts.editor
98
101
  );
99
102
  ret["linkToolbar"] = new LinkToolbarProsemirrorPlugin(opts.editor);
100
- ret["sideMenu"] = new SideMenuProsemirrorPlugin(opts.editor);
103
+ ret["sideMenu"] = new SideMenuProsemirrorPlugin(
104
+ opts.editor,
105
+ opts.sideMenuDetection
106
+ );
101
107
  ret["suggestionMenus"] = new SuggestionMenuProseMirrorPlugin(opts.editor);
102
108
  ret["filePanel"] = new FilePanelProsemirrorPlugin(opts.editor as any);
103
109
  ret["placeholder"] = new PlaceholderPlugin(opts.editor, opts.placeholders);
@@ -246,25 +252,114 @@ const getTipTapExtensions = <
246
252
  fragment: opts.collaboration.fragment,
247
253
  })
248
254
  );
249
- if (opts.collaboration.provider?.awareness) {
250
- const defaultRender = (user: { color: string; name: string }) => {
251
- const cursor = document.createElement("span");
252
255
 
253
- cursor.classList.add("collaboration-cursor__caret");
254
- cursor.setAttribute("style", `border-color: ${user.color}`);
256
+ const awareness = opts.collaboration?.provider.awareness as Awareness;
257
+
258
+ if (awareness) {
259
+ const cursors = new Map<
260
+ number,
261
+ { element: HTMLElement; hideTimeout: NodeJS.Timeout | undefined }
262
+ >();
263
+
264
+ if (opts.collaboration.showCursorLabels !== "always") {
265
+ awareness.on(
266
+ "change",
267
+ ({
268
+ updated,
269
+ }: {
270
+ added: Array<number>;
271
+ updated: Array<number>;
272
+ removed: Array<number>;
273
+ }) => {
274
+ for (const clientID of updated) {
275
+ const cursor = cursors.get(clientID);
276
+
277
+ if (cursor) {
278
+ cursor.element.setAttribute("data-active", "");
279
+
280
+ if (cursor.hideTimeout) {
281
+ clearTimeout(cursor.hideTimeout);
282
+ }
283
+
284
+ cursors.set(clientID, {
285
+ element: cursor.element,
286
+ hideTimeout: setTimeout(() => {
287
+ cursor.element.removeAttribute("data-active");
288
+ }, 2000),
289
+ });
290
+ }
291
+ }
292
+ }
293
+ );
294
+ }
295
+
296
+ const createCursor = (clientID: number, name: string, color: string) => {
297
+ const cursorElement = document.createElement("span");
298
+
299
+ cursorElement.classList.add("collaboration-cursor__caret");
300
+ cursorElement.setAttribute("style", `border-color: ${color}`);
301
+ if (opts.collaboration?.showCursorLabels === "always") {
302
+ cursorElement.setAttribute("data-active", "");
303
+ }
304
+
305
+ const labelElement = document.createElement("span");
306
+
307
+ labelElement.classList.add("collaboration-cursor__label");
308
+ labelElement.setAttribute("style", `background-color: ${color}`);
309
+ labelElement.insertBefore(document.createTextNode(name), null);
310
+
311
+ cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space
312
+ cursorElement.insertBefore(labelElement, null);
313
+ cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space
314
+
315
+ cursors.set(clientID, {
316
+ element: cursorElement,
317
+ hideTimeout: undefined,
318
+ });
319
+
320
+ if (opts.collaboration?.showCursorLabels !== "always") {
321
+ cursorElement.addEventListener("mouseenter", () => {
322
+ const cursor = cursors.get(clientID)!;
323
+ cursor.element.setAttribute("data-active", "");
324
+
325
+ if (cursor.hideTimeout) {
326
+ clearTimeout(cursor.hideTimeout);
327
+ cursors.set(clientID, {
328
+ element: cursor.element,
329
+ hideTimeout: undefined,
330
+ });
331
+ }
332
+ });
333
+
334
+ cursorElement.addEventListener("mouseleave", () => {
335
+ const cursor = cursors.get(clientID)!;
336
+
337
+ cursors.set(clientID, {
338
+ element: cursor.element,
339
+ hideTimeout: setTimeout(() => {
340
+ cursor.element.removeAttribute("data-active");
341
+ }, 2000),
342
+ });
343
+ });
344
+ }
345
+
346
+ return cursors.get(clientID)!;
347
+ };
348
+
349
+ const defaultRender = (user: { color: string; name: string }) => {
350
+ const clientState = [...awareness.getStates().entries()].find(
351
+ (state) => state[1].user === user
352
+ );
255
353
 
256
- const label = document.createElement("span");
354
+ if (!clientState) {
355
+ throw new Error("Could not find client state for user");
356
+ }
257
357
 
258
- label.classList.add("collaboration-cursor__label");
259
- label.setAttribute("style", `background-color: ${user.color}`);
260
- label.insertBefore(document.createTextNode(user.name), null);
358
+ const clientID = clientState[0];
261
359
 
262
- const nonbreakingSpace1 = document.createTextNode("\u2060");
263
- const nonbreakingSpace2 = document.createTextNode("\u2060");
264
- cursor.insertBefore(nonbreakingSpace1, null);
265
- cursor.insertBefore(label, null);
266
- cursor.insertBefore(nonbreakingSpace2, null);
267
- return cursor;
360
+ return (
361
+ cursors.get(clientID) || createCursor(clientID, user.name, user.color)
362
+ ).element;
268
363
  };
269
364
  tiptapExtensions.push(
270
365
  CollaborationCursor.configure({
@@ -83,7 +83,6 @@ Tippy popups that are appended to document.body directly
83
83
  border-right: 1px solid #0d0d0d;
84
84
  margin-left: -1px;
85
85
  margin-right: -1px;
86
- pointer-events: none;
87
86
  position: relative;
88
87
  word-break: normal;
89
88
  white-space: nowrap !important;
@@ -92,17 +91,33 @@ Tippy popups that are appended to document.body directly
92
91
  /* Render the username above the caret */
93
92
  .collaboration-cursor__label {
94
93
  border-radius: 3px 3px 3px 0;
95
- color: #0d0d0d;
96
94
  font-size: 12px;
97
95
  font-style: normal;
98
96
  font-weight: 600;
99
- left: -1px;
100
97
  line-height: normal;
101
- padding: 0.1rem 0.3rem;
98
+ left: -1px;
99
+ overflow: hidden;
102
100
  position: absolute;
103
- top: -1.4em;
104
- user-select: none;
105
101
  white-space: nowrap;
102
+
103
+ color: transparent;
104
+ max-height: 4px;
105
+ max-width: 4px;
106
+ padding: 0;
107
+ top: 0;
108
+
109
+ transition: all 0.2s;
110
+
111
+ }
112
+
113
+ .collaboration-cursor__caret[data-active] > .collaboration-cursor__label {
114
+ color: #0d0d0d;
115
+ max-height: 1.1rem;
116
+ max-width: 20rem;
117
+ padding: 0.1rem 0.3rem;
118
+ top: -14px;
119
+
120
+ transition: all 0.2s;
106
121
  }
107
122
 
108
123
  /* .tableWrapper {
@@ -134,7 +149,7 @@ Tippy popups that are appended to document.body directly
134
149
  .bn-editor [data-content-type="table"] th,
135
150
  .bn-editor [data-content-type="table"] td {
136
151
  border: 1px solid #ddd;
137
- padding: 3px 5px;
152
+ padding: 5px 10px;
138
153
  }
139
154
 
140
155
  .bn-editor [data-content-type="table"] th {