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");
}
})();
</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
text-[var(--color-border-strong)]
dark:text-[var(--color-dark-border-strong)]">
</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-gray-300 dark:text-gray-700">
404
</h1> <p class="text-xl mb-2 text-[var(--color-text-secondary)]
dark:text-[var(--color-dark-text-secondary)]">
</h1> <p class="text-xl mb-2 text-gray-600 dark:text-gray-400">
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.
</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
</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;
}
/* Scroll margin for anchored spec sections */
.spec-content :global(ol > li[id]) {
scroll-margin-top: calc(var(--header-height) + 2rem);
}
.spec-content :global(li) {
margin-bottom: 0.5rem;
color: #525252;

View File

@@ -96,6 +96,20 @@ function escapeRegex(str: string): string {
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.)
*/
@@ -107,24 +121,8 @@ function extractSpecSections(specContent: string): SpecSection[] {
const olMatch = specContent.match(/<ol[^>]*>([\s\S]*?)<\/ol>/i);
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
for (let i = 0; i < sectionTitles.length; i++) {
const title = sectionTitles[i];
for (const title of SPEC_SECTION_TITLES) {
const id = slugify(title);
// For the content, we'll just use the title for navigation
@@ -139,6 +137,27 @@ function extractSpecSections(specContent: string): SpecSection[] {
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
*/
@@ -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[] {
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) {
items.push({ id: "terminology", title: "Terminology", level: 2 });
}
@@ -256,12 +270,15 @@ export function parseSpecContent(html: string, version: string): ParsedSpec {
"License",
]);
const specification = extractSection(
const specificationRaw = extractSection(
content,
"Git Common-Flow Specification",
["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 about = extractSection(content, "About", ["License"]);
@@ -269,7 +286,7 @@ export function parseSpecContent(html: string, version: string): ParsedSpec {
const license = extractSection(content, "License", []);
// Parse subsections
const specSections = extractSpecSections(specification);
const specSections = extractSpecSections(specificationRaw);
const faq = extractFAQItems(faqContent);
const parsed: ParsedSpec = {