-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsource.config.ts
More file actions
105 lines (98 loc) · 4.23 KB
/
Copy pathsource.config.ts
File metadata and controls
105 lines (98 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import type { ShikiTransformer } from "@shikijs/types";
import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
import { metaSchema, pageSchema } from "fumadocs-core/source/schema";
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
import { z } from "zod";
// You can customise Zod schemas for frontmatter and `meta.json` here
// see https://fumadocs.dev/docs/mdx/collections
export const docs = defineDocs({
dir: "content/stack",
docs: {
// `seoTitle` overrides the <title>/OG title for pages whose visual `title`
// is brand-styled (e.g. "/ENCRYPTION") and not ideal for search results.
schema: pageSchema.extend({
seoTitle: z.string().optional(),
}),
postprocess: {
includeProcessedMarkdown: true,
},
},
meta: {
schema: metaSchema,
},
});
// Parse the leftover code-fence meta string (what remains after Fumadocs
// extracts `title`, `tab`, and line-number directives) for the analytics
// attributes documented for authors: `example-id`, `cta`, and `cta-type`.
// Attribute names may contain hyphens, e.g. `example-id="drizzle-basic-query"`.
function parseTrackingAttributes(raw: string): Record<string, string | true> {
const attributes: Record<string, string | true> = {};
// Accept quoted ("…"/'…') and bare unquoted values, so a forgotten quote
// (`example-id=foo`) still parses instead of being silently dropped. A bare
// key with no value (`cta`) is recorded as `true`.
const pattern = /(?<=^|\s)([\w-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
for (const match of raw.matchAll(pattern)) {
const [, name, double, single, unquoted] = match;
attributes[name] = double ?? single ?? unquoted ?? true;
}
return attributes;
}
// Emit code-fence metadata as `data-*` attributes on the rendered `<pre>` so the
// client-side copy button (see `src/components/code-block.tsx`) can report it to
// PostHog. This runs for every code block, so `data-language` is always present
// even when the fence has no meta string (e.g. a plain ```bash block).
const codeCopyTrackingTransformer: ShikiTransformer = {
name: "cipherstash:code-copy-tracking",
pre(node) {
node.properties["data-language"] = this.options.lang ?? "plaintext";
const raw =
typeof this.options.meta?.__raw === "string"
? this.options.meta.__raw
: "";
if (!raw) return;
const attributes = parseTrackingAttributes(raw);
// Only emit attributes for non-empty string values, so `example-id=""`
// falls back to the client-derived id rather than reporting an empty slug.
const exampleId = attributes["example-id"];
if (typeof exampleId === "string" && exampleId !== "") {
node.properties["data-example-id"] = exampleId;
}
// Surface the `filename` so the client can derive a readable fallback
// `example_id` for blocks that lack an explicit `example-id`.
const filename = attributes.filename;
if (typeof filename === "string" && filename !== "") {
node.properties["data-filename"] = filename;
}
const ctaType = attributes["cta-type"];
const hasCtaType = typeof ctaType === "string" && ctaType !== "";
// `cta` is a flag: a bare `cta` (or `cta="true"`) opts in. An explicit
// `cta="false"`/`cta=""` is an opt-out that wins even when a `cta-type` is
// present. A lone `cta-type` (no `cta`) still implies a CTA so the category
// isn't silently dropped.
const ctaFlag = attributes.cta;
const ctaOptOut = ctaFlag === "false" || ctaFlag === "";
const isCta =
!ctaOptOut && (ctaFlag === true || ctaFlag === "true" || hasCtaType);
if (isCta) {
node.properties["data-cta"] = "true";
if (hasCtaType) {
node.properties["data-cta-type"] = ctaType;
}
}
},
};
export default defineConfig({
mdxOptions: {
rehypeCodeOptions: {
// Preserve Fumadocs' default Shiki config (themes, parseMetaString) and
// its default transformers (notation highlight, diff, focus, word
// highlight) — passing `transformers` alone would replace them entirely —
// then append our copy-tracking transformer.
...rehypeCodeDefaultOptions,
transformers: [
...(rehypeCodeDefaultOptions.transformers ?? []),
codeCopyTrackingTransformer,
],
},
},
});