Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🗺 Add option for maxdepth on an outline #283

Merged
merged 1 commit into from
Nov 23, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/neat-spiders-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@myst-theme/site': patch
'@myst-theme/book': patch
---

Add `outline_maxdepth` to document outline
52 changes: 31 additions & 21 deletions packages/site/src/components/DocumentOutline.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
>
<a
className={classNames('block p-1', {
'text-slate-900 dark:text-slate-50': heading.level < 3 && heading.id !== activeId,
'text-slate-500 dark:text-slate-300': heading.level >= 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<Set<HTMLHeadingElement>>(new Set());
const [activeId, setActiveId] = useState<string>();
@@ -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,15 +232,17 @@ export const DocumentOutline = ({
className,
selector = SELECTOR,
children,
maxdepth = 4,
}: {
outlineRef?: React.RefObject<HTMLElement>;
top?: number;
height?: number;
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 <nav suppressHydrationWarning>{children}</nav>;
}
13 changes: 11 additions & 2 deletions themes/book/app/routes/$.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ArticlePageAndNavigation hide_toc={hide_toc} projectSlug={article.project}>
<main ref={container} className="article-grid subgrid-gap col-screen">
{!hide_outline && (
<div className="sticky z-10 hidden h-0 col-margin-right-inset lg:block" style={{ top }}>
<DocumentOutline top={16} className="relative" outlineRef={outline} />
<DocumentOutline
top={16}
className="relative"
outlineRef={outline}
maxdepth={outline_maxdepth}
/>
</div>
)}
<ArticlePage article={article} hide_all_footer_links={hide_footer_links} />
6 changes: 6 additions & 0 deletions themes/book/template.yml
Original file line number Diff line number Diff line change
@@ -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. `<H1>` and `<H2>`).
min: 1
max: 6
integer: true
- type: string
id: twitter
description: Twitter handle related to the site