@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.
Files changed (139) hide show
  1. package/dist/blocknote.cjs +9 -9
  2. package/dist/blocknote.cjs.map +1 -1
  3. package/dist/blocknote.js +2754 -2230
  4. package/dist/blocknote.js.map +1 -1
  5. package/dist/{en-D4taoCs4.cjs → en-BXVKCwYt.cjs} +2 -2
  6. package/dist/en-BXVKCwYt.cjs.map +1 -0
  7. package/dist/{en-B7ycW7c8.js → en-qGo6sk9V.js} +2 -3
  8. package/dist/en-qGo6sk9V.js.map +1 -0
  9. package/dist/locales.cjs +1 -1
  10. package/dist/locales.cjs.map +1 -1
  11. package/dist/locales.js +20 -39
  12. package/dist/locales.js.map +1 -1
  13. package/dist/style.css +1 -1
  14. package/dist/webpack-stats.json +1 -1
  15. package/package.json +4 -5
  16. package/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +2 -3
  17. package/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +1 -1
  18. package/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +2816 -0
  19. package/src/api/blockManipulation/commands/updateBlock/updateBlock.test.ts +158 -0
  20. package/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +87 -17
  21. package/src/api/blockManipulation/selections/selection.ts +48 -1
  22. package/src/api/blockManipulation/selections/{textCursorPosition/textCursorPosition.ts → textCursorPosition.ts} +7 -7
  23. package/src/api/getBlockInfoFromPos.ts +1 -1
  24. package/src/api/nodeConversions/blockToNode.ts +5 -2
  25. package/src/api/nodeConversions/nodeToBlock.ts +203 -8
  26. package/src/api/pmUtil.ts +3 -3
  27. package/src/blocks/CodeBlockContent/CodeBlockContent.ts +6 -6
  28. package/src/blocks/FileBlockContent/helpers/render/createAddFileButton.ts +1 -1
  29. package/src/blocks/TableBlockContent/TableBlockContent.ts +32 -2
  30. package/src/editor/Block.css +27 -1
  31. package/src/editor/BlockNoteEditor.test.ts +7 -0
  32. package/src/editor/BlockNoteEditor.ts +88 -37
  33. package/src/editor/BlockNoteExtension.ts +26 -0
  34. package/src/editor/BlockNoteExtensions.ts +28 -12
  35. package/src/editor/BlockNoteTipTapEditor.ts +23 -2
  36. package/src/extensions/Collaboration/CursorPlugin.ts +13 -7
  37. package/src/extensions/Collaboration/ForkYDocPlugin.test.ts +166 -0
  38. package/src/extensions/Collaboration/ForkYDocPlugin.ts +174 -0
  39. package/src/extensions/Collaboration/SyncPlugin.ts +7 -4
  40. package/src/extensions/Collaboration/UndoPlugin.ts +7 -4
  41. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +30 -0
  42. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +30 -0
  43. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +1 -0
  44. package/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +1 -0
  45. package/src/extensions/Comments/CommentsPlugin.ts +75 -70
  46. package/src/extensions/FilePanel/FilePanelPlugin.ts +50 -49
  47. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +56 -26
  48. package/src/extensions/LinkToolbar/LinkToolbarPlugin.ts +22 -21
  49. package/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.ts +45 -42
  50. package/src/extensions/Placeholder/PlaceholderPlugin.ts +111 -108
  51. package/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.ts +179 -170
  52. package/src/extensions/ShowSelection/ShowSelectionPlugin.ts +22 -19
  53. package/src/extensions/SideMenu/SideMenuPlugin.ts +19 -18
  54. package/src/extensions/SuggestionMenu/SuggestionPlugin.ts +168 -168
  55. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +4 -4
  56. package/src/extensions/Suggestions/SuggestionMarks.ts +175 -0
  57. package/src/extensions/TableHandles/TableHandlesPlugin.ts +153 -150
  58. package/src/i18n/locales/ar.ts +0 -1
  59. package/src/i18n/locales/de.ts +0 -1
  60. package/src/i18n/locales/en.ts +0 -1
  61. package/src/i18n/locales/es.ts +0 -1
  62. package/src/i18n/locales/fr.ts +0 -1
  63. package/src/i18n/locales/hr.ts +0 -1
  64. package/src/i18n/locales/is.ts +0 -1
  65. package/src/i18n/locales/it.ts +0 -1
  66. package/src/i18n/locales/ja.ts +0 -1
  67. package/src/i18n/locales/ko.ts +0 -1
  68. package/src/i18n/locales/nl.ts +0 -1
  69. package/src/i18n/locales/no.ts +0 -1
  70. package/src/i18n/locales/pl.ts +0 -1
  71. package/src/i18n/locales/pt.ts +0 -1
  72. package/src/i18n/locales/ru.ts +0 -1
  73. package/src/i18n/locales/sk.ts +0 -1
  74. package/src/i18n/locales/uk.ts +0 -1
  75. package/src/i18n/locales/vi.ts +0 -1
  76. package/src/i18n/locales/zh-tw.ts +0 -1
  77. package/src/i18n/locales/zh.ts +0 -1
  78. package/src/index.ts +18 -8
  79. package/src/pm-nodes/BlockContainer.ts +1 -1
  80. package/src/pm-nodes/BlockGroup.ts +1 -1
  81. package/src/pm-nodes/Doc.ts +1 -0
  82. package/types/src/api/blockManipulation/commands/insertBlocks/insertBlocks.d.ts +1 -1
  83. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.d.ts +4 -0
  84. package/types/src/api/blockManipulation/commands/removeBlocks/removeBlocks.test.d.ts +1 -0
  85. package/types/src/api/blockManipulation/commands/updateBlock/updateBlock.d.ts +3 -1
  86. package/types/src/api/blockManipulation/selections/selection.d.ts +10 -0
  87. package/types/src/api/blockManipulation/selections/textCursorPosition.d.ts +5 -0
  88. package/types/src/api/blockManipulation/transactions.test.d.ts +0 -0
  89. package/types/src/api/clipboard/clipboardExternal.test.d.ts +1 -0
  90. package/types/src/api/clipboard/clipboardInternal.test.d.ts +1 -0
  91. package/types/src/api/clipboard/testUtil.d.ts +541 -0
  92. package/types/src/api/exporters/html/htmlConversion.test.d.ts +1 -0
  93. package/types/src/api/exporters/markdown/markdownExporter.test.d.ts +1 -0
  94. package/types/src/api/nodeConversions/nodeConversions.test.d.ts +1 -0
  95. package/types/src/api/nodeConversions/nodeToBlock.d.ts +39 -2
  96. package/types/src/api/parsers/html/parseHTML.test.d.ts +1 -0
  97. package/types/src/api/parsers/markdown/parseMarkdown.test.d.ts +1 -0
  98. package/types/src/api/pmUtil.d.ts +3 -3
  99. package/types/src/api/testUtil/cases/customBlocks.d.ts +670 -0
  100. package/types/src/api/testUtil/cases/customInlineContent.d.ts +558 -0
  101. package/types/src/api/testUtil/cases/customStyles.d.ts +552 -0
  102. package/types/src/api/testUtil/cases/defaultSchema.d.ts +4 -0
  103. package/types/src/api/testUtil/index.d.ts +14 -0
  104. package/types/src/api/testUtil/partialBlockTestUtil.d.ts +9 -0
  105. package/types/src/api/testUtil/paste.d.ts +2 -0
  106. package/types/src/blocks/CodeBlockContent/defaultSupportedLanguages.d.ts +6 -0
  107. package/types/src/blocks/TableBlockContent/TableBlockContent.d.ts +9 -1
  108. package/types/src/editor/BlockNoteEditor.d.ts +55 -9
  109. package/types/src/editor/BlockNoteExtension.d.ts +9 -0
  110. package/types/src/editor/BlockNoteExtensions.d.ts +2 -2
  111. package/types/src/editor/BlockNoteTipTapEditor.d.ts +2 -2
  112. package/types/src/extensions/Collaboration/CursorPlugin.d.ts +3 -3
  113. package/types/src/extensions/Collaboration/ForkYDocPlugin.d.ts +41 -0
  114. package/types/src/extensions/Collaboration/ForkYDocPlugin.test.d.ts +1 -0
  115. package/types/src/extensions/Collaboration/SyncPlugin.d.ts +3 -3
  116. package/types/src/extensions/Collaboration/UndoPlugin.d.ts +3 -3
  117. package/types/src/extensions/Collaboration/createCollaborationExtensions.d.ts +17 -0
  118. package/types/src/extensions/Comments/CommentsPlugin.d.ts +2 -4
  119. package/types/src/extensions/FilePanel/FilePanelPlugin.d.ts +3 -4
  120. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +5 -5
  121. package/types/src/extensions/LinkToolbar/LinkToolbarPlugin.d.ts +3 -4
  122. package/types/src/extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.d.ts +2 -3
  123. package/types/src/extensions/Placeholder/PlaceholderPlugin.d.ts +2 -3
  124. package/types/src/extensions/PreviousBlockType/PreviousBlockTypePlugin.d.ts +2 -3
  125. package/types/src/extensions/ShowSelection/ShowSelectionPlugin.d.ts +2 -3
  126. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +3 -4
  127. package/types/src/extensions/SuggestionMenu/SuggestionPlugin.d.ts +2 -4
  128. package/types/src/extensions/Suggestions/SuggestionMarks.d.ts +4 -0
  129. package/types/src/extensions/TableHandles/TableHandlesPlugin.d.ts +5 -6
  130. package/types/src/i18n/locales/en.d.ts +0 -1
  131. package/types/src/i18n/locales/sk.d.ts +0 -1
  132. package/types/src/index.d.ts +15 -8
  133. package/dist/en-B7ycW7c8.js.map +0 -1
  134. package/dist/en-D4taoCs4.cjs.map +0 -1
  135. package/dist/tsconfig.tsbuildinfo +0 -1
  136. package/src/api/blockManipulation/selections/__snapshots__/selection.test.ts.snap +0 -844
  137. package/src/api/blockManipulation/selections/selection.test.ts +0 -72
  138. package/src/api/blockManipulation/selections/textCursorPosition/__snapshots__/textCursorPosition.test.ts.snap +0 -316
  139. 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
