@blocknote/core 0.40.0 → 0.41.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 (102) hide show
  1. package/dist/BlockNoteSchema-COA0fsXW.cjs +11 -0
  2. package/dist/BlockNoteSchema-COA0fsXW.cjs.map +1 -0
  3. package/dist/{BlockNoteSchema-oR047ACf.js → BlockNoteSchema-CYRHak18.js} +681 -581
  4. package/dist/BlockNoteSchema-CYRHak18.js.map +1 -0
  5. package/dist/blocknote.cjs +4 -4
  6. package/dist/blocknote.cjs.map +1 -1
  7. package/dist/blocknote.js +3663 -2817
  8. package/dist/blocknote.js.map +1 -1
  9. package/dist/blocks.cjs +1 -1
  10. package/dist/blocks.js +51 -49
  11. package/dist/en-Cl87Uuyf.cjs +2 -0
  12. package/dist/en-Cl87Uuyf.cjs.map +1 -0
  13. package/dist/{en-Bq3Es3Np.js → en-njEqD7AG.js} +9 -3
  14. package/dist/en-njEqD7AG.js.map +1 -0
  15. package/dist/locales.cjs +1 -1
  16. package/dist/locales.cjs.map +1 -1
  17. package/dist/locales.js +122 -2
  18. package/dist/locales.js.map +1 -1
  19. package/dist/style.css +1 -1
  20. package/dist/webpack-stats.json +1 -1
  21. package/package.json +4 -3
  22. package/src/api/exporters/html/externalHTMLExporter.ts +1 -1
  23. package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +2 -1
  24. package/src/api/exporters/markdown/markdownExporter.ts +3 -1
  25. package/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts +19 -0
  26. package/src/api/parsers/markdown/parseMarkdown.ts +31 -0
  27. package/src/blocks/Code/block.ts +14 -14
  28. package/src/blocks/Divider/block.ts +49 -0
  29. package/src/blocks/ListItem/BulletListItem/block.ts +9 -1
  30. package/src/blocks/ListItem/CheckListItem/block.ts +1 -0
  31. package/src/blocks/ListItem/NumberedListItem/block.ts +9 -1
  32. package/src/blocks/Table/block.ts +56 -1
  33. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +1 -1
  34. package/src/blocks/defaultBlocks.ts +16 -14
  35. package/src/blocks/index.ts +1 -0
  36. package/src/editor/Block.css +14 -20
  37. package/src/editor/BlockNoteEditor.test.ts +40 -0
  38. package/src/editor/BlockNoteEditor.ts +248 -465
  39. package/src/editor/BlockNoteExtensions.ts +3 -1
  40. package/src/editor/managers/BlockManager.ts +251 -0
  41. package/src/editor/managers/CollaborationManager.ts +212 -0
  42. package/src/editor/managers/EventManager.ts +134 -0
  43. package/src/editor/managers/ExportManager.ts +137 -0
  44. package/src/editor/managers/ExtensionManager.ts +130 -0
  45. package/src/editor/managers/SelectionManager.ts +114 -0
  46. package/src/editor/managers/StateManager.ts +238 -0
  47. package/src/editor/managers/StyleManager.ts +182 -0
  48. package/src/editor/managers/index.ts +11 -0
  49. package/src/extensions/Comments/CommentsPlugin.ts +7 -4
  50. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +10 -0
  51. package/src/i18n/locales/ar.ts +6 -0
  52. package/src/i18n/locales/de.ts +6 -0
  53. package/src/i18n/locales/en.ts +6 -0
  54. package/src/i18n/locales/es.ts +6 -0
  55. package/src/i18n/locales/fr.ts +6 -0
  56. package/src/i18n/locales/he.ts +6 -0
  57. package/src/i18n/locales/hr.ts +6 -0
  58. package/src/i18n/locales/is.ts +6 -0
  59. package/src/i18n/locales/it.ts +6 -0
  60. package/src/i18n/locales/ja.ts +6 -0
  61. package/src/i18n/locales/ko.ts +6 -0
  62. package/src/i18n/locales/nl.ts +6 -0
  63. package/src/i18n/locales/no.ts +6 -0
  64. package/src/i18n/locales/pl.ts +6 -0
  65. package/src/i18n/locales/pt.ts +6 -0
  66. package/src/i18n/locales/ru.ts +6 -0
  67. package/src/i18n/locales/sk.ts +6 -0
  68. package/src/i18n/locales/uk.ts +6 -0
  69. package/src/i18n/locales/vi.ts +6 -0
  70. package/src/i18n/locales/zh-tw.ts +6 -0
  71. package/src/i18n/locales/zh.ts +6 -0
  72. package/src/schema/blocks/createSpec.ts +1 -0
  73. package/src/util/string.ts +21 -0
  74. package/types/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.d.ts +2 -0
  75. package/types/src/blocks/Divider/block.d.ts +3 -0
  76. package/types/src/blocks/Heading/block.d.ts +3 -3
  77. package/types/src/blocks/defaultBlocks.d.ts +2 -1
  78. package/types/src/blocks/index.d.ts +1 -0
  79. package/types/src/editor/BlockNoteEditor.d.ts +68 -47
  80. package/types/src/editor/BlockNoteExtensions.d.ts +2 -1
  81. package/types/src/editor/managers/BlockManager.d.ts +114 -0
  82. package/types/src/editor/managers/CollaborationManager.d.ts +115 -0
  83. package/types/src/editor/managers/EventManager.d.ts +58 -0
  84. package/types/src/editor/managers/ExportManager.d.ts +64 -0
  85. package/types/src/editor/managers/ExtensionManager.d.ts +68 -0
  86. package/types/src/editor/managers/SelectionManager.d.ts +54 -0
  87. package/types/src/editor/managers/StateManager.d.ts +115 -0
  88. package/types/src/editor/managers/StyleManager.d.ts +48 -0
  89. package/types/src/editor/managers/index.d.ts +8 -0
  90. package/types/src/extensions/Comments/CommentsPlugin.d.ts +4 -3
  91. package/types/src/i18n/locales/en.d.ts +6 -0
  92. package/types/src/i18n/locales/sk.d.ts +6 -0
  93. package/types/src/util/string.d.ts +1 -0
  94. package/dist/BlockNoteSchema-DmZ6UQfY.cjs +0 -11
  95. package/dist/BlockNoteSchema-DmZ6UQfY.cjs.map +0 -1
  96. package/dist/BlockNoteSchema-oR047ACf.js.map +0 -1
  97. package/dist/en-Bq3Es3Np.js.map +0 -1
  98. package/dist/en-D3B48eJ7.cjs +0 -2
  99. package/dist/en-D3B48eJ7.cjs.map +0 -1
  100. package/dist/tsconfig.tsbuildinfo +0 -1
  101. /package/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.ts → util/removeUnderlinesRehypePlugin.ts} +0 -0
  102. /package/types/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.d.ts → util/removeUnderlinesRehypePlugin.d.ts} +0 -0
