diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index 14378248b..b4391b678 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -2,15 +2,22 @@ import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5"; import { useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; -import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor, useTriliumEvent } from "../react/hooks"; +import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; import RightPanelWidget from "./RightPanelWidget"; -interface CKHeading { +interface RawHeading { level: number; text: string; +} + +interface CKHeading extends RawHeading { element: ModelElement; } +interface HeadingsWithNesting extends RawHeading { + children: HeadingsWithNesting[]; +} + export default function TableOfContents() { const { note, noteContext } = useActiveNoteContext(); const noteType = useNoteProperty(note, "type"); @@ -56,16 +63,48 @@ function EditableTextTableOfContents() { } function AbstractTableOfContents({ headings }: { - headings: { - level: number; - text: string; - }[]; + headings: RawHeading[]; }) { - return headings.map(heading => ( -
  • {heading.text}
  • - )); + const nestedHeadings = buildHeadingTree(headings); + return nestedHeadings.map(heading => ); } +function TableOfContentsHeading({ heading }: { heading: HeadingsWithNesting }) { + return ( +
  • + {heading.text} + {heading.children && ( + + )} +
  • + ); +} + +function buildHeadingTree(headings: RawHeading[]): HeadingsWithNesting[] { + const root: HeadingsWithNesting = { level: 0, text: "", children: [] }; + const stack: HeadingsWithNesting[] = [root]; + + for (const h of headings) { + const node: HeadingsWithNesting = { ...h, children: [] }; + + // Pop until we find a parent with lower level + while (stack.length > 1 && stack[stack.length - 1].level >= h.level) { + stack.pop(); + } + + // Attach to current parent + stack[stack.length - 1].children.push(node); + + // This node becomes the new parent + stack.push(node); + } + + return root.children; +} + + function extractTocFromTextEditor(editor: CKTextEditor) { const headings: CKHeading[] = []; @@ -85,3 +124,4 @@ function extractTocFromTextEditor(editor: CKTextEditor) { return headings; } +