@t8/docsgen 0.3.32 → 0.3.33

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/bin.js CHANGED
@@ -278,14 +278,26 @@ async function setCName({ dir = "", name, cname, jsorg }) {
278
278
  }
279
279
 
280
280
  // src/bin/setContent.ts
281
- import { exec as defaultExec } from "node:child_process";
282
- import { access, cp, mkdir, writeFile as writeFile2 } from "node:fs/promises";
283
- import { dirname, join as join3 } from "node:path";
284
- import { fileURLToPath } from "node:url";
285
- import { promisify } from "node:util";
281
+ import { writeFile as writeFile2 } from "node:fs/promises";
282
+ import { join as join5 } from "node:path";
286
283
 
287
- // src/const/packageName.ts
288
- var packageName = "@t8/docsgen";
284
+ // src/bin/content/createDirs.ts
285
+ import { access, mkdir } from "node:fs/promises";
286
+ import { join as join3 } from "node:path";
287
+ async function createDirs(ctx) {
288
+ let { dir = "", contentDir = "" } = ctx;
289
+ let dirs = [contentDir];
290
+ await Promise.all(
291
+ dirs.map(async (path) => {
292
+ let dirPath = join3(dir, path);
293
+ try {
294
+ await access(dirPath);
295
+ } catch {
296
+ await mkdir(dirPath);
297
+ }
298
+ })
299
+ );
300
+ }
289
301
 
290
302
  // src/utils/escapeHTML.ts
