migrate to nextjs app router

This commit is contained in:
Johannes Kirschbauer 2023-12-07 17:56:23 +01:00 committed by Johannes Kirschbauer
parent b0af677201
commit 8a3cb24bf9
29 changed files with 19603 additions and 984 deletions

3
website/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -1,7 +1,31 @@
/** @type {import('next').NextConfig} */
// rehypePlugins: [
// [
// rehypeHighlight,
// {
// detect: true,
// languages: { nix, haskell, bash, default: nix },
// },
// ],
// [rehypeSlug, {}],
// [rehypeAutolinkHeadings, { behavior: "wrap" }],
// ],
// remarkPlugins: [remarkHeadingId],
// format: "md",
// const remarkHeadingId = require("remark-heading-id");
// const remarkFrontmatter = import("remark-frontmatter");
// const withMDX = require("@next/mdx")({
// options: {
// remarkPlugins: [remarkHeadingId, remarkFrontmatter],
// rehypePlugins: [],
// },
// extension: /\.mdx?$/,
// });
const nextConfig = (phase, { defaultConfig }) => {
const config = {
compress: true,
optimizeFonts: true,
output: "export",
reactStrictMode: true,
swcMinify: true,
@ -9,6 +33,7 @@ const nextConfig = (phase, { defaultConfig }) => {
loader: "custom",
},
};
return config;
};

View File

@ -32,7 +32,6 @@ in
config.floco.packages.${ident}.${version} =
let
cfg = config.floco.packages.${ident}.${version};
prefix = "models/data";
in
{
# ---------------------------------------------------------------------------- #
@ -49,9 +48,9 @@ in
override.preBuild = ''
export HOME=./home
${hooks.prepare "models/data"}
${hooks.prepare "src/models/data"}
ls -la models/data
ls -la src/models/data
'';
tree =

File diff suppressed because it is too large Load Diff

10280
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,20 +12,39 @@
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@fontsource/roboto": "^5.0.0",
"@jsdevtools/rehype-toc": "^3.0.2",
"@mdx-js/loader": "^3.0.0",
"@mdx-js/react": "^3.0.0",
"@mui/icons-material": "^5.10.9",
"@mui/material": "^5.10.13",
"@next/mdx": "^14.0.3",
"@types/mdx": "^2.0.10",
"@vcarl/remark-headings": "^0.1.0",
"highlight.js": "^11.7.0",
"minisearch": "^6.0.1",
"next": "^14.0.3",
"next-mdx-remote": "^4.4.1",
"notistack": "^3.0.0",
"pagefind": "^1.0.4",
"parse5": "^7.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-highlight": "^0.15.0",
"react-mark.js": "^9.0.7",
"react-markdown": "^9.0.0",
"react-minisearch": "^6.0.2",
"rehype": "^13.0.1",
"rehype-autolink-headings": "^7.1.0",
"rehype-highlight": "^7.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.0",
"remark-frontmatter": "^5.0.0",
"remark-heading-id": "^1.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0",
"remark-stringify": "^11.0.0",
"seedrandom": "^3.0.5",
"unified": "^11.0.4",
"usehooks-ts": "^2.9.1"
},
"devDependencies": {
@ -37,7 +56,6 @@
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"eslint": "8.50.0",
"eslint-config-next": "^14.0.3",
"typescript": "5.2.2"
"eslint-config-next": "^14.0.3"
}
}

View File

