diff --git a/package-lock.json b/package-lock.json index 72efcda..a449986 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1175,9 +1175,9 @@ } }, "node_modules/@fastify/static": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.1.0.tgz", - "integrity": "sha512-EPRNQYqEYEYTK8yyGbcM0iHpyJaupb94bey5O6iCQfLTADr02kaZU+qeHSdd9H9TiMwTBVkrMa59V8CMbn3avQ==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.1.3.tgz", + "integrity": "sha512-aXrYtsiryLhRxRNaxNqsn7FUISeb7rB9q4eHUPIot5aeQBLNahnz1m6thzm7JWC1poSGXS9XrX8DvuMivp2hkQ==", "funding": [ { "type": "github", @@ -4052,9 +4052,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastify": { - "version": "5.8.4", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.4.tgz", - "integrity": "sha512-sa42J1xylbBAYUWALSBoyXKPDUvM3OoNOibIefA+Oha57FryXKKCZarA1iDntOCWp3O35voZLuDg2mdODXtPzQ==", + "version": "5.8.5", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.5.tgz", + "integrity": "sha512-Yqptv59pQzPgQUSIm87hMqHJmdkb1+GPxdE6vW6FRyVE9G86mt7rOghitiU4JHRaTyDUk9pfeKmDeu70lAwM4Q==", "funding": [ { "type": "github", @@ -7294,9 +7294,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", + "integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==", "dev": true, "funding": [ { diff --git a/src/client/routes/wiki-route.tsx b/src/client/routes/wiki-route.tsx index afc7d31..6a12ea2 100644 --- a/src/client/routes/wiki-route.tsx +++ b/src/client/routes/wiki-route.tsx @@ -15,17 +15,56 @@ import { useWikiConfig } from "@/client/wiki-config"; import { getTopicColor, type TopicAliasConfig } from "@/lib/wiki-config"; import type { WikiHeading, WikiNeighbor, WikiPageData } from "@/lib/wiki-shared"; import { usePersonImage } from "@/client/use-person-image"; +import { createHeadingId } from "@/lib/markdown"; import { fetchJson, isSetupRequiredResponse } from "../api"; import { RouteErrorBoundary } from "../route-error-boundary"; const remarkPlugins = [remarkGfm]; const rehypePlugins = [rehypeHighlight]; + +/** Extracts plain text from React children to use for ID generation */ +function getTextContent(children: React.ReactNode): string { + if (typeof children === "string") return children; + if (Array.isArray(children)) return children.map(getTextContent).join(""); + // @ts-ignore - children might be a ReactElement with props + if (children?.props?.children) return getTextContent(children.props.children); + return ""; +} + const markdownComponents = { - h1: ({ ...props }) =>

, - h2: ({ ...props }) =>

, - h3: ({ ...props }) =>

, - h4: ({ ...props }) =>

, + h1: ({ children, ...props }: any) => { + const id = createHeadingId(getTextContent(children)); + return ( +

+ {children} +

+ ); + }, + h2: ({ children, ...props }: any) => { + const id = createHeadingId(getTextContent(children)); + return ( +

+ {children} +

+ ); + }, + h3: ({ children, ...props }: any) => { + const id = createHeadingId(getTextContent(children)); + return ( +

+ {children} +

+ ); + }, + h4: ({ children, ...props }: any) => { + const id = createHeadingId(getTextContent(children)); + return ( +

+ {children} +

+ ); + }, p: ({ ...props }) =>

, ul: ({ ...props }) =>