From de1393fdcf10ea71697da0bfc8b9812c02d696d2 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:28:32 +0100 Subject: [PATCH] update --- .github/workflows/tiny-docs.yml | 14 +- astro.config.ts | 14 +- config.server.ts | 6 +- custom-embeds/index.ts | 105 ++++++++++++++ custom-embeds/remark-plugin.ts | 151 +++++++++++++++++++++ package-lock.json | 21 ++- package.json | 1 + scripts/update-config.js | 9 +- src/components/BaseHead.astro | 1 - src/components/Sidebar.astro | 6 +- src/embeds/callouts/CautionCallout.astro | 45 ++++++ src/embeds/callouts/ImportantCallout.astro | 45 ++++++ src/embeds/callouts/NoteCallout.astro | 43 ++++++ src/embeds/callouts/TipCallout.astro | 45 ++++++ src/embeds/callouts/WarningCallout.astro | 45 ++++++ src/embeds/callouts/embeds.ts | 39 ++++++ src/embeds/excalidraw/Excalidraw.astro | 26 ++-- src/embeds/github/Github.astro | 2 +- src/embeds/index.ts | 17 ++- src/layouts/BaseLayout.astro | 4 + src/utils.ts | 5 - 21 files changed, 594 insertions(+), 50 deletions(-) create mode 100644 custom-embeds/index.ts create mode 100644 custom-embeds/remark-plugin.ts create mode 100644 src/embeds/callouts/CautionCallout.astro create mode 100644 src/embeds/callouts/ImportantCallout.astro create mode 100644 src/embeds/callouts/NoteCallout.astro create mode 100644 src/embeds/callouts/TipCallout.astro create mode 100644 src/embeds/callouts/WarningCallout.astro create mode 100644 src/embeds/callouts/embeds.ts diff --git a/.github/workflows/tiny-docs.yml b/.github/workflows/tiny-docs.yml index b728647..38ef821 100644 --- a/.github/workflows/tiny-docs.yml +++ b/.github/workflows/tiny-docs.yml @@ -73,19 +73,7 @@ jobs: with: node-version: 20 - - name: Logging - env: - DERIVED_SITE: ${{ steps.derive.outputs.site }} - DERIVED_BASE: ${{ steps.derive.outputs.base }} - DERIVED_NAME: ${{ steps.derive.outputs.name }} - run: | - set -euo pipefail - echo "🔎 Listing docs-builder contents..." - ls -la docs-builder || true - echo "🔎 Listing docs-builder/scripts contents..." - ls -la docs-builder/scripts || true - - - name: Ensure docs/config.json has required fields (merge w/ defaults) + - name: Ensure tdocs.config.json has required fields (merge w/ defaults) env: DERIVED_SITE: ${{ steps.derive.outputs.site }} DERIVED_BASE: ${{ steps.derive.outputs.base }} diff --git a/astro.config.ts b/astro.config.ts index 8ef5c27..76be600 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -10,7 +10,8 @@ import tailwind from "@astrojs/tailwind"; import svelte from "@astrojs/svelte"; import pagefind from "astro-pagefind"; -import customEmbeds from "astro-custom-embeds"; +import customEmbeds from "./custom-embeds"; +import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives"; import { transformerMetaHighlight, @@ -21,6 +22,7 @@ import LinkCardEmbed from "./src/embeds/link-card/embed"; import YoutubeEmbed from "./src/embeds/youtube/embed"; import ExcalidrawEmbed from "./src/embeds/excalidraw/embed"; import GithubEmbed from "./src/embeds/github/embed"; +import { calloutEmbeds } from "./src/embeds/callouts/embeds"; import react from "@astrojs/react"; import { config } from "./config.server"; @@ -45,7 +47,13 @@ export default defineConfig({ integrations: [ pagefind(), customEmbeds({ - embeds: [ExcalidrawEmbed, YoutubeEmbed, GithubEmbed, LinkCardEmbed], + embeds: [ + ExcalidrawEmbed, + YoutubeEmbed, + GithubEmbed, + LinkCardEmbed, + ...calloutEmbeds, + ], }), mdx(), sitemap(), @@ -68,7 +76,7 @@ export default defineConfig({ wrap: true, }, - remarkPlugins: [remarkMath], + remarkPlugins: [remarkGithubAdmonitionsToDirectives, remarkMath], rehypePlugins: [rehypeMathjax], }, diff --git a/config.server.ts b/config.server.ts index 158130c..0377b68 100644 --- a/config.server.ts +++ b/config.server.ts @@ -50,12 +50,14 @@ const defaultConfig: Config = { export function loadConfig(): Config { try { - const configPath = r("../docs/config.json"); + const configPath = r("../tdocs.config.json"); if (existsSync(configPath)) { return JSON.parse(readFileSync(configPath, "utf-8")) as Config; } } catch (e) { - console.warn("⚠️ No config.json found or invalid JSON — using defaults."); + console.warn( + "⚠️ No tdocs.config.json found or invalid JSON — using defaults.", + ); } return defaultConfig; } diff --git a/custom-embeds/index.ts b/custom-embeds/index.ts new file mode 100644 index 0000000..8b93d8f --- /dev/null +++ b/custom-embeds/index.ts @@ -0,0 +1,105 @@ +import type { AstroConfig, AstroIntegration } from "astro"; +import AutoImport from "astro-auto-import"; +import remarkDirective from "remark-directive"; + +import createEmbedPlugin from "./remark-plugin.ts"; + +const importNamespace = "AuToImPoRtEdAstroCusToMEmbed"; + +/** + * Embeds can be triggered by a single URL or a directive. + */ +export type EmbedsOption = { + componentName: string; + + /** + * path to import component from (default is 'src/components/embeds') + */ + importPath?: string; + + /** + * for matching single urls on a single line + * function that should return a string with the argument to pass to the component or undefined if it doesn't match + * + * @param url + * @returns + */ + urlMatcher?: (url: string) => string | undefined | null; + + /** + * what argument to pass the url as to the component (default is 'href') + */ + urlArgument?: string; + + /** + * for matching a directive, name of the directive, can also be an array of names + * + * @param directive + * @returns + */ + directiveName?: string | string[]; +}; + +/** + * Astro embed MDX integration. + */ +export default function customEmbeds({ + embeds = [], +}: { + embeds?: EmbedsOption[]; +} = {}) { + console.log("customEmbeds", embeds); + const AstroCustomEmbeds: AstroIntegration = { + name: "astro-custom-embeds", + hooks: { + "astro:config:setup": ({ config, updateConfig }) => { + checkIntegrationsOrder(config); + updateConfig({ + markdown: { + remarkPlugins: [ + remarkDirective, + createEmbedPlugin({ embeds, importNamespace }), + ], + }, + }); + }, + }, + }; + + const imports: Record = {}; + + for (const embed of embeds) { + let importPath = embed.importPath ?? "src/components/embeds"; + + if (!imports[importPath]) { + imports[importPath] = []; + } + + imports[importPath].push([ + embed.componentName, + importNamespace + "_" + embed.componentName, + ]); + } + + return [ + AutoImport({ + imports: [imports], + }), + AstroCustomEmbeds, + ]; +} + +function checkIntegrationsOrder({ integrations }: AstroConfig) { + const indexOf = (name: string) => + integrations.findIndex((i) => i.name === name); + const mdxIndex = indexOf("@astrojs/mdx"); + const embedIndex = indexOf("astro-custom-embeds"); + + if (mdxIndex > -1 && mdxIndex < embedIndex) { + throw new Error( + "MDX integration configured before astro-custom-embeds.\n" + + "Please move `mdx()` after `customEmbeds()` in the `integrations` array in astro.config.ts.", + ); + } + console.log("integrations", integrations); +} diff --git a/custom-embeds/remark-plugin.ts b/custom-embeds/remark-plugin.ts new file mode 100644 index 0000000..3fa4e8f --- /dev/null +++ b/custom-embeds/remark-plugin.ts @@ -0,0 +1,151 @@ +import { type Node, select, selectAll } from "unist-util-select"; +import { visit } from "unist-util-visit"; +import type { EmbedsOption } from "."; + +export default function createPlugin({ + embeds = [], + importNamespace, +}: { + embeds: EmbedsOption[]; + importNamespace: string; +}) { + /** + * Get the name of the embed component for this URL + * @param {string} url URL to test + * @returns Component node for this URL or undefined if none matched + */ + function getComponentForUrl(url: string) { + for (const embed of embeds) { + const { componentName, urlArgument = "href", urlMatcher } = embed; + + if (!urlMatcher) continue; + + let match = urlMatcher(url); + + if (!match) continue; + + // MDX custom component node. + return { + type: "mdxJsxFlowElement", + name: `${importNamespace}_${componentName}`, + attributes: [ + { type: "mdxJsxAttribute", name: urlArgument, value: match }, + ], + children: [], + }; + } + return undefined; + } + + function getComponentForDirective(node: Node) { + for (const embed of embeds) { + const { componentName, urlArgument = "href", directiveName } = embed; + + // @ts-expect-error + if (!directiveName || !node.name) continue; + + const name = (node as any).name; + const attributes = (node as any).attributes ?? {}; + const children = (node as any).children ?? []; + + if ( + directiveName === name || + (Array.isArray(directiveName) && directiveName.includes(name)) + ) { + // turn into array + const attributeArray = Object.keys(attributes).map((key) => { + return { type: "mdxJsxAttribute", name: key, value: attributes[key] }; + }); + + // if there's one child and it's either a link node with a text node child or a text node + // add that as a attribute using the urlArgument + if (children && children.length === 1) { + const child = children[0]; + if (child.type === "link") { + if ( + child.children.length === 1 && + child.children[0].type === "text" + ) { + attributeArray.push({ + type: "mdxJsxAttribute", + name: urlArgument, + value: child.url, + }); + } + } else if (child.type === "text") { + attributeArray.push({ + type: "mdxJsxAttribute", + name: urlArgument, + value: child.value, + }); + } + } + + return { + type: "mdxJsxFlowElement", + name: `${importNamespace}_${componentName}`, + attributes: attributeArray, + children: children ?? [], + }; + } + } + return undefined; + } + + type Link = Node & { + url?: string; + value?: string; + children?: Node[]; + }; + + function transformer(tree: Node) { + visit(tree, function (node) { + if ( + node.type === "containerDirective" || + node.type === "leafDirective" || + node.type === "textDirective" + ) { + let component = getComponentForDirective(node); + if (component) { + // @ts-expect-error We’re overriding the initial node type with arbitrary data. + for (const key in component) node[key] = component[key]; + } + } + }); + + const paragraphs = selectAll("paragraph", tree); + paragraphs.forEach((paragraph) => { + const link: Link | null = select(":scope > link:only-child", paragraph); + if (!link) return; + + const { url, children } = link; + // We’re only interested in HTTP links + if (!url || !url.startsWith("http")) return; + // URLs should have a single child + if (!children || children.length !== 1) return; + + // The child should be a text node with a value matching the URL + const child = children[0]; + if ( + !child || + child.type !== "text" || + !("value" in child) || + child.value !== url + ) { + return; + } + + const component = getComponentForUrl(url); + if (component) { + // @ts-expect-error We’re overriding the initial node type with arbitrary data. + for (const key in component) paragraph[key] = component[key]; + } + }); + + return tree; + } + + return function attacher() { + return transformer; + }; +} diff --git a/package-lock.json b/package-lock.json index e6fec6d..2b60a26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "blog-template", + "name": "tiny-docs", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "blog-template", + "name": "tiny-docs", "version": "0.0.1", "license": "MIT", "dependencies": { @@ -43,6 +43,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tailwindcss/typography": "^0.5.15", "pagefind": "^1.3.0", + "remark-github-admonitions-to-directives": "^2.1.0", "vite-plugin-pagefind": "^1.0.5" } }, @@ -7196,6 +7197,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-github-admonitions-to-directives": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/remark-github-admonitions-to-directives/-/remark-github-admonitions-to-directives-2.1.0.tgz", + "integrity": "sha512-bI3E4Oj1pKY3ym2IQrrVCdORgEu0+mSrWgpCYFNy8QvytfnLs/nAacVPjkWU/JzDMUiQio2k4nTFP7bsIr9TSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "pnpm": ">=9.0.0" + } + }, "node_modules/remark-math": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", diff --git a/package.json b/package.json index e118d56..1fb2a7f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tailwindcss/typography": "^0.5.15", "pagefind": "^1.3.0", + "remark-github-admonitions-to-directives": "^2.1.0", "vite-plugin-pagefind": "^1.0.5" }, "license": "MIT" diff --git a/scripts/update-config.js b/scripts/update-config.js index 2a00530..0d0583c 100644 --- a/scripts/update-config.js +++ b/scripts/update-config.js @@ -1,12 +1,14 @@ import fs from "node:fs"; -const CONFIG_PATH = "docs/config.json"; +const CONFIG_PATH = "tdocs.config.json"; // Defaults derived from workflow env const derived = { SITE: process.env.DERIVED_SITE, BASE: process.env.DERIVED_BASE, SITE_NAME: process.env.DERIVED_NAME, + + SHOW_THEME_TOGGLE: true, }; function readExisting(path) { @@ -16,7 +18,7 @@ function readExisting(path) { return JSON.parse(raw); } catch (e) { console.warn( - "⚠️ Invalid JSON in docs/config.json; continuing with empty object.", + "⚠️ Invalid JSON in tdocs.config.json; continuing with empty object.", ); return {}; } @@ -33,7 +35,6 @@ function normalize(merged) { return merged; } -fs.mkdirSync("docs", { recursive: true }); const existing = readExisting(CONFIG_PATH); // Merge so existing values win; only fill in missing keys from derived @@ -41,7 +42,7 @@ const merged = normalize({ ...derived, ...existing }); fs.writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2) + "\n"); console.log( - "✅ Wrote docs/config.json with keys:", + "✅ Wrote tdocs.config.json with keys:", Object.keys(merged).join(", "), ); diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro index 645943e..2d21588 100644 --- a/src/components/BaseHead.astro +++ b/src/components/BaseHead.astro @@ -6,7 +6,6 @@ interface Props { image?: string; } -console.log(Astro.url.pathname, Astro.site); let canonicalURL: URL | undefined = undefined; try { canonicalURL = new URL(Astro.url.pathname, Astro.site); diff --git a/src/components/Sidebar.astro b/src/components/Sidebar.astro index 75b0323..5f32c61 100644 --- a/src/components/Sidebar.astro +++ b/src/components/Sidebar.astro @@ -15,7 +15,11 @@ const numberOfSecondHeadings = headings.filter( class="mx-8 mt-8 pb-16 text-xl text-base-950 dark:text-base-50 flex flex-col gap-8" > -
{config.SITE_FAVICON}
+ { + config.SITE_FAVICON ? ( +
{config.SITE_FAVICON}
+ ) : null + }
{config.SITE_NAME}
diff --git a/src/embeds/callouts/CautionCallout.astro b/src/embeds/callouts/CautionCallout.astro new file mode 100644 index 0000000..b82d117 --- /dev/null +++ b/src/embeds/callouts/CautionCallout.astro @@ -0,0 +1,45 @@ +--- +interface Props { + title: string; + class?: string; +} + +const { title, class: className, ...props } = Astro.props; +--- + +
+
+
+ + + +
+
+

