diff --git a/package-lock.json b/package-lock.json
index 6fc560a..3b872f7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,23 +1,19 @@
{
- "name": "@quartz-community/plugin-template",
+ "name": "@quartz-community/github-flavored-markdown",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "@quartz-community/plugin-template",
+ "name": "@quartz-community/github-flavored-markdown",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@quartz-community/types": "github:quartz-community/types",
- "mdast-util-find-and-replace": "^3.0.1",
+ "rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
- "remark-parse": "^11.0.0",
- "remark-stringify": "^11.0.0",
- "unified": "^11.0.5",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.3"
+ "remark-smartypants": "^3.0.2"
},
"devDependencies": {
"@types/hast": "^3.0.4",
@@ -1123,6 +1119,15 @@
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
"license": "MIT"
},
+ "node_modules/@types/nlcst": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz",
+ "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
"node_modules/@types/node": {
"version": "24.10.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
@@ -1338,7 +1343,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
- "dev": true,
"license": "ISC"
},
"node_modules/@vitest/expect": {
@@ -1549,6 +1553,16 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/array-iterate": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz",
+ "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -2459,6 +2473,19 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-to-string": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
@@ -3609,6 +3636,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/nlcst-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz",
+ "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -3692,6 +3732,24 @@
"node": ">=6"
}
},
+ "node_modules/parse-latin": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
+ "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "@types/unist": "^3.0.0",
+ "nlcst-to-string": "^4.0.0",
+ "unist-util-modify-children": "^4.0.0",
+ "unist-util-visit-children": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -3946,6 +4004,24 @@
"url": "https://paulmillr.com/funding/"
}
},
+ "node_modules/rehype-autolink-headings": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz",
+ "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "hast-util-heading-rank": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unified": "^11.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/rehype-slug": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz",
@@ -3997,6 +4073,21 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remark-smartypants": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz",
+ "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==",
+ "license": "MIT",
+ "dependencies": {
+ "retext": "^9.0.0",
+ "retext-smartypants": "^6.0.0",
+ "unified": "^11.0.4",
+ "unist-util-visit": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/remark-stringify": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
@@ -4022,6 +4113,67 @@
"node": ">=4"
}
},
+ "node_modules/retext": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz",
+ "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "retext-latin": "^4.0.0",
+ "retext-stringify": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/retext-latin": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz",
+ "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "parse-latin": "^7.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/retext-smartypants": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz",
+ "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "nlcst-to-string": "^4.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/retext-stringify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz",
+ "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "nlcst-to-string": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -4594,6 +4746,20 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-modify-children": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz",
+ "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "array-iterate": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"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",
@@ -4622,6 +4788,19 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-visit-children": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz",
+ "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/unist-util-visit-parents": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
diff --git a/package.json b/package.json
index 4c507b6..f049d19 100644
--- a/package.json
+++ b/package.json
@@ -1,23 +1,21 @@
{
- "name": "@quartz-community/plugin-template",
+ "name": "@quartz-community/github-flavored-markdown",
"version": "0.1.0",
- "description": "Template repository for Quartz community plugins.",
+ "description": "GitHub Flavored Markdown transformer plugin for Quartz",
"type": "module",
"license": "MIT",
"author": "Quartz Community",
- "homepage": "https://quartz.jzhao.xyz",
+ "homepage": "https://github.com/quartz-community/github-flavored-markdown",
"repository": {
"type": "git",
- "url": "https://github.com/quartz-community/plugin-template"
+ "url": "https://github.com/quartz-community/github-flavored-markdown"
},
"keywords": [
"quartz",
"quartz-plugin",
- "plugin-template",
- "remark",
- "rehype",
- "mdast",
- "hast"
+ "gfm",
+ "github-flavored-markdown",
+ "remark"
],
"files": [
"dist",
@@ -30,14 +28,6 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
- "./types": {
- "types": "./dist/types.d.ts",
- "import": "./dist/types.js"
- },
- "./components": {
- "types": "./dist/components/index.d.ts",
- "import": "./dist/components/index.js"
- },
"./package.json": "./package.json"
},
"main": "./dist/index.js",
@@ -67,14 +57,10 @@
},
"dependencies": {
"@quartz-community/types": "github:quartz-community/types",
- "mdast-util-find-and-replace": "^3.0.1",
- "rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
- "remark-parse": "^11.0.0",
- "remark-stringify": "^11.0.0",
- "unified": "^11.0.5",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.3"
+ "remark-smartypants": "^3.0.2",
+ "rehype-slug": "^6.0.0",
+ "rehype-autolink-headings": "^7.1.0"
},
"devDependencies": {
"@types/hast": "^3.0.4",
diff --git a/src/components/ExampleComponent.tsx b/src/components/ExampleComponent.tsx
deleted file mode 100644
index d18ee6a..0000000
--- a/src/components/ExampleComponent.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import type {
- QuartzComponent,
- QuartzComponentProps,
- QuartzComponentConstructor,
-} from "@quartz-community/types";
-import { classNames } from "../util/lang";
-import { i18n } from "../i18n";
-import style from "./styles/example.scss";
-// @ts-ignore
-import script from "./scripts/example.inline.ts";
-
-export interface ExampleComponentOptions {
- prefix?: string;
- suffix?: string;
- className?: string;
-}
-
-export default ((opts?: ExampleComponentOptions) => {
- const { prefix = "", suffix = "", className = "example-component" } = opts ?? {};
-
- const Component: QuartzComponent = (props: QuartzComponentProps) => {
- const frontmatter = props.fileData?.frontmatter as { title?: string } | undefined;
- const title = frontmatter?.title ?? "Untitled";
- const fullText = `${prefix}${title}${suffix}`;
-
- return
{fullText}
;
- };
-
- Component.css = style;
- Component.afterDOMLoaded = script;
-
- return Component;
-}) satisfies QuartzComponentConstructor;
diff --git a/src/components/index.ts b/src/components/index.ts
deleted file mode 100644
index f2ca23a..0000000
--- a/src/components/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as ExampleComponent } from "./ExampleComponent";
-export type { ExampleComponentOptions } from "./ExampleComponent";
diff --git a/src/components/scripts.d.ts b/src/components/scripts.d.ts
deleted file mode 100644
index 89df165..0000000
--- a/src/components/scripts.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-declare module "*.inline.ts" {
- const content: string;
- export default content;
-}
diff --git a/src/components/scripts/example.inline.ts b/src/components/scripts/example.inline.ts
deleted file mode 100644
index 366f8e5..0000000
--- a/src/components/scripts/example.inline.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-// @ts-nocheck
-// ============================================================================
-// Example Inline Script for Quartz Community Plugin
-// ============================================================================
-// This file demonstrates patterns commonly used in Quartz plugin client-side code.
-// It is bundled as a string and injected via Component.afterDOMLoaded.
-//
-// Key patterns demonstrated:
-// 1. Listening to Quartz navigation events ('nav', 'prenav')
-// 2. Fetching content index data
-// 3. DOM manipulation with cleanup
-// 4. State persistence (localStorage/sessionStorage)
-// 5. Keyboard shortcut handling
-// 6. Proper event listener cleanup
-// ============================================================================
-
-// Helper: Remove all children from an element
-function removeAllChildren(element) {
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
-}
-
-// Helper: Simplify slug by removing trailing /index
-function simplifySlug(slug) {
- if (slug.endsWith("/index")) {
- return slug.slice(0, -6);
- }
- return slug;
-}
-
-// Helper: Get current page slug from URL
-function getCurrentSlug() {
- let slug = window.location.pathname;
- if (slug.startsWith("/")) slug = slug.slice(1);
- if (slug.endsWith("/")) slug = slug.slice(0, -1);
- return slug || "index";
-}
-
-// Helper: Fetch content index (commonly needed for search, graph, explorer)
-async function fetchContentIndex() {
- try {
- const response = await fetch("/static/contentIndex.json");
- const data = await response.json();
- // Handle both formats: { "slug": {...} } or { "content": { "slug": {...} } }
- return data.content || data;
- } catch (error) {
- console.error("[Plugin] Error fetching content index:", error);
- return null;
- }
-}
-
-// Main initialization function
-function init() {
- const components = document.querySelectorAll(".example-component");
- if (components.length === 0) return;
-
- // Example: Track cleanup functions for event listeners
- const cleanupFns = [];
-
- // Example: Add a keyboard shortcut (Ctrl/Cmd + Shift + E)
- function keyboardHandler(e) {
- if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "e") {
- e.preventDefault();
- console.log("[ExampleComponent] Keyboard shortcut triggered!");
- // Do something interesting here
- }
- }
-
- document.addEventListener("keydown", keyboardHandler);
- cleanupFns.push(() => document.removeEventListener("keydown", keyboardHandler));
-
- // Example: Click handler with proper cleanup
- for (const component of components) {
- const clickHandler = () => {
- console.log("[ExampleComponent] Clicked!");
- };
- component.addEventListener("click", clickHandler);
- cleanupFns.push(() => component.removeEventListener("click", clickHandler));
- }
-
- // Register cleanup with Quartz's cleanup system
- if (typeof window !== "undefined" && window.addCleanup) {
- window.addCleanup(() => {
- cleanupFns.forEach((fn) => fn());
- });
- }
-
- console.log("[ExampleComponent] Initialized with", components.length, "component(s)");
-}
-
-// Listen to Quartz navigation events
-// 'nav' fires after page navigation (including initial load)
-document.addEventListener("nav", (e) => {
- const slug = e.detail?.url || getCurrentSlug();
- console.log("[ExampleComponent] Navigation to:", slug);
- init();
-});
-
-// 'prenav' fires before navigation - use for saving state
-document.addEventListener("prenav", () => {
- // Example: Save scroll position before navigation
- const component = document.querySelector(".example-component");
- if (component) {
- sessionStorage.setItem("exampleScrollTop", component.scrollTop?.toString() || "0");
- }
-});
diff --git a/src/components/styles.d.ts b/src/components/styles.d.ts
deleted file mode 100644
index 0a31e5e..0000000
--- a/src/components/styles.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-declare module "*.scss" {
- const content: string;
- export default content;
-}
diff --git a/src/components/styles/example.scss b/src/components/styles/example.scss
deleted file mode 100644
index 49b4379..0000000
--- a/src/components/styles/example.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-.example-component {
- padding: 8px 16px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- border-radius: 4px;
- font-weight: 600;
- display: inline-block;
-}
diff --git a/src/emitter.ts b/src/emitter.ts
deleted file mode 100644
index 00d69be..0000000
--- a/src/emitter.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import path from "node:path";
-import fs from "node:fs/promises";
-import type {
- QuartzEmitterPlugin,
- ProcessedContent,
- BuildCtx,
- FilePath,
- FullSlug,
-} from "@quartz-community/types";
-import type { ExampleEmitterOptions } from "./types";
-
-const defaultOptions: ExampleEmitterOptions = {
- manifestSlug: "plugin-manifest",
- includeFrontmatter: true,
- metadata: {
- generator: "Quartz Plugin Template",
- },
-};
-
-const joinSegments = (...segments: string[]) =>
- segments
- .filter((segment) => segment.length > 0)
- .join("/")
- .replace(/\/+/g, "/") as FilePath;
-
-const writeFile = async (
- outputDir: string,
- slug: FullSlug,
- ext: `.${string}` | "",
- content: string,
-) => {
- const outputPath = joinSegments(outputDir, `${slug}${ext}`) as FilePath;
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
- await fs.writeFile(outputPath, content);
- return outputPath;
-};
-
-/**
- * Example emitter that writes a JSON manifest of content metadata.
- */
-export const ExampleEmitter: QuartzEmitterPlugin> = (
- userOptions?: Partial,
-) => {
- const options = { ...defaultOptions, ...userOptions };
- const emitManifest = async (ctx: BuildCtx, content: ProcessedContent[]) => {
- const manifest = {
- ...options.metadata,
- generatedAt: new Date().toISOString(),
- pages: content.map(([_tree, vfile]) => {
- const frontmatter = (vfile.data?.frontmatter ?? {}) as {
- title?: string;
- tags?: string[];
- [key: string]: unknown;
- };
- return {
- slug: vfile.data?.slug ?? null,
- title: frontmatter.title ?? null,
- tags: frontmatter.tags ?? null,
- filePath: vfile.data?.filePath ?? null,
- frontmatter: options.includeFrontmatter ? frontmatter : undefined,
- };
- }),
- };
-
- let json = `${JSON.stringify(manifest, null, 2)}\n`;
- if (options.transformManifest) {
- json = options.transformManifest(json);
- }
-
- const output = await writeFile(
- ctx.argv.output,
- options.manifestSlug as FullSlug,
- ".json",
- json,
- );
- return [output];
- };
-
- return {
- name: "ExampleEmitter",
- async emit(ctx, content, _resources) {
- return emitManifest(ctx, content);
- },
- async *partialEmit(ctx, content, _resources, _changeEvents) {
- const outputPaths = await emitManifest(ctx, content);
- for (const outputPath of outputPaths) {
- yield outputPath;
- }
- },
- };
-};
diff --git a/src/filter.ts b/src/filter.ts
deleted file mode 100644
index 5d308c6..0000000
--- a/src/filter.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { QuartzFilterPlugin, ProcessedContent, BuildCtx } from "@quartz-community/types";
-import type { ExampleFilterOptions } from "./types";
-
-const defaultOptions: ExampleFilterOptions = {
- allowDrafts: false,
- excludeTags: ["private"],
- excludePathPrefixes: ["_drafts/", "_private/"],
-};
-
-const normalizeTag = (tag: unknown) => (typeof tag === "string" ? tag.trim().toLowerCase() : "");
-
-const includesTag = (tags: unknown, excludedTags: string[]) => {
- if (!Array.isArray(tags)) {
- return false;
- }
-
- const normalizedExcluded = excludedTags.map((tag) => tag.toLowerCase());
- return tags.some((tag) => normalizedExcluded.includes(normalizeTag(tag)));
-};
-
-/**
- * Example filter that removes drafts, tagged pages, and excluded path prefixes.
- */
-export const ExampleFilter: QuartzFilterPlugin> = (
- userOptions?: Partial,
-) => {
- const options = { ...defaultOptions, ...userOptions };
- return {
- name: "ExampleFilter",
- shouldPublish(_ctx: BuildCtx, [_tree, vfile]: ProcessedContent) {
- const frontmatter = (vfile.data?.frontmatter ?? {}) as {
- draft?: boolean | string;
- tags?: string[];
- };
- const isDraft = frontmatter.draft === true || frontmatter.draft === "true";
- if (isDraft && !options.allowDrafts) {
- return false;
- }
-
- if (includesTag(frontmatter.tags, options.excludeTags)) {
- return false;
- }
-
- const filePath = typeof vfile.data?.filePath === "string" ? vfile.data.filePath : "";
- const normalizedPath = filePath.replace(/\\/g, "/");
- if (options.excludePathPrefixes.some((prefix) => normalizedPath.startsWith(prefix))) {
- return false;
- }
-
- return true;
- },
- };
-};
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
deleted file mode 100644
index 8ad01cf..0000000
--- a/src/i18n/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import enUS from "./locales/en-US";
-
-const locales: Record = {
- "en-US": enUS,
-};
-
-export function i18n(locale: string) {
- return locales[locale] || enUS;
-}
diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts
deleted file mode 100644
index 001f5c2..0000000
--- a/src/i18n/locales/en-US.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export default {
- components: {
- example: {
- title: "Example",
- },
- },
-};
diff --git a/src/index.ts b/src/index.ts
index d5c31b9..7f812b8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,28 +1,3 @@
-export { ExampleTransformer } from "./transformer";
-export { ExampleFilter } from "./filter";
-export { ExampleEmitter } from "./emitter";
-export { default as ExampleComponent } from "./components/ExampleComponent";
-
-export type {
- ExampleTransformerOptions,
- ExampleFilterOptions,
- ExampleEmitterOptions,
-} from "./types";
-
-export type { ExampleComponentOptions } from "./components/ExampleComponent";
-
-// Re-export shared types from @quartz-community/types
-export type {
- QuartzComponent,
- QuartzComponentProps,
- QuartzComponentConstructor,
- StringResource,
- QuartzTransformerPlugin,
- QuartzFilterPlugin,
- QuartzEmitterPlugin,
- QuartzPageTypePlugin,
- QuartzPageTypePluginInstance,
- PageMatcher,
- PageGenerator,
- VirtualPage,
-} from "@quartz-community/types";
+export { GitHubFlavoredMarkdown } from "./transformer";
+export type { GfmOptions } from "./transformer";
+export type { QuartzTransformerPlugin } from "@quartz-community/types";
diff --git a/src/transformer.ts b/src/transformer.ts
index 6afeead..426d3eb 100644
--- a/src/transformer.ts
+++ b/src/transformer.ts
@@ -1,103 +1,78 @@
-import type { PluggableList, Plugin } from "unified";
-import type { Root as MdastRoot } from "mdast";
-import type { Root as HastRoot, Element } from "hast";
-import type { VFile } from "vfile";
import remarkGfm from "remark-gfm";
+import smartypants from "remark-smartypants";
+import type { QuartzTransformerPlugin } from "@quartz-community/types";
import rehypeSlug from "rehype-slug";
-import { findAndReplace } from "mdast-util-find-and-replace";
-import { visit } from "unist-util-visit";
-import type { QuartzTransformerPlugin, BuildCtx } from "@quartz-community/types";
-import type { ExampleTransformerOptions } from "./types";
+import rehypeAutolinkHeadings from "rehype-autolink-headings";
-const defaultOptions: ExampleTransformerOptions = {
- highlightToken: "==",
- headingClass: "example-plugin-heading",
- enableGfm: true,
- addHeadingSlugs: true,
+export interface GfmOptions {
+ enableSmartyPants: boolean;
+ linkHeadings: boolean;
+}
+
+const defaultOptions: GfmOptions = {
+ enableSmartyPants: true,
+ linkHeadings: true,
};
-const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
-
-const remarkHighlightToken = (token: string): Plugin<[], MdastRoot> => {
- const escapedToken = escapeRegExp(token);
- const pattern = new RegExp(`${escapedToken}([^\n]+?)${escapedToken}`, "g");
- return () => (tree: MdastRoot, _file: VFile) => {
- findAndReplace(tree, [
- [
- pattern,
- (_match: string, value: string) => ({
- type: "strong",
- children: [{ type: "text", value }],
- }),
- ],
- ]);
- };
-};
-
-const rehypeHeadingClass = (className: string): Plugin<[], HastRoot> => {
- return () => (tree: HastRoot, _file: VFile) => {
- visit(tree, "element", (node: Element) => {
- if (!/^h[1-6]$/.test(node.tagName)) {
- return;
- }
-
- const existing = node.properties?.className;
- const classes: string[] = Array.isArray(existing)
- ? existing.filter((value): value is string => typeof value === "string")
- : typeof existing === "string"
- ? [existing]
- : [];
- node.properties = {
- ...node.properties,
- className: [...classes, className],
- };
- });
- };
-};
-
-/**
- * Example transformer showing remark/rehype usage and resource injection.
- */
-export const ExampleTransformer: QuartzTransformerPlugin> = (
- userOptions?: Partial,
-) => {
- const options = { ...defaultOptions, ...userOptions };
+export const GitHubFlavoredMarkdown: QuartzTransformerPlugin> = (userOpts) => {
+ const opts = { ...defaultOptions, ...userOpts };
return {
- name: "ExampleTransformer",
- textTransform(_ctx: BuildCtx, src: string) {
- return src.endsWith("\n") ? src : `${src}\n`;
+ name: "GitHubFlavoredMarkdown",
+ markdownPlugins() {
+ return opts.enableSmartyPants ? [remarkGfm, smartypants] : [remarkGfm];
},
- markdownPlugins(): PluggableList {
- const plugins: PluggableList = [remarkHighlightToken(options.highlightToken)];
- if (options.enableGfm) {
- plugins.unshift(remarkGfm);
+ htmlPlugins() {
+ if (opts.linkHeadings) {
+ return [
+ rehypeSlug,
+ [
+ rehypeAutolinkHeadings,
+ {
+ behavior: "append",
+ properties: {
+ role: "anchor",
+ ariaHidden: true,
+ tabIndex: -1,
+ "data-no-popover": true,
+ },
+ content: {
+ type: "element",
+ tagName: "svg",
+ properties: {
+ width: 18,
+ height: 18,
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ "stroke-width": "2",
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round",
+ },
+ children: [
+ {
+ type: "element",
+ tagName: "path",
+ properties: {
+ d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",
+ },
+ children: [],
+ },
+ {
+ type: "element",
+ tagName: "path",
+ properties: {
+ d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71",
+ },
+ children: [],
+ },
+ ],
+ },
+ },
+ ],
+ ];
+ } else {
+ return [];
}
- return plugins;
- },
- htmlPlugins(): PluggableList {
- const plugins: PluggableList = [rehypeHeadingClass(options.headingClass)];
- if (options.addHeadingSlugs) {
- plugins.unshift(rehypeSlug);
- }
- return plugins;
- },
- externalResources() {
- return {
- css: [
- {
- content: `.${options.headingClass} { letter-spacing: 0.02em; }`,
- inline: true,
- },
- ],
- js: [
- {
- contentType: "inline",
- loadTime: "afterDOMReady",
- script: "document.documentElement.dataset.exampleTransformer = 'true'",
- },
- ],
- additionalHead: [],
- };
},
};
};
diff --git a/src/types.ts b/src/types.ts
deleted file mode 100644
index f3fac5a..0000000
--- a/src/types.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-export type {
- BuildCtx,
- ChangeEvent,
- CSSResource,
- JSResource,
- ProcessedContent,
- QuartzEmitterPlugin,
- QuartzEmitterPluginInstance,
- QuartzFilterPlugin,
- QuartzFilterPluginInstance,
- QuartzPluginData,
- QuartzTransformerPlugin,
- QuartzTransformerPluginInstance,
- StaticResources,
- PageMatcher,
- PageGenerator,
- VirtualPage,
- QuartzPageTypePlugin,
- QuartzPageTypePluginInstance,
-} from "@quartz-community/types";
-
-export interface ExampleTransformerOptions {
- /** Token used to highlight text, defaults to ==highlight== */
- highlightToken: string;
- /** Add a CSS class to all headings in the rendered HTML. */
- headingClass: string;
- /** Enable remark-gfm for tables/task lists. */
- enableGfm: boolean;
- /** Enable adding slug IDs to headings. */
- addHeadingSlugs: boolean;
-}
-
-export interface ExampleFilterOptions {
- /** Allow pages marked draft: true to publish. */
- allowDrafts: boolean;
- /** Exclude pages that contain any of these frontmatter tags. */
- excludeTags: string[];
- /** Exclude paths that start with any of these prefixes (relative to content root). */
- excludePathPrefixes: string[];
-}
-
-export interface ExampleEmitterOptions {
- /** Filename to emit at the site root. */
- manifestSlug: string;
- /** Whether to include the frontmatter block in the manifest. */
- includeFrontmatter: boolean;
- /** Extra metadata to write at the top level of the manifest. */
- metadata: Record;
- /** Optional hook to transform the emitted manifest JSON string. */
- transformManifest?: (json: string) => string;
- /** Add a custom class to the emitted manifest