docmedown 0.1.5 → 0.1.7

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/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ var import_commander = require("commander");
31
31
  // package.json
32
32
  var package_default = {
33
33
  name: "docmedown",
34
- version: "0.1.5",
34
+ version: "0.1.7",
35
35
  description: "The simplest MarkDown documenter yet. CLI & Web Script React SPA documentation engine.",
36
36
  main: "./dist/docmedown.cjs",
37
37
  module: "./dist/docmedown.mjs",
@@ -77,7 +77,7 @@ var package_default = {
77
77
  "build:docs:watch": "node ./bin/docmedown.js build ./docs --watch",
78
78
  "build:offline": "node ./bin/docmedown.js build ./docs",
79
79
  "config:docs": "node ./bin/docmedown.js config ./docs/docs.json",
80
- test: "npx tsx --test tests/parser.test.ts tests/scanner.test.ts tests/theme-schema.test.ts tests/runtime-integration.test.ts tests/package-smoke.test.ts tests/hosting.test.ts tests/deploy.test.ts",
80
+ test: "npx tsx --test tests/parser.test.ts tests/scanner.test.ts tests/theme-schema.test.ts tests/runtime-integration.test.ts tests/package-smoke.test.ts tests/hosting.test.ts tests/deploy.test.ts tests/home.test.ts",
81
81
  "test:artifacts": "npx tsx --test tests/offline-artifact.test.ts",
82
82
  "test:release": "npm run typecheck && npm run lint && npm run test && npm run build && npm run build:docs && npm run test:artifacts && npm pack --dry-run",
83
83
  deploy: "node ./scripts/deploy.mjs",
@@ -240,6 +240,9 @@ var docSearchConfigSchema = import_zod.z.object({
240
240
  placeholder: import_zod.z.string().optional(),
241
241
  maxResults: import_zod.z.number().int().positive().max(100).optional()
242
242
  }).strict();
243
+ var docOfflineConfigSchema = import_zod.z.object({
244
+ embedNestedDocs: import_zod.z.boolean().optional()
245
+ }).strict();
243
246
  var docConfigSchema = import_zod.z.object({
244
247
  $schema: import_zod.z.string().url("Must be an absolute URL.").optional(),
245
248
  name: nonEmptyString.optional(),
@@ -247,6 +250,7 @@ var docConfigSchema = import_zod.z.object({
247
250
  description: import_zod.z.string().optional(),
248
251
  version: import_zod.z.string().optional(),
249
252
  rootDoc: nonEmptyString.optional(),
253
+ home: nonEmptyString.optional(),
250
254
  source: remoteSourceSchema.optional(),
251
255
  theme: docThemeConfigSchema.optional(),
252
256
  nav: import_zod.z.array(navLinkSchema).optional(),
@@ -259,6 +263,7 @@ var docConfigSchema = import_zod.z.object({
259
263
  defaultCollapsed: import_zod.z.boolean().optional()
260
264
  }).strict().optional(),
261
265
  search: docSearchConfigSchema.optional(),
266
+ offline: docOfflineConfigSchema.optional(),
262
267
  footer: import_zod.z.object({
263
268
  copyright: import_zod.z.string().optional(),
264
269
  links: import_zod.z.array(navLinkSchema).optional(),
@@ -313,6 +318,9 @@ var DEFAULT_CONFIG = {
313
318
  placeholder: "Search documentation...",
314
319
  maxResults: 10
315
320
  },
321
+ offline: {
322
+ embedNestedDocs: true
323
+ },
316
324
  footer: {
317
325
  copyright: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} DocMeDown. All rights reserved.`,
318
326
  showBuiltWith: true
@@ -344,6 +352,10 @@ function normalizeConfig(userConfig = {}) {
344
352
  ...DEFAULT_CONFIG.search,
345
353
  ...parsedConfig.search
346
354
  },
355
+ offline: {
356
+ ...DEFAULT_CONFIG.offline,
357
+ ...parsedConfig.offline
358
+ },
347
359
  footer: {
348
360
  ...DEFAULT_CONFIG.footer,
349
361
  ...parsedConfig.footer
@@ -365,7 +377,7 @@ function escapeHtml(value) {
365
377
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
366
378
  }
367
379
  function createCompressedOfflineHtml(encodedGzip, title) {
368
- const bootstrap = `(async()=>{try{const b=Uint8Array.from(atob(document.getElementById("d").textContent.trim()),c=>c.charCodeAt(0)),s=await new Response(new Blob([b]).stream().pipeThrough(new DecompressionStream("gzip"))).text(),e=JSON.parse(s);if(e.version!==${OFFLINE_FORMAT_VERSION})throw new Error("Unsupported offline format");window.__DOCMEDOWN_OFFLINE__=true;window.__DOCMEDOWN_DATA__=e.data;window.__DOCMEDOWN_CONFIG__=e.data.manifest.config;const u=URL.createObjectURL(new Blob([e.runtime],{type:"text/javascript"})),j=document.createElement("script");j.src=u;j.onload=()=>URL.revokeObjectURL(u);j.onerror=()=>{URL.revokeObjectURL(u);throw new Error("Runtime bootstrap failed")};document.head.appendChild(j)}catch(e){document.getElementById("dmd-app").textContent="Could not open this offline documentation copy: "+e.message}})();`;
380
+ const bootstrap = `(async()=>{try{const b=Uint8Array.from(atob(document.getElementById("d").textContent.trim()),c=>c.charCodeAt(0)),s=await new Response(new Blob([b]).stream().pipeThrough(new DecompressionStream("gzip"))).text(),e=JSON.parse(s);if(e.version!==${OFFLINE_FORMAT_VERSION})throw new Error("Unsupported offline format");window.__DOCMEDOWN_OFFLINE__=true;window.__DOCMEDOWN_DATA__=e.data;window.__DOCMEDOWN_CONFIG__=e.data.manifest.config;const u=URL.createObjectURL(new Blob([e.runtime],{type:"text/javascript"}));window.__DOCMEDOWN_RUNTIME_URL__=u;const j=document.createElement("script");j.src=u;j.onerror=()=>{URL.revokeObjectURL(u);throw new Error("Runtime bootstrap failed")};document.head.appendChild(j)}catch(e){document.getElementById("dmd-app").textContent="Could not open this offline documentation copy: "+e.message}})();`;
369
381
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="data:,"><title>${escapeHtml(title)}</title></head><body><div id="dmd-app">Opening offline documentation\u2026</div><script id="d" type="application/octet-stream">${encodedGzip}</script><script>${bootstrap}</script></body></html>`;
370
382
  }
371
383
 
@@ -484,6 +496,49 @@ function mapManualSidebar(items) {
484
496
  });
485
497
  }
486
498
 
499
+ // src/runtime/markdown/inline-text.ts
500
+ var NAMED_HTML_ENTITIES = {
501
+ amp: "&",
502
+ lt: "<",
503
+ gt: ">",
504
+ quot: '"',
505
+ apos: "'",
506
+ nbsp: " ",
507
+ copy: "\xA9",
508
+ reg: "\xAE",
509
+ trade: "\u2122",
510
+ middot: "\xB7",
511
+ hellip: "\u2026",
512
+ mdash: "\u2014",
513
+ ndash: "\u2013",
514
+ bull: "\u2022",
515
+ deg: "\xB0",
516
+ times: "\xD7",
517
+ plusmn: "\xB1",
518
+ laquo: "\xAB",
519
+ raquo: "\xBB",
520
+ sect: "\xA7",
521
+ para: "\xB6"
522
+ };
523
+ function codePointToChar(code) {
524
+ return Number.isInteger(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : "";
525
+ }
526
+ function decodeHtmlEntities(value) {
527
+ return value.replace(/&#x([0-9a-f]+);/gi, (_match, hex) => codePointToChar(parseInt(hex, 16))).replace(/&#([0-9]+);/g, (_match, dec) => codePointToChar(Number(dec))).replace(/&([a-z][a-z0-9]*);/gi, (match, name) => NAMED_HTML_ENTITIES[name.toLowerCase()] ?? match);
528
+ }
529
+ var IMAGE_PATTERN = /!\[([^\]]*)\]\([^)]*\)/g;
530
+ var LINK_PATTERN = /\[([^\]]+)\]\([^)]*\)/g;
531
+ var CODE_PATTERN = /`([^`]+)`/g;
532
+ var BOLD_PATTERN = /\*\*([^*\n]+)\*\*/g;
533
+ var UNDERSCORE_BOLD_PATTERN = /__([^_\n]+)__/g;
534
+ var ITALIC_PATTERN = /\*([^*\n]+)\*/g;
535
+ var STRIKETHROUGH_PATTERN = /~~([^~\n]+)~~/g;
536
+ function inlineHeadingToPlainText(raw) {
537
+ return decodeHtmlEntities(
538
+ raw.replace(IMAGE_PATTERN, "$1").replace(LINK_PATTERN, "$1").replace(CODE_PATTERN, "$1").replace(BOLD_PATTERN, "$1").replace(UNDERSCORE_BOLD_PATTERN, "$1").replace(ITALIC_PATTERN, "$1").replace(STRIKETHROUGH_PATTERN, "$1")
539
+ ).replace(/\s+/g, " ").trim();
540
+ }
541
+
487
542
  // src/cli/utils/scanner.ts
488
543
  function extractHeadings(content) {
489
544
  const headings = [];
@@ -492,8 +547,9 @@ function extractHeadings(content) {
492
547
  const match = line.match(/^(#{1,4})\s+(.+)$/);
493
548
  if (match) {
494
549
  const level = match[1].length;
495
- const text = match[2].trim().replace(/[#*`_]/g, "");
496
- const id = text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/[\s_-]+/g, "-");
550
+ const rawHeading = match[2].trim();
551
+ const text = inlineHeadingToPlainText(rawHeading);
552
+ const id = rawHeading.replace(/[#*`_]/g, "").toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/[\s_-]+/g, "-");
497
553
  headings.push({ level, text, id });
498
554
  }
499
555
  }
