diff --git a/app/lib/md.server.ts b/app/lib/md.server.ts index 6b14ff25..016a2351 100644 --- a/app/lib/md.server.ts +++ b/app/lib/md.server.ts @@ -1,19 +1,16 @@ /*! - * Forked from https://github.com/ryanflorence/md/blob/master/index.ts - * * Adapted from * - ggoodman/nostalgie * - MIT https://github.com/ggoodman/nostalgie/blob/45f3f6356684287a214dab667064ec9776def933/LICENSE * - https://github.com/ggoodman/nostalgie/blob/45f3f6356684287a214dab667064ec9776def933/src/worker/mdxCompiler.ts + * - remix/remix-website */ -import { getHighlighter, toShikiTheme } from "shiki"; -import rangeParser from "parse-numeric-range"; import parseFrontMatter from "front-matter"; -import type * as Hast from "hast"; +import themeJson from "../../data/base16.json"; +import { getHighlighter } from "shiki"; +import { transformerCopyButton } from '@rehype-pretty/transformers'; import type * as Unist from "unist"; -import type * as Shiki from "shiki"; import type * as Unified from "unified"; -import themeJson from "../../data/base16.json"; export interface ProcessorOptions { resolveHref?(href: string): string; @@ -40,6 +37,7 @@ export async function getProcessor(options?: ProcessorOptions) { { default: rehypeSlug }, { default: rehypeStringify }, { default: rehypeAutolinkHeadings }, + { default: rehypePrettyCode }, plugins, ] = await Promise.all([ import("unified"), @@ -49,20 +47,40 @@ export async function getProcessor(options?: ProcessorOptions) { import("rehype-slug"), import("rehype-stringify"), import("rehype-autolink-headings"), + import("rehype-pretty-code"), loadPlugins(), ]); + // The theme actually stores #FFFF${base-16-color-id} because vscode-textmate + // requires colors to be valid hex codes, if they aren't, it changes them to a + // default, so this is a mega hack to trick it. + const themeString = JSON.stringify(themeJson).replace(/#FFFF(.{2})/g, "var(--base$1)"); + return unified() .use(remarkParse) .use(plugins.stripLinkExtPlugin, options) - .use(plugins.remarkCodeBlocksShiki, options) .use(remarkGfm) .use(remarkRehype, { allowDangerousHtml: true }) .use(rehypeStringify, { allowDangerousHtml: true }) .use(rehypeSlug) - .use(rehypeAutolinkHeadings); + .use(rehypeAutolinkHeadings) + .use(rehypePrettyCode, { + keepBackground: false, + theme: JSON.parse(themeString), + filterMetaString: (str) => str.replace(/lines=\[([^]*)\]/g, '{$1}').replace(/filename=([^ ]*)/g, 'title="$1"'), + transformers: [ + transformerCopyButton({ + visibility: 'always', + feedbackDuration: 3_000, + copyIcon: "data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='rgb(9, 9, 11)' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5'%3E%3Crect width='13' height='13' x='9' y='9' rx='2' ry='2' vector-effect='non-scaling-stroke'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E", + successIcon: "data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='none' stroke='rgb(9, 9, 11)' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5'%3E%3Cpath d='M20 6 9 17l-5-5' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E", + }), + ], + }); } + + type InternalPlugin< Input extends string | Unist.Node | undefined, Output, @@ -96,236 +114,13 @@ export async function loadPlugins() { }; }; - const remarkCodeBlocksShiki: InternalPlugin< - UnistNode.Root, - UnistNode.Root - > = (options) => { - let theme: ReturnType; - let highlighterPromise: ReturnType; - - return async function transformer(tree: UnistNode.Root) { - theme = theme || toShikiTheme(themeJson as any); - highlighterPromise = - highlighterPromise || getHighlighter({ themes: [theme] }); - let highlighter = await highlighterPromise; - let fgColor = convertFakeHexToCustomProp( - highlighter.getForegroundColor(theme.name) || "", - ); - let langs: Shiki.Lang[] = [ - "js", - "json", - "jsx", - "ts", - "tsx", - "markdown", - "shellscript", - "html", - "css", - "diff", - "mdx", - "prisma", - ]; - let langSet = new Set(langs); - let transformTasks: Array<() => Promise> = []; - - visit(tree, "code", (node) => { - if ( - !node.lang || - !node.value || - !langSet.has(node.lang as Shiki.Lang) - ) { - return; - } - - if (node.lang === "js") node.lang = "javascript"; - if (node.lang === "ts") node.lang = "typescript"; - let language = node.lang; - let code = node.value; - let { - addedLines, - highlightLines, - nodeProperties, - removedLines, - startingLineNumber, - usesLineNumbers, - } = getCodeBlockMeta(); - - transformTasks.push(highlightNodes); - return SKIP; - - async function highlightNodes() { - let tokens = getThemedTokens({ code, language }); - let children = tokens.map( - (lineTokens, zeroBasedLineNumber): Hast.Element => { - let children = lineTokens.map( - (token): Hast.Text | Hast.Element => { - let color = convertFakeHexToCustomProp(token.color || ""); - let content: Hast.Text = { - type: "text", - // Do not escape the _actual_ content - value: token.content, - }; - - return color && color !== fgColor - ? { - type: "element", - tagName: "span", - properties: { - style: `color: ${htmlEscape(color)}`, - }, - children: [content], - } - : content; - }, - ); - - children.push({ - type: "text", - value: "\n", - }); - - let isDiff = addedLines.length > 0 || removedLines.length > 0; - let diffLineNumber = startingLineNumber - 1; - let lineNumber = zeroBasedLineNumber + startingLineNumber; - let highlightLine = highlightLines?.includes(lineNumber); - let removeLine = removedLines.includes(lineNumber); - let addLine = addedLines.includes(lineNumber); - if (!removeLine) { - diffLineNumber++; - } - - return { - type: "element", - tagName: "span", - properties: { - className: "codeblock-line", - dataHighlight: highlightLine ? "true" : undefined, - dataLineNumber: usesLineNumbers ? lineNumber : undefined, - dataAdd: isDiff ? addLine : undefined, - dataRemove: isDiff ? removeLine : undefined, - dataDiffLineNumber: isDiff ? diffLineNumber : undefined, - }, - children, - }; - }, - ); - - let nodeValue = { - type: "element", - tagName: "pre", - properties: { - ...nodeProperties, - dataLineNumbers: usesLineNumbers ? "true" : "false", - dataLang: htmlEscape(language), - style: `color: ${htmlEscape(fgColor)};`, - }, - children: [ - { - type: "element", - tagName: "code", - children, - }, - ], - }; - - let data = node.data ?? {}; - (node as any).type = "element"; - (node as any).tagName = "div"; - let properties = - data.hProperties && typeof data.hProperties === "object" - ? data.hProperties - : {}; - data.hProperties = { - ...properties, - dataCodeBlock: "", - ...nodeProperties, - dataLineNumbers: usesLineNumbers ? "true" : "false", - dataLang: htmlEscape(language), - }; - data.hChildren = [nodeValue]; - node.data = data; - } - - function getCodeBlockMeta() { - // TODO: figure out how this is ever an array? - let meta = Array.isArray(node.meta) ? node.meta[0] : node.meta; - - let metaParams = new URLSearchParams(); - if (meta) { - let linesHighlightsMetaShorthand = meta.match(/^\[(.+)\]$/); - if (linesHighlightsMetaShorthand) { - metaParams.set("lines", linesHighlightsMetaShorthand[0]); - } else { - metaParams = new URLSearchParams(meta.split(/\s+/).join("&")); - } - } - - let addedLines = parseLineHighlights(metaParams.get("add")); - let removedLines = parseLineHighlights(metaParams.get("remove")); - let highlightLines = parseLineHighlights(metaParams.get("lines")); - let startValNum = metaParams.has("start") - ? Number(metaParams.get("start")) - : 1; - let startingLineNumber = Number.isFinite(startValNum) - ? startValNum - : 1; - let usesLineNumbers = !metaParams.has("nonumber"); - - let nodeProperties: { [key: string]: string } = {}; - metaParams.forEach((val, key) => { - if (key === "lines") return; - nodeProperties[`data-${key}`] = val; - }); - - return { - addedLines, - highlightLines, - nodeProperties, - removedLines, - startingLineNumber, - usesLineNumbers, - }; - } - }); - - await Promise.all(transformTasks.map((exec) => exec())); - - function getThemedTokens({ - code, - language, - }: { - code: string; - language: Shiki.Lang; - }) { - return highlighter.codeToThemedTokens(code, language, theme.name, { - includeExplanation: false, - }); - } - }; - }; - return { stripLinkExtPlugin, - remarkCodeBlocksShiki, }; } //////////////////////////////////////////////////////////////////////////////// -function parseLineHighlights(param: string | null) { - if (!param) return []; - let range = param.match(/^\[(.+)\]$/); - if (!range) return []; - return rangeParser(range[1]); -} - -// The theme actually stores #FFFF${base-16-color-id} because vscode-textmate -// requires colors to be valid hex codes, if they aren't, it changes them to a -// default, so this is a mega hack to trick it. -function convertFakeHexToCustomProp(color: string) { - return color.replace(/^#FFFF(.+)/, "var(--base$1)"); -} - function isRelativeUrl(test: string) { // Probably fragile but should work well enough. // It would be nice if the consumer could provide a baseURI we could do @@ -431,4 +226,4 @@ export namespace UnistNode { type: "text"; value: string; } -} +} \ No newline at end of file diff --git a/app/routes/docs.$lang.$ref.tsx b/app/routes/docs.$lang.$ref.tsx index 01979ef6..a0ae85fe 100644 --- a/app/routes/docs.$lang.$ref.tsx +++ b/app/routes/docs.$lang.$ref.tsx @@ -16,7 +16,6 @@ import type { LoaderFunctionArgs, HeadersFunction } from "@remix-run/node"; import cx from "clsx"; import { DocSearch } from "~/ui/docsearch"; import { useNavigate } from "react-router-dom"; - import "~/styles/docs.css"; import { Wordmark } from "~/ui/logo"; import { DetailsMenu, DetailsPopup } from "~/ui/details-menu"; @@ -102,7 +101,6 @@ export default function DocsLayout() { }, [location]); let docsContainer = React.useRef(null); - useCodeBlockCopyButton(docsContainer); return (
@@ -884,64 +882,3 @@ function useIsActivePath(to: string) { let match = matchPath(pathname + "/*", location.pathname); return Boolean(match); } - -function useCodeBlockCopyButton(ref: React.RefObject) { - let location = useLocation(); - React.useEffect(() => { - let container = ref.current; - if (!container) return; - - let codeBlocks = container.querySelectorAll( - "[data-code-block][data-lang]:not([data-nocopy])", - ); - let buttons = new Map< - HTMLButtonElement, - { listener: (event: MouseEvent) => void; to: number } - >(); - - for (let codeBlock of codeBlocks) { - let button = document.createElement("button"); - let label = document.createElement("span"); - button.type = "button"; - button.dataset.codeBlockCopy = ""; - button.addEventListener("click", listener); - - label.textContent = "Copy code to clipboard"; - label.classList.add("sr-only"); - button.appendChild(label); - codeBlock.appendChild(button); - buttons.set(button, { listener, to: -1 }); - - function listener(event: MouseEvent) { - event.preventDefault(); - let pre = codeBlock.querySelector("pre"); - let text = pre?.textContent; - if (!text) return; - navigator.clipboard - .writeText(text) - .then(() => { - button.dataset.copied = "true"; - let to = window.setTimeout(() => { - window.clearTimeout(to); - if (button) { - button.dataset.copied = undefined; - } - }, 3000); - if (buttons.has(button)) { - buttons.get(button)!.to = to; - } - }) - .catch((error) => { - console.error(error); - }); - } - } - return () => { - for (let [button, props] of buttons) { - button.removeEventListener("click", props.listener); - button.parentElement?.removeChild(button); - window.clearTimeout(props.to); - } - }; - }, [ref, location.pathname]); -} diff --git a/app/styles/docs.css b/app/styles/docs.css index 497f7ff4..26ea9dd2 100644 --- a/app/styles/docs.css +++ b/app/styles/docs.css @@ -287,7 +287,7 @@ & kbd, & pre { word-wrap: normal; - @apply overflow-auto rounded-lg p-3 text-sm leading-none md:p-4; + @apply overflow-auto rounded-lg py-3 text-sm leading-none md:py-4 px-0; } & pre { @@ -309,7 +309,7 @@ @apply m-0 whitespace-pre break-normal border-0 bg-transparent p-0; } - & .codeblock-line { + & span[data-line] { @apply relative block pr-4; } @@ -345,27 +345,10 @@ border-radius: initial; } - & [data-code-block] { + & [data-rehype-pretty-code] { position: relative; } - & [data-code-block-copy] { - @apply absolute right-4 top-[1.125rem] h-5 w-5 cursor-pointer bg-gray-500 opacity-0 hover:bg-black dark:bg-gray-400 dark:hover:bg-gray-100; - mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='9' width='13' height='13' rx='2' ry='2' vector-effect='non-scaling-stroke'%3E%3C/rect%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' vector-effect='non-scaling-stroke'%3E%3C/path%3E%3C/svg%3E"); - mask-size: cover; - } - - & [data-code-block]:hover [data-code-block-copy], - & [data-code-block-copy]:focus { - @apply opacity-100; - } - - & [data-code-block-copy][data-copied="true"] { - @apply bg-black opacity-100 dark:bg-gray-100; - mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12' vector-effect='non-scaling-stroke'%3E%3C/polyline%3E%3C/svg%3E"); - mask-size: cover; - } - & pre[data-line-numbers="true"]:not([data-lang="bash"], [data-lang="sh"]) { padding-left: 0rem; padding-right: 0rem; @@ -414,31 +397,6 @@ @apply text-red-700 dark:text-red-400; } - & pre[data-filename]::before { - @apply mb-4 block border-b border-gray-100 bg-no-repeat pb-4 pl-11 dark:border-gray-800; - content: attr(data-filename); - } - - & pre[data-filename]::after { - @apply absolute left-[1.125rem] top-5 h-4 w-4 bg-gray-400 dark:bg-gray-500; - content: ""; - mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z' vector-effect='non-scaling-stroke'%3E%3C/path%3E%3Cpolyline points='13 2 13 9 20 9' vector-effect='non-scaling-stroke'%3E%3C/polyline%3E%3C/svg%3E"); - mask-size: cover; - } - - & .codeblock-line[data-highlight="true"]::after { - content: " "; - @apply pointer-events-none absolute left-0 top-0 w-full bg-green-400 opacity-10 dark:bg-green-300 dark:opacity-20; - } - - & pre[data-good] .codeblock-line[data-highlight="true"]::after { - @apply bg-green-700 dark:bg-green-400; - } - - & pre[data-bad] .codeblock-line[data-highlight="true"]::after { - @apply bg-red-700 dark:bg-red-400; - } - /*****************************************************************************/ /* tables */ @@ -577,3 +535,86 @@ @apply rounded-sm bg-gray-100/50 px-1.5 pb-0.5 pt-px text-xs text-gray-600 group-hover:text-gray-100 dark:bg-gray-800/50 dark:text-gray-300; } } + +/* +* Rehype Pretty Code +*/ + +code { + counter-reset: line; + + & [data-highlighted-line]::after { + content: " "; + @apply pointer-events-none absolute left-0 top-0 w-full bg-green-400 opacity-10 dark:bg-green-300 dark:opacity-20; + } + + & [data-good] [data-highlighted-line] { + @apply bg-green-700 dark:bg-green-400; + } + + & [data-bad] [data-highlighted-line] { + @apply bg-red-700 dark:bg-red-400; + } +} + +code > [data-line]::before { + counter-increment: line; + content: counter(line); + + /* Other styling */ + display: inline-block; + width: 2rem; + margin-right: 1.5rem; + text-align: right; + color: gray; + padding-left: 0; +} + +code[data-line-numbers-max-digits="2"] > [data-line]::before { + width: 2rem; +} + +code[data-line-numbers-max-digits="3"] > [data-line]::before { + width: 3rem; +} + +.rehype-pretty-copy { + @apply absolute !mr-3 !mt-[-5px] cursor-pointer opacity-50 !h-7 !w-7 shadow-sm !rounded-md; + background-color: hsl(var(--background)); + border-color: hsl(var(--input)); + border-width: 1px; + align-items: center; + justify-content: center; + + & .ready { + @apply !h-4 !w-4 !m-0; + } + + & .success { + @apply !h-4 !w-4 !m-0; + } +} + +.rehype-pretty-copy:hover { + @apply opacity-100 bg-gray-100; +} + +figure[data-rehype-pretty-code-figure]:has(> [data-rehype-pretty-code-title]) > pre > code button.rehype-pretty-copy { + @apply !mt-0; +} + +[data-rehype-pretty-code-title] { + @apply rounded-t-lg text-sm border-t border-x border-gray-100 px-3 pl-11 py-4; +} + +figure[data-rehype-pretty-code-figure]:has(> [data-rehype-pretty-code-title]) + pre { + @apply !rounded-t-none mt-0; +} + +[data-rehype-pretty-code-title]::before { + @apply absolute ml-[-1.5rem] mt-[0.125rem] h-4 w-4 bg-gray-400; + content: ""; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z' vector-effect='non-scaling-stroke'%3E%3C/path%3E%3Cpolyline points='13 2 13 9 20 9' vector-effect='non-scaling-stroke'%3E%3C/polyline%3E%3C/svg%3E"); + mask-size: cover; +} diff --git a/package-lock.json b/package-lock.json index 1821fe97..da1d83c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@docsearch/css": "^3.5.2", "@docsearch/react": "^3.5.2", + "@rehype-pretty/transformers": "^0.13.2", "@remix-run/express": "^2.9.1", "@remix-run/node": "^2.9.1", "@remix-run/react": "^2.9.1", @@ -42,6 +43,7 @@ "react-dom": "^18.2.0", "react-is": "^18.2.0", "rehype-autolink-headings": "^7.1.0", + "rehype-pretty-code": "^0.13.2", "rehype-slug": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", @@ -51,7 +53,7 @@ "remix": "^2.9.1", "satori": "^0.10.11", "semver": "^7.5.4", - "shiki": "^0.14.7", + "shiki": "^1.6.0", "source-map-support": "^0.5.21", "svg2img": "^1.0.0-beta.2", "tabbable": "^6.2.0", @@ -2822,6 +2824,14 @@ "node": ">=14" } }, + "node_modules/@rehype-pretty/transformers": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@rehype-pretty/transformers/-/transformers-0.13.2.tgz", + "integrity": "sha512-p2ciQSwqy5Ip8aNUa9q6rdS/hJZXrxHYYfDVOHvKOsBu3t9HDmQ65YX6r9Qbl19vi160OAxmGF7MIoCRDJrRhg==", + "engines": { + "node": ">=18" + } + }, "node_modules/@remix-run/dev": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.9.1.tgz", @@ -3488,6 +3498,11 @@ "integrity": "sha512-2/U3GXA6YiPYQDLGwtGlnNgKYBSwCFIHf8Y9LUY5VATHdtbLlU0Y1R3QoBnT0aB4qv/BEiVVsj7LJXoQCgJ2vA==", "dev": true }, + "node_modules/@shikijs/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.0.tgz", + "integrity": "sha512-NIEAi5U5R7BLkbW1pG/ZKu3eb1lzc3/+jD0lFsuxMT7zjaf9bbNwdNyMr7zh/Zl8EXQtQ+MYBAt5G+JLu+5DlA==" + }, "node_modules/@shuding/opentype.js": { "version": "1.4.0-beta.0", "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", @@ -4918,11 +4933,6 @@ "node": ">=8" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8702,6 +8712,75 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-html/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/hast-util-from-html/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", @@ -10056,7 +10135,8 @@ "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true }, "node_modules/jsonfile": { "version": "6.1.0", @@ -17478,6 +17558,55 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" }, + "node_modules/rehype-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.0.tgz", + "integrity": "sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/rehype-pretty-code": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.13.2.tgz", + "integrity": "sha512-F+PaFMscfJOcSHcR2b//+hk/0jT56hmGDqXcVD6VC9j0CUSGiqv8YxaWUyhR7qEIRRSbzAVxx+0uxzk+akXs+w==", + "dependencies": { + "@types/hast": "^3.0.4", + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "rehype-parse": "^9.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "shiki": "^1.3.0" + } + }, + "node_modules/rehype-pretty-code/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/rehype-slug": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", @@ -18881,14 +19010,11 @@ } }, "node_modules/shiki": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.0.tgz", + "integrity": "sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==", "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" + "@shikijs/core": "1.6.0" } }, "node_modules/side-channel": { @@ -21892,16 +22018,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" - }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index cbd77de2..06cd1103 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "@docsearch/css": "^3.5.2", "@docsearch/react": "^3.5.2", + "@rehype-pretty/transformers": "^0.13.2", "@remix-run/express": "^2.9.1", "@remix-run/node": "^2.9.1", "@remix-run/react": "^2.9.1", @@ -54,6 +55,7 @@ "react-dom": "^18.2.0", "react-is": "^18.2.0", "rehype-autolink-headings": "^7.1.0", + "rehype-pretty-code": "^0.13.2", "rehype-slug": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", @@ -63,7 +65,7 @@ "remix": "^2.9.1", "satori": "^0.10.11", "semver": "^7.5.4", - "shiki": "^0.14.7", + "shiki": "^1.6.0", "source-map-support": "^0.5.21", "svg2img": "^1.0.0-beta.2", "tabbable": "^6.2.0",