@blocknote/core 0.26.0 → 0.27.0

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 (146) hide show
  1. package/dist/blocknote.cjs +11 -10
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +3685 -9960
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/comments.cjs.map +1 -1
  6. package/dist/comments.js.map +1 -1
  7. package/dist/en-B7ycW7c8.js +360 -0
  8. package/dist/en-B7ycW7c8.js.map +1 -0
  9. package/dist/en-D4taoCs4.cjs +2 -0
  10. package/dist/en-D4taoCs4.cjs.map +1 -0
  11. package/dist/locales.cjs +2 -0
  12. package/dist/locales.cjs.map +1 -0
  13. package/dist/locales.js +6129 -0
  14. package/dist/locales.js.map +1 -0
  15. package/dist/style.css +1 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/dist/webpack-stats.json +1 -1
  18. package/package.json +36 -27
  19. package/src/api/clipboard/__snapshots__/internal/basicBlocks.html +1 -1
  20. package/src/api/clipboard/__snapshots__/internal/basicBlocksWithProps.html +1 -1
  21. package/src/api/clipboard/clipboardExternal.test.ts +1 -1
  22. package/src/api/clipboard/clipboardInternal.test.ts +1 -1
  23. package/src/api/clipboard/fromClipboard/acceptedMIMETypes.ts +1 -0
  24. package/src/api/clipboard/fromClipboard/pasteExtension.ts +96 -42
  25. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/external.html +1 -1
  26. package/src/api/exporters/html/__snapshots__/codeBlock/contains-newlines/internal.html +1 -1
  27. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/external.html +1 -1
  28. package/src/api/exporters/html/__snapshots__/codeBlock/defaultLanguage/internal.html +1 -1
  29. package/src/api/exporters/html/__snapshots__/codeBlock/empty/external.html +1 -1
  30. package/src/api/exporters/html/__snapshots__/codeBlock/empty/internal.html +1 -1
  31. package/src/api/exporters/html/__snapshots__/codeBlock/python/external.html +1 -1
  32. package/src/api/exporters/html/__snapshots__/codeBlock/python/internal.html +1 -1
  33. package/src/api/exporters/html/htmlConversion.test.ts +2 -2
  34. package/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +4 -4
  35. package/src/api/exporters/markdown/__snapshots__/codeBlock/defaultLanguage/markdown.md +1 -1
  36. package/src/api/exporters/markdown/__snapshots__/codeBlock/empty/markdown.md +1 -1
  37. package/src/api/exporters/markdown/__snapshots__/complex/misc/markdown.md +1 -1
  38. package/src/api/exporters/markdown/__snapshots__/lists/basic/markdown.md +8 -6
  39. package/src/api/exporters/markdown/__snapshots__/lists/nested/markdown.md +6 -6
  40. package/src/api/exporters/markdown/markdownExporter.test.ts +2 -2
  41. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +2 -2
  42. package/src/api/parsers/html/__snapshots__/parse-codeblocks.json +1 -1
  43. package/src/api/parsers/html/parseHTML.test.ts +1 -1
  44. package/src/api/parsers/markdown/__snapshots__/pasted/whitespace bold.json +42 -0
  45. package/src/api/parsers/markdown/__snapshots__/whitespace bold.json +19 -0
  46. package/src/api/parsers/markdown/detectMarkdown.ts +60 -0
  47. package/src/api/parsers/markdown/parseMarkdown.test.ts +7 -2
  48. package/src/api/parsers/markdown/parseMarkdown.ts +19 -18
  49. package/src/api/testUtil/cases/defaultSchema.ts +13 -0
  50. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +100 -69
  51. package/src/blocks/QuoteBlockContent/QuoteBlockContent.ts +98 -0
  52. package/src/blocks/TableBlockContent/TableExtension.ts +1 -1
  53. package/src/blocks/defaultBlocks.ts +2 -2
  54. package/src/editor/Block.css +13 -0
  55. package/src/editor/BlockNoteEditor.ts +102 -10
  56. package/src/editor/BlockNoteExtensions.ts +18 -4
  57. package/src/extensions/Comments/CommentsPlugin.ts +1 -1
  58. package/src/extensions/HardBreak/HardBreak.ts +35 -0
  59. package/src/extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts +100 -3
  60. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +3 -1
  61. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +12 -0
  62. package/src/i18n/index.ts +2 -0
  63. package/src/i18n/locales/ar.ts +6 -0
  64. package/src/i18n/locales/de.ts +6 -0
  65. package/src/i18n/locales/en.ts +6 -0
  66. package/src/i18n/locales/es.ts +6 -0
  67. package/src/i18n/locales/fr.ts +6 -0
  68. package/src/i18n/locales/hr.ts +6 -0
  69. package/src/i18n/locales/is.ts +6 -0
  70. package/src/i18n/locales/it.ts +6 -0
  71. package/src/i18n/locales/ja.ts +6 -0
  72. package/src/i18n/locales/ko.ts +6 -0
  73. package/src/i18n/locales/nl.ts +6 -0
  74. package/src/i18n/locales/no.ts +6 -0
  75. package/src/i18n/locales/pl.ts +6 -0
  76. package/src/i18n/locales/pt.ts +6 -0
  77. package/src/i18n/locales/ru.ts +6 -0
  78. package/src/i18n/locales/uk.ts +6 -0
  79. package/src/i18n/locales/vi.ts +6 -0
  80. package/src/i18n/locales/zh.ts +6 -0
  81. package/src/index.ts +2 -3
  82. package/src/locales.ts +1 -0
  83. package/src/schema/blocks/types.ts +1 -0
  84. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +1 -1
  85. package/types/src/api/blockManipulation/setupTestEnv.d.ts +34 -2
  86. package/types/src/api/clipboard/fromClipboard/acceptedMIMETypes.d.ts +1 -1
  87. package/types/src/api/clipboard/fromClipboard/fileDropExtension.d.ts +2 -2
  88. package/types/src/api/clipboard/fromClipboard/pasteExtension.d.ts +3 -3
  89. package/types/src/api/clipboard/testUtil.d.ts +66 -34
  90. package/types/src/api/clipboard/toClipboard/copyExtension.d.ts +1 -1
  91. package/types/src/api/exporters/html/externalHTMLExporter.d.ts +2 -2
  92. package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +2 -2
  93. package/types/src/api/exporters/html/util/serializeBlocksExternalHTML.d.ts +1 -1
  94. package/types/src/api/exporters/html/util/serializeBlocksInternalHTML.d.ts +1 -1
  95. package/types/src/api/parsers/markdown/detectMarkdown.d.ts +6 -0
  96. package/types/src/api/parsers/markdown/parseMarkdown.d.ts +1 -0
  97. package/types/src/api/testUtil/cases/customBlocks.d.ts +72 -40
  98. package/types/src/api/testUtil/cases/customInlineContent.d.ts +34 -2
  99. package/types/src/api/testUtil/cases/customStyles.d.ts +34 -2
  100. package/types/src/blocks/AudioBlockContent/AudioBlockContent.d.ts +3 -3
  101. package/types/src/blocks/CodeBlockContent/CodeBlockContent.d.ts +46 -34
  102. package/types/src/blocks/FileBlockContent/FileBlockContent.d.ts +3 -3
  103. package/types/src/blocks/FileBlockContent/helpers/render/createFileBlockWrapper.d.ts +1 -1
  104. package/types/src/blocks/HeadingBlockContent/HeadingBlockContent.d.ts +2 -2
  105. package/types/src/blocks/ImageBlockContent/ImageBlockContent.d.ts +2 -2
  106. package/types/src/blocks/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.d.ts +2 -2
  107. package/types/src/blocks/ListItemBlockContent/CheckListItemBlockContent/CheckListItemBlockContent.d.ts +2 -2
  108. package/types/src/blocks/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.d.ts +2 -2
  109. package/types/src/blocks/PageBreakBlockContent/PageBreakBlockContent.d.ts +2 -2
  110. package/types/src/blocks/PageBreakBlockContent/schema.d.ts +13 -13
  111. package/types/src/blocks/ParagraphBlockContent/ParagraphBlockContent.d.ts +2 -2
  112. package/types/src/blocks/QuoteBlockContent/QuoteBlockContent.d.ts +52 -0
  113. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +2 -2
  114. package/types/src/blocks/VideoBlockContent/VideoBlockContent.d.ts +2 -2
  115. package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
  116. package/types/src/blocks/defaultBlocks.d.ts +107 -44
  117. package/types/src/comments/threadstore/yjs/YjsThreadStore.d.ts +3 -3
  118. package/types/src/editor/BlockNoteEditor.d.ts +58 -0
  119. package/types/src/editor/BlockNoteExtensions.d.ts +3 -2
  120. package/types/src/exporter/mapping.d.ts +2 -2
  121. package/types/src/extensions/BackgroundColor/BackgroundColorMark.d.ts +1 -1
  122. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +1 -1
  123. package/types/src/extensions/HardBreak/HardBreak.d.ts +2 -0
  124. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +4 -4
  125. package/types/src/extensions/TextColor/TextColorMark.d.ts +1 -1
  126. package/types/src/i18n/index.d.ts +2 -0
  127. package/types/src/i18n/locales/en.d.ts +7 -1
  128. package/types/src/index.d.ts +1 -2
  129. package/types/src/locales.d.ts +1 -0
  130. package/types/src/pm-nodes/BlockContainer.d.ts +2 -7
  131. package/types/src/pm-nodes/BlockGroup.d.ts +2 -7
  132. package/types/src/schema/blocks/types.d.ts +1 -0
  133. package/types/src/schema/inlineContent/internal.d.ts +1 -1
  134. package/README.md +0 -125
  135. package/src/blocks/CodeBlockContent/defaultSupportedLanguages.ts +0 -116
  136. package/types/src/extensions/Comments/threadstore/DefaultThreadStoreAuth.d.ts +0 -47
  137. package/types/src/extensions/Comments/threadstore/ThreadStore.d.ts +0 -121
  138. package/types/src/extensions/Comments/threadstore/ThreadStoreAuth.d.ts +0 -12
  139. package/types/src/extensions/Comments/threadstore/TipTapThreadStore.d.ts +0 -97
  140. package/types/src/extensions/Comments/threadstore/yjs/RESTYjsThreadStore.d.ts +0 -83
  141. package/types/src/extensions/Comments/threadstore/yjs/YjsThreadStore.d.ts +0 -79
  142. package/types/src/extensions/Comments/threadstore/yjs/YjsThreadStore.test.d.ts +0 -1
  143. package/types/src/extensions/Comments/threadstore/yjs/YjsThreadStoreBase.d.ts +0 -15
  144. package/types/src/extensions/Comments/threadstore/yjs/yjsHelpers.d.ts +0 -13
  145. package/types/src/extensions/Comments/types.d.ts +0 -109
  146. package/types/src/models/User.d.ts +0 -5
