@blocknote/core 0.11.1 → 0.12.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.
- package/README.md +13 -17
- package/dist/blocknote.js +1611 -1408
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +6 -6
- package/dist/blocknote.umd.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +8 -4
- package/src/api/blockManipulation/blockManipulation.test.ts +19 -15
- package/src/api/blockManipulation/blockManipulation.ts +107 -17
- package/src/api/exporters/html/externalHTMLExporter.ts +3 -7
- package/src/api/exporters/html/htmlConversion.test.ts +6 -3
- package/src/api/exporters/html/internalHTMLSerializer.ts +3 -7
- package/src/api/exporters/html/util/sharedHTMLConversion.ts +3 -3
- package/src/api/exporters/markdown/markdownExporter.test.ts +7 -3
- package/src/api/exporters/markdown/markdownExporter.ts +2 -6
- package/src/api/nodeConversions/nodeConversions.test.ts +14 -7
- package/src/api/nodeConversions/nodeConversions.ts +1 -2
- package/src/api/parsers/html/parseHTML.test.ts +5 -1
- package/src/api/parsers/html/parseHTML.ts +2 -6
- package/src/api/parsers/html/util/nestedLists.ts +11 -1
- package/src/api/parsers/markdown/parseMarkdown.test.ts +3 -0
- package/src/api/parsers/markdown/parseMarkdown.ts +2 -6
- package/src/api/testUtil/cases/customBlocks.ts +18 -16
- package/src/api/testUtil/cases/customInlineContent.ts +12 -13
- package/src/api/testUtil/cases/customStyles.ts +12 -10
- package/src/api/testUtil/index.ts +4 -2
- package/src/api/testUtil/partialBlockTestUtil.ts +2 -6
- package/src/blocks/ImageBlockContent/ImageBlockContent.ts +1 -2
- package/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts +8 -1
- package/src/blocks/ParagraphBlockContent/ParagraphBlockContent.ts +13 -0
- package/src/blocks/defaultBlockHelpers.ts +3 -3
- package/src/blocks/defaultBlockTypeGuards.ts +84 -0
- package/src/blocks/defaultBlocks.ts +29 -3
- package/src/editor/Block.css +2 -31
- package/src/editor/BlockNoteEditor.ts +219 -263
- package/src/editor/BlockNoteExtensions.ts +5 -2
- package/src/editor/BlockNoteSchema.ts +98 -0
- package/src/editor/BlockNoteTipTapEditor.ts +162 -0
- package/src/editor/cursorPositionTypes.ts +2 -6
- package/src/editor/editor.css +0 -1
- package/src/editor/selectionTypes.ts +2 -6
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +22 -29
- package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +26 -27
- package/src/extensions/ImageToolbar/ImageToolbarPlugin.ts +45 -51
- package/src/extensions/Placeholder/PlaceholderExtension.ts +81 -88
- package/src/extensions/SideMenu/SideMenuPlugin.ts +55 -56
- package/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts +8 -0
- package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +353 -0
- package/src/extensions/{SlashMenu/defaultSlashMenuItems.ts → SuggestionMenu/getDefaultSlashMenuItems.ts} +119 -89
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +62 -45
- package/src/extensions-shared/UiElementPosition.ts +4 -0
- package/src/index.ts +6 -6
- package/src/pm-nodes/BlockContainer.ts +5 -9
- package/src/schema/blocks/types.ts +15 -15
- package/src/schema/inlineContent/createSpec.ts +2 -2
- package/src/schema/inlineContent/types.ts +1 -1
- package/src/util/browser.ts +6 -4
- package/src/util/typescript.ts +7 -4
- package/types/src/api/blockManipulation/blockManipulation.d.ts +6 -1
- package/types/src/api/exporters/html/externalHTMLExporter.d.ts +2 -1
- package/types/src/api/exporters/html/internalHTMLSerializer.d.ts +2 -1
- package/types/src/api/exporters/markdown/markdownExporter.d.ts +2 -1
- package/types/src/api/nodeConversions/nodeConversions.d.ts +2 -1
- package/types/src/api/parsers/html/parseHTML.d.ts +2 -1
- package/types/src/api/parsers/markdown/parseMarkdown.d.ts +2 -1
- package/types/src/api/testUtil/cases/customBlocks.d.ts +72 -13
- package/types/src/api/testUtil/cases/customInlineContent.d.ts +281 -6
- package/types/src/api/testUtil/cases/customStyles.d.ts +247 -13
- package/types/src/api/testUtil/index.d.ts +4 -2
- package/types/src/api/testUtil/partialBlockTestUtil.d.ts +2 -1
- package/types/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.d.ts +6 -1
- package/types/src/blocks/defaultBlockHelpers.d.ts +2 -2
- package/types/src/blocks/defaultBlockTypeGuards.d.ts +24 -0
- package/types/src/blocks/defaultBlocks.d.ts +21 -15
- package/types/src/editor/BlockNoteEditor.d.ts +48 -53
- package/types/src/editor/BlockNoteExtensions.d.ts +1 -0
- package/types/src/editor/BlockNoteSchema.d.ts +34 -0
- package/types/src/editor/BlockNoteTipTapEditor.d.ts +28 -0
- package/types/src/editor/cursorPositionTypes.d.ts +2 -1
- package/types/src/editor/selectionTypes.d.ts +2 -1
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +5 -6
- package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +2 -2
- package/types/src/extensions/ImageToolbar/ImageToolbarPlugin.d.ts +15 -14
- package/types/src/extensions/Placeholder/PlaceholderExtension.d.ts +2 -15
- package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +8 -7
- package/types/src/extensions/SuggestionMenu/DefaultSuggestionItem.d.ts +8 -0
- package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +31 -0
- package/types/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.d.ts +10 -0
- package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +7 -7
- package/types/src/extensions-shared/UiElementPosition.d.ts +4 -0
- package/types/src/index.d.ts +6 -6
- package/types/src/pm-nodes/BlockContainer.d.ts +3 -2
- package/types/src/pm-nodes/BlockGroup.d.ts +1 -1
- package/types/src/schema/blocks/types.d.ts +15 -15
- package/types/src/schema/inlineContent/types.d.ts +1 -1
- package/types/src/util/browser.d.ts +1 -0
- package/types/src/util/typescript.d.ts +1 -0
- package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +0 -12
- package/src/extensions/SlashMenu/SlashMenuPlugin.ts +0 -53
- package/src/extensions-shared/BaseUiElementTypes.ts +0 -8
- package/src/extensions-shared/README.md +0 -3
- package/src/extensions-shared/suggestion/SuggestionItem.ts +0 -3
- package/src/extensions-shared/suggestion/SuggestionPlugin.ts +0 -448
- package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +0 -7
- package/types/src/extensions/SlashMenu/SlashMenuPlugin.d.ts +0 -13
- package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +0 -3
- package/types/src/extensions-shared/BaseUiElementTypes.d.ts +0 -7
- package/types/src/extensions-shared/suggestion/SuggestionItem.d.ts +0 -3
- package/types/src/extensions-shared/suggestion/SuggestionPlugin.d.ts +0 -36
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-100.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-100.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-200.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-200.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-300.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-300.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-500.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-500.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-600.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-600.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-700.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-700.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-800.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-800.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-900.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-900.woff2 +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-regular.woff +0 -0
- /package/src/{assets → fonts}/inter-v12-latin/inter-v12-latin-regular.woff2 +0 -0
- /package/src/{assets/fonts-inter.css → fonts/inter.css} +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Define the main block types **/
|
|
2
|
-
import { Extension, Node } from "@tiptap/core";
|
|
2
|
+
import type { Extension, Node } from "@tiptap/core";
|
|
3
3
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
4
|
-
import { InlineContent, InlineContentSchema, PartialInlineContent } from "../inlineContent/types";
|
|
5
|
-
import { PropSchema, Props } from "../propTypes";
|
|
6
|
-
import { StyleSchema } from "../styles/types";
|
|
7
|
-
export type BlockNoteDOMElement = "editor" | "
|
|
4
|
+
import type { InlineContent, InlineContentSchema, PartialInlineContent } from "../inlineContent/types";
|
|
5
|
+
import type { PropSchema, Props } from "../propTypes";
|
|
6
|
+
import type { StyleSchema } from "../styles/types";
|
|
7
|
+
export type BlockNoteDOMElement = "editor" | "block" | "blockGroup" | "blockContent" | "inlineContent";
|
|
8
8
|
export type BlockNoteDOMAttributes = Partial<{
|
|
9
9
|
[DOMElement in BlockNoteDOMElement]: Record<string, string>;
|
|
10
10
|
}>;
|
|
@@ -17,13 +17,13 @@ export type TiptapBlockImplementation<T extends BlockConfig, B extends BlockSche
|
|
|
17
17
|
requiredExtensions?: Array<Extension | Node>;
|
|
18
18
|
node: Node;
|
|
19
19
|
toInternalHTML: (block: BlockFromConfigNoChildren<T, I, S> & {
|
|
20
|
-
children:
|
|
20
|
+
children: BlockNoDefaults<B, I, S>[];
|
|
21
21
|
}, editor: BlockNoteEditor<B, I, S>) => {
|
|
22
22
|
dom: HTMLElement;
|
|
23
23
|
contentDOM?: HTMLElement;
|
|
24
24
|
};
|
|
25
25
|
toExternalHTML: (block: BlockFromConfigNoChildren<T, I, S> & {
|
|
26
|
-
children:
|
|
26
|
+
children: BlockNoDefaults<B, I, S>[];
|
|
27
27
|
}, editor: BlockNoteEditor<B, I, S>) => {
|
|
28
28
|
dom: HTMLElement;
|
|
29
29
|
contentDOM?: HTMLElement;
|
|
@@ -60,16 +60,16 @@ export type BlockFromConfigNoChildren<B extends BlockConfig, I extends InlineCon
|
|
|
60
60
|
content: B["content"] extends "inline" ? InlineContent<I, S>[] : B["content"] extends "table" ? TableContent<I, S> : B["content"] extends "none" ? undefined : never;
|
|
61
61
|
};
|
|
62
62
|
export type BlockFromConfig<B extends BlockConfig, I extends InlineContentSchema, S extends StyleSchema> = BlockFromConfigNoChildren<B, I, S> & {
|
|
63
|
-
children:
|
|
63
|
+
children: BlockNoDefaults<BlockSchema, I, S>[];
|
|
64
64
|
};
|
|
65
65
|
type BlocksWithoutChildren<BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema> = {
|
|
66
66
|
[BType in keyof BSchema]: BlockFromConfigNoChildren<BSchema[BType], I, S>;
|
|
67
67
|
};
|
|
68
|
-
export type
|
|
69
|
-
children:
|
|
68
|
+
export type BlockNoDefaults<BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema> = BlocksWithoutChildren<BSchema, I, S>[keyof BSchema] & {
|
|
69
|
+
children: BlockNoDefaults<BSchema, I, S>[];
|
|
70
70
|
};
|
|
71
71
|
export type SpecificBlock<BSchema extends BlockSchema, BType extends keyof BSchema, I extends InlineContentSchema, S extends StyleSchema> = BlocksWithoutChildren<BSchema, I, S>[BType] & {
|
|
72
|
-
children:
|
|
72
|
+
children: BlockNoDefaults<BSchema, I, S>[];
|
|
73
73
|
};
|
|
74
74
|
/** CODE FOR PARTIAL BLOCKS, analogous to above
|
|
75
75
|
*
|
|
@@ -92,14 +92,14 @@ type PartialBlockFromConfigNoChildren<B extends BlockConfig, I extends InlineCon
|
|
|
92
92
|
type PartialBlocksWithoutChildren<BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema> = {
|
|
93
93
|
[BType in keyof BSchema]: PartialBlockFromConfigNoChildren<BSchema[BType], I, S>;
|
|
94
94
|
};
|
|
95
|
-
export type
|
|
96
|
-
children:
|
|
95
|
+
export type PartialBlockNoDefaults<BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema> = PartialBlocksWithoutChildren<BSchema, I, S>[keyof PartialBlocksWithoutChildren<BSchema, I, S>] & Partial<{
|
|
96
|
+
children: PartialBlockNoDefaults<BSchema, I, S>[];
|
|
97
97
|
}>;
|
|
98
98
|
export type SpecificPartialBlock<BSchema extends BlockSchema, I extends InlineContentSchema, BType extends keyof BSchema, S extends StyleSchema> = PartialBlocksWithoutChildren<BSchema, I, S>[BType] & {
|
|
99
|
-
children?:
|
|
99
|
+
children?: BlockNoDefaults<BSchema, I, S>[];
|
|
100
100
|
};
|
|
101
101
|
export type PartialBlockFromConfig<B extends BlockConfig, I extends InlineContentSchema, S extends StyleSchema> = PartialBlockFromConfigNoChildren<B, I, S> & {
|
|
102
|
-
children?:
|
|
102
|
+
children?: BlockNoDefaults<BlockSchema, I, S>[];
|
|
103
103
|
};
|
|
104
104
|
export type BlockIdentifier = {
|
|
105
105
|
id: string;
|
|
@@ -37,7 +37,7 @@ export type InlineContentFromConfig<I extends InlineContentConfig, S extends Sty
|
|
|
37
37
|
export type PartialCustomInlineContentFromConfig<I extends CustomInlineContentConfig, S extends StyleSchema> = {
|
|
38
38
|
type: I["type"];
|
|
39
39
|
props?: Props<I["propSchema"]>;
|
|
40
|
-
content
|
|
40
|
+
content?: I["content"] extends "styled" ? StyledText<S>[] | string : I["content"] extends "plain" ? string : I["content"] extends "none" ? undefined : never;
|
|
41
41
|
};
|
|
42
42
|
export type PartialInlineContentFromConfig<I extends InlineContentConfig, S extends StyleSchema> = I extends "text" ? string | StyledText<S> : I extends "link" ? PartialLink<S> : I extends CustomInlineContentConfig ? PartialCustomInlineContentFromConfig<I, S> : never;
|
|
43
43
|
export type StyledText<T extends StyleSchema> = {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
2
|
-
import { SuggestionItem } from "../../extensions-shared/suggestion/SuggestionItem";
|
|
3
|
-
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
|
|
4
|
-
|
|
5
|
-
export type BaseSlashMenuItem<
|
|
6
|
-
BSchema extends BlockSchema,
|
|
7
|
-
I extends InlineContentSchema,
|
|
8
|
-
S extends StyleSchema
|
|
9
|
-
> = SuggestionItem & {
|
|
10
|
-
execute: (editor: BlockNoteEditor<BSchema, I, S>) => void;
|
|
11
|
-
aliases?: string[];
|
|
12
|
-
};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
-
|
|
3
|
-
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
4
|
-
import {
|
|
5
|
-
SuggestionsMenuState,
|
|
6
|
-
setupSuggestionsMenu,
|
|
7
|
-
} from "../../extensions-shared/suggestion/SuggestionPlugin";
|
|
8
|
-
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
|
|
9
|
-
import { EventEmitter } from "../../util/EventEmitter";
|
|
10
|
-
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
11
|
-
|
|
12
|
-
export const slashMenuPluginKey = new PluginKey("SlashMenuPlugin");
|
|
13
|
-
|
|
14
|
-
export class SlashMenuProsemirrorPlugin<
|
|
15
|
-
BSchema extends BlockSchema,
|
|
16
|
-
I extends InlineContentSchema,
|
|
17
|
-
S extends StyleSchema,
|
|
18
|
-
SlashMenuItem extends BaseSlashMenuItem<BSchema, I, S>
|
|
19
|
-
> extends EventEmitter<any> {
|
|
20
|
-
public readonly plugin: Plugin;
|
|
21
|
-
public readonly itemCallback: (item: SlashMenuItem) => void;
|
|
22
|
-
|
|
23
|
-
constructor(editor: BlockNoteEditor<BSchema, I, S>, items: SlashMenuItem[]) {
|
|
24
|
-
super();
|
|
25
|
-
const suggestions = setupSuggestionsMenu<SlashMenuItem, BSchema, I, S>(
|
|
26
|
-
editor,
|
|
27
|
-
(state) => {
|
|
28
|
-
this.emit("update", state);
|
|
29
|
-
},
|
|
30
|
-
slashMenuPluginKey,
|
|
31
|
-
"/",
|
|
32
|
-
(query) =>
|
|
33
|
-
items.filter(
|
|
34
|
-
({ name, aliases }: SlashMenuItem) =>
|
|
35
|
-
name.toLowerCase().startsWith(query.toLowerCase()) ||
|
|
36
|
-
(aliases &&
|
|
37
|
-
aliases.filter((alias) =>
|
|
38
|
-
alias.toLowerCase().startsWith(query.toLowerCase())
|
|
39
|
-
).length !== 0)
|
|
40
|
-
),
|
|
41
|
-
({ item, editor }) => item.execute(editor)
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
this.plugin = suggestions.plugin;
|
|
45
|
-
this.itemCallback = suggestions.itemCallback;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
public onUpdate(
|
|
49
|
-
callback: (state: SuggestionsMenuState<SlashMenuItem>) => void
|
|
50
|
-
) {
|
|
51
|
-
return this.on("update", callback);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
import { findParentNode } from "@tiptap/core";
|
|
2
|
-
import { EditorState, Plugin, PluginKey } from "prosemirror-state";
|
|
3
|
-
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
|
|
4
|
-
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
5
|
-
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
|
|
6
|
-
import { BaseUiElementState } from "../BaseUiElementTypes";
|
|
7
|
-
import { SuggestionItem } from "./SuggestionItem";
|
|
8
|
-
|
|
9
|
-
const findBlock = findParentNode((node) => node.type.name === "blockContainer");
|
|
10
|
-
|
|
11
|
-
export type SuggestionsMenuState<T extends SuggestionItem> =
|
|
12
|
-
BaseUiElementState & {
|
|
13
|
-
// The suggested items to display.
|
|
14
|
-
filteredItems: T[];
|
|
15
|
-
// The index of the suggested item that's currently hovered by the keyboard.
|
|
16
|
-
keyboardHoveredItemIndex: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
class SuggestionsMenuView<
|
|
20
|
-
T extends SuggestionItem,
|
|
21
|
-
BSchema extends BlockSchema,
|
|
22
|
-
I extends InlineContentSchema,
|
|
23
|
-
S extends StyleSchema
|
|
24
|
-
> {
|
|
25
|
-
private suggestionsMenuState?: SuggestionsMenuState<T>;
|
|
26
|
-
public updateSuggestionsMenu: () => void;
|
|
27
|
-
|
|
28
|
-
pluginState: SuggestionPluginState<T>;
|
|
29
|
-
|
|
30
|
-
constructor(
|
|
31
|
-
private readonly editor: BlockNoteEditor<BSchema, I, S>,
|
|
32
|
-
private readonly pluginKey: PluginKey,
|
|
33
|
-
updateSuggestionsMenu: (
|
|
34
|
-
suggestionsMenuState: SuggestionsMenuState<T>
|
|
35
|
-
) => void = () => {
|
|
36
|
-
// noop
|
|
37
|
-
}
|
|
38
|
-
) {
|
|
39
|
-
this.pluginState = getDefaultPluginState<T>();
|
|
40
|
-
|
|
41
|
-
this.updateSuggestionsMenu = () => {
|
|
42
|
-
if (!this.suggestionsMenuState) {
|
|
43
|
-
throw new Error("Attempting to update uninitialized suggestions menu");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
updateSuggestionsMenu(this.suggestionsMenuState);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
document.addEventListener("scroll", this.handleScroll);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
handleScroll = () => {
|
|
53
|
-
if (this.suggestionsMenuState?.show) {
|
|
54
|
-
const decorationNode = document.querySelector(
|
|
55
|
-
`[data-decoration-id="${this.pluginState.decorationId}"]`
|
|
56
|
-
);
|
|
57
|
-
this.suggestionsMenuState.referencePos =
|
|
58
|
-
decorationNode!.getBoundingClientRect();
|
|
59
|
-
this.updateSuggestionsMenu();
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
update(view: EditorView, prevState: EditorState) {
|
|
64
|
-
const prev = this.pluginKey.getState(prevState);
|
|
65
|
-
const next = this.pluginKey.getState(view.state);
|
|
66
|
-
|
|
67
|
-
// See how the state changed
|
|
68
|
-
const started = !prev.active && next.active;
|
|
69
|
-
const stopped = prev.active && !next.active;
|
|
70
|
-
// TODO: Currently also true for cases in which an update isn't needed so selected list item index updates still
|
|
71
|
-
// cause the view to update. May need to be more strict.
|
|
72
|
-
const changed = prev.active && next.active;
|
|
73
|
-
|
|
74
|
-
// Cancel when suggestion isn't active
|
|
75
|
-
if (!started && !changed && !stopped) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
this.pluginState = stopped ? prev : next;
|
|
80
|
-
|
|
81
|
-
if (stopped || !this.editor.isEditable) {
|
|
82
|
-
this.suggestionsMenuState!.show = false;
|
|
83
|
-
this.updateSuggestionsMenu();
|
|
84
|
-
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const decorationNode = document.querySelector(
|
|
89
|
-
`[data-decoration-id="${this.pluginState.decorationId}"]`
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (this.editor.isEditable) {
|
|
93
|
-
this.suggestionsMenuState = {
|
|
94
|
-
show: true,
|
|
95
|
-
referencePos: decorationNode!.getBoundingClientRect(),
|
|
96
|
-
filteredItems: this.pluginState.items,
|
|
97
|
-
keyboardHoveredItemIndex: this.pluginState.keyboardHoveredItemIndex!,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
this.updateSuggestionsMenu();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
destroy() {
|
|
105
|
-
document.removeEventListener("scroll", this.handleScroll);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
type SuggestionPluginState<T extends SuggestionItem> = {
|
|
110
|
-
// True when the menu is shown, false when hidden.
|
|
111
|
-
active: boolean;
|
|
112
|
-
// The character that triggered the menu being shown. Allowing the trigger to be different to the default
|
|
113
|
-
// trigger allows other extensions to open it programmatically.
|
|
114
|
-
triggerCharacter: string | undefined;
|
|
115
|
-
// The editor position just after the trigger character, i.e. where the user query begins. Used to figure out
|
|
116
|
-
// which menu items to show and can also be used to delete the trigger character.
|
|
117
|
-
queryStartPos: number | undefined;
|
|
118
|
-
// The items that should be shown in the menu.
|
|
119
|
-
items: T[];
|
|
120
|
-
// The index of the item in the menu that's currently hovered using the keyboard.
|
|
121
|
-
keyboardHoveredItemIndex: number | undefined;
|
|
122
|
-
// The number of characters typed after the last query that matched with at least 1 item. Used to close the
|
|
123
|
-
// menu if the user keeps entering queries that don't return any results.
|
|
124
|
-
notFoundCount: number | undefined;
|
|
125
|
-
decorationId: string | undefined;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
function getDefaultPluginState<
|
|
129
|
-
T extends SuggestionItem
|
|
130
|
-
>(): SuggestionPluginState<T> {
|
|
131
|
-
return {
|
|
132
|
-
active: false,
|
|
133
|
-
triggerCharacter: undefined,
|
|
134
|
-
queryStartPos: undefined,
|
|
135
|
-
items: [] as T[],
|
|
136
|
-
keyboardHoveredItemIndex: undefined,
|
|
137
|
-
notFoundCount: 0,
|
|
138
|
-
decorationId: undefined,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* A ProseMirror plugin for suggestions, designed to make '/'-commands possible as well as mentions.
|
|
144
|
-
*
|
|
145
|
-
* This is basically a simplified version of TipTap's [Suggestions](https://github.com/ueberdosis/tiptap/tree/db92a9b313c5993b723c85cd30256f1d4a0b65e1/packages/suggestion) plugin.
|
|
146
|
-
*
|
|
147
|
-
* This version is adapted from the aforementioned version in the following ways:
|
|
148
|
-
* - This version supports generic items instead of only strings (to allow for more advanced filtering for example)
|
|
149
|
-
* - This version hides some unnecessary complexity from the user of the plugin.
|
|
150
|
-
* - This version handles key events differently
|
|
151
|
-
*/
|
|
152
|
-
export const setupSuggestionsMenu = <
|
|
153
|
-
T extends SuggestionItem,
|
|
154
|
-
BSchema extends BlockSchema,
|
|
155
|
-
I extends InlineContentSchema,
|
|
156
|
-
S extends StyleSchema
|
|
157
|
-
>(
|
|
158
|
-
editor: BlockNoteEditor<BSchema, I, S>,
|
|
159
|
-
updateSuggestionsMenu: (
|
|
160
|
-
suggestionsMenuState: SuggestionsMenuState<T>
|
|
161
|
-
) => void,
|
|
162
|
-
|
|
163
|
-
pluginKey: PluginKey,
|
|
164
|
-
defaultTriggerCharacter: string,
|
|
165
|
-
items: (query: string) => T[] = () => [],
|
|
166
|
-
onSelectItem: (props: {
|
|
167
|
-
item: T;
|
|
168
|
-
editor: BlockNoteEditor<BSchema, I, S>;
|
|
169
|
-
}) => void = () => {
|
|
170
|
-
// noop
|
|
171
|
-
}
|
|
172
|
-
) => {
|
|
173
|
-
// Assertions
|
|
174
|
-
if (defaultTriggerCharacter.length !== 1) {
|
|
175
|
-
throw new Error("'char' should be a single character");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
let suggestionsPluginView: SuggestionsMenuView<T, BSchema, I, S>;
|
|
179
|
-
|
|
180
|
-
const deactivate = (view: EditorView) => {
|
|
181
|
-
view.dispatch(view.state.tr.setMeta(pluginKey, { deactivate: true }));
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
plugin: new Plugin({
|
|
186
|
-
key: pluginKey,
|
|
187
|
-
|
|
188
|
-
view: () => {
|
|
189
|
-
suggestionsPluginView = new SuggestionsMenuView<T, BSchema, I, S>(
|
|
190
|
-
editor,
|
|
191
|
-
pluginKey,
|
|
192
|
-
|
|
193
|
-
updateSuggestionsMenu
|
|
194
|
-
);
|
|
195
|
-
return suggestionsPluginView;
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
state: {
|
|
199
|
-
// Initialize the plugin's internal state.
|
|
200
|
-
init(): SuggestionPluginState<T> {
|
|
201
|
-
return getDefaultPluginState<T>();
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
// Apply changes to the plugin state from an editor transaction.
|
|
205
|
-
apply(transaction, prev, oldState, newState): SuggestionPluginState<T> {
|
|
206
|
-
// TODO: More clearly define which transactions should be ignored.
|
|
207
|
-
if (transaction.getMeta("orderedListIndexing") !== undefined) {
|
|
208
|
-
return prev;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Checks if the menu should be shown.
|
|
212
|
-
if (transaction.getMeta(pluginKey)?.activate) {
|
|
213
|
-
return {
|
|
214
|
-
active: true,
|
|
215
|
-
triggerCharacter:
|
|
216
|
-
transaction.getMeta(pluginKey)?.triggerCharacter || "",
|
|
217
|
-
queryStartPos: newState.selection.from,
|
|
218
|
-
items: items(""),
|
|
219
|
-
keyboardHoveredItemIndex: 0,
|
|
220
|
-
// TODO: Maybe should be 1 if the menu has no possible items? Probably redundant since a menu with no items
|
|
221
|
-
// is useless in practice.
|
|
222
|
-
notFoundCount: 0,
|
|
223
|
-
decorationId: `id_${Math.floor(Math.random() * 0xffffffff)}`,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Checks if the menu is hidden, in which case it doesn't need to be hidden or updated.
|
|
228
|
-
if (!prev.active) {
|
|
229
|
-
return prev;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const next = { ...prev };
|
|
233
|
-
|
|
234
|
-
// Updates which menu items to show by checking which items the current query (the text between the trigger
|
|
235
|
-
// character and caret) matches with.
|
|
236
|
-
next.items = items(
|
|
237
|
-
newState.doc.textBetween(
|
|
238
|
-
prev.queryStartPos!,
|
|
239
|
-
newState.selection.from
|
|
240
|
-
)
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
// Updates notFoundCount if the query doesn't match any items.
|
|
244
|
-
next.notFoundCount = 0;
|
|
245
|
-
if (next.items.length === 0) {
|
|
246
|
-
// Checks how many characters were typed or deleted since the last transaction, and updates the notFoundCount
|
|
247
|
-
// accordingly. Also ensures the notFoundCount does not become negative.
|
|
248
|
-
next.notFoundCount = Math.max(
|
|
249
|
-
0,
|
|
250
|
-
prev.notFoundCount! +
|
|
251
|
-
(newState.selection.from - oldState.selection.from)
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Hides the menu. This is done after items and notFoundCount are already updated as notFoundCount is needed to
|
|
256
|
-
// check if the menu should be hidden.
|
|
257
|
-
if (
|
|
258
|
-
// Highlighting text should hide the menu.
|
|
259
|
-
newState.selection.from !== newState.selection.to ||
|
|
260
|
-
// Transactions with plugin metadata {deactivate: true} should hide the menu.
|
|
261
|
-
transaction.getMeta(pluginKey)?.deactivate ||
|
|
262
|
-
// Certain mouse events should hide the menu.
|
|
263
|
-
// TODO: Change to global mousedown listener.
|
|
264
|
-
transaction.getMeta("focus") ||
|
|
265
|
-
transaction.getMeta("blur") ||
|
|
266
|
-
transaction.getMeta("pointer") ||
|
|
267
|
-
// Moving the caret before the character which triggered the menu should hide it.
|
|
268
|
-
(prev.active && newState.selection.from < prev.queryStartPos!) ||
|
|
269
|
-
// Entering more than 3 characters, after the last query that matched with at least 1 menu item, should hide
|
|
270
|
-
// the menu.
|
|
271
|
-
next.notFoundCount > 3
|
|
272
|
-
) {
|
|
273
|
-
return getDefaultPluginState<T>();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Updates keyboardHoveredItemIndex if the up or down arrow key was
|
|
277
|
-
// pressed, or resets it if the keyboard cursor moved.
|
|
278
|
-
if (
|
|
279
|
-
transaction.getMeta(pluginKey)?.selectedItemIndexChanged !==
|
|
280
|
-
undefined
|
|
281
|
-
) {
|
|
282
|
-
let newIndex =
|
|
283
|
-
transaction.getMeta(pluginKey).selectedItemIndexChanged;
|
|
284
|
-
|
|
285
|
-
// Allows selection to jump between first and last items.
|
|
286
|
-
if (newIndex < 0) {
|
|
287
|
-
newIndex = prev.items.length - 1;
|
|
288
|
-
} else if (newIndex >= prev.items.length) {
|
|
289
|
-
newIndex = 0;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
next.keyboardHoveredItemIndex = newIndex;
|
|
293
|
-
} else if (oldState.selection.from !== newState.selection.from) {
|
|
294
|
-
next.keyboardHoveredItemIndex = 0;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return next;
|
|
298
|
-
},
|
|
299
|
-
},
|
|
300
|
-
|
|
301
|
-
props: {
|
|
302
|
-
handleKeyDown(view, event) {
|
|
303
|
-
const menuIsActive = (this as Plugin).getState(view.state).active;
|
|
304
|
-
|
|
305
|
-
// Shows the menu if the default trigger character was pressed and the menu isn't active.
|
|
306
|
-
if (event.key === defaultTriggerCharacter && !menuIsActive) {
|
|
307
|
-
view.dispatch(
|
|
308
|
-
view.state.tr
|
|
309
|
-
.insertText(defaultTriggerCharacter)
|
|
310
|
-
.scrollIntoView()
|
|
311
|
-
.setMeta(pluginKey, {
|
|
312
|
-
activate: true,
|
|
313
|
-
triggerCharacter: defaultTriggerCharacter,
|
|
314
|
-
})
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
return true;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Doesn't handle other keystrokes if the menu isn't active.
|
|
321
|
-
if (!menuIsActive) {
|
|
322
|
-
return false;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Handles keystrokes for navigating the menu.
|
|
326
|
-
const {
|
|
327
|
-
triggerCharacter,
|
|
328
|
-
queryStartPos,
|
|
329
|
-
items,
|
|
330
|
-
keyboardHoveredItemIndex,
|
|
331
|
-
} = pluginKey.getState(view.state);
|
|
332
|
-
|
|
333
|
-
// Moves the keyboard selection to the previous item.
|
|
334
|
-
if (event.key === "ArrowUp") {
|
|
335
|
-
view.dispatch(
|
|
336
|
-
view.state.tr.setMeta(pluginKey, {
|
|
337
|
-
selectedItemIndexChanged: keyboardHoveredItemIndex - 1,
|
|
338
|
-
})
|
|
339
|
-
);
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Moves the keyboard selection to the next item.
|
|
344
|
-
if (event.key === "ArrowDown") {
|
|
345
|
-
view.dispatch(
|
|
346
|
-
view.state.tr.setMeta(pluginKey, {
|
|
347
|
-
selectedItemIndexChanged: keyboardHoveredItemIndex + 1,
|
|
348
|
-
})
|
|
349
|
-
);
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Selects an item and closes the menu.
|
|
354
|
-
if (event.key === "Enter") {
|
|
355
|
-
if (items.length === 0) {
|
|
356
|
-
return true;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
deactivate(view);
|
|
360
|
-
editor._tiptapEditor
|
|
361
|
-
.chain()
|
|
362
|
-
.focus()
|
|
363
|
-
.deleteRange({
|
|
364
|
-
from: queryStartPos! - triggerCharacter!.length,
|
|
365
|
-
to: editor._tiptapEditor.state.selection.from,
|
|
366
|
-
})
|
|
367
|
-
.run();
|
|
368
|
-
|
|
369
|
-
onSelectItem({
|
|
370
|
-
item: items[keyboardHoveredItemIndex],
|
|
371
|
-
editor: editor,
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
return true;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Closes the menu.
|
|
378
|
-
if (event.key === "Escape") {
|
|
379
|
-
deactivate(view);
|
|
380
|
-
return true;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return false;
|
|
384
|
-
},
|
|
385
|
-
|
|
386
|
-
// Setup decorator on the currently active suggestion.
|
|
387
|
-
decorations(state) {
|
|
388
|
-
const { active, decorationId, queryStartPos, triggerCharacter } = (
|
|
389
|
-
this as Plugin
|
|
390
|
-
).getState(state);
|
|
391
|
-
|
|
392
|
-
if (!active) {
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// If the menu was opened programmatically by another extension, it may not use a trigger character. In this
|
|
397
|
-
// case, the decoration is set on the whole block instead, as the decoration range would otherwise be empty.
|
|
398
|
-
if (triggerCharacter === "") {
|
|
399
|
-
const blockNode = findBlock(state.selection);
|
|
400
|
-
if (blockNode) {
|
|
401
|
-
return DecorationSet.create(state.doc, [
|
|
402
|
-
Decoration.node(
|
|
403
|
-
blockNode.pos,
|
|
404
|
-
blockNode.pos + blockNode.node.nodeSize,
|
|
405
|
-
{
|
|
406
|
-
nodeName: "span",
|
|
407
|
-
class: "bn-suggestion-decorator",
|
|
408
|
-
"data-decoration-id": decorationId,
|
|
409
|
-
}
|
|
410
|
-
),
|
|
411
|
-
]);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
// Creates an inline decoration around the trigger character.
|
|
415
|
-
return DecorationSet.create(state.doc, [
|
|
416
|
-
Decoration.inline(
|
|
417
|
-
queryStartPos - triggerCharacter.length,
|
|
418
|
-
queryStartPos,
|
|
419
|
-
{
|
|
420
|
-
nodeName: "span",
|
|
421
|
-
class: "bn-suggestion-decorator",
|
|
422
|
-
"data-decoration-id": decorationId,
|
|
423
|
-
}
|
|
424
|
-
),
|
|
425
|
-
]);
|
|
426
|
-
},
|
|
427
|
-
},
|
|
428
|
-
}),
|
|
429
|
-
itemCallback: (item: T) => {
|
|
430
|
-
deactivate(editor._tiptapEditor.view);
|
|
431
|
-
editor._tiptapEditor
|
|
432
|
-
.chain()
|
|
433
|
-
.focus()
|
|
434
|
-
.deleteRange({
|
|
435
|
-
from:
|
|
436
|
-
suggestionsPluginView.pluginState.queryStartPos! -
|
|
437
|
-
suggestionsPluginView.pluginState.triggerCharacter!.length,
|
|
438
|
-
to: editor._tiptapEditor.state.selection.from,
|
|
439
|
-
})
|
|
440
|
-
.run();
|
|
441
|
-
|
|
442
|
-
onSelectItem({
|
|
443
|
-
item: item,
|
|
444
|
-
editor: editor,
|
|
445
|
-
});
|
|
446
|
-
},
|
|
447
|
-
};
|
|
448
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
2
|
-
import { SuggestionItem } from "../../extensions-shared/suggestion/SuggestionItem";
|
|
3
|
-
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
|
|
4
|
-
export type BaseSlashMenuItem<BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema> = SuggestionItem & {
|
|
5
|
-
execute: (editor: BlockNoteEditor<BSchema, I, S>) => void;
|
|
6
|
-
aliases?: string[];
|
|
7
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
-
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
3
|
-
import { SuggestionsMenuState } from "../../extensions-shared/suggestion/SuggestionPlugin";
|
|
4
|
-
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
|
|
5
|
-
import { EventEmitter } from "../../util/EventEmitter";
|
|
6
|
-
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
7
|
-
export declare const slashMenuPluginKey: PluginKey<any>;
|
|
8
|
-
export declare class SlashMenuProsemirrorPlugin<BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema, SlashMenuItem extends BaseSlashMenuItem<BSchema, I, S>> extends EventEmitter<any> {
|
|
9
|
-
readonly plugin: Plugin;
|
|
10
|
-
readonly itemCallback: (item: SlashMenuItem) => void;
|
|
11
|
-
constructor(editor: BlockNoteEditor<BSchema, I, S>, items: SlashMenuItem[]);
|
|
12
|
-
onUpdate(callback: (state: SuggestionsMenuState<SlashMenuItem>) => void): () => void;
|
|
13
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { InlineContentSchema, StyleSchema } from "../../schema";
|
|
2
|
-
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
3
|
-
export declare const getDefaultSlashMenuItems: <BSchema extends Record<string, import("../../schema").BlockConfig>, I extends InlineContentSchema, S extends StyleSchema>(schema?: BSchema) => BaseSlashMenuItem<BSchema, I, S>[];
|