@blocknote/core 0.9.0 → 0.9.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 +487 -431
- package/dist/blocknote.js.map +1 -1
- package/dist/blocknote.umd.cjs +5 -4
- package/dist/blocknote.umd.cjs.map +1 -1
- package/package.json +2 -2
- package/src/BlockNoteEditor.ts +2 -2
- package/src/BlockNoteExtensions.ts +24 -22
- package/src/api/blockManipulation/blockManipulation.test.ts +2 -2
- package/src/api/blockManipulation/blockManipulation.ts +1 -1
- package/src/api/formatConversions/formatConversions.test.ts +2 -2
- package/src/api/formatConversions/formatConversions.ts +47 -3
- package/src/api/nodeConversions/nodeConversions.test.ts +6 -6
- package/src/api/nodeConversions/nodeConversions.ts +6 -6
- package/src/extensions/Blocks/PreviousBlockTypePlugin.ts +2 -2
- package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +27 -5
- package/src/extensions/Blocks/nodes/BlockContainer.ts +41 -17
- package/src/extensions/Blocks/nodes/BlockContent/HeadingBlockContent/HeadingBlockContent.ts +2 -0
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/BulletListItemBlockContent/BulletListItemBlockContent.ts +2 -0
- package/src/extensions/Blocks/nodes/BlockContent/ListItemBlockContent/NumberedListItemBlockContent/NumberedListItemBlockContent.ts +2 -0
- package/src/extensions/SideMenu/MultipleNodeSelection.ts +3 -3
- package/src/extensions/SideMenu/SideMenuPlugin.ts +9 -9
- package/src/extensions/TrailingNode/TrailingNodeExtension.ts +13 -1
- package/src/extensions/UniqueID/UniqueID.ts +10 -9
- package/src/shared/EventEmitter.ts +1 -0
- package/src/shared/plugins/suggestion/SuggestionPlugin.ts +6 -2
- package/types/src/extensions/Blocks/NonEditableBlockPlugin.d.ts +2 -0
- package/types/src/extensions/Blocks/api/defaultProps.d.ts +14 -0
- package/types/src/extensions/Blocks/helpers/getBlockInfoFromPos.d.ts +9 -1
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/Image.d.ts +6 -0
- package/types/src/extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent.d.ts +37 -0
- package/types/src/extensions/ImageToolbar/ImageToolbarPlugin.d.ts +36 -0
- package/types/src/extensions/SideMenu/MultipleNodeSelection.d.ts +1 -1
- package/types/src/EventEmitter.d.ts +0 -11
- package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +0 -0
- package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +0 -16
- package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +0 -55
- package/types/src/extensions/DraggableBlocks/MultipleNodeSelection.d.ts +0 -24
- package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +0 -11
- package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +0 -10
- package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +0 -8
- package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +0 -0
- package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +0 -13
- package/types/src/extensions/SlashMenu/index.d.ts +0 -3
- package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +0 -12
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://github.com/TypeCellOS/BlockNote",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "0.9.
|
|
6
|
+
"version": "0.9.3",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"types",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"access": "public",
|
|
110
110
|
"registry": "https://registry.npmjs.org/"
|
|
111
111
|
},
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "3a3ca4dfc448556f9796ce884b6ba61f036dfe1a"
|
|
113
113
|
}
|
package/src/BlockNoteEditor.ts
CHANGED
|
@@ -353,7 +353,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
353
353
|
*/
|
|
354
354
|
public forEachBlock(
|
|
355
355
|
callback: (block: Block<BSchema>) => boolean,
|
|
356
|
-
reverse
|
|
356
|
+
reverse = false
|
|
357
357
|
): void {
|
|
358
358
|
const blocks = this.topLevelBlocks.slice();
|
|
359
359
|
|
|
@@ -692,7 +692,7 @@ export class BlockNoteEditor<BSchema extends BlockSchema = DefaultBlockSchema> {
|
|
|
692
692
|
return;
|
|
693
693
|
}
|
|
694
694
|
|
|
695
|
-
|
|
695
|
+
const { from, to } = this._tiptapEditor.state.selection;
|
|
696
696
|
|
|
697
697
|
if (!text) {
|
|
698
698
|
text = this._tiptapEditor.state.doc.textBetween(from, to);
|
|
@@ -118,32 +118,34 @@ export const getBlockNoteExtensions = <BSchema extends BlockSchema>(opts: {
|
|
|
118
118
|
fragment: opts.collaboration.fragment,
|
|
119
119
|
})
|
|
120
120
|
);
|
|
121
|
-
|
|
122
|
-
const
|
|
121
|
+
if (opts.collaboration.provider?.awareness) {
|
|
122
|
+
const defaultRender = (user: { color: string; name: string }) => {
|
|
123
|
+
const cursor = document.createElement("span");
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
cursor.classList.add(styles["collaboration-cursor__caret"]);
|
|
126
|
+
cursor.setAttribute("style", `border-color: ${user.color}`);
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
const label = document.createElement("span");
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
label.classList.add(styles["collaboration-cursor__label"]);
|
|
131
|
+
label.setAttribute("style", `background-color: ${user.color}`);
|
|
132
|
+
label.insertBefore(document.createTextNode(user.name), null);
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
134
|
+
const nonbreakingSpace1 = document.createTextNode("\u2060");
|
|
135
|
+
const nonbreakingSpace2 = document.createTextNode("\u2060");
|
|
136
|
+
cursor.insertBefore(nonbreakingSpace1, null);
|
|
137
|
+
cursor.insertBefore(label, null);
|
|
138
|
+
cursor.insertBefore(nonbreakingSpace2, null);
|
|
139
|
+
return cursor;
|
|
140
|
+
};
|
|
141
|
+
ret.push(
|
|
142
|
+
CollaborationCursor.configure({
|
|
143
|
+
user: opts.collaboration.user,
|
|
144
|
+
render: opts.collaboration.renderCursor || defaultRender,
|
|
145
|
+
provider: opts.collaboration.provider,
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
}
|
|
147
149
|
} else {
|
|
148
150
|
// disable history extension when collaboration is enabled as Yjs takes care of undo / redo
|
|
149
151
|
ret.push(History);
|
|
@@ -24,7 +24,7 @@ let insert: (
|
|
|
24
24
|
) => Block<DefaultBlockSchema>[];
|
|
25
25
|
|
|
26
26
|
beforeEach(() => {
|
|
27
|
-
(window as Window & { __TEST_OPTIONS?:
|
|
27
|
+
(window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
|
|
28
28
|
|
|
29
29
|
editor = new BlockNoteEditor();
|
|
30
30
|
|
|
@@ -80,7 +80,7 @@ afterEach(() => {
|
|
|
80
80
|
editor._tiptapEditor.destroy();
|
|
81
81
|
editor = undefined as any;
|
|
82
82
|
|
|
83
|
-
delete (window as Window & { __TEST_OPTIONS?:
|
|
83
|
+
delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
describe("Inserting Blocks with Different Placements", () => {
|
|
@@ -107,7 +107,7 @@ export function removeBlocks(
|
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
if (idsOfBlocksToRemove.size > 0) {
|
|
110
|
-
|
|
110
|
+
const notFoundIds = [...idsOfBlocksToRemove].join("\n");
|
|
111
111
|
|
|
112
112
|
throw Error(
|
|
113
113
|
"Blocks with the following IDs could not be found in the editor: " +
|
|
@@ -579,7 +579,7 @@ function removeInlineContentClass(html: string) {
|
|
|
579
579
|
}
|
|
580
580
|
|
|
581
581
|
beforeEach(() => {
|
|
582
|
-
(window as Window & { __TEST_OPTIONS?:
|
|
582
|
+
(window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
|
|
583
583
|
|
|
584
584
|
editor = new BlockNoteEditor();
|
|
585
585
|
});
|
|
@@ -588,7 +588,7 @@ afterEach(() => {
|
|
|
588
588
|
editor._tiptapEditor.destroy();
|
|
589
589
|
editor = undefined as any;
|
|
590
590
|
|
|
591
|
-
delete (window as Window & { __TEST_OPTIONS?:
|
|
591
|
+
delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
|
|
592
592
|
});
|
|
593
593
|
|
|
594
594
|
describe("Non-Nested Block/HTML/Markdown Conversions", () => {
|
|
@@ -4,7 +4,7 @@ import rehypeRemark from "rehype-remark";
|
|
|
4
4
|
import rehypeStringify from "rehype-stringify";
|
|
5
5
|
import remarkGfm from "remark-gfm";
|
|
6
6
|
import remarkParse from "remark-parse";
|
|
7
|
-
import remarkRehype from "remark-rehype";
|
|
7
|
+
import remarkRehype, { defaultHandlers } from "remark-rehype";
|
|
8
8
|
import remarkStringify from "remark-stringify";
|
|
9
9
|
import { unified } from "unified";
|
|
10
10
|
import { Block, BlockSchema } from "../../extensions/Blocks/api/blockTypes";
|
|
@@ -47,7 +47,7 @@ export async function HTMLToBlocks<BSchema extends BlockSchema>(
|
|
|
47
47
|
htmlNode.innerHTML = html.trim();
|
|
48
48
|
|
|
49
49
|
const parser = DOMParser.fromSchema(schema);
|
|
50
|
-
const parentNode = parser.parse(htmlNode);
|
|
50
|
+
const parentNode = parser.parse(htmlNode); //, { preserveWhitespace: "full" });
|
|
51
51
|
|
|
52
52
|
const blocks: Block<BSchema>[] = [];
|
|
53
53
|
|
|
@@ -73,6 +73,45 @@ export async function blocksToMarkdown<BSchema extends BlockSchema>(
|
|
|
73
73
|
return markdownString.value as string;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// modefied version of https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/code.js
|
|
77
|
+
// that outputs a data-language attribute instead of a CSS class (e.g.: language-typescript)
|
|
78
|
+
function code(state: any, node: any) {
|
|
79
|
+
const value = node.value ? node.value + "\n" : "";
|
|
80
|
+
/** @type {Properties} */
|
|
81
|
+
const properties: any = {};
|
|
82
|
+
|
|
83
|
+
if (node.lang) {
|
|
84
|
+
// changed line
|
|
85
|
+
properties["data-language"] = node.lang;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Create `<code>`.
|
|
89
|
+
/** @type {Element} */
|
|
90
|
+
let result: any = {
|
|
91
|
+
type: "element",
|
|
92
|
+
tagName: "code",
|
|
93
|
+
properties,
|
|
94
|
+
children: [{ type: "text", value }],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (node.meta) {
|
|
98
|
+
result.data = { meta: node.meta };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
state.patch(node, result);
|
|
102
|
+
result = state.applyData(node, result);
|
|
103
|
+
|
|
104
|
+
// Create `<pre>`.
|
|
105
|
+
result = {
|
|
106
|
+
type: "element",
|
|
107
|
+
tagName: "pre",
|
|
108
|
+
properties: {},
|
|
109
|
+
children: [result],
|
|
110
|
+
};
|
|
111
|
+
state.patch(node, result);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
76
115
|
export async function markdownToBlocks<BSchema extends BlockSchema>(
|
|
77
116
|
markdown: string,
|
|
78
117
|
blockSchema: BSchema,
|
|
@@ -81,7 +120,12 @@ export async function markdownToBlocks<BSchema extends BlockSchema>(
|
|
|
81
120
|
const htmlString = await unified()
|
|
82
121
|
.use(remarkParse)
|
|
83
122
|
.use(remarkGfm)
|
|
84
|
-
.use(remarkRehype
|
|
123
|
+
.use(remarkRehype, {
|
|
124
|
+
handlers: {
|
|
125
|
+
...(defaultHandlers as any),
|
|
126
|
+
code,
|
|
127
|
+
},
|
|
128
|
+
})
|
|
85
129
|
.use(rehypeStringify)
|
|
86
130
|
.process(markdown);
|
|
87
131
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Editor } from "@tiptap/core";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
3
|
import { BlockNoteEditor, PartialBlock } from "../..";
|
|
4
|
-
import UniqueID from "../../extensions/UniqueID/UniqueID";
|
|
5
|
-
import { blockToNode, nodeToBlock } from "./nodeConversions";
|
|
6
|
-
import { partialBlockToBlockForTesting } from "./testUtil";
|
|
7
4
|
import {
|
|
8
|
-
defaultBlockSchema,
|
|
9
5
|
DefaultBlockSchema,
|
|
6
|
+
defaultBlockSchema,
|
|
10
7
|
} from "../../extensions/Blocks/api/defaultBlocks";
|
|
8
|
+
import UniqueID from "../../extensions/UniqueID/UniqueID";
|
|
9
|
+
import { blockToNode, nodeToBlock } from "./nodeConversions";
|
|
10
|
+
import { partialBlockToBlockForTesting } from "./testUtil";
|
|
11
11
|
|
|
12
12
|
let editor: BlockNoteEditor;
|
|
13
13
|
let tt: Editor;
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
(window as Window & { __TEST_OPTIONS?:
|
|
16
|
+
(window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS = {};
|
|
17
17
|
|
|
18
18
|
editor = new BlockNoteEditor();
|
|
19
19
|
tt = editor._tiptapEditor;
|
|
@@ -24,7 +24,7 @@ afterEach(() => {
|
|
|
24
24
|
editor = undefined as any;
|
|
25
25
|
tt = undefined as any;
|
|
26
26
|
|
|
27
|
-
delete (window as Window & { __TEST_OPTIONS?:
|
|
27
|
+
delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
describe("Simple ProseMirror Node Conversions", () => {
|
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
Styles,
|
|
17
17
|
ToggledStyle,
|
|
18
18
|
} from "../../extensions/Blocks/api/inlineContentTypes";
|
|
19
|
-
import { getBlockInfoFromPos } from "../../extensions/Blocks/helpers/getBlockInfoFromPos";
|
|
20
19
|
import UniqueID from "../../extensions/UniqueID/UniqueID";
|
|
21
20
|
import { UnreachableCaseError } from "../../shared/utils";
|
|
21
|
+
import { getBlockInfo } from "../../extensions/Blocks/helpers/getBlockInfoFromPos";
|
|
22
22
|
|
|
23
23
|
const toggleStyles = new Set<ToggledStyle>([
|
|
24
24
|
"bold",
|
|
@@ -91,7 +91,7 @@ function styledTextArrayToNodes(
|
|
|
91
91
|
content: string | StyledText[],
|
|
92
92
|
schema: Schema
|
|
93
93
|
): Node[] {
|
|
94
|
-
|
|
94
|
+
const nodes: Node[] = [];
|
|
95
95
|
|
|
96
96
|
if (typeof content === "string") {
|
|
97
97
|
nodes.push(
|
|
@@ -113,7 +113,7 @@ export function inlineContentToNodes(
|
|
|
113
113
|
blockContent: PartialInlineContent[],
|
|
114
114
|
schema: Schema
|
|
115
115
|
): Node[] {
|
|
116
|
-
|
|
116
|
+
const nodes: Node[] = [];
|
|
117
117
|
|
|
118
118
|
for (const content of blockContent) {
|
|
119
119
|
if (content.type === "link") {
|
|
@@ -369,7 +369,7 @@ export function nodeToBlock<BSchema extends BlockSchema>(
|
|
|
369
369
|
return cachedBlock;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
const blockInfo =
|
|
372
|
+
const blockInfo = getBlockInfo(node);
|
|
373
373
|
|
|
374
374
|
let id = blockInfo.id;
|
|
375
375
|
|
|
@@ -380,7 +380,7 @@ export function nodeToBlock<BSchema extends BlockSchema>(
|
|
|
380
380
|
|
|
381
381
|
const props: any = {};
|
|
382
382
|
for (const [attr, value] of Object.entries({
|
|
383
|
-
...
|
|
383
|
+
...node.attrs,
|
|
384
384
|
...blockInfo.contentNode.attrs,
|
|
385
385
|
})) {
|
|
386
386
|
const blockSpec = blockSchema[blockInfo.contentType.name];
|
|
@@ -414,7 +414,7 @@ export function nodeToBlock<BSchema extends BlockSchema>(
|
|
|
414
414
|
const children: Block<BSchema>[] = [];
|
|
415
415
|
for (let i = 0; i < blockInfo.numChildBlocks; i++) {
|
|
416
416
|
children.push(
|
|
417
|
-
nodeToBlock(
|
|
417
|
+
nodeToBlock(node.lastChild!.child(i), blockSchema, blockCache)
|
|
418
418
|
);
|
|
419
419
|
}
|
|
420
420
|
|
|
@@ -96,7 +96,7 @@ export const PreviousBlockTypePlugin = () => {
|
|
|
96
96
|
const newNodes = findChildren(newState.doc, (node) => node.attrs.id);
|
|
97
97
|
|
|
98
98
|
// Traverses all block containers in the new editor state.
|
|
99
|
-
for (
|
|
99
|
+
for (const node of newNodes) {
|
|
100
100
|
const oldNode = oldNodesById.get(node.node.attrs.id);
|
|
101
101
|
|
|
102
102
|
const oldContentNode = oldNode?.node.firstChild;
|
|
@@ -192,7 +192,7 @@ export const PreviousBlockTypePlugin = () => {
|
|
|
192
192
|
pluginState.currentTransactionOldBlockAttrs[node.attrs.id];
|
|
193
193
|
const decorationAttrs: any = {};
|
|
194
194
|
|
|
195
|
-
for (
|
|
195
|
+
for (const [nodeAttr, val] of Object.entries(prevAttrs)) {
|
|
196
196
|
decorationAttrs["data-prev-" + nodeAttributes[nodeAttr]] =
|
|
197
197
|
val || "none";
|
|
198
198
|
}
|
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
import { Node, NodeType } from "prosemirror-model";
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type BlockInfoWithoutPositions = {
|
|
4
4
|
id: string;
|
|
5
5
|
node: Node;
|
|
6
6
|
contentNode: Node;
|
|
7
7
|
contentType: NodeType;
|
|
8
8
|
numChildBlocks: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type BlockInfo = BlockInfoWithoutPositions & {
|
|
9
12
|
startPos: number;
|
|
10
13
|
endPos: number;
|
|
11
14
|
depth: number;
|
|
12
15
|
};
|
|
13
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Helper function for `getBlockInfoFromPos`, returns information regarding
|
|
19
|
+
* provided blockContainer node.
|
|
20
|
+
* @param blockContainer The blockContainer node to retrieve info for.
|
|
21
|
+
*/
|
|
22
|
+
export function getBlockInfo(blockContainer: Node): BlockInfoWithoutPositions {
|
|
23
|
+
const id = blockContainer.attrs["id"];
|
|
24
|
+
const contentNode = blockContainer.firstChild!;
|
|
25
|
+
const contentType = contentNode.type;
|
|
26
|
+
const numChildBlocks =
|
|
27
|
+
blockContainer.childCount === 2 ? blockContainer.lastChild!.childCount : 0;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
id,
|
|
31
|
+
node: blockContainer,
|
|
32
|
+
contentNode,
|
|
33
|
+
contentType,
|
|
34
|
+
numChildBlocks,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
14
38
|
/**
|
|
15
39
|
* Retrieves information regarding the nearest blockContainer node in a
|
|
16
40
|
* ProseMirror doc, relative to a position.
|
|
@@ -56,6 +80,7 @@ export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo {
|
|
|
56
80
|
let node = $pos.node(maxDepth);
|
|
57
81
|
let depth = maxDepth;
|
|
58
82
|
|
|
83
|
+
// eslint-disable-next-line no-constant-condition
|
|
59
84
|
while (true) {
|
|
60
85
|
if (depth < 0) {
|
|
61
86
|
throw new Error(
|
|
@@ -71,10 +96,7 @@ export function getBlockInfoFromPos(doc: Node, pos: number): BlockInfo {
|
|
|
71
96
|
node = $pos.node(depth);
|
|
72
97
|
}
|
|
73
98
|
|
|
74
|
-
const id = node
|
|
75
|
-
const contentNode = node.firstChild!;
|
|
76
|
-
const contentType = contentNode.type;
|
|
77
|
-
const numChildBlocks = node.childCount === 2 ? node.lastChild!.childCount : 0;
|
|
99
|
+
const { id, contentNode, contentType, numChildBlocks } = getBlockInfo(node);
|
|
78
100
|
|
|
79
101
|
const startPos = $pos.start(depth);
|
|
80
102
|
const endPos = $pos.end(depth);
|
|
@@ -6,15 +6,15 @@ import {
|
|
|
6
6
|
inlineContentToNodes,
|
|
7
7
|
} from "../../../api/nodeConversions/nodeConversions";
|
|
8
8
|
|
|
9
|
-
import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos";
|
|
10
|
-
import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
|
|
11
|
-
import styles from "./Block.module.css";
|
|
12
|
-
import BlockAttributes from "./BlockAttributes";
|
|
13
9
|
import {
|
|
14
10
|
BlockNoteDOMAttributes,
|
|
15
11
|
BlockSchema,
|
|
16
12
|
PartialBlock,
|
|
17
13
|
} from "../api/blockTypes";
|
|
14
|
+
import { getBlockInfoFromPos } from "../helpers/getBlockInfoFromPos";
|
|
15
|
+
import { PreviousBlockTypePlugin } from "../PreviousBlockTypePlugin";
|
|
16
|
+
import styles from "./Block.module.css";
|
|
17
|
+
import BlockAttributes from "./BlockAttributes";
|
|
18
18
|
import { mergeCSSClasses } from "../../../shared/utils";
|
|
19
19
|
|
|
20
20
|
declare module "@tiptap/core" {
|
|
@@ -60,7 +60,7 @@ export const BlockContainer = Node.create<{
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const attrs: Record<string, string> = {};
|
|
63
|
-
for (
|
|
63
|
+
for (const [nodeAttr, HTMLAttr] of Object.entries(BlockAttributes)) {
|
|
64
64
|
if (element.getAttribute(HTMLAttr)) {
|
|
65
65
|
attrs[nodeAttr] = element.getAttribute(HTMLAttr)!;
|
|
66
66
|
}
|
|
@@ -192,18 +192,42 @@ export const BlockContainer = Node.create<{
|
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
// Since some block types contain inline content and others don't,
|
|
196
|
+
// we either need to call setNodeMarkup to just update type &
|
|
197
|
+
// attributes, or replaceWith to replace the whole blockContent.
|
|
198
|
+
const oldType = contentNode.type.name;
|
|
199
|
+
const newType = block.type || oldType;
|
|
200
|
+
|
|
201
|
+
const oldContentType = state.schema.nodes[oldType].spec.content;
|
|
202
|
+
const newContentType = state.schema.nodes[newType].spec.content;
|
|
203
|
+
|
|
204
|
+
if (oldContentType === "inline*" && newContentType === "") {
|
|
205
|
+
// Replaces the blockContent node with one of the new type and
|
|
206
|
+
// adds the provided props as attributes. Also preserves all
|
|
207
|
+
// existing attributes that are compatible with the new type.
|
|
208
|
+
state.tr.replaceWith(
|
|
209
|
+
startPos,
|
|
210
|
+
endPos,
|
|
211
|
+
state.schema.nodes[newType].create({
|
|
212
|
+
...contentNode.attrs,
|
|
213
|
+
...block.props,
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
// Changes the blockContent node type and adds the provided props
|
|
218
|
+
// as attributes. Also preserves all existing attributes that are
|
|
219
|
+
// compatible with the new type.
|
|
220
|
+
state.tr.setNodeMarkup(
|
|
221
|
+
startPos,
|
|
222
|
+
block.type === undefined
|
|
223
|
+
? undefined
|
|
224
|
+
: state.schema.nodes[block.type],
|
|
225
|
+
{
|
|
226
|
+
...contentNode.attrs,
|
|
227
|
+
...block.props,
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
207
231
|
|
|
208
232
|
// Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing
|
|
209
233
|
// attributes.
|
|
@@ -73,6 +73,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
|
|
|
73
73
|
return [
|
|
74
74
|
"div",
|
|
75
75
|
mergeAttributes(HTMLAttributes, {
|
|
76
|
+
...blockContentDOMAttributes,
|
|
76
77
|
class: mergeCSSClasses(
|
|
77
78
|
styles.blockContent,
|
|
78
79
|
blockContentDOMAttributes.class
|
|
@@ -82,6 +83,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
|
|
|
82
83
|
[
|
|
83
84
|
"h" + node.attrs.level,
|
|
84
85
|
{
|
|
86
|
+
...inlineContentDOMAttributes,
|
|
85
87
|
class: mergeCSSClasses(
|
|
86
88
|
styles.inlineContent,
|
|
87
89
|
inlineContentDOMAttributes.class
|
|
@@ -91,6 +91,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
|
|
|
91
91
|
return [
|
|
92
92
|
"div",
|
|
93
93
|
mergeAttributes(HTMLAttributes, {
|
|
94
|
+
...blockContentDOMAttributes,
|
|
94
95
|
class: mergeCSSClasses(
|
|
95
96
|
styles.blockContent,
|
|
96
97
|
blockContentDOMAttributes.class
|
|
@@ -100,6 +101,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
|
|
|
100
101
|
[
|
|
101
102
|
"p",
|
|
102
103
|
{
|
|
104
|
+
...inlineContentDOMAttributes,
|
|
103
105
|
class: mergeCSSClasses(
|
|
104
106
|
styles.inlineContent,
|
|
105
107
|
inlineContentDOMAttributes.class
|
|
@@ -115,6 +115,7 @@ export const NumberedListItemBlockContent =
|
|
|
115
115
|
return [
|
|
116
116
|
"div",
|
|
117
117
|
mergeAttributes(HTMLAttributes, {
|
|
118
|
+
...blockContentDOMAttributes,
|
|
118
119
|
class: mergeCSSClasses(
|
|
119
120
|
styles.blockContent,
|
|
120
121
|
blockContentDOMAttributes.class
|
|
@@ -126,6 +127,7 @@ export const NumberedListItemBlockContent =
|
|
|
126
127
|
[
|
|
127
128
|
"p",
|
|
128
129
|
{
|
|
130
|
+
...inlineContentDOMAttributes,
|
|
129
131
|
class: mergeCSSClasses(
|
|
130
132
|
styles.inlineContent,
|
|
131
133
|
inlineContentDOMAttributes.class
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Selection } from "prosemirror-state";
|
|
2
1
|
import { Fragment, Node, ResolvedPos, Slice } from "prosemirror-model";
|
|
2
|
+
import { Selection } from "prosemirror-state";
|
|
3
3
|
import { Mappable } from "prosemirror-transform";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -64,8 +64,8 @@ export class MultipleNodeSelection extends Selection {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
map(doc: Node, mapping: Mappable): Selection {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const fromResult = mapping.mapResult(this.from);
|
|
68
|
+
const toResult = mapping.mapResult(this.to);
|
|
69
69
|
|
|
70
70
|
if (toResult.deleted) {
|
|
71
71
|
return Selection.near(doc.resolve(fromResult.pos));
|
|
@@ -32,7 +32,7 @@ function getDraggableBlockFromCoords(
|
|
|
32
32
|
return undefined;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const pos = view.posAtCoords(coords);
|
|
36
36
|
if (!pos) {
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
@@ -61,12 +61,12 @@ function blockPositionFromCoords(
|
|
|
61
61
|
coords: { left: number; top: number },
|
|
62
62
|
view: EditorView
|
|
63
63
|
) {
|
|
64
|
-
|
|
64
|
+
const block = getDraggableBlockFromCoords(coords, view);
|
|
65
65
|
|
|
66
66
|
if (block && block.node.nodeType === 1) {
|
|
67
67
|
// TODO: this uses undocumented PM APIs? do we need this / let's add docs?
|
|
68
68
|
const docView = (view as any).docView;
|
|
69
|
-
|
|
69
|
+
const desc = docView.nearestDesc(block.node, true);
|
|
70
70
|
if (!desc || desc === docView) {
|
|
71
71
|
return null;
|
|
72
72
|
}
|
|
@@ -186,12 +186,12 @@ function dragStart(
|
|
|
186
186
|
|
|
187
187
|
const editorBoundingBox = view.dom.getBoundingClientRect();
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
const coords = {
|
|
190
190
|
left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor
|
|
191
191
|
top: e.clientY,
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
const pos = blockPositionFromCoords(coords, view);
|
|
195
195
|
if (pos != null) {
|
|
196
196
|
const selection = view.state.selection;
|
|
197
197
|
const doc = view.state.doc;
|
|
@@ -215,8 +215,8 @@ function dragStart(
|
|
|
215
215
|
setDragImage(view, pos);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
const slice = view.state.selection.content();
|
|
219
|
+
const { dom, text } = serializeForClipboard(view, slice);
|
|
220
220
|
|
|
221
221
|
e.dataTransfer.clearData();
|
|
222
222
|
e.dataTransfer.setData("text/html", dom.innerHTML);
|
|
@@ -288,7 +288,7 @@ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
|
|
|
288
288
|
return;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
const pos = this.pmView.posAtCoords({
|
|
292
292
|
left: event.clientX,
|
|
293
293
|
top: event.clientY,
|
|
294
294
|
});
|
|
@@ -319,7 +319,7 @@ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
|
|
|
319
319
|
if ((event as any).synthetic || !this.isDragging) {
|
|
320
320
|
return;
|
|
321
321
|
}
|
|
322
|
-
|
|
322
|
+
const pos = this.pmView.posAtCoords({
|
|
323
323
|
left: event.clientX,
|
|
324
324
|
top: event.clientY,
|
|
325
325
|
});
|
|
@@ -64,7 +64,19 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
|
|
64
64
|
if (!lastNode || lastNode.type.name !== "blockContainer") {
|
|
65
65
|
throw new Error("Expected blockContainer");
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
|
|
68
|
+
const lastContentNode = lastNode.firstChild;
|
|
69
|
+
|
|
70
|
+
if (!lastContentNode) {
|
|
71
|
+
throw new Error("Expected blockContent");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If last node is not empty (size > 4) or it doesn't contain
|
|
75
|
+
// inline content, we need to add a trailing node.
|
|
76
|
+
return (
|
|
77
|
+
lastNode.nodeSize > 4 ||
|
|
78
|
+
lastContentNode.type.spec.content !== "inline*"
|
|
79
|
+
);
|
|
68
80
|
},
|
|
69
81
|
},
|
|
70
82
|
}),
|