@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.
- package/dist/BlockNoteSchema-COA0fsXW.cjs +11 -0
- package/dist/BlockNoteSchema-COA0fsXW.cjs.map +1 -0
- package/dist/{BlockNoteSchema-oR047ACf.js → BlockNoteSchema-CYRHak18.js} +681 -581
- package/dist/BlockNoteSchema-CYRHak18.js.map +1 -0
- package/dist/blocknote.cjs +4 -4
- package/dist/blocknote.cjs.map +1 -1
- package/dist/blocknote.js +3663 -2817
- package/dist/blocknote.js.map +1 -1
- package/dist/blocks.cjs +1 -1
- package/dist/blocks.js +51 -49
- package/dist/en-Cl87Uuyf.cjs +2 -0
- package/dist/en-Cl87Uuyf.cjs.map +1 -0
- package/dist/{en-Bq3Es3Np.js → en-njEqD7AG.js} +9 -3
- package/dist/en-njEqD7AG.js.map +1 -0
- package/dist/locales.cjs +1 -1
- package/dist/locales.cjs.map +1 -1
- package/dist/locales.js +122 -2
- package/dist/locales.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +4 -3
- package/src/api/exporters/html/externalHTMLExporter.ts +1 -1
- package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +2 -1
- package/src/api/exporters/markdown/markdownExporter.ts +3 -1
- package/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts +19 -0
- package/src/api/parsers/markdown/parseMarkdown.ts +31 -0
- package/src/blocks/Code/block.ts +14 -14
- package/src/blocks/Divider/block.ts +49 -0
- package/src/blocks/ListItem/BulletListItem/block.ts +9 -1
- package/src/blocks/ListItem/CheckListItem/block.ts +1 -0
- package/src/blocks/ListItem/NumberedListItem/block.ts +9 -1
- package/src/blocks/Table/block.ts +56 -1
- package/src/blocks/ToggleWrapper/createToggleWrapper.ts +1 -1
- package/src/blocks/defaultBlocks.ts +16 -14
- package/src/blocks/index.ts +1 -0
- package/src/editor/Block.css +14 -20
- package/src/editor/BlockNoteEditor.test.ts +40 -0
- package/src/editor/BlockNoteEditor.ts +248 -465
- package/src/editor/BlockNoteExtensions.ts +3 -1
- package/src/editor/managers/BlockManager.ts +251 -0
- package/src/editor/managers/CollaborationManager.ts +212 -0
- package/src/editor/managers/EventManager.ts +134 -0
- package/src/editor/managers/ExportManager.ts +137 -0
- package/src/editor/managers/ExtensionManager.ts +130 -0
- package/src/editor/managers/SelectionManager.ts +114 -0
- package/src/editor/managers/StateManager.ts +238 -0
- package/src/editor/managers/StyleManager.ts +182 -0
- package/src/editor/managers/index.ts +11 -0
- package/src/extensions/Comments/CommentsPlugin.ts +7 -4
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +10 -0
- package/src/i18n/locales/ar.ts +6 -0
- package/src/i18n/locales/de.ts +6 -0
- package/src/i18n/locales/en.ts +6 -0
- package/src/i18n/locales/es.ts +6 -0
- package/src/i18n/locales/fr.ts +6 -0
- package/src/i18n/locales/he.ts +6 -0
- package/src/i18n/locales/hr.ts +6 -0
- package/src/i18n/locales/is.ts +6 -0
- package/src/i18n/locales/it.ts +6 -0
- package/src/i18n/locales/ja.ts +6 -0
- package/src/i18n/locales/ko.ts +6 -0
- package/src/i18n/locales/nl.ts +6 -0
- package/src/i18n/locales/no.ts +6 -0
- package/src/i18n/locales/pl.ts +6 -0
- package/src/i18n/locales/pt.ts +6 -0
- package/src/i18n/locales/ru.ts +6 -0
- package/src/i18n/locales/sk.ts +6 -0
- package/src/i18n/locales/uk.ts +6 -0
- package/src/i18n/locales/vi.ts +6 -0
- package/src/i18n/locales/zh-tw.ts +6 -0
- package/src/i18n/locales/zh.ts +6 -0
- package/src/schema/blocks/createSpec.ts +1 -0
- package/src/util/string.ts +21 -0
- package/types/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.d.ts +2 -0
- package/types/src/blocks/Divider/block.d.ts +3 -0
- package/types/src/blocks/Heading/block.d.ts +3 -3
- package/types/src/blocks/defaultBlocks.d.ts +2 -1
- package/types/src/blocks/index.d.ts +1 -0
- package/types/src/editor/BlockNoteEditor.d.ts +68 -47
- package/types/src/editor/BlockNoteExtensions.d.ts +2 -1
- package/types/src/editor/managers/BlockManager.d.ts +114 -0
- package/types/src/editor/managers/CollaborationManager.d.ts +115 -0
- package/types/src/editor/managers/EventManager.d.ts +58 -0
- package/types/src/editor/managers/ExportManager.d.ts +64 -0
- package/types/src/editor/managers/ExtensionManager.d.ts +68 -0
- package/types/src/editor/managers/SelectionManager.d.ts +54 -0
- package/types/src/editor/managers/StateManager.d.ts +115 -0
- package/types/src/editor/managers/StyleManager.d.ts +48 -0
- package/types/src/editor/managers/index.d.ts +8 -0
- package/types/src/extensions/Comments/CommentsPlugin.d.ts +4 -3
- package/types/src/i18n/locales/en.d.ts +6 -0
- package/types/src/i18n/locales/sk.d.ts +6 -0
- package/types/src/util/string.d.ts +1 -0
- package/dist/BlockNoteSchema-DmZ6UQfY.cjs +0 -11
- package/dist/BlockNoteSchema-DmZ6UQfY.cjs.map +0 -1
- package/dist/BlockNoteSchema-oR047ACf.js.map +0 -1
- package/dist/en-Bq3Es3Np.js.map +0 -1
- package/dist/en-D3B48eJ7.cjs +0 -2
- package/dist/en-D3B48eJ7.cjs.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- /package/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.ts → util/removeUnderlinesRehypePlugin.ts} +0 -0
- /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
|
+
}
|