@blocknote/core 0.4.2 → 0.4.3
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/blocknote.js +12265 -12287
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +20 -20
- package/dist/blocknote.umd.cjs.map +1 -1
- package/package.json +2 -2
- package/src/BlockNoteEditor.ts +232 -10
- package/src/BlockNoteExtensions.ts +8 -4
- package/src/extensions/Blocks/api/cursorPositionTypes.ts +2 -0
- package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +31 -0
- package/src/extensions/SlashMenu/SlashMenuExtension.ts +10 -7
- package/src/extensions/SlashMenu/{defaultSlashCommands.tsx → defaultSlashMenuItems.tsx} +59 -106
- package/src/extensions/SlashMenu/index.ts +3 -7
- package/src/index.ts +2 -3
- package/src/shared/plugins/suggestion/SuggestionItem.ts +2 -13
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +31 -18
- package/types/src/BlockNoteEditor.d.ts +80 -6
- package/types/src/BlockNoteExtensions.d.ts +5 -4
- package/types/src/extensions/Blocks/api/cursorPositionTypes.d.ts +2 -0
- package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +20 -0
- package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +4 -2
- package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +5 -0
- package/types/src/extensions/SlashMenu/index.d.ts +3 -3
- package/types/src/index.d.ts +2 -3
- package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +3 -11
- package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +4 -4
- package/src/api/Editor.ts +0 -226
- package/src/extensions/SlashMenu/SlashMenuItem.ts +0 -34
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://github.com/yousefed/blocknote",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "0.4.
|
|
6
|
+
"version": "0.4.3",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"types",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
"access": "public",
|
|
107
107
|
"registry": "https://registry.npmjs.org/"
|
|
108
108
|
},
|
|
109
|
-
"gitHead": "
|
|
109
|
+
"gitHead": "4f578e0a8676b815316bdeb978cd4748d948868c"
|
|
110
110
|
}
|
package/src/BlockNoteEditor.ts
CHANGED
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import { Editor, EditorOptions } from "@tiptap/core";
|
|
2
|
-
|
|
2
|
+
import { Node } from "prosemirror-model";
|
|
3
3
|
// import "./blocknote.css";
|
|
4
|
-
import {
|
|
4
|
+
import { Block, PartialBlock } from "./extensions/Blocks/api/blockTypes";
|
|
5
5
|
import { getBlockNoteExtensions, UiFactories } from "./BlockNoteExtensions";
|
|
6
6
|
import styles from "./editor.module.css";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
defaultSlashMenuItems,
|
|
9
|
+
BaseSlashMenuItem,
|
|
10
|
+
} from "./extensions/SlashMenu";
|
|
11
|
+
import { Editor as TiptapEditor } from "@tiptap/core/dist/packages/core/src/Editor";
|
|
12
|
+
import { nodeToBlock } from "./api/nodeConversions/nodeConversions";
|
|
13
|
+
import { TextCursorPosition } from "./extensions/Blocks/api/cursorPositionTypes";
|
|
14
|
+
import { getBlockInfoFromPos } from "./extensions/Blocks/helpers/getBlockInfoFromPos";
|
|
15
|
+
import { getNodeById } from "./api/util/nodeUtil";
|
|
16
|
+
import {
|
|
17
|
+
insertBlocks,
|
|
18
|
+
updateBlock,
|
|
19
|
+
removeBlocks,
|
|
20
|
+
replaceBlocks,
|
|
21
|
+
} from "./api/blockManipulation/blockManipulation";
|
|
22
|
+
import {
|
|
23
|
+
blocksToHTML,
|
|
24
|
+
HTMLToBlocks,
|
|
25
|
+
blocksToMarkdown,
|
|
26
|
+
markdownToBlocks,
|
|
27
|
+
} from "./api/formatConversions/formatConversions";
|
|
8
28
|
|
|
9
29
|
export type BlockNoteEditorOptions = {
|
|
10
30
|
// TODO: Figure out if enableBlockNoteExtensions/disableHistoryExtension are needed and document them.
|
|
11
31
|
enableBlockNoteExtensions: boolean;
|
|
12
32
|
disableHistoryExtension: boolean;
|
|
13
33
|
uiFactories: UiFactories;
|
|
14
|
-
slashCommands:
|
|
34
|
+
slashCommands: BaseSlashMenuItem[];
|
|
15
35
|
parentElement: HTMLElement;
|
|
16
36
|
editorDOMAttributes: Record<string, string>;
|
|
17
37
|
onUpdate: (editor: BlockNoteEditor) => void;
|
|
@@ -27,8 +47,9 @@ const blockNoteTipTapOptions = {
|
|
|
27
47
|
enableCoreExtensions: false,
|
|
28
48
|
};
|
|
29
49
|
|
|
30
|
-
export class BlockNoteEditor
|
|
31
|
-
public readonly _tiptapEditor:
|
|
50
|
+
export class BlockNoteEditor {
|
|
51
|
+
public readonly _tiptapEditor: TiptapEditor & { contentComponent: any };
|
|
52
|
+
private blockCache = new WeakMap<Node, Block>();
|
|
32
53
|
|
|
33
54
|
public get domElement() {
|
|
34
55
|
return this._tiptapEditor.view.dom as HTMLDivElement;
|
|
@@ -36,8 +57,9 @@ export class BlockNoteEditor extends EditorAPI {
|
|
|
36
57
|
|
|
37
58
|
constructor(options: Partial<BlockNoteEditorOptions> = {}) {
|
|
38
59
|
const blockNoteExtensions = getBlockNoteExtensions({
|
|
60
|
+
editor: this,
|
|
39
61
|
uiFactories: options.uiFactories || {},
|
|
40
|
-
slashCommands: options.slashCommands ||
|
|
62
|
+
slashCommands: options.slashCommands || defaultSlashMenuItems,
|
|
41
63
|
});
|
|
42
64
|
|
|
43
65
|
let extensions = options.disableHistoryExtension
|
|
@@ -69,10 +91,210 @@ export class BlockNoteEditor extends EditorAPI {
|
|
|
69
91
|
},
|
|
70
92
|
};
|
|
71
93
|
|
|
72
|
-
|
|
94
|
+
this._tiptapEditor = new Editor(tiptapOptions) as Editor & {
|
|
73
95
|
contentComponent: any;
|
|
74
96
|
};
|
|
75
|
-
|
|
76
|
-
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Gets a list of all top-level blocks that are in the editor.
|
|
101
|
+
*/
|
|
102
|
+
public get topLevelBlocks(): Block[] {
|
|
103
|
+
const blocks: Block[] = [];
|
|
104
|
+
|
|
105
|
+
this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
|
|
106
|
+
blocks.push(nodeToBlock(node, this.blockCache));
|
|
107
|
+
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return blocks;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Traverses all blocks in the editor, including all nested blocks, and executes a callback for each. The traversal is
|
|
116
|
+
* depth-first, which is the same order as blocks appear in the editor by y-coordinate.
|
|
117
|
+
* @param callback The callback to execute for each block.
|
|
118
|
+
* @param reverse Whether the blocks should be traversed in reverse order.
|
|
119
|
+
*/
|
|
120
|
+
public allBlocks(
|
|
121
|
+
callback: (block: Block) => void,
|
|
122
|
+
reverse: boolean = false
|
|
123
|
+
): void {
|
|
124
|
+
function helper(blocks: Block[]) {
|
|
125
|
+
if (reverse) {
|
|
126
|
+
for (const block of blocks.reverse()) {
|
|
127
|
+
helper(block.children);
|
|
128
|
+
callback(block);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
for (const block of blocks) {
|
|
132
|
+
callback(block);
|
|
133
|
+
helper(block.children);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
helper(this.topLevelBlocks);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gets information regarding the position of the text cursor in the editor.
|
|
143
|
+
*/
|
|
144
|
+
public getTextCursorPosition(): TextCursorPosition {
|
|
145
|
+
const { node, depth, startPos, endPos } = getBlockInfoFromPos(
|
|
146
|
+
this._tiptapEditor.state.doc,
|
|
147
|
+
this._tiptapEditor.state.selection.from
|
|
148
|
+
)!;
|
|
149
|
+
|
|
150
|
+
// Index of the current blockContainer node relative to its parent blockGroup.
|
|
151
|
+
const nodeIndex = this._tiptapEditor.state.doc
|
|
152
|
+
.resolve(endPos)
|
|
153
|
+
.index(depth - 1);
|
|
154
|
+
// Number of the parent blockGroup's child blockContainer nodes.
|
|
155
|
+
const numNodes = this._tiptapEditor.state.doc
|
|
156
|
+
.resolve(endPos + 1)
|
|
157
|
+
.node().childCount;
|
|
158
|
+
|
|
159
|
+
// Gets previous blockContainer node at the same nesting level, if the current node isn't the first child.
|
|
160
|
+
let prevNode: Node | undefined = undefined;
|
|
161
|
+
if (nodeIndex > 0) {
|
|
162
|
+
prevNode = this._tiptapEditor.state.doc.resolve(startPos - 2).node();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Gets next blockContainer node at the same nesting level, if the current node isn't the last child.
|
|
166
|
+
let nextNode: Node | undefined = undefined;
|
|
167
|
+
if (nodeIndex < numNodes - 1) {
|
|
168
|
+
nextNode = this._tiptapEditor.state.doc.resolve(endPos + 2).node();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
block: nodeToBlock(node, this.blockCache),
|
|
173
|
+
prevBlock:
|
|
174
|
+
prevNode === undefined
|
|
175
|
+
? undefined
|
|
176
|
+
: nodeToBlock(prevNode, this.blockCache),
|
|
177
|
+
nextBlock:
|
|
178
|
+
nextNode === undefined
|
|
179
|
+
? undefined
|
|
180
|
+
: nodeToBlock(nextNode, this.blockCache),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public setTextCursorPosition(
|
|
185
|
+
block: Block,
|
|
186
|
+
placement: "start" | "end" = "start"
|
|
187
|
+
) {
|
|
188
|
+
const { posBeforeNode } = getNodeById(
|
|
189
|
+
block.id,
|
|
190
|
+
this._tiptapEditor.state.doc
|
|
191
|
+
);
|
|
192
|
+
const { startPos, contentNode } = getBlockInfoFromPos(
|
|
193
|
+
this._tiptapEditor.state.doc,
|
|
194
|
+
posBeforeNode + 2
|
|
195
|
+
)!;
|
|
196
|
+
|
|
197
|
+
if (placement === "start") {
|
|
198
|
+
this._tiptapEditor.commands.setTextSelection(startPos + 1);
|
|
199
|
+
} else {
|
|
200
|
+
this._tiptapEditor.commands.setTextSelection(
|
|
201
|
+
startPos + contentNode.nodeSize - 1
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Inserts multiple blocks before, after, or nested inside an existing block in the editor.
|
|
208
|
+
* @param blocksToInsert An array of blocks to insert.
|
|
209
|
+
* @param blockToInsertAt An existing block, marking where the new blocks should be inserted at.
|
|
210
|
+
* @param placement Determines whether the blocks should be inserted just before, just after, or nested inside the
|
|
211
|
+
* existing block.
|
|
212
|
+
*/
|
|
213
|
+
public insertBlocks(
|
|
214
|
+
blocksToInsert: PartialBlock[],
|
|
215
|
+
blockToInsertAt: Block,
|
|
216
|
+
placement: "before" | "after" | "nested" = "before"
|
|
217
|
+
): void {
|
|
218
|
+
insertBlocks(
|
|
219
|
+
blocksToInsert,
|
|
220
|
+
blockToInsertAt,
|
|
221
|
+
placement,
|
|
222
|
+
this._tiptapEditor
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Updates a block in the editor to the given specification.
|
|
228
|
+
* @param blockToUpdate The block that should be updated.
|
|
229
|
+
* @param updatedBlock The specification that the block should be updated to.
|
|
230
|
+
*/
|
|
231
|
+
public updateBlock(blockToUpdate: Block, updatedBlock: PartialBlock) {
|
|
232
|
+
updateBlock(blockToUpdate, updatedBlock, this._tiptapEditor);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Removes multiple blocks from the editor. Throws an error if any of the blocks could not be found.
|
|
237
|
+
* @param blocksToRemove An array of blocks that should be removed.
|
|
238
|
+
*/
|
|
239
|
+
public removeBlocks(blocksToRemove: Block[]) {
|
|
240
|
+
removeBlocks(blocksToRemove, this._tiptapEditor);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Replaces multiple blocks in the editor with several other blocks. If the provided blocks to remove are not adjacent
|
|
245
|
+
* to each other, the new blocks are inserted at the position of the first block in the array. Throws an error if any
|
|
246
|
+
* of the blocks could not be found.
|
|
247
|
+
* @param blocksToRemove An array of blocks that should be replaced.
|
|
248
|
+
* @param blocksToInsert An array of blocks to replace the old ones with.
|
|
249
|
+
*/
|
|
250
|
+
public replaceBlocks(
|
|
251
|
+
blocksToRemove: Block[],
|
|
252
|
+
blocksToInsert: PartialBlock[]
|
|
253
|
+
) {
|
|
254
|
+
replaceBlocks(blocksToRemove, blocksToInsert, this._tiptapEditor);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Executes a callback function whenever the editor's content changes.
|
|
259
|
+
* @param callback The callback function to execute.
|
|
260
|
+
*/
|
|
261
|
+
public onContentChange(callback: () => void) {
|
|
262
|
+
this._tiptapEditor.on("update", callback);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Serializes a list of blocks into an HTML string. The output is not the same as what's rendered by the editor, and
|
|
267
|
+
* is simplified in order to better conform to HTML standards. Block structuring elements are removed, children of
|
|
268
|
+
* blocks which aren't list items are lifted out of them, and list items blocks are wrapped in `ul`/`ol` tags.
|
|
269
|
+
* @param blocks The list of blocks to serialize into HTML.
|
|
270
|
+
*/
|
|
271
|
+
public async blocksToHTML(blocks: Block[]): Promise<string> {
|
|
272
|
+
return blocksToHTML(blocks, this._tiptapEditor.schema);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Creates a list of blocks from an HTML string.
|
|
277
|
+
* @param htmlString The HTML string to create a list of blocks from.
|
|
278
|
+
*/
|
|
279
|
+
public async HTMLToBlocks(htmlString: string): Promise<Block[]> {
|
|
280
|
+
return HTMLToBlocks(htmlString, this._tiptapEditor.schema);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Serializes a list of blocks into a Markdown string. The output is simplified as Markdown does not support all
|
|
285
|
+
* features of BlockNote. Block structuring elements are removed, children of blocks which aren't list items are
|
|
286
|
+
* lifted out of them, and certain styles are removed.
|
|
287
|
+
* @param blocks The list of blocks to serialize into Markdown.
|
|
288
|
+
*/
|
|
289
|
+
public async blocksToMarkdown(blocks: Block[]): Promise<string> {
|
|
290
|
+
return blocksToMarkdown(blocks, this._tiptapEditor.schema);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Creates a list of blocks from a Markdown string.
|
|
295
|
+
* @param markdownString The Markdown string to create a list of blocks from.
|
|
296
|
+
*/
|
|
297
|
+
public async markdownToBlocks(markdownString: string): Promise<Block[]> {
|
|
298
|
+
return markdownToBlocks(markdownString, this._tiptapEditor.schema);
|
|
77
299
|
}
|
|
78
300
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Extensions, extensions } from "@tiptap/core";
|
|
2
2
|
|
|
3
|
+
import { BlockNoteEditor } from "./BlockNoteEditor";
|
|
4
|
+
|
|
3
5
|
import { Bold } from "@tiptap/extension-bold";
|
|
4
6
|
import { Code } from "@tiptap/extension-code";
|
|
5
7
|
import { Dropcursor } from "@tiptap/extension-dropcursor";
|
|
@@ -22,8 +24,8 @@ import { FormattingToolbarFactory } from "./extensions/FormattingToolbar/Formatt
|
|
|
22
24
|
import HyperlinkMark from "./extensions/HyperlinkToolbar/HyperlinkMark";
|
|
23
25
|
import { HyperlinkToolbarFactory } from "./extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes";
|
|
24
26
|
import { Placeholder } from "./extensions/Placeholder/PlaceholderExtension";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
+
import { SlashMenuExtension } from "./extensions/SlashMenu";
|
|
28
|
+
import { BaseSlashMenuItem } from "./extensions/SlashMenu";
|
|
27
29
|
import { TextAlignmentExtension } from "./extensions/TextAlignment/TextAlignmentExtension";
|
|
28
30
|
import { TextColorExtension } from "./extensions/TextColor/TextColorExtension";
|
|
29
31
|
import { TextColorMark } from "./extensions/TextColor/TextColorMark";
|
|
@@ -34,7 +36,7 @@ import { SuggestionsMenuFactory } from "./shared/plugins/suggestion/SuggestionsM
|
|
|
34
36
|
export type UiFactories = Partial<{
|
|
35
37
|
formattingToolbarFactory: FormattingToolbarFactory;
|
|
36
38
|
hyperlinkToolbarFactory: HyperlinkToolbarFactory;
|
|
37
|
-
slashMenuFactory: SuggestionsMenuFactory<
|
|
39
|
+
slashMenuFactory: SuggestionsMenuFactory<BaseSlashMenuItem>;
|
|
38
40
|
blockSideMenuFactory: BlockSideMenuFactory;
|
|
39
41
|
}>;
|
|
40
42
|
|
|
@@ -42,8 +44,9 @@ export type UiFactories = Partial<{
|
|
|
42
44
|
* Get all the Tiptap extensions BlockNote is configured with by default
|
|
43
45
|
*/
|
|
44
46
|
export const getBlockNoteExtensions = (opts: {
|
|
47
|
+
editor: BlockNoteEditor;
|
|
45
48
|
uiFactories: UiFactories;
|
|
46
|
-
slashCommands:
|
|
49
|
+
slashCommands: BaseSlashMenuItem[];
|
|
47
50
|
}) => {
|
|
48
51
|
const ret: Extensions = [
|
|
49
52
|
extensions.ClipboardTextSerializer,
|
|
@@ -123,6 +126,7 @@ export const getBlockNoteExtensions = (opts: {
|
|
|
123
126
|
if (opts.uiFactories.slashMenuFactory) {
|
|
124
127
|
ret.push(
|
|
125
128
|
SlashMenuExtension.configure({
|
|
129
|
+
editor: opts.editor,
|
|
126
130
|
commands: opts.slashCommands,
|
|
127
131
|
slashMenuFactory: opts.uiFactories.slashMenuFactory,
|
|
128
132
|
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SuggestionItem } from "../../shared/plugins/suggestion/SuggestionItem";
|
|
2
|
+
import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A class that defines a slash command (/<command>).
|
|
6
|
+
*
|
|
7
|
+
* (Not to be confused with ProseMirror commands nor TipTap commands.)
|
|
8
|
+
*/
|
|
9
|
+
export class BaseSlashMenuItem extends SuggestionItem {
|
|
10
|
+
/**
|
|
11
|
+
* Constructs a new slash-command.
|
|
12
|
+
*
|
|
13
|
+
* @param name The name of the command
|
|
14
|
+
* @param execute The callback for creating a new node
|
|
15
|
+
* @param aliases Aliases for this command
|
|
16
|
+
*/
|
|
17
|
+
constructor(
|
|
18
|
+
public readonly name: string,
|
|
19
|
+
public readonly execute: (editor: BlockNoteEditor) => void,
|
|
20
|
+
public readonly aliases: string[] = []
|
|
21
|
+
) {
|
|
22
|
+
super(name, (query: string): boolean => {
|
|
23
|
+
return (
|
|
24
|
+
this.name.toLowerCase().startsWith(query.toLowerCase()) ||
|
|
25
|
+
this.aliases.filter((alias) =>
|
|
26
|
+
alias.toLowerCase().startsWith(query.toLowerCase())
|
|
27
|
+
).length !== 0
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -2,10 +2,12 @@ import { Extension } from "@tiptap/core";
|
|
|
2
2
|
import { PluginKey } from "prosemirror-state";
|
|
3
3
|
import { createSuggestionPlugin } from "../../shared/plugins/suggestion/SuggestionPlugin";
|
|
4
4
|
import { SuggestionsMenuFactory } from "../../shared/plugins/suggestion/SuggestionsMenuFactoryTypes";
|
|
5
|
-
import {
|
|
5
|
+
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
6
|
+
import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
6
7
|
|
|
7
8
|
export type SlashMenuOptions = {
|
|
8
|
-
|
|
9
|
+
editor: BlockNoteEditor | undefined;
|
|
10
|
+
commands: BaseSlashMenuItem[] | undefined;
|
|
9
11
|
slashMenuFactory: SuggestionsMenuFactory<any> | undefined;
|
|
10
12
|
};
|
|
11
13
|
|
|
@@ -16,6 +18,7 @@ export const SlashMenuExtension = Extension.create<SlashMenuOptions>({
|
|
|
16
18
|
|
|
17
19
|
addOptions() {
|
|
18
20
|
return {
|
|
21
|
+
editor: undefined,
|
|
19
22
|
commands: undefined,
|
|
20
23
|
slashMenuFactory: undefined,
|
|
21
24
|
};
|
|
@@ -29,16 +32,16 @@ export const SlashMenuExtension = Extension.create<SlashMenuOptions>({
|
|
|
29
32
|
const commands = this.options.commands;
|
|
30
33
|
|
|
31
34
|
return [
|
|
32
|
-
createSuggestionPlugin<
|
|
35
|
+
createSuggestionPlugin<BaseSlashMenuItem>({
|
|
33
36
|
pluginKey: SlashMenuPluginKey,
|
|
34
|
-
editor: this.editor
|
|
37
|
+
editor: this.options.editor!,
|
|
35
38
|
defaultTriggerCharacter: "/",
|
|
36
39
|
suggestionsMenuFactory: this.options.slashMenuFactory!,
|
|
37
40
|
items: (query) => {
|
|
38
|
-
return commands.filter((cmd:
|
|
41
|
+
return commands.filter((cmd: BaseSlashMenuItem) => cmd.match(query));
|
|
39
42
|
},
|
|
40
|
-
onSelectItem: ({ item, editor
|
|
41
|
-
item.execute(editor
|
|
43
|
+
onSelectItem: ({ item, editor }) => {
|
|
44
|
+
item.execute(editor);
|
|
42
45
|
},
|
|
43
46
|
}),
|
|
44
47
|
];
|
|
@@ -1,135 +1,88 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
2
|
+
import { PartialBlock } from "../Blocks/api/blockTypes";
|
|
3
|
+
import { BlockNoteEditor } from "../../BlockNoteEditor";
|
|
4
|
+
|
|
5
|
+
function insertOrUpdateBlock(editor: BlockNoteEditor, block: PartialBlock) {
|
|
6
|
+
const currentBlock = editor.getTextCursorPosition().block;
|
|
7
|
+
|
|
8
|
+
if (
|
|
9
|
+
(currentBlock.content.length === 1 &&
|
|
10
|
+
currentBlock.content[0].type === "text" &&
|
|
11
|
+
currentBlock.content[0].text === "/") ||
|
|
12
|
+
currentBlock.content.length === 0
|
|
13
|
+
) {
|
|
14
|
+
editor.updateBlock(currentBlock, block);
|
|
15
|
+
} else {
|
|
16
|
+
editor.insertBlocks([block], currentBlock, "after");
|
|
17
|
+
editor.setTextCursorPosition(editor.getTextCursorPosition().nextBlock!);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
4
20
|
|
|
5
21
|
/**
|
|
6
22
|
* An array containing commands for creating all default blocks.
|
|
7
23
|
*/
|
|
8
|
-
export const
|
|
24
|
+
export const defaultSlashMenuItems: BaseSlashMenuItem[] = [
|
|
9
25
|
// Command for creating a level 1 heading
|
|
10
|
-
new
|
|
26
|
+
new BaseSlashMenuItem(
|
|
11
27
|
"Heading",
|
|
12
|
-
(editor
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type: "heading",
|
|
19
|
-
props: {
|
|
20
|
-
level: "1",
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
.run();
|
|
24
|
-
},
|
|
25
|
-
["h", "heading1", "h1"],
|
|
26
|
-
"Headings",
|
|
27
|
-
"Used for a top-level heading",
|
|
28
|
-
formatKeyboardShortcut("Mod-Alt-1")
|
|
28
|
+
(editor) =>
|
|
29
|
+
insertOrUpdateBlock(editor, {
|
|
30
|
+
type: "heading",
|
|
31
|
+
props: { level: "1" },
|
|
32
|
+
}),
|
|
33
|
+
["h", "heading1", "h1"]
|
|
29
34
|
),
|
|
30
35
|
|
|
31
36
|
// Command for creating a level 2 heading
|
|
32
|
-
new
|
|
37
|
+
new BaseSlashMenuItem(
|
|
33
38
|
"Heading 2",
|
|
34
|
-
(editor
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
type: "heading",
|
|
41
|
-
props: {
|
|
42
|
-
level: "2",
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
.run();
|
|
46
|
-
},
|
|
47
|
-
["h2", "heading2", "subheading"],
|
|
48
|
-
"Headings",
|
|
49
|
-
"Used for key sections",
|
|
50
|
-
formatKeyboardShortcut("Mod-Alt-2")
|
|
39
|
+
(editor) =>
|
|
40
|
+
insertOrUpdateBlock(editor, {
|
|
41
|
+
type: "heading",
|
|
42
|
+
props: { level: "2" },
|
|
43
|
+
}),
|
|
44
|
+
["h2", "heading2", "subheading"]
|
|
51
45
|
),
|
|
52
46
|
|
|
53
47
|
// Command for creating a level 3 heading
|
|
54
|
-
new
|
|
48
|
+
new BaseSlashMenuItem(
|
|
55
49
|
"Heading 3",
|
|
56
|
-
(editor
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
type: "heading",
|
|
63
|
-
props: {
|
|
64
|
-
level: "3",
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
.run();
|
|
68
|
-
},
|
|
69
|
-
["h3", "heading3", "subheading"],
|
|
70
|
-
"Headings",
|
|
71
|
-
"Used for subsections and group headings",
|
|
72
|
-
formatKeyboardShortcut("Mod-Alt-3")
|
|
50
|
+
(editor) =>
|
|
51
|
+
insertOrUpdateBlock(editor, {
|
|
52
|
+
type: "heading",
|
|
53
|
+
props: { level: "3" },
|
|
54
|
+
}),
|
|
55
|
+
["h3", "heading3", "subheading"]
|
|
73
56
|
),
|
|
74
57
|
|
|
75
58
|
// Command for creating an ordered list
|
|
76
|
-
new
|
|
59
|
+
new BaseSlashMenuItem(
|
|
77
60
|
"Numbered List",
|
|
78
|
-
(editor
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.BNCreateOrUpdateBlock(range.from, {
|
|
84
|
-
type: "numberedListItem",
|
|
85
|
-
props: {},
|
|
86
|
-
})
|
|
87
|
-
.run();
|
|
88
|
-
},
|
|
89
|
-
["li", "list", "numberedlist", "numbered list"],
|
|
90
|
-
"Basic blocks",
|
|
91
|
-
"Used to display a numbered list",
|
|
92
|
-
"Mod-Alt-7"
|
|
61
|
+
(editor) =>
|
|
62
|
+
insertOrUpdateBlock(editor, {
|
|
63
|
+
type: "numberedListItem",
|
|
64
|
+
}),
|
|
65
|
+
["li", "list", "numberedlist", "numbered list"]
|
|
93
66
|
),
|
|
94
67
|
|
|
95
68
|
// Command for creating a bullet list
|
|
96
|
-
new
|
|
69
|
+
new BaseSlashMenuItem(
|
|
97
70
|
"Bullet List",
|
|
98
|
-
(editor
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.BNCreateOrUpdateBlock(range.from, {
|
|
104
|
-
type: "bulletListItem",
|
|
105
|
-
props: {},
|
|
106
|
-
})
|
|
107
|
-
.run();
|
|
108
|
-
},
|
|
109
|
-
["ul", "list", "bulletlist", "bullet list"],
|
|
110
|
-
"Basic blocks",
|
|
111
|
-
"Used to display an unordered list",
|
|
112
|
-
"Mod-Alt-9"
|
|
71
|
+
(editor) =>
|
|
72
|
+
insertOrUpdateBlock(editor, {
|
|
73
|
+
type: "bulletListItem",
|
|
74
|
+
}),
|
|
75
|
+
["ul", "list", "bulletlist", "bullet list"]
|
|
113
76
|
),
|
|
114
77
|
|
|
115
78
|
// Command for creating a paragraph (pretty useless)
|
|
116
|
-
new
|
|
79
|
+
new BaseSlashMenuItem(
|
|
117
80
|
"Paragraph",
|
|
118
|
-
(editor
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.BNCreateOrUpdateBlock(range.from, {
|
|
124
|
-
type: "paragraph",
|
|
125
|
-
props: {},
|
|
126
|
-
})
|
|
127
|
-
.run();
|
|
128
|
-
},
|
|
129
|
-
["p"],
|
|
130
|
-
"Basic blocks",
|
|
131
|
-
"Used for the body of your document",
|
|
132
|
-
"Mod-Alt-0"
|
|
81
|
+
(editor) =>
|
|
82
|
+
insertOrUpdateBlock(editor, {
|
|
83
|
+
type: "paragraph",
|
|
84
|
+
}),
|
|
85
|
+
["p"]
|
|
133
86
|
),
|
|
134
87
|
|
|
135
88
|
// replaceRangeWithNode(editor, range, node);
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defaultSlashMenuItems } from "./defaultSlashMenuItems";
|
|
2
2
|
import { SlashMenuExtension } from "./SlashMenuExtension";
|
|
3
|
-
import {
|
|
3
|
+
import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
|
|
4
4
|
|
|
5
|
-
export {
|
|
6
|
-
defaultSlashCommands,
|
|
7
|
-
SlashMenuItem as SlashCommand,
|
|
8
|
-
SlashMenuExtension,
|
|
9
|
-
};
|
|
5
|
+
export { defaultSlashMenuItems, BaseSlashMenuItem, SlashMenuExtension };
|
package/src/index.ts
CHANGED
|
@@ -4,9 +4,8 @@ export * from "./extensions/Blocks/api/blockTypes";
|
|
|
4
4
|
export * from "./extensions/DraggableBlocks/BlockSideMenuFactoryTypes";
|
|
5
5
|
export * from "./extensions/FormattingToolbar/FormattingToolbarFactoryTypes";
|
|
6
6
|
export * from "./extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes";
|
|
7
|
-
export {
|
|
8
|
-
export * from "./extensions/SlashMenu/
|
|
7
|
+
export { defaultSlashMenuItems } from "./extensions/SlashMenu/defaultSlashMenuItems";
|
|
8
|
+
export * from "./extensions/SlashMenu/BaseSlashMenuItem";
|
|
9
9
|
export * from "./shared/EditorElement";
|
|
10
10
|
export type { SuggestionItem } from "./shared/plugins/suggestion/SuggestionItem";
|
|
11
11
|
export * from "./shared/plugins/suggestion/SuggestionsMenuFactoryTypes";
|
|
12
|
-
export * from "../src/api/Editor";
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A generic interface used in all suggestion menus (slash menu, mentions, etc)
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
* The name of the item
|
|
7
|
-
*/
|
|
8
|
-
name: string;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* This function matches this item against a query string, the function should return **true** if the item
|
|
12
|
-
* matches the query or **false** otherwise.
|
|
13
|
-
*
|
|
14
|
-
* @param query the query string
|
|
15
|
-
*/
|
|
16
|
-
match(query: string): boolean;
|
|
4
|
+
export class SuggestionItem {
|
|
5
|
+
constructor(public name: string, public match: (query: string) => boolean) {}
|
|
17
6
|
}
|