From 68c64502709dc6f6818464e01cca3aa19c9c939c Mon Sep 17 00:00:00 2001
From: Suyog241005 <166608895+Suyog241005@users.noreply.github.com>
Date: Thu, 23 Oct 2025 11:32:49 +0530
Subject: [PATCH 1/5] feat: add table of contents navigation for documentation
pages
---
components/Sidebar.tsx | 16 +-
components/TableOfContents.tsx | 187 ++++++++++++++++++
pages/learn/[slug].page.tsx | 4 +-
pages/overview/[slug].page.tsx | 4 +-
.../understanding-json-schema/[slug].page.tsx | 4 +-
.../reference/[slug].page.tsx | 4 +-
6 files changed, 212 insertions(+), 7 deletions(-)
create mode 100644 components/TableOfContents.tsx
diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx
index 463e92a4b..812fc0010 100644
--- a/components/Sidebar.tsx
+++ b/components/Sidebar.tsx
@@ -17,6 +17,7 @@ import {
CollapsibleTrigger,
} from './ui/collapsible';
import { Button } from './ui/button';
+import TableOfContents from './TableOfContents';
const DocLink = ({
uri,
@@ -276,9 +277,9 @@ export const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
-
+
{!shouldHideSidebar && (
-
+
{
)}
{children}
+ {!shouldHideSidebar && (
+
+ )}
diff --git a/components/TableOfContents.tsx b/components/TableOfContents.tsx
new file mode 100644
index 000000000..fe386d9b4
--- /dev/null
+++ b/components/TableOfContents.tsx
@@ -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
= ({
+ className,
+}) => {
+ const router = useRouter();
+ const [tocItems, setTocItems] = useState([]);
+ const [activeId, setActiveId] = useState('');
+
+ // 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
+
+ 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,
+ }
+ );
+
+ // Observe all headings
+ tocItems.forEach(({ id }) => {
+ const element = document.getElementById(id);
+ if (element) {
+ observer.observe(element);
+ }
+ });
+
+
+ return () => {
+ tocItems.forEach(({ id }) => {
+ const element = document.getElementById(id);
+ if (element) {
+ observer.unobserve(element);
+ }
+ });
+ };
+ }, [tocItems]);
+
+ 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, id: string) => {
+ e.preventDefault();
+ const element = id === 'introduction'
+ ? 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 (
+
+ );
+};
+
+export default TableOfContents;
diff --git a/pages/learn/[slug].page.tsx b/pages/learn/[slug].page.tsx
index d186f1a4d..c60723ed4 100644
--- a/pages/learn/[slug].page.tsx
+++ b/pages/learn/[slug].page.tsx
@@ -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';
@@ -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 (
@@ -31,7 +33,7 @@ export default function StaticMarkdownPage({
{newTitle}
{frontmatter.title}
-
+
{newTitle}
{frontmatter.title}
-
+
{newTitle}
{frontmatter.title || 'NO TITLE!'}
-
+
{newTitle}
{frontmatter.title || 'NO TITLE!'}
-
+
Date: Thu, 23 Oct 2025 12:46:22 +0530
Subject: [PATCH 2/5] Fix: Resolve linting and formatting issues
---
components/Sidebar.tsx | 4 +--
components/TableOfContents.tsx | 61 +++++++++++++++++++---------------
2 files changed, 36 insertions(+), 29 deletions(-)
diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx
index 812fc0010..221f4d5df 100644
--- a/components/Sidebar.tsx
+++ b/components/Sidebar.tsx
@@ -291,8 +291,8 @@ export const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
)}
diff --git a/components/TableOfContents.tsx b/components/TableOfContents.tsx
index fe386d9b4..ea6f5b46b 100644
--- a/components/TableOfContents.tsx
+++ b/components/TableOfContents.tsx
@@ -62,7 +62,7 @@ export const TableOfContents: React.FC
= ({
const observer = new IntersectionObserver(
(entries) => {
let newActiveId = '';
- let isAtTop = window.scrollY < 100; // 100px from top
+ const isAtTop = window.scrollY < 100; // 100px from top
if (isAtTop) {
// If at the top, highlight Introduction
@@ -83,9 +83,9 @@ export const TableOfContents: React.FC = ({
{
rootMargin: '-20% 0px -60% 0px',
threshold: 0.1,
- }
+ },
);
-
+
// Observe all headings
tocItems.forEach(({ id }) => {
const element = document.getElementById(id);
@@ -93,8 +93,7 @@ export const TableOfContents: React.FC = ({
observer.observe(element);
}
});
-
-
+
return () => {
tocItems.forEach(({ id }) => {
const element = document.getElementById(id);
@@ -104,7 +103,7 @@ export const TableOfContents: React.FC = ({
});
};
}, [tocItems]);
-
+
useEffect(() => {
const handleScroll = () => {
if (window.scrollY < 100) {
@@ -116,21 +115,28 @@ export const TableOfContents: React.FC = ({
return () => window.removeEventListener('scroll', handleScroll);
}, []);
- const handleClick = useCallback((e: React.MouseEvent, id: string) => {
- e.preventDefault();
- const element = id === 'introduction'
- ? 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;
+ const handleClick = useCallback(
+ (e: React.MouseEvent, id: string) => {
+ e.preventDefault();
+ const element =
+ id === 'introduction'
+ ? document.documentElement // Scroll to top for introduction
+ : document.getElementById(id);
- window.scrollTo({ top: y, behavior: 'smooth' });
- }
- }, []);
+ 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;
@@ -141,19 +147,19 @@ export const TableOfContents: React.FC = ({
className={cn(
'hidden xl:block sticky top-24 h-[calc(100vh-6rem)] overflow-y-auto',
'pr-4',
- className
+ className,
)}
- aria-label="Table of contents"
+ aria-label='Table of contents'
style={{
scrollbarWidth: 'thin',
scrollbarColor: 'rgb(203 213 225) transparent',
}}
>
-
-
+
+
On This Page
-
+
{tocItems.map((item) => (
- = ({
onClick={(e) => handleClick(e, item.id)}
className={cn(
'block py-2 text-sm transition-colors duration-200',
- activeId === item.id || (item.id === 'introduction' && !activeId)
+ 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.level === 3 ? 'pl-2' : '',
)}
>
{item.text}
From bd6a10f401aeded2ecbebced37b29fd1803b76ab Mon Sep 17 00:00:00 2001
From: Suyog241005 <166608895+Suyog241005@users.noreply.github.com>
Date: Thu, 23 Oct 2025 13:10:20 +0530
Subject: [PATCH 3/5] Fix: Resolve build and linting issues
From f62470f180d35d0c659e57d8c39c7fbeffddd089 Mon Sep 17 00:00:00 2001
From: Suyog241005 <166608895+Suyog241005@users.noreply.github.com>
Date: Thu, 23 Oct 2025 13:48:24 +0530
Subject: [PATCH 4/5] resolved the deployments issue
---
components/TableOfContents.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/components/TableOfContents.tsx b/components/TableOfContents.tsx
index ea6f5b46b..76910e727 100644
--- a/components/TableOfContents.tsx
+++ b/components/TableOfContents.tsx
@@ -145,8 +145,7 @@ export const TableOfContents: React.FC = ({
return (