docmedown 0.1.6 → 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.6",
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",
@@ -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(),
@@ -260,6 +263,7 @@ var docConfigSchema = import_zod.z.object({
260
263
  defaultCollapsed: import_zod.z.boolean().optional()
261
264
  }).strict().optional(),
262
265
  search: docSearchConfigSchema.optional(),
266
+ offline: docOfflineConfigSchema.optional(),
263
267
  footer: import_zod.z.object({
264
268
  copyright: import_zod.z.string().optional(),
265
269
  links: import_zod.z.array(navLinkSchema).optional(),
@@ -314,6 +318,9 @@ var DEFAULT_CONFIG = {
314
318
  placeholder: "Search documentation...",
315
319
  maxResults: 10
316
320
  },
321
+ offline: {
322
+ embedNestedDocs: true
323
+ },
317
324
  footer: {
318
325
  copyright: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} DocMeDown. All rights reserved.`,
319
326
  showBuiltWith: true
@@ -345,6 +352,10 @@ function normalizeConfig(userConfig = {}) {
345
352
  ...DEFAULT_CONFIG.search,
346
353
  ...parsedConfig.search
347
354
  },
355
+ offline: {
356
+ ...DEFAULT_CONFIG.offline,
357
+ ...parsedConfig.offline
358
+ },
348
359
  footer: {
349
360
  ...DEFAULT_CONFIG.footer,
350
361
  ...parsedConfig.footer
@@ -366,7 +377,7 @@ function escapeHtml(value) {
366
377
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
367
378
  }
368
379
  function createCompressedOfflineHtml(encodedGzip, title) {
369
- 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}})();`;
370
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>`;
371
382
  }
372
383
 
@@ -485,6 +496,49 @@ function mapManualSidebar(items) {
485
496
  });
486
497
  }
487
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
+
488
542
  // src/cli/utils/scanner.ts
489
543
  function extractHeadings(content) {
490
544
  const headings = [];
@@ -493,8 +547,9 @@ function extractHeadings(content) {
493
547
  const match = line.match(/^(#{1,4})\s+(.+)$/);
494
548
  if (match) {
495
549
  const level = match[1].length;
496
- const text = match[2].trim().replace(/[#*`_]/g, "");
497
- 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, "-");
498
553
  headings.push({ level, text, id });
499
554
  }
500
555
  }
@@ -615,6 +670,32 @@ function findNestedDocumentationRoots(targetDir) {
615
670
  visit(targetDir);
616
671
  return roots;
617
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
+ }
618
699
  function shouldWatchDocumentationSource(rootDir, changedPath) {
619
700
  const relativePath = import_node_path2.default.relative(rootDir, changedPath).replace(/\\/g, "/");
620
701
  const firstSegment = relativePath.split("/")[0];
@@ -682,9 +763,24 @@ async function buildCommand(targetDirArg = "./docs", options = {}) {
682
763
  } else {
683
764
  console.warn(import_chalk.default.yellow(" \u26A0 Local runtime bundle not found, linking CDN script."));
684
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
+ }
685
776
  const envelope = {
686
777
  version: OFFLINE_FORMAT_VERSION,
687
- data: { manifest, docs: docsMap, componentsSource },
778
+ data: {
779
+ manifest,
780
+ docs: docsMap,
781
+ componentsSource,
782
+ ...nestedSites && nestedCount > 0 ? { nestedSites } : {}
783
+ },
688
784
  runtime: bundleJs
689
785
  };
690
786
  const encodedEnvelope = encodeCompressedOfflineEnvelope(envelope);