291
303
  var htmlEntityMap = [
@@ -302,70 +314,6 @@ function escapeHTML(x) {
302
314
  return s;
303
315
  }
304
316
 
305
- // src/utils/escapeRegExp.ts
306
- function escapeRegExp(s) {
307
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
308
- }
309
-
310
- // src/bin/getCounterContent.ts
311
- function getCounterContent({ ymid }) {
312
- if (!ymid) return "";
313
- return `
314
- <script>
315
- (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
316
- m[i].l=1*new Date();
317
- for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
318
- k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
319
- (window, document, "script", "https://mc.yandex.com/metrika/tag.js", "ym");
320
- ym(${ymid}, "init", {clickmap: true, trackLinks: true, accurateTrackBounce: true});
321
- </script>
322
- <noscript><div><img src="https://mc.yandex.com/watch/${ymid}" style="position:absolute;left:-9999px;" alt=""></div></noscript>
323
- `.trim();
324
- }
325
-
326
- // src/bin/getIcon.ts
327
- var iconTypeMap = {
328
- ico: "image/x-icon",
329
- svg: "image/svg+xml",
330
- png: "image/png",
331
- gif: "image/gif",
332
- jpg: "image/jpeg",
333
- jpeg: "image/jpeg"
334
- };
335
- function getIcon({ favicon, faviconType }) {
336
- let icon = {
337
- url: favicon,
338
- type: faviconType
339
- };
340
- if (icon.url && !icon.type) {
341
- let ext = icon.url.split("/").at(-1)?.split(".").at(-1)?.toLowerCase();
342
- if (ext) icon.type = iconTypeMap[ext];
343
- }
344
- return icon;
345
- }
346
-
347
- // src/bin/getInjectedContent.ts
348
- function getInjectedContent(ctx, page, target, mode) {
349
- let injectedContent = ctx[mode]?.[target];
350
- if (!injectedContent) return "";
351
- return (Array.isArray(injectedContent) ? injectedContent : [injectedContent]).reduce((s, item) => {
352
- if (item === void 0) return s;
353
- if (typeof item === "string") return `${s}${s ? "\n" : ""}${item}`;
354
- let { content, pages } = item;
355
- if (!content || pages && !pages.includes(page)) return s;
356
- return `${s}${s ? "\n" : ""}${content}`;
357
- }, "");
358
- }
359
-
360
- // src/bin/getNav.ts
361
- import { JSDOM as JSDOM2 } from "jsdom";
362
-
363
- // src/bin/getNpmLink.ts
364
- function getNpmLink({ npm }, className) {
365
- if (!npm) return "";
366
- return `<a href="${npm}"${className ? ` class="${className}"` : ""} target="_blank">npm</a>`;
367
- }
368
-
369
317
  // src/bin/getRepoLink.ts
370
318
  function getRepoLink({ repo }, className) {
371
319
  if (!repo) return "";
@@ -373,82 +321,8 @@ function getRepoLink({ repo }, className) {
373
321
  return `<a href="${repo}"${className ? ` class="${className}"` : ""} target="_blank">${caption}</a>`;
374
322
  }
375
323
 
376
- // src/bin/getNav.ts
377
- async function getNav(ctx, navItems) {
378
- let { name, root, contentDir, backstory, nav } = ctx;
379
- let navContent = await fetchContent(nav);
380
- let s = "";
381
- if (navContent) {
382
- let navDom = new JSDOM2(navContent).window.document.body;
383
- for (let link of navDom.querySelectorAll("a")) {
384
- if (link.dataset.name === name) {
385
- let parent = link.parentElement;
386
- link.remove();
387
- while (parent && parent.innerHTML.trim() === "") {
388
- let nextParent = parent.parentElement;
389
- parent.remove();
390
- parent = nextParent;
391
- }
392
- }
393
- }
394
- navContent = navDom.innerHTML;
395
- }
396
- let navItemCount = 0;
397
- if (navItems.length === 1) {
398
- let { id, items } = navItems[0];
399
- let itemLink = `${root}${contentDir}/${encodeURIComponent(id)}`;
400
- for (let { id: id2, title } of items) {
401
- s += `
402
- <li><a href="${itemLink}#${encodeURIComponent(id2)}">${title}</a></li>`;
403
- navItemCount++;
404
- }
405
- } else {
406
- for (let { id, title, items } of navItems) {
407
- let itemLink = `${root}${contentDir}/${encodeURIComponent(id)}`;
408
- s += `
409
- <li data-id="${id}"><a href="${itemLink}">${title}</a>`;
410
- if (items.length !== 0) {
411
- s += "\n <ul>";
412
- for (let { id: id2, title: title2 } of items) {
413
- s += `
414
- <li><a href="${itemLink}#${encodeURIComponent(id2)}">${title2}</a></li>`;
415
- navItemCount++;
416
- }
417
- s += "\n </ul>\n";
418
- }
419
- s += "</li>";
420
- navItemCount++;
421
- }
422
- }
423
- if ((!s || navItemCount < 2) && !navContent) return "";
424
- s = s.trim();
425
- s = s ? `<p class="title">Contents</p>
426
- <ul>${s}
427
- </ul>
428
- ` : "";
429
- let repoLink = getRepoLink(ctx);
430
- let npmLink = getNpmLink(ctx);
431
- if (repoLink || npmLink || backstory)
432
- s += `
433
- <p class="title">Resources</p>
434
- <ul>
435
- ${repoLink ? `<li>${repoLink}</li>` : ""}
436
- ${npmLink ? `<li>${npmLink}</li>` : ""}
437
- ${backstory ? `<li><a href="${backstory}">Backstory</a></li>` : ""}
438
- </ul>
439
- `;
440
- s = s.trim();
441
- s = s ? `<section>
442
- ${s}
443
- </section>` : "";
444
- return `<nav class="aux">
445
- ${s}
446
- ${navContent}
447
- </nav>`;
448
- }
449
-
450
324
  // src/bin/parsing/getParsedContent.ts
451
- import { JSDOM as JSDOM3 } from "jsdom";
325
+ import { JSDOM as JSDOM2 } from "jsdom";
452
326
  import Markdown from "markdown-it";
453
327
 
454
328
  // src/bin/getSlug.ts
@@ -576,13 +450,15 @@ function preprocessContent(s) {
576
450
  var md = new Markdown({
577
451
  html: true
578
452
  });
453
+ var parsedContentCache = /* @__PURE__ */ new Map();
579
454
  async function getParsedContent(ctx) {
455
+ let contentLocation = getLocation(ctx, "README.md", ctx.source);
456
+ let parsedContent = parsedContentCache.get(contentLocation);
457
+ if (parsedContent) return parsedContent;
580
458
  let { singlePage, firstLineDescription, linkMap } = ctx;
581
- let rawContent = await fetchContent(
582
- getLocation(ctx, "README.md", ctx.source)
583
- );
459
+ let rawContent = await fetchContent(contentLocation);
584
460
  let content = md.render(preprocessContent(rawContent));
585
- let dom = new JSDOM3(content);
461
+ let dom = new JSDOM2(content);
586
462
  let { nav, linkMap: navLinkMap } = buildNav(ctx, dom);
587
463
  let badges = "";
588
464
  let title = "";
@@ -650,7 +526,7 @@ async function getParsedContent(ctx) {
650
526
  intro[i] = `<p><span class="li">${s}</span></p>`;
651
527
  }
652
528
  }
653
- return {
529
+ parsedContent = {
654
530
  badges,
655
531
  // postprocessBadges(joinLines(badges)),
656
532
  title,
@@ -662,25 +538,63 @@ async function getParsedContent(ctx) {
662
538
  sections: sections.map(postprocess),
663
539
  nav
664
540
  };
541
+ parsedContentCache.set(contentLocation, parsedContent);
542
+ return parsedContent;
665
543
  }
666
544
 
667
- // src/bin/toFileContent.ts
668
- function toFileContent(x) {
669
- let s = x.replace(
670
- /\s+(\r?\n *<\/?(script|link|head|body|html|meta|title)[> ])/g,
671
- "$1"
672
- ).replace(
673
- /\s+(\r?\n *<\/?(main|header|footer|nav|section|div|h\d|p|ul|ol|li|table)[> ])/g,
674
- "$1"
675
- ).trim();
676
- return `${s}
677
- `;
545
+ // src/bin/content/getCounterContent.ts
546
+ function getCounterContent({ ymid }) {
547
+ if (!ymid) return "";
548
+ return `
549
+ <script>
550
+ (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
551
+ m[i].l=1*new Date();
552
+ for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
553
+ k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
554
+ (window, document, "script", "https://mc.yandex.com/metrika/tag.js", "ym");
555
+ ym(${ymid}, "init", {clickmap: true, trackLinks: true, accurateTrackBounce: true});
556
+ </script>
557
+ <noscript><div><img src="https://mc.yandex.com/watch/${ymid}" style="position:absolute;left:-9999px;" alt=""></div></noscript>
558
+ `.trim();
678
559
  }
679
560
 
680
- // src/bin/setContent.ts
561
+ // src/bin/content/getCSSRoot.ts
562
+ import { exec as defaultExec } from "node:child_process";
563
+ import { cp } from "node:fs/promises";
564
+ import { dirname, join as join4 } from "node:path";
565
+ import { fileURLToPath } from "node:url";
566
+ import { promisify } from "node:util";
567
+
568
+ // src/const/packageName.ts
569
+ var packageName = "@t8/docsgen";
570
+
571
+ // src/bin/content/getCSSRoot.ts
681
572
  var exec = promisify(defaultExec);
682
573
  var __filename = fileURLToPath(import.meta.url);
683
574
  var __dirname = dirname(__filename);
575
+ async function getCSSRoot(ctx, type) {
576
+ let { dir = "", assetsDir } = ctx;
577
+ let cssRoot = {
578
+ index: "",
579
+ content: ""
580
+ };
581
+ if (assetsDir) {
582
+ cssRoot.index = assetsDir;
583
+ cssRoot.content = `../${assetsDir}`;
584
+ await cp(join4(__dirname, "css"), join4(dir, cssRoot.index), {
585
+ force: true,
586
+ recursive: true
587
+ });
588
+ } else {
589
+ let packageVersion = (await exec(`npm view ${packageName} version`)).stdout.trim().split(".").slice(0, 2).join(".");
590
+ let packageUrl = `https://unpkg.com/${packageName}@${packageVersion}`;
591
+ cssRoot.index = `${packageUrl}/dist/css`;
592
+ cssRoot.content = `${packageUrl}/dist/css`;
593
+ }
594
+ return cssRoot[type];
595
+ }
596
+
597
+ // src/bin/content/getDefaultCodeStyleContent.ts
684
598
  function getDefaultCodeStyleContent(cssRoot) {
685
599
  return `
686
600
  <link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/styles/stackoverflow-light.min.css" media="(prefers-color-scheme: light)">
@@ -688,72 +602,87 @@ function getDefaultCodeStyleContent(cssRoot) {
688
602
  <link rel="stylesheet" href="${cssRoot}/code.css">
689
603
  <script src="https://unpkg.com/@highlightjs/cdn-assets@11.11.1/highlight.min.js"></script>
690
604
  <script>hljs.highlightAll()</script>
691
- `.trim();
605
+ `.trim();
692
606
  }
607
+
608
+ // src/bin/content/getIcon.ts
609
+ var iconTypeMap = {
610
+ ico: "image/x-icon",
611
+ svg: "image/svg+xml",
612
+ png: "image/png",
613
+ gif: "image/gif",
614
+ jpg: "image/jpeg",
615
+ jpeg: "image/jpeg"
616
+ };
617
+ function getIcon({ favicon, faviconType }) {
618
+ let icon = {
619
+ url: favicon,
620
+ type: faviconType
621
+ };
622
+ if (icon.url && !icon.type) {
623
+ let ext = icon.url.split("/").at(-1)?.split(".").at(-1)?.toLowerCase();
624
+ if (ext) icon.type = iconTypeMap[ext];
625
+ }
626
+ return icon;
627
+ }
628
+
629
+ // src/bin/content/getIconTag.ts
630
+ function getIconTag(ctx) {
631
+ let { url, type } = getIcon(ctx);
632
+ return url ? `<link rel="icon"${type ? ` type="${type}"` : ""} href="${url}">` : "";
633
+ }
634
+
635
+ // src/bin/content/getInjectedContent.ts
636
+ function getInjectedContent(ctx, page, target, mode) {
637
+ let injectedContent = ctx[mode]?.[target];
638
+ if (!injectedContent) return "";
639
+ return (Array.isArray(injectedContent) ? injectedContent : [injectedContent]).reduce((s, item) => {
640
+ if (item === void 0) return s;
641
+ if (typeof item === "string") return `${s}${s ? "\n" : ""}${item}`;
642
+ let { content, pages } = item;
643
+ if (!content || pages && !pages.includes(page)) return s;
644
+ return `${s}${s ? "\n" : ""}${content}`;
645
+ }, "");
646
+ }
647
+
648
+ // src/bin/content/getPlainTitle.ts
649
+ async function getPlainTitle(ctx) {
650
+ let { name, title, htmlTitle } = ctx;
651
+ let { title: parsedTitle } = await getParsedContent(ctx);
652
+ return escapeHTML(title || stripHTML(htmlTitle || parsedTitle, true) || name);
653
+ }
654
+
655
+ // src/bin/content/toFileContent.ts
656
+ function toFileContent(x) {
657
+ let s = x.replace(
658
+ /\s+(\r?\n *<\/?(script|link|head|body|html|meta|title)[> ])/g,
659
+ "$1"
660
+ ).replace(
661
+ /\s+(\r?\n *<\/?(main|header|footer|nav|section|div|h\d|p|ul|ol|li|table)[> ])/g,
662
+ "$1"
663
+ ).trim();
664
+ return `${s}
665
+ `;
666
+ }
667
+
668
+ // src/bin/content/tweakTypography.ts
693
669
  function tweakTypography(s = "") {
694
670
  return s.replace(/\b(for|in|on|at|to|with|a|an|the|its)\s+/gi, "$1\xA0").replace(/\b(React)\s+(apps?)\b/gi, "$1\xA0$2");
695
671
  }
696
- async function setContent(ctx) {
672
+
673
+ // src/bin/content/getIndexContent.ts
674
+ async function getIndexContent(ctx) {
697
675
  let {
698
- dir = "",
699
- assetsDir,
700
676
  root,
701
677
  contentDir = "",
702
678
  name,
703
- title,
704
679
  htmlTitle,
705
680
  description: packageDescription,
706
681
  isDevDep,
707
- backstory,
708
- redirect
682
+ backstory
709
683
  } = ctx;
710
684
  let counterContent = getCounterContent(ctx);
711
685
  let escapedPackageDescription = escapeHTML(packageDescription);
712
- let cssRoot = {
713
- index: "",
714
- content: ""
715
- };
716
- if (assetsDir) {
717
- cssRoot.index = assetsDir;
718
- cssRoot.content = `../${assetsDir}`;
719
- await cp(join3(__dirname, "css"), join3(dir, cssRoot.index), {
720
- force: true,
721
- recursive: true
722
- });
723
- } else {
724
- let packageVersion = (await exec(`npm view ${packageName} version`)).stdout.trim().split(".").slice(0, 2).join(".");
725
- let packageUrl = `https://unpkg.com/${packageName}@${packageVersion}`;
726
- cssRoot.index = `${packageUrl}/dist/css`;
727
- cssRoot.content = `${packageUrl}/dist/css`;
728
- }
729
- let icon = getIcon(ctx);
730
- let iconTag = icon.url ? `<link rel="icon"${icon.type ? ` type="${icon.type}"` : ""} href="${icon.url}">` : "";
731
- if (redirect) {
732
- let escapedRedirect = escapeHTML(redirect);
733
- await writeFile2(
734
- join3(dir, "index.html"),
735
- toFileContent(`
736
- <!DOCTYPE html>
737
- <html lang="en" data-layout="redirect" class="blank">
738
- <head>
739
- ${getInjectedContent(ctx, "redirect", "head", "prepend")}
740
- <meta charset="utf-8">
741
- <meta name="viewport" content="width=device-width">
742
- <meta http-equiv="refresh" content="0; URL=${escapedRedirect}">
743
- ${iconTag}
744
- ${getInjectedContent(ctx, "redirect", "head", "append")}
745
- </head>
746
- <body>
747
- ${getInjectedContent(ctx, "redirect", "body", "prepend")}
748
- ${counterContent}
749
- ${getInjectedContent(ctx, "redirect", "body", "append")}
750
- <script>window.location.replace("${escapedRedirect}");</script>
751
- </body>
752
- </html>
753
- `)
754
- );
755
- return;
756
- }
757
686
  let {
758
687
  title: parsedTitle,
759
688
  description,
@@ -761,86 +690,15 @@ ${getInjectedContent(ctx, "redirect", "body", "append")}
761
690
  features,
762
691
  note,
763
692
  installation,
764
- sections,
765
693
  nav
766
694
  } = await getParsedContent(ctx);
767
- let plainTitle = escapeHTML(
768
- title || stripHTML(htmlTitle || parsedTitle, true) || name
769
- );
695
+ let plainTitle = await getPlainTitle(ctx);
770
696
  let coverTitle = htmlTitle || parsedTitle || plainTitle;
771
697
  let descriptionContent = tweakTypography(description) || (escapedPackageDescription ? `<p>${tweakTypography(escapedPackageDescription)}<p>` : "");
772
698
  if (!installation || isDevDep !== void 0)
773
699
  installation = `npm i${isDevDep ? " -D" : ""} ${name}`;
774
- let navContent = await getNav(ctx, nav);
775
- let dirs = [contentDir];
776
- await Promise.all(
777
- dirs.map(async (path) => {
778
- let dirPath = join3(dir, path);
779
- try {
780
- await access(dirPath);
781
- } catch {
782
- await mkdir(dirPath);
783
- }
784
- })
785
- );
786
- await Promise.all([
787
- ...sections.map(
788
- async (content, i) => writeFile2(
789
- join3(dir, contentDir, `${nav[i]?.id ?? `_untitled_${i}`}.html`),
790
- toFileContent(`
791
- <!DOCTYPE html>
792
- <html lang="en" data-layout="section">
793
- <head>
794
- ${getInjectedContent(ctx, "section", "head", "prepend")}
795
- <meta charset="utf-8">
796
- <meta name="viewport" content="width=device-width, initial-scale=1">
797
- <meta name="description" content="${plainTitle}: ${escapeHTML(stripHTML(nav[i]?.title, true))}">
798
- <title>${escapeHTML(stripHTML(nav[i]?.title, true))} | ${plainTitle}</title>
799
- <link rel="stylesheet" href="${cssRoot.content}/base.css">
800
- <link rel="stylesheet" href="${cssRoot.content}/section.css">
801
- ${iconTag}
802
- ${nav[i + 1]?.id ? `<link rel="prefetch" href="${root}${contentDir}/${nav[i + 1]?.id}">` : ""}
803
- ${nav[i - 1]?.id ? `<link rel="prefetch" href="${root}${contentDir}/${nav[i - 1]?.id}">` : ""}
804
- ${getInjectedContent(ctx, "section", "head", "append")}
805
- </head>
806
- <body>
807
- ${getInjectedContent(ctx, "section", "body", "prepend")}
808
- <div class="layout">
809
- <div class="${navContent ? "" : "no-nav "}body">
810
- <main>
811
- <h1><a href="${root}">${plainTitle}</a></h1>
812
- ${content}
813
-
814
- <p class="pagenav">
815
- <span class="prev">
816
- <span class="icon">\u2190</span>
817
- ${nav[i - 1]?.id ? `<a href="${root}${contentDir}/${nav[i - 1]?.id}">${nav[i - 1]?.title}</a>` : `<a href="${root}">Intro</a>`}
818
- </span>
819
- <span class="sep">|</span>
820
- ${nav[i + 1]?.id ? `<span class="next"><a href="${root}${contentDir}/${nav[i + 1]?.id}">${nav[i + 1]?.title}</a> <span class="icon">\u2192</span></span>` : `<span class="repo">${getRepoLink(ctx)}</span>`}
821
- </p>
822
- </main>
823
- ${navContent ? "<hr>" : ""}
824
- ${navContent.replace(
825
- new RegExp(
826
- `(<li data-id="${escapeRegExp(nav[i]?.id)}">)<a href="[^"]+">([^<]+)</a>`
827
- ),
828
- "$1<strong>$2</strong>"
829
- )}
830
- </div>
831
- </div>
832
-
833
- ${content.includes("<pre><code ") ? getInjectedContent(ctx, "section", ":has-code", "append") || getDefaultCodeStyleContent(cssRoot.content) : ""}
834
- ${counterContent}
835
- ${getInjectedContent(ctx, "section", "body", "append")}
836
- </body>
837
- </html>
838
- `)
839
- )
840
- ),
841
- writeFile2(
842
- join3(dir, "index.html"),
843
- toFileContent(`
700
+ let cssRoot = await getCSSRoot(ctx, "index");
701
+ return toFileContent(`
844
702
  <!DOCTYPE html>
845
703
  <html lang="en" data-layout="index">
846
704
  <head>
@@ -849,9 +707,9 @@ ${getInjectedContent(ctx, "section", "body", "append")}
849
707
  <meta name="viewport" content="width=device-width, initial-scale=1">
850
708
  <meta name="description" content="${plainTitle}${escapedPackageDescription ? `: ${escapedPackageDescription}` : ""}">
851
709
  <title>${plainTitle}${escapedPackageDescription ? ` | ${escapedPackageDescription}` : ""}</title>
852
- <link rel="stylesheet" href="${cssRoot.index}/base.css">
853
- <link rel="stylesheet" href="${cssRoot.index}/index.css">
854
- ${iconTag}
710
+ <link rel="stylesheet" href="${cssRoot}/base.css">
711
+ <link rel="stylesheet" href="${cssRoot}/index.css">
712
+ ${getIconTag(ctx)}
855
713
  <link rel="prefetch" href="${root}start">
856
714
  ${nav[0] ? `<link rel="prefetch" href="${root}${contentDir}/${nav[0]?.id ?? ""}">` : ""}
857
715
  ${getInjectedContent(ctx, "index", "head", "append")}
@@ -892,16 +750,192 @@ ${intro || features || note ? `
892
750
  </main>
893
751
  </div>
894
752
 
895
- ${[description, intro, features, note].some((s) => s.includes("<pre><code ")) ? getInjectedContent(ctx, "index", ":has-code", "append") || getDefaultCodeStyleContent(cssRoot.index) : ""}
753
+ ${[description, intro, features, note].some((s) => s.includes("<pre><code ")) ? getInjectedContent(ctx, "index", ":has-code", "append") || getDefaultCodeStyleContent(cssRoot) : ""}
896
754
  ${counterContent}
897
755
  ${getInjectedContent(ctx, "index", "body", "append")}
898
756
  </body>
899
757
  </html>
900
- `)
758
+ `);
759
+ }
760
+
761
+ // src/bin/content/getRedirectContent.ts
762
+ function getRedirectContent(ctx) {
763
+ let { redirect } = ctx;
764
+ let escapedRedirect = escapeHTML(redirect);
765
+ return toFileContent(`
766
+ <!DOCTYPE html>
767
+ <html lang="en" data-layout="redirect" class="blank">
768
+ <head>
769
+ ${getInjectedContent(ctx, "redirect", "head", "prepend")}
770
+ <meta charset="utf-8">
771
+ <meta name="viewport" content="width=device-width">
772
+ <meta http-equiv="refresh" content="0; URL=${escapedRedirect}">
773
+ ${getIconTag(ctx)}
774
+ ${getInjectedContent(ctx, "redirect", "head", "append")}
775
+ </head>
776
+ <body>
777
+ ${getInjectedContent(ctx, "redirect", "body", "prepend")}
778
+ ${getCounterContent(ctx)}
779
+ ${getInjectedContent(ctx, "redirect", "body", "append")}
780
+ <script>window.location.replace("${escapedRedirect}");</script>
781
+ </body>
782
+ </html>
783
+ `);
784
+ }
785
+
786
+ // src/utils/escapeRegExp.ts
787
+ function escapeRegExp(s) {
788
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
789
+ }
790
+
791
+ // src/bin/content/getNav.ts
792
+ import { JSDOM as JSDOM3 } from "jsdom";
793
+
794
+ // src/bin/getNpmLink.ts
795
+ function getNpmLink({ npm }, className) {
796
+ if (!npm) return "";
797
+ return `<a href="${npm}"${className ? ` class="${className}"` : ""} target="_blank">npm</a>`;
798
+ }
799
+
800
+ // src/bin/content/getNav.ts
801
+ async function getNav(ctx, navItems) {
802
+ let { name, root, contentDir, backstory, nav } = ctx;
803
+ let navContent = await fetchContent(nav);
804
+ let s = "";
805
+ if (navContent) {
806
+ let navDom = new JSDOM3(navContent).window.document.body;
807
+ for (let link of navDom.querySelectorAll("a")) {
808
+ if (link.dataset.name === name) {
809
+ let parent = link.parentElement;
810
+ link.remove();
811
+ while (parent && parent.innerHTML.trim() === "") {
812
+ let nextParent = parent.parentElement;
813
+ parent.remove();
814
+ parent = nextParent;
815
+ }
816
+ }
817
+ }
818
+ navContent = navDom.innerHTML;
819
+ }
820
+ let navItemCount = 0;
821
+ if (navItems.length === 1) {
822
+ let { id, items } = navItems[0];
823
+ let itemLink = `${root}${contentDir}/${encodeURIComponent(id)}`;
824
+ for (let { id: id2, title } of items) {
825
+ s += `
826
+ <li><a href="${itemLink}#${encodeURIComponent(id2)}">${title}</a></li>`;
827
+ navItemCount++;
828
+ }
829
+ } else {
830
+ for (let { id, title, items } of navItems) {
831
+ let itemLink = `${root}${contentDir}/${encodeURIComponent(id)}`;
832
+ s += `
833
+ <li data-id="${id}"><a href="${itemLink}">${title}</a>`;
834
+ if (items.length !== 0) {
835
+ s += "\n <ul>";
836
+ for (let { id: id2, title: title2 } of items) {
837
+ s += `
838
+ <li><a href="${itemLink}#${encodeURIComponent(id2)}">${title2}</a></li>`;
839
+ navItemCount++;
840
+ }
841
+ s += "\n </ul>\n";
842
+ }
843
+ s += "</li>";
844
+ navItemCount++;
845
+ }
846
+ }
847
+ if ((!s || navItemCount < 2) && !navContent) return "";
848
+ s = s.trim();
849
+ s = s ? `<p class="title">Contents</p>
850
+ <ul>${s}
851
+ </ul>
852
+ ` : "";
853
+ let repoLink = getRepoLink(ctx);
854
+ let npmLink = getNpmLink(ctx);
855
+ if (repoLink || npmLink || backstory)
856
+ s += `
857
+ <p class="title">Resources</p>
858
+ <ul>
859
+ ${repoLink ? `<li>${repoLink}</li>` : ""}
860
+ ${npmLink ? `<li>${npmLink}</li>` : ""}
861
+ ${backstory ? `<li><a href="${backstory}">Backstory</a></li>` : ""}
862
+ </ul>
863
+ `;
864
+ s = s.trim();
865
+ s = s ? `<section>
866
+ ${s}
867
+ </section>` : "";
868
+ return `<nav class="aux">
869
+ ${s}
870
+ ${navContent}
871
+ </nav>`;
872
+ }
873
+
874
+ // src/bin/content/getSectionContent.ts
875
+ async function getSectionContent(ctx, index) {
876
+ let { root, contentDir = "" } = ctx;
877
+ let cssRoot = await getCSSRoot(ctx, "content");
878
+ let { sections, nav } = await getParsedContent(ctx);
879
+ let content = sections[index];
880
+ let navContent = await getNav(ctx, nav);
881
+ let plainTitle = await getPlainTitle(ctx);
882
+ return toFileContent(`
883
+ <!DOCTYPE html>
884
+ <html lang="en" data-layout="section">
885
+ <head>
886
+ ${getInjectedContent(ctx, "section", "head", "prepend")}
887
+ <meta charset="utf-8">
888
+ <meta name="viewport" content="width=device-width, initial-scale=1">
889
+ <meta name="description" content="${plainTitle}: ${escapeHTML(stripHTML(nav[index]?.title, true))}">
890
+ <title>${escapeHTML(stripHTML(nav[index]?.title, true))} | ${plainTitle}</title>
891
+ <link rel="stylesheet" href="${cssRoot}/base.css">
892
+ <link rel="stylesheet" href="${cssRoot}/section.css">
893
+ ${getIconTag(ctx)}
894
+ ${nav[index + 1]?.id ? `<link rel="prefetch" href="${root}${contentDir}/${nav[index + 1]?.id}">` : ""}
895
+ ${nav[index - 1]?.id ? `<link rel="prefetch" href="${root}${contentDir}/${nav[index - 1]?.id}">` : ""}
896
+ ${getInjectedContent(ctx, "section", "head", "append")}
897
+ </head>
898
+ <body>
899
+ ${getInjectedContent(ctx, "section", "body", "prepend")}
900
+ <div class="layout">
901
+ <div class="${navContent ? "" : "no-nav "}body">
902
+ <main>
903
+ <h1><a href="${root}">${plainTitle}</a></h1>
904
+ ${content}
905
+
906
+ <p class="pagenav">
907
+ <span class="prev">
908
+ <span class="icon">\u2190</span>
909
+ ${nav[index - 1]?.id ? `<a href="${root}${contentDir}/${nav[index - 1]?.id}">${nav[index - 1]?.title}</a>` : `<a href="${root}">Intro</a>`}
910
+ </span>
911
+ <span class="sep">|</span>
912
+ ${nav[index + 1]?.id ? `<span class="next"><a href="${root}${contentDir}/${nav[index + 1]?.id}">${nav[index + 1]?.title}</a> <span class="icon">\u2192</span></span>` : `<span class="repo">${getRepoLink(ctx)}</span>`}
913
+ </p>
914
+ </main>
915
+ ${navContent ? "<hr>" : ""}
916
+ ${navContent.replace(
917
+ new RegExp(
918
+ `(<li data-id="${escapeRegExp(nav[index]?.id)}">)<a href="[^"]+">([^<]+)</a>`
901
919
  ),
902
- writeFile2(
903
- join3(dir, "start.html"),
904
- toFileContent(`
920
+ "$1<strong>$2</strong>"
921
+ )}
922
+ </div>
923
+ </div>
924
+
925
+ ${content.includes("<pre><code ") ? getInjectedContent(ctx, "section", ":has-code", "append") || getDefaultCodeStyleContent(cssRoot) : ""}
926
+ ${getCounterContent(ctx)}
927
+ ${getInjectedContent(ctx, "section", "body", "append")}
928
+ </body>
929
+ </html>
930
+ `);
931
+ }
932
+
933
+ // src/bin/content/getStartContent.ts
934
+ async function getStartContent(ctx) {
935
+ let { root, contentDir = "" } = ctx;
936
+ let { nav } = await getParsedContent(ctx);
937
+ let plainTitle = await getPlainTitle(ctx);
938
+ return toFileContent(`
905
939
  <!DOCTYPE html>
906
940
  <html lang="en" data-layout="start" class="blank">
907
941
  <head>
@@ -910,8 +944,8 @@ ${getInjectedContent(ctx, "index", "body", "append")}
910
944
  <meta name="viewport" content="width=device-width">
911
945
  <meta http-equiv="refresh" content="0; URL=${root}${contentDir}/${nav[0]?.id}">
912
946
  <title>${plainTitle}</title>
913
- <link rel="stylesheet" href="${cssRoot.index}/base.css">
914
- ${iconTag}
947
+ <link rel="stylesheet" href="${await getCSSRoot(ctx, "index")}/base.css">
948
+ ${getIconTag(ctx)}
915
949
  <script>window.location.replace("${root}${contentDir}/${nav[0]?.id}");</script>
916
950
  ${getInjectedContent(ctx, "start", "head", "append")}
917
951
  </head>
@@ -921,18 +955,37 @@ ${getInjectedContent(ctx, "start", "body", "prepend")}
921
955
  <h1>${plainTitle}</h1>
922
956
  </div>
923
957
 
924
- ${counterContent}
958
+ ${getCounterContent(ctx)}
925
959
  ${getInjectedContent(ctx, "start", "body", "append")}
926
960
  </body>
927
961
  </html>
928
- `)
929
- )
962
+ `);
963
+ }
964
+
965
+ // src/bin/setContent.ts
966
+ async function setContent(ctx) {
967
+ let { dir = "", contentDir = "", redirect } = ctx;
968
+ if (redirect) {
969
+ await writeFile2(join5(dir, "index.html"), getRedirectContent(ctx));
970
+ return;
971
+ }
972
+ let { sections, nav } = await getParsedContent(ctx);
973
+ await createDirs(ctx);
974
+ await Promise.all([
975
+ ...sections.map(
976
+ async (_, i) => writeFile2(
977
+ join5(dir, contentDir, `${nav[i]?.id ?? `_Section_${i + 1}`}.html`),
978
+ await getSectionContent(ctx, i)
979
+ )
980
+ ),
981
+ writeFile2(join5(dir, "index.html"), await getIndexContent(ctx)),
982
+ writeFile2(join5(dir, "start.html"), await getStartContent(ctx))
930
983
  ]);
931
984
  }
932
985
 
933
986
  // src/bin/setImages.ts
934
987
  import { writeFile as writeFile3 } from "node:fs/promises";
935
- import { join as join4 } from "node:path";
988
+ import { join as join6 } from "node:path";
936
989
 
937
990
  // src/utils/getIconContent.ts
938
991
  function getIconContent(baseColor = "gray") {
@@ -952,7 +1005,7 @@ function getIconContent(baseColor = "gray") {
952
1005
  // src/bin/setImages.ts
953
1006
  async function setImages({ dir = "", favicon }) {
954
1007
  if (favicon) return;
955
- await writeFile3(join4(dir, "./favicon.svg"), `${getIconContent()}
1008
+ await writeFile3(join6(dir, "./favicon.svg"), `${getIconContent()}
956
1009
  `);
957
1010
  }
958
1011