+ Caution +

+ +
+ +
+
+
+
diff --git a/src/embeds/callouts/ImportantCallout.astro b/src/embeds/callouts/ImportantCallout.astro new file mode 100644 index 0000000..478e033 --- /dev/null +++ b/src/embeds/callouts/ImportantCallout.astro @@ -0,0 +1,45 @@ +--- +interface Props { + title: string; + class?: string; +} + +const { title, class: className, ...props } = Astro.props; +--- + +
+
+
+ + + +
+
+

+ Important +

+ +
+ +
+
+
+
diff --git a/src/embeds/callouts/NoteCallout.astro b/src/embeds/callouts/NoteCallout.astro new file mode 100644 index 0000000..1ce3563 --- /dev/null +++ b/src/embeds/callouts/NoteCallout.astro @@ -0,0 +1,43 @@ +--- +interface Props { + title: string; + class?: string; +} + +const { title, class: className, ...props } = Astro.props; +--- + +
+
+
+ + + +
+
+

Note

+ +
+ +
+
+
+
diff --git a/src/embeds/callouts/TipCallout.astro b/src/embeds/callouts/TipCallout.astro new file mode 100644 index 0000000..cb7f642 --- /dev/null +++ b/src/embeds/callouts/TipCallout.astro @@ -0,0 +1,45 @@ +--- +interface Props { + title: string; + class?: string; +} + +const { title, class: className, ...props } = Astro.props; +--- + +
+
+
+ + + +
+
+

