Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 13 additions & 3 deletions components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
CollapsibleTrigger,
} from './ui/collapsible';
import { Button } from './ui/button';
import TableOfContents from './TableOfContents';

const DocLink = ({
uri,
Expand Down Expand Up @@ -276,9 +277,9 @@
<DocsNav open={open} setOpen={setOpen} />
</div>
</div>
<div className='dark:bg-slate-800 max-w-[1400px] grid grid-cols-1 lg:grid-cols-4 mx-4 md:mx-12'>
<div className='dark:bg-slate-800 max-w-[1400px] grid grid-cols-1 lg:grid-cols-12 mx-4 md:mx-12 gap-4'>
{!shouldHideSidebar && (
<div className='hidden lg:block mt-24 sticky top-24 h-[calc(100vh-6rem)] overflow-hidden'>
<div className='hidden lg:block lg:col-span-3 mt-24 sticky top-24 h-[calc(100vh-6rem)] overflow-hidden'>
<div className='h-full overflow-y-auto scrollbar-hidden'>
<DocsNav open={open} setOpen={setOpen} />
<CarbonAds
Expand All @@ -289,10 +290,19 @@
</div>
)}
<div
className={`lg:mt-20 mx-4 md:mx-0 ${shouldHideSidebar ? 'col-span-4 w-full' : 'col-span-4 md:col-span-3 lg:w-5/6'}`}
className={`lg:mt-20 mx-4 md:mx-0 ${
shouldHideSidebar

Check failure on line 294 in components/Sidebar.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Delete `·`
? 'col-span-12 w-full'

Check failure on line 295 in components/Sidebar.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Delete `·`
: 'col-span-12 lg:col-span-6 xl:col-span-6'
}`}
>
{children}
</div>
{!shouldHideSidebar && (
<div className='hidden xl:block xl:col-span-3 mt-20'>
<TableOfContents />
</div>
)}
</div>
</section>
</div>
Expand Down
187 changes: 187 additions & 0 deletions components/TableOfContents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
'use client';

import React, { useEffect, useState, useCallback } from 'react';
import { useRouter } from 'next/router';
import { cn } from '~/lib/utils';

interface TocItem {
id: string;
text: string;
level: number;
}

interface TableOfContentsProps {
className?: string;
}

export const TableOfContents: React.FC<TableOfContentsProps> = ({
className,
}) => {
const router = useRouter();
const [tocItems, setTocItems] = useState<TocItem[]>([]);
const [activeId, setActiveId] = useState<string>('');

// Extract headings from the page
useEffect(() => {
const headings = document.querySelectorAll('h2, h3');
const items: TocItem[] = [];

// Skip the first heading and add "Introduction" as the first item
if (headings.length > 0) {
items.push({
id: 'introduction',
text: 'Introduction',
level: 2, // Same level as h2
});
}

// Start from index 1 to skip the first heading
for (let i = 1; i < headings.length; i++) {
const heading = headings[i];
const text = heading.textContent || '';
const id = heading.id || text.toLowerCase().replace(/\s+/g, '-');

if (!heading.id && id) {
heading.id = id;
}

items.push({
id,
text,
level: parseInt(heading.tagName.substring(1), 10), // Get heading level (2 for h2, 3 for h3, etc.)
});
}

setTocItems(items);
}, [router.asPath]);

// Intersection Observer to track which section is visible
useEffect(() => {
if (tocItems.length === 0) return;

const observer = new IntersectionObserver(
(entries) => {
let newActiveId = '';
let isAtTop = window.scrollY < 100; // 100px from top

Check failure on line 65 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

'isAtTop' is never reassigned. Use 'const' instead

if (isAtTop) {
// If at the top, highlight Introduction
newActiveId = 'introduction';
} else {
// Otherwise, find the first visible heading
entries.forEach((entry) => {
if (entry.isIntersecting && !newActiveId) {
newActiveId = entry.target.id;
}
});
}

if (newActiveId) {
setActiveId(newActiveId);
}
},
{
rootMargin: '-20% 0px -60% 0px',
threshold: 0.1,
}

Check failure on line 86 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Insert `,`
);

Check failure on line 88 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Delete `····`
// Observe all headings
tocItems.forEach(({ id }) => {
const element = document.getElementById(id);
if (element) {
observer.observe(element);
}
});

Check failure on line 96 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Delete `····⏎····`

return () => {
tocItems.forEach(({ id }) => {
const element = document.getElementById(id);
if (element) {
observer.unobserve(element);
}
});
};
}, [tocItems]);

Check failure on line 107 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Delete `··`
useEffect(() => {
const handleScroll = () => {
if (window.scrollY < 100) {
setActiveId('introduction');
}
};

window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);

const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement>, id: string) => {

Check failure on line 119 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Insert `⏎····`
e.preventDefault();

Check failure on line 120 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Insert `··`
const element = id === 'introduction'

Check failure on line 121 in components/TableOfContents.tsx

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Replace `const·element·=` with `··const·element·=⏎·······`
? document.documentElement // Scroll to top for introduction
: document.getElementById(id);

if (element) {
const yOffset = -80; // Adjust this value to match your header height
const y = id === 'introduction'
? 0
: element.getBoundingClientRect().top + window.pageYOffset + yOffset;

window.scrollTo({ top: y, behavior: 'smooth' });
}
}, []);

if (tocItems.length === 0) {
return null;
}

return (
<nav
className={cn(
'hidden xl:block sticky top-24 h-[calc(100vh-6rem)] overflow-y-auto',
'pr-4',
className
)}
aria-label="Table of contents"
style={{
scrollbarWidth: 'thin',
scrollbarColor: 'rgb(203 213 225) transparent',
}}
>
<div className="space-y-2 pb-8">
<h4 className="font-semibold text-slate-900 dark:text-slate-100 mb-4 text-sm uppercase tracking-wide">
On This Page
</h4>
<ul className="space-y-2 text-sm border-l-2 border-slate-200 dark:border-slate-700">
{tocItems.map((item) => (
<li
key={item.id}
className={cn('transition-all duration-200', {
'pl-4': item.level === 2,
'pl-8': item.level === 3,
})}
>
<a
key={item.id}
href={`#${item.id}`}

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.
onClick={(e) => handleClick(e, item.id)}
className={cn(
'block py-2 text-sm transition-colors duration-200',
activeId === item.id || (item.id === 'introduction' && !activeId)
? 'text-primary font-medium'
: 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-300',
item.level === 3 ? 'pl-2' : ''
)}
>
{item.text}
</a>
</li>
))}
</ul>
</div>
</nav>
);
};

export default TableOfContents;
4 changes: 3 additions & 1 deletion pages/learn/[slug].page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import StyledMarkdown from '~/components/StyledMarkdown';
import { getLayout } from '~/components/Sidebar';
import getStaticMarkdownPaths from '~/lib/getStaticMarkdownPaths';
Expand All @@ -23,6 +24,7 @@ export default function StaticMarkdownPage({
frontmatter: any;
content: any;
}) {
const router = useRouter();
const fileRenderType = '_md';
const newTitle = 'JSON Schema - ' + frontmatter.title;
return (
Expand All @@ -31,7 +33,7 @@ export default function StaticMarkdownPage({
<title>{newTitle}</title>
</Head>
<Headline1>{frontmatter.title}</Headline1>
<StyledMarkdown markdown={content} />
<StyledMarkdown key={router.asPath} markdown={content} />
<NextPrevButton
prevLabel={frontmatter?.prev?.label}
prevURL={frontmatter?.prev?.url}
Expand Down
4 changes: 3 additions & 1 deletion pages/overview/[slug].page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { getLayout } from '~/components/Sidebar';
import StyledMarkdown from '~/components/StyledMarkdown';
import getStaticMarkdownPaths from '~/lib/getStaticMarkdownPaths';
Expand All @@ -23,6 +24,7 @@ export default function StaticMarkdownPage({
frontmatter: any;
content: any;
}) {
const router = useRouter();
const fileRenderType = '_md';
const newTitle = 'JSON Schema - ' + frontmatter.title;

Expand All @@ -32,7 +34,7 @@ export default function StaticMarkdownPage({
<title>{newTitle}</title>
</Head>
<Headline1>{frontmatter.title}</Headline1>
<StyledMarkdown markdown={content} />
<StyledMarkdown key={router.asPath} markdown={content} />
<NextPrevButton
prevLabel={frontmatter.prev?.label}
prevURL={frontmatter.prev?.url}
Expand Down
4 changes: 3 additions & 1 deletion pages/understanding-json-schema/[slug].page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import StyledMarkdown from '~/components/StyledMarkdown';
import { getLayout } from '~/components/Sidebar';
import getStaticMarkdownPaths from '~/lib/getStaticMarkdownPaths';
Expand All @@ -23,6 +24,7 @@ export default function StaticMarkdownPage({
frontmatter: any;
content: any;
}) {
const router = useRouter();
const fileRenderType = '_md';
const newTitle = 'JSON Schema - ' + frontmatter.title;
return (
Expand All @@ -31,7 +33,7 @@ export default function StaticMarkdownPage({
<title>{newTitle}</title>
</Head>
<Headline1>{frontmatter.title || 'NO TITLE!'}</Headline1>
<StyledMarkdown markdown={content} />
<StyledMarkdown key={router.asPath} markdown={content} />
<NextPrevButton
prevLabel={frontmatter?.prev?.label}
prevURL={frontmatter?.prev?.url}
Expand Down
4 changes: 3 additions & 1 deletion pages/understanding-json-schema/reference/[slug].page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { getLayout } from '~/components/Sidebar';
import { Headline1 } from '~/components/Headlines';
import StyledMarkdown from '~/components/StyledMarkdown';
Expand All @@ -26,6 +27,7 @@ export default function StaticMarkdownPage({
frontmatter: any;
content: any;
}) {
const router = useRouter();
const newTitle = 'JSON Schema - ' + frontmatter.title;
const fileRenderType = '_md';
return (
Expand All @@ -34,7 +36,7 @@ export default function StaticMarkdownPage({
<title>{newTitle}</title>
</Head>
<Headline1>{frontmatter.title || 'NO TITLE!'}</Headline1>
<StyledMarkdown markdown={content} />
<StyledMarkdown key={router.asPath} markdown={content} />
<NextPrevButton
prevLabel={frontmatter?.prev?.label}
prevURL={frontmatter?.prev?.url}
Expand Down
Loading