@blocknote/core 0.30.1 → 0.31.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/dist/blocknote.cjs +9 -9
- package/dist/blocknote.cjs.map +1 -1
- package/dist/blocknote.js +2754 -2230
- package/dist/blocknote.js.map +1 -1
- package/dist/{en-D4taoCs4.cjs → en-BXVKCwYt.cjs} +2 -2
- package/dist/en-BXVKCwYt.cjs.map +1 -0
- package/dist/{en-B7ycW7c8.js → en-qGo6sk9V.js} +2 -3
- package/dist/en-qGo6sk9V.js.map +1 -0
- package/dist/locales.cjs +1 -1
- package/dist/locales.cjs.map +1 -1
- package/dist/locales.js +20 -39
- package/dist/locales.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/webpack-stats.json +1 -1
- package/package.json +4 -5
- package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +2 -3
- package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +1 -1
- package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2816 -0
- package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +158 -0
- package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +87 -17
- package/src/api/blockManipulation/selections/selection.ts +48 -1
- package/src/api/blockManipulation/selections/{textCursorPosition/textCursorPosition.ts → textCursorPosition.ts} +7 -7
- package/src/api/getBlockInfoFromPos.ts +1 -1
- package/src/api/nodeConversions/blockToNode.ts +5 -2
- package/src/api/nodeConversions/nodeToBlock.ts +203 -8
- package/src/api/pmUtil.ts +3 -3
- package/src/blocks/CodeBlockContent/CodeBlockContent.ts +6 -6
- package/src/blocks/FileBlockContent/helpers/render/createAddFileButton.ts +1 -1
- package/src/blocks/TableBlockContent/TableBlockContent.ts +32 -2
- package/src/editor/Block.css +27 -1
- package/src/editor/BlockNoteEditor.test.ts +7 -0
- package/src/editor/BlockNoteEditor.ts +88 -37
- package/src/editor/BlockNoteExtension.ts +26 -0
- package/src/editor/BlockNoteExtensions.ts +28 -12
- package/src/editor/BlockNoteTipTapEditor.ts +23 -2
- package/src/extensions/Collaboration/CursorPlugin.ts +13 -7
- package/src/extensions/Collaboration/ForkYDocPlugin.test.ts +166 -0
- package/src/extensions/Collaboration/ForkYDocPlugin.ts +174 -0
- package/src/extensions/Collaboration/SyncPlugin.ts +7 -4
- package/src/extensions/Collaboration/UndoPlugin.ts +7 -4
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +30 -0
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +30 -0
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +1 -0
- package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +1 -0
- package/src/extensions/Comments/CommentsPlugin.ts +75 -70
- package/src/extensions/FilePanel/FilePanelPlugin.ts +50 -49
- package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +56 -26
- package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +22 -21
- package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +45 -42
- package/src/extensions/Placeholder/PlaceholderPlugin.ts +111 -108
- package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +179 -170
- package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +22 -19
- package/src/extensions/SideMenu/SideMenuPlugin.ts +19 -18
- package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +168 -168
- package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +4 -4
- package/src/extensions/Suggestions/SuggestionMarks.ts +175 -0
- package/src/extensions/TableHandles/TableHandlesPlugin.ts +153 -150
- package/src/i18n/locales/ar.ts +0 -1
- package/src/i18n/locales/de.ts +0 -1
- package/src/i18n/locales/en.ts +0 -1
- package/src/i18n/locales/es.ts +0 -1
- package/src/i18n/locales/fr.ts +0 -1
- package/src/i18n/locales/hr.ts +0 -1
- package/src/i18n/locales/is.ts +0 -1
- package/src/i18n/locales/it.ts +0 -1
- package/src/i18n/locales/ja.ts +0 -1
- package/src/i18n/locales/ko.ts +0 -1
- package/src/i18n/locales/nl.ts +0 -1
- package/src/i18n/locales/no.ts +0 -1
- package/src/i18n/locales/pl.ts +0 -1
- package/src/i18n/locales/pt.ts +0 -1
- package/src/i18n/locales/ru.ts +0 -1
- package/src/i18n/locales/sk.ts +0 -1
- package/src/i18n/locales/uk.ts +0 -1
- package/src/i18n/locales/vi.ts +0 -1
- package/src/i18n/locales/zh-tw.ts +0 -1
- package/src/i18n/locales/zh.ts +0 -1
- package/src/index.ts +18 -8
- package/src/pm-nodes/BlockContainer.ts +1 -1
- package/src/pm-nodes/BlockGroup.ts +1 -1
- package/src/pm-nodes/Doc.ts +1 -0
- package/types/src/api/blockManipulation/commands/insertBlocks/insertBlocks.d.ts +1 -1
- package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.d.ts +4 -0
- package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.d.ts +1 -0
- package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +3 -1
- package/types/src/api/blockManipulation/selections/selection.d.ts +10 -0
- package/types/src/api/blockManipulation/selections/textCursorPosition.d.ts +5 -0
- package/types/src/api/blockManipulation/transactions.test.d.ts +0 -0
- package/types/src/api/clipboard/clipboardExternal.test.d.ts +1 -0
- package/types/src/api/clipboard/clipboardInternal.test.d.ts +1 -0
- package/types/src/api/clipboard/testUtil.d.ts +541 -0
- package/types/src/api/exporters/html/htmlConversion.test.d.ts +1 -0
- package/types/src/api/exporters/markdown/markdownExporter.test.d.ts +1 -0
- package/types/src/api/nodeConversions/nodeConversions.test.d.ts +1 -0
- package/types/src/api/nodeConversions/nodeToBlock.d.ts +39 -2
- package/types/src/api/parsers/html/parseHTML.test.d.ts +1 -0
- package/types/src/api/parsers/markdown/parseMarkdown.test.d.ts +1 -0
- package/types/src/api/pmUtil.d.ts +3 -3
- package/types/src/api/testUtil/cases/customBlocks.d.ts +670 -0
- package/types/src/api/testUtil/cases/customInlineContent.d.ts +558 -0
- package/types/src/api/testUtil/cases/customStyles.d.ts +552 -0
- package/types/src/api/testUtil/cases/defaultSchema.d.ts +4 -0
- package/types/src/api/testUtil/index.d.ts +14 -0
- package/types/src/api/testUtil/partialBlockTestUtil.d.ts +9 -0
- package/types/src/api/testUtil/paste.d.ts +2 -0
- package/types/src/blocks/CodeBlockContent/defaultSupportedLanguages.d.ts +6 -0
- package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +9 -1
- package/types/src/editor/BlockNoteEditor.d.ts +55 -9
- package/types/src/editor/BlockNoteExtension.d.ts +9 -0
- package/types/src/editor/BlockNoteExtensions.d.ts +2 -2
- package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
- package/types/src/extensions/Collaboration/CursorPlugin.d.ts +3 -3
- package/types/src/extensions/Collaboration/ForkYDocPlugin.d.ts +41 -0
- package/types/src/extensions/Collaboration/ForkYDocPlugin.test.d.ts +1 -0
- package/types/src/extensions/Collaboration/SyncPlugin.d.ts +3 -3
- package/types/src/extensions/Collaboration/UndoPlugin.d.ts +3 -3
- package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +17 -0
- package/types/src/extensions/Comments/CommentsPlugin.d.ts +2 -4
- package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +3 -4
- package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +5 -5
- package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +3 -4
- package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +2 -3
- package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +2 -3
- package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +2 -3
- package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +2 -3
- package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +3 -4
- package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +2 -4
- package/types/src/extensions/Suggestions/SuggestionMarks.d.ts +4 -0
- package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +5 -6
- package/types/src/i18n/locales/en.d.ts +0 -1
- package/types/src/i18n/locales/sk.d.ts +0 -1
- package/types/src/index.d.ts +15 -8
- package/dist/en-B7ycW7c8.js.map +0 -1
- package/dist/en-D4taoCs4.cjs.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +0 -844
- package/src/api/blockManipulation/selections/selection.test.ts +0 -72
- package/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap +0 -316
- package/src/api/blockManipulation/selections/textCursorPosition/textCursorPosition.test.ts +0 -74
|
@@ -2,11 +2,11 @@ import { Plugin, PluginKey } from "prosemirror-state";
|
|
|
2
2
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
|
|
5
|
+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
|
|
5
6
|
|
|
6
7
|
const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
|
|
7
8
|
|
|
8
|
-
export class PlaceholderPlugin {
|
|
9
|
-
public readonly plugin: Plugin;
|
|
9
|
+
export class PlaceholderPlugin extends BlockNoteExtension {
|
|
10
10
|
constructor(
|
|
11
11
|
editor: BlockNoteEditor<any, any, any>,
|
|
12
12
|
placeholders: Record<
|
|
@@ -14,127 +14,130 @@ export class PlaceholderPlugin {
|
|
|
14
14
|
string | undefined
|
|
15
15
|
>,
|
|
16
16
|
) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
view
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
// FIXME: the names "default" and "emptyDocument" are hardcoded
|
|
42
|
-
const {
|
|
43
|
-
default: defaultPlaceholder,
|
|
44
|
-
emptyDocument: emptyPlaceholder,
|
|
45
|
-
...rest
|
|
46
|
-
} = placeholders;
|
|
47
|
-
|
|
48
|
-
// add block specific placeholders
|
|
49
|
-
for (const [blockType, placeholder] of Object.entries(rest)) {
|
|
50
|
-
const blockTypeSelector = `[data-content-type="${blockType}"]`;
|
|
17
|
+
super();
|
|
18
|
+
this.addProsemirrorPlugin(
|
|
19
|
+
new Plugin({
|
|
20
|
+
key: PLUGIN_KEY,
|
|
21
|
+
view: (view) => {
|
|
22
|
+
const uniqueEditorSelector = `placeholder-selector-${v4()}`;
|
|
23
|
+
view.dom.classList.add(uniqueEditorSelector);
|
|
24
|
+
const styleEl = document.createElement("style");
|
|
25
|
+
|
|
26
|
+
const nonce = editor._tiptapEditor.options.injectNonce;
|
|
27
|
+
if (nonce) {
|
|
28
|
+
styleEl.setAttribute("nonce", nonce);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (editor.prosemirrorView?.root instanceof ShadowRoot) {
|
|
32
|
+
editor.prosemirrorView.root.append(styleEl);
|
|
33
|
+
} else {
|
|
34
|
+
editor.prosemirrorView?.root.head.appendChild(styleEl);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const styleSheet = styleEl.sheet!;
|
|
51
38
|
|
|
39
|
+
const getSelector = (additionalSelectors = "") =>
|
|
40
|
+
`.${uniqueEditorSelector} .bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// FIXME: the names "default" and "emptyDocument" are hardcoded
|
|
44
|
+
const {
|
|
45
|
+
default: defaultPlaceholder,
|
|
46
|
+
emptyDocument: emptyPlaceholder,
|
|
47
|
+
...rest
|
|
48
|
+
} = placeholders;
|
|
49
|
+
|
|
50
|
+
// add block specific placeholders
|
|
51
|
+
for (const [blockType, placeholder] of Object.entries(rest)) {
|
|
52
|
+
const blockTypeSelector = `[data-content-type="${blockType}"]`;
|
|
53
|
+
|
|
54
|
+
styleSheet.insertRule(
|
|
55
|
+
`${getSelector(blockTypeSelector)} { content: ${JSON.stringify(
|
|
56
|
+
placeholder,
|
|
57
|
+
)}; }`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const onlyBlockSelector = `[data-is-only-empty-block]`;
|
|
62
|
+
const mustBeFocusedSelector = `[data-is-empty-and-focused]`;
|
|
63
|
+
|
|
64
|
+
// placeholder for when there's only one empty block
|
|
52
65
|
styleSheet.insertRule(
|
|
53
|
-
`${getSelector(
|
|
54
|
-
|
|
66
|
+
`${getSelector(onlyBlockSelector)} { content: ${JSON.stringify(
|
|
67
|
+
emptyPlaceholder,
|
|
55
68
|
)}; }`,
|
|
56
69
|
);
|
|
70
|
+
|
|
71
|
+
// placeholder for default blocks, only when the cursor is in the block (mustBeFocused)
|
|
72
|
+
styleSheet.insertRule(
|
|
73
|
+
`${getSelector(mustBeFocusedSelector)} { content: ${JSON.stringify(
|
|
74
|
+
defaultPlaceholder,
|
|
75
|
+
)}; }`,
|
|
76
|
+
);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.warn(
|
|
80
|
+
`Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`,
|
|
81
|
+
e,
|
|
82
|
+
);
|
|
57
83
|
}
|
|
58
84
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
} catch (e) {
|
|
76
|
-
// eslint-disable-next-line no-console
|
|
77
|
-
console.warn(
|
|
78
|
-
`Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)`,
|
|
79
|
-
e,
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
destroy: () => {
|
|
85
|
-
if (editor.prosemirrorView?.root instanceof ShadowRoot) {
|
|
86
|
-
editor.prosemirrorView.root.removeChild(styleEl);
|
|
87
|
-
} else {
|
|
88
|
-
editor.prosemirrorView?.root.head.removeChild(styleEl);
|
|
85
|
+
return {
|
|
86
|
+
destroy: () => {
|
|
87
|
+
if (editor.prosemirrorView?.root instanceof ShadowRoot) {
|
|
88
|
+
editor.prosemirrorView.root.removeChild(styleEl);
|
|
89
|
+
} else {
|
|
90
|
+
editor.prosemirrorView?.root.head.removeChild(styleEl);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
props: {
|
|
96
|
+
decorations: (state) => {
|
|
97
|
+
const { doc, selection } = state;
|
|
98
|
+
|
|
99
|
+
if (!editor.isEditable) {
|
|
100
|
+
return;
|
|
89
101
|
}
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
props: {
|
|
94
|
-
decorations: (state) => {
|
|
95
|
-
const { doc, selection } = state;
|
|
96
|
-
|
|
97
|
-
if (!editor.isEditable) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
if (!selection.empty) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// Don't show placeholder when the cursor is inside a code block
|
|
108
|
+
if (selection.$from.parent.type.spec.code) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
const decs = [];
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
// decoration for when there's only one empty block
|
|
115
|
+
// positions are hardcoded for now
|
|
116
|
+
if (state.doc.content.size === 6) {
|
|
117
|
+
decs.push(
|
|
118
|
+
Decoration.node(2, 4, {
|
|
119
|
+
"data-is-only-empty-block": "true",
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
const $pos = selection.$anchor;
|
|
125
|
+
const node = $pos.parent;
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
if (node.content.size === 0) {
|
|
128
|
+
const before = $pos.before();
|
|
127
129
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
decs.push(
|
|
131
|
+
Decoration.node(before, before + node.nodeSize, {
|
|
132
|
+
"data-is-empty-and-focused": "true",
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
return DecorationSet.create(doc, decs);
|
|
138
|
+
},
|
|
136
139
|
},
|
|
137
|
-
},
|
|
138
|
-
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
139
142
|
}
|
|
140
143
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { findChildren } from "@tiptap/core";
|
|
2
2
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
3
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
4
|
+
import { BlockNoteExtension } from "../../editor/BlockNoteExtension.js";
|
|
4
5
|
|
|
5
6
|
const PLUGIN_KEY = new PluginKey(`previous-blocks`);
|
|
6
7
|
|
|
@@ -23,199 +24,207 @@ const nodeAttributes: Record<string, string> = {
|
|
|
23
24
|
*
|
|
24
25
|
* Solution: When attributes change on a node, this plugin sets a data-* attribute with the "previous" value. This way we can still use CSS transitions. (See block.module.css)
|
|
25
26
|
*/
|
|
26
|
-
export class PreviousBlockTypePlugin {
|
|
27
|
-
public readonly plugin: Plugin;
|
|
27
|
+
export class PreviousBlockTypePlugin extends BlockNoteExtension {
|
|
28
28
|
constructor() {
|
|
29
|
+
super();
|
|
29
30
|
let timeout: ReturnType<typeof setTimeout>;
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
update: async (view, _prevState) => {
|
|
35
|
-
if (this.key?.getState(view.state).updatedBlocks.size > 0) {
|
|
36
|
-
// use setTimeout 0 to clear the decorations so that at least
|
|
37
|
-
// for one DOM-render the decorations have been applied
|
|
38
|
-
timeout = setTimeout(() => {
|
|
39
|
-
view.dispatch(
|
|
40
|
-
view.state.tr.setMeta(PLUGIN_KEY, { clearUpdate: true }),
|
|
41
|
-
);
|
|
42
|
-
}, 0);
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
destroy: () => {
|
|
46
|
-
if (timeout) {
|
|
47
|
-
clearTimeout(timeout);
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
state: {
|
|
53
|
-
init() {
|
|
31
|
+
this.addProsemirrorPlugin(
|
|
32
|
+
new Plugin({
|
|
33
|
+
key: PLUGIN_KEY,
|
|
34
|
+
view(_editorView) {
|
|
54
35
|
return {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
update: async (view, _prevState) => {
|
|
37
|
+
if (this.key?.getState(view.state).updatedBlocks.size > 0) {
|
|
38
|
+
// use setTimeout 0 to clear the decorations so that at least
|
|
39
|
+
// for one DOM-render the decorations have been applied
|
|
40
|
+
timeout = setTimeout(() => {
|
|
41
|
+
view.dispatch(
|
|
42
|
+
view.state.tr.setMeta(PLUGIN_KEY, { clearUpdate: true }),
|
|
43
|
+
);
|
|
44
|
+
}, 0);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
destroy: () => {
|
|
48
|
+
if (timeout) {
|
|
49
|
+
clearTimeout(timeout);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
61
52
|
};
|
|
62
53
|
},
|
|
54
|
+
state: {
|
|
55
|
+
init() {
|
|
56
|
+
return {
|
|
57
|
+
// Block attributes, by block ID, from just before the previous transaction.
|
|
58
|
+
prevTransactionOldBlockAttrs: {} as any,
|
|
59
|
+
// Block attributes, by block ID, from just before the current transaction.
|
|
60
|
+
currentTransactionOldBlockAttrs: {} as any,
|
|
61
|
+
// Set of IDs of blocks whose attributes changed from the current transaction.
|
|
62
|
+
updatedBlocks: new Set<string>(),
|
|
63
|
+
};
|
|
64
|
+
},
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
apply(transaction, prev, oldState, newState) {
|
|
67
|
+
prev.currentTransactionOldBlockAttrs = {};
|
|
68
|
+
prev.updatedBlocks.clear();
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// TODO: Instead of iterating through the entire document, only check nodes affected by the transactions. Will
|
|
73
|
-
// also probably require checking nodes affected by the previous transaction too.
|
|
74
|
-
// We didn't get this to work yet:
|
|
75
|
-
// const transform = combineTransactionSteps(oldState.doc, [transaction]);
|
|
76
|
-
// // const { mapping } = transform;
|
|
77
|
-
// const changes = getChangedRanges(transform);
|
|
78
|
-
//
|
|
79
|
-
// changes.forEach(({ oldRange, newRange }) => {
|
|
80
|
-
// const oldNodes = findChildrenInRange(
|
|
81
|
-
// oldState.doc,
|
|
82
|
-
// oldRange,
|
|
83
|
-
// (node) => node.attrs.id
|
|
84
|
-
// );
|
|
85
|
-
//
|
|
86
|
-
// const newNodes = findChildrenInRange(
|
|
87
|
-
// newState.doc,
|
|
88
|
-
// newRange,
|
|
89
|
-
// (node) => node.attrs.id
|
|
90
|
-
// );
|
|
91
|
-
|
|
92
|
-
const currentTransactionOriginalOldBlockAttrs = {} as any;
|
|
93
|
-
|
|
94
|
-
const oldNodes = findChildren(oldState.doc, (node) => node.attrs.id);
|
|
95
|
-
const oldNodesById = new Map(
|
|
96
|
-
oldNodes.map((node) => [node.node.attrs.id, node]),
|
|
97
|
-
);
|
|
98
|
-
const newNodes = findChildren(newState.doc, (node) => node.attrs.id);
|
|
99
|
-
|
|
100
|
-
// Traverses all block containers in the new editor state.
|
|
101
|
-
for (const node of newNodes) {
|
|
102
|
-
const oldNode = oldNodesById.get(node.node.attrs.id);
|
|
103
|
-
|
|
104
|
-
const oldContentNode = oldNode?.node.firstChild;
|
|
105
|
-
const newContentNode = node.node.firstChild;
|
|
106
|
-
|
|
107
|
-
if (oldNode && oldContentNode && newContentNode) {
|
|
108
|
-
const newAttrs = {
|
|
109
|
-
index: newContentNode.attrs.index,
|
|
110
|
-
level: newContentNode.attrs.level,
|
|
111
|
-
type: newContentNode.type.name,
|
|
112
|
-
depth: newState.doc.resolve(node.pos).depth,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
let oldAttrs = {
|
|
116
|
-
index: oldContentNode.attrs.index,
|
|
117
|
-
level: oldContentNode.attrs.level,
|
|
118
|
-
type: oldContentNode.type.name,
|
|
119
|
-
depth: oldState.doc.resolve(oldNode.pos).depth,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
currentTransactionOriginalOldBlockAttrs[node.node.attrs.id] =
|
|
123
|
-
oldAttrs;
|
|
124
|
-
|
|
125
|
-
// Whenever a transaction is appended by the OrderedListItemIndexPlugin, it's given the metadata:
|
|
126
|
-
// { "orderedListIndexing": true }
|
|
127
|
-
// These appended transactions happen immediately after any transaction which causes ordered list item
|
|
128
|
-
// indices to require updating, including those which trigger animations. Therefore, these animations are
|
|
129
|
-
// immediately overridden when the PreviousBlockTypePlugin processes the appended transaction, despite only
|
|
130
|
-
// the listItemIndex attribute changing. To solve this, oldAttrs must be edited for transactions with the
|
|
131
|
-
// "orderedListIndexing" metadata, so the correct animation can be re-triggered.
|
|
132
|
-
if (transaction.getMeta("numberedListIndexing")) {
|
|
133
|
-
// If the block existed before the transaction, gets the attributes from before the previous transaction
|
|
134
|
-
// (i.e. the transaction that caused list item indices to need updating).
|
|
135
|
-
if (node.node.attrs.id in prev.prevTransactionOldBlockAttrs) {
|
|
136
|
-
oldAttrs =
|
|
137
|
-
prev.prevTransactionOldBlockAttrs[node.node.attrs.id];
|
|
138
|
-
}
|
|
70
|
+
if (!transaction.docChanged || oldState.doc.eq(newState.doc)) {
|
|
71
|
+
return prev;
|
|
72
|
+
}
|
|
139
73
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
74
|
+
// TODO: Instead of iterating through the entire document, only check nodes affected by the transactions. Will
|
|
75
|
+
// also probably require checking nodes affected by the previous transaction too.
|
|
76
|
+
// We didn't get this to work yet:
|
|
77
|
+
// const transform = combineTransactionSteps(oldState.doc, [transaction]);
|
|
78
|
+
// // const { mapping } = transform;
|
|
79
|
+
// const changes = getChangedRanges(transform);
|
|
80
|
+
//
|
|
81
|
+
// changes.forEach(({ oldRange, newRange }) => {
|
|
82
|
+
// const oldNodes = findChildrenInRange(
|
|
83
|
+
// oldState.doc,
|
|
84
|
+
// oldRange,
|
|
85
|
+
// (node) => node.attrs.id
|
|
86
|
+
// );
|
|
87
|
+
//
|
|
88
|
+
// const newNodes = findChildrenInRange(
|
|
89
|
+
// newState.doc,
|
|
90
|
+
// newRange,
|
|
91
|
+
// (node) => node.attrs.id
|
|
92
|
+
// );
|
|
93
|
+
|
|
94
|
+
const currentTransactionOriginalOldBlockAttrs = {} as any;
|
|
95
|
+
|
|
96
|
+
const oldNodes = findChildren(
|
|
97
|
+
oldState.doc,
|
|
98
|
+
(node) => node.attrs.id,
|
|
99
|
+
);
|
|
100
|
+
const oldNodesById = new Map(
|
|
101
|
+
oldNodes.map((node) => [node.node.attrs.id, node]),
|
|
102
|
+
);
|
|
103
|
+
const newNodes = findChildren(
|
|
104
|
+
newState.doc,
|
|
105
|
+
(node) => node.attrs.id,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Traverses all block containers in the new editor state.
|
|
109
|
+
for (const node of newNodes) {
|
|
110
|
+
const oldNode = oldNodesById.get(node.node.attrs.id);
|
|
111
|
+
|
|
112
|
+
const oldContentNode = oldNode?.node.firstChild;
|
|
113
|
+
const newContentNode = node.node.firstChild;
|
|
114
|
+
|
|
115
|
+
if (oldNode && oldContentNode && newContentNode) {
|
|
116
|
+
const newAttrs = {
|
|
117
|
+
index: newContentNode.attrs.index,
|
|
118
|
+
level: newContentNode.attrs.level,
|
|
119
|
+
type: newContentNode.type.name,
|
|
120
|
+
depth: newState.doc.resolve(node.pos).depth,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
let oldAttrs = {
|
|
124
|
+
index: oldContentNode.attrs.index,
|
|
125
|
+
level: oldContentNode.attrs.level,
|
|
126
|
+
type: oldContentNode.type.name,
|
|
127
|
+
depth: oldState.doc.resolve(oldNode.pos).depth,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
currentTransactionOriginalOldBlockAttrs[node.node.attrs.id] =
|
|
131
|
+
oldAttrs;
|
|
132
|
+
|
|
133
|
+
// Whenever a transaction is appended by the OrderedListItemIndexPlugin, it's given the metadata:
|
|
134
|
+
// { "orderedListIndexing": true }
|
|
135
|
+
// These appended transactions happen immediately after any transaction which causes ordered list item
|
|
136
|
+
// indices to require updating, including those which trigger animations. Therefore, these animations are
|
|
137
|
+
// immediately overridden when the PreviousBlockTypePlugin processes the appended transaction, despite only
|
|
138
|
+
// the listItemIndex attribute changing. To solve this, oldAttrs must be edited for transactions with the
|
|
139
|
+
// "orderedListIndexing" metadata, so the correct animation can be re-triggered.
|
|
140
|
+
if (transaction.getMeta("numberedListIndexing")) {
|
|
141
|
+
// If the block existed before the transaction, gets the attributes from before the previous transaction
|
|
142
|
+
// (i.e. the transaction that caused list item indices to need updating).
|
|
143
|
+
if (node.node.attrs.id in prev.prevTransactionOldBlockAttrs) {
|
|
144
|
+
oldAttrs =
|
|
145
|
+
prev.prevTransactionOldBlockAttrs[node.node.attrs.id];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Stops list item indices themselves being animated (looks smoother), unless the block's content type is
|
|
149
|
+
// changing from a numbered list item to something else.
|
|
150
|
+
if (newAttrs.type === "numberedListItem") {
|
|
151
|
+
oldAttrs.index = newAttrs.index;
|
|
152
|
+
}
|
|
144
153
|
}
|
|
145
|
-
}
|
|
146
154
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
155
|
+
prev.currentTransactionOldBlockAttrs[node.node.attrs.id] =
|
|
156
|
+
oldAttrs;
|
|
157
|
+
|
|
158
|
+
// TODO: faster deep equal?
|
|
159
|
+
if (JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs)) {
|
|
160
|
+
(oldAttrs as any)["depth-change"] =
|
|
161
|
+
oldAttrs.depth - newAttrs.depth;
|
|
162
|
+
|
|
163
|
+
// for debugging:
|
|
164
|
+
// console.log(
|
|
165
|
+
// "id:",
|
|
166
|
+
// node.node.attrs.id,
|
|
167
|
+
// "previousBlockTypePlugin changes detected, oldAttrs",
|
|
168
|
+
// oldAttrs,
|
|
169
|
+
// "new",
|
|
170
|
+
// newAttrs
|
|
171
|
+
// );
|
|
172
|
+
|
|
173
|
+
prev.updatedBlocks.add(node.node.attrs.id);
|
|
174
|
+
}
|
|
166
175
|
}
|
|
167
176
|
}
|
|
168
|
-
}
|
|
169
177
|
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
prev.prevTransactionOldBlockAttrs =
|
|
179
|
+
currentTransactionOriginalOldBlockAttrs;
|
|
172
180
|
|
|
173
|
-
|
|
181
|
+
return prev;
|
|
182
|
+
},
|
|
174
183
|
},
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const decorations: Decoration[] = [];
|
|
184
|
-
|
|
185
|
-
state.doc.descendants((node, pos) => {
|
|
186
|
-
if (!node.attrs.id) {
|
|
187
|
-
return;
|
|
184
|
+
props: {
|
|
185
|
+
decorations(state) {
|
|
186
|
+
const pluginState = (this as Plugin).getState(state);
|
|
187
|
+
if (pluginState.updatedBlocks.size === 0) {
|
|
188
|
+
return undefined;
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
191
|
+
const decorations: Decoration[] = [];
|
|
193
192
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
state.doc.descendants((node, pos) => {
|
|
194
|
+
if (!node.attrs.id) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
198
|
+
if (!pluginState.updatedBlocks.has(node.attrs.id)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
202
201
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// decorationAttrs
|
|
207
|
-
// );
|
|
202
|
+
const prevAttrs =
|
|
203
|
+
pluginState.currentTransactionOldBlockAttrs[node.attrs.id];
|
|
204
|
+
const decorationAttrs: any = {};
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
for (const [nodeAttr, val] of Object.entries(prevAttrs)) {
|
|
207
|
+
decorationAttrs["data-prev-" + nodeAttributes[nodeAttr]] =
|
|
208
|
+
val || "none";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// for debugging:
|
|
212
|
+
// console.log(
|
|
213
|
+
// "previousBlockTypePlugin committing decorations",
|
|
214
|
+
// decorationAttrs
|
|
215
|
+
// );
|
|
212
216
|
|
|
213
|
-
|
|
214
|
-
|
|
217
|
+
const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
|
218
|
+
...decorationAttrs,
|
|
219
|
+
});
|
|
215
220
|
|
|
216
|
-
|
|
221
|
+
decorations.push(decoration);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return DecorationSet.create(state.doc, decorations);
|
|
225
|
+
},
|
|
217
226
|
},
|
|
218
|
-
},
|
|
219
|
-
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
220
229
|
}
|
|
221
230
|
}
|