@@ -614,6 +670,32 @@ function findNestedDocumentationRoots(targetDir) {
614
670
  visit(targetDir);
615
671
  return roots;
616
672
  }
673
+ function collectNestedOfflineSites(targetDir) {
674
+ const sites = {};
675
+ for (const nestedRoot of findNestedDocumentationRoots(targetDir)) {
676
+ const key = import_node_path2.default.relative(targetDir, nestedRoot).replace(/\\/g, "/");
677
+ const nestedConfigPath = import_node_path2.default.join(nestedRoot, "docs.json");
678
+ let nestedConfig = normalizeConfig();
679
+ if (import_node_fs2.default.existsSync(nestedConfigPath)) {
680
+ nestedConfig = normalizeConfig(parseDocConfigJson(import_node_fs2.default.readFileSync(nestedConfigPath, "utf-8"), nestedConfigPath));
681
+ }
682
+ const nestedDocs = {};
683
+ for (const file of scanDirectory(nestedRoot)) {
684
+ const raw = import_node_fs2.default.readFileSync(import_node_path2.default.join(nestedRoot, file), "utf-8");
685
+ const slug = file.replace(/\.(md|mdx)$/i, "");
686
+ const cleanSlug = slug.toLowerCase() === "readme" || slug.toLowerCase() === "index" ? "README" : slug;
687
+ nestedDocs[cleanSlug] = raw;
688
+ nestedDocs[file] = raw;
689
+ }
690
+ sites[key] = {
691
+ name: nestedConfig.name || key,
692
+ manifest: generateManifest(nestedRoot, nestedConfig),
693
+ docs: nestedDocs,
694
+ componentsSource: bundleCustomComponents(import_node_path2.default.join(nestedRoot, ".dmd", "components.js"))
695
+ };
696
+ }
697
+ return sites;
698
+ }
617
699
  function shouldWatchDocumentationSource(rootDir, changedPath) {
618
700
  const relativePath = import_node_path2.default.relative(rootDir, changedPath).replace(/\\/g, "/");
619
701
  const firstSegment = relativePath.split("/")[0];
@@ -681,9 +763,24 @@ async function buildCommand(targetDirArg = "./docs", options = {}) {
681
763
  } else {
682
764
  console.warn(import_chalk.default.yellow(" \u26A0 Local runtime bundle not found, linking CDN script."));
683
765
  }
766
+ const embedNestedDocs = config.offline?.embedNestedDocs !== false;
767
+ const nestedSites = embedNestedDocs && options.buildNested !== false ? collectNestedOfflineSites(targetDir) : void 0;
768
+ const nestedCount = nestedSites ? Object.keys(nestedSites).length : 0;
769
+ if (nestedCount > 0) {
770
+ console.log(
771
+ import_chalk.default.green(
772
+ ` \u2714 Embedded ${nestedCount} nested documentation site${nestedCount === 1 ? "" : "s"} into the offline bundle`
773
+ )
774
+ );
775
+ }
684
776
  const envelope = {
685
777
  version: OFFLINE_FORMAT_VERSION,
686
- data: { manifest, docs: docsMap, componentsSource },
778
+ data: {
779
+ manifest,
780
+ docs: docsMap,
781
+ componentsSource,
782
+ ...nestedSites && nestedCount > 0 ? { nestedSites } : {}
783
+ },
687
784
  runtime: bundleJs
688
785
  };
689
786
  const encodedEnvelope = encodeCompressedOfflineEnvelope(envelope);