@void/md 0.26.0 → 0.27.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @void/md
2
2
 
3
- Add Markdown routes to Void Pages with frontmatter, syntax highlighting, layouts, and interactive islands.
3
+ Markdown adapter for [Void](https://void.cloud). Build content pages with frontmatter, syntax highlighting, layouts, and interactive islands alongside your application routes.
4
4
 
5
5
  ## Setup
6
6
 
@@ -13,10 +13,10 @@ npm install @void/md
13
13
  Add `voidMarkdown()` after the adapter in your Vite config. For example, with Vue:
14
14
 
15
15
  ```ts
16
+ import { voidMarkdown } from '@void/md/plugin';
17
+ import { voidVue } from '@void/vue/plugin';
16
18
  import { defineConfig } from 'vite';
17
19
  import { voidPlugin } from 'void';
18
- import { voidVue } from '@void/vue/plugin';
19
- import { voidMarkdown } from '@void/md/plugin';
20
20
 
21
21
  export default defineConfig({
22
22
  plugins: [voidPlugin(), voidVue(), voidMarkdown()],
@@ -25,4 +25,8 @@ export default defineConfig({
25
25
 
26
26
  Create `.md` files in `pages/`. The plugin also works with React, Svelte, and Solid.
27
27
 
28
- See [Markdown](https://void.cloud/guide/pages-routing/markdown) for frontmatter, themes, navigation, and plugin options, and [Islands](https://void.cloud/guide/pages-routing/islands) for interactive components. Repository verification is documented in [TESTING.md](../../TESTING.md).
28
+ ## Documentation
29
+
30
+ See [Markdown](https://void.cloud/guide/pages-routing/markdown) for frontmatter, themes, navigation, and plugin options, and [Islands](https://void.cloud/guide/pages-routing/islands) for interactive components.
31
+
32
+ Read the [Void documentation](https://void.cloud) online or use the Markdown guides and references in `node_modules/void/docs/`, included with the `void` package.
@@ -1,3 +1,4 @@
1
+ import { t as extractHeadings } from "./metadata-TdHmXD-i.mjs";
1
2
  import matter from "gray-matter";
2
3
  import MarkdownExit from "markdown-exit";
3
4
  import anchor from "markdown-it-anchor";
@@ -220,30 +221,8 @@ function tocPlugin(md, options) {
220
221
  "reference",
221
222
  "blockquote"
222
223
  ] });
223
- md.renderer.rules.toc_open = (tokens, idx, options, _env, self) => {
224
- return self.renderToken(tokens, idx, options);
225
- };
226
224
  md.renderer.rules.toc_body = (tokens) => {
227
- const headers = [];
228
- for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {
229
- const token = tokens[tokenIndex];
230
- if (token.type === "heading_open") {
231
- const depth = Number(token.tag.slice(1));
232
- if (level.includes(depth)) {
233
- const slug = token.attrGet?.("id") ?? "";
234
- const text = (tokens[tokenIndex + 1]?.children?.filter((child) => child.type === "text" || child.type === "code_inline").map((child) => child.content).join("") ?? "").trim();
235
- headers.push({
236
- depth,
237
- slug,
238
- text
239
- });
240
- }
241
- }
242
- }
243
- return renderHeaders(headers, listTag);
244
- };
245
- md.renderer.rules.toc_close = (tokens, idx, options, _env, self) => {
246
- return self.renderToken(tokens, idx, options);
225
+ return renderHeaders(extractHeadings(tokens).filter((heading) => level.includes(heading.depth)), listTag);
247
226
  };
248
227
  }
249
228
  //#endregion
@@ -312,27 +291,11 @@ async function createCompiler(options) {
312
291
  markdown.use(tocPlugin);
313
292
  return markdown;
314
293
  }
315
- function extractHeadings(tokens) {
316
- const headings = [];
317
- for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {
318
- const token = tokens[tokenIndex];
319
- if (token.type === "heading_open") {
320
- const depth = Number(token.tag.slice(1));
321
- const slug = token.attrGet?.("id") ?? "";
322
- const text = (tokens[tokenIndex + 1]?.children?.filter((child) => child.type === "text" || child.type === "code_inline").map((child) => child.content).join("") ?? "").trim();
323
- headings.push({
324
- depth,
325
- slug,
326
- text
327
- });
328
- }
329
- }
330
- return headings;
331
- }
332
294
  async function compile(source, options) {
333
295
  const { data: frontmatter, content } = matter(source);
334
296
  const compiler = await getCompiler(options);
335
- const headings = extractHeadings(compiler.parse(content, {}));
297
+ const tokens = compiler.parse(content, {});
298
+ const headings = extractHeadings(tokens);
336
299
  return {
337
300
  html: await compiler.renderAsync(content, {}),
338
301
  frontmatter,
@@ -0,0 +1,45 @@
1
+ import matter from "gray-matter";
2
+ import MarkdownExit from "markdown-exit";
3
+ import anchor from "markdown-it-anchor";
4
+ import footnote from "markdown-it-footnote";
5
+ //#region src/metadata.ts
6
+ let metadataCompiler = null;
7
+ function getMetadataCompiler() {
8
+ if (metadataCompiler) return metadataCompiler;
9
+ const markdown = new MarkdownExit({
10
+ html: true,
11
+ linkify: true
12
+ });
13
+ markdown.use(footnote);
14
+ markdown.use(anchor, { permalink: anchor.permalink.ariaHidden({ placement: "before" }) });
15
+ metadataCompiler = markdown;
16
+ return markdown;
17
+ }
18
+ function extractHeadings(tokens) {
19
+ const headings = [];
20
+ for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {
21
+ const token = tokens[tokenIndex];
22
+ if (token.type === "heading_open") {
23
+ const depth = Number(token.tag.slice(1));
24
+ const slug = token.attrGet?.("id") ?? "";
25
+ const text = (tokens[tokenIndex + 1]?.children?.filter((child) => child.type === "text" || child.type === "code_inline").map((child) => child.content).join("") ?? "").trim();
26
+ headings.push({
27
+ depth,
28
+ slug,
29
+ text
30
+ });
31
+ }
32
+ }
33
+ return headings;
34
+ }
35
+ function extractMetadata(source) {
36
+ const { data: frontmatter, content } = matter(source);
37
+ const headings = extractHeadings(getMetadataCompiler().parse(content, {}));
38
+ return {
39
+ frontmatter,
40
+ headings,
41
+ title: frontmatter.title ?? headings.find((heading) => heading.depth === 1)?.text ?? ""
42
+ };
43
+ }
44
+ //#endregion
45
+ export { extractMetadata as n, extractHeadings as t };
package/dist/plugin.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as extractMetadata } from "./metadata-TdHmXD-i.mjs";
1
2
  import { createHash } from "node:crypto";
2
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
4
  import { dirname, join, relative, resolve } from "pathslash";
@@ -5,10 +6,6 @@ import { glob } from "tinyglobby";
5
6
  import { normalizePath } from "vite";
6
7
  import { resolvePagesDir } from "void/pages";
7
8
  import { editIslandSource, findIslandImports } from "void/pages-islands-plugin";
8
- import matter from "gray-matter";
9
- import MarkdownExit from "markdown-exit";
10
- import anchor from "markdown-it-anchor";
11
- import footnote from "markdown-it-footnote";
12
9
  //#region src/codegen/md-types.ts
13
10
  /**
14
11
  * Generate .void/md.d.ts content.
@@ -34,12 +31,12 @@ function generateMdTypes() {
34
31
  }
35
32
  //#endregion
36
33
  //#region src/emit-shared.ts
37
- const ISLAND_TAG_RE$1 = /<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)\/\s*>|<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/\3\s*>/g;
38
- function findIslandTags$1(html, names) {
34
+ const ISLAND_TAG_RE = /<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)\/\s*>|<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/\3\s*>/g;
35
+ function findIslandTags(html, names) {
39
36
  const matches = [];
40
37
  let match;
41
- ISLAND_TAG_RE$1.lastIndex = 0;
42
- while ((match = ISLAND_TAG_RE$1.exec(html)) !== null) {
38
+ ISLAND_TAG_RE.lastIndex = 0;
39
+ while ((match = ISLAND_TAG_RE.exec(html)) !== null) {
43
40
  const name = match[1] ?? match[3];
44
41
  if (names.has(name)) matches.push({
45
42
  name,
@@ -50,7 +47,7 @@ function findIslandTags$1(html, names) {
50
47
  }
51
48
  return matches;
52
49
  }
53
- function parseAttrs$1(attrStr) {
50
+ function parseAttrs(attrStr) {
54
51
  const result = {};
55
52
  const attrRe = /(\w[\w-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
56
53
  let match;
@@ -62,7 +59,7 @@ function toIslandId(imp) {
62
59
  }
63
60
  function buildRenderChunks(html, islandImports) {
64
61
  const islandByName = new Map(islandImports.map((item) => [item.name, item]));
65
- const tags = findIslandTags$1(html, new Set(islandByName.keys()));
62
+ const tags = findIslandTags(html, new Set(islandByName.keys()));
66
63
  const chunks = [];
67
64
  let cursor = 0;
68
65
  for (const tag of tags) {
@@ -77,7 +74,7 @@ function buildRenderChunks(html, islandImports) {
77
74
  if (islandImport) chunks.push({
78
75
  type: "island",
79
76
  islandImport,
80
- props: parseAttrs$1(tag.attrs)
77
+ props: parseAttrs(tag.attrs)
81
78
  });
82
79
  cursor = tag.end;
83
80
  }
@@ -318,33 +315,8 @@ export default defineComponent({
318
315
  });
319
316
  `;
320
317
  }