+ Tip +

+ +
+ +
+
+
+
diff --git a/src/embeds/callouts/WarningCallout.astro b/src/embeds/callouts/WarningCallout.astro new file mode 100644 index 0000000..a20da1d --- /dev/null +++ b/src/embeds/callouts/WarningCallout.astro @@ -0,0 +1,45 @@ +--- +interface Props { + title: string; + class?: string; +} + +const { title, class: className, ...props } = Astro.props; +--- + +
+
+
+ + + +
+
+

+ Warning +

+ +
+ +
+
+
+
diff --git a/src/embeds/callouts/embeds.ts b/src/embeds/callouts/embeds.ts new file mode 100644 index 0000000..4e9feee --- /dev/null +++ b/src/embeds/callouts/embeds.ts @@ -0,0 +1,39 @@ +import type { EmbedsOption } from "astro-custom-embeds"; + +const NoteCalloutEmbed: EmbedsOption = { + componentName: "NoteCallout", + directiveName: "note", + importPath: "src/embeds", +}; + +const TipCalloutEmbed: EmbedsOption = { + componentName: "TipCallout", + directiveName: "tip", + importPath: "src/embeds", +}; + +const ImportantCalloutEmbed: EmbedsOption = { + componentName: "ImportantCallout", + directiveName: "info", + importPath: "src/embeds", +}; + +const WarningCalloutEmbed: EmbedsOption = { + componentName: "WarningCallout", + directiveName: "warning", + importPath: "src/embeds", +}; + +const CautionCalloutEmbed: EmbedsOption = { + componentName: "CautionCallout", + directiveName: "danger", + importPath: "src/embeds", +}; + +export const calloutEmbeds: EmbedsOption[] = [ + NoteCalloutEmbed, + TipCalloutEmbed, + ImportantCalloutEmbed, + WarningCalloutEmbed, + CautionCalloutEmbed, +]; diff --git a/src/embeds/excalidraw/Excalidraw.astro b/src/embeds/excalidraw/Excalidraw.astro index fc0ab7d..1ac3f5e 100644 --- a/src/embeds/excalidraw/Excalidraw.astro +++ b/src/embeds/excalidraw/Excalidraw.astro @@ -45,19 +45,19 @@ function modifySvg(svgString: string): string { $("text").each((_, el) => { $(el).removeAttr("fill"); - $(el).addClass("fill-skin-base"); + $(el).addClass("fill-base-800 dark:fill-base-100"); }); $("rect").each((_, el) => { $(el).removeAttr("fill"); - $(el).addClass("fill-skin-soft"); + $(el).addClass("fill-accent-600 dark:fill-accent-500"); }); $("path").each((_, el) => { $(el).removeAttr("stroke"); - $(el).addClass("stroke-skin-accent"); + $(el).addClass("stroke-accent-600 dark:stroke-accent-500"); if ($(el).attr("fill") !== "none") { - $(el).addClass("fill-skin-soft"); + $(el).addClass("fill-accent-600 dark:fill-accent-500"); $(el).removeAttr("fill"); } }); @@ -82,7 +82,11 @@ try { ---
-
+
{ caption && (
@@ -93,21 +97,9 @@ try {
+ { export const getDocs = async () => { const docs = await getSortedDocs(); - docs.forEach((doc) => { - console.log(doc.id); - }); - const categories = docs .map((doc) => getCategory(doc)) .filter((category) => category); @@ -40,7 +36,6 @@ export const getDocs = async () => { docs: docs.filter((doc) => getCategory(doc) === category), }; }); - console.log(sortedDocs.map((doc) => doc.docs.map((d) => d.data))); return sortedDocs; }; -- 2.51.2