diff --git a/.changeset/neat-spiders-chew.md b/.changeset/neat-spiders-chew.md new file mode 100644 index 000000000..56d363ffe --- /dev/null +++ b/.changeset/neat-spiders-chew.md @@ -0,0 +1,6 @@ +--- +'@myst-theme/site': patch +'@myst-theme/book': patch +--- + +Add `outline_maxdepth` to document outline diff --git a/packages/site/src/components/DocumentOutline.tsx b/packages/site/src/components/DocumentOutline.tsx index 92ec6f274..e82cae73f 100644 --- a/packages/site/src/components/DocumentOutline.tsx +++ b/packages/site/src/components/DocumentOutline.tsx @@ -16,6 +16,7 @@ const HIGHLIGHT_CLASS = 'highlight'; const onClient = typeof document !== 'undefined'; type Heading = { + element: HTMLHeadingElement; id: string; title: string; titleHTML: string; @@ -46,15 +47,15 @@ const Headings = ({ headings, activeId, highlight, selector }: Props) => ( > = 3 && heading.id !== activeId, + 'text-slate-900 dark:text-slate-50': heading.level < 2 && heading.id !== activeId, + 'text-slate-500 dark:text-slate-300': heading.level >= 2 && heading.id !== activeId, 'text-blue-600 dark:text-white font-bold': heading.id === activeId, 'pr-2': heading.id !== activeId, // Allows for bold to change length - 'pl-2': heading.level === 1 || heading.level === 2, - 'pl-4': heading.level === 3, - 'pl-8 text-xs': heading.level === 4, - 'pl-10 text-xs font-light': heading.level === 5, - 'pl-12 text-xs font-extralight': heading.level === 6, + 'pl-2': heading.level === 1, + 'pl-4': heading.level === 2, + 'pl-8 text-xs': heading.level === 3, + 'pl-10 text-xs font-light': heading.level === 4, + 'pl-12 text-xs font-extralight': heading.level === 5, })} href={`#${heading.id}`} onClick={(e) => { @@ -104,7 +105,7 @@ function getHeaders(selector: string): HTMLHeadingElement[] { return headers as HTMLHeadingElement[]; } -function useHeaders(selector: string) { +function useHeaders(selector: string, maxdepth: number) { if (!onClient) return { activeId: '', headings: [] }; const onScreen = useRef>(new Set()); const [activeId, setActiveId] = useState(); @@ -145,28 +146,35 @@ function useHeaders(selector: string) { Array.from(elements).map((e) => observer.current?.observe(e)); }, [observer]); - elements.forEach((e) => { - if (headingsSet.current.has(e)) return; - observer.current?.observe(e); - headingsSet.current.add(e); - }); - + let minLevel = 10; const headings: Heading[] = elements - .map((heading) => { + .map((element) => { return { - level: Number(heading.tagName.slice(1)), - id: heading.id, - text: heading.querySelector('.heading-text'), + element, + level: Number(element.tagName.slice(1)), + id: element.id, + text: element.querySelector('.heading-text'), }; }) .filter((h) => !!h.text) - .map(({ level, text, id }) => { + .map(({ element, level, text, id }) => { const { innerText: title, innerHTML: titleHTML } = cloneHeadingElement( text as HTMLSpanElement, ); - return { title, titleHTML, id, level }; + minLevel = Math.min(minLevel, level); + return { element, title, titleHTML, id, level }; + }) + .filter((heading) => { + heading.level = heading.level - minLevel + 1; + return heading.level < maxdepth + 1; }); + headings.forEach(({ element: e }) => { + if (headingsSet.current.has(e)) return; + observer.current?.observe(e); + headingsSet.current.add(e); + }); + return { activeId, highlight, headings }; } @@ -224,6 +232,7 @@ export const DocumentOutline = ({ className, selector = SELECTOR, children, + maxdepth = 4, }: { outlineRef?: React.RefObject; top?: number; @@ -231,8 +240,9 @@ export const DocumentOutline = ({ className?: string; selector?: string; children?: React.ReactNode; + maxdepth?: number; }) => { - const { activeId, headings, highlight } = useHeaders(selector); + const { activeId, headings, highlight } = useHeaders(selector, maxdepth); if (headings.length <= 1 || !onClient) { return ; } diff --git a/themes/book/app/routes/$.tsx b/themes/book/app/routes/$.tsx index b69650dc5..12a259337 100644 --- a/themes/book/app/routes/$.tsx +++ b/themes/book/app/routes/$.tsx @@ -90,6 +90,7 @@ interface BookThemeTemplateOptions { hide_toc?: boolean; hide_outline?: boolean; hide_footer_links?: boolean; + outline_maxdepth?: number; } export default function Page() { @@ -99,13 +100,21 @@ export default function Page() { const pageDesign: BookThemeTemplateOptions = (article.frontmatter as any)?.options ?? {}; const siteDesign: BookThemeTemplateOptions = (useSiteManifest() as SiteManifest & BookThemeTemplateOptions)?.options ?? {}; - const { hide_toc, hide_outline, hide_footer_links } = { ...siteDesign, ...pageDesign }; + const { hide_toc, hide_outline, hide_footer_links, outline_maxdepth } = { + ...siteDesign, + ...pageDesign, + }; return (
{!hide_outline && (
- +
)} diff --git a/themes/book/template.yml b/themes/book/template.yml index bd8da449a..9981a3986 100644 --- a/themes/book/template.yml +++ b/themes/book/template.yml @@ -22,6 +22,12 @@ options: - type: boolean id: hide_outline description: Hide the document outline on all pages + - type: number + id: outline_maxdepth + description: The maximum depth to show on the document outline, for example, `2` would show only two depths of headings (e.g. `

` and `

`). + min: 1 + max: 6 + integer: true - type: string id: twitter description: Twitter handle related to the site