feat: implement content reveal mechanism via DOM traversal
Add `addRevealedClasses` to apply `revealed` or `concealed` CSS classes to article elements based on the current heading hierarchy and revealed paths. Update `Article` to expose its DOM element via an `onDom` callback.
This commit is contained in:
@@ -627,6 +627,54 @@ export function isPathRevealed(path: string, section?: string): boolean {
|
||||
return revealed.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk every element in `root` and tag it as revealed or concealed.
|
||||
*
|
||||
* Headings (h1–h6) update a current-heading tracker. When a higher-level
|
||||
* heading is encountered (e.g. h2 after h1), all sub-headings at deeper
|
||||
* levels are cleared. Each non-heading element receives a `revealed` or
|
||||
* `concealed` CSS class depending on whether any of its current heading
|
||||
* ancestors is present in the `revealed` set.
|
||||
*/
|
||||
export function addRevealedClasses(
|
||||
root: HTMLDivElement,
|
||||
revealed: Set<string>,
|
||||
) {
|
||||
if (!revealed) return;
|
||||
// Current heading text for each level (h1..h6, index 1..6)
|
||||
const cur: Record<number, string> = {};
|
||||
|
||||
const HEADING_TAGS = new Set(["H1", "H2", "H3", "H4", "H5", "H6"]);
|
||||
|
||||
const walk = (el: Element) => {
|
||||
const tag = el.tagName.toUpperCase();
|
||||
|
||||
if (HEADING_TAGS.has(tag)) {
|
||||
const level = Number(tag.charAt(1)); // 1-6
|
||||
const text = el.textContent?.trim() ?? "";
|
||||
|
||||
// Update current heading at this level
|
||||
cur[level] = text;
|
||||
// Clear all deeper heading levels
|
||||
for (let l = level + 1; l <= 6; l++) delete cur[l];
|
||||
} else {
|
||||
// Non-heading: check if any current heading ancestor is revealed
|
||||
const isRevealed = Object.values(cur).some((h) => revealed.has(h));
|
||||
el.classList.add(isRevealed ? "revealed" : "concealed");
|
||||
}
|
||||
|
||||
// Recurse into children
|
||||
for (const child of el.children) {
|
||||
walk(child);
|
||||
}
|
||||
};
|
||||
|
||||
// Start walk from root's children (skip the container itself)
|
||||
for (const child of root.children) {
|
||||
walk(child);
|
||||
}
|
||||
}
|
||||
|
||||
/** Normalize a path for lookup: strip .md, leading ./ or / */
|
||||
function normalizePath(p: string): string {
|
||||
return p.replace(/^\.?\//, "").replace(/\.md$/, "");
|
||||
|
||||
Reference in New Issue
Block a user