diff --git a/apps/web/src/components/editor/table-of-contents.tsx b/apps/web/src/components/editor/table-of-contents.tsx
index b21dbecbd..8ab1424d8 100644
--- a/apps/web/src/components/editor/table-of-contents.tsx
+++ b/apps/web/src/components/editor/table-of-contents.tsx
@@ -36,17 +36,22 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import React, { useEffect, useState } from "react";
-import { Cross } from "../icons";
+import React, { useEffect, useLayoutEffect, useState } from "react";
+import { ChevronDown, ChevronRight, Circle, Cross } from "../icons";
import { useEditorStore } from "../../stores/editor-store";
import ScrollContainer from "../scroll-container";
import { ScopedThemeProvider } from "../theme-provider";
import { Section } from "../properties";
-import { scrollIntoViewById } from "@notesnook/editor";
+import { scrollIntoViewById, TOCItem } from "@notesnook/editor";
import { Button, Flex, Text } from "@theme-ui/components";
import { useEditorManager } from "./manager";
import { TITLE_BAR_HEIGHT } from "../title-bar";
import { strings } from "@notesnook/intl";
+import {
+ TreeNode,
+ VirtualizedTree,
+ VirtualizedTreeHandle
+} from "../virtualized-tree";
type TableOfContentsProps = {
sessionId: string;
@@ -54,34 +59,35 @@ type TableOfContentsProps = {
function TableOfContents(props: TableOfContentsProps) {
const { sessionId } = props;
- const [active, setActive] = useState([]);
- const toggleTableOfContents = useEditorStore(
- (store) => store.toggleTableOfContents
- );
+ const treeRef = React.useRef>(null);
const tableOfContents = useEditorManager(
(store) => store.editors[sessionId]?.tableOfContents || []
);
- useEffect(() => {
+ useLayoutEffect(() => {
+ treeRef.current?.refresh();
const editorScroll = document.getElementById(`editorScroll_${sessionId}`);
- if (!editorScroll) return;
function onScroll() {
const scrollTop = editorScroll?.scrollTop || 0;
const height = editorScroll?.clientHeight || 0;
- const active = tableOfContents.filter((t, i, array) => {
- const next = array.at(i + 1);
+ for (let i = 0; i < tableOfContents.length; i++) {
+ const toc = tableOfContents[i];
+ const element = document.getElementById(`toc-${toc.id}`);
+ if (!element) continue;
+
+ const next = tableOfContents.at(i + 1);
const viewport = scrollTop + height - 160;
const lessThanNext = next ? scrollTop <= next.top : true;
- const isInViewport = t.top <= viewport && t.top >= scrollTop;
- const isActive = scrollTop >= t.top && lessThanNext;
- return isInViewport || isActive;
- });
- setActive(active.map((a) => a.id));
+ const isInViewport = toc.top <= viewport && toc.top >= scrollTop;
+ const isActive = scrollTop >= toc.top && lessThanNext;
+
+ element.classList.toggle("active", isActive || isInViewport);
+ }
}
- editorScroll.addEventListener("scroll", onScroll);
+ editorScroll?.addEventListener("scroll", onScroll);
onScroll();
return () => {
- editorScroll.removeEventListener("scroll", onScroll);
+ editorScroll?.removeEventListener("scroll", onScroll);
};
}, [sessionId, tableOfContents]);
@@ -107,58 +113,100 @@ function TableOfContents(props: TableOfContentsProps) {
flexDirection: "column"
}}
>
-
- toggleTableOfContents(false)}
- size={18}
- sx={{ mr: 1, cursor: "pointer" }}
- />
- }
+
+
-
- {tableOfContents.length <= 0 ? (
-
- {strings.noHeadingsFound()}.
-
- ) : (
- tableOfContents.map((t) => (
+ {tableOfContents.length <= 0 ? (
+
+ {strings.noHeadingsFound()}.
+
+ ) : (
+ {
+ const nodes: TreeNode[] = [];
+ for (let i = 0; i < tableOfContents.length; i++) {
+ const item = tableOfContents[i];
+ if (item.level !== depth + 1) continue;
+ nodes.push({
+ id: item.id,
+ data: item,
+ depth: depth + 1,
+ parentId: id,
+ hasChildren:
+ i + 1 < tableOfContents.length &&
+ tableOfContents[i + 1].level > item.level,
+ expanded: true
+ });
+ }
+ return nodes;
+ }}
+ renderItem={({ item, collapse, expand, expanded }) => (
- ))
- )}
-
-
-
+ )}
+ >
+ )}
+
+
);