void-plugin-hono-jsx 0.1.0-rc.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +29 -23
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/dist/plugins/hono-islands.d.ts.map +1 -1
  5. package/dist/plugins/hono-islands.js +11 -3
  6. package/dist/plugins/hono-islands.js.map +1 -1
  7. package/dist/plugins/hono-markdown.d.ts +3 -1
  8. package/dist/plugins/hono-markdown.d.ts.map +1 -1
  9. package/dist/plugins/hono-markdown.js +13 -6
  10. package/dist/plugins/hono-markdown.js.map +1 -1
  11. package/dist/plugins/markdown-compiler.d.ts +65 -2
  12. package/dist/plugins/markdown-compiler.d.ts.map +1 -1
  13. package/dist/plugins/markdown-compiler.js +54 -40
  14. package/dist/plugins/markdown-compiler.js.map +1 -1
  15. package/dist/plugins/markdown-copy.d.ts +3 -0
  16. package/dist/plugins/markdown-copy.d.ts.map +1 -0
  17. package/dist/plugins/markdown-copy.js +31 -0
  18. package/dist/plugins/markdown-copy.js.map +1 -0
  19. package/dist/plugins/markdown-features.d.ts +12 -0
  20. package/dist/plugins/markdown-features.d.ts.map +1 -0
  21. package/dist/plugins/markdown-features.js +56 -0
  22. package/dist/plugins/markdown-features.js.map +1 -0
  23. package/dist/theme/code.css +202 -0
  24. package/dist/theme/containers.css +125 -0
  25. package/dist/theme/content.css +4 -0
  26. package/dist/theme/index.css +7 -0
  27. package/dist/theme/prose.css +294 -0
  28. package/dist/theme/reset.css +100 -0
  29. package/package.json +20 -2
  30. package/src/plugins/hono-islands.ts +11 -3
  31. package/src/plugins/hono-markdown.ts +15 -8
  32. package/src/plugins/markdown-compiler.ts +46 -30
  33. package/src/plugins/markdown-copy.ts +30 -0
  34. package/src/plugins/markdown-features.ts +57 -0
  35. package/src/theme/code.css +202 -0
  36. package/src/theme/containers.css +125 -0
  37. package/src/theme/content.css +4 -0
  38. package/src/theme/index.css +7 -0
  39. package/src/theme/prose.css +294 -0
  40. package/src/theme/reset.css +100 -0