321
- const ISLAND_TAG_RE = /<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)\/\s*>|<([A-Z]\w*)\s*((?:[^>/"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/\3\s*>/g;
322
- function findIslandTags(html, names) {
323
- const matches = [];
324
- let tagMatch;
325
- ISLAND_TAG_RE.lastIndex = 0;
326
- while ((tagMatch = ISLAND_TAG_RE.exec(html)) !== null) {
327
- const name = tagMatch[1] ?? tagMatch[3];
328
- if (names.has(name)) matches.push({
329
- name,
330
- start: tagMatch.index,
331
- end: tagMatch.index + tagMatch[0].length,
332
- attrs: (tagMatch[2] ?? tagMatch[4] ?? "").trim(),
333
- children: tagMatch[5] ?? ""
334
- });
335
- }
336
- return matches;
337
- }
338
- function parseAttrs(attrStr) {
339
- const result = {};
340
- const attrRe = /(\w[\w-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
341
- let attrMatch;
342
- while ((attrMatch = attrRe.exec(attrStr)) !== null) result[attrMatch[1]] = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
343
- return result;
344
- }
345
318
  function emitVueComponentWithIslands(result, islandImports) {
346
- const nameSet = new Set(islandImports.map((islandImport) => islandImport.name));
347
- const islandTags = findIslandTags(result.html, nameSet);
319
+ const chunks = buildRenderChunks(result.html, islandImports);
348
320
  let code = `import { defineComponent, h, createStaticVNode, provide } from "vue";\n`;
349
321
  for (let islandIndex = 0; islandIndex < islandImports.length; islandIndex++) {
350
322
  const islandImport = islandImports[islandIndex];
@@ -362,20 +334,10 @@ function emitVueComponentWithIslands(result, islandImports) {
362
334
  }};\n`;
363
335
  }
364
336
  const vnodes = [];
365
- let cursor = 0;
366
- for (const tag of islandTags) {
367
- if (tag.start > cursor) {
368
- const segment = result.html.slice(cursor, tag.start);
369
- if (segment.trim()) vnodes.push(`createStaticVNode(${JSON.stringify(segment)}, 1)`);
370
- }
371
- const attrs = parseAttrs(tag.attrs);
372
- const propsStr = Object.keys(attrs).length > 0 ? JSON.stringify(attrs) : "null";
373
- vnodes.push(`h(${tag.name}, ${propsStr})`);
374
- cursor = tag.end;
375
- }
376
- if (cursor < result.html.length) {
377
- const segment = result.html.slice(cursor);
378
- if (segment.trim()) vnodes.push(`createStaticVNode(${JSON.stringify(segment)}, 1)`);
337
+ for (const chunk of chunks) if (chunk.type === "html") vnodes.push(`createStaticVNode(${JSON.stringify(chunk.html)}, 1)`);
338
+ else {
339
+ const propsStr = Object.keys(chunk.props).length > 0 ? JSON.stringify(chunk.props) : "null";
340
+ vnodes.push(`h(${chunk.islandImport.name}, ${propsStr})`);
379
341
  }
380
342
  code += `
381
343
  const __frontmatter = ${JSON.stringify(result.frontmatter)};
@@ -425,46 +387,6 @@ function extractScript(source) {
425
387
  };
426
388
  }
427
389
  //#endregion
428
- //#region src/metadata.ts
429
- let metadataCompiler = null;
430
- function getMetadataCompiler() {
431
- if (metadataCompiler) return metadataCompiler;
432
- const markdown = new MarkdownExit({
433
- html: true,
434
- linkify: true
435
- });
436
- markdown.use(footnote);
437
- markdown.use(anchor, { permalink: anchor.permalink.ariaHidden({ placement: "before" }) });
438
- metadataCompiler = markdown;
439
- return markdown;
440
- }
441
- function extractHeadings(tokens) {
442
- const headings = [];
443
- for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) {
444
- const token = tokens[tokenIndex];
445
- if (token.type === "heading_open") {
446
- const depth = Number(token.tag.slice(1));
447
- const slug = token.attrGet?.("id") ?? "";
448
- const text = (tokens[tokenIndex + 1]?.children?.filter((child) => child.type === "text" || child.type === "code_inline").map((child) => child.content).join("") ?? "").trim();
449
- headings.push({
450
- depth,
451
- slug,
452
- text
453
- });
454
- }
455
- }
456
- return headings;
457
- }
458
- function extractMetadata(source) {
459
- const { data: frontmatter, content } = matter(source);
460
- const headings = extractHeadings(getMetadataCompiler().parse(content, {}));
461
- return {
462
- frontmatter,
463
- headings,
464
- title: frontmatter.title ?? headings.find((heading) => heading.depth === 1)?.text ?? ""
465
- };
466
- }
467
- //#endregion
468
390
  //#region src/plugin.ts
469
391
  const MD_CLIENT_ENTRY_ID = "virtual:void-md-client";
470
392
  const ADAPTER_PLUGIN_NAMES = {
@@ -475,6 +397,9 @@ const ADAPTER_PLUGIN_NAMES = {
475
397
  };
476
398
  const SVELTE_VIRTUAL_PREFIX = "/@void-md-svelte/";
477
399
  const SOLID_VIRTUAL_PREFIX = "/@void-md-solid/";
400
+ function stripPageSuffix(file) {
401
+ return file.replace(/\.md$/, "").replace(/\/index$/, "").replace(/^index$/, "");
402
+ }
478
403
  function hasMdFilesWithScript(dir) {
479
404
  try {
480
405
  for (const entry of readdirSync(dir, {
@@ -514,10 +439,11 @@ function voidMarkdown(options) {
514
439
  return (await Promise.all(mdFiles.map(async (file) => {
515
440
  const { body, clientCode } = extractScript(readFileSync(join(pagesDir, file), "utf-8"));
516
441
  const result = extractMetadata(body);
517
- const componentId = file.replace(/\.md$/, "").replace(/\/index$/, "").replace(/^index$/, "") || "index";
442
+ const routeName = stripPageSuffix(file);
443
+ const componentId = routeName || "index";
518
444
  if (clientCode) clientScripts.set(componentId, clientCode);
519
445
  return {
520
- path: "/" + file.replace(/\.md$/, "").replace(/\/index$/, "").replace(/^index$/, "") || "/",
446
+ path: "/" + routeName,
521
447
  title: result.title,
522
448
  frontmatter: result.frontmatter,
523
449
  headings: result.headings
@@ -536,9 +462,6 @@ function voidMarkdown(options) {
536
462
  clientEntryId: MD_CLIENT_ENTRY_ID,
537
463
  getClientScripts() {
538
464
  return clientScripts;
539
- },
540
- getClientScriptIds() {
541
- return new Set(clientScripts.keys());
542
465
  }
543
466
  },
544
467
  config(config, env) {
@@ -648,14 +571,14 @@ export function useFrontmatter() { return useContext(FrontmatterContext) ?? {};
648
571
  const { body, islandImports, clientCode } = extractScript(code);
649
572
  const pagesPrefix = pagesDir + "/";
650
573
  if (_id.startsWith(pagesPrefix) && clientCode) {
651
- const componentId = _id.slice(pagesPrefix.length).replace(/\.md$/, "").replace(/\/index$/, "").replace(/^index$/, "") || "index";
574
+ const componentId = stripPageSuffix(_id.slice(pagesPrefix.length)) || "index";
652
575
  clientScripts.set(componentId, clientCode);
653
576
  }
654
577
  for (const imp of islandImports) {
655
578
  const absPath = resolve(dirname(_id), imp.specifier);
656
579
  imp.islandId = normalizePath(relative(root, absPath)).replace(/\.[^.]+$/, "");
657
580
  }
658
- const { compile } = await import("./compile-DiIM3f5f.mjs");
581
+ const { compile } = await import("./compile-BwShiv84.mjs");
659
582
  const result = await compile(body, { shiki: options?.shiki });
660
583
  if (adapter === "vue") {
661
584
  if (islandImports.length > 0) return {
@@ -28,14 +28,7 @@ html {
28
28
  /* Body */
29
29
  body {
30
30
  min-height: 100dvh;
31
- font-family:
32
- system-ui,
33
- -apple-system,
34
- 'Segoe UI',
35
- Roboto,
36
- 'Helvetica Neue',
37
- Arial,
38
- sans-serif;
31
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
39
32
  -webkit-font-smoothing: antialiased;
40
33
  -moz-osx-font-smoothing: grayscale;
41
34
  text-rendering: optimizeLegibility;
package/package.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "name": "@void/md",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
+ "description": "Markdown pages with layouts and interactive islands for Void.",
5
+ "homepage": "https://void.cloud",
6
+ "bugs": {
7
+ "url": "https://github.com/voidzero-dev/void/issues"
8
+ },
9
+ "license": "MIT",
4
10
  "repository": {
5
11
  "type": "git",
6
12
  "url": "git+https://github.com/voidzero-dev/void.git",
@@ -33,7 +39,7 @@
33
39
  "@shikijs/engine-javascript": "^4.5.0",
34
40
  "@shikijs/transformers": "^4.5.0",
35
41
  "gray-matter": "^4.0.3",
36
- "markdown-exit": "1.3.0",
42
+ "markdown-exit": "^1.3.0",
37
43
  "markdown-it-anchor": "^10.0.0",
38
44
  "markdown-it-attrs": "^5.0.1",
39
45
  "markdown-it-container": "^4.0.0",
@@ -45,14 +51,14 @@
45
51
  },
46
52
  "devDependencies": {
47
53
  "@types/node": "^26.6.4",
48
- "vite": "npm:@voidzero-dev/vite-plus-core@1.0.0",
49
- "vite-plus": "1.0.0",
50
- "void": "0.26.0",
54
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
55
+ "vite-plus": "1.1.0",
56
+ "void": "0.27.0",
51
57
  "vue": "^3.5.43"
52
58
  },
53
59
  "peerDependencies": {
54
60
  "vite": "^8.0.0",
55
- "void": "0.26.0"
61
+ "void": "0.27.0"
56
62
  },
57
63
  "scripts": {
58
64
  "build": "vp pack",