@lexical/react 0.14.5 → 0.16.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/LexicalAutoEmbedPlugin.dev.js +15 -11
- package/LexicalAutoEmbedPlugin.dev.mjs +5 -2
- package/LexicalAutoEmbedPlugin.js +2 -0
- package/LexicalAutoEmbedPlugin.mjs +2 -0
- package/LexicalAutoEmbedPlugin.node.mjs +2 -0
- package/LexicalAutoEmbedPlugin.prod.js +6 -5
- package/LexicalAutoEmbedPlugin.prod.mjs +3 -1
- package/LexicalAutoFocusPlugin.dev.js +3 -0
- package/LexicalAutoFocusPlugin.dev.mjs +3 -0
- package/LexicalAutoFocusPlugin.js +2 -0
- package/LexicalAutoFocusPlugin.mjs +2 -0
- package/LexicalAutoFocusPlugin.node.mjs +2 -0
- package/LexicalAutoFocusPlugin.prod.js +2 -0
- package/LexicalAutoFocusPlugin.prod.mjs +2 -0
- package/LexicalAutoLinkPlugin.dev.js +9 -4
- package/LexicalAutoLinkPlugin.dev.mjs +10 -5
- package/LexicalAutoLinkPlugin.js +2 -0
- package/LexicalAutoLinkPlugin.mjs +2 -0
- package/LexicalAutoLinkPlugin.node.mjs +2 -0
- package/LexicalAutoLinkPlugin.prod.js +12 -10
- package/LexicalAutoLinkPlugin.prod.mjs +3 -1
- package/LexicalBlockWithAlignableContents.dev.js +14 -9
- package/LexicalBlockWithAlignableContents.dev.mjs +11 -7
- package/LexicalBlockWithAlignableContents.js +2 -0
- package/LexicalBlockWithAlignableContents.mjs +2 -0
- package/LexicalBlockWithAlignableContents.node.mjs +2 -0
- package/LexicalBlockWithAlignableContents.prod.js +6 -4
- package/LexicalBlockWithAlignableContents.prod.mjs +3 -1
- package/LexicalCharacterLimitPlugin.dev.js +17 -12
- package/LexicalCharacterLimitPlugin.dev.mjs +12 -8
- package/LexicalCharacterLimitPlugin.js +2 -0
- package/LexicalCharacterLimitPlugin.mjs +2 -0
- package/LexicalCharacterLimitPlugin.node.mjs +2 -0
- package/LexicalCharacterLimitPlugin.prod.js +11 -9
- package/LexicalCharacterLimitPlugin.prod.mjs +3 -1
- package/LexicalCheckListPlugin.dev.js +5 -0
- package/LexicalCheckListPlugin.dev.mjs +5 -0
- package/LexicalCheckListPlugin.js +2 -0
- package/LexicalCheckListPlugin.mjs +2 -0
- package/LexicalCheckListPlugin.node.mjs +2 -0
- package/LexicalCheckListPlugin.prod.js +4 -2
- package/LexicalCheckListPlugin.prod.mjs +3 -1
- package/LexicalClearEditorPlugin.dev.js +9 -2
- package/LexicalClearEditorPlugin.dev.mjs +11 -4
- package/LexicalClearEditorPlugin.js +2 -0
- package/LexicalClearEditorPlugin.mjs +2 -0
- package/LexicalClearEditorPlugin.node.mjs +2 -0
- package/LexicalClearEditorPlugin.prod.js +3 -1
- package/LexicalClearEditorPlugin.prod.mjs +3 -1
- package/LexicalClickableLinkPlugin.d.ts +3 -1
- package/LexicalClickableLinkPlugin.dev.js +17 -15
- package/LexicalClickableLinkPlugin.dev.mjs +16 -15
- package/LexicalClickableLinkPlugin.js +2 -0
- package/LexicalClickableLinkPlugin.js.flow +3 -1
- package/LexicalClickableLinkPlugin.mjs +3 -0
- package/LexicalClickableLinkPlugin.node.mjs +3 -0
- package/LexicalClickableLinkPlugin.prod.js +6 -4
- package/LexicalClickableLinkPlugin.prod.mjs +3 -1
- package/LexicalCollaborationContext.dev.js +3 -0
- package/LexicalCollaborationContext.dev.mjs +3 -0
- package/LexicalCollaborationContext.js +2 -0
- package/LexicalCollaborationContext.mjs +2 -0
- package/LexicalCollaborationContext.node.mjs +2 -0
- package/LexicalCollaborationContext.prod.js +2 -0
- package/LexicalCollaborationContext.prod.mjs +2 -0
- package/LexicalCollaborationPlugin.dev.js +20 -2
- package/LexicalCollaborationPlugin.dev.mjs +6 -1
- package/LexicalCollaborationPlugin.js +2 -0
- package/LexicalCollaborationPlugin.mjs +2 -0
- package/LexicalCollaborationPlugin.node.mjs +2 -0
- package/LexicalCollaborationPlugin.prod.js +12 -10
- package/LexicalCollaborationPlugin.prod.mjs +3 -1
- package/LexicalComposer.dev.js +17 -8
- package/LexicalComposer.dev.mjs +16 -8
- package/LexicalComposer.js +2 -0
- package/LexicalComposer.mjs +2 -0
- package/LexicalComposer.node.mjs +2 -0
- package/LexicalComposer.prod.js +5 -4
- package/LexicalComposer.prod.mjs +3 -1
- package/LexicalComposerContext.dev.js +3 -0
- package/LexicalComposerContext.dev.mjs +3 -0
- package/LexicalComposerContext.js +2 -0
- package/LexicalComposerContext.mjs +2 -0
- package/LexicalComposerContext.node.mjs +2 -0
- package/LexicalComposerContext.prod.js +4 -2
- package/LexicalComposerContext.prod.mjs +3 -1
- package/LexicalContentEditable.dev.js +24 -23
- package/LexicalContentEditable.dev.mjs +22 -22
- package/LexicalContentEditable.js +2 -0
- package/LexicalContentEditable.mjs +2 -0
- package/LexicalContentEditable.node.mjs +2 -0
- package/LexicalContentEditable.prod.js +5 -3
- package/LexicalContentEditable.prod.mjs +3 -1
- package/LexicalContextMenuPlugin.dev.js +29 -7
- package/LexicalContextMenuPlugin.dev.mjs +17 -8
- package/LexicalContextMenuPlugin.js +2 -0
- package/LexicalContextMenuPlugin.js.flow +12 -0
- package/LexicalContextMenuPlugin.mjs +2 -0
- package/LexicalContextMenuPlugin.node.mjs +2 -0
- package/LexicalContextMenuPlugin.prod.js +18 -16
- package/LexicalContextMenuPlugin.prod.mjs +3 -1
- package/LexicalDecoratorBlockNode.d.ts +1 -0
- package/LexicalDecoratorBlockNode.dev.js +6 -0
- package/LexicalDecoratorBlockNode.dev.mjs +6 -0
- package/LexicalDecoratorBlockNode.js +2 -0
- package/LexicalDecoratorBlockNode.mjs +2 -0
- package/LexicalDecoratorBlockNode.node.mjs +2 -0
- package/LexicalDecoratorBlockNode.prod.js +3 -1
- package/LexicalDecoratorBlockNode.prod.mjs +3 -1
- package/LexicalEditorRefPlugin.dev.js +17 -1
- package/LexicalEditorRefPlugin.dev.mjs +3 -0
- package/LexicalEditorRefPlugin.js +2 -0
- package/LexicalEditorRefPlugin.js.flow +19 -0
- package/LexicalEditorRefPlugin.mjs +2 -0
- package/LexicalEditorRefPlugin.node.mjs +2 -0
- package/LexicalEditorRefPlugin.prod.js +3 -1
- package/LexicalEditorRefPlugin.prod.mjs +2 -0
- package/LexicalErrorBoundary.d.ts +3 -1
- package/LexicalErrorBoundary.dev.js +30 -10
- package/LexicalErrorBoundary.dev.mjs +13 -7
- package/LexicalErrorBoundary.js +2 -0
- package/LexicalErrorBoundary.js.flow +4 -1
- package/LexicalErrorBoundary.mjs +3 -0
- package/LexicalErrorBoundary.node.mjs +3 -0
- package/LexicalErrorBoundary.prod.js +6 -4
- package/LexicalErrorBoundary.prod.mjs +3 -1
- package/LexicalHashtagPlugin.dev.js +5 -2
- package/LexicalHashtagPlugin.dev.mjs +5 -2
- package/LexicalHashtagPlugin.js +2 -0
- package/LexicalHashtagPlugin.mjs +2 -0
- package/LexicalHashtagPlugin.node.mjs +2 -0
- package/LexicalHashtagPlugin.prod.js +2 -0
- package/LexicalHashtagPlugin.prod.mjs +2 -0
- package/LexicalHistoryPlugin.dev.js +4 -0
- package/LexicalHistoryPlugin.dev.mjs +4 -0
- package/LexicalHistoryPlugin.js +2 -0
- package/LexicalHistoryPlugin.mjs +2 -0
- package/LexicalHistoryPlugin.node.mjs +2 -0
- package/LexicalHistoryPlugin.prod.js +2 -0
- package/LexicalHistoryPlugin.prod.mjs +2 -0
- package/LexicalHorizontalRuleNode.d.ts +2 -2
- package/LexicalHorizontalRuleNode.dev.js +23 -12
- package/LexicalHorizontalRuleNode.dev.mjs +21 -11
- package/LexicalHorizontalRuleNode.js +2 -0
- package/LexicalHorizontalRuleNode.mjs +2 -0
- package/LexicalHorizontalRuleNode.node.mjs +2 -0
- package/LexicalHorizontalRuleNode.prod.js +7 -5
- package/LexicalHorizontalRuleNode.prod.mjs +3 -1
- package/LexicalHorizontalRulePlugin.dev.js +3 -0
- package/LexicalHorizontalRulePlugin.dev.mjs +3 -0
- package/LexicalHorizontalRulePlugin.js +2 -0
- package/LexicalHorizontalRulePlugin.mjs +2 -0
- package/LexicalHorizontalRulePlugin.node.mjs +2 -0
- package/LexicalHorizontalRulePlugin.prod.js +2 -0
- package/LexicalHorizontalRulePlugin.prod.mjs +2 -0
- package/LexicalLinkPlugin.dev.js +6 -3
- package/LexicalLinkPlugin.dev.mjs +7 -4
- package/LexicalLinkPlugin.js +2 -0
- package/LexicalLinkPlugin.mjs +2 -0
- package/LexicalLinkPlugin.node.mjs +2 -0
- package/LexicalLinkPlugin.prod.js +3 -1
- package/LexicalLinkPlugin.prod.mjs +3 -1
- package/LexicalListPlugin.dev.js +4 -0
- package/LexicalListPlugin.dev.mjs +4 -0
- package/LexicalListPlugin.js +2 -0
- package/LexicalListPlugin.mjs +2 -0
- package/LexicalListPlugin.node.mjs +2 -0
- package/LexicalListPlugin.prod.js +2 -0
- package/LexicalListPlugin.prod.mjs +2 -0
- package/LexicalMarkdownShortcutPlugin.dev.js +3 -0
- package/LexicalMarkdownShortcutPlugin.dev.mjs +3 -0
- package/LexicalMarkdownShortcutPlugin.js +2 -0
- package/LexicalMarkdownShortcutPlugin.mjs +2 -0
- package/LexicalMarkdownShortcutPlugin.node.mjs +2 -0
- package/LexicalMarkdownShortcutPlugin.prod.js +2 -0
- package/LexicalMarkdownShortcutPlugin.prod.mjs +2 -0
- package/LexicalNestedComposer.dev.js +14 -9
- package/LexicalNestedComposer.dev.mjs +8 -4
- package/LexicalNestedComposer.js +2 -0
- package/LexicalNestedComposer.mjs +2 -0
- package/LexicalNestedComposer.node.mjs +2 -0
- package/LexicalNestedComposer.prod.js +7 -5
- package/LexicalNestedComposer.prod.mjs +3 -1
- package/LexicalNodeEventPlugin.dev.js +3 -0
- package/LexicalNodeEventPlugin.dev.mjs +3 -0
- package/LexicalNodeEventPlugin.js +2 -0
- package/LexicalNodeEventPlugin.js.flow +12 -0
- package/LexicalNodeEventPlugin.mjs +2 -0
- package/LexicalNodeEventPlugin.node.mjs +2 -0
- package/LexicalNodeEventPlugin.prod.js +2 -0
- package/LexicalNodeEventPlugin.prod.mjs +2 -0
- package/LexicalNodeMenuPlugin.dev.js +30 -8
- package/LexicalNodeMenuPlugin.dev.mjs +17 -8
- package/LexicalNodeMenuPlugin.js +2 -0
- package/LexicalNodeMenuPlugin.mjs +2 -0
- package/LexicalNodeMenuPlugin.node.mjs +2 -0
- package/LexicalNodeMenuPlugin.prod.js +17 -15
- package/LexicalNodeMenuPlugin.prod.mjs +3 -1
- package/LexicalOnChangePlugin.dev.js +9 -2
- package/LexicalOnChangePlugin.dev.mjs +11 -4
- package/LexicalOnChangePlugin.js +2 -0
- package/LexicalOnChangePlugin.mjs +2 -0
- package/LexicalOnChangePlugin.node.mjs +2 -0
- package/LexicalOnChangePlugin.prod.js +3 -1
- package/LexicalOnChangePlugin.prod.mjs +3 -1
- package/LexicalPlainTextPlugin.dev.js +34 -19
- package/LexicalPlainTextPlugin.dev.mjs +31 -17
- package/LexicalPlainTextPlugin.js +2 -0
- package/LexicalPlainTextPlugin.js.flow +1 -1
- package/LexicalPlainTextPlugin.mjs +2 -0
- package/LexicalPlainTextPlugin.node.mjs +2 -0
- package/LexicalPlainTextPlugin.prod.js +6 -4
- package/LexicalPlainTextPlugin.prod.mjs +3 -1
- package/LexicalRichTextPlugin.dev.js +34 -19
- package/LexicalRichTextPlugin.dev.mjs +31 -17
- package/LexicalRichTextPlugin.js +2 -0
- package/LexicalRichTextPlugin.js.flow +1 -1
- package/LexicalRichTextPlugin.mjs +2 -0
- package/LexicalRichTextPlugin.node.mjs +2 -0
- package/LexicalRichTextPlugin.prod.js +6 -4
- package/LexicalRichTextPlugin.prod.mjs +3 -1
- package/LexicalTabIndentationPlugin.dev.js +5 -2
- package/LexicalTabIndentationPlugin.dev.mjs +5 -2
- package/LexicalTabIndentationPlugin.js +2 -0
- package/LexicalTabIndentationPlugin.mjs +2 -0
- package/LexicalTabIndentationPlugin.node.mjs +2 -0
- package/LexicalTabIndentationPlugin.prod.js +2 -0
- package/LexicalTabIndentationPlugin.prod.mjs +2 -0
- package/LexicalTableOfContents.d.ts +6 -13
- package/LexicalTableOfContents.dev.js +57 -10
- package/LexicalTableOfContents.dev.mjs +58 -11
- package/LexicalTableOfContents.js +2 -0
- package/LexicalTableOfContents.js.flow +3 -7
- package/LexicalTableOfContents.mjs +2 -0
- package/LexicalTableOfContents.node.mjs +2 -0
- package/LexicalTableOfContents.prod.js +6 -4
- package/LexicalTableOfContents.prod.mjs +3 -1
- package/LexicalTableOfContentsPlugin.d.ts +20 -0
- package/LexicalTableOfContentsPlugin.dev.js +198 -0
- package/LexicalTableOfContentsPlugin.dev.mjs +196 -0
- package/LexicalTableOfContentsPlugin.js +11 -0
- package/LexicalTableOfContentsPlugin.js.flow +18 -0
- package/LexicalTableOfContentsPlugin.mjs +12 -0
- package/LexicalTableOfContentsPlugin.node.mjs +10 -0
- package/LexicalTableOfContentsPlugin.prod.js +12 -0
- package/LexicalTableOfContentsPlugin.prod.mjs +9 -0
- package/LexicalTablePlugin.dev.js +28 -2
- package/LexicalTablePlugin.dev.mjs +31 -5
- package/LexicalTablePlugin.js +2 -0
- package/LexicalTablePlugin.mjs +2 -0
- package/LexicalTablePlugin.node.mjs +2 -0
- package/LexicalTablePlugin.prod.js +9 -5
- package/LexicalTablePlugin.prod.mjs +3 -1
- package/LexicalTreeView.d.ts +3 -1
- package/LexicalTreeView.dev.js +22 -4
- package/LexicalTreeView.dev.mjs +8 -3
- package/LexicalTreeView.js +2 -0
- package/LexicalTreeView.js.flow +2 -0
- package/LexicalTreeView.mjs +2 -0
- package/LexicalTreeView.node.mjs +2 -0
- package/LexicalTreeView.prod.js +5 -3
- package/LexicalTreeView.prod.mjs +3 -1
- package/LexicalTypeaheadMenuPlugin.dev.js +30 -8
- package/LexicalTypeaheadMenuPlugin.dev.mjs +17 -8
- package/LexicalTypeaheadMenuPlugin.js +2 -0
- package/LexicalTypeaheadMenuPlugin.mjs +2 -0
- package/LexicalTypeaheadMenuPlugin.node.mjs +2 -0
- package/LexicalTypeaheadMenuPlugin.prod.js +21 -19
- package/LexicalTypeaheadMenuPlugin.prod.mjs +3 -1
- package/package.json +50 -20
- package/shared/useCharacterLimit.d.ts +1 -1
- package/useLexicalEditable.d.ts +11 -1
- package/useLexicalEditable.dev.js +22 -3
- package/useLexicalEditable.dev.mjs +23 -5
- package/useLexicalEditable.js +2 -0
- package/useLexicalEditable.js.flow +4 -1
- package/useLexicalEditable.mjs +4 -1
- package/useLexicalEditable.node.mjs +4 -1
- package/useLexicalEditable.prod.js +4 -2
- package/useLexicalEditable.prod.mjs +3 -1
- package/useLexicalIsTextContentEmpty.dev.js +9 -2
- package/useLexicalIsTextContentEmpty.dev.mjs +11 -4
- package/useLexicalIsTextContentEmpty.js +2 -0
- package/useLexicalIsTextContentEmpty.mjs +2 -0
- package/useLexicalIsTextContentEmpty.node.mjs +2 -0
- package/useLexicalIsTextContentEmpty.prod.js +3 -1
- package/useLexicalIsTextContentEmpty.prod.mjs +3 -1
- package/useLexicalNodeSelection.dev.js +3 -0
- package/useLexicalNodeSelection.dev.mjs +4 -1
- package/useLexicalNodeSelection.js +2 -0
- package/useLexicalNodeSelection.mjs +2 -0
- package/useLexicalNodeSelection.node.mjs +2 -0
- package/useLexicalNodeSelection.prod.js +2 -0
- package/useLexicalNodeSelection.prod.mjs +3 -1
- package/useLexicalSubscription.d.ts +4 -1
- package/useLexicalSubscription.dev.js +12 -3
- package/useLexicalSubscription.dev.mjs +13 -5
- package/useLexicalSubscription.js +2 -0
- package/useLexicalSubscription.js.flow +4 -1
- package/useLexicalSubscription.mjs +4 -1
- package/useLexicalSubscription.node.mjs +4 -1
- package/useLexicalSubscription.prod.js +4 -2
- package/useLexicalSubscription.prod.mjs +3 -1
- package/useLexicalTextEntity.dev.js +3 -0
- package/useLexicalTextEntity.dev.mjs +3 -0
- package/useLexicalTextEntity.js +2 -0
- package/useLexicalTextEntity.mjs +2 -0
- package/useLexicalTextEntity.node.mjs +2 -0
- package/useLexicalTextEntity.prod.js +2 -0
- package/useLexicalTextEntity.prod.mjs +2 -0
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
6
7
|
*/
|
|
8
|
+
|
|
7
9
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
8
10
|
import { $isHeadingNode, HeadingNode } from '@lexical/rich-text';
|
|
9
|
-
import { $
|
|
11
|
+
import { $getNextRightPreorderNode } from '@lexical/utils';
|
|
12
|
+
import { $getRoot, $isElementNode, $getNodeByKey, TextNode } from 'lexical';
|
|
10
13
|
import { useState, useEffect } from 'react';
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -16,6 +19,7 @@ import { useState, useEffect } from 'react';
|
|
|
16
19
|
* LICENSE file in the root directory of this source tree.
|
|
17
20
|
*
|
|
18
21
|
*/
|
|
22
|
+
|
|
19
23
|
function toEntry(heading) {
|
|
20
24
|
return [heading.getKey(), heading.getTextContent(), heading.getTag()];
|
|
21
25
|
}
|
|
@@ -26,12 +30,20 @@ function $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTable
|
|
|
26
30
|
const newEntry = toEntry(newHeading);
|
|
27
31
|
let newTableOfContents = [];
|
|
28
32
|
if (prevHeading === null) {
|
|
33
|
+
// check if key already exists
|
|
34
|
+
if (currentTableOfContents.length > 0 && currentTableOfContents[0][0] === newHeading.__key) {
|
|
35
|
+
return currentTableOfContents;
|
|
36
|
+
}
|
|
29
37
|
newTableOfContents = [newEntry, ...currentTableOfContents];
|
|
30
38
|
} else {
|
|
31
39
|
for (let i = 0; i < currentTableOfContents.length; i++) {
|
|
32
40
|
const key = currentTableOfContents[i][0];
|
|
33
41
|
newTableOfContents.push(currentTableOfContents[i]);
|
|
34
42
|
if (key === prevHeading.getKey() && key !== newHeading.getKey()) {
|
|
43
|
+
// check if key already exists
|
|
44
|
+
if (i + 1 < currentTableOfContents.length && currentTableOfContents[i + 1][0] === newHeading.__key) {
|
|
45
|
+
return currentTableOfContents;
|
|
46
|
+
}
|
|
35
47
|
newTableOfContents.push(newEntry);
|
|
36
48
|
}
|
|
37
49
|
}
|
|
@@ -80,7 +92,14 @@ function $updateHeadingPosition(prevHeading, heading, currentTableOfContents) {
|
|
|
80
92
|
}
|
|
81
93
|
return newTableOfContents;
|
|
82
94
|
}
|
|
83
|
-
function
|
|
95
|
+
function $getPreviousHeading(node) {
|
|
96
|
+
let prevHeading = $getNextRightPreorderNode(node);
|
|
97
|
+
while (prevHeading !== null && !$isHeadingNode(prevHeading)) {
|
|
98
|
+
prevHeading = $getNextRightPreorderNode(prevHeading);
|
|
99
|
+
}
|
|
100
|
+
return prevHeading;
|
|
101
|
+
}
|
|
102
|
+
function TableOfContentsPlugin({
|
|
84
103
|
children
|
|
85
104
|
}) {
|
|
86
105
|
const [tableOfContents, setTableOfContents] = useState([]);
|
|
@@ -98,6 +117,31 @@ function LexicalTableOfContentsPlugin({
|
|
|
98
117
|
}
|
|
99
118
|
setTableOfContents(currentTableOfContents);
|
|
100
119
|
});
|
|
120
|
+
const removeRootUpdateListener = editor.registerUpdateListener(({
|
|
121
|
+
editorState,
|
|
122
|
+
dirtyElements
|
|
123
|
+
}) => {
|
|
124
|
+
editorState.read(() => {
|
|
125
|
+
const updateChildHeadings = node => {
|
|
126
|
+
for (const child of node.getChildren()) {
|
|
127
|
+
if ($isHeadingNode(child)) {
|
|
128
|
+
const prevHeading = $getPreviousHeading(child);
|
|
129
|
+
currentTableOfContents = $updateHeadingPosition(prevHeading, child, currentTableOfContents);
|
|
130
|
+
setTableOfContents(currentTableOfContents);
|
|
131
|
+
} else if ($isElementNode(child)) {
|
|
132
|
+
updateChildHeadings(child);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// If a node is changes, all child heading positions need to be updated
|
|
138
|
+
$getRoot().getChildren().forEach(node => {
|
|
139
|
+
if ($isElementNode(node) && dirtyElements.get(node.__key)) {
|
|
140
|
+
updateChildHeadings(node);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
101
145
|
|
|
102
146
|
// Listen to updates to heading mutations and update state
|
|
103
147
|
const removeHeaderMutationListener = editor.registerMutationListener(HeadingNode, mutatedNodes => {
|
|
@@ -106,10 +150,7 @@ function LexicalTableOfContentsPlugin({
|
|
|
106
150
|
if (mutation === 'created') {
|
|
107
151
|
const newHeading = $getNodeByKey(nodeKey);
|
|
108
152
|
if (newHeading !== null) {
|
|
109
|
-
|
|
110
|
-
while (prevHeading !== null && !$isHeadingNode(prevHeading)) {
|
|
111
|
-
prevHeading = prevHeading.getPreviousSibling();
|
|
112
|
-
}
|
|
153
|
+
const prevHeading = $getPreviousHeading(newHeading);
|
|
113
154
|
currentTableOfContents = $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents);
|
|
114
155
|
}
|
|
115
156
|
} else if (mutation === 'destroyed') {
|
|
@@ -117,10 +158,7 @@ function LexicalTableOfContentsPlugin({
|
|
|
117
158
|
} else if (mutation === 'updated') {
|
|
118
159
|
const newHeading = $getNodeByKey(nodeKey);
|
|
119
160
|
if (newHeading !== null) {
|
|
120
|
-
|
|
121
|
-
while (prevHeading !== null && !$isHeadingNode(prevHeading)) {
|
|
122
|
-
prevHeading = prevHeading.getPreviousSibling();
|
|
123
|
-
}
|
|
161
|
+
const prevHeading = $getPreviousHeading(newHeading);
|
|
124
162
|
currentTableOfContents = $updateHeadingPosition(prevHeading, newHeading, currentTableOfContents);
|
|
125
163
|
}
|
|
126
164
|
}
|
|
@@ -149,9 +187,18 @@ function LexicalTableOfContentsPlugin({
|
|
|
149
187
|
return () => {
|
|
150
188
|
removeHeaderMutationListener();
|
|
151
189
|
removeTextNodeMutationListener();
|
|
190
|
+
removeRootUpdateListener();
|
|
152
191
|
};
|
|
153
192
|
}, [editor]);
|
|
154
193
|
return children(tableOfContents, editor);
|
|
155
194
|
}
|
|
156
195
|
|
|
157
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
198
|
+
*
|
|
199
|
+
* This source code is licensed under the MIT license found in the
|
|
200
|
+
* LICENSE file in the root directory of this source tree.
|
|
201
|
+
*
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
export { TableOfContentsPlugin as default };
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
6
7
|
*/
|
|
8
|
+
|
|
7
9
|
'use strict'
|
|
8
10
|
const LexicalTableOfContents = process.env.NODE_ENV === 'development' ? require('./LexicalTableOfContents.dev.js') : require('./LexicalTableOfContents.prod.js');
|
|
9
11
|
module.exports = LexicalTableOfContents;
|
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
* @flow strict
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
11
|
-
import type {NodeKey} from 'lexical';
|
|
10
|
+
import {TableOfContentsPlugin} from '@lexical/react/LexicalTableOfContentsPlugin';
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
tableOfContents: Array<[NodeKey, string, HeadingTagType]>,
|
|
16
|
-
) => React$Node,
|
|
17
|
-
}): React$Node;
|
|
12
|
+
/** @deprecated use the named export {@link LexicalTableOfContentsPlugin} */
|
|
13
|
+
declare export default typeof TableOfContentsPlugin;
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
6
7
|
*/
|
|
8
|
+
|
|
7
9
|
import * as modDev from './LexicalTableOfContents.dev.mjs';
|
|
8
10
|
import * as modProd from './LexicalTableOfContents.prod.mjs';
|
|
9
11
|
const mod = process.env.NODE_ENV === 'development' ? modDev : modProd;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
6
7
|
*/
|
|
8
|
+
|
|
7
9
|
const mod = await (process.env.NODE_ENV === 'development' ? import('./LexicalTableOfContents.dev.mjs') : import('./LexicalTableOfContents.prod.mjs'));
|
|
8
10
|
export default mod.default;
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
b.
|
|
8
|
+
|
|
9
|
+
'use strict';var h=require("@lexical/react/LexicalComposerContext"),q=require("@lexical/rich-text"),v=require("@lexical/utils"),w=require("lexical"),x=require("react");function y(d){return[d.getKey(),d.getTextContent(),d.getTag()]}function z(d,t,n){let e=[],c=y(t);d||e.push(c);for(let r of n)r[0]!==t.getKey()&&(e.push(r),d&&r[0]===d.getKey()&&e.push(c));return e}function B(d){for(d=v.$getNextRightPreorderNode(d);null!==d&&!q.$isHeadingNode(d);)d=v.$getNextRightPreorderNode(d);return d}
|
|
10
|
+
exports.default=function({children:d}){let [t,n]=x.useState([]),[e]=h.useLexicalComposerContext();x.useEffect(()=>{let c=[];e.getEditorState().read(()=>{let l=w.$getRoot().getChildren();for(let a of l)q.$isHeadingNode(a)&&c.push([a.getKey(),a.getTextContent(),a.getTag()]);n(c)});let r=e.registerUpdateListener(({editorState:l,dirtyElements:a})=>{l.read(()=>{const f=b=>{for(const g of b.getChildren())q.$isHeadingNode(g)?(b=B(g),c=z(b,g,c),n(c)):w.$isElementNode(g)&&f(g)};w.$getRoot().getChildren().forEach(b=>
|
|
11
|
+
{w.$isElementNode(b)&&a.get(b.__key)&&f(b)})})}),C=e.registerMutationListener(q.HeadingNode,l=>{e.getEditorState().read(()=>{for(const [g,m]of l)if("created"===m){var a=w.$getNodeByKey(g);if(null!==a)a:{var f=B(a),b=c;if(null===a){c=b;break a}let k=y(a),u=[];if(null===f){if(0<b.length&&b[0][0]===a.__key){c=b;break a}u=[k,...b]}else for(let p=0;p<b.length;p++){let A=b[p][0];u.push(b[p]);if(A===f.getKey()&&A!==a.getKey()){if(p+1<b.length&&b[p+1][0]===a.__key){c=b;break a}u.push(k)}}c=u}}else if("destroyed"===
|
|
12
|
+
m){f=g;a=c;b=[];for(let k of a)k[0]!==f&&b.push(k);c=b}else"updated"===m&&(f=w.$getNodeByKey(g),null!==f&&(a=B(f),c=z(a,f,c)));n(c)})}),D=e.registerMutationListener(w.TextNode,l=>{e.getEditorState().read(()=>{for(const [b,g]of l)if("updated"===g){var a=w.$getNodeByKey(b);if(null!==a&&(a=a.getParentOrThrow(),q.$isHeadingNode(a))){var f=c;let m=[];for(let k of f)k[0]===a.getKey()?m.push(y(a)):m.push(k);c=m;n(c)}}})});return()=>{C();D();r()}},[e]);return d(t,e)}
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
import{useLexicalComposerContext as t}from"@lexical/react/LexicalComposerContext";import{$isHeadingNode as e,HeadingNode as r}from"@lexical/rich-text";import{$getNextRightPreorderNode as n}from"@lexical/utils";import{$getRoot as o,$isElementNode as i,$getNodeByKey as s,TextNode as f}from"lexical";import{useState as c,useEffect as l}from"react";function u(t){return[t.getKey(),t.getTextContent(),t.getTag()]}function a(t,e,r){if(null===e)return r;const n=u(e);let o=[];if(null===t){if(r.length>0&&r[0][0]===e.__key)return r;o=[n,...r]}else for(let i=0;i<r.length;i++){const s=r[i][0];if(o.push(r[i]),s===t.getKey()&&s!==e.getKey()){if(i+1<r.length&&r[i+1][0]===e.__key)return r;o.push(n)}}return o}function g(t,e){const r=[];for(const n of e)n[0]!==t&&r.push(n);return r}function d(t,e){const r=[];for(const n of e)n[0]===t.getKey()?r.push(u(t)):r.push(n);return r}function h(t,e,r){const n=[],o=u(e);t||n.push(o);for(const i of r)i[0]!==e.getKey()&&(n.push(i),t&&i[0]===t.getKey()&&n.push(o));return n}function p(t){let r=n(t);for(;null!==r&&!e(r);)r=n(r);return r}function m({children:n}){const[u,m]=c([]),[y]=t();return l((()=>{let t=[];y.getEditorState().read((()=>{const r=o().getChildren();for(const n of r)e(n)&&t.push([n.getKey(),n.getTextContent(),n.getTag()]);m(t)}));const n=y.registerUpdateListener((({editorState:r,dirtyElements:n})=>{r.read((()=>{const r=n=>{for(const o of n.getChildren())if(e(o)){const e=p(o);t=h(e,o,t),m(t)}else i(o)&&r(o)};o().getChildren().forEach((t=>{i(t)&&n.get(t.__key)&&r(t)}))}))})),c=y.registerMutationListener(r,(e=>{y.getEditorState().read((()=>{for(const[r,n]of e)if("created"===n){const e=s(r);if(null!==e){const r=p(e);t=a(r,e,t)}}else if("destroyed"===n)t=g(r,t);else if("updated"===n){const e=s(r);if(null!==e){const r=p(e);t=h(r,e,t)}}m(t)}))})),l=y.registerMutationListener(f,(r=>{y.getEditorState().read((()=>{for(const[n,o]of r)if("updated"===o){const r=s(n);if(null!==r){const n=r.getParentOrThrow();e(n)&&(t=d(n,t),m(t))}}}))}));return()=>{c(),l(),n()}}),[y]),n(u,y)}export{m as default};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
/// <reference types="react" />
|
|
9
|
+
import { HeadingTagType } from '@lexical/rich-text';
|
|
10
|
+
import { LexicalEditor, NodeKey } from 'lexical';
|
|
11
|
+
export type TableOfContentsEntry = [
|
|
12
|
+
key: NodeKey,
|
|
13
|
+
text: string,
|
|
14
|
+
tag: HeadingTagType
|
|
15
|
+
];
|
|
16
|
+
type Props = {
|
|
17
|
+
children: (values: Array<TableOfContentsEntry>, editor: LexicalEditor) => JSX.Element;
|
|
18
|
+
};
|
|
19
|
+
export declare function TableOfContentsPlugin({ children }: Props): JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
|
|
12
|
+
var richText = require('@lexical/rich-text');
|
|
13
|
+
var utils = require('@lexical/utils');
|
|
14
|
+
var lexical = require('lexical');
|
|
15
|
+
var react = require('react');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
19
|
+
*
|
|
20
|
+
* This source code is licensed under the MIT license found in the
|
|
21
|
+
* LICENSE file in the root directory of this source tree.
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
function toEntry(heading) {
|
|
26
|
+
return [heading.getKey(), heading.getTextContent(), heading.getTag()];
|
|
27
|
+
}
|
|
28
|
+
function $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents) {
|
|
29
|
+
if (newHeading === null) {
|
|
30
|
+
return currentTableOfContents;
|
|
31
|
+
}
|
|
32
|
+
const newEntry = toEntry(newHeading);
|
|
33
|
+
let newTableOfContents = [];
|
|
34
|
+
if (prevHeading === null) {
|
|
35
|
+
// check if key already exists
|
|
36
|
+
if (currentTableOfContents.length > 0 && currentTableOfContents[0][0] === newHeading.__key) {
|
|
37
|
+
return currentTableOfContents;
|
|
38
|
+
}
|
|
39
|
+
newTableOfContents = [newEntry, ...currentTableOfContents];
|
|
40
|
+
} else {
|
|
41
|
+
for (let i = 0; i < currentTableOfContents.length; i++) {
|
|
42
|
+
const key = currentTableOfContents[i][0];
|
|
43
|
+
newTableOfContents.push(currentTableOfContents[i]);
|
|
44
|
+
if (key === prevHeading.getKey() && key !== newHeading.getKey()) {
|
|
45
|
+
// check if key already exists
|
|
46
|
+
if (i + 1 < currentTableOfContents.length && currentTableOfContents[i + 1][0] === newHeading.__key) {
|
|
47
|
+
return currentTableOfContents;
|
|
48
|
+
}
|
|
49
|
+
newTableOfContents.push(newEntry);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return newTableOfContents;
|
|
54
|
+
}
|
|
55
|
+
function $deleteHeadingFromTableOfContents(key, currentTableOfContents) {
|
|
56
|
+
const newTableOfContents = [];
|
|
57
|
+
for (const heading of currentTableOfContents) {
|
|
58
|
+
if (heading[0] !== key) {
|
|
59
|
+
newTableOfContents.push(heading);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return newTableOfContents;
|
|
63
|
+
}
|
|
64
|
+
function $updateHeadingInTableOfContents(heading, currentTableOfContents) {
|
|
65
|
+
const newTableOfContents = [];
|
|
66
|
+
for (const oldHeading of currentTableOfContents) {
|
|
67
|
+
if (oldHeading[0] === heading.getKey()) {
|
|
68
|
+
newTableOfContents.push(toEntry(heading));
|
|
69
|
+
} else {
|
|
70
|
+
newTableOfContents.push(oldHeading);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return newTableOfContents;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns the updated table of contents, placing the given `heading` before the given `prevHeading`. If `prevHeading`
|
|
78
|
+
* is undefined, `heading` is placed at the start of table of contents
|
|
79
|
+
*/
|
|
80
|
+
function $updateHeadingPosition(prevHeading, heading, currentTableOfContents) {
|
|
81
|
+
const newTableOfContents = [];
|
|
82
|
+
const newEntry = toEntry(heading);
|
|
83
|
+
if (!prevHeading) {
|
|
84
|
+
newTableOfContents.push(newEntry);
|
|
85
|
+
}
|
|
86
|
+
for (const oldHeading of currentTableOfContents) {
|
|
87
|
+
if (oldHeading[0] === heading.getKey()) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
newTableOfContents.push(oldHeading);
|
|
91
|
+
if (prevHeading && oldHeading[0] === prevHeading.getKey()) {
|
|
92
|
+
newTableOfContents.push(newEntry);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return newTableOfContents;
|
|
96
|
+
}
|
|
97
|
+
function $getPreviousHeading(node) {
|
|
98
|
+
let prevHeading = utils.$getNextRightPreorderNode(node);
|
|
99
|
+
while (prevHeading !== null && !richText.$isHeadingNode(prevHeading)) {
|
|
100
|
+
prevHeading = utils.$getNextRightPreorderNode(prevHeading);
|
|
101
|
+
}
|
|
102
|
+
return prevHeading;
|
|
103
|
+
}
|
|
104
|
+
function TableOfContentsPlugin({
|
|
105
|
+
children
|
|
106
|
+
}) {
|
|
107
|
+
const [tableOfContents, setTableOfContents] = react.useState([]);
|
|
108
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
109
|
+
react.useEffect(() => {
|
|
110
|
+
// Set table of contents initial state
|
|
111
|
+
let currentTableOfContents = [];
|
|
112
|
+
editor.getEditorState().read(() => {
|
|
113
|
+
const root = lexical.$getRoot();
|
|
114
|
+
const rootChildren = root.getChildren();
|
|
115
|
+
for (const child of rootChildren) {
|
|
116
|
+
if (richText.$isHeadingNode(child)) {
|
|
117
|
+
currentTableOfContents.push([child.getKey(), child.getTextContent(), child.getTag()]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
setTableOfContents(currentTableOfContents);
|
|
121
|
+
});
|
|
122
|
+
const removeRootUpdateListener = editor.registerUpdateListener(({
|
|
123
|
+
editorState,
|
|
124
|
+
dirtyElements
|
|
125
|
+
}) => {
|
|
126
|
+
editorState.read(() => {
|
|
127
|
+
const updateChildHeadings = node => {
|
|
128
|
+
for (const child of node.getChildren()) {
|
|
129
|
+
if (richText.$isHeadingNode(child)) {
|
|
130
|
+
const prevHeading = $getPreviousHeading(child);
|
|
131
|
+
currentTableOfContents = $updateHeadingPosition(prevHeading, child, currentTableOfContents);
|
|
132
|
+
setTableOfContents(currentTableOfContents);
|
|
133
|
+
} else if (lexical.$isElementNode(child)) {
|
|
134
|
+
updateChildHeadings(child);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// If a node is changes, all child heading positions need to be updated
|
|
140
|
+
lexical.$getRoot().getChildren().forEach(node => {
|
|
141
|
+
if (lexical.$isElementNode(node) && dirtyElements.get(node.__key)) {
|
|
142
|
+
updateChildHeadings(node);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Listen to updates to heading mutations and update state
|
|
149
|
+
const removeHeaderMutationListener = editor.registerMutationListener(richText.HeadingNode, mutatedNodes => {
|
|
150
|
+
editor.getEditorState().read(() => {
|
|
151
|
+
for (const [nodeKey, mutation] of mutatedNodes) {
|
|
152
|
+
if (mutation === 'created') {
|
|
153
|
+
const newHeading = lexical.$getNodeByKey(nodeKey);
|
|
154
|
+
if (newHeading !== null) {
|
|
155
|
+
const prevHeading = $getPreviousHeading(newHeading);
|
|
156
|
+
currentTableOfContents = $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents);
|
|
157
|
+
}
|
|
158
|
+
} else if (mutation === 'destroyed') {
|
|
159
|
+
currentTableOfContents = $deleteHeadingFromTableOfContents(nodeKey, currentTableOfContents);
|
|
160
|
+
} else if (mutation === 'updated') {
|
|
161
|
+
const newHeading = lexical.$getNodeByKey(nodeKey);
|
|
162
|
+
if (newHeading !== null) {
|
|
163
|
+
const prevHeading = $getPreviousHeading(newHeading);
|
|
164
|
+
currentTableOfContents = $updateHeadingPosition(prevHeading, newHeading, currentTableOfContents);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
setTableOfContents(currentTableOfContents);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Listen to text node mutation updates
|
|
173
|
+
const removeTextNodeMutationListener = editor.registerMutationListener(lexical.TextNode, mutatedNodes => {
|
|
174
|
+
editor.getEditorState().read(() => {
|
|
175
|
+
for (const [nodeKey, mutation] of mutatedNodes) {
|
|
176
|
+
if (mutation === 'updated') {
|
|
177
|
+
const currNode = lexical.$getNodeByKey(nodeKey);
|
|
178
|
+
if (currNode !== null) {
|
|
179
|
+
const parentNode = currNode.getParentOrThrow();
|
|
180
|
+
if (richText.$isHeadingNode(parentNode)) {
|
|
181
|
+
currentTableOfContents = $updateHeadingInTableOfContents(parentNode, currentTableOfContents);
|
|
182
|
+
setTableOfContents(currentTableOfContents);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
return () => {
|
|
190
|
+
removeHeaderMutationListener();
|
|
191
|
+
removeTextNodeMutationListener();
|
|
192
|
+
removeRootUpdateListener();
|
|
193
|
+
};
|
|
194
|
+
}, [editor]);
|
|
195
|
+
return children(tableOfContents, editor);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
exports.TableOfContentsPlugin = TableOfContentsPlugin;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
10
|
+
import { $isHeadingNode, HeadingNode } from '@lexical/rich-text';
|
|
11
|
+
import { $getNextRightPreorderNode } from '@lexical/utils';
|
|
12
|
+
import { $getRoot, $isElementNode, $getNodeByKey, TextNode } from 'lexical';
|
|
13
|
+
import { useState, useEffect } from 'react';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
17
|
+
*
|
|
18
|
+
* This source code is licensed under the MIT license found in the
|
|
19
|
+
* LICENSE file in the root directory of this source tree.
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
function toEntry(heading) {
|
|
24
|
+
return [heading.getKey(), heading.getTextContent(), heading.getTag()];
|
|
25
|
+
}
|
|
26
|
+
function $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents) {
|
|
27
|
+
if (newHeading === null) {
|
|
28
|
+
return currentTableOfContents;
|
|
29
|
+
}
|
|
30
|
+
const newEntry = toEntry(newHeading);
|
|
31
|
+
let newTableOfContents = [];
|
|
32
|
+
if (prevHeading === null) {
|
|
33
|
+
// check if key already exists
|
|
34
|
+
if (currentTableOfContents.length > 0 && currentTableOfContents[0][0] === newHeading.__key) {
|
|
35
|
+
return currentTableOfContents;
|
|
36
|
+
}
|
|
37
|
+
newTableOfContents = [newEntry, ...currentTableOfContents];
|
|
38
|
+
} else {
|
|
39
|
+
for (let i = 0; i < currentTableOfContents.length; i++) {
|
|
40
|
+
const key = currentTableOfContents[i][0];
|
|
41
|
+
newTableOfContents.push(currentTableOfContents[i]);
|
|
42
|
+
if (key === prevHeading.getKey() && key !== newHeading.getKey()) {
|
|
43
|
+
// check if key already exists
|
|
44
|
+
if (i + 1 < currentTableOfContents.length && currentTableOfContents[i + 1][0] === newHeading.__key) {
|
|
45
|
+
return currentTableOfContents;
|
|
46
|
+
}
|
|
47
|
+
newTableOfContents.push(newEntry);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return newTableOfContents;
|
|
52
|
+
}
|
|
53
|
+
function $deleteHeadingFromTableOfContents(key, currentTableOfContents) {
|
|
54
|
+
const newTableOfContents = [];
|
|
55
|
+
for (const heading of currentTableOfContents) {
|
|
56
|
+
if (heading[0] !== key) {
|
|
57
|
+
newTableOfContents.push(heading);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return newTableOfContents;
|
|
61
|
+
}
|
|
62
|
+
function $updateHeadingInTableOfContents(heading, currentTableOfContents) {
|
|
63
|
+
const newTableOfContents = [];
|
|
64
|
+
for (const oldHeading of currentTableOfContents) {
|
|
65
|
+
if (oldHeading[0] === heading.getKey()) {
|
|
66
|
+
newTableOfContents.push(toEntry(heading));
|
|
67
|
+
} else {
|
|
68
|
+
newTableOfContents.push(oldHeading);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return newTableOfContents;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns the updated table of contents, placing the given `heading` before the given `prevHeading`. If `prevHeading`
|
|
76
|
+
* is undefined, `heading` is placed at the start of table of contents
|
|
77
|
+
*/
|
|
78
|
+
function $updateHeadingPosition(prevHeading, heading, currentTableOfContents) {
|
|
79
|
+
const newTableOfContents = [];
|
|
80
|
+
const newEntry = toEntry(heading);
|
|
81
|
+
if (!prevHeading) {
|
|
82
|
+
newTableOfContents.push(newEntry);
|
|
83
|
+
}
|
|
84
|
+
for (const oldHeading of currentTableOfContents) {
|
|
85
|
+
if (oldHeading[0] === heading.getKey()) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
newTableOfContents.push(oldHeading);
|
|
89
|
+
if (prevHeading && oldHeading[0] === prevHeading.getKey()) {
|
|
90
|
+
newTableOfContents.push(newEntry);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return newTableOfContents;
|
|
94
|
+
}
|
|
95
|
+
function $getPreviousHeading(node) {
|
|
96
|
+
let prevHeading = $getNextRightPreorderNode(node);
|
|
97
|
+
while (prevHeading !== null && !$isHeadingNode(prevHeading)) {
|
|
98
|
+
prevHeading = $getNextRightPreorderNode(prevHeading);
|
|
99
|
+
}
|
|
100
|
+
return prevHeading;
|
|
101
|
+
}
|
|
102
|
+
function TableOfContentsPlugin({
|
|
103
|
+
children
|
|
104
|
+
}) {
|
|
105
|
+
const [tableOfContents, setTableOfContents] = useState([]);
|
|
106
|
+
const [editor] = useLexicalComposerContext();
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
// Set table of contents initial state
|
|
109
|
+
let currentTableOfContents = [];
|
|
110
|
+
editor.getEditorState().read(() => {
|
|
111
|
+
const root = $getRoot();
|
|
112
|
+
const rootChildren = root.getChildren();
|
|
113
|
+
for (const child of rootChildren) {
|
|
114
|
+
if ($isHeadingNode(child)) {
|
|
115
|
+
currentTableOfContents.push([child.getKey(), child.getTextContent(), child.getTag()]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
setTableOfContents(currentTableOfContents);
|
|
119
|
+
});
|
|
120
|
+
const removeRootUpdateListener = editor.registerUpdateListener(({
|
|
121
|
+
editorState,
|
|
122
|
+
dirtyElements
|
|
123
|
+
}) => {
|
|
124
|
+
editorState.read(() => {
|
|
125
|
+
const updateChildHeadings = node => {
|
|
126
|
+
for (const child of node.getChildren()) {
|
|
127
|
+
if ($isHeadingNode(child)) {
|
|
128
|
+
const prevHeading = $getPreviousHeading(child);
|
|
129
|
+
currentTableOfContents = $updateHeadingPosition(prevHeading, child, currentTableOfContents);
|
|
130
|
+
setTableOfContents(currentTableOfContents);
|
|
131
|
+
} else if ($isElementNode(child)) {
|
|
132
|
+
updateChildHeadings(child);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// If a node is changes, all child heading positions need to be updated
|
|
138
|
+
$getRoot().getChildren().forEach(node => {
|
|
139
|
+
if ($isElementNode(node) && dirtyElements.get(node.__key)) {
|
|
140
|
+
updateChildHeadings(node);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Listen to updates to heading mutations and update state
|
|
147
|
+
const removeHeaderMutationListener = editor.registerMutationListener(HeadingNode, mutatedNodes => {
|
|
148
|
+
editor.getEditorState().read(() => {
|
|
149
|
+
for (const [nodeKey, mutation] of mutatedNodes) {
|
|
150
|
+
if (mutation === 'created') {
|
|
151
|
+
const newHeading = $getNodeByKey(nodeKey);
|
|
152
|
+
if (newHeading !== null) {
|
|
153
|
+
const prevHeading = $getPreviousHeading(newHeading);
|
|
154
|
+
currentTableOfContents = $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents);
|
|
155
|
+
}
|
|
156
|
+
} else if (mutation === 'destroyed') {
|
|
157
|
+
currentTableOfContents = $deleteHeadingFromTableOfContents(nodeKey, currentTableOfContents);
|
|
158
|
+
} else if (mutation === 'updated') {
|
|
159
|
+
const newHeading = $getNodeByKey(nodeKey);
|
|
160
|
+
if (newHeading !== null) {
|
|
161
|
+
const prevHeading = $getPreviousHeading(newHeading);
|
|
162
|
+
currentTableOfContents = $updateHeadingPosition(prevHeading, newHeading, currentTableOfContents);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
setTableOfContents(currentTableOfContents);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Listen to text node mutation updates
|
|
171
|
+
const removeTextNodeMutationListener = editor.registerMutationListener(TextNode, mutatedNodes => {
|
|
172
|
+
editor.getEditorState().read(() => {
|
|
173
|
+
for (const [nodeKey, mutation] of mutatedNodes) {
|
|
174
|
+
if (mutation === 'updated') {
|
|
175
|
+
const currNode = $getNodeByKey(nodeKey);
|
|
176
|
+
if (currNode !== null) {
|
|
177
|
+
const parentNode = currNode.getParentOrThrow();
|
|
178
|
+
if ($isHeadingNode(parentNode)) {
|
|
179
|
+
currentTableOfContents = $updateHeadingInTableOfContents(parentNode, currentTableOfContents);
|
|
180
|
+
setTableOfContents(currentTableOfContents);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
return () => {
|
|
188
|
+
removeHeaderMutationListener();
|
|
189
|
+
removeTextNodeMutationListener();
|
|
190
|
+
removeRootUpdateListener();
|
|
191
|
+
};
|
|
192
|
+
}, [editor]);
|
|
193
|
+
return children(tableOfContents, editor);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { TableOfContentsPlugin };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict'
|
|
10
|
+
const LexicalTableOfContentsPlugin = process.env.NODE_ENV === 'development' ? require('./LexicalTableOfContentsPlugin.dev.js') : require('./LexicalTableOfContentsPlugin.prod.js');
|
|
11
|
+
module.exports = LexicalTableOfContentsPlugin;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @flow strict
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {HeadingTagType} from '@lexical/rich-text';
|
|
11
|
+
import type {LexicalEditor, NodeKey} from 'lexical';
|
|
12
|
+
|
|
13
|
+
declare export function TableOfContentsPlugin({
|
|
14
|
+
children: (
|
|
15
|
+
tableOfContents: Array<[NodeKey, string, HeadingTagType]>,
|
|
16
|
+
editor: LexicalEditor,
|
|
17
|
+
) => React$Node,
|
|
18
|
+
}): React$Node;
|