Skip to content
Draft
Show file tree
Hide file tree
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
4 changes: 2 additions & 2 deletions src/components/docPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import {CopyMarkdownButton} from '../copyMarkdownButton';
import {DocFeedback} from '../docFeedback';
import {GitHubCTA} from '../githubCTA';
import {Header} from '../header';
import {InlineTableOfContents} from '../inlineTableOfContents';
import Mermaid from '../mermaid';
import {PaginationNav} from '../paginationNav';
import {PlatformSdkDetail} from '../platformSdkDetail';
import {Sidebar} from '../sidebar';
import {SidebarTableOfContents} from '../sidebarTableOfContents';
import {ReaderDepthTracker} from '../track-reader-depth';

type Props = {
Expand Down Expand Up @@ -96,6 +96,7 @@ export function DocPage({
<h1>{frontMatter.title}</h1>
<h2>{frontMatter.description}</h2>
</hgroup>
<InlineTableOfContents />
{/* This exact id is important for Algolia indexing */}
<div id="main">
<CodeContextProvider>{children}</CodeContextProvider>
Expand All @@ -122,7 +123,6 @@ export function DocPage({
className="sticky h-[calc(100vh-var(--header-height))] top-[var(--header-height)] overflow-y-auto hidden toc:block flex-none w-[250px] min-w-[250px]"
>
<div className="sidebar">
<SidebarTableOfContents />
<PlatformSdkDetail />
</div>
</aside>
Expand Down
105 changes: 105 additions & 0 deletions src/components/inlineTableOfContents/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use client';

import {useEffect, useState} from 'react';

import {isNotNil} from 'sentry-docs/utils';

import styles from './style.module.scss';

interface TocItem {
title: string;
url: string;
}

function getMainElement() {
if (typeof document === 'undefined') {
return null;
}
return document.getElementById('main');
}

function getTocItems(main: HTMLElement): TocItem[] {
return Array.from(main.querySelectorAll('h2'))
.map(el => {
const title = el.textContent?.trim();
if (!el.id || !title) {
return null;
}
// This is a relatively new API, that checks if the element is visible in the document
// With this, we filter out e.g. sections hidden via CSS
if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) {
return null;
}
return {
url: `#${el.id}`,
title,
};
})
.filter(isNotNil);
}

// Inline table of contents that appears at the top of the page content
// Shows only H2 headings, and only appears if there are 3 or more sections
export function InlineTableOfContents() {
const [tocItems, setTocItems] = useState<TocItem[]>([]);

// gather the toc items on mount
useEffect(() => {
const main = getMainElement();
if (!main) {
return;
}

const items = getTocItems(main);
setTocItems(items);
}, []);

// ensure toc items are kept up-to-date if the DOM changes
useEffect(() => {
const main = getMainElement();
if (!main) {
return () => {};
}

const observer = new MutationObserver(() => {
const newTocItems = getTocItems(main);

// Avoid flashing if nothing changes
if (
newTocItems.length === tocItems.length &&
newTocItems.every((item, index) => item.url === tocItems[index].url)
) {
return;
}
setTocItems(newTocItems);
});

// Start observing the target node for any changes in its subtree
observer.observe(main, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'id', 'style'],
});

return () => observer.disconnect();
}, [tocItems]);

// Only show if there are 3 or more sections
if (tocItems.length < 3) {
return null;
}

return (
<div className={styles['inline-toc']}>
<div className={styles['inline-toc-title']}>On this page</div>
<ul className={styles['inline-toc-list']}>
{tocItems.map(item => (
<li key={item.url}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</div>
);
}
62 changes: 62 additions & 0 deletions src/components/inlineTableOfContents/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.inline-toc {
--title-color: var(--gray-11);
--link-color: var(--gray-11);
--link-hover-color: var(--accent-purple);

border-left: 2px solid var(--gray-6);
padding-left: 1rem;
padding-right: 1rem;
margin: 1.5rem 0;
max-width: 100%;
width: 100%;
}

:global(.dark) {
.inline-toc {
--title-color: var(--gray-11);
--link-color: var(--gray-11);
}
}

.inline-toc-title {
font-weight: 600;
font-size: 0.925rem;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--title-color);
margin-bottom: 0.5rem;
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
}

.inline-toc-list {
list-style-type: none;
padding-left: 0;
margin: 0;
max-width: 100%;
width: 100%;

li {
margin: 0;
max-width: 100%;
}

a {
color: var(--link-color);
text-decoration: none;
font-size: 0.875rem;
line-height: 1.8;
display: block;
transition: color 0.2s ease;
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;

&:hover {
color: var(--link-hover-color);
}
}
}

Loading