@ -1,8 +1,8 @@
{ fmod, pkg, pkgs, hooks, ... }:
pkgs.mkShell {
buildInputs = [ fmod.config.floco.settings.nodePackage ];
packages = [ fmod.config.floco.settings.nodePackage ];
shellHook = ''
${hooks.prepare "models/data"}
${hooks.prepare "src/models/data"}
ID=${pkg.built.tree}
currID=$(cat .floco/.node_modules_id 2> /dev/null)

View File

@ -1,13 +1,15 @@
"use client";
import { NixFunctions } from "@/components/NixFunctions/nixFunctions";
import NixFunctions from "@/components/NixFunctions";
import { usePageContext } from "@/components/pageContext";
export default function Home() {
const { pageState, setPageStateVariable } = usePageContext();
return (
// <PageContextProvider>
<NixFunctions
pageState={pageState}
setPageStateVariable={setPageStateVariable}
/>
// </PageContextProvider>
);
}

View File

@ -0,0 +1,107 @@
// import { DocsFrontmatter, getMdxMeta } from "@/components/ListGroup";
import {
docsDir,
extractHeadings,
getMdxSource,
mdxRenderOptions,
} from "@/utils";
import { Edit } from "@mui/icons-material";
import { Box, Button, Typography } from "@mui/material";
import fs from "fs";
// import "highlight.js/styles/github-dark-dimmed.css";
import "highlight.js/styles/github-dark.css";
import { MDXRemote } from "next-mdx-remote/rsc";
import Link from "next/link";
import path from "path";
export async function generateStaticParams() {
const files = fs.readdirSync(docsDir, {
recursive: true,
withFileTypes: true,
encoding: "utf-8",
});
const paths: { id: string[] }[] = files
.filter((f) => !f.isDirectory())
.map((f) => {
const dirname = path.relative(docsDir, f.path);
const filename = path.parse(f.name).name;
return {
id: [...dirname.split("/"), filename],
};
});
return paths;
}
interface TocProps {
mdxSource: Buffer;
}
const Toc = async (props: TocProps) => {
const { mdxSource } = props;
const headings = await extractHeadings(mdxSource);
return (
<Box
sx={{
order: 2,
width: "19rem",
py: 4,
px: 2,
}}
component={"aside"}
>
<Box
sx={{
position: "fixed",
top: 0,
pt: 4,
pl: 2,
}}
>
<Typography variant="subtitle1">Table of Contents</Typography>
<Box sx={{ display: "flex", flexDirection: "column" }}>
{headings.map((h, idx) => (
<Link key={idx} href={`#${h.id}`}>
<Button
fullWidth
variant="text"
sx={{
justifyContent: "start",
textTransform: "none",
color: "text.secondary",
pl: (h.level - 1) * 2,
}}
>
{h.value}
</Button>
</Link>
))}
</Box>
</Box>
</Box>
);
};
// Multiple versions of this page will be statically generated
// using the `params` returned by `generateStaticParams`
export default async function Page(props: { params: { id: string[] } }) {
const { mdxSource } = await getMdxSource(props.params.id);
return (
<>
<Box sx={{ display: "flex" }}>
<Toc mdxSource={mdxSource} />
<Box sx={{ order: 1, width: "60rem", marginInline: "auto", py: 2 }}>
<MDXRemote
options={{
parseFrontmatter: true,
mdxOptions: mdxRenderOptions,
}}
source={mdxSource}
/>
<Button sx={{ textTransform: "none", my: 4 }} startIcon={<Edit />}>
Edit source
</Button>
</Box>
</Box>
</>
);
}

View File

@ -0,0 +1,36 @@
import { NavSidebar } from "@/components/NavSidebar";
import { Box } from "@mui/material";
export default async function Page({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<aside
style={{
position: "fixed",
height: "100vh",
overflowY: "scroll",
overflowX: "hidden",
left: 0,
width: "19rem",
}}
>
<NavSidebar />
</aside>
<Box
sx={{
ml: "25em",
px: 2,
py: 4,
w: "100%",
bgcolor: "background.paper",
}}
>
{children}
</Box>
</div>
);
}

26
website/src/client.ts Normal file
View File

@ -0,0 +1,26 @@
import seedrandom from "seedrandom";
const date = new Date();
const dayOfYear = (date: Date) => {
const diff = Number(date) - Number(new Date(date.getFullYear(), 0, 0));
return Math.floor(diff / 1000 / 60 / 60 / 24);
};
const seed = dayOfYear(date).toString() + date.getFullYear().toString();
const rng = seedrandom(seed);
const FOTD_RND = rng();
type Config = {
init?: number;
};
export function pseudoRandomIntInclusive(
min: number,
max: number,
config?: Config
) {
min = Math.ceil(min);
max = Math.floor(max);
const randomNumber = config?.init || FOTD_RND;
return Math.floor(randomNumber * (max - min + 1) + min); // The maximum is inclusive and the minimum is inclusive
}

View File

@ -0,0 +1,73 @@
"use client";
import { ChevronRight, ExpandMore } from "@mui/icons-material";
import {
Collapse,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
} from "@mui/material";
import { ReactNode, useState } from "react";
const borderStyle = {
borderInlineStartStyle: "solid",
borderInlineStartWidth: "1px",
borderInlineStartColor: "#e4e4e7",
ml: 2,
pl: 2,
};
interface CollapseWrapperProps {
children: ReactNode;
name: string;
position: number;
}
export const CollapseWrapper = (props: CollapseWrapperProps) => {
const { children, name, position } = props;
const [expandedKeys, setExpanded] = useState<string[]>([]);
const isExpanded = expandedKeys.includes(name);
return (
<>
<ListItem
disablePadding
disableGutters
sx={{
...(position > 0 ? borderStyle : undefined),
}}
>
<ListItemButton
disableGutters
onClick={() => {
setExpanded((s) =>
isExpanded ? s.filter((n) => n !== name) : [...s, name]
);
}}
>
<ListItemText
primary={name}
primaryTypographyProps={{
sx: {
fontWeight: 700,
},
}}
/>
<ListItemIcon sx={{ minWidth: "40px" }}>
{isExpanded ? <ExpandMore /> : <ChevronRight />}
</ListItemIcon>
</ListItemButton>
</ListItem>
<Collapse in={isExpanded}>
<List
dense
disablePadding
sx={{
...(position > 0 ? borderStyle : undefined),
}}
>
{children}
</List>
</Collapse>
</>
);
};

View File

@ -0,0 +1,18 @@
"use client";
import { ListItemButton } from "@mui/material";
import { usePathname } from "next/navigation";
interface ListIndicatorProps {
currentPath: string;
children: React.ReactNode;
}
export const ListEntry = (props: ListIndicatorProps) => {
const { currentPath, children } = props;
const pathname = usePathname();
const selected = pathname === currentPath;
return (
<ListItemButton disableGutters selected={selected}>
{children}
</ListItemButton>
);
};

View File

@ -0,0 +1,47 @@
import { Group, getMdxMeta } from "@/utils";
import { ListItem, ListItemText } from "@mui/material";
import Link from "next/link";
import { CollapseWrapper } from "./ListCollapse";
import { ListEntry } from "./ListEntry";
interface ListGroupProps {
grp: Group;
position: number;
}
export const ListGroup = async (props: ListGroupProps) => {
const { grp, position } = props;
const sorted = Object.entries(grp).sort(([, v], [,]) =>
Array.isArray(v) ? 1 : -1
);
return sorted.map(async ([name, entry], idx) => {
if (Array.isArray(entry)) {
const matter = await getMdxMeta(entry);
return (
<Link key={`${idx}`} href={`/ref/${entry.join("/")}`}>
<ListItem
disablePadding
disableGutters
sx={{
ml: 2,
pl: 2,
borderInlineStartStyle: "solid",
borderInlineStartWidth: "1px",
borderInlineStartColor: "#e4e4e7",
}}
>
<ListEntry currentPath={`/ref/${entry.join("/")}`}>
<ListItemText primary={matter.compiled.frontmatter.title} />
</ListEntry>
</ListItem>
</Link>
);
} else {
return (
<CollapseWrapper key={idx} name={name} position={position}>
<ListGroup grp={entry} position={position + 1} />
</CollapseWrapper>
);
}
});
};

View File

@ -0,0 +1,33 @@
import { Group, generateStaticSidebarEntries, set } from "@/utils";
import { Box, List, Paper, Typography } from "@mui/material";
import { ListGroup } from "./ListGroup";
export const NavSidebar = async () => {
const list = await generateStaticSidebarEntries();
const grp: Group = list.reduce((acc, item) => {
set(acc, item.id.join("."), item.id);
return acc;
}, {});
return (
<Paper sx={{ height: "100%" }}>
<Box
sx={{
display: "flex",
width: "100%",
py: 2,
}}
>
<Typography variant="subtitle1" component="h3">
References
</Typography>
</Box>
<List
sx={{ width: "100%", bgcolor: "background.paper" }}
component="nav"
disablePadding
>
<ListGroup grp={grp} position={0} />
</List>
</Paper>
);
};

View File

@ -1,3 +1,4 @@
"use client";
import { PageState, normalizePath } from "@/models/internals";
import { DocItem } from "@/models/nix";
import { byType, pipe } from "@/queries";

View File

@ -0,0 +1,31 @@
"use client";
import { pseudoRandomIntInclusive } from "@/client";
import { useMemo, useState } from "react";
import Markdown from "react-markdown";
interface FunctionOfTheDayProps {
allPaths: { id: string[] }[];
}
export const FunctionOfTheDay = (props: FunctionOfTheDayProps) => {
const { allPaths } = props;
const todaysIdx = useMemo(
() => pseudoRandomIntInclusive(0, allPaths.length - 1),
[allPaths.length]
);
const [idx] = useState<number>(todaysIdx);
// redirect(`ref/${allPaths[idx].id.join("/")}`);
// const setFunctionOfTheDay = () => {
// setIdx(todaysIdx);
// };
return (
<>
<div>{idx}</div>
<div>{allPaths[idx].id.join("/")}</div>
<Markdown>{allPaths[idx].id.join("\n")}</Markdown>
</>
);
};

View File

@ -1,3 +1,4 @@
"use client";
import {
Button,
Card,

View File

@ -1,3 +1,4 @@
"use client";
import {
InitialPageState,
PageState,

View File

@ -0,0 +1,75 @@
"use client";
import React, { useState } from "react";
export default function SearchPage() {
const [query, setQuery] = React.useState("");
const [results, setResults] = React.useState([]);
React.useEffect(() => {
async function loadPagefind() {
// @ts-ignore
if (typeof window.pagefind === "undefined") {
try {
// @ts-ignore
window.pagefind = await import(
// @ts-expect-error pagefind.js generated after build
/* webpackIgnore: true */ "/pagefind/pagefind.js"
);
// @ts-ignore
console.log("setup:", window?.pagefind);
} catch (e) {
// @ts-ignore
window.pagefind = { search: () => ({ results: [] }) };
}
}
}
loadPagefind();
}, []);
async function handleSearch() {
// @ts-ignore
console.log("searching", window?.pagefind);
// @ts-ignore
if (window.pagefind) {
// @ts-ignore
const search = await window.pagefind.search(query);
setResults(search.results);
}
}
console.log("result", { results, query });
return (
<div>
<input
type="text"
placeholder="Search..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onInput={handleSearch}
/>
<div id="results">
{results.map((result, index) => (
// @ts-ignore
<Result key={result.id} result={result} />
))}
</div>
</div>
);
}
function Result({ result }: any) {
const [data, setData] = useState(null);
React.useEffect(() => {
async function fetchData() {
const data = await result.data();
setData(data);
}
fetchData();
}, [result]);
if (!data) return null;
// @ts-ignore
return <div>{`${data.url}`}</div>;
}

View File

@ -0,0 +1,73 @@
// app/ThemeRegistry.tsx
"use client";
import { darkThemeOptions, lightThemeOptions } from "@/styles/theme";
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import { useMediaQuery } from "@mui/material";
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { useServerInsertedHTML } from "next/navigation";
import React, { ReactNode } from "react";
const lightTheme = createTheme(lightThemeOptions);
const darkTheme = createTheme(darkThemeOptions);
// This implementation is from emotion-js
// https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902
export default function ThemeRegistry(props: {
children: ReactNode;
options: any;
}) {
const userPrefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)");
const { options, children } = props;
const [{ cache, flush }] = React.useState(() => {
const cache = createCache(options);
cache.compat = true;
const prevInsert = cache.insert;
let inserted: string[] = [];
cache.insert = (...args) => {
const serialized = args[1];
if (cache.inserted[serialized.name] === undefined) {
inserted.push(serialized.name);
}
return prevInsert(...args);
};
const flush = () => {
const prevInserted = inserted;
inserted = [];
return prevInserted;
};
return { cache, flush };
});
useServerInsertedHTML(() => {
const names = flush();
if (names.length === 0) {
return null;
}
let styles = "";
for (const name of names) {
styles += cache.inserted[name];
}
return (
<style
key={cache.key}
data-emotion={`${cache.key} ${names.join(" ")}`}
dangerouslySetInnerHTML={{
__html: options.prepend ? `@layer emotion {${styles}}` : styles,
}}
/>
);
});
return (
<CacheProvider value={cache}>
<ThemeProvider theme={userPrefersDarkmode ? darkTheme : lightTheme}>
<CssBaseline />
{children}
</ThemeProvider>
</CacheProvider>
);
}

View File

@ -0,0 +1,16 @@
---
title: builtins.break
editUrl: false
description: builtins.break
sidebar:
badge:
text: Builtin
variant: note
order: 0
---
In debug mode (enabled using `--debugger`), pause Nix expression evaluation and enter the REPL.
Otherwise, return the argument `v`.

View File

@ -0,0 +1,41 @@
---
title: lib.attrsets.attrByPath
editUrl: https://www.github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments/lib/attrsets.nix#L45C5
description: lib.attrsets.attrByPath
sidebar:
order: 7
---
Return an attribute from nested attribute sets.
# Example
```nix
x = { a = { b = 3; }; }
# ["a" "b"] is equivalent to x.a.b
# 6 is a default value to return if the path does not exist in attrset
attrByPath ["a" "b"] 6 x
=> 3
attrByPath ["z" "z"] 6 x
=> 6
```
# Type
```
attrByPath :: [String] -> Any -> AttrSet -> Any
```
# Arguments
- [attrPath] A list of strings representing the attribute path to return from `set`
- [default] Default value if `attrPath` does not resolve to an existing value
- [set] The nested attribute set to select values from
# Aliases
- [lib.attrByPath](/reference/lib/lib-attrbypath)

View File

@ -0,0 +1,24 @@
---
title: lib.attrsets.attrNames
editUrl: false
description: lib.attrsets.attrNames
sidebar:
badge:
text: Builtin
variant: note
order: 7
---
Return the names of the attributes in the set *set* in an
alphabetically sorted list. For instance, `builtins.attrNames { y
= 1; x = "foo"; }` evaluates to `[ "x" "y" ]`.
# Aliases
- [builtins.attrNames](/reference/builtins/builtins-attrnames)
- [lib.attrNames](/reference/lib/lib-attrnames)

View File

@ -0,0 +1,13 @@
---
title: pkgs.buildLinux
editUrl: https://www.github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments/pkgs/top-level/linux-kernels.nix#L716C16
description: pkgs.buildLinux
sidebar:
order: 8
---
<a href="https://www.github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments/pkgs/top-level/linux-kernels.nix#L716C16">Contribute Now!</a>

View File

@ -0,0 +1,10 @@
---
title: pkgs.stdenv.mkDerivation
editUrl: https://www.github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments/pkgs/stdenv/generic/make-derivation.nix#L548C3
description: pkgs.stdenv.mkDerivation
sidebar:
order: 7
---
<a href="https://www.github.com/hsjobeki/nixpkgs/tree/migrate-doc-comments/pkgs/stdenv/generic/make-derivation.nix#L548C3">Contribute Now!</a>

View File

@ -1,19 +1,9 @@
import { MetaData } from "../nix";
import nixBuiltins from "./builtins.json";
import nixLibs from "./lib.json";
import nixTrivialBuilders from "./trivial-builders.json";
// const mock = {
// id: "mock",
// category: "mock",
// name: "mock",
// fn_type: null,
// description: "No data yet in dev mode",
// example: null,
// line: null,
// };
import nixTrivialBuilders from "./build_support.json" assert { type: "json" };
import nixBuiltins from "./builtins.json" assert { type: "json" };
import nixLibs from "./lib.json" assert { type: "json" };
export const data: MetaData = [
// mock,
...(nixLibs as MetaData),
...(nixBuiltins as MetaData),
...(nixTrivialBuilders as MetaData),

160
website/src/utils.ts Normal file
View File

@ -0,0 +1,160 @@
import fs from "fs";
import bash from "highlight.js/lib/languages/bash";
import haskell from "highlight.js/lib/languages/haskell";
import nix from "highlight.js/lib/languages/nix";
import "highlight.js/styles/github-dark-dimmed.css";
import { SerializeOptions } from "next-mdx-remote/dist/types";
import { CompileMDXResult, compileMDX } from "next-mdx-remote/rsc";
import { parse, serialize } from "parse5";
import path from "path";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeHighlight from "rehype-highlight";
import rehypeSlug from "rehype-slug";
import rehypeStringify from "rehype-stringify";
// @ts-expect-error: Could not find type declaration file.
import remarkHeadingId from "remark-heading-id";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import { unified } from "unified";
/**
* Function to generate a set from a path in lodash style
* ```
* set({}, "foo.bar", "value");
* ->
* {
* foo: {
* bar: "value"
* }
* }
* ```
* @param obj Object in which to insert records
* @param path Path/String in "." seperated form
* @param value Any value to insert.
*/
export function set(obj: Record<string, any>, path: string, value: any): void {
let schema = obj; // a moving reference to internal objects within obj
const pList = path.split(".");
const len = pList.length;
for (let i = 0; i < len - 1; i++) {
const elem = pList[i];
if (!schema[elem]) schema[elem] = {};
schema = schema[elem];
}
schema[pList[len - 1]] = value;
}
export const docsDir = path.join(process.cwd(), "src/docs");
export async function generateStaticSidebarEntries() {
const files = fs.readdirSync(docsDir, {
recursive: true,
withFileTypes: true,
encoding: "utf-8",
});
const paths: { id: string[] }[] = await Promise.all(
files
.filter((f) => !f.isDirectory())
.map(async (f) => {
const dirname = path.relative(docsDir, f.path);
const filename = path.parse(f.name).name;
const id = [...dirname.split("/"), filename];
return {
id,
};
})
);
return paths;
}
export type DocsFrontmatter = { title: String };
export const getMdxMeta = async (
parts: string[]
): Promise<{
mdxSource: string;
compiled: CompileMDXResult<DocsFrontmatter>;
}> => {
const location = path.join(docsDir, `${parts.map(decodeURI).join("/")}.md`);
const source = fs.readFileSync(location, { encoding: "utf-8" });
return {
mdxSource: source,
compiled: await compileMDX({
source,
options: {
parseFrontmatter: true,
mdxOptions: {
format: "md",
},
},
}),
};
};
export const getMdxSource = async (parts: string[]) => {
const decoded = parts.map(decodeURI).join("/");
const location = path.join(docsDir, `${decoded}.md`);
const source = fs.readFileSync(location);
return {
mdxSource: source,
};
};
export type Group = {
[name: string]: string[] | Group;
};
type Heading = {
level: number;
value: string;
id: string;
};
export const extractHeadings = async (content: Buffer): Promise<Heading[]> => {
const processor = unified()
.use(remarkParse)
.use(remarkHeadingId)
.use(remarkRehype)
.use(rehypeSlug)
.use(rehypeStringify);
const result = await processor.process(content);
const document = parse(result.value.toString());
const headings: Heading[] = [];
const processNode = (node: any) => {
if (
node?.tagName &&
["h1", "h2", "h3", "h4", "h5", "h6"].some((hN) => node?.tagName === hN)
) {
const nm = node.tagName.slice(1);
const level = parseInt(nm, 10);
const id = node?.attrs?.find((attr: any) => attr?.name === "id")?.value;
const value: string = serialize(node);
headings.push({ level, id, value });
}
if (node.childNodes) {
node.childNodes.forEach(processNode);
}
};
processNode(document);
return headings as Heading[];
};
export const mdxRenderOptions: SerializeOptions["mdxOptions"] = {
rehypePlugins: [
[
rehypeHighlight,
{
detect: true,
languages: { nix, haskell, bash, default: nix },
},
],
[rehypeSlug, {}],
[rehypeAutolinkHeadings, { behavior: "wrap" }],
],
remarkPlugins: [remarkHeadingId],
format: "md",
} as SerializeOptions["mdxOptions"];

View File

@ -1,4 +1,5 @@
{
"compilerOptions": {
"target": "es6",
"lib": [
@ -31,8 +32,8 @@
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"src/**/*.ts",
"src/**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [