+ Read docs for other{" "} + versions of Metabase. +
+ ) + } +diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b0874e370a..6c7cdf7624 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,10 @@ jobs: with: bundler-cache: true # runs 'bundle install' and caches installed gems automatically cache-version: 0 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + - name: Install js dependencies + run: bun install --frozen-lockfile - name: Build the Jekyll Site env: JEKYLL_ENV: production diff --git a/.gitignore b/.gitignore index 839f51fa99..b0aaa10d20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,20 @@ /node_modules/ .DS_Store .idea +.vscode *.log tmp/ /htmlproofer.out /.clj-kondo/ /.lsp/ /_site/ + +# Astro build output +dist/ + +# generated types +.astro/ + +# environment variables +.env +.env.production diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..1898fddc4e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "plugins": [ + "@ianvs/prettier-plugin-sort-imports", + "prettier-plugin-astro" + ], + "trailingComma": "all" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..c5ff42975b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,22 @@ +## Development + +When starting the dev server, use background mode: + +``` +astro dev --background +``` + +Manage the background server with `astro dev stop`, `astro dev status`, and `astro dev logs`. + +## Documentation + +Full documentation: https://docs.astro.build + +Consult these guides before working on related tasks: + +- [Adding pages, dynamic routes, or middleware](https://docs.astro.build/en/guides/routing/) +- [Working with Astro components](https://docs.astro.build/en/basics/astro-components/) +- [Using React, Vue, Svelte, or other framework components](https://docs.astro.build/en/guides/framework-components/) +- [Adding or managing content](https://docs.astro.build/en/guides/content-collections/) +- [Adding styles or using Tailwind](https://docs.astro.build/en/guides/styling/) +- [Supporting multiple languages](https://docs.astro.build/en/guides/internationalization/) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/_includes/learn/left-nav.html b/_includes/learn/left-nav.html index 85d2bfd923..c78e237ee9 100644 --- a/_includes/learn/left-nav.html +++ b/_includes/learn/left-nav.html @@ -1,4 +1,4 @@ -{% assign cleanPageURL = page.url | replace: '/index' '' %} +{% assign cleanPageURL = page.url | replace: '/index', '' %} {% for category in include.categories %}
+ Read docs for other{" "} + versions of Metabase. +
+ ) + } +`.
+// CommonMark/Sätteri has no such hook for inline code (only fenced blocks
+// carry a language), so plain `` comes out bare. Two site scripts rely
+// on kramdown's classes being there: js/syntax-highlight.js only
+// syntax-highlights `.highlighter-rouge` elements, and
+// js/new-docs-code-snippet-copy.js attaches a copy-button overlay to every
+// `` *except* ones classed `language-plaintext` — without this plugin,
+// every inline code span across the site would get a copy button.
+export const inlineCodeHastPlugin = defineHastPlugin({
+ name: "inline-code",
+ element: {
+ filter: ["code"],
+ visit(node, ctx) {
+ const parent = ctx.parent(node);
+ if (parent && "tagName" in parent && parent.tagName === "pre") return;
+ const existing = Array.isArray(node.properties?.className)
+ ? (node.properties.className as unknown[])
+ : [];
+ ctx.setProperty(node, "className", [
+ ...existing,
+ "language-plaintext",
+ "highlighter-rouge",
+ ]);
+ },
+ },
+});
diff --git a/src/lib/markdown/plugins/responsiveTableLabelsHastPlugin.ts b/src/lib/markdown/plugins/responsiveTableLabelsHastPlugin.ts
new file mode 100644
index 0000000000..c4795921b2
--- /dev/null
+++ b/src/lib/markdown/plugins/responsiveTableLabelsHastPlugin.ts
@@ -0,0 +1,69 @@
+import type { Element, ElementContent } from "hast";
+import { defineHastPlugin } from "satteri";
+
+// Ported from _plugins/jekyll_responsive_table_labels_plugin.rb: the site's
+// responsive CSS shows a `data-label` before each ``'s content when a
+// table collapses to a stacked layout on narrow screens. Kramdown/Jekyll
+// stamped that attribute on with a post-render Nokogiri pass; Sätteri has no
+// such pass, so this plugin stamps it on during the hast phase instead.
+function isElement(node: ElementContent): node is Element {
+ return node.type === "element";
+}
+
+function findFirstDescendant(
+ node: Element,
+ tagName: string,
+): Element | undefined {
+ for (const child of node.children) {
+ if (!isElement(child)) continue;
+ if (child.tagName === tagName) return child;
+ const found = findFirstDescendant(child, tagName);
+ if (found) return found;
+ }
+ return undefined;
+}
+
+function findAllDescendants(node: Element, tagName: string): Element[] {
+ const results: Element[] = [];
+ for (const child of node.children) {
+ if (!isElement(child)) continue;
+ if (child.tagName === tagName) results.push(child);
+ results.push(...findAllDescendants(child, tagName));
+ }
+ return results;
+}
+
+export const responsiveTableLabelsHastPlugin = defineHastPlugin({
+ name: "responsive-table-labels",
+ element: {
+ filter: ["table"],
+ visit(node, ctx) {
+ const thead = findFirstDescendant(node, "thead");
+ const headerRow = thead
+ ? findFirstDescendant(thead, "tr")
+ : findFirstDescendant(node, "tr");
+ if (!headerRow) return;
+
+ const headerTags = thead ? ["th"] : ["th", "td"];
+ const headerCells = headerRow.children.filter(
+ (child): child is Element =>
+ isElement(child) && headerTags.includes(child.tagName),
+ );
+ if (headerCells.length === 0) return;
+
+ const headers = headerCells.map((cell) => ctx.textContent(cell).trim());
+
+ for (const tr of findAllDescendants(node, "tr")) {
+ const dataCells = tr.children.filter(
+ (child): child is Element =>
+ isElement(child) && child.tagName === "td",
+ );
+ dataCells.forEach((td, idx) => {
+ const label = headers[idx];
+ if (!label) return;
+ ctx.setProperty(td, "dataLabel", label);
+ });
+ }
+ },
+ },
+});
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000000..a45e86b5c7
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,27 @@
+import { defineMiddleware } from "astro:middleware";
+
+/**
+ * Dev-only Jekyll proxy: if Astro can't handle a request (404), proxy it to
+ * Jekyll so both run on the "same" port during migration. Removable once
+ * everything is migrated.
+ */
+
+const JEKYLL_PORT = 4002;
+
+export const onRequest = defineMiddleware(async (context, next) => {
+ const response = await next();
+
+ // Astro handled it.
+ if (response.status !== 404) return response;
+
+ // In prod, return Astro's own 404 response. In dev, fall through to the
+ // Jekyll proxy so unmigrated routes still resolve.
+ if (!import.meta.env.DEV) return response;
+
+ try {
+ const { pathname, search } = context.url;
+ return await fetch(`http://localhost:${JEKYLL_PORT}${pathname}${search}`);
+ } catch {
+ return response;
+ }
+});
diff --git a/src/pages/docs/astro-test/[...slug].astro b/src/pages/docs/astro-test/[...slug].astro
new file mode 100644
index 0000000000..88eb3d98eb
--- /dev/null
+++ b/src/pages/docs/astro-test/[...slug].astro
@@ -0,0 +1,41 @@
+---
+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/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000..85eaf84be9
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "include": [".astro/types.d.ts", "**/*"],
+ "exclude": ["dist", "_docs", "_site", "gdpr-cookie-notice", "lib", "js", "script"],
+ "compilerOptions": {
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}