- this.plugin = new Plugin({
18
- key: PLUGIN_KEY,
19
- view: (view) => {
20
- const uniqueEditorSelector = `placeholder-selector-${v4()}`;
21
- view.dom.classList.add(uniqueEditorSelector);
22
- const styleEl = document.createElement("style");
23
-
24
- const nonce = editor._tiptapEditor.options.injectNonce;
25
- if (nonce) {
26
- styleEl.setAttribute("nonce", nonce);
27
- }
28
-
29
- if (editor.prosemirrorView?.root instanceof ShadowRoot) {
30
- editor.prosemirrorView.root.append(styleEl);
31
- } else {
32
- editor.prosemirrorView?.root.head.appendChild(styleEl);
33
- }
34
-
35
- const styleSheet = styleEl.sheet!;
36
-
37
- const getSelector = (additionalSelectors = "") =>
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(blockTypeSelector)} { content: ${JSON.stringify(
54
- placeholder,
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
- const onlyBlockSelector = `[data-is-only-empty-block]`;
60
- const mustBeFocusedSelector = `[data-is-empty-and-focused]`;
61
-
62
- // placeholder for when there's only one empty block
63
- styleSheet.insertRule(
64
- `${getSelector(onlyBlockSelector)} { content: ${JSON.stringify(
65
- emptyPlaceholder,
66
- )}; }`,
67
- );
68
-
69
- // placeholder for default blocks, only when the cursor is in the block (mustBeFocused)
70
- styleSheet.insertRule(
71
- `${getSelector(mustBeFocusedSelector)} { content: ${JSON.stringify(
72
- defaultPlaceholder,
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
- if (!selection.empty) {
102
- return;
103
- }
103
+ if (!selection.empty) {
104
+ return;
105
+ }
104
106
 
105
- // Don't show placeholder when the cursor is inside a code block
106
- if (selection.$from.parent.type.spec.code) {
107
- return;
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
- const decs = [];
112
+ const decs = [];
111
113
 
112
- // decoration for when there's only one empty block
113
- // positions are hardcoded for now
114
- if (state.doc.content.size === 6) {
115
- decs.push(
116
- Decoration.node(2, 4, {
117
- "data-is-only-empty-block": "true",
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
- const $pos = selection.$anchor;
123
- const node = $pos.parent;
124
+ const $pos = selection.$anchor;
125
+ const node = $pos.parent;
124
126
 
125
- if (node.content.size === 0) {
126
- const before = $pos.before();
127
+ if (node.content.size === 0) {
128
+ const before = $pos.before();
127
129
 
128
- decs.push(
129
- Decoration.node(before, before + node.nodeSize, {
130
- "data-is-empty-and-focused": "true",
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
- return DecorationSet.create(doc, decs);
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.plugin = new Plugin({
31
- key: PLUGIN_KEY,
32
- view(_editorView) {
33
- return {
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
- // Block attributes, by block ID, from just before the previous transaction.
56
- prevTransactionOldBlockAttrs: {} as any,
57
- // Block attributes, by block ID, from just before the current transaction.
58
- currentTransactionOldBlockAttrs: {} as any,
59
- // Set of IDs of blocks whose attributes changed from the current transaction.
60
- updatedBlocks: new Set<string>(),
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
- apply(transaction, prev, oldState, newState) {
65
- prev.currentTransactionOldBlockAttrs = {};
66
- prev.updatedBlocks.clear();
66
+ apply(transaction, prev, oldState, newState) {
67
+ prev.currentTransactionOldBlockAttrs = {};
68
+ prev.updatedBlocks.clear();
67
69
 
68
- if (!transaction.docChanged || oldState.doc.eq(newState.doc)) {
69
- return prev;
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
- // Stops list item indices themselves being animated (looks smoother), unless the block's content type is
141
- // changing from a numbered list item to something else.
142
- if (newAttrs.type === "numberedListItem") {
143
- oldAttrs.index = newAttrs.index;
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
- prev.currentTransactionOldBlockAttrs[node.node.attrs.id] =
148
- oldAttrs;
149
-
150
- // TODO: faster deep equal?
151
- if (JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs)) {
152
- (oldAttrs as any)["depth-change"] =
153
- oldAttrs.depth - newAttrs.depth;
154
-
155
- // for debugging:
156
- // console.log(
157
- // "id:",
158
- // node.node.attrs.id,
159
- // "previousBlockTypePlugin changes detected, oldAttrs",
160
- // oldAttrs,
161
- // "new",
162
- // newAttrs
163
- // );
164
-
165
- prev.updatedBlocks.add(node.node.attrs.id);
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
- prev.prevTransactionOldBlockAttrs =
171
- currentTransactionOriginalOldBlockAttrs;
178
+ prev.prevTransactionOldBlockAttrs =
179
+ currentTransactionOriginalOldBlockAttrs;
172
180
 
173
- return prev;
181
+ return prev;
182
+ },
174
183
  },
175
- },
176
- props: {
177
- decorations(state) {
178
- const pluginState = (this as Plugin).getState(state);
179
- if (pluginState.updatedBlocks.size === 0) {
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
- if (!pluginState.updatedBlocks.has(node.attrs.id)) {
191
- return;
192
- }
191
+ const decorations: Decoration[] = [];
193
192
 
194
- const prevAttrs =
195
- pluginState.currentTransactionOldBlockAttrs[node.attrs.id];
196
- const decorationAttrs: any = {};
193
+ state.doc.descendants((node, pos) => {
194
+ if (!node.attrs.id) {
195
+ return;
196
+ }
197
197
 
198
- for (const [nodeAttr, val] of Object.entries(prevAttrs)) {
199
- decorationAttrs["data-prev-" + nodeAttributes[nodeAttr]] =
200
- val || "none";
201
- }
198
+ if (!pluginState.updatedBlocks.has(node.attrs.id)) {
199
+ return;
200
+ }
202
201
 
203
- // for debugging:
204
- // console.log(
205
- // "previousBlockTypePlugin committing decorations",
206
- // decorationAttrs
207
- // );
202
+ const prevAttrs =
203
+ pluginState.currentTransactionOldBlockAttrs[node.attrs.id];
204
+ const decorationAttrs: any = {};
208
205
 
209
- const decoration = Decoration.node(pos, pos + node.nodeSize, {
210
- ...decorationAttrs,
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
- decorations.push(decoration);
214
- });
217
+ const decoration = Decoration.node(pos, pos + node.nodeSize, {
218
+ ...decorationAttrs,
219
+ });
215
220
 
216
- return DecorationSet.create(state.doc, decorations);
221
+ decorations.push(decoration);
222
+ });
223
+
224
+ return DecorationSet.create(state.doc, decorations);
225
+ },
217
226
  },
218
- },
219
- });
227
+ }),
228
+ );
220
229
  }
221
230
  }