rework/lightweight #12
@ -1,6 +1,8 @@
|
||||
import type { SectionCache } from "@/services/DocCache";
|
||||
import type { PageContext } from "vike/types";
|
||||
|
||||
import { useConfig } from "vike-solid/useConfig";
|
||||
import { docCache } from "@/services/DocCache";
|
||||
import buildTitle from "./buildTitle";
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
@ -9,7 +11,7 @@ export async function data(pageContext: PageContext) {
|
||||
const config = useConfig();
|
||||
|
||||
const {
|
||||
exports: { tableOfContents, frontmatter },
|
||||
exports: { frontmatter },
|
||||
urlParsed,
|
||||
} = pageContext;
|
||||
const isRoot = urlParsed.pathname === "/";
|
||||
@ -19,7 +21,16 @@ export async function data(pageContext: PageContext) {
|
||||
description: frontmatter?.description,
|
||||
});
|
||||
|
||||
let doc: SectionCache | undefined;
|
||||
|
||||
if (urlParsed.pathname === "/") {
|
||||
doc = docCache.get("index");
|
||||
} else {
|
||||
doc = docCache.get(urlParsed.pathname);
|
||||
}
|
||||
|
||||
return {
|
||||
tableOfContents,
|
||||
sections: doc?.sections || [],
|
||||
frontmatter,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import type { TableOfContents as TableOfContentsType } from "@/remarkHeadingId";
|
||||
import type { Section } from "@/libs/sections";
|
||||
import type { DocSection } from "@/services/DocCache";
|
||||
import type { Data } from "@/pages/+data";
|
||||
|
||||
import { createSignal, createEffect, For } from "solid-js";
|
||||
@ -8,31 +7,36 @@ import { Link } from "@/components/Link";
|
||||
import clsx from "clsx";
|
||||
|
||||
export function TableOfContents() {
|
||||
const { tableOfContents } = useData<Data>();
|
||||
const { sections } = useData<Data>();
|
||||
if (!sections) return null;
|
||||
|
||||
if (!tableOfContents) return null;
|
||||
createEffect(() => {
|
||||
for (const [key, value] of Object.entries(sections)) {
|
||||
console.log(`${key}: ${JSON.stringify(value)}`);
|
||||
}
|
||||
});
|
||||
|
||||
const [currentSection, setCurrentSection] = createSignal(
|
||||
tableOfContents[0]?.id,
|
||||
);
|
||||
const [currentSection, setCurrentSection] = createSignal(sections[0]?.hash);
|
||||
|
||||
const getHeadings = () => {
|
||||
return tableOfContents
|
||||
return sections
|
||||
.map((section) => {
|
||||
const el = document.getElementById(section.id);
|
||||
if (!section.hash) return null;
|
||||
|
||||
const el = document.getElementById(section.hash);
|
||||
if (!el) return null;
|
||||
|
||||
const style = window.getComputedStyle(el);
|
||||
const scrollMt = Number.parseFloat(style.scrollMarginTop);
|
||||
|
||||
const top = window.scrollY + el.getBoundingClientRect().top - scrollMt;
|
||||
return { id: section.id, top };
|
||||
return { id: section.hash, top };
|
||||
})
|
||||
.filter((x): x is { id: string; top: number } => x !== null);
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
if (tableOfContents.length === 0) return;
|
||||
if (sections.length === 0) return;
|
||||
const headings = getHeadings();
|
||||
|
||||
function onScroll() {
|
||||
@ -51,10 +55,10 @@ export function TableOfContents() {
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [getHeadings, tableOfContents]);
|
||||
}, [getHeadings, sections]);
|
||||
|
||||
function isActive(section: Section) {
|
||||
if (section.id === currentSection()) return true;
|
||||
function isActive(section: DocSection) {
|
||||
if (section.hash === currentSection()) return true;
|
||||
return false;
|
||||
// if (!section.children) return false;
|
||||
|
||||
@ -72,19 +76,19 @@ export function TableOfContents() {
|
||||
</h2>
|
||||
|
||||
<ol class="mt-4 space-y-3 text-sm">
|
||||
<For each={tableOfContents}>
|
||||
<For each={sections}>
|
||||
{(section) => (
|
||||
<li>
|
||||
<h3>
|
||||
<Link
|
||||
href={`#${section.id}`}
|
||||
href={`#${section.hash}`}
|
||||
class={clsx(
|
||||
isActive(section)
|
||||
? "text-violet-500"
|
||||
: "font-normal text-slate-500 hover:text-slate-700",
|
||||
)}
|
||||
>
|
||||
{section.title}
|
||||
{section.content}
|
||||
</Link>
|
||||
</h3>
|
||||
</li>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { FlexSearchData } from "./FlexSearchService";
|
||||
import type { TableOfContents } from "@/remarkHeadingId";
|
||||
import type { Node } from "@markdoc/markdoc";
|
||||
|
||||
import { slugifyWithCounter } from "@sindresorhus/slugify";
|
||||
@ -7,7 +8,6 @@ import { hrtime } from "node:process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import yaml from "js-yaml";
|
||||
import { copyFile } from "node:fs";
|
||||
|
||||
const __dirname = path.resolve();
|
||||
|
||||
@ -15,12 +15,14 @@ export type SectionCache = {
|
||||
content: string;
|
||||
frontmatter?: SectionFrontmatter;
|
||||
markdocNode: Node;
|
||||
sections: DocSection[];
|
||||
};
|
||||
|
||||
export type DocSection = {
|
||||
content: string;
|
||||
hash?: string;
|
||||
subsections: string[];
|
||||
level?: number;
|
||||
};
|
||||
|
||||
type SectionFrontmatter = {
|
||||
@ -99,7 +101,12 @@ class DocCache {
|
||||
if (node.type === "heading" && node.attributes?.level <= 2) {
|
||||
const hash = (node.attributes?.id as string) ?? this.slugify(content);
|
||||
const subsections: string[] = [];
|
||||
sections.push({ content, hash, subsections });
|
||||
sections.push({
|
||||
content,
|
||||
hash,
|
||||
subsections,
|
||||
level: node.attributes?.level,
|
||||
});
|
||||
} else {
|
||||
sections.at(-1)?.subsections.push(content);
|
||||
}
|
||||
@ -124,9 +131,26 @@ class DocCache {
|
||||
return data;
|
||||
}
|
||||
|
||||
private getTableOfContents(markdocNode: Node) {
|
||||
const sections: DocSection[] = [];
|
||||
this.extractSections(markdocNode, sections);
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
private async cacheFile(file: string): Promise<void> {
|
||||
const [content, frontmatter, markdocNode] = await this.getFileContent(file);
|
||||
this.cache.set(file, { content, frontmatter, markdocNode });
|
||||
const filePath = file
|
||||
.replace(/\+Page\.md(x)?$/, "")
|
||||
.replace(/\/+/, "/")
|
||||
.replace(/\/$/, "");
|
||||
|
||||
this.cache.set(filePath, {
|
||||
content,
|
||||
frontmatter,
|
||||
markdocNode,
|
||||
sections: this.getTableOfContents(markdocNode),
|
||||
});
|
||||
}
|
||||
|
||||
private async populateCache(): Promise<void> {
|
||||
@ -157,6 +181,10 @@ class DocCache {
|
||||
return DocCache.getInstance().cache;
|
||||
}
|
||||
|
||||
public getCache(): Map<string, SectionCache> {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public get(file: string): SectionCache | undefined {
|
||||
return this.cache.get(file);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user