fix: spec TOC

This commit is contained in:
2026-01-10 21:31:06 +00:00
parent 4fadbd111a
commit f1fa264ed7
11 changed files with 960 additions and 846 deletions

View File

@@ -9,15 +9,17 @@
document.documentElement.classList.add("dark"); document.documentElement.classList.add("dark");
} }
})(); })();
</script><link rel="stylesheet" href="/_astro/index.DViPM5fQ.css"></head> <body class="min-h-screen flex flex-col items-center justify-center p-8"> <div class="text-center"> <h1 class="text-[8rem] sm:text-[12rem] font-display font-bold leading-none </script><link rel="stylesheet" href="/_astro/index.BpjWaW-v.css"></head> <body class="min-h-screen flex flex-col items-center justify-center p-8"> <div class="text-center"> <h1 class="text-[8rem] sm:text-[12rem] font-display font-bold leading-none
text-[var(--color-border-strong)] text-gray-300 dark:text-gray-700">
dark:text-[var(--color-dark-border-strong)]">
404 404
</h1> <p class="text-xl mb-2 text-[var(--color-text-secondary)] </h1> <p class="text-xl mb-2 text-gray-600 dark:text-gray-400">
dark:text-[var(--color-dark-text-secondary)]">
Page not found Page not found
</p> <p class="text-[var(--color-text-muted)] dark:text-[var(--color-dark-text-muted)]"> </p> <p class="text-gray-500 dark:text-gray-500">
The page you're looking for doesn't exist. The page you're looking for doesn't exist.
</p> <a href="/" class="inline-block mt-8 btn btn-primary"> </p> <a href="/" class="inline-flex items-center justify-center gap-2 mt-8
px-5 py-2.5 text-sm font-medium rounded-lg
transition-all cursor-pointer
bg-sky-600 text-white
hover:bg-sky-500 hover:-translate-y-0.5 hover:shadow-md">
Go to homepage Go to homepage
</a> </div> </body></html> </a> </div> </body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -132,6 +132,11 @@ const { terminology, specification, tocItems } = Astro.props;
color: #737373; color: #737373;
} }
/* Scroll margin for anchored spec sections */
.spec-content :global(ol > li[id]) {
scroll-margin-top: calc(var(--header-height) + 2rem);
}
.spec-content :global(li) { .spec-content :global(li) {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
color: #525252; color: #525252;

View File

@@ -96,6 +96,20 @@ function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
} }
// Spec section titles in order (used for both ToC and anchor injection)
const SPEC_SECTION_TITLES = [
"TL;DR",
"The Master Branch",
"Change Branches",
"Pull Requests",
"Versioning",
"Releases",
"Short-Term Release Branches",
"Long-term Release Branches",
"Bug Fixes & Rollback",
"Git Best Practices",
];
/** /**
* Extract the numbered spec sections (1. TL;DR, 2. The Master Branch, etc.) * Extract the numbered spec sections (1. TL;DR, 2. The Master Branch, etc.)
*/ */
@@ -107,24 +121,8 @@ function extractSpecSections(specContent: string): SpecSection[] {
const olMatch = specContent.match(/<ol[^>]*>([\s\S]*?)<\/ol>/i); const olMatch = specContent.match(/<ol[^>]*>([\s\S]*?)<\/ol>/i);
if (!olMatch) return sections; if (!olMatch) return sections;
// Split by top-level list items
// We need to handle nested lists carefully
const sectionTitles = [
"TL;DR",
"The Master Branch",
"Change Branches",
"Pull Requests",
"Versioning",
"Releases",
"Short-Term Release Branches",
"Long-term Release Branches",
"Bug Fixes & Rollback",
"Git Best Practices",
];
// Find each section by looking for the title pattern // Find each section by looking for the title pattern
for (let i = 0; i < sectionTitles.length; i++) { for (const title of SPEC_SECTION_TITLES) {
const title = sectionTitles[i];
const id = slugify(title); const id = slugify(title);
// For the content, we'll just use the title for navigation // For the content, we'll just use the title for navigation
@@ -139,6 +137,27 @@ function extractSpecSections(specContent: string): SpecSection[] {
return sections; return sections;
} }
/**
* Add anchor IDs to spec section list items.
* Finds top-level <li> elements that start with section titles and adds IDs.
*/
function addSpecSectionAnchors(specContent: string): string {
let result = specContent;
for (const title of SPEC_SECTION_TITLES) {
const id = `spec-${slugify(title)}`;
// Match <li> followed by the section title (possibly with whitespace)
// The title appears right after <li> in the rendered HTML
const pattern = new RegExp(
`(<li>)(\\s*${escapeRegex(title)})`,
"i"
);
result = result.replace(pattern, `<li id="${id}">$2`);
}
return result;
}
/** /**
* Extract FAQ items from the FAQ section HTML * Extract FAQ items from the FAQ section HTML
*/ */
@@ -187,18 +206,13 @@ function extractFAQItems(faqContent: string): FAQItem[] {
} }
/** /**
* Build table of contents from parsed sections * Build table of contents from parsed sections.
* Only includes sections rendered in SpecSection (Terminology + Specification).
* Introduction/Summary are in AboutSection and excluded from this ToC.
*/ */
function buildTocItems(parsed: Partial<ParsedSpec>): TocItem[] { function buildTocItems(parsed: Partial<ParsedSpec>): TocItem[] {
const items: TocItem[] = []; const items: TocItem[] = [];
// Main sections
if (parsed.introduction) {
items.push({ id: "introduction", title: "Introduction", level: 2 });
}
if (parsed.summary) {
items.push({ id: "summary", title: "Summary", level: 2 });
}
if (parsed.terminology) { if (parsed.terminology) {
items.push({ id: "terminology", title: "Terminology", level: 2 }); items.push({ id: "terminology", title: "Terminology", level: 2 });
} }
@@ -256,12 +270,15 @@ export function parseSpecContent(html: string, version: string): ParsedSpec {
"License", "License",
]); ]);
const specification = extractSection( const specificationRaw = extractSection(
content, content,
"Git Common-Flow Specification", "Git Common-Flow Specification",
["FAQ", "About", "License"] ["FAQ", "About", "License"]
); );
// Add anchor IDs to spec section list items for ToC navigation
const specification = addSpecSectionAnchors(specificationRaw);
const faqContent = extractSection(content, "FAQ", ["About", "License"]); const faqContent = extractSection(content, "FAQ", ["About", "License"]);
const about = extractSection(content, "About", ["License"]); const about = extractSection(content, "About", ["License"]);
@@ -269,7 +286,7 @@ export function parseSpecContent(html: string, version: string): ParsedSpec {
const license = extractSection(content, "License", []); const license = extractSection(content, "License", []);
// Parse subsections // Parse subsections
const specSections = extractSpecSections(specification); const specSections = extractSpecSections(specificationRaw);
const faq = extractFAQItems(faqContent); const faq = extractFAQItems(faqContent);
const parsed: ParsedSpec = { const parsed: ParsedSpec = {