@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,51 +1,44 @@
|
|
|
1
1
|
import { EditorState, Plugin, PluginKey } from "prosemirror-state";
|
|
2
2
|
import { EditorView } from "prosemirror-view";
|
|
3
3
|
|
|
4
|
-
import { EventEmitter } from "../../util/EventEmitter";
|
|
5
4
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import type {
|
|
6
|
+
BlockFromConfig,
|
|
8
7
|
InlineContentSchema,
|
|
9
|
-
SpecificBlock,
|
|
10
8
|
StyleSchema,
|
|
11
9
|
} from "../../schema";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "../../extensions-shared/BaseUiElementTypes";
|
|
16
|
-
export type ImageToolbarCallbacks = BaseUiElementCallbacks;
|
|
10
|
+
import { UiElementPosition } from "../../extensions-shared/UiElementPosition";
|
|
11
|
+
import { EventEmitter } from "../../util/EventEmitter";
|
|
12
|
+
import { DefaultBlockSchema } from "../../blocks/defaultBlocks";
|
|
17
13
|
|
|
18
14
|
export type ImageToolbarState<
|
|
19
|
-
B extends BlockSchema,
|
|
20
15
|
I extends InlineContentSchema,
|
|
21
|
-
S extends StyleSchema
|
|
22
|
-
> =
|
|
23
|
-
|
|
16
|
+
S extends StyleSchema
|
|
17
|
+
> = UiElementPosition & {
|
|
18
|
+
// TODO: This typing is not quite right (children should be from BSchema)
|
|
19
|
+
block: BlockFromConfig<DefaultBlockSchema["image"], I, S>;
|
|
24
20
|
};
|
|
25
21
|
|
|
26
22
|
export class ImageToolbarView<
|
|
27
|
-
BSchema extends BlockSchema,
|
|
28
23
|
I extends InlineContentSchema,
|
|
29
24
|
S extends StyleSchema
|
|
30
25
|
> {
|
|
31
|
-
|
|
32
|
-
public
|
|
26
|
+
public state?: ImageToolbarState<I, S>;
|
|
27
|
+
public emitUpdate: () => void;
|
|
33
28
|
|
|
34
29
|
public prevWasEditable: boolean | null = null;
|
|
35
30
|
|
|
36
31
|
constructor(
|
|
37
32
|
private readonly pluginKey: PluginKey,
|
|
38
33
|
private readonly pmView: EditorView,
|
|
39
|
-
|
|
40
|
-
imageToolbarState: ImageToolbarState<BSchema, I, S>
|
|
41
|
-
) => void
|
|
34
|
+
emitUpdate: (state: ImageToolbarState<I, S>) => void
|
|
42
35
|
) {
|
|
43
|
-
this.
|
|
44
|
-
if (!this.
|
|
36
|
+
this.emitUpdate = () => {
|
|
37
|
+
if (!this.state) {
|
|
45
38
|
throw new Error("Attempting to update uninitialized image toolbar");
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
|
|
41
|
+
emitUpdate(this.state);
|
|
49
42
|
};
|
|
50
43
|
|
|
51
44
|
pmView.dom.addEventListener("mousedown", this.mouseDownHandler);
|
|
@@ -58,17 +51,17 @@ export class ImageToolbarView<
|
|
|
58
51
|
}
|
|
59
52
|
|
|
60
53
|
mouseDownHandler = () => {
|
|
61
|
-
if (this.
|
|
62
|
-
this.
|
|
63
|
-
this.
|
|
54
|
+
if (this.state?.show) {
|
|
55
|
+
this.state.show = false;
|
|
56
|
+
this.emitUpdate();
|
|
64
57
|
}
|
|
65
58
|
};
|
|
66
59
|
|
|
67
60
|
// For dragging the whole editor.
|
|
68
61
|
dragstartHandler = () => {
|
|
69
|
-
if (this.
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
62
|
+
if (this.state?.show) {
|
|
63
|
+
this.state.show = false;
|
|
64
|
+
this.emitUpdate();
|
|
72
65
|
}
|
|
73
66
|
};
|
|
74
67
|
|
|
@@ -88,41 +81,40 @@ export class ImageToolbarView<
|
|
|
88
81
|
return;
|
|
89
82
|
}
|
|
90
83
|
|
|
91
|
-
if (this.
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
84
|
+
if (this.state?.show) {
|
|
85
|
+
this.state.show = false;
|
|
86
|
+
this.emitUpdate();
|
|
94
87
|
}
|
|
95
88
|
};
|
|
96
89
|
|
|
97
90
|
scrollHandler = () => {
|
|
98
|
-
if (this.
|
|
91
|
+
if (this.state?.show) {
|
|
99
92
|
const blockElement = document.querySelector(
|
|
100
|
-
`[data-node-type="blockContainer"][data-id="${this.
|
|
93
|
+
`[data-node-type="blockContainer"][data-id="${this.state.block.id}"]`
|
|
101
94
|
)!;
|
|
102
95
|
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
this.updateImageToolbar();
|
|
96
|
+
this.state.referencePos = blockElement.getBoundingClientRect();
|
|
97
|
+
this.emitUpdate();
|
|
106
98
|
}
|
|
107
99
|
};
|
|
108
100
|
|
|
109
101
|
update(view: EditorView, prevState: EditorState) {
|
|
110
102
|
const pluginState: {
|
|
111
|
-
block:
|
|
103
|
+
block: BlockFromConfig<DefaultBlockSchema["image"], I, S>;
|
|
112
104
|
} = this.pluginKey.getState(view.state);
|
|
113
105
|
|
|
114
|
-
if (!this.
|
|
106
|
+
if (!this.state?.show && pluginState.block) {
|
|
115
107
|
const blockElement = document.querySelector(
|
|
116
108
|
`[data-node-type="blockContainer"][data-id="${pluginState.block.id}"]`
|
|
117
109
|
)!;
|
|
118
110
|
|
|
119
|
-
this.
|
|
111
|
+
this.state = {
|
|
120
112
|
show: true,
|
|
121
113
|
referencePos: blockElement.getBoundingClientRect(),
|
|
122
114
|
block: pluginState.block,
|
|
123
115
|
};
|
|
124
116
|
|
|
125
|
-
this.
|
|
117
|
+
this.emitUpdate();
|
|
126
118
|
|
|
127
119
|
return;
|
|
128
120
|
}
|
|
@@ -131,10 +123,10 @@ export class ImageToolbarView<
|
|
|
131
123
|
!view.state.selection.eq(prevState.selection) ||
|
|
132
124
|
!view.state.doc.eq(prevState.doc)
|
|
133
125
|
) {
|
|
134
|
-
if (this.
|
|
135
|
-
this.
|
|
126
|
+
if (this.state?.show) {
|
|
127
|
+
this.state.show = false;
|
|
136
128
|
|
|
137
|
-
this.
|
|
129
|
+
this.emitUpdate();
|
|
138
130
|
}
|
|
139
131
|
}
|
|
140
132
|
}
|
|
@@ -150,20 +142,21 @@ export class ImageToolbarView<
|
|
|
150
142
|
}
|
|
151
143
|
}
|
|
152
144
|
|
|
153
|
-
|
|
145
|
+
const imageToolbarPluginKey = new PluginKey("ImageToolbarPlugin");
|
|
154
146
|
|
|
155
147
|
export class ImageToolbarProsemirrorPlugin<
|
|
156
|
-
BSchema extends BlockSchema,
|
|
157
148
|
I extends InlineContentSchema,
|
|
158
149
|
S extends StyleSchema
|
|
159
150
|
> extends EventEmitter<any> {
|
|
160
|
-
private view: ImageToolbarView<
|
|
151
|
+
private view: ImageToolbarView<I, S> | undefined;
|
|
161
152
|
public readonly plugin: Plugin;
|
|
162
153
|
|
|
163
|
-
constructor(
|
|
154
|
+
constructor(
|
|
155
|
+
_editor: BlockNoteEditor<{ image: DefaultBlockSchema["image"] }, I, S>
|
|
156
|
+
) {
|
|
164
157
|
super();
|
|
165
158
|
this.plugin = new Plugin<{
|
|
166
|
-
block:
|
|
159
|
+
block: BlockFromConfig<DefaultBlockSchema["image"], I, S> | undefined;
|
|
167
160
|
}>({
|
|
168
161
|
key: imageToolbarPluginKey,
|
|
169
162
|
view: (editorView) => {
|
|
@@ -184,8 +177,9 @@ export class ImageToolbarProsemirrorPlugin<
|
|
|
184
177
|
};
|
|
185
178
|
},
|
|
186
179
|
apply: (transaction) => {
|
|
187
|
-
const block:
|
|
188
|
-
|
|
180
|
+
const block:
|
|
181
|
+
| BlockFromConfig<DefaultBlockSchema["image"], I, S>
|
|
182
|
+
| undefined = transaction.getMeta(imageToolbarPluginKey)?.block;
|
|
189
183
|
|
|
190
184
|
return {
|
|
191
185
|
block,
|
|
@@ -195,7 +189,7 @@ export class ImageToolbarProsemirrorPlugin<
|
|
|
195
189
|
});
|
|
196
190
|
}
|
|
197
191
|
|
|
198
|
-
public onUpdate(callback: (state: ImageToolbarState<
|
|
192
|
+
public onUpdate(callback: (state: ImageToolbarState<I, S>) => void) {
|
|
199
193
|
return this.on("update", callback);
|
|
200
194
|
}
|
|
201
195
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Node as ProsemirrorNode } from "prosemirror-model";
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
3
2
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
4
3
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
5
|
-
import { slashMenuPluginKey } from "../SlashMenu/SlashMenuPlugin";
|
|
6
4
|
|
|
7
5
|
const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
|
|
8
6
|
|
|
@@ -14,21 +12,7 @@ const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
|
|
|
14
12
|
*
|
|
15
13
|
*/
|
|
16
14
|
export interface PlaceholderOptions {
|
|
17
|
-
|
|
18
|
-
emptyNodeClass: string;
|
|
19
|
-
isFilterClass: string;
|
|
20
|
-
hasAnchorClass: string;
|
|
21
|
-
placeholder:
|
|
22
|
-
| ((PlaceholderProps: {
|
|
23
|
-
editor: Editor;
|
|
24
|
-
node: ProsemirrorNode;
|
|
25
|
-
pos: number;
|
|
26
|
-
hasAnchor: boolean;
|
|
27
|
-
}) => string)
|
|
28
|
-
| string;
|
|
29
|
-
showOnlyWhenEditable: boolean;
|
|
30
|
-
showOnlyCurrent: boolean;
|
|
31
|
-
includeChildren: boolean;
|
|
15
|
+
placeholders: Record<string | "default", string>;
|
|
32
16
|
}
|
|
33
17
|
|
|
34
18
|
export const Placeholder = Extension.create<PlaceholderOptions>({
|
|
@@ -36,93 +20,102 @@ export const Placeholder = Extension.create<PlaceholderOptions>({
|
|
|
36
20
|
|
|
37
21
|
addOptions() {
|
|
38
22
|
return {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
showOnlyCurrent: true,
|
|
46
|
-
includeChildren: false,
|
|
23
|
+
placeholders: {
|
|
24
|
+
default: "Enter text or type '/' for commands",
|
|
25
|
+
heading: "Heading",
|
|
26
|
+
bulletListItem: "List",
|
|
27
|
+
numberedListItem: "List",
|
|
28
|
+
},
|
|
47
29
|
};
|
|
48
30
|
},
|
|
49
31
|
|
|
50
32
|
addProseMirrorPlugins() {
|
|
33
|
+
const placeholders = this.options.placeholders;
|
|
51
34
|
return [
|
|
52
35
|
new Plugin({
|
|
53
36
|
key: PLUGIN_KEY,
|
|
37
|
+
view: () => {
|
|
38
|
+
const styleEl = document.createElement("style");
|
|
39
|
+
document.head.appendChild(styleEl);
|
|
40
|
+
const styleSheet = styleEl.sheet!;
|
|
41
|
+
|
|
42
|
+
const getBaseSelector = (additionalSelectors = "") =>
|
|
43
|
+
`.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak):before`;
|
|
44
|
+
|
|
45
|
+
const getSelector = (
|
|
46
|
+
blockType: string | "default",
|
|
47
|
+
mustBeFocused = true
|
|
48
|
+
) => {
|
|
49
|
+
const mustBeFocusedSelector = mustBeFocused
|
|
50
|
+
? `[data-is-empty-and-focused]`
|
|
51
|
+
: ``;
|
|
52
|
+
|
|
53
|
+
if (blockType === "default") {
|
|
54
|
+
return getBaseSelector(mustBeFocusedSelector);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const blockTypeSelector = `[data-content-type="${blockType}"]`;
|
|
58
|
+
return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
for (const [blockType, placeholder] of Object.entries(placeholders)) {
|
|
62
|
+
const mustBeFocused = blockType === "default";
|
|
63
|
+
|
|
64
|
+
styleSheet.insertRule(
|
|
65
|
+
`${getSelector(
|
|
66
|
+
blockType,
|
|
67
|
+
mustBeFocused
|
|
68
|
+
)}{ content: ${JSON.stringify(placeholder)}; }`
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// For some reason, the placeholders which show when the block is focused
|
|
72
|
+
// take priority over ones which show depending on block type, so we need
|
|
73
|
+
// to make sure the block specific ones are also used when the block is
|
|
74
|
+
// focused.
|
|
75
|
+
if (!mustBeFocused) {
|
|
76
|
+
styleSheet.insertRule(
|
|
77
|
+
`${getSelector(blockType, true)}{ content: ${JSON.stringify(
|
|
78
|
+
placeholder
|
|
79
|
+
)}; }`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
destroy: () => {
|
|
86
|
+
document.head.removeChild(styleEl);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
54
90
|
props: {
|
|
91
|
+
// TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
|
|
55
92
|
decorations: (state) => {
|
|
56
93
|
const { doc, selection } = state;
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const active =
|
|
60
|
-
this.editor.isEditable || !this.options.showOnlyWhenEditable;
|
|
61
|
-
const { anchor } = selection;
|
|
62
|
-
const decorations: Decoration[] = [];
|
|
94
|
+
|
|
95
|
+
const active = this.editor.isEditable;
|
|
63
96
|
|
|
64
97
|
if (!active) {
|
|
65
98
|
return;
|
|
66
99
|
}
|
|
67
100
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// If slash menu is of drag type and active, show the filter placeholder
|
|
85
|
-
if (menuState?.triggerCharacter === "" && menuState?.active) {
|
|
86
|
-
classes.push(this.options.isFilterClass);
|
|
87
|
-
}
|
|
88
|
-
// using widget, didn't work (caret position bug)
|
|
89
|
-
// const decoration = Decoration.widget(
|
|
90
|
-
// pos + 1,
|
|
91
|
-
// () => {
|
|
92
|
-
// const el = document.createElement("span");
|
|
93
|
-
// el.innerText = "hello";
|
|
94
|
-
// return el;
|
|
95
|
-
// },
|
|
96
|
-
// { side: 0 }
|
|
97
|
-
|
|
98
|
-
// Code that sets variables / classes
|
|
99
|
-
// const ph =
|
|
100
|
-
// typeof this.options.placeholder === "function"
|
|
101
|
-
// ? this.options.placeholder({
|
|
102
|
-
// editor: this.editor,
|
|
103
|
-
// node,
|
|
104
|
-
// pos,
|
|
105
|
-
// hasAnchor,
|
|
106
|
-
// })
|
|
107
|
-
// : this.options.placeholder;
|
|
108
|
-
// const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
|
109
|
-
// class: classes.join(" "),
|
|
110
|
-
// style: `--placeholder:'${ph.replaceAll("'", "\\'")}';`,
|
|
111
|
-
// "data-placeholder": ph,
|
|
112
|
-
// });
|
|
113
|
-
|
|
114
|
-
// Latest version, only set isEmpty and hasAnchor, rest is done via CSS
|
|
115
|
-
|
|
116
|
-
const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
|
117
|
-
class: classes.join(" "),
|
|
118
|
-
});
|
|
119
|
-
decorations.push(decoration);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return this.options.includeChildren;
|
|
101
|
+
if (!selection.empty) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const $pos = selection.$anchor;
|
|
106
|
+
const node = $pos.parent;
|
|
107
|
+
|
|
108
|
+
if (node.content.size > 0) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const before = $pos.before();
|
|
113
|
+
|
|
114
|
+
const dec = Decoration.node(before, before + node.nodeSize, {
|
|
115
|
+
"data-is-empty-and-focused": "true",
|
|
123
116
|
});
|
|
124
117
|
|
|
125
|
-
return DecorationSet.create(doc,
|
|
118
|
+
return DecorationSet.create(doc, [dec]);
|
|
126
119
|
},
|
|
127
120
|
},
|
|
128
121
|
}),
|
|
@@ -2,20 +2,17 @@ import { PluginView } from "@tiptap/pm/state";
|
|
|
2
2
|
import { Node } from "prosemirror-model";
|
|
3
3
|
import { NodeSelection, Plugin, PluginKey, Selection } from "prosemirror-state";
|
|
4
4
|
import { EditorView } from "prosemirror-view";
|
|
5
|
+
|
|
5
6
|
import { createExternalHTMLExporter } from "../../api/exporters/html/externalHTMLExporter";
|
|
6
7
|
import { createInternalHTMLSerializer } from "../../api/exporters/html/internalHTMLSerializer";
|
|
7
8
|
import { cleanHTMLToMarkdown } from "../../api/exporters/markdown/markdownExporter";
|
|
8
9
|
import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos";
|
|
10
|
+
import { Block } from "../../blocks/defaultBlocks";
|
|
9
11
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
Block,
|
|
13
|
-
BlockSchema,
|
|
14
|
-
InlineContentSchema,
|
|
15
|
-
StyleSchema,
|
|
16
|
-
} from "../../schema";
|
|
12
|
+
import { UiElementPosition } from "../../extensions-shared/UiElementPosition";
|
|
13
|
+
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
|
|
17
14
|
import { EventEmitter } from "../../util/EventEmitter";
|
|
18
|
-
import {
|
|
15
|
+
import { suggestionMenuPluginKey } from "../SuggestionMenu/SuggestionPlugin";
|
|
19
16
|
import { MultipleNodeSelection } from "./MultipleNodeSelection";
|
|
20
17
|
|
|
21
18
|
let dragImageElement: Element | undefined;
|
|
@@ -24,7 +21,7 @@ export type SideMenuState<
|
|
|
24
21
|
BSchema extends BlockSchema,
|
|
25
22
|
I extends InlineContentSchema,
|
|
26
23
|
S extends StyleSchema
|
|
27
|
-
> =
|
|
24
|
+
> = UiElementPosition & {
|
|
28
25
|
// The block that the side menu is attached to.
|
|
29
26
|
block: Block<BSchema, I, S>;
|
|
30
27
|
};
|
|
@@ -255,7 +252,8 @@ export class SideMenuView<
|
|
|
255
252
|
S extends StyleSchema
|
|
256
253
|
> implements PluginView
|
|
257
254
|
{
|
|
258
|
-
private
|
|
255
|
+
private state?: SideMenuState<BSchema, I, S>;
|
|
256
|
+
private readonly emitUpdate: (state: SideMenuState<BSchema, I, S>) => void;
|
|
259
257
|
|
|
260
258
|
// When true, the drag handle with be anchored at the same level as root elements
|
|
261
259
|
// When false, the drag handle with be just to the left of the element
|
|
@@ -273,10 +271,16 @@ export class SideMenuView<
|
|
|
273
271
|
constructor(
|
|
274
272
|
private readonly editor: BlockNoteEditor<BSchema, I, S>,
|
|
275
273
|
private readonly pmView: EditorView,
|
|
276
|
-
|
|
277
|
-
sideMenuState: SideMenuState<BSchema, I, S>
|
|
278
|
-
) => void
|
|
274
|
+
emitUpdate: (state: SideMenuState<BSchema, I, S>) => void
|
|
279
275
|
) {
|
|
276
|
+
this.emitUpdate = () => {
|
|
277
|
+
if (!this.state) {
|
|
278
|
+
throw new Error("Attempting to update uninitialized side menu");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
emitUpdate(this.state);
|
|
282
|
+
};
|
|
283
|
+
|
|
280
284
|
this.horizontalPosAnchoredAtRoot = true;
|
|
281
285
|
this.horizontalPosAnchor = (
|
|
282
286
|
this.pmView.dom.firstChild! as HTMLElement
|
|
@@ -369,17 +373,17 @@ export class SideMenuView<
|
|
|
369
373
|
};
|
|
370
374
|
|
|
371
375
|
onKeyDown = (_event: KeyboardEvent) => {
|
|
372
|
-
if (this.
|
|
373
|
-
this.
|
|
374
|
-
this.
|
|
376
|
+
if (this.state?.show) {
|
|
377
|
+
this.state.show = false;
|
|
378
|
+
this.emitUpdate(this.state);
|
|
375
379
|
}
|
|
376
380
|
this.menuFrozen = false;
|
|
377
381
|
};
|
|
378
382
|
|
|
379
383
|
onMouseDown = (_event: MouseEvent) => {
|
|
380
|
-
if (this.
|
|
381
|
-
this.
|
|
382
|
-
this.
|
|
384
|
+
if (this.state && !this.state.show) {
|
|
385
|
+
this.state.show = true;
|
|
386
|
+
this.emitUpdate(this.state);
|
|
383
387
|
}
|
|
384
388
|
this.menuFrozen = false;
|
|
385
389
|
};
|
|
@@ -421,9 +425,9 @@ export class SideMenuView<
|
|
|
421
425
|
editorWrapper.contains(event.target as HTMLElement)
|
|
422
426
|
)
|
|
423
427
|
) {
|
|
424
|
-
if (this.
|
|
425
|
-
this.
|
|
426
|
-
this.
|
|
428
|
+
if (this.state?.show) {
|
|
429
|
+
this.state.show = false;
|
|
430
|
+
this.emitUpdate(this.state);
|
|
427
431
|
}
|
|
428
432
|
|
|
429
433
|
return;
|
|
@@ -440,9 +444,9 @@ export class SideMenuView<
|
|
|
440
444
|
|
|
441
445
|
// Closes the menu if the mouse cursor is beyond the editor vertically.
|
|
442
446
|
if (!block || !this.editor.isEditable) {
|
|
443
|
-
if (this.
|
|
444
|
-
this.
|
|
445
|
-
this.
|
|
447
|
+
if (this.state?.show) {
|
|
448
|
+
this.state.show = false;
|
|
449
|
+
this.emitUpdate(this.state);
|
|
446
450
|
}
|
|
447
451
|
|
|
448
452
|
return;
|
|
@@ -450,7 +454,7 @@ export class SideMenuView<
|
|
|
450
454
|
|
|
451
455
|
// Doesn't update if the menu is already open and the mouse cursor is still hovering the same block.
|
|
452
456
|
if (
|
|
453
|
-
this.
|
|
457
|
+
this.state?.show &&
|
|
454
458
|
this.hoveredBlock?.hasAttribute("data-id") &&
|
|
455
459
|
this.hoveredBlock?.getAttribute("data-id") === block.id
|
|
456
460
|
) {
|
|
@@ -470,7 +474,7 @@ export class SideMenuView<
|
|
|
470
474
|
if (this.editor.isEditable) {
|
|
471
475
|
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
472
476
|
|
|
473
|
-
this.
|
|
477
|
+
this.state = {
|
|
474
478
|
show: true,
|
|
475
479
|
referencePos: new DOMRect(
|
|
476
480
|
this.horizontalPosAnchoredAtRoot
|
|
@@ -485,16 +489,16 @@ export class SideMenuView<
|
|
|
485
489
|
)!,
|
|
486
490
|
};
|
|
487
491
|
|
|
488
|
-
this.
|
|
492
|
+
this.emitUpdate(this.state);
|
|
489
493
|
}
|
|
490
494
|
};
|
|
491
495
|
|
|
492
496
|
onScroll = () => {
|
|
493
|
-
if (this.
|
|
497
|
+
if (this.state?.show) {
|
|
494
498
|
const blockContent = this.hoveredBlock!.firstChild as HTMLElement;
|
|
495
499
|
const blockContentBoundingBox = blockContent.getBoundingClientRect();
|
|
496
500
|
|
|
497
|
-
this.
|
|
501
|
+
this.state.referencePos = new DOMRect(
|
|
498
502
|
this.horizontalPosAnchoredAtRoot
|
|
499
503
|
? this.horizontalPosAnchor
|
|
500
504
|
: blockContentBoundingBox.x,
|
|
@@ -502,16 +506,16 @@ export class SideMenuView<
|
|
|
502
506
|
blockContentBoundingBox.width,
|
|
503
507
|
blockContentBoundingBox.height
|
|
504
508
|
);
|
|
505
|
-
this.
|
|
509
|
+
this.emitUpdate(this.state);
|
|
506
510
|
}
|
|
507
511
|
};
|
|
508
512
|
|
|
509
513
|
destroy() {
|
|
510
|
-
if (this.
|
|
511
|
-
this.
|
|
512
|
-
this.
|
|
514
|
+
if (this.state?.show) {
|
|
515
|
+
this.state.show = false;
|
|
516
|
+
this.emitUpdate(this.state);
|
|
513
517
|
}
|
|
514
|
-
document.body.removeEventListener("mousemove", this.onMouseMove);
|
|
518
|
+
document.body.removeEventListener("mousemove", this.onMouseMove, true);
|
|
515
519
|
document.body.removeEventListener("dragover", this.onDragOver);
|
|
516
520
|
this.pmView.dom.removeEventListener("dragstart", this.onDragStart);
|
|
517
521
|
document.body.removeEventListener("drop", this.onDrop, true);
|
|
@@ -521,9 +525,9 @@ export class SideMenuView<
|
|
|
521
525
|
}
|
|
522
526
|
|
|
523
527
|
addBlock() {
|
|
524
|
-
if (this.
|
|
525
|
-
this.
|
|
526
|
-
this.
|
|
528
|
+
if (this.state?.show) {
|
|
529
|
+
this.state.show = false;
|
|
530
|
+
this.emitUpdate(this.state);
|
|
527
531
|
}
|
|
528
532
|
|
|
529
533
|
this.menuFrozen = true;
|
|
@@ -560,7 +564,7 @@ export class SideMenuView<
|
|
|
560
564
|
this.editor._tiptapEditor
|
|
561
565
|
.chain()
|
|
562
566
|
.BNCreateBlock(newBlockInsertionPos)
|
|
563
|
-
.BNUpdateBlock(newBlockContentPos, { type: "paragraph", props: {} })
|
|
567
|
+
// .BNUpdateBlock(newBlockContentPos, { type: "paragraph", props: {} })
|
|
564
568
|
.setTextSelection(newBlockContentPos)
|
|
565
569
|
.run();
|
|
566
570
|
} else {
|
|
@@ -570,10 +574,9 @@ export class SideMenuView<
|
|
|
570
574
|
// Focuses and activates the suggestion menu.
|
|
571
575
|
this.pmView.focus();
|
|
572
576
|
this.pmView.dispatch(
|
|
573
|
-
this.pmView.state.tr.scrollIntoView().setMeta(
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
type: "drag",
|
|
577
|
+
this.pmView.state.tr.scrollIntoView().setMeta(suggestionMenuPluginKey, {
|
|
578
|
+
triggerCharacter: "/",
|
|
579
|
+
fromUserInput: false,
|
|
577
580
|
})
|
|
578
581
|
);
|
|
579
582
|
}
|
|
@@ -586,7 +589,7 @@ export class SideMenuProsemirrorPlugin<
|
|
|
586
589
|
I extends InlineContentSchema,
|
|
587
590
|
S extends StyleSchema
|
|
588
591
|
> extends EventEmitter<any> {
|
|
589
|
-
|
|
592
|
+
public view: SideMenuView<BSchema, I, S> | undefined;
|
|
590
593
|
public readonly plugin: Plugin;
|
|
591
594
|
|
|
592
595
|
constructor(private readonly editor: BlockNoteEditor<BSchema, I, S>) {
|
|
@@ -594,14 +597,10 @@ export class SideMenuProsemirrorPlugin<
|
|
|
594
597
|
this.plugin = new Plugin({
|
|
595
598
|
key: sideMenuPluginKey,
|
|
596
599
|
view: (editorView) => {
|
|
597
|
-
this.
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
this.emit("update", sideMenuState);
|
|
602
|
-
}
|
|
603
|
-
);
|
|
604
|
-
return this.sideMenuView;
|
|
600
|
+
this.view = new SideMenuView(editor, editorView, (state) => {
|
|
601
|
+
this.emit("update", state);
|
|
602
|
+
});
|
|
603
|
+
return this.view;
|
|
605
604
|
},
|
|
606
605
|
});
|
|
607
606
|
}
|
|
@@ -614,7 +613,7 @@ export class SideMenuProsemirrorPlugin<
|
|
|
614
613
|
* If the block is empty, opens the slash menu. If the block has content,
|
|
615
614
|
* creates a new block below and opens the slash menu in it.
|
|
616
615
|
*/
|
|
617
|
-
addBlock = () => this.
|
|
616
|
+
addBlock = () => this.view!.addBlock();
|
|
618
617
|
|
|
619
618
|
/**
|
|
620
619
|
* Handles drag & drop events for blocks.
|
|
@@ -623,7 +622,7 @@ export class SideMenuProsemirrorPlugin<
|
|
|
623
622
|
dataTransfer: DataTransfer | null;
|
|
624
623
|
clientY: number;
|
|
625
624
|
}) => {
|
|
626
|
-
this.
|
|
625
|
+
this.view!.isDragging = true;
|
|
627
626
|
dragStart(event, this.editor);
|
|
628
627
|
};
|
|
629
628
|
|
|
@@ -636,11 +635,11 @@ export class SideMenuProsemirrorPlugin<
|
|
|
636
635
|
* attached to the same block regardless of which block is hovered by the
|
|
637
636
|
* mouse cursor.
|
|
638
637
|
*/
|
|
639
|
-
freezeMenu = () => (this.
|
|
638
|
+
freezeMenu = () => (this.view!.menuFrozen = true);
|
|
640
639
|
/**
|
|
641
640
|
* Unfreezes the side menu. When frozen, the side menu will stay
|
|
642
641
|
* attached to the same block regardless of which block is hovered by the
|
|
643
642
|
* mouse cursor.
|
|
644
643
|
*/
|
|
645
|
-
unfreezeMenu = () => (this.
|
|
644
|
+
unfreezeMenu = () => (this.view!.menuFrozen = false);
|
|
646
645
|
}
|