mirror of
https://github.com/jimeh/commonflow.org.git
synced 2026-02-19 05:46:40 +00:00
wip: anchor links on every spec clause
This commit is contained in:
@@ -6,7 +6,14 @@ import { unified } from "unified";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkRehype from "remark-rehype";
|
||||
import rehypeStringify from "rehype-stringify";
|
||||
import type { Root, RootContent, Heading, List, ListItem } from "mdast";
|
||||
import type {
|
||||
Root,
|
||||
RootContent,
|
||||
Heading,
|
||||
List,
|
||||
ListItem,
|
||||
Html,
|
||||
} from "mdast";
|
||||
import type { Root as HastRoot } from "hast";
|
||||
|
||||
export interface TocItem {
|
||||
@@ -186,11 +193,12 @@ function findSpecSections(nodes: RootContent[]): SpecSection[] {
|
||||
const titles = extractListItemTitles(node as List);
|
||||
for (let i = 0; i < titles.length; i++) {
|
||||
const title = titles[i];
|
||||
const clauseNum = i + 1;
|
||||
sections.push({
|
||||
id: `spec-${slugify(title)}`,
|
||||
id: `clause-${clauseNum}`,
|
||||
title,
|
||||
content: "",
|
||||
clause: `${i + 1}.`,
|
||||
clause: `${clauseNum}.`,
|
||||
});
|
||||
}
|
||||
break; // Only process first ordered list
|
||||
@@ -201,33 +209,44 @@ function findSpecSections(nodes: RootContent[]): SpecSection[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add anchor IDs to list items in the spec ordered list
|
||||
* Add anchor IDs and links to ordered list items recursively.
|
||||
* Injects an invisible anchor link before content for hover-to-reveal behavior.
|
||||
*/
|
||||
function addAnchorsToList(list: List, sections: SpecSection[]): void {
|
||||
const titleMap = new Map(sections.map((s) => [s.title, s.id]));
|
||||
|
||||
for (const item of list.children) {
|
||||
function addClauseAnchors(list: List, prefix: string = ""): void {
|
||||
for (let i = 0; i < list.children.length; i++) {
|
||||
const item = list.children[i];
|
||||
if (item.type !== "listItem") continue;
|
||||
|
||||
// Get the title of this item
|
||||
let title = "";
|
||||
// Calculate clause number and ID
|
||||
const clauseNum = prefix ? `${prefix}.${i + 1}` : `${i + 1}`;
|
||||
const clauseId = `clause-${clauseNum.replace(/\./g, "-")}`;
|
||||
|
||||
// Add ID to the list item via hProperties
|
||||
(item as ListItem & { data?: { hProperties?: { id?: string } } }).data = {
|
||||
hProperties: { id: clauseId },
|
||||
};
|
||||
|
||||
// Find the first paragraph in the item and prepend an anchor link
|
||||
for (const child of item.children) {
|
||||
if (child.type === "list") break;
|
||||
if (child.type === "paragraph") {
|
||||
title = extractText(child).split("\n")[0].trim();
|
||||
// Create anchor link HTML to inject
|
||||
const anchorHtml: Html = {
|
||||
type: "html",
|
||||
value: `<a href="#${clauseId}" class="clause-link" aria-hidden="true"></a>`,
|
||||
};
|
||||
// Prepend anchor to paragraph children
|
||||
(child as { children: RootContent[] }).children.unshift(
|
||||
anchorHtml as unknown as RootContent,
|
||||
);
|
||||
break;
|
||||
}
|
||||
title += extractText(child);
|
||||
}
|
||||
title = title.split("\n")[0].trim();
|
||||
|
||||
// Add ID as data attribute (will be processed by rehype)
|
||||
const id = titleMap.get(title);
|
||||
if (id) {
|
||||
// Add hProperties for rehype to convert to HTML id attribute
|
||||
(item as ListItem & { data?: { hProperties?: { id?: string } } }).data = {
|
||||
hProperties: { id },
|
||||
};
|
||||
// Recursively process nested ordered lists
|
||||
for (const child of item.children) {
|
||||
if (child.type === "list" && (child as List).ordered) {
|
||||
addClauseAnchors(child as List, clauseNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,10 +358,10 @@ export async function parseSpecContent(
|
||||
// Extract spec sections from the first ordered list
|
||||
const specSections = findSpecSections(specNodes);
|
||||
|
||||
// Add anchor IDs to spec list items
|
||||
// Add anchor IDs and links to spec list items
|
||||
for (const node of specNodes) {
|
||||
if (node.type === "list" && (node as List).ordered) {
|
||||
addAnchorsToList(node as List, specSections);
|
||||
addClauseAnchors(node as List);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user