diff --git a/_config.yml b/_config.yml index 0eee6dfae2..f5e8eebb00 100644 --- a/_config.yml +++ b/_config.yml @@ -81,7 +81,6 @@ available_versions: - v0.62 - v0.63 collections: - docs: { output: true } community-posts: { output: true } defaults: - { diff --git a/astro.config.mjs b/astro.config.mjs index e762ba5cf6..ee119ba7b9 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,5 +1,33 @@ // @ts-check -import { defineConfig } from 'astro/config'; +import { defineConfig } from "astro/config"; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import { noopMarkdownProcessor } from "./src/lib/markdown/noopMarkdownProcessor"; // https://astro.build/config -export default defineConfig({}); +export default defineConfig({ + vite: { + plugins: [ + viteStaticCopy({ + targets: [ + { + // TODO: What other extensions? + src: "_docs/**/*.{jpg,png}", + dest: "docs", + rename: { stripBase: 1 }, // strips `_docs/` + }, + ], + }), + ], + }, + markdown: { + // Use `getMarkdownRenderer` instead for faster dev builds. There are + // thousands of docs md files, and astro processes the markdown for all of + // them even if you don't even navigate to a markdown-generated page. Also, + // docs md files use liquid syntax which must be processed before the + // markdown is converted to html. Processing liquid as part of a satteri + // plugin would be unnecessarily and frustratingly slow for dev builds + // since it would need to resolve all the includes for thousands of + // markdown files. + processor: noopMarkdownProcessor, + }, +}); diff --git a/bun.lock b/bun.lock index 254dbdbf03..607261e08f 100644 --- a/bun.lock +++ b/bun.lock @@ -44,6 +44,7 @@ "stylelint": "^14.9.0", "stylelint-config-standard-scss": "^4.0.0", "stylelint-order": "^5.0.0", + "vite-plugin-static-copy": "^4.1.1", "wait-on": "^9.0.10", }, }, @@ -514,6 +515,8 @@ "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], @@ -564,7 +567,7 @@ "chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="], - "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], @@ -974,6 +977,8 @@ "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], "is-buffer": ["is-buffer@2.0.5", "", {}, "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="], @@ -1348,6 +1353,8 @@ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-map": ["p-map@7.0.5", "", {}, "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA=="], + "p-queue": ["p-queue@9.3.1", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-POWdiIPmsUPGwb4FeQ4OBg46aqmcInSWe45CKDsGHiOBiVQM9chqfQTuqhuTzcg2Vz9faTI65at0KkVyVEiCHw=="], "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="], @@ -1466,7 +1473,7 @@ "readable-stream": ["readable-stream@1.0.34", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg=="], - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], @@ -1800,6 +1807,8 @@ "vite": ["vite@8.1.4", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.16", "rolldown": "~1.1.4", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ=="], + "vite-plugin-static-copy": ["vite-plugin-static-copy@4.1.1", "", { "dependencies": { "chokidar": "^3.6.0", "p-map": "^7.0.4", "picocolors": "^1.1.1", "tinyglobby": "^0.2.17" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-GrlA8YklrAfSyxJ4M3fdQLOo9oNkp56IM9FYgX/WtEgeIFkPwhu4wzpufBCIuNKCa6Fn77FkRdYxkHqV0FwjAw=="], + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], "volar-service-css": ["volar-service-css@0.0.71", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw=="], @@ -1946,6 +1955,8 @@ "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], @@ -2096,6 +2107,8 @@ "read-pkg-up/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="], + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "remark-parse/mdast-util-from-markdown": ["mdast-util-from-markdown@1.3.1", "", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "decode-named-character-reference": "^1.0.0", "mdast-util-to-string": "^3.1.0", "micromark": "^3.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-decode-string": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "unist-util-stringify-position": "^3.0.0", "uvu": "^0.5.0" } }, "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww=="], "retext-smartypants/unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], diff --git a/docs/sitemap.xml b/docs/sitemap.xml deleted file mode 100644 index f749f15d30..0000000000 --- a/docs/sitemap.xml +++ /dev/null @@ -1,18 +0,0 @@ ---- -layout: null -title: Docs Sitemap -permalink: /docs/sitemap.xml ---- - - -{% assign latest_docs = site.docs | where_exp: "doc", "doc.url contains '/docs/latest/'" | sort: "url" %} -{% for doc in latest_docs %} - {% assign canonical = doc.canonical | default: "" %} - {% unless canonical contains "http" or doc.url contains ".json" %} - {% assign loc = doc.url | replace: "/index", "/" | replace: "/.html", "/" | replace: ".html", "" %} - - https://www.metabase.com{{ loc | xml_escape }} - - {% endunless %} -{% endfor %} - diff --git a/package.json b/package.json index cf2019e27f..1774a62efc 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "stylelint": "^14.9.0", "stylelint-config-standard-scss": "^4.0.0", "stylelint-order": "^5.0.0", + "vite-plugin-static-copy": "^4.1.1", "wait-on": "^9.0.10" }, "resolutions": { diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000000..44aa8e6d69 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const DOCS_SRC_ROOT = "_docs"; diff --git a/src/content.config.ts b/src/content.config.ts index 3a204e761c..6087964841 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -1,10 +1,12 @@ import { defineCollection } from "astro:content"; import { glob } from "astro/loaders"; +import { DOCS_SRC_ROOT } from "./constants"; -const examples = defineCollection({ +const docs = defineCollection({ loader: glob({ - pattern: ["src/example-collection/**/*.md"], + pattern: ["**/*.md", "!**/embedding/sdk/api/snippets/**"], + base: DOCS_SRC_ROOT, }), }); -export const collections = { examples }; +export const collections = { docs }; diff --git a/src/lib/liquid/liquidRenderer.ts b/src/lib/liquid/liquidRenderer.ts index deed1d438b..da601e3677 100644 --- a/src/lib/liquid/liquidRenderer.ts +++ b/src/lib/liquid/liquidRenderer.ts @@ -1,9 +1,44 @@ import fs from "node:fs"; import path from "node:path"; -import { Liquid } from "liquidjs"; +import { Liquid, ParseError, TokenizationError } from "liquidjs"; import YAML from "yamljs"; import { registerIncludeFileTag } from "./tags/includeFileTag"; +const MAX_LIQUID_SYNTAX_ERRORS = 25; + +// Old markdown docs sometimes contain text that merely looks like Liquid +// (e.g. "{{#...}}" used to describe template tag syntax) but isn't valid +// Liquid. Jekyll/Ruby rendered these as blank rather than failing the build, +// so on a syntax error we cut out just the offending "{{ }}"/"{% %}" span and +// retry, instead of taking down the whole page. +const stripInvalidLiquidSpan = ( + source: string, + err: unknown, +): string | null => { + if (!(err instanceof TokenizationError) && !(err instanceof ParseError)) { + return null; + } + + let { begin, end } = err.token; + if (!(source.startsWith("{{", begin) || source.startsWith("{%", begin))) { + const outputOpen = source.lastIndexOf("{{", begin); + const tagOpen = source.lastIndexOf("{%", begin); + let openStart = outputOpen; + let closer = "}}"; + if (tagOpen > outputOpen) { + openStart = tagOpen; + closer = "%}"; + } + if (openStart === -1) return null; + const closeIdx = source.indexOf(closer, end); + if (closeIdx === -1) return null; + begin = openStart; + end = closeIdx + closer.length; + } + + return source.slice(0, begin) + source.slice(end); +}; + const ROOT = process.cwd(); const INCLUDES_ROOT = path.join(ROOT, "_includes"); @@ -63,14 +98,31 @@ export const getLiquidRenderer = ({ }; return { - render: ( + render: async ( html: string, { include }: { include?: Record } = {}, ) => { - return liquidEngine.parseAndRender(html, { - ...ctx, - include, - }); + let source = html; + for (let attempt = 0; attempt < MAX_LIQUID_SYNTAX_ERRORS; attempt++) { + try { + return await liquidEngine.parseAndRender(source, { + ...ctx, + include, + }); + } catch (err) { + const stripped = stripInvalidLiquidSpan(source, err); + if (stripped === null) throw err; + console.warn( + `[liquid] Ignoring invalid Liquid syntax in ${dirname}: ${ + (err as Error).message + }`, + ); + source = stripped; + } + } + throw new Error( + `[liquid] Too many invalid Liquid syntax errors in ${dirname}`, + ); }, }; }; diff --git a/src/lib/markdown/markdownRenderer.ts b/src/lib/markdown/markdownRenderer.ts index a6f0c275cb..6dd923817e 100644 --- a/src/lib/markdown/markdownRenderer.ts +++ b/src/lib/markdown/markdownRenderer.ts @@ -1,6 +1,7 @@ import { satteri } from "@astrojs/markdown-satteri"; import { ialHastPlugin } from "./plugins/ialHastPlugin"; import { inlineCodeHastPlugin } from "./plugins/inlineCodeHastPlugin"; +import { relativeImagePlugin } from "./plugins/relativeImagePlugin"; import { responsiveTableLabelsHastPlugin } from "./plugins/responsiveTableLabelsHastPlugin"; const docsMarkdownProcessor = satteri({ @@ -8,6 +9,7 @@ const docsMarkdownProcessor = satteri({ ialHastPlugin, inlineCodeHastPlugin, responsiveTableLabelsHastPlugin, + relativeImagePlugin, ], }); diff --git a/src/lib/markdown/noopMarkdownProcessor.ts b/src/lib/markdown/noopMarkdownProcessor.ts new file mode 100644 index 0000000000..b604bb598d --- /dev/null +++ b/src/lib/markdown/noopMarkdownProcessor.ts @@ -0,0 +1,17 @@ +import type { MarkdownProcessor } from "astro/markdown"; + +export const noopMarkdownProcessor: MarkdownProcessor = { + name: "no-op", + options: {}, + createRenderer: async () => ({ + render: async () => ({ + code: "", + metadata: { + headings: [], + localImagePaths: [], + remoteImagePaths: [], + frontmatter: {}, + }, + }), + }), +}; diff --git a/src/lib/markdown/plugins/relativeImagePlugin.ts b/src/lib/markdown/plugins/relativeImagePlugin.ts new file mode 100644 index 0000000000..3a42f3ba94 --- /dev/null +++ b/src/lib/markdown/plugins/relativeImagePlugin.ts @@ -0,0 +1,28 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { DOCS_SRC_ROOT } from "@/constants"; +import { defineHastPlugin } from "satteri"; + +// Resolves relative images from docs md files. +// The images themselves are copied via viteStaticCopy in astro.config.mjs. + +export const relativeImagePlugin = defineHastPlugin({ + name: "relative-image-resolver", + element: { + filter: ["img"], + visit(node, ctx) { + const rawSrc = node.properties?.src; + if (typeof rawSrc !== "string" || rawSrc === "") return; + // Absolute site paths, absolute URLs, and data: URIs need no rewriting. + if (rawSrc.startsWith("/") || URL.canParse(rawSrc)) return; + if (!ctx.fileURL) return; + + const absPath = fileURLToPath(new URL(decodeURI(rawSrc), ctx.fileURL)); + const relPath = path.relative(DOCS_SRC_ROOT, absPath); + if (relPath.startsWith("..") || path.isAbsolute(relPath)) return; + + const newSrc = `/docs/${relPath.split(path.sep).map(encodeURIComponent).join("/")}`; + ctx.setProperty(node, "src", newSrc); + }, + }, +}); diff --git a/src/pages/docs/[version]/[...slug].astro b/src/pages/docs/[version]/[...slug].astro new file mode 100644 index 0000000000..28b53088b0 --- /dev/null +++ b/src/pages/docs/[version]/[...slug].astro @@ -0,0 +1,53 @@ +--- +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import NewDocsLayout from "@/layouts/NewDocsLayout.astro"; +import { getLiquidRenderer } from "@/lib/liquid/liquidRenderer"; +import { getMarkdownRenderer } from "@/lib/markdown/markdownRenderer"; +import { getCollection, type DataEntryMap } from "astro:content"; + +type Props = { + doc: DataEntryMap["docs"][number]; +}; + +export const getStaticPaths = async () => { + const splitAtFirst = (str: string, delimiter: string) => { + const index = str.indexOf(delimiter); + return index !== -1 ? [str.slice(0, index), str.slice(index + 1)] : [str]; + }; + // TODO: Handle README -> index + // TODO: Handle permalink in frontmatter + // TODO: Handle redirect_to/redirect_from + const docs = await getCollection("docs"); + return [ + ...docs.map((doc) => { + const [version, slug] = splitAtFirst(doc.id, "/"); + return { + props: { doc }, + params: { version, slug }, + }; + }), + ]; +}; + +const { doc } = Astro.props; +const dirname = path.dirname(doc.filePath!); + +// Process liquid first (e.g. control flow, includes, variables, etc) +const lq = getLiquidRenderer({ page: doc.data, dirname }); +const lqOutput = await lq.render(doc.body!); + +// Remove whitespace between elements so satteri doesn't turn them into code snippets. +// Mimics previous behavior (which used jekyll + kramdown). +const lqProcessed = lqOutput.replace(/>\s+<"); + +// Convert processed markdown to html +const md = await getMarkdownRenderer(); +const renderResult = await md.render(lqProcessed, { + fileURL: pathToFileURL(path.resolve(doc.filePath!)), +}); +--- + + + + diff --git a/src/pages/docs/astro-test/[...slug].astro b/src/pages/docs/astro-test/[...slug].astro deleted file mode 100644 index 88eb3d98eb..0000000000 --- a/src/pages/docs/astro-test/[...slug].astro +++ /dev/null @@ -1,41 +0,0 @@ ---- -import NewDocsLayout from "@/layouts/NewDocsLayout.astro"; -import { getLiquidRenderer } from "@/lib/liquid/liquidRenderer"; -import { getMarkdownRenderer } from "@/lib/markdown/markdownRenderer"; -import { getCollection, type DataEntryMap } from "astro:content"; - -type Props = { - example: DataEntryMap["examples"][number]; -}; - -export const getStaticPaths = async () => { - const examples = await getCollection("examples"); - return [ - ...examples.map((example) => ({ - props: { example }, - params: { slug: example.id.replace(/^src\//, "") }, - })), - ]; -}; - -const { example } = Astro.props; - -// This is for debug purposes only. When rendering _docs, the dirname will be the source md file's path. -const dirname = "_docs/latest/embedding"; - -// Process liquid first (e.g. control flow, includes, variables, etc) -const lq = getLiquidRenderer({ page: example.data, dirname }); -const lqOutput = await lq.render(example.body!); - -// Remove whitespace between elements so satteri doesn't turn them into code snippets. -// Mimics previous behavior (which used jekyll + kramdown). -const lqProcessed = lqOutput.replace(/>\s+<"); - -// Convert processed markdown to html -const md = await getMarkdownRenderer(); -const renderResult = await md.render(lqProcessed); ---- - - - - diff --git a/src/pages/docs/sitemap.xml.ts b/src/pages/docs/sitemap.xml.ts new file mode 100644 index 0000000000..7b17ea87b3 --- /dev/null +++ b/src/pages/docs/sitemap.xml.ts @@ -0,0 +1,31 @@ +import type { APIRoute } from "astro"; +import { getCollection } from "astro:content"; + +const SITE_URL = "https://www.metabase.com"; + +const escapeXml = (value: string) => + value.replace(/&/g, "&").replace(//g, ">"); + +export const GET: APIRoute = async () => { + const docs = await getCollection( + "docs", + (doc) => doc.id === "latest" || doc.id.startsWith("latest/"), + ); + + const paths = docs + .map((doc) => { + const slug = doc.id.slice("latest".length).replace(/^\//, ""); + return slug ? `/docs/latest/${slug}` : "/docs/latest"; + }) + .sort(); + + const body = ` + +${paths.map((path) => ` \n ${escapeXml(`${SITE_URL}${path}`)}\n `).join("\n")} + +`; + + return new Response(body, { + headers: { "Content-Type": "application/xml" }, + }); +};