diff --git a/.github/workflows/www.yaml b/.github/workflows/www.yaml index 76da0cf10..92e78aa41 100644 --- a/.github/workflows/www.yaml +++ b/.github/workflows/www.yaml @@ -10,7 +10,7 @@ on: jobs: www: - runs-on: ubuntu-latest + runs-on: macos-latest timeout-minutes: 15 permissions: @@ -26,7 +26,7 @@ jobs: - name: Setup Deno uses: denoland/setup-deno@v2 with: - deno-version: v2.x + deno-version: v2.6.1 - name: Serve Website run: | @@ -43,14 +43,14 @@ jobs: - name: Download Staticalize run: | - wget https://github.com/thefrontside/staticalize/releases/download/v0.2.2/staticalize-linux.tar.gz \ - -O /tmp/staticalize-linux.tar.gz - tar -xzf /tmp/staticalize-linux.tar.gz -C /usr/local/bin - chmod +x /usr/local/bin/staticalize-linux + wget https://github.com/thefrontside/staticalize/releases/download/v0.2.2/staticalize-macos.tar.gz \ + -O /tmp/staticalize-macos.tar.gz + tar -xzf /tmp/staticalize-macos.tar.gz -C /usr/local/bin + chmod +x /usr/local/bin/staticalize-macos - name: Staticalize run: | - staticalize-linux \ + staticalize-macos \ --site=http://127.0.0.1:8000 \ --output=www/built \ --base=https://effection-www.deno.dev/ diff --git a/lib/main.ts b/lib/main.ts index 0bc54be41..065d39caa 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -180,8 +180,7 @@ function* withHost(op: HostOperation): Operation { // @see https://github.com/iliakan/detect-node/blob/master/index.js } else if ( Object.prototype.toString.call( - // @ts-expect-error we are just detecting the possibility, so type strictness not required - typeof globalThis.process !== "undefined" ? globalThis.process : 0, + typeof global.process !== "undefined" ? global.process : 0, ) === "[object process]" ) { return yield* op.node(); diff --git a/www/components/api/api-page.tsx b/www/components/api/api-page.tsx index a8bfc9495..1327ed82f 100644 --- a/www/components/api/api-page.tsx +++ b/www/components/api/api-page.tsx @@ -1,10 +1,11 @@ +import { all } from "effection"; import type { JSXElement } from "revolution"; -import { DocPage } from "../../hooks/use-deno-doc.tsx"; +import { useConfig } from "../../context/config.ts"; +import { LocalDocPage } from "../../hooks/use-deno-doc.tsx"; import { ResolveLinkFunction, useMarkdown } from "../../hooks/use-markdown.tsx"; -import { Package } from "../../lib/package.ts"; +import { Package, usePackage } from "../../lib/package.ts"; import { major } from "../../lib/semver.ts"; -import { createSibling } from "../../routes/links-resolvers.ts"; -import { IconExternal } from "../icons/external.tsx"; +import { createRootUrl, createSibling } from "../../lib/links-resolvers.ts"; import { SourceCodeIcon } from "../icons/source-code.tsx"; import { GithubPill } from "../package/source-link.tsx"; import { Icon } from "../type/icon.tsx"; @@ -19,7 +20,7 @@ export function* ApiPage({ banner, }: { current: string; - pages: DocPage[]; + pages: LocalDocPage[]; pkg: Package; banner?: JSXElement; externalLinkResolver: ResolveLinkFunction; @@ -61,6 +62,33 @@ export function* ApiPage({ ), linkResolver: createSibling, + versionToggle: yield* (function* () { + const { series: SERIES } = yield* useConfig(); + const currentSeries = `v${major(pkg.version)}`; + + const links = yield* all( + SERIES.map(function* (s) { + const seriesPkg = yield* usePackage({ + type: "worktree", + series: s, + }); + const seriesDocs = yield* seriesPkg.docs(); + const hasSymbol = seriesDocs["."].some((node) => node.name === current); + + if (!hasSymbol) return null; + + return ( + + {seriesPkg.version} + + ); + }), + ); + return {...links.filter((link): link is JSXElement => link !== null)}; + })(), })} ); @@ -70,7 +98,7 @@ export function* ApiBody({ page, linkResolver, }: { - page: DocPage; + page: LocalDocPage; linkResolver: ResolveLinkFunction; }) { const elements: JSXElement[] = []; @@ -89,8 +117,8 @@ export function* ApiBody({ {yield* Type({ node: section.node })} @@ -115,12 +143,14 @@ export function* ApiReference({ current, pages, linkResolver, + versionToggle }: { pkg: Package; content: JSXElement; current: string; - pages: DocPage[]; + pages: LocalDocPage[]; linkResolver: ResolveLinkFunction; + versionToggle: JSXElement; }) { return (
@@ -128,16 +158,7 @@ export function* ApiReference({ @@ -153,7 +174,7 @@ export function* ApiReference({ ); } -export function* SymbolHeader({ page, pkg }: { page: DocPage; pkg: Package }) { +export function* SymbolHeader({ page, pkg }: { page: LocalDocPage; pkg: Package }) { return (

@@ -178,7 +199,7 @@ function* Menu({ linkResolver, }: { current: string; - pages: DocPage[]; + pages: LocalDocPage[]; linkResolver: ResolveLinkFunction; }) { const elements = []; diff --git a/www/components/header.tsx b/www/components/header.tsx index d4c3a2343..7ebfe0c4c 100644 --- a/www/components/header.tsx +++ b/www/components/header.tsx @@ -32,7 +32,7 @@ export function* Header(props?: HeaderProps) {
  • Guides diff --git a/www/components/type/jsx.tsx b/www/components/type/jsx.tsx index 2f5cff5a1..6f388503f 100644 --- a/www/components/type/jsx.tsx +++ b/www/components/type/jsx.tsx @@ -168,6 +168,16 @@ function TSParam({ param }: { param: ParamDef }) { ); } + case "assign": { + return ( + <> + + {" = "} + {param.tsType ? : <>} + {param.right === "[UNSUPPORTED]" ? "{}" : <> } + + ) + } default: console.log(" unimplemented:", param); } diff --git a/www/components/type/markdown.tsx b/www/components/type/markdown.tsx index 3b1dda457..7fa0cd50f 100644 --- a/www/components/type/markdown.tsx +++ b/www/components/type/markdown.tsx @@ -380,7 +380,6 @@ function Param(paramDef: ParamDef): string { paramDef.tsType ? TypeDef(paramDef.tsType) : "" }`; } - case "assign": case "array": case "object": console.log("Param: unimplemented", paramDef); diff --git a/www/context/config.ts b/www/context/config.ts new file mode 100644 index 000000000..64c90c963 --- /dev/null +++ b/www/context/config.ts @@ -0,0 +1,19 @@ +import { createContext, type Operation } from "effection"; + +export interface SiteConfig { + series: T[]; + current: NoInfer; +} + +const ConfigContext = createContext("site-config", { + series: ["v3", "v4"], + current: "v4", +}); + +export function* initConfig(config: SiteConfig): Operation { + yield* ConfigContext.set(config); +} + +export function* useConfig(): Operation { + return yield* ConfigContext.expect(); +} diff --git a/www/context/doc-page.ts b/www/context/doc-page.ts index bffa404ae..6282ba7e8 100644 --- a/www/context/doc-page.ts +++ b/www/context/doc-page.ts @@ -1,4 +1,4 @@ import { createContext } from "effection"; -import type { DocPage } from "../hooks/use-deno-doc.tsx"; +import type { LocalDocPage } from "../hooks/use-deno-doc.tsx"; -export const DocPageContext = createContext("doc-page"); +export const DocPageContext = createContext("doc-page"); diff --git a/www/deno-deploy-patch.ts b/www/deno-deploy-patch.ts deleted file mode 100644 index 457411e89..000000000 --- a/www/deno-deploy-patch.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** see https://github.com/denoland/deploy_feedback/issues/527#issuecomment-2510631720 */ -export function patchDenoPermissionsQuerySync() { - const permissions = { - run: "denied", - read: "granted", - write: "denied", - net: "granted", - env: "granted", - sys: "denied", - ffi: "denied", - } as const; - - Deno.permissions.querySync ??= ({ name }) => { - return { - // @ts-expect-error deno-ts(7053) - state: permissions[name], - onchange: null, - partial: false, - addEventListener() {}, - removeEventListener() {}, - dispatchEvent() { - return false; - }, - }; - }; -} diff --git a/www/hooks/use-deno-doc.tsx b/www/hooks/use-deno-doc.tsx index f713a22d5..69d938408 100644 --- a/www/hooks/use-deno-doc.tsx +++ b/www/hooks/use-deno-doc.tsx @@ -4,15 +4,22 @@ import { type DocNode, type DocOptions, LoadResponse, + Location } from "@deno/doc"; import { call, type Operation, until, useScope } from "effection"; import { createGraph } from "@deno/graph"; +import { regex } from "arktype"; import { exportHash, extract } from "../components/type/markdown.tsx"; import { operations } from "../context/fetch.ts"; import { DenoJsonSchema } from "../lib/deno-json.ts"; import { useDescription } from "./use-description-parse.tsx"; +// Matches npm/jsr specifiers like @std/testing/bdd or lodash/fp +export const npmSpecifierPattern = regex( + "^(?:(?@[^/]+)/)?(?[^/]+)(?/.*)?$" +); + export type { DocNode }; export function* useDenoDoc( @@ -67,6 +74,16 @@ export function* useDocPages(specifier: string): Operation { resolved = new URL(specifier, referrer).toString(); } else if (specifier.startsWith("node:")) { resolved = `npm:@types/node@^22.13.5`; + } else { + const match = npmSpecifierPattern.exec(specifier); + if (match) { + const { scope, package: pkg, subpath } = match.groups; + const baseKey = scope ? `${scope}/${pkg}` : pkg; + if (baseKey in imports) { + const baseUrl = imports[baseKey]; + resolved = subpath ? `${baseUrl}${subpath}` : baseUrl; + } + } } return resolved; } @@ -169,7 +186,7 @@ function docLoader( }; } - if (url?.host === "github.com") { + if (url?.host && ['github.com', 'jsr.io'].includes(url.host)) { const response = yield* operations.fetch(specifier); const content = yield* until(response.text()); if (response.ok) { @@ -183,9 +200,7 @@ function docLoader( cause: response, }); } - } - - if (url?.host === "jsr.io") { + } else { console.log(`Ignoring ${url} while reading docs`); } }; @@ -281,3 +296,24 @@ function* extractImports( return imports; } + +/** + * LocalDocsPages are DocNodes that are stored locally + * but they represent symbols hosted on GitHub. They + * have LocalDocNode locations that include URLs to GitHub. + */ +export type LocalDocsPages = Record; + +export type LocalDocPage = DocPage & { sections: LocalDocPageSection[] } + +export type LocalDocPageSection = DocPageSection & { + node: LocalDocNode +} + +export type LocalDocNode = DocNode & { + location: LocalLocation +} + +export type LocalLocation = Location & { + url: URL +} \ No newline at end of file diff --git a/www/hooks/use-markdown.tsx b/www/hooks/use-markdown.tsx index f71bbd6df..9523d294e 100644 --- a/www/hooks/use-markdown.tsx +++ b/www/hooks/use-markdown.tsx @@ -79,7 +79,7 @@ export function* useMarkdown( rehypeAddClasses, { "h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]": - "group scroll-mt-[100px]", + "group scroll-mt-[100px] grow", pre: "grid", }, ], diff --git a/www/routes/links-resolvers.ts b/www/lib/links-resolvers.ts similarity index 100% rename from www/routes/links-resolvers.ts rename to www/lib/links-resolvers.ts diff --git a/www/lib/package.ts b/www/lib/package.ts index 2242eed8d..64ed80e2e 100644 --- a/www/lib/package.ts +++ b/www/lib/package.ts @@ -1,5 +1,12 @@ import { all, Operation, until } from "effection"; -import { DocsPages, useDocPages } from "../hooks/use-deno-doc.tsx"; +import { fileURLToPath } from "node:url"; +import { relative } from "node:path"; + +import { + DocsPages, + LocalDocsPages, + useDocPages, +} from "../hooks/use-deno-doc.tsx"; import { createRepo, Ref } from "./repo.ts"; import { extractVersion, findLatestSemverTag } from "./semver.ts"; import { useWorktree } from "./worktrees.ts"; @@ -12,10 +19,11 @@ import { useJSRClient } from "../context/jsr.ts"; import z from "zod"; import { useMDX } from "../hooks/use-mdx.tsx"; import { useDescription, useTitle } from "../hooks/use-description-parse.tsx"; +import { SiteConfig } from "../context/config.ts"; export type WorkTreePackageOptions = { type: "worktree"; - series: "v3" | "v4"; + series: SiteConfig["series"][number]; }; export type ClonePackageOptions = { @@ -36,7 +44,7 @@ export interface Package { ref: Ref; exports: Record; entrypoints: Record; - docs: () => Operation; + docs: () => Operation; workspaces: string[]; jsrPackageDetails: () => Operation< [ @@ -68,9 +76,7 @@ export function* usePackage(options: PackageOptions): Operation { if (options.type === "worktree") { let repo = createRepo({ name: "effection", owner: "thefrontside" }); - let tags = yield* repo.tags( - new RegExp(`effection-${options.series}.*`), - ); + let tags = yield* repo.tags(new RegExp(`effection-${options.series}.*`)); let ref = findLatestSemverTag(tags); @@ -136,12 +142,29 @@ function* initPackage( return entrypoints; }, *docs() { - let docs: DocsPages = {}; + let docs: LocalDocsPages = {}; for (let [entrypoint, url] of Object.entries(pkg.entrypoints)) { const pages = yield* useDocPages(`${url}`); - docs[entrypoint] = pages[`${url}`]; + docs[entrypoint] = pages[`${url}`].map((page) => { + return { + ...page, + sections: page.sections.map((section) => ({ + ...section, + node: { + ...section.node, + location: { + ...section.node.location, + url: new URL( + `${relative(path, fileURLToPath(section.node.location.filename))}#L${section.node.location.line}`, + `${ref.url}/`, + ), + }, + }, + })), + }; + }); } return docs; diff --git a/www/lib/workspace.ts b/www/lib/workspace.ts index a949c8188..c42e4ec3d 100644 --- a/www/lib/workspace.ts +++ b/www/lib/workspace.ts @@ -1,6 +1,7 @@ import { Operation } from "effection"; import { useClone } from "./clones.ts"; import { Package, usePackage } from "./package.ts"; +import { resolve } from "@std/path"; export interface Workspace { url: string; @@ -33,7 +34,7 @@ export function* useWorkspace(nameWithOwner: string): Operation { packages.push( yield* usePackage({ type: "clone", - path: `${path}/${workspacePath}`, + path: resolve(path, workspacePath), workspacePath, ref: { name: "main", diff --git a/www/main.tsx b/www/main.tsx index a4d6e228a..23728bc49 100644 --- a/www/main.tsx +++ b/www/main.tsx @@ -1,4 +1,3 @@ -import { initDenoDeploy } from "@effectionx/deno-deploy"; import { main, suspend } from "effection"; import { createRevolution, ServerInfo } from "revolution"; @@ -14,9 +13,9 @@ import { indexRoute } from "./routes/index-route.tsx"; import { xIndexRedirect, xIndexRoute } from "./routes/x-index-route.tsx"; import { xPackageRedirect, xPackageRoute } from "./routes/x-package-route.tsx"; +import { initConfig, useConfig } from "./context/config.ts"; import { initFetch } from "./context/fetch.ts"; import { initJSRClient } from "./context/jsr.ts"; -import { patchDenoPermissionsQuerySync } from "./deno-deploy-patch.ts"; import { initWorktrees } from "./lib/worktrees.ts"; import { initGuides } from "./resources/guides.ts"; import { apiIndexRoute } from "./routes/api-index-route.tsx"; @@ -30,17 +29,13 @@ import { initOctokitContext } from "./lib/octokit.ts"; // Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts if (import.meta.main) { await main(function* () { - const denoDeploy = yield* initDenoDeploy(); - - // if (denoDeploy.isDenoDeploy) { - // patchDenoPermissionsQuerySync(); - // } + const { current, series } = yield* useConfig(); yield* initClones("build/clones"); yield* initWorktrees("build/worktrees"); yield* initGuides({ - current: "v4", - worktrees: ["v3"], + current, + worktrees: series.filter((s) => s !== current), }); yield* initJSRClient(); @@ -53,18 +48,20 @@ if (import.meta.main) { app: [ route("/", indexRoute()), route("/search", searchRoute()), - route("/docs", redirectIndexRoute(firstPage("v3"))), - route("/docs/:id", redirectDocsRoute("v3")), - route("/guides/v3", redirectIndexRoute(firstPage("v3"))), - route("/guides/v4", redirectIndexRoute(firstPage("v4"))), + route("/docs", redirectIndexRoute(firstPage(current))), + route("/docs/:id", redirectDocsRoute(current)), + ...series.map((s) => + route(`/guides/${s}`, redirectIndexRoute(firstPage(s))), + ), route("/guides/:series/:id", guidesRoute({ search: true })), route("/contrib", xIndexRedirect()), route("/contrib/:workspacePath", xPackageRedirect()), route("/x", xIndexRoute({ search: true })), route("/x/:workspacePath", xPackageRoute({ search: true })), route("/api", apiIndexRoute({ search: true })), - route("/api/v3/:symbol", apiReferenceRoute("v3", { search: true })), - route("/api/v4/:symbol", apiReferenceRoute("v4", { search: true })), + ...series.map((s) => + route(`/api/${s}/:symbol`, apiReferenceRoute(s, { search: true })), + ), route( "/pagefind{/*path}", pagefindRoute({ pagefindDir: "pagefind", publicDir: "./built/" }), diff --git a/www/routes/api-index-route.tsx b/www/routes/api-index-route.tsx index 0fdcd9110..bbd543a18 100644 --- a/www/routes/api-index-route.tsx +++ b/www/routes/api-index-route.tsx @@ -6,7 +6,7 @@ import { ResolveLinkFunction } from "../hooks/use-markdown.tsx"; import { usePackage } from "../lib/package.ts"; import { SitemapRoute } from "../plugins/sitemap.ts"; import { useAppHtml } from "./app.html.tsx"; -import { createChildURL } from "./links-resolvers.ts"; +import { createChildURL } from "../lib/links-resolvers.ts"; export function apiIndexRoute( { search }: { search: boolean }, @@ -41,7 +41,7 @@ export function apiIndexRoute(

    API Reference

    -

    +

    {v4.version}
    -

    +

    {v3.version} diff --git a/www/routes/api-reference-route.tsx b/www/routes/api-reference-route.tsx index 8fc10aa3a..2ab1a7064 100644 --- a/www/routes/api-reference-route.tsx +++ b/www/routes/api-reference-route.tsx @@ -5,9 +5,10 @@ import { useAppHtml } from "./app.html.tsx"; import { ApiPage } from "../components/api/api-page.tsx"; import { usePackage } from "../lib/package.ts"; -import { createSibling } from "./links-resolvers.ts"; +import { createSibling } from "../lib/links-resolvers.ts"; +import { SiteConfig } from "../context/config.ts"; -export function apiReferenceRoute(series: "v3" | "v4", { +export function apiReferenceRoute(series: SiteConfig["series"][number], { search, }: { search: boolean; diff --git a/www/routes/guides-route.tsx b/www/routes/guides-route.tsx index 0598e081c..c91811ad5 100644 --- a/www/routes/guides-route.tsx +++ b/www/routes/guides-route.tsx @@ -5,11 +5,14 @@ import { useDescription } from "../hooks/use-description-parse.tsx"; import { RoutePath, SitemapRoute } from "../plugins/sitemap.ts"; import { type GuidesMeta, useGuides } from "../resources/guides.ts"; import { useAppHtml } from "./app.html.tsx"; -import { createChildURL, createSibling } from "./links-resolvers.ts"; +import { + createChildURL, + createRootUrl, + createSibling, +} from "../lib/links-resolvers.ts"; import { Navburger } from "../components/navburger.tsx"; import { softRedirect } from "./redirect.tsx"; -import { IconExternal } from "../components/icons/external.tsx"; -import { createRepo } from "../lib/repo.ts"; +import { useConfig } from "../context/config.ts"; export function firstPage(series: string): () => Operation { return function* () { @@ -20,11 +23,6 @@ export function firstPage(series: string): () => Operation { }; } -const SERIES = ["v3", "v4"]; -const STABLE_SERIES = "v3"; - -const repo = createRepo({ name: "effection", owner: "thefrontside" }); - export function guidesRoute({ search, }: { @@ -32,6 +30,7 @@ export function guidesRoute({ }): SitemapRoute { return { *routemap(pathname) { + const { series: SERIES } = yield* useConfig(); const paths = SERIES.map(function* (series) { let paths: RoutePath[] = []; @@ -47,7 +46,9 @@ export function guidesRoute({ return (yield* all(paths)).flat(); }, *handler(req) { - let { id, series = STABLE_SERIES } = yield* useParams<{ + const { series: SERIES, current } = yield* useConfig(); + + let { id, series = current } = yield* useParams<{ id: string | undefined; series: string | undefined; }>(); @@ -85,20 +86,18 @@ export function guidesRoute({ for (const item of topic.items) { items.push(
  • - {page.id !== item.id - ? ( - - {item.title} - - ) - : ( - - {item.title} - - )} + {page.id !== item.id ? ( + + {item.title} + + ) : ( + + {item.title} + + )}
  • , ); } @@ -112,7 +111,22 @@ export function guidesRoute({ ); } - const latest = yield* repo.latest(new RegExp(`effection-${series}.*`)); + const versionToggle = yield* all( + SERIES.map(function* (s) { + return ( + + {s} + + ); + }), + ); return ( @@ -141,15 +155,10 @@ export function guidesRoute({ @@ -159,6 +168,20 @@ export function guidesRoute({ data-series={series} >

    {page.title}

    + {series !== current ? ( +
    + You're viewing documentation for an older version. Effection + {current} is now available.{" "} + + View ${current} docs → + +
    + ) : ( + <> + )} <>{page.content} {yield* NextPrevLinks({ page })}

    @@ -179,32 +202,32 @@ function* NextPrevLinks({ page }: { page: GuidesMeta }): Operation { let { next, prev } = page; return ( - {prev - ? ( -
  • - Previous - - {prev.title} - -
  • - ) - :
  • } - {next - ? ( -
  • - Next - - {next.title} - -
  • - ) - :
  • } + {prev ? ( +
  • + Previous + + {prev.title} + +
  • + ) : ( +
  • + )} + {next ? ( +
  • + Next + + {next.title} + +
  • + ) : ( +
  • + )}
  • ); } diff --git a/www/routes/redirect-docs-route.tsx b/www/routes/redirect-docs-route.tsx index b1749d34a..8d79a453a 100644 --- a/www/routes/redirect-docs-route.tsx +++ b/www/routes/redirect-docs-route.tsx @@ -2,7 +2,7 @@ import { type JSXElement, useParams } from "revolution"; import { SitemapRoute } from "../plugins/sitemap.ts"; import { useGuides } from "../resources/guides.ts"; -import { createRootUrl } from "./links-resolvers.ts"; +import { createRootUrl } from "../lib/links-resolvers.ts"; import { softRedirect } from "./redirect.tsx"; export function redirectDocsRoute(series: string): SitemapRoute { diff --git a/www/routes/x-index-route.tsx b/www/routes/x-index-route.tsx index eb1721fcc..6521ca733 100644 --- a/www/routes/x-index-route.tsx +++ b/www/routes/x-index-route.tsx @@ -4,7 +4,7 @@ import { GithubPill } from "../components/package/source-link.tsx"; import { useWorkspace } from "../lib/workspace.ts"; import type { SitemapRoute } from "../plugins/sitemap.ts"; import { useAppHtml } from "./app.html.tsx"; -import { createChildURL, createSibling } from "./links-resolvers.ts"; +import { createChildURL, createSibling } from "../lib/links-resolvers.ts"; import { softRedirect } from "./redirect.tsx"; export function xIndexRedirect(): SitemapRoute { diff --git a/www/routes/x-package-route.tsx b/www/routes/x-package-route.tsx index 8ef5b54c4..02a64343e 100644 --- a/www/routes/x-package-route.tsx +++ b/www/routes/x-package-route.tsx @@ -19,7 +19,7 @@ import { createToc } from "../lib/toc.ts"; import { useWorkspace } from "../lib/workspace.ts"; import type { RoutePath, SitemapRoute } from "../plugins/sitemap.ts"; import { useAppHtml } from "./app.html.tsx"; -import { createSibling } from "./links-resolvers.ts"; +import { createSibling } from "../lib/links-resolvers.ts"; import { softRedirect } from "./redirect.tsx"; interface XPackageRouteParams { @@ -153,7 +153,7 @@ export function xPackageRoute({ customizeTOCItem(item, heading) { heading.properties.class = [ heading.properties.class, - `group scroll-mt-[100px]`, + `group grow scroll-mt-[100px]`, ] .filter(Boolean) .join("");