@@ -37,6 +37,10 @@ BASIC STYLES
37
37
  outline: 4px solid rgb(100, 160, 255);
38
38
  }
39
39
 
40
+ .bn-inline-content {
41
+ width: 100%;
42
+ }
43
+
40
44
  /*
41
45
  NESTED BLOCKS
42
46
  */
@@ -146,6 +150,14 @@ NESTED BLOCKS
146
150
  font-weight: bold;
147
151
  }
148
152
 
153
+ /* QUOTES */
154
+ [data-content-type="quote"] blockquote {
155
+ border-left: 2px solid rgb(125, 121, 122);
156
+ color: rgb(125, 121, 122);
157
+ margin: 0;
158
+ padding-left: 1em;
159
+ }
160
+
149
161
  /* LISTS */
150
162
 
151
163
  .bn-block-content::before {
@@ -188,6 +200,7 @@ NESTED BLOCKS
188
200
  /* Checked */
189
201
  .bn-block-content[data-content-type="checkListItem"] > div {
190
202
  display: flex;
203
+ width: 100%;
191
204
  }
192
205
 
193
206
  .bn-block-content[data-content-type="checkListItem"] > div > div > input {
@@ -43,7 +43,10 @@ import {
43
43
  import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
44
44
  import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
45
45
  import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
46
- import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown.js";
46
+ import {
47
+ markdownToBlocks,
48
+ markdownToHTML,
49
+ } from "../api/parsers/markdown/parseMarkdown.js";
47
50
  import {
48
51
  Block,
49
52
  DefaultBlockSchema,
@@ -91,7 +94,7 @@ import {
91
94
  import { Dictionary } from "../i18n/dictionary.js";
92
95
  import { en } from "../i18n/locales/index.js";
93
96
 
94
- import { Plugin, Transaction } from "@tiptap/pm/state";
97
+ import { Plugin, TextSelection, Transaction } from "@tiptap/pm/state";
95
98
  import { dropCursor } from "prosemirror-dropcursor";
96
99
  import { EditorView } from "prosemirror-view";
97
100
  import { ySyncPluginKey } from "y-prosemirror";
@@ -101,6 +104,8 @@ import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js";
101
104
  import type { ThreadStore, User } from "../comments/index.js";
102
105
  import "../style.css";
103
106
  import { EventEmitter } from "../util/EventEmitter.js";
107
+ import { CodeBlockOptions } from "../blocks/CodeBlockContent/CodeBlockContent.js";
108
+ import { nestedListsToBlockNoteStructure } from "../api/parsers/html/util/nestedLists.js";
104
109
 
105
110
  export type BlockNoteExtensionFactory = (
106
111
  editor: BlockNoteEditor<any, any, any>
@@ -156,6 +161,11 @@ export type BlockNoteEditorOptions<
156
161
  showCursorLabels?: "always" | "activity";
157
162
  };
158
163
 
164
+ /**
165
+ * Options for code blocks.
166
+ */
167
+ codeBlock?: CodeBlockOptions;
168
+
159
169
  comments: {
160
170
  threadStore: ThreadStore;
161
171
  };
@@ -212,6 +222,39 @@ export type BlockNoteEditorOptions<
212
222
  string | undefined
213
223
  >;
214
224
 
225
+ /**
226
+ * Custom paste handler that can be used to override the default paste behavior.
227
+ * @returns The function should return `true` if the paste event was handled, otherwise it should return `false` if it should be canceled or `undefined` if it should be handled by another handler.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * pasteHandler: ({ defaultPasteHandler }) => {
232
+ * return defaultPasteHandler({ pasteBehavior: "prefer-html" });
233
+ * }
234
+ * ```
235
+ */
236
+ pasteHandler?: (context: {
237
+ event: ClipboardEvent;
238
+ editor: BlockNoteEditor<BSchema, ISchema, SSchema>;
239
+ /**
240
+ * The default paste handler
241
+ * @param context The context object
242
+ * @returns Whether the paste event was handled or not
243
+ */
244
+ defaultPasteHandler: (context?: {
245
+ /**
246
+ * Whether to prioritize Markdown content in `text/plain` over `text/html` when pasting from the clipboard.
247
+ * @default true
248
+ */
249
+ prioritizeMarkdownOverHTML?: boolean;
250
+ /**
251
+ * Whether to parse `text/plain` content from the clipboard as Markdown content.
252
+ * @default true
253
+ */
254
+ plainTextAsMarkdown?: boolean;
255
+ }) => boolean | undefined;
256
+ }) => boolean | undefined;
257
+
215
258
  /**
216
259
  * Resolve a URL of a file block to one that can be displayed or downloaded. This can be used for creating authenticated URL or
217
260
  * implementing custom protocols / schemes
@@ -442,6 +485,7 @@ export class BlockNoteEditor<
442
485
  cellTextColor: boolean;
443
486
  headers: boolean;
444
487
  };
488
+ codeBlock: CodeBlockOptions;
445
489
  };
446
490
 
447
491
  public static create<
@@ -489,6 +533,12 @@ export class BlockNoteEditor<
489
533
  cellTextColor: options?.tables?.cellTextColor ?? false,
490
534
  headers: options?.tables?.headers ?? false,
491
535
  },
536
+ codeBlock: {
537
+ indentLineWithTab: options?.codeBlock?.indentLineWithTab ?? true,
538
+ defaultLanguage: options?.codeBlock?.defaultLanguage ?? "text",
539
+ supportedLanguages: options?.codeBlock?.supportedLanguages ?? {},
540
+ createHighlighter: options?.codeBlock?.createHighlighter ?? undefined,
541
+ },
492
542
  };
493
543
 
494
544
  // apply defaults
@@ -532,6 +582,7 @@ export class BlockNoteEditor<
532
582
  tabBehavior: newOptions.tabBehavior,
533
583
  sideMenuDetection: newOptions.sideMenuDetection || "viewport",
534
584
  comments: newOptions.comments,
585
+ pasteHandler: newOptions.pasteHandler,
535
586
  });
536
587
 
537
588
  // add extensions from _tiptapOptions
@@ -1136,17 +1187,18 @@ export class BlockNoteEditor<
1136
1187
  }
1137
1188
 
1138
1189
  const { from, to } = this._tiptapEditor.state.selection;
1139
-
1140
- if (!text) {
1141
- text = this._tiptapEditor.state.doc.textBetween(from, to);
1142
- }
1143
-
1144
1190
  const mark = this.pmSchema.mark("link", { href: url });
1145
1191
 
1146
1192
  this.dispatch(
1147
- this._tiptapEditor.state.tr
1148
- .insertText(text, from, to)
1149
- .addMark(from, from + text.length, mark)
1193
+ text
1194
+ ? this._tiptapEditor.state.tr
1195
+ .insertText(text, from, to)
1196
+ .addMark(from, from + text.length, mark)
1197
+ : this._tiptapEditor.state.tr
1198
+ .setSelection(
1199
+ TextSelection.create(this._tiptapEditor.state.tr.doc, to)
1200
+ )
1201
+ .addMark(from, to, mark)
1150
1202
  );
1151
1203
  }
1152
1204
 
@@ -1431,4 +1483,44 @@ export class BlockNoteEditor<
1431
1483
  public setForceSelectionVisible(forceSelectionVisible: boolean) {
1432
1484
  this.showSelectionPlugin.setEnabled(forceSelectionVisible);
1433
1485
  }
1486
+
1487
+ /**
1488
+ * This will convert HTML into a format that is compatible with BlockNote.
1489
+ */
1490
+ private convertHtmlToBlockNoteHtml(html: string) {
1491
+ const htmlNode = nestedListsToBlockNoteStructure(html.trim());
1492
+ return htmlNode.innerHTML;
1493
+ }
1494
+
1495
+ /**
1496
+ * Paste HTML into the editor. Defaults to converting HTML to BlockNote HTML.
1497
+ * @param html The HTML to paste.
1498
+ * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML.
1499
+ */
1500
+ public pasteHTML(html: string, raw = false) {
1501
+ let htmlToPaste = html;
1502
+ if (!raw) {
1503
+ htmlToPaste = this.convertHtmlToBlockNoteHtml(html);
1504
+ }
1505
+ if (!htmlToPaste) {
1506
+ return;
1507
+ }
1508
+ this.prosemirrorView?.pasteHTML(htmlToPaste);
1509
+ }
1510
+
1511
+ /**
1512
+ * Paste text into the editor. Defaults to interpreting text as markdown.
1513
+ * @param text The text to paste.
1514
+ */
1515
+ public pasteText(text: string) {
1516
+ return this.prosemirrorView?.pasteText(text);
1517
+ }
1518
+
1519
+ /**
1520
+ * Paste markdown into the editor.
1521
+ * @param markdown The markdown to paste.
1522
+ */
1523
+ public async pasteMarkdown(markdown: string) {
1524
+ return this.pasteHTML(await markdownToHTML(markdown));
1525
+ }
1434
1526
  }
@@ -1,6 +1,5 @@
1
1
  import { AnyExtension, Extension, extensions } from "@tiptap/core";
2
2
  import { Gapcursor } from "@tiptap/extension-gapcursor";
3
- import { HardBreak } from "@tiptap/extension-hard-break";
4
3
  import { History } from "@tiptap/extension-history";
5
4
  import { Link } from "@tiptap/extension-link";
6
5
  import { Text } from "@tiptap/extension-text";
@@ -17,6 +16,7 @@ import { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js";
17
16
  import type { ThreadStore } from "../comments/index.js";
18
17
  import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
19
18
  import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
19
+ import { HardBreak } from "../extensions/HardBreak/HardBreak.js";
20
20
  import { KeyboardShortcutsExtension } from "../extensions/KeyboardShortcuts/KeyboardShortcutsExtension.js";
21
21
  import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
22
22
  import {
@@ -44,7 +44,11 @@ import {
44
44
  StyleSchema,
45
45
  StyleSpecs,
46
46
  } from "../schema/index.js";
47
- import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
47
+ import type {
48
+ BlockNoteEditor,
49
+ BlockNoteEditorOptions,
50
+ BlockNoteExtension,
51
+ } from "./BlockNoteEditor.js";
48
52
 
49
53
  type ExtensionOptions<
50
54
  BSchema extends BlockSchema,
@@ -82,6 +86,7 @@ type ExtensionOptions<
82
86
  comments?: {
83
87
  threadStore: ThreadStore;
84
88
  };
89
+ pasteHandler: BlockNoteEditorOptions<any, any, any>["pasteHandler"];
85
90
  };
86
91
 
87
92
  /**
@@ -179,7 +184,7 @@ const getTipTapExtensions = <
179
184
  types: ["blockContainer", "columnList", "column"],
180
185
  setIdAttribute: opts.setIdAttribute,
181
186
  }),
182
- HardBreak.extend({ priority: 10 }),
187
+ HardBreak,
183
188
  // Comments,
184
189
 
185
190
  // basics:
@@ -258,7 +263,16 @@ const getTipTapExtensions = <
258
263
  ];
259
264
  }),
260
265
  createCopyToClipboardExtension(opts.editor),
261
- createPasteFromClipboardExtension(opts.editor),
266
+ createPasteFromClipboardExtension(
267
+ opts.editor,
268
+ opts.pasteHandler ||
269
+ ((context: {
270
+ defaultPasteHandler: (context?: {
271
+ prioritizeMarkdownOverHTML?: boolean;
272
+ plainTextAsMarkdown?: boolean;
273
+ }) => boolean | undefined;
274
+ }) => context.defaultPasteHandler())
275
+ ),
262
276
  createDropFileExtension(opts.editor),
263
277
 
264
278
  // This needs to be at the bottom of this list, because Key events (such as enter, when selecting a /command),
@@ -106,7 +106,7 @@ export class CommentsPlugin extends EventEmitter<any> {
106
106
  pos + node.nodeSize,
107
107
  ttEditor.state.doc.content.size - 1
108
108
  );
109
- tr.removeMark(trimmedFrom, trimmedTo, markType);
109
+ tr.removeMark(trimmedFrom, trimmedTo, mark);
110
110
  tr.addMark(
111
111
  trimmedFrom,
112
112
  trimmedTo,
@@ -0,0 +1,35 @@
1
+ // Stripped down version of the TipTap HardBreak extension:
2
+ // https://github.com/ueberdosis/tiptap/blob/f3258d9ee5fb7979102fe63434f6ea4120507311/packages/extension-hard-break/src/hard-break.ts#L80
3
+ // Changes:
4
+ // - Removed options
5
+ // - Removed keyboard shortcuts & moved them to the `KeyboardShortcutsExtension`
6
+ // - Removed `setHardBreak` command (added a simpler version in the Shift+Enter
7
+ // handler in `KeyboardShortcutsExtension`).
8
+ // - Added priority
9
+ import { mergeAttributes, Node } from "@tiptap/core";
10
+
11
+ export const HardBreak = Node.create({
12
+ name: "hardBreak",
13
+
14
+ inline: true,
15
+
16
+ group: "inline",
17
+
18
+ selectable: false,
19
+
20
+ linebreakReplacement: true,
21
+
22
+ priority: 10,
23
+
24
+ parseHTML() {
25
+ return [{ tag: "br" }];
26
+ },
27
+
28
+ renderHTML({ HTMLAttributes }) {
29
+ return ["br", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
30
+ },
31
+
32
+ renderText() {
33
+ return "\n";
34
+ },
35
+ });
@@ -260,6 +260,73 @@ export const KeyboardShortcutsExtension = Extension.create<{
260
260
 
261
261
  return true;
262
262
  }),
263
+ // Deletes the current block if it's an empty block with inline content,
264
+ // and moves the selection to the previous block.
265
+ () =>
266
+ commands.command(({ state }) => {
267
+ const blockInfo = getBlockInfoFromSelection(state);
268
+ if (!blockInfo.isBlockContainer) {
269
+ return false;
270
+ }
271
+
272
+ const blockEmpty =
273
+ blockInfo.blockContent.node.childCount === 0 &&
274
+ blockInfo.blockContent.node.type.spec.content === "inline*";
275
+
276
+ if (blockEmpty) {
277
+ const prevBlockInfo = getPrevBlockInfo(
278
+ state.doc,
279
+ blockInfo.bnBlock.beforePos
280
+ );
281
+ if (!prevBlockInfo || !prevBlockInfo.isBlockContainer) {
282
+ return false;
283
+ }
284
+
285
+ let chainedCommands = chain();
286
+
287
+ if (
288
+ prevBlockInfo.blockContent.node.type.spec.content ===
289
+ "tableRow+"
290
+ ) {
291
+ const tableBlockEndPos = blockInfo.bnBlock.beforePos - 1;
292
+ const tableBlockContentEndPos = tableBlockEndPos - 1;
293
+ const lastRowEndPos = tableBlockContentEndPos - 1;
294
+ const lastCellEndPos = lastRowEndPos - 1;
295
+ const lastCellParagraphEndPos = lastCellEndPos - 1;
296
+
297
+ chainedCommands = chainedCommands.setTextSelection(
298
+ lastCellParagraphEndPos
299
+ );
300
+ } else if (
301
+ prevBlockInfo.blockContent.node.type.spec.content === ""
302
+ ) {
303
+ const nonEditableBlockContentStartPos =
304
+ prevBlockInfo.blockContent.afterPos -
305
+ prevBlockInfo.blockContent.node.nodeSize;
306
+
307
+ chainedCommands = chainedCommands.setNodeSelection(
308
+ nonEditableBlockContentStartPos
309
+ );
310
+ } else {
311
+ const blockContentStartPos =
312
+ prevBlockInfo.blockContent.afterPos -
313
+ prevBlockInfo.blockContent.node.nodeSize;
314
+
315
+ chainedCommands =
316
+ chainedCommands.setTextSelection(blockContentStartPos);
317
+ }
318
+
319
+ return chainedCommands
320
+ .deleteRange({
321
+ from: blockInfo.bnBlock.beforePos,
322
+ to: blockInfo.bnBlock.afterPos,
323
+ })
324
+ .scrollIntoView()
325
+ .run();
326
+ }
327
+
328
+ return false;
329
+ }),
263
330
  // Deletes previous block if it contains no content and isn't a table,
264
331
  // when the selection is empty and at the start of the block. Moves the
265
332
  // current block into the deleted block's place.
@@ -370,8 +437,8 @@ export const KeyboardShortcutsExtension = Extension.create<{
370
437
  }),
371
438
  ]);
372
439
 
373
- const handleEnter = () =>
374
- this.editor.commands.first(({ commands }) => [
440
+ const handleEnter = (withShift = false) => {
441
+ return this.editor.commands.first(({ commands }) => [
375
442
  // Removes a level of nesting if the block is empty & indented, while the selection is also empty & at the start
376
443
  // of the block.
377
444
  () =>
@@ -400,6 +467,34 @@ export const KeyboardShortcutsExtension = Extension.create<{
400
467
  return commands.liftListItem("blockContainer");
401
468
  }
402
469
 
470
+ return false;
471
+ }),
472
+ // Creates a hard break if block is configured to do so.
473
+ () =>
474
+ commands.command(({ state }) => {
475
+ const blockInfo = getBlockInfoFromSelection(state);
476
+
477
+ const blockHardBreakShortcut: "shift+enter" | "enter" | "none" =
478
+ this.options.editor.schema.blockSchema[blockInfo.blockNoteType]
479
+ .hardBreakShortcut ?? "shift+enter";
480
+
481
+ if (blockHardBreakShortcut === "none") {
482
+ return false;
483
+ }
484
+
485
+ if (
486
+ // If shortcut is not configured, or is configured as "shift+enter",
487
+ // create a hard break for shift+enter, but not for enter.
488
+ (blockHardBreakShortcut === "shift+enter" && withShift) ||
489
+ // If shortcut is configured as "enter", create a hard break for
490
+ // both enter and shift+enter.
491
+ blockHardBreakShortcut === "enter"
492
+ ) {
493
+ return commands.insertContent({
494
+ type: "hardBreak",
495
+ });
496
+ }
497
+
403
498
  return false;
404
499
  }),
405
500
  // Creates a new block and moves the selection to it if the current one is empty, while the selection is also
@@ -471,11 +566,13 @@ export const KeyboardShortcutsExtension = Extension.create<{
471
566
  return false;
472
567
  }),
473
568
  ]);
569
+ };
474
570
 
475
571
  return {
476
572
  Backspace: handleBackspace,
477
573
  Delete: handleDelete,
478
- Enter: handleEnter,
574
+ Enter: () => handleEnter(),
575
+ "Shift-Enter": () => handleEnter(true),
479
576
  // Always returning true for tab key presses ensures they're not captured by the browser. Otherwise, they blur the
480
577
  // editor since the browser will try to use tab for keyboard navigation.
481
578
  Tab: () => {
@@ -86,7 +86,9 @@ class SuggestionMenuView<
86
86
  this.pluginState = stopped ? prev : next;
87
87
 
88
88
  if (stopped || !this.editor.isEditable) {
89
- this.state!.show = false;
89
+ if (this.state) {
90
+ this.state.show = false;
91
+ }
90
92
  this.emitUpdate(this.pluginState!.triggerCharacter);
91
93
 
92
94
  return;
@@ -125,6 +125,18 @@ export function getDefaultSlashMenuItems<
125
125
  );
126
126
  }
127
127
 
128
+ if (checkDefaultBlockTypeInSchema("quote", editor)) {
129
+ items.push({
130
+ onItemClick: () => {
131
+ insertOrUpdateBlock(editor, {
132
+ type: "quote",
133
+ });
134
+ },
135
+ key: "quote",
136
+ ...editor.dictionary.slash_menu.quote,
137
+ });
138
+ }
139
+
128
140
  if (checkDefaultBlockTypeInSchema("numberedListItem", editor)) {
129
141
  items.push({
130
142
  onItemClick: () => {
@@ -0,0 +1,2 @@
1
+ export * from "./locales/index.js";
2
+ export * from "./dictionary.js";
@@ -20,6 +20,12 @@ export const ar: Dictionary = {
20
20
  aliases: ["ع3", "عنوان3", "عنوان فرعي"],
21
21
  group: "العناوين",
22
22
  },
23
+ quote: {
24
+ title: "اقتباس",
25
+ subtext: "اقتباس أو مقتطف",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "الكتل الأساسية",
28
+ },
23
29
  numbered_list: {
24
30
  title: "قائمة مرقمة",
25
31
  subtext: "تستخدم لعرض قائمة مرقمة",
@@ -20,6 +20,12 @@ export const de: Dictionary = {
20
20
  aliases: ["h3", "überschrift3", "unterüberschrift"],
21
21
  group: "Überschriften",
22
22
  },
23
+ quote: {
24
+ title: "Zitat",
25
+ subtext: "Zitat oder Auszug",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Grundlegende blöcke",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Nummerierte Liste",
25
31
  subtext: "Liste mit nummerierten Elementen",
@@ -18,6 +18,12 @@ export const en = {
18
18
  aliases: ["h3", "heading3", "subheading"],
19
19
  group: "Headings",
20
20
  },
21
+ quote: {
22
+ title: "Quote",
23
+ subtext: "Quote or excerpt",
24
+ aliases: ["quotation", "blockquote", "bq"],
25
+ group: "Basic blocks",
26
+ },
21
27
  numbered_list: {
22
28
  title: "Numbered List",
23
29
  subtext: "List with ordered items",
@@ -20,6 +20,12 @@ export const es: Dictionary = {
20
20
  aliases: ["h3", "encabezado3", "subencabezado"],
21
21
  group: "Encabezados",
22
22
  },
23
+ quote: {
24
+ title: "Cita",
25
+ subtext: "Cita o extracto",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Bloques básicos",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Lista Numerada",
25
31
  subtext: "Lista con elementos ordenados",
@@ -21,6 +21,12 @@ export const fr: Dictionary = {
21
21
  aliases: ["h3", "titre3", "sous-titre"],
22
22
  group: "Titres",
23
23
  },
24
+ quote: {
25
+ title: "Citation",
26
+ subtext: "Citation ou extrait",
27
+ aliases: ["quotation", "blockquote", "bq"],
28
+ group: "Blocs de base",
29
+ },
24
30
  numbered_list: {
25
31
  title: "Liste Numérotée",
26
32
  subtext: "Utilisé pour afficher une liste numérotée",
@@ -20,6 +20,12 @@ export const hr: Dictionary = {
20
20
  aliases: ["h3", "naslov3", "podnaslov"],
21
21
  group: "Naslovi",
22
22
  },
23
+ quote: {
24
+ title: "Citat",
25
+ subtext: "Citat ili izvadak",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Osnovni blokovi",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Numerirani popis",
25
31
  subtext: "Popis s numeriranim stavkama",
@@ -20,6 +20,12 @@ export const is: Dictionary = {
20
20
  aliases: ["h3", "fyrirsogn3", "undirfyrirsogn"],
21
21
  group: "Fyrirsagnir",
22
22
  },
23
+ quote: {
24
+ title: "Tilvitnun",
25
+ subtext: "Tilvitnun eða útdráttur",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Grunnblokkar",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Númeruð listi",
25
31
  subtext: "Notað til að birta númeraðan lista",
@@ -20,6 +20,12 @@ export const it: Dictionary = {
20
20
  aliases: ["h3", "intestazione3", "sottotitolo"],
21
21
  group: "Intestazioni",
22
22
  },
23
+ quote: {
24
+ title: "Citazione",
25
+ subtext: "Citazione o estratto",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Blocchi Base",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Elenco Numerato",
25
31
  subtext: "Elenco con elementi ordinati",
@@ -20,6 +20,12 @@ export const ja: Dictionary = {
20
20
  aliases: ["h3", "見出し3", "subheading", "小見出し"],
21
21
  group: "見出し",
22
22
  },
23
+ quote: {
24
+ title: "引用",
25
+ subtext: "引用または抜粋",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "基本ブロック",
28
+ },
23
29
  numbered_list: {
24
30
  title: "番号付リスト",
25
31
  subtext: "番号付リストを表示するために使用",
@@ -20,6 +20,12 @@ export const ko: Dictionary = {
20
20
  aliases: ["h3", "제목3", "subheading"],
21
21
  group: "제목",
22
22
  },
23
+ quote: {
24
+ title: "인용",
25
+ subtext: "인용문 또는 발췌",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "기본 블록",
28
+ },
23
29
  numbered_list: {
24
30
  title: "번호 매기기 목록",
25
31
  subtext: "번호가 매겨진 목록을 추가합니다.",
@@ -20,6 +20,12 @@ export const nl: Dictionary = {
20
20
  aliases: ["h3", "kop3", "subkop"],
21
21
  group: "Koppen",
22
22
  },
23
+ quote: {
24
+ title: "Citaat",
25
+ subtext: "Citaat of uittreksel",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Basisblokken",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Genummerde Lijst",
25
31
  subtext: "Gebruikt om een genummerde lijst weer te geven",
@@ -20,6 +20,12 @@ export const no: Dictionary = {
20
20
  aliases: ["h3", "overskrift3", "underoverskrift"],
21
21
  group: "Overskrifter",
22
22
  },
23
+ quote: {
24
+ title: "Sitat",
25
+ subtext: "Sitat eller utdrag",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Grunnleggende blokker",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Nummerert liste",
25
31
  subtext: "Liste med ordnede elementer",
@@ -20,6 +20,12 @@ export const pl: Dictionary = {
20
20
  aliases: ["h3", "naglowek3", "podnaglowek"],
21
21
  group: "Nagłówki",
22
22
  },
23
+ quote: {
24
+ title: "Cytat",
25
+ subtext: "Cytat lub fragment",
26
+ aliases: ["quotation", "blockquote", "bq"],
27
+ group: "Podstawowe bloki",
28
+ },
23
29
  numbered_list: {
24
30
  title: "Lista numerowana",
25
31
  subtext: "Używana do wyświetlania listy numerowanej",