@@ -1,3 +1,4 @@
1
+ import { type HonoMarkdownOptions } from './markdown-features.ts';
1
2
  export type Heading = {
2
3
  depth: number;
3
4
  slug: string;
@@ -17,7 +18,67 @@ export declare function extractMarkdown(source: string): {
17
18
  graphSource: string;
18
19
  clientCode: string;
19
20
  };
20
- export declare function compileMarkdown(source: string): {
21
+ export declare function createMarkdownCompiler(options?: HonoMarkdownOptions): {
22
+ parse: (source: string) => {
23
+ body: string;
24
+ script: string;
25
+ imports: {
26
+ start: number;
27
+ end: number;
28
+ importName: string;
29
+ specifierRaw: string;
30
+ specifier: string;
31
+ hydrate: string;
32
+ }[];
33
+ graphSource: string;
34
+ clientCode: string;
35
+ source: string;
36
+ frontmatter: Record<string, unknown>;
37
+ headings: Heading[];
38
+ tokens: import("markdown-exit").Token[];
39
+ env: Record<string, unknown>;
40
+ hasCode: boolean;
41
+ };
42
+ render: (parsed: ReturnType<(source: string) => {
43
+ body: string;
44
+ script: string;
45
+ imports: {
46
+ start: number;
47
+ end: number;
48
+ importName: string;
49
+ specifierRaw: string;
50
+ specifier: string;
51
+ hydrate: string;
52
+ }[];
53
+ graphSource: string;
54
+ clientCode: string;
55
+ source: string;
56
+ frontmatter: Record<string, unknown>;
57
+ headings: Heading[];
58
+ tokens: import("markdown-exit").Token[];
59
+ env: Record<string, unknown>;
60
+ hasCode: boolean;
61
+ }>) => Promise<{
62
+ body: string;
63
+ script: string;
64
+ imports: {
65
+ start: number;
66
+ end: number;
67
+ importName: string;
68
+ specifierRaw: string;
69
+ specifier: string;
70
+ hydrate: string;
71
+ }[];
72
+ graphSource: string;
73
+ clientCode: string;
74
+ source: string;
75
+ frontmatter: Record<string, unknown>;
76
+ headings: Heading[];
77
+ hasCode: boolean;
78
+ html: string;
79
+ }>;
80
+ };
81
+ export declare function compileMarkdown(source: string, options?: HonoMarkdownOptions): Promise<{
21
82
  body: string;
22
83
  script: string;
23
84
  imports: {
@@ -30,10 +91,12 @@ export declare function compileMarkdown(source: string): {
30
91
  }[];
31
92
  graphSource: string;
32
93
  clientCode: string;
94
+ source: string;
33
95
  frontmatter: Record<string, unknown>;
34
96
  headings: Heading[];
97
+ hasCode: boolean;
35
98
  html: string;
36
- };
99
+ }>;
37
100
  export declare function renderChunks(html: string, names: Set<string>): ({
38
101
  html: string;
39
102
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-compiler.d.ts","sourceRoot":"","sources":["../../src/plugins/markdown-compiler.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACpE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;EAU7C;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;EAgC7C;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;UAE/B,MAAM;;UAAa,MAAM;WAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;KAkB/F"}
1
+ {"version":3,"file":"markdown-compiler.d.ts","sourceRoot":"","sources":["../../src/plugins/markdown-compiler.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAO7F,MAAM,MAAM,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACpE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;EAU7C;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,mBAAwB;oBAG/C,MAAM;;;;;;;;;;;;;;;;;;;;qBAkCC,UAAU;;;;;;;;;;;;;;;;;;;MAAc;;;;;;;;;;;;;;;;;;;EAKvD;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;;;;;;;;;;;;;;;;;;GAGtF;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;UAE/B,MAAM;;UAAa,MAAM;WAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;KAkB/F"}
@@ -1,4 +1,5 @@
1
1
  import MarkdownExit from 'markdown-exit';
2
+ import { configureMarkdownFeatures } from './markdown-features.js';
2
3
  import { parseSync } from 'vite';
3
4
  import { parseDocument } from 'yaml';
4
5
  import { findIslandImports, editIslandSource } from 'void/pages-islands-plugin';
@@ -15,50 +16,63 @@ export function extractMarkdown(source) {
15
16
  const clientCode = editIslandSource(script, imports.map(entry => ({ start: entry.start, end: entry.end, text: '' }))).code.trim();
16
17
  return { body, script, imports, graphSource, clientCode };
17
18
  }
18
- export function compileMarkdown(source) {
19
- const extracted = extractMarkdown(source);
20
- const match = extracted.body.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
21
- let frontmatter = {};
22
- if (match) {
23
- const yaml = parseDocument(match[1], { uniqueKeys: true });
24
- if (yaml.errors.length)
25
- throw new Error('Hono Markdown: invalid frontmatter: ' + yaml.errors.map(error => error.message).join('; '));
26
- const value = yaml.toJS({ maxAliasCount: 50 });
27
- if (value !== null && (typeof value !== 'object' || Array.isArray(value)))
28
- throw new Error('Hono Markdown: frontmatter must be a mapping.');
29
- frontmatter = (value ?? {});
30
- try {
31
- JSON.stringify(frontmatter);
19
+ export function createMarkdownCompiler(options = {}) {
20
+ const compiler = new MarkdownExit({ html: true, linkify: true });
21
+ configureMarkdownFeatures(compiler, options);
22
+ function parse(source) {
23
+ const extracted = extractMarkdown(source);
24
+ const match = extracted.body.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
25
+ let frontmatter = {};
26
+ if (match) {
27
+ const yaml = parseDocument(match[1], { uniqueKeys: true });
28
+ if (yaml.errors.length)
29
+ throw new Error('Hono Markdown: invalid frontmatter: ' + yaml.errors.map(error => error.message).join('; '));
30
+ const value = yaml.toJS({ maxAliasCount: 50 });
31
+ if (value !== null && (typeof value !== 'object' || Array.isArray(value)))
32
+ throw new Error('Hono Markdown: frontmatter must be a mapping.');
33
+ frontmatter = (value ?? {});
34
+ try {
35
+ JSON.stringify(frontmatter);
36
+ }
37
+ catch {
38
+ throw new Error('Hono Markdown: frontmatter must be JSON serializable.');
39
+ }
32
40
  }
33
- catch {
34
- throw new Error('Hono Markdown: frontmatter must be JSON serializable.');
41
+ const content = match ? extracted.body.slice(match[0].length) : extracted.body;
42
+ const env = {};
43
+ const tokens = compiler.parse(content, env);
44
+ const headings = [];
45
+ const slugs = new Map();
46
+ const authoredHtml = tokens.flatMap(token => token.type === 'html_block' || token.type === 'html_inline' ? [token.content] : (token.children ?? []).filter(child => child.type === 'html_inline').map(child => child.content)).join('\n');
47
+ const usedIds = new Set([...authoredHtml.matchAll(/\bid\s*=\s*(?:"([^"]*)"|'([^']*)')/g)].map(match => match[1] ?? match[2]));
48
+ for (let index = 0; index < tokens.length; index++) {
49
+ if (tokens[index].type !== 'heading_open')
50
+ continue;
51
+ const inline = tokens[index + 1];
52
+ const text = inline.children?.map(token => token.type === 'text' || token.type === 'code_inline' ? token.content : token.type === 'image' ? token.content : '').join('') ?? inline.content;
53
+ const base = text.toLowerCase().replace(/[^\p{L}\p{N} _-]/gu, '').trim().replace(/\s+/g, '-') || 'heading';
54
+ let count = slugs.get(base) ?? 0;
55
+ let slug = count ? `${base}-${count}` : base;
56
+ while (usedIds.has(slug)) {
57
+ count++;
58
+ slug = `${base}-${count}`;
59
+ }
60
+ slugs.set(base, count + 1);
61
+ usedIds.add(slug);
62
+ tokens[index].attrSet('id', slug);
63
+ headings.push({ depth: Number(tokens[index].tag.slice(1)), slug, text });
35
64
  }
65
+ return { ...extracted, source, frontmatter, headings, tokens, env, hasCode: tokens.some(token => token.type === 'fence') };
36
66
  }
37
- const content = match ? extracted.body.slice(match[0].length) : extracted.body;
38
- const compiler = new MarkdownExit({ html: true, linkify: true });
39
- const tokens = compiler.parse(content, {});
40
- const headings = [];
41
- const slugs = new Map();
42
- const authoredHtml = tokens.flatMap(token => token.type === 'html_block' || token.type === 'html_inline' ? [token.content] : (token.children ?? []).filter(child => child.type === 'html_inline').map(child => child.content)).join('\n');
43
- const usedIds = new Set([...authoredHtml.matchAll(/\bid\s*=\s*(?:"([^"]*)"|'([^']*)')/g)].map(match => match[1] ?? match[2]));
44
- for (let index = 0; index < tokens.length; index++) {
45
- if (tokens[index].type !== 'heading_open')
46
- continue;
47
- const inline = tokens[index + 1];
48
- const text = inline.children?.map(token => token.type === 'text' || token.type === 'code_inline' ? token.content : token.type === 'image' ? token.content : '').join('') ?? inline.content;
49
- const base = text.toLowerCase().replace(/[^\p{L}\p{N} _-]/gu, '').trim().replace(/\s+/g, '-') || 'heading';
50
- let count = slugs.get(base) ?? 0;
51
- let slug = count ? `${base}-${count}` : base;
52
- while (usedIds.has(slug)) {
53
- count++;
54
- slug = `${base}-${count}`;
55
- }
56
- slugs.set(base, count + 1);
57
- usedIds.add(slug);
58
- tokens[index].attrSet('id', slug);
59
- headings.push({ depth: Number(tokens[index].tag.slice(1)), slug, text });
67
+ async function render(parsed) {
68
+ const { tokens, env, ...result } = parsed;
69
+ return { ...result, html: await compiler.renderer.renderAsync(tokens, compiler.options, env) };
60
70
  }
61
- return { ...extracted, frontmatter, headings, html: compiler.renderer.render(tokens, compiler.options, {}) };
71
+ return { parse, render };
72
+ }
73
+ export async function compileMarkdown(source, options = {}) {
74
+ const compiler = createMarkdownCompiler(options);
75
+ return compiler.render(compiler.parse(source));
62
76
  }
63
77
  // Component tag splitting follows @void/md 0.27.0's emit-shared convention.
64
78
  // Hono raw fragments preserve enclosing HTML across the component boundaries.
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-compiler.js","sourceRoot":"","sources":["../../src/plugins/markdown-compiler.ts"],"names":[],"mappings":"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","sourcesContent":["import MarkdownExit from 'markdown-exit';\nimport { parseSync } from 'vite';\nimport { parseDocument } from 'yaml';\nimport { findIslandImports, editIslandSource } from 'void/pages-islands-plugin';\n\nconst unescapeAttribute = new MarkdownExit().utils.unescapeAll;\n\nexport type Heading = { depth: number; slug: string; text: string };\nexport function extractMarkdown(source: string) {\n const match = source.match(/^<script\\b[^>]*>([\\s\\S]*?)<\\/script>\\s*\\n?/);\n const script = match?.[1] ?? '';\n const body = match ? source.slice(match[0].length) : source;\n const imports = findIslandImports(script, 'markdown-script.ts');\n const attributed = parseSync('markdown-script.ts', script).program.body.filter(node => node.type === 'ImportDeclaration' && node.attributes?.some(({ key }) => ('name' in key && key.name === 'island') || ('value' in key && key.value === 'island')));\n if (attributed.length !== imports.length) throw new Error('Hono Markdown: use one default component import with exactly one island attribute.');\n const graphSource = imports.map(entry => script.slice(entry.start, entry.end)).join('\\n');\n const clientCode = editIslandSource(script, imports.map(entry => ({ start: entry.start, end: entry.end, text: '' }))).code.trim();\n return { body, script, imports, graphSource, clientCode };\n}\n\nexport function compileMarkdown(source: string) {\n const extracted = extractMarkdown(source);\n const match = extracted.body.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/);\n let frontmatter: Record<string, unknown> = {};\n if (match) {\n const yaml = parseDocument(match[1], { uniqueKeys: true });\n if (yaml.errors.length) throw new Error('Hono Markdown: invalid frontmatter: ' + yaml.errors.map(error => error.message).join('; '));\n const value: unknown = yaml.toJS({ maxAliasCount: 50 });\n if (value !== null && (typeof value !== 'object' || Array.isArray(value))) throw new Error('Hono Markdown: frontmatter must be a mapping.');\n frontmatter = (value ?? {}) as Record<string, unknown>;\n try { JSON.stringify(frontmatter); } catch { throw new Error('Hono Markdown: frontmatter must be JSON serializable.'); }\n }\n const content = match ? extracted.body.slice(match[0].length) : extracted.body;\n const compiler = new MarkdownExit({ html: true, linkify: true });\n const tokens = compiler.parse(content, {});\n const headings: Heading[] = [];\n const slugs = new Map<string, number>();\n const authoredHtml = tokens.flatMap(token => token.type === 'html_block' || token.type === 'html_inline' ? [token.content] : (token.children ?? []).filter(child => child.type === 'html_inline').map(child => child.content)).join('\\n');\n const usedIds = new Set([...authoredHtml.matchAll(/\\bid\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/g)].map(match => match[1] ?? match[2]));\n for (let index = 0; index < tokens.length; index++) {\n if (tokens[index].type !== 'heading_open') continue;\n const inline = tokens[index + 1];\n const text = inline.children?.map(token => token.type === 'text' || token.type === 'code_inline' ? token.content : token.type === 'image' ? token.content : '').join('') ?? inline.content;\n const base = text.toLowerCase().replace(/[^\\p{L}\\p{N} _-]/gu, '').trim().replace(/\\s+/g, '-') || 'heading';\n let count = slugs.get(base) ?? 0;\n let slug = count ? `${base}-${count}` : base;\n while (usedIds.has(slug)) { count++; slug = `${base}-${count}`; }\n slugs.set(base, count + 1); usedIds.add(slug);\n tokens[index].attrSet('id', slug);\n headings.push({ depth: Number(tokens[index].tag.slice(1)), slug, text });\n }\n return { ...extracted, frontmatter, headings, html: compiler.renderer.render(tokens, compiler.options, {}) };\n}\n\n// Component tag splitting follows @void/md 0.27.0's emit-shared convention.\n// Hono raw fragments preserve enclosing HTML across the component boundaries.\nexport function renderChunks(html: string, names: Set<string>) {\n const tags = /<([A-Z]\\w*)\\s*((?:[^>/\"']|\"[^\"]*\"|'[^']*')*)\\/\\s*>|<([A-Z]\\w*)\\s*((?:[^>/\"']|\"[^\"]*\"|'[^']*')*)>([\\s\\S]*?)<\\/\\3\\s*>/g;\n const chunks: Array<{ html: string } | { name: string; props: Record<string, string | boolean> }> = [];\n let cursor = 0;\n for (const match of html.matchAll(tags)) {\n const name = match[1] ?? match[3];\n if (!names.has(name)) continue;\n if (match[5]?.trim()) throw new Error('Hono Markdown: island tag children are unsupported; pass literal attributes to a self-closing tag.');\n if (match.index > cursor) chunks.push({ html: html.slice(cursor, match.index) });\n const props: Record<string, string | boolean> = Object.create(null);\n const attrs = match[2] ?? match[4] ?? '';\n const attrRe = /(\\w[\\w-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s]+)))?/g;\n for (const attr of attrs.matchAll(attrRe)) {\n const value = attr[2] ?? attr[3] ?? attr[4];\n props[attr[1]] = value === undefined ? true : unescapeAttribute(value);\n }\n chunks.push({ name, props }); cursor = match.index + match[0].length;\n }\n if (cursor < html.length) chunks.push({ html: html.slice(cursor) });\n return chunks;\n}\n"]}
1
+ {"version":3,"file":"markdown-compiler.js","sourceRoot":"","sources":["../../src/plugins/markdown-compiler.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAA4B,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEhF,MAAM,iBAAiB,GAAG,IAAI,YAAY,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC;AAG/D,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5D,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,SAAS,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,mBAAmB,IAAI,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC;IACxP,IAAI,UAAU,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;IAChJ,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1F,MAAM,UAAU,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAClI,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAO,GAAwB,EAAE;IACtE,MAAM,QAAQ,GAAG,IAAI,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7C,SAAS,KAAK,CAAC,MAAc;QAC3B,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC7E,IAAI,WAAW,GAA4B,EAAE,CAAC;QAC9C,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACrI,MAAM,KAAK,GAAY,IAAI,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC;YACxD,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC5I,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;YACvD,IAAI,CAAC;gBAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;YAAC,CAAC;QAC1H,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;QAC/E,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;QACxC,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1O,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,qCAAqC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9H,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YACnD,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,cAAc;gBAAE,SAAS;YACpD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC;YAC3L,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,SAAS,CAAC;YAC3G,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,IAAI,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAC7C,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAC,KAAK,EAAE,CAAC;gBAAC,IAAI,GAAG,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;YAAC,CAAC;YACjE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC;IAC7H,CAAC;IAED,KAAK,UAAU,MAAM,CAAC,MAAgC;QACpD,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC;QAC1C,OAAO,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;IACjG,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc,EAAE,OAAO,GAAwB,EAAE;IACrF,MAAM,QAAQ,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACjD,OAAO,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,4EAA4E;AAC5E,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAkB;IAC3D,MAAM,IAAI,GAAG,sHAAsH,CAAC;IACpI,MAAM,MAAM,GAAwF,EAAE,CAAC;IACvG,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/B,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,oGAAoG,CAAC,CAAC;QAC5I,IAAI,KAAK,CAAC,KAAK,GAAG,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACjF,MAAM,KAAK,GAAqC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,yDAAyD,CAAC;QACzE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAAC,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACvE,CAAC;IACD,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM;QAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import MarkdownExit from 'markdown-exit';\nimport { configureMarkdownFeatures, type HonoMarkdownOptions } from './markdown-features.ts';\nimport { parseSync } from 'vite';\nimport { parseDocument } from 'yaml';\nimport { findIslandImports, editIslandSource } from 'void/pages-islands-plugin';\n\nconst unescapeAttribute = new MarkdownExit().utils.unescapeAll;\n\nexport type Heading = { depth: number; slug: string; text: string };\nexport function extractMarkdown(source: string) {\n const match = source.match(/^<script\\b[^>]*>([\\s\\S]*?)<\\/script>\\s*\\n?/);\n const script = match?.[1] ?? '';\n const body = match ? source.slice(match[0].length) : source;\n const imports = findIslandImports(script, 'markdown-script.ts');\n const attributed = parseSync('markdown-script.ts', script).program.body.filter(node => node.type === 'ImportDeclaration' && node.attributes?.some(({ key }) => ('name' in key && key.name === 'island') || ('value' in key && key.value === 'island')));\n if (attributed.length !== imports.length) throw new Error('Hono Markdown: use one default component import with exactly one island attribute.');\n const graphSource = imports.map(entry => script.slice(entry.start, entry.end)).join('\\n');\n const clientCode = editIslandSource(script, imports.map(entry => ({ start: entry.start, end: entry.end, text: '' }))).code.trim();\n return { body, script, imports, graphSource, clientCode };\n}\n\nexport function createMarkdownCompiler(options: HonoMarkdownOptions = {}) {\n const compiler = new MarkdownExit({ html: true, linkify: true });\n configureMarkdownFeatures(compiler, options);\n function parse(source: string) {\n const extracted = extractMarkdown(source);\n const match = extracted.body.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/);\n let frontmatter: Record<string, unknown> = {};\n if (match) {\n const yaml = parseDocument(match[1], { uniqueKeys: true });\n if (yaml.errors.length) throw new Error('Hono Markdown: invalid frontmatter: ' + yaml.errors.map(error => error.message).join('; '));\n const value: unknown = yaml.toJS({ maxAliasCount: 50 });\n if (value !== null && (typeof value !== 'object' || Array.isArray(value))) throw new Error('Hono Markdown: frontmatter must be a mapping.');\n frontmatter = (value ?? {}) as Record<string, unknown>;\n try { JSON.stringify(frontmatter); } catch { throw new Error('Hono Markdown: frontmatter must be JSON serializable.'); }\n }\n const content = match ? extracted.body.slice(match[0].length) : extracted.body;\n const env: Record<string, unknown> = {};\n const tokens = compiler.parse(content, env);\n const headings: Heading[] = [];\n const slugs = new Map<string, number>();\n const authoredHtml = tokens.flatMap(token => token.type === 'html_block' || token.type === 'html_inline' ? [token.content] : (token.children ?? []).filter(child => child.type === 'html_inline').map(child => child.content)).join('\\n');\n const usedIds = new Set([...authoredHtml.matchAll(/\\bid\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/g)].map(match => match[1] ?? match[2]));\n for (let index = 0; index < tokens.length; index++) {\n if (tokens[index].type !== 'heading_open') continue;\n const inline = tokens[index + 1];\n const text = inline.children?.map(token => token.type === 'text' || token.type === 'code_inline' ? token.content : token.type === 'image' ? token.content : '').join('') ?? inline.content;\n const base = text.toLowerCase().replace(/[^\\p{L}\\p{N} _-]/gu, '').trim().replace(/\\s+/g, '-') || 'heading';\n let count = slugs.get(base) ?? 0;\n let slug = count ? `${base}-${count}` : base;\n while (usedIds.has(slug)) { count++; slug = `${base}-${count}`; }\n slugs.set(base, count + 1); usedIds.add(slug);\n tokens[index].attrSet('id', slug);\n headings.push({ depth: Number(tokens[index].tag.slice(1)), slug, text });\n }\n return { ...extracted, source, frontmatter, headings, tokens, env, hasCode: tokens.some(token => token.type === 'fence') };\n }\n\n async function render(parsed: ReturnType<typeof parse>) {\n const { tokens, env, ...result } = parsed;\n return { ...result, html: await compiler.renderer.renderAsync(tokens, compiler.options, env) };\n }\n return { parse, render };\n}\n\nexport async function compileMarkdown(source: string, options: HonoMarkdownOptions = {}) {\n const compiler = createMarkdownCompiler(options);\n return compiler.render(compiler.parse(source));\n}\n\n// Component tag splitting follows @void/md 0.27.0's emit-shared convention.\n// Hono raw fragments preserve enclosing HTML across the component boundaries.\nexport function renderChunks(html: string, names: Set<string>) {\n const tags = /<([A-Z]\\w*)\\s*((?:[^>/\"']|\"[^\"]*\"|'[^']*')*)\\/\\s*>|<([A-Z]\\w*)\\s*((?:[^>/\"']|\"[^\"]*\"|'[^']*')*)>([\\s\\S]*?)<\\/\\3\\s*>/g;\n const chunks: Array<{ html: string } | { name: string; props: Record<string, string | boolean> }> = [];\n let cursor = 0;\n for (const match of html.matchAll(tags)) {\n const name = match[1] ?? match[3];\n if (!names.has(name)) continue;\n if (match[5]?.trim()) throw new Error('Hono Markdown: island tag children are unsupported; pass literal attributes to a self-closing tag.');\n if (match.index > cursor) chunks.push({ html: html.slice(cursor, match.index) });\n const props: Record<string, string | boolean> = Object.create(null);\n const attrs = match[2] ?? match[4] ?? '';\n const attrRe = /(\\w[\\w-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s]+)))?/g;\n for (const attr of attrs.matchAll(attrRe)) {\n const value = attr[2] ?? attr[3] ?? attr[4];\n props[attr[1]] = value === undefined ? true : unescapeAttribute(value);\n }\n chunks.push({ name, props }); cursor = match.index + match[0].length;\n }\n if (cursor < html.length) chunks.push({ html: html.slice(cursor) });\n return chunks;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export declare const markdownCopyId = "virtual:void-hono-markdown-copy";
2
+ export declare const markdownCopyCode = "\nconst timers = new WeakMap();\ndocument.addEventListener('click', async event => {\n const button = event.target instanceof Element && event.target.closest(\"div[class*='language-'] > button.copy\");\n if (!button || button.disabled) return;\n const pre = button.parentElement?.querySelector('pre');\n if (!pre) return;\n clearTimeout(timers.get(button));\n button.classList.remove('copied', 'copy-error');\n const reset = () => { button.classList.remove('copied', 'copy-error'); button.title = 'Copy Code'; button.setAttribute('aria-label', 'Copy Code'); };\n try {\n if (!navigator.clipboard?.writeText) throw new Error('Clipboard unavailable');\n const lines = Array.from(pre.querySelectorAll('.line:not(.diff.remove)'), line => line.textContent);\n if (lines.at(-1) === '') lines.pop();\n button.disabled = true;\n await navigator.clipboard.writeText(lines.join('\\n'));\n button.classList.add('copied');\n button.title = 'Copied'; button.setAttribute('aria-label', 'Copied');\n } catch {\n button.classList.add('copy-error');\n button.title = 'Copy failed'; button.setAttribute('aria-label', 'Copy failed');\n } finally {\n button.disabled = false;\n timers.set(button, setTimeout(reset, 2000));\n }\n});\n";
3
+ //# sourceMappingURL=markdown-copy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-copy.d.ts","sourceRoot":"","sources":["../../src/plugins/markdown-copy.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,oCAAoC,CAAC;AAChE,eAAO,MAAM,gBAAgB,8uCA0B5B,CAAC"}
@@ -0,0 +1,31 @@
1
+ // Imported as a module only by Markdown pages with fenced code. No Hono runtime
2
+ // or island scheduler is required for code-copy-only documents.
3
+ export const markdownCopyId = 'virtual:void-hono-markdown-copy';
4
+ export const markdownCopyCode = `
5
+ const timers = new WeakMap();
6
+ document.addEventListener('click', async event => {
7
+ const button = event.target instanceof Element && event.target.closest("div[class*='language-'] > button.copy");
8
+ if (!button || button.disabled) return;
9
+ const pre = button.parentElement?.querySelector('pre');
10
+ if (!pre) return;
11
+ clearTimeout(timers.get(button));
12
+ button.classList.remove('copied', 'copy-error');
13
+ const reset = () => { button.classList.remove('copied', 'copy-error'); button.title = 'Copy Code'; button.setAttribute('aria-label', 'Copy Code'); };
14
+ try {
15
+ if (!navigator.clipboard?.writeText) throw new Error('Clipboard unavailable');
16
+ const lines = Array.from(pre.querySelectorAll('.line:not(.diff.remove)'), line => line.textContent);
17
+ if (lines.at(-1) === '') lines.pop();
18
+ button.disabled = true;
19
+ await navigator.clipboard.writeText(lines.join('\\n'));
20
+ button.classList.add('copied');
21
+ button.title = 'Copied'; button.setAttribute('aria-label', 'Copied');
22
+ } catch {
23
+ button.classList.add('copy-error');
24
+ button.title = 'Copy failed'; button.setAttribute('aria-label', 'Copy failed');
25
+ } finally {
26
+ button.disabled = false;
27
+ timers.set(button, setTimeout(reset, 2000));
28
+ }
29
+ });
30
+ `;
31
+ //# sourceMappingURL=markdown-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-copy.js","sourceRoot":"","sources":["../../src/plugins/markdown-copy.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,iCAAiC,CAAC;AAChE,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CA0B/B,CAAC","sourcesContent":["// Imported as a module only by Markdown pages with fenced code. No Hono runtime\n// or island scheduler is required for code-copy-only documents.\nexport const markdownCopyId = 'virtual:void-hono-markdown-copy';\nexport const markdownCopyCode = `\nconst timers = new WeakMap();\ndocument.addEventListener('click', async event => {\n const button = event.target instanceof Element && event.target.closest(\"div[class*='language-'] > button.copy\");\n if (!button || button.disabled) return;\n const pre = button.parentElement?.querySelector('pre');\n if (!pre) return;\n clearTimeout(timers.get(button));\n button.classList.remove('copied', 'copy-error');\n const reset = () => { button.classList.remove('copied', 'copy-error'); button.title = 'Copy Code'; button.setAttribute('aria-label', 'Copy Code'); };\n try {\n if (!navigator.clipboard?.writeText) throw new Error('Clipboard unavailable');\n const lines = Array.from(pre.querySelectorAll('.line:not(.diff.remove)'), line => line.textContent);\n if (lines.at(-1) === '') lines.pop();\n button.disabled = true;\n await navigator.clipboard.writeText(lines.join('\\\\n'));\n button.classList.add('copied');\n button.title = 'Copied'; button.setAttribute('aria-label', 'Copied');\n } catch {\n button.classList.add('copy-error');\n button.title = 'Copy failed'; button.setAttribute('aria-label', 'Copy failed');\n } finally {\n button.disabled = false;\n timers.set(button, setTimeout(reset, 2000));\n }\n});\n`;\n"]}
@@ -0,0 +1,12 @@
1
+ import type MarkdownExit from 'markdown-exit';
2
+ /** Matches @void/md's public Markdown options; languages load on demand. */
3
+ export type HonoMarkdownOptions = {
4
+ shiki?: {
5
+ themes?: {
6
+ light: string;
7
+ dark: string;
8
+ };
9
+ };
10
+ };
11
+ export declare function configureMarkdownFeatures(md: MarkdownExit, options: HonoMarkdownOptions): void;
12
+ //# sourceMappingURL=markdown-features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-features.d.ts","sourceRoot":"","sources":["../../src/plugins/markdown-features.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,YAAY,MAAM,eAAe,CAAC;AAG9C,4EAA4E;AAC5E,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACtD,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,QA+CvF"}
@@ -0,0 +1,56 @@
1
+ import { createRequire } from 'node:module';
2
+ export function configureMarkdownFeatures(md, options) {
3
+ const container = createRequire(import.meta.url)('markdown-it-container');
4
+ for (const [kind, title] of [['tip', 'TIP'], ['info', 'INFO'], ['warning', 'WARNING'], ['danger', 'DANGER'], ['details', 'Details']]) {
5
+ md.use(container, kind, { render(tokens, index, _options, env) {
6
+ if (tokens[index].nesting !== 1)
7
+ return kind === 'details' ? '</details>\n' : '</div>\n';
8
+ const authored = tokens[index].info.trim().slice(kind.length).trim();
9
+ const rendered = md.renderInline(authored || title, { references: env.references });
10
+ if (kind === 'details')
11
+ return `<details class="${kind} custom-block"><summary>${rendered}</summary>\n`;
12
+ return `<div class="${kind} custom-block"><p class="custom-block-title${authored ? '' : ' custom-block-title-default'}">${rendered}</p>\n`;
13
+ } });
14
+ }
15
+ // One highlighter per plugin, not a global first-options-wins singleton.
16
+ const themes = { light: options.shiki?.themes?.light ?? 'github-light', dark: options.shiki?.themes?.dark ?? 'github-dark' };
17
+ const initialize = async () => {
18
+ const [shiki, { createJavaScriptRegexEngine }, transformers] = await Promise.all([
19
+ import('shiki'), import('shiki/engine/javascript'), import('@shikijs/transformers'),
20
+ ]);
21
+ for (const theme of Object.values(themes))
22
+ if (!Object.hasOwn(shiki.bundledThemes, theme))
23
+ throw new Error(`Hono Markdown: unknown Shiki theme ${JSON.stringify(theme)}.`);
24
+ const selected = themes;
25
+ const highlighter = await shiki.createHighlighter({ engine: createJavaScriptRegexEngine(), themes: [...new Set(Object.values(selected))], langs: [] });
26
+ return { highlighter, selected, languages: shiki.bundledLanguages, transformers: [
27
+ transformers.transformerMetaHighlight(), transformers.transformerNotationDiff(),
28
+ transformers.transformerNotationHighlight(), transformers.transformerNotationFocus(), transformers.transformerNotationErrorLevel(),
29
+ ] };
30
+ };
31
+ let initialized;
32
+ const loading = new Map();
33
+ md.renderer.rules.fence = async (tokens, index) => {
34
+ const token = tokens[index];
35
+ const { highlighter, selected, languages, transformers } = await (initialized ??= initialize());
36
+ const label = /^[\w+-]+/.exec(token.info.trim())?.[0] ?? 'text';
37
+ let lang = label;
38
+ if (!['text', 'txt', 'plain', 'plaintext', 'ansi'].includes(lang) && !highlighter.getLoadedLanguages().includes(lang)) {
39
+ if (Object.hasOwn(languages, lang)) {
40
+ if (!loading.has(lang))
41
+ loading.set(lang, highlighter.loadLanguage(lang));
42
+ await loading.get(lang);
43
+ }
44
+ else
45
+ lang = 'text';
46
+ }
47
+ const highlighted = highlighter.codeToHtml(token.content, { lang, themes: selected, defaultColor: 'light', transformers, meta: { __raw: token.info } });
48
+ const numbers = /:line-numbers\b/.test(token.info);
49
+ const start = Number(/\{=(\d+)\}/.exec(token.info)?.[1] ?? 1);
50
+ const count = [...highlighted.matchAll(/class="line(?:[ "])/g)].length;
51
+ const gutter = numbers ? `<div class="line-numbers-wrapper" aria-hidden="true">${Array.from({ length: count }, (_, offset) => `<span class="line-number">${start + offset}</span><br>`).join('')}</div>` : '';
52
+ const active = /(?:^|\s)active(?:\s|$)/.test(token.info) ? ' active' : '';
53
+ return `<div class="language-${md.utils.escapeHtml(label)}${active}${numbers ? ' line-numbers-mode' : ''}"><button type="button" title="Copy Code" aria-label="Copy Code" class="copy"></button><span class="lang">${md.utils.escapeHtml(label)}</span>${highlighted}${gutter}</div>\n`;
54
+ };
55
+ }
56
+ //# sourceMappingURL=markdown-features.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-features.js","sourceRoot":"","sources":["../../src/plugins/markdown-features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAS5C,MAAM,UAAU,yBAAyB,CAAC,EAAgB,EAAE,OAA4B;IACtF,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,uBAAuB,CAAqB,CAAC;IAC9F,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;QACrI,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,MAAkE,EAAE,KAAa,EAAE,QAAiB,EAAE,GAA+D;gBACpM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC;oBAAE,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;gBACzF,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACrE,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,IAAI,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;gBACpF,IAAI,IAAI,KAAK,SAAS;oBAAE,OAAO,mBAAmB,IAAI,2BAA2B,QAAQ,cAAc,CAAC;gBACxG,OAAO,eAAe,IAAI,8CAA8C,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,6BAA6B,KAAK,QAAQ,QAAQ,CAAC;YAC7I,CAAC,EAAE,CAAC,CAAC;IACP,CAAC;IAED,yEAAyE;IACzE,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,IAAI,aAAa,EAAE,CAAC;IAC7H,MAAM,UAAU,GAAG,KAAK,IAAI,EAAE;QAC5B,MAAM,CAAC,KAAK,EAAE,EAAE,2BAA2B,EAAE,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC/E,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,yBAAyB,CAAC,EAAE,MAAM,CAAC,uBAAuB,CAAC;SACpF,CAAC,CAAC;QACH,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3K,MAAM,QAAQ,GAAG,MAA6F,CAAC;QAC/G,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,2BAA2B,EAAE,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACvJ,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,gBAAgB,EAAE,YAAY,EAAE;gBAC/E,YAAY,CAAC,wBAAwB,EAAE,EAAE,YAAY,CAAC,uBAAuB,EAAE;gBAC/E,YAAY,CAAC,4BAA4B,EAAE,EAAE,YAAY,CAAC,wBAAwB,EAAE,EAAE,YAAY,CAAC,6BAA6B,EAAE;aACnI,EAAE,CAAC;IACN,CAAC,CAAC;IACF,IAAI,WAAsD,CAAC;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACjD,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;QAChD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC,CAAC;QAChG,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;QAChE,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACtH,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,YAAY,CAAC,IAA8B,CAAC,CAAC,CAAC;gBACpG,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;;gBAAM,IAAI,GAAG,MAAM,CAAC;QACvB,CAAC;QACD,MAAM,WAAW,GAAG,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACxJ,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC,MAAM,CAAC;QACvE,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,wDAAwD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,6BAA6B,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9M,MAAM,MAAM,GAAG,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,OAAO,wBAAwB,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,6GAA6G,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,WAAW,GAAG,MAAM,UAAU,CAAC;IAC1R,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { createRequire } from 'node:module';\nimport type MarkdownExit from 'markdown-exit';\nimport type { PluginWithParams } from 'markdown-exit';\n\n/** Matches @void/md's public Markdown options; languages load on demand. */\nexport type HonoMarkdownOptions = {\n shiki?: { themes?: { light: string; dark: string } };\n};\n\nexport function configureMarkdownFeatures(md: MarkdownExit, options: HonoMarkdownOptions) {\n const container = createRequire(import.meta.url)('markdown-it-container') as PluginWithParams;\n for (const [kind, title] of [['tip', 'TIP'], ['info', 'INFO'], ['warning', 'WARNING'], ['danger', 'DANGER'], ['details', 'Details']]) {\n md.use(container, kind, { render(tokens: Parameters<NonNullable<typeof md.renderer.rules.fence>>[0], index: number, _options: unknown, env: Parameters<NonNullable<typeof md.renderer.rules.fence>>[3]) {\n if (tokens[index].nesting !== 1) return kind === 'details' ? '</details>\\n' : '</div>\\n';\n const authored = tokens[index].info.trim().slice(kind.length).trim();\n const rendered = md.renderInline(authored || title, { references: env.references });\n if (kind === 'details') return `<details class=\"${kind} custom-block\"><summary>${rendered}</summary>\\n`;\n return `<div class=\"${kind} custom-block\"><p class=\"custom-block-title${authored ? '' : ' custom-block-title-default'}\">${rendered}</p>\\n`;\n } });\n }\n\n // One highlighter per plugin, not a global first-options-wins singleton.\n const themes = { light: options.shiki?.themes?.light ?? 'github-light', dark: options.shiki?.themes?.dark ?? 'github-dark' };\n const initialize = async () => {\n const [shiki, { createJavaScriptRegexEngine }, transformers] = await Promise.all([\n import('shiki'), import('shiki/engine/javascript'), import('@shikijs/transformers'),\n ]);\n for (const theme of Object.values(themes)) if (!Object.hasOwn(shiki.bundledThemes, theme)) throw new Error(`Hono Markdown: unknown Shiki theme ${JSON.stringify(theme)}.`);\n const selected = themes as { light: keyof typeof shiki.bundledThemes; dark: keyof typeof shiki.bundledThemes };\n const highlighter = await shiki.createHighlighter({ engine: createJavaScriptRegexEngine(), themes: [...new Set(Object.values(selected))], langs: [] });\n return { highlighter, selected, languages: shiki.bundledLanguages, transformers: [\n transformers.transformerMetaHighlight(), transformers.transformerNotationDiff(),\n transformers.transformerNotationHighlight(), transformers.transformerNotationFocus(), transformers.transformerNotationErrorLevel(),\n ] };\n };\n let initialized: ReturnType<typeof initialize> | undefined;\n const loading = new Map<string, Promise<void>>();\n md.renderer.rules.fence = async (tokens, index) => {\n const token = tokens[index];\n const { highlighter, selected, languages, transformers } = await (initialized ??= initialize());\n const label = /^[\\w+-]+/.exec(token.info.trim())?.[0] ?? 'text';\n let lang = label;\n if (!['text', 'txt', 'plain', 'plaintext', 'ansi'].includes(lang) && !highlighter.getLoadedLanguages().includes(lang)) {\n if (Object.hasOwn(languages, lang)) {\n if (!loading.has(lang)) loading.set(lang, highlighter.loadLanguage(lang as keyof typeof languages));\n await loading.get(lang);\n } else lang = 'text';\n }\n const highlighted = highlighter.codeToHtml(token.content, { lang, themes: selected, defaultColor: 'light', transformers, meta: { __raw: token.info } });\n const numbers = /:line-numbers\\b/.test(token.info);\n const start = Number(/\\{=(\\d+)\\}/.exec(token.info)?.[1] ?? 1);\n const count = [...highlighted.matchAll(/class=\"line(?:[ \"])/g)].length;\n const gutter = numbers ? `<div class=\"line-numbers-wrapper\" aria-hidden=\"true\">${Array.from({ length: count }, (_, offset) => `<span class=\"line-number\">${start + offset}</span><br>`).join('')}</div>` : '';\n const active = /(?:^|\\s)active(?:\\s|$)/.test(token.info) ? ' active' : '';\n return `<div class=\"language-${md.utils.escapeHtml(label)}${active}${numbers ? ' line-numbers-mode' : ''}\"><button type=\"button\" title=\"Copy Code\" aria-label=\"Copy Code\" class=\"copy\"></button><span class=\"lang\">${md.utils.escapeHtml(label)}</span>${highlighted}${gutter}</div>\\n`;\n };\n}\n"]}
@@ -0,0 +1,202 @@
1
+ /* Void Markdown — Code Blocks */
2
+
3
+ .void-md div[class*='language-'] {
4
+ position: relative;
5
+ margin: 1em 0;
6
+ border-radius: 8px;
7
+ overflow: hidden;
8
+ background: var(--vmd-bg-soft, #f8fafc);
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :root:not([data-theme='light']) .void-md div[class*='language-'] {
13
+ background: #1e293b;
14
+ }
15
+ }
16
+
17
+ :root[data-theme='dark'] .void-md div[class*='language-'] {
18
+ background: #1e293b;
19
+ }
20
+
21
+ .void-md div[class*='language-'] pre {
22
+ margin: 0;
23
+ padding: 1.25em 1.5em;
24
+ overflow-x: auto;
25
+ background: transparent;
26
+ }
27
+
28
+ .void-md div[class*='language-'] code {
29
+ font-family: var(--vmd-font-mono, ui-monospace, 'Cascadia Code', Menlo, monospace);
30
+ font-size: 0.875em;
31
+ line-height: 1.7;
32
+ display: block;
33
+ width: fit-content;
34
+ min-width: 100%;
35
+ }
36
+
37
+ /* Language label */
38
+ .void-md div[class*='language-'] .lang {
39
+ position: absolute;
40
+ top: 0.5em;
41
+ right: 3em;
42
+ font-size: 0.75em;
43
+ font-weight: 500;
44
+ color: var(--vmd-text-muted, #94a3b8);
45
+ pointer-events: none;
46
+ text-transform: uppercase;
47
+ }
48
+
49
+ /* Copy button */
50
+ .void-md div[class*='language-'] .copy {
51
+ position: absolute;
52
+ top: 0.5em;
53
+ right: 0.5em;
54
+ width: 2em;
55
+ height: 2em;
56
+ border: 1px solid var(--vmd-border, #e2e8f0);
57
+ border-radius: 4px;
58
+ background: var(--vmd-bg-soft, #f8fafc);
59
+ cursor: pointer;
60
+ opacity: 0;
61
+ transition: opacity 0.15s;
62
+ }
63
+
64
+ .void-md div[class*='language-']:hover .copy {
65
+ opacity: 1;
66
+ }
67
+
68
+ .void-md div[class*='language-'] .copy::before {
69
+ content: '';
70
+ display: block;
71
+ width: 100%;
72
+ height: 100%;
73
+ background: currentColor;
74
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Crect x='9' y='9' width='13' height='13' rx='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/svg%3E")
75
+ center / 60% no-repeat;
76
+ }
77
+
78
+ .void-md div[class*='language-'] .copy.copied {
79
+ opacity: 1;
80
+ }
81
+
82
+ .void-md div[class*='language-'] .copy.copied::before {
83
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
84
+ }
85
+
86
+ /* Shiki dual theme — keep inline light colors, only swap in dark mode */
87
+ .void-md pre.shiki.shiki-themes {
88
+ background-color: var(--shiki-light-bg, #fff) !important;
89
+ }
90
+
91
+ @media (prefers-color-scheme: dark) {
92
+ :root:not([data-theme='light']) .void-md pre.shiki.shiki-themes {
93
+ background-color: var(--shiki-dark-bg, #1e293b) !important;
94
+ color: var(--shiki-dark, #e1e4e8) !important;
95
+ }
96
+
97
+ :root:not([data-theme='light']) .void-md pre.shiki.shiki-themes span[style*='--shiki-dark'] {
98
+ color: var(--shiki-dark) !important;
99
+ }
100
+ }
101
+
102
+ :root[data-theme='dark'] .void-md pre.shiki.shiki-themes {
103
+ background-color: var(--shiki-dark-bg, #1e293b) !important;
104
+ color: var(--shiki-dark, #e1e4e8) !important;
105
+ }
106
+
107
+ :root[data-theme='dark'] .void-md pre.shiki.shiki-themes span[style*='--shiki-dark'] {
108
+ color: var(--shiki-dark) !important;
109
+ }
110
+
111
+ /* Shiki notation transformers */
112
+ .void-md pre .diff.add {
113
+ background: rgba(16, 185, 129, 0.14);
114
+ }
115
+
116
+ .void-md pre .diff.add::before {
117
+ content: '+';
118
+ position: absolute;
119
+ left: 0.5em;
120
+ color: #10b981;
121
+ }
122
+
123
+ .void-md pre .diff.remove {
124
+ background: rgba(239, 68, 68, 0.14);
125
+ opacity: 0.7;
126
+ }
127
+
128
+ .void-md pre .diff.remove::before {
129
+ content: '-';
130
+ position: absolute;
131
+ left: 0.5em;
132
+ color: #ef4444;
133
+ }
134
+
135
+ .void-md pre .diff {
136
+ position: relative;
137
+ padding-left: 2em;
138
+ margin: 0 -1.5em;
139
+ padding-right: 1.5em;
140
+ }
141
+
142
+ .void-md pre .highlighted {
143
+ background: rgba(99, 102, 241, 0.1);
144
+ margin: 0 -1.5em;
145
+ padding: 0 1.5em;
146
+ display: inline-block;
147
+ width: calc(100% + 3em);
148
+ }
149
+
150
+ .void-md pre .highlighted.warning {
151
+ background: rgba(234, 179, 8, 0.14);
152
+ }
153
+
154
+ .void-md pre .highlighted.error {
155
+ background: rgba(239, 68, 68, 0.14);
156
+ }
157
+
158
+ .void-md pre .has-focus .line:not(.focused) {
159
+ opacity: 0.4;
160
+ transition: opacity 0.3s;
161
+ }
162
+
163
+ .void-md pre:hover .has-focus .line:not(.focused) {
164
+ opacity: 1;
165
+ }
166
+
167
+ /* Line numbers */
168
+ .void-md div[class*='language-'].line-numbers-mode {
169
+ padding-left: 2.5em;
170
+ }
171
+
172
+ .void-md .line-numbers-wrapper {
173
+ position: absolute;
174
+ top: 0;
175
+ left: 0;
176
+ bottom: 0;
177
+ width: 2.5em;
178
+ padding: 1.25em 0;
179
+ text-align: center;
180
+ font-family: var(--vmd-font-mono, monospace);
181
+ font-size: 0.875em;
182
+ line-height: 1.7;
183
+ color: var(--vmd-text-muted, #94a3b8);
184
+ border-right: 1px solid var(--vmd-border, #e2e8f0);
185
+ background: var(--vmd-bg-soft, #f8fafc);
186
+ }
187
+
188
+ @media (prefers-color-scheme: dark) {
189
+ :root:not([data-theme='light']) .void-md .line-numbers-wrapper {
190
+ background: #1e293b;
191
+ }
192
+ }
193
+
194
+ :root[data-theme='dark'] .void-md .line-numbers-wrapper {
195
+ background: #1e293b;
196
+ }
197
+
198
+ /* Keep the official theme usable with a keyboard and touch input. */
199
+ .void-md div[class*='language-'] .copy:focus-visible,
200
+ .void-md div[class*='language-'] .copy.copy-error { opacity: 1; }
201
+ .void-md div[class*='language-'] .copy:focus-visible { outline: 2px solid var(--vmd-link, #2563eb); outline-offset: 2px; }
202
+ @media (hover: none) { .void-md div[class*='language-'] .copy { opacity: 1; } }