@@ -0,0 +1,137 @@
1
+ import { createExternalHTMLExporter } from "../../api/exporters/html/externalHTMLExporter.js";
2
+ import { createInternalHTMLSerializer } from "../../api/exporters/html/internalHTMLSerializer.js";
3
+ import { blocksToMarkdown } from "../../api/exporters/markdown/markdownExporter.js";
4
+ import { HTMLToBlocks } from "../../api/parsers/html/parseHTML.js";
5
+ import {
6
+ markdownToBlocks,
7
+ markdownToHTML,
8
+ } from "../../api/parsers/markdown/parseMarkdown.js";
9
+ import {
10
+ Block,
11
+ DefaultBlockSchema,
12
+ DefaultInlineContentSchema,
13
+ DefaultStyleSchema,
14
+ PartialBlock,
15
+ } from "../../blocks/defaultBlocks.js";
16
+ import {
17
+ BlockSchema,
18
+ InlineContentSchema,
19
+ StyleSchema,
20
+ } from "../../schema/index.js";
21
+ import { BlockNoteEditor } from "../BlockNoteEditor.js";
22
+
23
+ export class ExportManager<
24
+ BSchema extends BlockSchema = DefaultBlockSchema,
25
+ ISchema extends InlineContentSchema = DefaultInlineContentSchema,
26
+ SSchema extends StyleSchema = DefaultStyleSchema,
27
+ > {
28
+ constructor(private editor: BlockNoteEditor<BSchema, ISchema, SSchema>) {}
29
+
30
+ /**
31
+ * Exports blocks into a simplified HTML string. To better conform to HTML standards, children of blocks which aren't list
32
+ * items are un-nested in the output HTML.
33
+ *
34
+ * @param blocks An array of blocks that should be serialized into HTML.
35
+ * @returns The blocks, serialized as an HTML string.
36
+ */
37
+ public blocksToHTMLLossy(
38
+ blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.editor.document,
39
+ ): string {
40
+ const exporter = createExternalHTMLExporter(
41
+ this.editor.pmSchema,
42
+ this.editor,
43
+ );
44
+ return exporter.exportBlocks(blocks, {});
45
+ }
46
+
47
+ /**
48
+ * Serializes blocks into an HTML string in the format that would normally be rendered by the editor.
49
+ *
50
+ * Use this method if you want to server-side render HTML (for example, a blog post that has been edited in BlockNote)
51
+ * and serve it to users without loading the editor on the client (i.e.: displaying the blog post)
52
+ *
53
+ * @param blocks An array of blocks that should be serialized into HTML.
54
+ * @returns The blocks, serialized as an HTML string.
55
+ */
56
+ public blocksToFullHTML(
57
+ blocks: PartialBlock<BSchema, ISchema, SSchema>[],
58
+ ): string {
59
+ const exporter = createInternalHTMLSerializer(
60
+ this.editor.pmSchema,
61
+ this.editor,
62
+ );
63
+ return exporter.serializeBlocks(blocks, {});
64
+ }
65
+
66
+ /**
67
+ * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and
68
+ * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote
69
+ * doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text.
70
+ * @param html The HTML string to parse blocks from.
71
+ * @returns The blocks parsed from the HTML string.
72
+ */
73
+ public tryParseHTMLToBlocks(
74
+ html: string,
75
+ ): Block<BSchema, ISchema, SSchema>[] {
76
+ return HTMLToBlocks(html, this.editor.pmSchema);
77
+ }
78
+
79
+ /**
80
+ * Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of
81
+ * BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed.
82
+ * @param blocks An array of blocks that should be serialized into Markdown.
83
+ * @returns The blocks, serialized as a Markdown string.
84
+ */
85
+ public blocksToMarkdownLossy(
86
+ blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.editor.document,
87
+ ): string {
88
+ return blocksToMarkdown(blocks, this.editor.pmSchema, this.editor, {});
89
+ }
90
+
91
+ /**
92
+ * Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on
93
+ * Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it
94
+ * as text.
95
+ * @param markdown The Markdown string to parse blocks from.
96
+ * @returns The blocks parsed from the Markdown string.
97
+ */
98
+ public tryParseMarkdownToBlocks(
99
+ markdown: string,
100
+ ): Block<BSchema, ISchema, SSchema>[] {
101
+ return markdownToBlocks(markdown, this.editor.pmSchema);
102
+ }
103
+
104
+ /**
105
+ * Paste HTML into the editor. Defaults to converting HTML to BlockNote HTML.
106
+ * @param html The HTML to paste.
107
+ * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML.
108
+ */
109
+ public pasteHTML(html: string, raw = false) {
110
+ let htmlToPaste = html;
111
+ if (!raw) {
112
+ const blocks = this.tryParseHTMLToBlocks(html);
113
+ htmlToPaste = this.blocksToFullHTML(blocks);
114
+ }
115
+ if (!htmlToPaste) {
116
+ return;
117
+ }
118
+ this.editor.prosemirrorView?.pasteHTML(htmlToPaste);
119
+ }
120
+
121
+ /**
122
+ * Paste text into the editor. Defaults to interpreting text as markdown.
123
+ * @param text The text to paste.
124
+ */
125
+ public pasteText(text: string) {
126
+ return this.editor.prosemirrorView?.pasteText(text);
127
+ }
128
+
129
+ /**
130
+ * Paste markdown into the editor.
131
+ * @param markdown The markdown to paste.
132
+ */
133
+ public pasteMarkdown(markdown: string) {
134
+ const html = markdownToHTML(markdown);
135
+ return this.pasteHTML(html);
136
+ }
137
+ }
@@ -0,0 +1,130 @@
1
+ import { FilePanelProsemirrorPlugin } from "../../extensions/FilePanel/FilePanelPlugin.js";
2
+ import { FormattingToolbarProsemirrorPlugin } from "../../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
3
+ import { LinkToolbarProsemirrorPlugin } from "../../extensions/LinkToolbar/LinkToolbarPlugin.js";
4
+ import { ShowSelectionPlugin } from "../../extensions/ShowSelection/ShowSelectionPlugin.js";
5
+ import { SideMenuProsemirrorPlugin } from "../../extensions/SideMenu/SideMenuPlugin.js";
6
+ import { SuggestionMenuProseMirrorPlugin } from "../../extensions/SuggestionMenu/SuggestionPlugin.js";
7
+ import { TableHandlesProsemirrorPlugin } from "../../extensions/TableHandles/TableHandlesPlugin.js";
8
+ import { BlockNoteExtension } from "../BlockNoteExtension.js";
9
+ import { BlockNoteEditor } from "../BlockNoteEditor.js";
10
+
11
+ export class ExtensionManager {
12
+ constructor(private editor: BlockNoteEditor) {}
13
+
14
+ /**
15
+ * Shorthand to get a typed extension from the editor, by
16
+ * just passing in the extension class.
17
+ *
18
+ * @param ext - The extension class to get
19
+ * @param key - optional, the key of the extension in the extensions object (defaults to the extension name)
20
+ * @returns The extension instance
21
+ */
22
+ public extension<T extends BlockNoteExtension>(
23
+ ext: { new (...args: any[]): T } & typeof BlockNoteExtension,
24
+ key = ext.key(),
25
+ ): T {
26
+ const extension = this.editor.extensions[key] as T;
27
+ if (!extension) {
28
+ throw new Error(`Extension ${key} not found`);
29
+ }
30
+ return extension;
31
+ }
32
+
33
+ /**
34
+ * Get all extensions
35
+ */
36
+ public getExtensions() {
37
+ return this.editor.extensions;
38
+ }
39
+
40
+ /**
41
+ * Get a specific extension by key
42
+ */
43
+ public getExtension(key: string) {
44
+ return this.editor.extensions[key];
45
+ }
46
+
47
+ /**
48
+ * Check if an extension exists
49
+ */
50
+ public hasExtension(key: string): boolean {
51
+ return key in this.editor.extensions;
52
+ }
53
+
54
+ // Plugin getters - these provide access to the core BlockNote plugins
55
+
56
+ /**
57
+ * Get the formatting toolbar plugin
58
+ */
59
+ public get formattingToolbar(): FormattingToolbarProsemirrorPlugin {
60
+ return this.editor.extensions[
61
+ "formattingToolbar"
62
+ ] as FormattingToolbarProsemirrorPlugin;
63
+ }
64
+
65
+ /**
66
+ * Get the link toolbar plugin
67
+ */
68
+ public get linkToolbar(): LinkToolbarProsemirrorPlugin<any, any, any> {
69
+ return this.editor.extensions[
70
+ "linkToolbar"
71
+ ] as LinkToolbarProsemirrorPlugin<any, any, any>;
72
+ }
73
+
74
+ /**
75
+ * Get the side menu plugin
76
+ */
77
+ public get sideMenu(): SideMenuProsemirrorPlugin<any, any, any> {
78
+ return this.editor.extensions["sideMenu"] as SideMenuProsemirrorPlugin<
79
+ any,
80
+ any,
81
+ any
82
+ >;
83
+ }
84
+
85
+ /**
86
+ * Get the suggestion menus plugin
87
+ */
88
+ public get suggestionMenus(): SuggestionMenuProseMirrorPlugin<any, any, any> {
89
+ return this.editor.extensions[
90
+ "suggestionMenus"
91
+ ] as SuggestionMenuProseMirrorPlugin<any, any, any>;
92
+ }
93
+
94
+ /**
95
+ * Get the file panel plugin (if available)
96
+ */
97
+ public get filePanel(): FilePanelProsemirrorPlugin<any, any> | undefined {
98
+ return this.editor.extensions["filePanel"] as
99
+ | FilePanelProsemirrorPlugin<any, any>
100
+ | undefined;
101
+ }
102
+
103
+ /**
104
+ * Get the table handles plugin (if available)
105
+ */
106
+ public get tableHandles():
107
+ | TableHandlesProsemirrorPlugin<any, any>
108
+ | undefined {
109
+ return this.editor.extensions["tableHandles"] as
110
+ | TableHandlesProsemirrorPlugin<any, any>
111
+ | undefined;
112
+ }
113
+
114
+ /**
115
+ * Get the show selection plugin
116
+ */
117
+ public get showSelectionPlugin(): ShowSelectionPlugin {
118
+ return this.editor.extensions["showSelection"] as ShowSelectionPlugin;
119
+ }
120
+
121
+ /**
122
+ * Check if collaboration is enabled (Yjs or Liveblocks)
123
+ */
124
+ public get isCollaborationEnabled(): boolean {
125
+ return (
126
+ this.hasExtension("ySyncPlugin") ||
127
+ this.hasExtension("liveblocksExtension")
128
+ );
129
+ }
130
+ }
@@ -0,0 +1,114 @@
1
+ import {
2
+ getSelection,
3
+ getSelectionCutBlocks,
4
+ setSelection,
5
+ } from "../../api/blockManipulation/selections/selection.js";
6
+ import {
7
+ getTextCursorPosition,
8
+ setTextCursorPosition,
9
+ } from "../../api/blockManipulation/selections/textCursorPosition.js";
10
+ import { isNodeSelection, posToDOMRect } from "@tiptap/core";
11
+ import {
12
+ BlockIdentifier,
13
+ BlockSchema,
14
+ InlineContentSchema,
15
+ StyleSchema,
16
+ } from "../../schema/index.js";
17
+ import {
18
+ DefaultBlockSchema,
19
+ DefaultInlineContentSchema,
20
+ DefaultStyleSchema,
21
+ } from "../../blocks/defaultBlocks.js";
22
+ import { Selection } from "../selectionTypes.js";
23
+ import { TextCursorPosition } from "../cursorPositionTypes.js";
24
+ import { BlockNoteEditor } from "../BlockNoteEditor.js";
25
+
26
+ export class SelectionManager<
27
+ BSchema extends BlockSchema = DefaultBlockSchema,
28
+ ISchema extends InlineContentSchema = DefaultInlineContentSchema,
29
+ SSchema extends StyleSchema = DefaultStyleSchema,
30
+ > {
31
+ constructor(private editor: BlockNoteEditor<BSchema, ISchema, SSchema>) {}
32
+
33
+ /**
34
+ * Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
35
+ * that the selection spans across.
36
+ *
37
+ * If the selection starts / ends halfway through a block, the returned data will contain the entire block.
38
+ */
39
+ public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {
40
+ return this.editor.transact((tr) => getSelection(tr));
41
+ }
42
+
43
+ /**
44
+ * Gets a snapshot of the current selection. This contains all blocks (included nested blocks)
45
+ * that the selection spans across.
46
+ *
47
+ * If the selection starts / ends halfway through a block, the returned block will be
48
+ * only the part of the block that is included in the selection.
49
+ */
50
+ public getSelectionCutBlocks() {
51
+ return this.editor.transact((tr) => getSelectionCutBlocks(tr));
52
+ }
53
+
54
+ /**
55
+ * Sets the selection to a range of blocks.
56
+ * @param startBlock The identifier of the block that should be the start of the selection.
57
+ * @param endBlock The identifier of the block that should be the end of the selection.
58
+ */
59
+ public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {
60
+ return this.editor.transact((tr) => setSelection(tr, startBlock, endBlock));
61
+ }
62
+
63
+ /**
64
+ * Gets a snapshot of the current text cursor position.
65
+ * @returns A snapshot of the current text cursor position.
66
+ */
67
+ public getTextCursorPosition(): TextCursorPosition<
68
+ BSchema,
69
+ ISchema,
70
+ SSchema
71
+ > {
72
+ return this.editor.transact((tr) => getTextCursorPosition(tr));
73
+ }
74
+
75
+ /**
76
+ * Sets the text cursor position to the start or end of an existing block. Throws an error if the target block could
77
+ * not be found.
78
+ * @param targetBlock The identifier of an existing block that the text cursor should be moved to.
79
+ * @param placement Whether the text cursor should be placed at the start or end of the block.
80
+ */
81
+ public setTextCursorPosition(
82
+ targetBlock: BlockIdentifier,
83
+ placement: "start" | "end" = "start",
84
+ ) {
85
+ return this.editor.transact((tr) =>
86
+ setTextCursorPosition(tr, targetBlock, placement),
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Gets the bounding box of the current selection.
92
+ */
93
+ public getSelectionBoundingBox() {
94
+ if (!this.editor.prosemirrorView) {
95
+ return undefined;
96
+ }
97
+
98
+ const { selection } = this.editor.prosemirrorState;
99
+
100
+ // support for CellSelections
101
+ const { ranges } = selection;
102
+ const from = Math.min(...ranges.map((range) => range.$from.pos));
103
+ const to = Math.max(...ranges.map((range) => range.$to.pos));
104
+
105
+ if (isNodeSelection(selection)) {
106
+ const node = this.editor.prosemirrorView.nodeDOM(from) as HTMLElement;
107
+ if (node) {
108
+ return node.getBoundingClientRect();
109
+ }
110
+ }
111
+
112
+ return posToDOMRect(this.editor.prosemirrorView, from, to);
113
+ }
114
+ }
@@ -0,0 +1,238 @@
1
+ import { redo, undo } from "@tiptap/pm/history";
2
+ import { Command, Transaction } from "prosemirror-state";
3
+ import { BlockNoteEditor } from "../BlockNoteEditor.js";
4
+
5
+ export class StateManager {
6
+ constructor(
7
+ private editor: BlockNoteEditor,
8
+ private options?: {
9
+ /**
10
+ * Swap the default undo command with a custom command.
11
+ */
12
+ undo?: typeof undo;
13
+ /**
14
+ * Swap the default redo command with a custom command.
15
+ */
16
+ redo?: typeof redo;
17
+ },
18
+ ) {}
19
+
20
+ /**
21
+ * Stores the currently active transaction, which is the accumulated transaction from all {@link dispatch} calls during a {@link transact} calls
22
+ */
23
+ private activeTransaction: Transaction | null = null;
24
+
25
+ /**
26
+ * For any command that can be executed, you can check if it can be executed by calling `editor.can(command)`.
27
+ * @example
28
+ * ```ts
29
+ * if (editor.can(editor.undo)) {
30
+ * // show button
31
+ * } else {
32
+ * // hide button
33
+ * }
34
+ */
35
+ public can(cb: () => boolean) {
36
+ try {
37
+ this.isInCan = true;
38
+ return cb();
39
+ } finally {
40
+ this.isInCan = false;
41
+ }
42
+ }
43
+
44
+ // Flag to indicate if we're in a `can` call
45
+ private isInCan = false;
46
+
47
+ /**
48
+ * Execute a prosemirror command. This is mostly for backwards compatibility with older code.
49
+ *
50
+ * @note You should prefer the {@link transact} method when possible, as it will automatically handle the dispatching of the transaction and work across blocknote transactions.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * editor.exec((state, dispatch, view) => {
55
+ * dispatch(state.tr.insertText("Hello, world!"));
56
+ * });
57
+ * ```
58
+ */
59
+ public exec(command: Command) {
60
+ if (this.activeTransaction) {
61
+ throw new Error(
62
+ "`exec` should not be called within a `transact` call, move the `exec` call outside of the `transact` call",
63
+ );
64
+ }
65
+ if (this.isInCan) {
66
+ return this.canExec(command);
67
+ }
68
+ const state = this.prosemirrorState;
69
+ const view = this.prosemirrorView;
70
+ const dispatch = (tr: Transaction) => this.prosemirrorView.dispatch(tr);
71
+
72
+ return command(state, dispatch, view);
73
+ }
74
+
75
+ /**
76
+ * Check if a command can be executed. A command should return `false` if it is not valid in the current state.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * if (editor.canExec(command)) {
81
+ * // show button
82
+ * } else {
83
+ * // hide button
84
+ * }
85
+ * ```
86
+ */
87
+ public canExec(command: Command): boolean {
88
+ if (this.activeTransaction) {
89
+ throw new Error(
90
+ "`canExec` should not be called within a `transact` call, move the `canExec` call outside of the `transact` call",
91
+ );
92
+ }
93
+ const state = this.prosemirrorState;
94
+ const view = this.prosemirrorView;
95
+
96
+ return command(state, undefined, view);
97
+ }
98
+
99
+ /**
100
+ * Execute a function within a "blocknote transaction".
101
+ * All changes to the editor within the transaction will be grouped together, so that
102
+ * we can dispatch them as a single operation (thus creating only a single undo step)
103
+ *
104
+ * @note There is no need to dispatch the transaction, as it will be automatically dispatched when the callback is complete.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // All changes to the editor will be grouped together
109
+ * editor.transact((tr) => {
110
+ * tr.insertText("Hello, world!");
111
+ * // These two operations will be grouped together in a single undo step
112
+ * editor.transact((tr) => {
113
+ * tr.insertText("Hello, world!");
114
+ * });
115
+ * });
116
+ * ```
117
+ */
118
+ public transact<T>(
119
+ callback: (
120
+ /**
121
+ * The current active transaction, this will automatically be dispatched to the editor when the callback is complete
122
+ * If another `transact` call is made within the callback, it will be passed the same transaction as the parent call.
123
+ */
124
+ tr: Transaction,
125
+ ) => T,
126
+ ): T {
127
+ if (this.activeTransaction) {
128
+ // Already in a transaction, so we can just callback immediately
129
+ return callback(this.activeTransaction);
130
+ }
131
+
132
+ try {
133
+ // Enter transaction mode, by setting a starting transaction
134
+ this.activeTransaction = this.editor._tiptapEditor.state.tr;
135
+
136
+ // Capture all dispatch'd transactions
137
+ const result = callback(this.activeTransaction);
138
+
139
+ // Any transactions captured by the `dispatch` call will be stored in `this.activeTransaction`
140
+ const activeTr = this.activeTransaction;
141
+
142
+ this.activeTransaction = null;
143
+ if (
144
+ activeTr &&
145
+ // Only dispatch if the transaction was actually modified in some way
146
+ (activeTr.docChanged ||
147
+ activeTr.selectionSet ||
148
+ activeTr.scrolledIntoView ||
149
+ activeTr.storedMarksSet ||
150
+ !activeTr.isGeneric)
151
+ ) {
152
+ // Dispatch the transaction if it was modified
153
+ this.prosemirrorView.dispatch(activeTr);
154
+ }
155
+
156
+ return result;
157
+ } finally {
158
+ // We wrap this in a finally block to ensure we don't disable future transactions just because of an error in the callback
159
+ this.activeTransaction = null;
160
+ }
161
+ }
162
+ /**
163
+ * Get the underlying prosemirror state
164
+ * @note Prefer using `editor.transact` to read the current editor state, as that will ensure the state is up to date
165
+ * @see https://prosemirror.net/docs/ref/#state.EditorState
166
+ */
167
+ public get prosemirrorState() {
168
+ if (this.activeTransaction) {
169
+ throw new Error(
170
+ "`prosemirrorState` should not be called within a `transact` call, move the `prosemirrorState` call outside of the `transact` call or use `editor.transact` to read the current editor state",
171
+ );
172
+ }
173
+ return this.editor._tiptapEditor.state;
174
+ }
175
+
176
+ /**
177
+ * Get the underlying prosemirror view
178
+ * @see https://prosemirror.net/docs/ref/#view.EditorView
179
+ */
180
+ public get prosemirrorView() {
181
+ return this.editor._tiptapEditor.view;
182
+ }
183
+
184
+ public isFocused() {
185
+ return this.prosemirrorView?.hasFocus() || false;
186
+ }
187
+
188
+ public focus() {
189
+ this.prosemirrorView?.focus();
190
+ }
191
+
192
+ /**
193
+ * Checks if the editor is currently editable, or if it's locked.
194
+ * @returns True if the editor is editable, false otherwise.
195
+ */
196
+ public get isEditable(): boolean {
197
+ if (!this.editor._tiptapEditor) {
198
+ if (!this.editor.headless) {
199
+ throw new Error("no editor, but also not headless?");
200
+ }
201
+ return false;
202
+ }
203
+ return this.editor._tiptapEditor.isEditable === undefined
204
+ ? true
205
+ : this.editor._tiptapEditor.isEditable;
206
+ }
207
+
208
+ /**
209
+ * Makes the editor editable or locks it, depending on the argument passed.
210
+ * @param editable True to make the editor editable, or false to lock it.
211
+ */
212
+ public set isEditable(editable: boolean) {
213
+ if (!this.editor._tiptapEditor) {
214
+ if (!this.editor.headless) {
215
+ throw new Error("no editor, but also not headless?");
216
+ }
217
+ // not relevant on headless
218
+ return;
219
+ }
220
+ if (this.editor._tiptapEditor.options.editable !== editable) {
221
+ this.editor._tiptapEditor.setEditable(editable);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Undo the last action.
227
+ */
228
+ public undo() {
229
+ return this.exec(this.options?.undo ?? undo);
230
+ }
231
+
232
+ /**
233
+ * Redo the last action.
234
+ */
235
+ public redo() {
236
+ return this.exec(this.options?.redo ?? redo);
237
+ }
238
+ }