@abreen/tada 1.17.0 → 1.18.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 (107) hide show
  1. package/README.md +41 -7
  2. package/bin/tada.ts +23 -9
  3. package/bin/validators.ts +34 -1
  4. package/build/branding.ts +41 -0
  5. package/build/build-manifest.ts +10 -5
  6. package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
  7. package/build/build-validation.ts +176 -0
  8. package/build/bundle.ts +75 -65
  9. package/build/columns-plugin.ts +27 -1
  10. package/build/config-loader.ts +3 -3
  11. package/build/custom-fonts.ts +8 -5
  12. package/build/deflist-id-plugin.ts +2 -2
  13. package/build/generate-favicon.ts +7 -6
  14. package/build/generate-fonts.ts +8 -15
  15. package/build/generate-katex-assets.ts +19 -17
  16. package/build/generate-web-app-manifest.ts +3 -11
  17. package/build/lodash-template.ts +14 -0
  18. package/build/output-publication.ts +238 -0
  19. package/build/pagefind.ts +49 -21
  20. package/build/pdf-text.ts +41 -1
  21. package/build/pipeline.ts +95 -101
  22. package/build/site-assets.ts +46 -0
  23. package/build/site-build.ts +267 -0
  24. package/build/source-model.ts +223 -229
  25. package/build/source-records.ts +69 -81
  26. package/build/template-globals.ts +3 -0
  27. package/build/templates.ts +12 -8
  28. package/build/toc-plugin.ts +21 -21
  29. package/build/types.d.ts +38 -38
  30. package/build/util.ts +0 -4
  31. package/build/utils/code.ts +51 -117
  32. package/build/utils/final-html.ts +171 -47
  33. package/build/utils/jdi-runner/LiterateRunner.java +9 -1
  34. package/build/utils/literate-java.ts +11 -7
  35. package/build/utils/markdown-partials.ts +4 -3
  36. package/build/utils/markdown.ts +6 -17
  37. package/build/utils/paths.ts +3 -0
  38. package/build/utils/plain-text.ts +18 -0
  39. package/build/utils/render.ts +197 -154
  40. package/build/utils/shiki-highlighter.ts +11 -0
  41. package/build/utils/source-template.ts +2 -2
  42. package/build/utils/trace-core.ts +8 -83
  43. package/build/utils/trace.ts +38 -95
  44. package/build/validate-config-links.ts +39 -6
  45. package/build/watch/build-incremental.ts +46 -103
  46. package/build/watch/compiler.ts +158 -37
  47. package/build/watch/engine.ts +207 -162
  48. package/build/watch/index.ts +9 -2
  49. package/build/watch/planner.ts +88 -144
  50. package/build/watch/runtime.ts +21 -34
  51. package/build/watch/types.d.ts +32 -64
  52. package/package.json +4 -5
  53. package/schema/site.schema.json +18 -2
  54. package/src/_content.scss +447 -0
  55. package/src/_fonts.scss +90 -0
  56. package/src/_layout.scss +50 -35
  57. package/src/appearance-picker/{style.scss → _index.scss} +1 -1
  58. package/src/code/{style.scss → _index.scss} +61 -0
  59. package/src/header/{_base.scss → _index.scss} +98 -6
  60. package/src/index.ts +1 -17
  61. package/src/navigate/{style.scss → _index.scss} +20 -0
  62. package/src/navigate/eligible.ts +0 -40
  63. package/src/navigate/index.ts +50 -5
  64. package/src/navigate/lifecycle.ts +19 -2
  65. package/src/navigate/runtime.ts +20 -4
  66. package/src/page-update/index.ts +33 -20
  67. package/src/print/{style.scss → _index.scss} +3 -1
  68. package/src/search/index.ts +30 -2
  69. package/src/slides/{style.scss → _index.scss} +5 -0
  70. package/src/style.scss +24 -536
  71. package/src/timezone/index.ts +29 -3
  72. package/src/timezone/time-format.ts +4 -0
  73. package/src/toc/index.ts +6 -5
  74. package/src/trace/index.ts +11 -1
  75. package/templates/_author.html +2 -2
  76. package/templates/_download.html +1 -1
  77. package/templates/_footer.html +1 -1
  78. package/templates/_heading.html +3 -3
  79. package/templates/_nav.html +2 -2
  80. package/templates/_page-bottom.html +6 -0
  81. package/templates/_top.html +18 -14
  82. package/templates/code.html +22 -21
  83. package/templates/default.html +12 -11
  84. package/templates/literate.html +6 -5
  85. package/build/copy.ts +0 -82
  86. package/build/generate-content-assets.ts +0 -121
  87. package/build/utils/content-files.ts +0 -170
  88. package/build/watch/assets.ts +0 -103
  89. package/build/watch/build-full.ts +0 -97
  90. package/build/watch/build-helpers.ts +0 -66
  91. package/build/watch/build-result.ts +0 -19
  92. package/build/watch/fs-commit.ts +0 -153
  93. package/build/watch/mutations.ts +0 -73
  94. package/build/watch/snapshot.ts +0 -201
  95. package/build/watch/validation.ts +0 -50
  96. package/src/code.scss +0 -60
  97. package/src/critical.scss +0 -5
  98. package/src/header/style.scss +0 -91
  99. /package/src/{literate.scss → _literate.scss} +0 -0
  100. /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
  101. /package/src/anchor/{style.scss → _index.scss} +0 -0
  102. /package/src/page-update/{style.scss → _index.scss} +0 -0
  103. /package/src/question/{style.scss → _index.scss} +0 -0
  104. /package/src/search/{style.scss → _index.scss} +0 -0
  105. /package/src/timezone/{style.scss → _index.scss} +0 -0
  106. /package/src/toc/{style.scss → _index.scss} +0 -0
  107. /package/src/trace/{style.scss → _index.scss} +0 -0
@@ -5,6 +5,7 @@ interface WidgetState {
5
5
  manifest: TraceManifest;
6
6
  chunks: Map<number, TraceChunkEntry[]>;
7
7
  currentStep: number;
8
+ navigationRequest: number;
8
9
  }
9
10
 
10
11
  interface TraceResizeState {
@@ -54,6 +55,7 @@ async function goToStep(
54
55
  stepIndex: number,
55
56
  doc: Document,
56
57
  ): Promise<void> {
58
+ const request = ++state.navigationRequest;
57
59
  stepIndex = Math.max(0, Math.min(stepIndex, state.manifest.totalSteps - 1));
58
60
  const lastChunk = Math.floor(stepIndex / state.manifest.chunkSize);
59
61
  const loads: Promise<void>[] = [];
@@ -61,6 +63,9 @@ async function goToStep(
61
63
  loads.push(loadChunk(state, manifestUrl, i));
62
64
  }
63
65
  await Promise.all(loads);
66
+ if (request !== state.navigationRequest) {
67
+ return;
68
+ }
64
69
  state.currentStep = stepIndex;
65
70
  renderWidgetState(state, elements, doc);
66
71
  }
@@ -492,7 +497,12 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
492
497
  }
493
498
  const manifest: TraceManifest = await res.json();
494
499
 
495
- const state: WidgetState = { manifest, chunks: new Map(), currentStep: 0 };
500
+ const state: WidgetState = {
501
+ manifest,
502
+ chunks: new Map(),
503
+ currentStep: 0,
504
+ navigationRequest: 0,
505
+ };
496
506
 
497
507
  await loadChunk(state, manifestUrl, 0);
498
508
 
@@ -3,13 +3,13 @@
3
3
  <% if (page.author.url) { %>
4
4
  <a href="<%- encodeAuthoredUrl(page.author.url) %>">
5
5
  <% } %>
6
- <span class="text"><%= page.author.name %></span>
6
+ <span class="text"><%- page.author.name %></span>
7
7
  <% if (page.author.avatar) { %>
8
8
  <img
9
9
  width="32"
10
10
  height="32"
11
11
  src="<%- encodeAuthoredUrl(page.author.avatar) %>"
12
- alt="Picture of <%= page.author.name %>"
12
+ alt="Picture of <%- page.author.name %>"
13
13
  class="avatar"
14
14
  />
15
15
  <% } else { %>
@@ -1 +1 @@
1
- <a class="button" href="<%= page.codeFilePath %>" download="<%= page.downloadName %>" data-pagefind-ignore>Download</a>
1
+ <a class="button" href="<%= page.codeFilePath %>" download="<%- page.downloadName %>" data-pagefind-ignore>Download</a>
@@ -1,5 +1,5 @@
1
1
  <% if (site.features.footer) { %>
2
2
  <footer data-pagefind-ignore>
3
- <p>Built using <a href="https://github.com/abreen/Tada" target="_blank" rel="noopener noreferrer">Tada</a> <%= tadaVersion %></p>
3
+ <p>Built using <a href="https://github.com/abreen/Tada" target="_blank" rel="noopener noreferrer">Tada</a> <%- tadaVersion %></p>
4
4
  </footer>
5
5
  <% } %>
@@ -1,14 +1,14 @@
1
1
  <div class="title-and-info" data-pagefind-body>
2
2
  <% if (page.parent) { %>
3
- <a class="breadcrumb" href="<%- encodeAuthoredUrl(page.parent) %>" data-pagefind-ignore><%= page.parentLabel %></a>
3
+ <a class="breadcrumb" href="<%- encodeAuthoredUrl(page.parent) %>" data-pagefind-ignore><%- page.parentLabel %></a>
4
4
  <% } %>
5
5
  <h1 data-pagefind-weight="10" data-pagefind-meta="title_html[data-title-html]" data-title-html="<%- page.titleHtml %>"><%= page.titleHtml %></h1>
6
6
 
7
7
  <div class="info" data-pagefind-ignore>
8
8
  <% if (page.published) { %>
9
9
  <div class="publish-date">
10
- <time datetime="<%= isoDate(page.published) %>">
11
- <%= readableDate(page.published) %>
10
+ <time datetime="<%- isoDate(page.published) %>">
11
+ <%- readableDate(page.published) %>
12
12
  </time>
13
13
  </div>
14
14
  <% } %>
@@ -1,7 +1,7 @@
1
1
  <nav>
2
2
  <% config('nav').forEach(function(section) { %>
3
3
  <div>
4
- <p><%= section.title %></p>
4
+ <p><%- section.title %></p>
5
5
  <ul>
6
6
  <% section.links.forEach(function(link) { %>
7
7
  <li>
@@ -10,7 +10,7 @@
10
10
  <% if (!link.disabled) { %>href="<%- encodeAuthoredUrl(link.internal || link.external) %>"<% } %>
11
11
  <% if (link.external) { %>target="_blank" rel="noopener noreferrer"<% } %>
12
12
  >
13
- <%= link.text %></a>
13
+ <%- link.text %></a>
14
14
  </li>
15
15
  <% }) %>
16
16
  </ul>
@@ -0,0 +1,6 @@
1
+ <% if (site.features.footer || site.features.pickers) { %>
2
+ <div class="page-bottom" data-pagefind-ignore>
3
+ <%= render('_footer.html') %>
4
+ <%= render('_appearance-picker.html') %>
5
+ </div>
6
+ <% } %>
@@ -1,11 +1,15 @@
1
1
  <!doctype html>
2
2
  <html lang="en"
3
- data-default-font-preference="<%= site.defaultFont %>"
4
- data-default-contrast-preference="<%= site.defaultContrast %>"<% if (site.defaultFont === 'serif') { %>
3
+ data-default-font-preference="<%- site.defaultFont %>"
4
+ data-default-contrast-preference="<%- site.defaultContrast %>"<% if (site.defaultFont === 'serif') { %>
5
5
  data-font-preference="serif"<% } %><% if (site.defaultContrast === 'high') { %>
6
6
  data-contrast-preference="high"<% } %>>
7
7
  <head>
8
+ <meta charset="UTF-8" />
8
9
  <% if (site.features.favicon) { %>
10
+ <% if (site.favicon) { %>
11
+ <link rel="icon" type="image/x-icon" href="/<%- encodePublicAssetPath(site.favicon) %>" sizes="any" />
12
+ <% } else { %>
9
13
  <link rel="icon" href="/favicon.ico" sizes="any" />
10
14
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
11
15
  <link rel="icon" type="image/png" sizes="512x512" href="/favicon-512.png" />
@@ -19,32 +23,32 @@
19
23
  <link rel="apple-touch-icon" sizes="512x512" href="/favicon-512.png" />
20
24
  <link rel="apple-touch-icon" sizes="256x256" href="/favicon-256.png" />
21
25
  <link rel="manifest" href="/manifest.json" />
22
- <meta name="apple-mobile-web-app-title" content="<%= site.symbol %>">
26
+ <meta name="apple-mobile-web-app-title" content="<%- site.symbol %>">
23
27
  <meta name="mobile-web-app-capable" content="yes">
24
28
  <% } %>
25
- <meta name="generator" content="Tada <%= tadaVersion %>" />
26
- <meta charset="UTF-8" />
29
+ <% } %>
30
+ <meta name="generator" content="Tada <%- tadaVersion %>" />
27
31
  <meta http-equiv="cache-control" content="no-cache" />
28
32
  <meta http-equiv="expires" content="0" />
29
33
  <meta http-equiv="pragma" content="no-cache" />
30
34
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
31
35
  <% if (page.description) { %>
32
- <meta name="description" content="<%= page.description %>" />
36
+ <meta name="description" content="<%- page.description %>" />
33
37
  <% } %>
34
- <meta property="og:title" content="<%= page.title %>" />
38
+ <meta property="og:title" content="<%- page.title %>" />
35
39
  <% if (page.author) { %>
36
- <meta name="author" content="<%= page.author.name %>" />
37
- <meta property="og:author" content="<%= page.author.name %>" />
40
+ <meta name="author" content="<%- page.author.name %>" />
41
+ <meta property="og:author" content="<%- page.author.name %>" />
38
42
  <% } %>
39
- <title><%= page.title %><%= site.titlePostfix %></title>
43
+ <title><%- page.title %><%- site.titlePostfix %></title>
40
44
  <script>
41
45
  (() => {
42
46
  document.documentElement.classList.add('js');
43
47
  const fontPreferenceFamilies = {
44
48
  sans: ['Inter', 'Google Sans Code'],
45
49
  serif: [
46
- '<%= site.fontOverrides?.serif ? 'Tada Custom Serif' : 'Source Serif 4' %>',
47
- '<%= site.fontOverrides?.serifMono ? 'Tada Custom Serif Mono' : 'Courier Prime' %>',
50
+ '<%= site.fontOverrides?.serif?.regular ? 'Tada Custom Serif' : 'Source Serif 4' %>',
51
+ '<%= site.fontOverrides?.serifMono?.regular ? 'Tada Custom Serif Mono' : 'Courier Prime' %>',
48
52
  ],
49
53
  };
50
54
  const fontPreferenceLoader = {
@@ -215,8 +219,8 @@
215
219
  <path class="menu-icon-line menu-icon-line-middle" d="M3 12h18" />
216
220
  <path class="menu-icon-line menu-icon-line-bottom" d="M3 18h18" />
217
221
  </svg>
218
- <% if (site.symbol) { %><span class="logo" aria-hidden="true"><%= site.symbol %></span><% } %>
219
- <span class="site-title" aria-hidden="true"><%= site.title %></span>
222
+ <% if (site.logo) { %><img class="logo-image" src="/<%- encodePublicAssetPath(site.logo) %>" alt="" aria-hidden="true" /><% } else if (site.symbol) { %><span class="logo" aria-hidden="true"><%- site.symbol %></span><% } %>
223
+ <span class="site-title" aria-hidden="true"><%- site.title %></span>
220
224
  </summary>
221
225
  <div class="content">
222
226
  <%= render('_nav.html') %>
@@ -2,28 +2,29 @@
2
2
 
3
3
  <div class="main-content">
4
4
  <main class="body" data-pagefind-body>
5
- <div class="file-header">
6
- <h1 class="file-title" data-pagefind-weight="10" data-pagefind-meta="title_html[data-title-html]" data-title-html="<%- page.titleHtml %>"><%= page.titleHtml %></h1>
7
- <%= render('_download.html') %>
5
+ <div class="page-content">
6
+ <div class="file-header">
7
+ <h1 class="file-title" data-pagefind-weight="10" data-pagefind-meta="title_html[data-title-html]" data-title-html="<%- page.titleHtml %>"><%= page.titleHtml %></h1>
8
+ <%= render('_download.html') %>
9
+ </div>
10
+ <div class="code-body">
11
+ <%= content %>
12
+ </div>
13
+ <div class="code-scrollbar" aria-hidden="true">
14
+ <div></div>
15
+ </div>
16
+ <% if (page.tocItems && page.tocItems.length > 0) { %>
17
+ <div class="code-search-index" aria-hidden="true" inert>
18
+ <% page.tocItems.forEach(function(item) { %>
19
+ <section>
20
+ <h2 id="L<%- item.line %>"><%- item.name %></h2>
21
+ <p><%- item.label %> defined at line <%- item.line %>.</p>
22
+ </section>
23
+ <% }); %>
24
+ </div>
25
+ <% } %>
8
26
  </div>
9
- <div class="code-body">
10
- <%= content %>
11
- </div>
12
- <div class="code-scrollbar" aria-hidden="true">
13
- <div></div>
14
- </div>
15
- <% if (page.tocItems && page.tocItems.length > 0) { %>
16
- <div class="code-search-index" aria-hidden="true" inert>
17
- <% page.tocItems.forEach(function(item) { %>
18
- <section>
19
- <h2 id="L<%= item.line %>"><%= item.name %></h2>
20
- <p><%= item.label %> defined at line <%= item.line %>.</p>
21
- </section>
22
- <% }); %>
23
- </div>
24
- <% } %>
25
- <%= render('_footer.html') %>
26
- <%= render('_appearance-picker.html') %>
27
+ <%= render('_page-bottom.html') %>
27
28
  </main>
28
29
  <% if (page.tocHtml) { %>
29
30
  <nav class="toc"><%= page.tocHtml %></nav>
@@ -3,18 +3,19 @@
3
3
  <div class="main-content">
4
4
  <%= render('_heading.html') %>
5
5
  <main class="body" data-pagefind-body>
6
- <% if (page.slides) { %>
7
- <div class="slides-header" data-pagefind-ignore>
8
- <button type="button" data-slides-present disabled>Present</button>
9
- <label>
10
- <input id="slides-fullscreen" type="checkbox" data-slides-fullscreen checked disabled>
11
- Full screen
12
- </label>
6
+ <div class="page-content">
7
+ <% if (page.slides) { %>
8
+ <div class="slides-header" data-pagefind-ignore>
9
+ <button type="button" data-slides-present disabled>Present</button>
10
+ <label>
11
+ <input id="slides-fullscreen" type="checkbox" data-slides-fullscreen checked disabled>
12
+ Full screen
13
+ </label>
14
+ </div>
15
+ <% } %>
16
+ <%= content %>
13
17
  </div>
14
- <% } %>
15
- <%= content %>
16
- <%= render('_footer.html') %>
17
- <%= render('_appearance-picker.html') %>
18
+ <%= render('_page-bottom.html') %>
18
19
  </main>
19
20
  <% if (page.toc && page.tocHtml) { %>
20
21
  <nav class="toc"><%= page.tocHtml %></nav>
@@ -3,12 +3,13 @@
3
3
  <div class="main-content">
4
4
  <%= render('_heading.html') %>
5
5
  <main class="body" data-pagefind-body>
6
- <div class="file-header">
7
- <code><%= page.downloadName %></code><%= render('_download.html') %>
6
+ <div class="page-content">
7
+ <div class="file-header">
8
+ <code><%- page.downloadName %></code><%= render('_download.html') %>
9
+ </div>
10
+ <%= content %>
8
11
  </div>
9
- <%= content %>
10
- <%= render('_footer.html') %>
11
- <%= render('_appearance-picker.html') %>
12
+ <%= render('_page-bottom.html') %>
12
13
  </main>
13
14
  <% if (page.toc && page.tocHtml) { %>
14
15
  <nav class="toc"><%= page.tocHtml %></nav>
package/build/copy.ts DELETED
@@ -1,82 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { toPosix } from './utils/paths';
4
- import { makeLogger } from './log';
5
- import { B } from './colors';
6
-
7
- const log = makeLogger(import.meta.url);
8
-
9
- interface CollectedFile {
10
- abs: string;
11
- rel: string;
12
- }
13
-
14
- function collectFiles(dir: string): CollectedFile[] {
15
- let entries: fs.Dirent[];
16
- try {
17
- entries = fs.readdirSync(dir, { withFileTypes: true, recursive: true });
18
- } catch (err: unknown) {
19
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
20
- return [];
21
- }
22
- throw err;
23
- }
24
- return entries
25
- .filter(entry => entry.isFile())
26
- .map(entry => {
27
- const abs = path.join(entry.parentPath, entry.name);
28
- const rel = path.relative(dir, abs);
29
- return { abs, rel: toPosix(rel) };
30
- });
31
- }
32
-
33
- export function copyPublicFiles(
34
- publicDir: string,
35
- distDir: string,
36
- ): Set<string> {
37
- const files = collectFiles(publicDir);
38
- const publicRelPaths = new Set<string>();
39
- for (const { abs, rel } of files) {
40
- const dest = path.join(distDir, rel);
41
- fs.mkdirSync(path.dirname(dest), { recursive: true });
42
- fs.copyFileSync(abs, dest);
43
- publicRelPaths.add(rel);
44
- log.info`Copying public file ${B`${rel}`}`;
45
- }
46
- return publicRelPaths;
47
- }
48
-
49
- export function copyContentAssets(
50
- contentDir: string,
51
- distDir: string,
52
- processedExtensions: string[],
53
- publicRelPaths: Set<string>,
54
- ): Set<string> {
55
- const processedExtSet = new Set(processedExtensions);
56
- const files = collectFiles(contentDir);
57
- const contentAssetRelPaths = new Set<string>();
58
- const conflicts: string[] = [];
59
- for (const { abs, rel } of files) {
60
- const ext = path.extname(abs).slice(1).toLowerCase();
61
- if (processedExtSet.has(ext)) {
62
- continue;
63
- }
64
- contentAssetRelPaths.add(rel);
65
- if (publicRelPaths.has(rel)) {
66
- conflicts.push(rel);
67
- }
68
- const dest = path.join(distDir, rel);
69
- fs.mkdirSync(path.dirname(dest), { recursive: true });
70
- fs.copyFileSync(abs, dest);
71
- }
72
- if (conflicts.length > 0) {
73
- for (const rel of conflicts) {
74
- log.error`content/${B`${rel}`} conflicts with public/${B`${rel}`}`;
75
- }
76
- const noun = conflicts.length === 1 ? 'file' : 'files';
77
- throw new Error(
78
- `${conflicts.length} ${noun} in content/ and public/ have the same path`,
79
- );
80
- }
81
- return contentAssetRelPaths;
82
- }
@@ -1,121 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { makeLogger } from './log';
4
- import { getRuntimeBundledShikiLanguages } from './site-variables';
5
- import { createContentRecord } from './source-records';
6
- import { validateConfigLinks } from './validate-config-links';
7
- import { config, getConfigFileName } from './templates';
8
- import { initHighlighter } from './utils/shiki-highlighter';
9
- import { checkTraceToolAvailability } from './utils/trace';
10
- import type {
11
- SiteVariables,
12
- ContentRenderOptions,
13
- ContentRenderResult,
14
- HtmlOutputAnalysis,
15
- TraceToolAvailability,
16
- } from './types';
17
- import type { TraceCache } from './watch/compiler-types';
18
-
19
- const log = makeLogger(import.meta.url);
20
-
21
- function cloneHtmlOutputAnalysis(
22
- analysis: HtmlOutputAnalysis,
23
- ): HtmlOutputAnalysis {
24
- return { outgoingTargets: new Set(analysis.outgoingTargets) };
25
- }
26
-
27
- function writeRecordOutputs(
28
- distDir: string,
29
- outputs: Map<string, string | Buffer>,
30
- ): void {
31
- for (const [outputPath, content] of outputs) {
32
- const outPath = path.join(distDir, outputPath);
33
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
34
- fs.writeFileSync(outPath, content);
35
- }
36
- }
37
-
38
- export class ContentRenderer {
39
- private siteVariables: SiteVariables;
40
- private traceToolAvailability: TraceToolAvailability | undefined;
41
- private traceCache: TraceCache = new Map();
42
-
43
- constructor(siteVariables: SiteVariables) {
44
- this.siteVariables = siteVariables;
45
- }
46
-
47
- async initHighlighter(): Promise<void> {
48
- await initHighlighter(getRuntimeBundledShikiLanguages(this.siteVariables));
49
- }
50
-
51
- processContent({
52
- distDir,
53
- assetFiles,
54
- scan,
55
- }: ContentRenderOptions): ContentRenderResult {
56
- const buildContentFiles = [...scan.buildContentFiles];
57
- const validInternalTargets = scan.validTargets;
58
-
59
- const errors: Error[] = [];
60
- const htmlAssetsByPath = new Map<string, string>();
61
- const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
62
-
63
- const configLinkErrors = validateConfigLinks(
64
- validInternalTargets,
65
- config('nav'),
66
- config('authors'),
67
- {
68
- navFileName: getConfigFileName('nav'),
69
- authorsFileName: getConfigFileName('authors'),
70
- },
71
- );
72
- for (const msg of configLinkErrors) {
73
- errors.push(new Error(msg));
74
- }
75
-
76
- if (buildContentFiles.length > 0) {
77
- const noun = buildContentFiles.length === 1 ? 'file' : 'files';
78
- log.info`Processing ${buildContentFiles.length} content ${noun}`;
79
- }
80
-
81
- if (!this.traceToolAvailability && buildContentFiles.length > 0) {
82
- this.traceToolAvailability = checkTraceToolAvailability();
83
- if (!this.traceToolAvailability.java) {
84
- log.warn`javac was not found; literate Java pages will not include execution output`;
85
- }
86
- }
87
-
88
- for (const filePath of buildContentFiles) {
89
- try {
90
- const record = createContentRecord({
91
- filePath,
92
- siteVariables: this.siteVariables,
93
- scan,
94
- assetFiles,
95
- outputDir: distDir,
96
- traceCache: this.traceCache,
97
- traceToolAvailability: this.traceToolAvailability,
98
- skipLiterateJavaExecution: !this.traceToolAvailability?.java,
99
- });
100
- writeRecordOutputs(distDir, record.outputs);
101
- for (const [outputPath, content] of record.outputs) {
102
- if (!outputPath.endsWith('.html') || typeof content !== 'string') {
103
- continue;
104
- }
105
- htmlAssetsByPath.set(outputPath, content);
106
- const analysis = record.htmlAnalysisByOutputPath?.get(outputPath);
107
- if (analysis) {
108
- htmlAnalysisByPath.set(
109
- outputPath,
110
- cloneHtmlOutputAnalysis(analysis),
111
- );
112
- }
113
- }
114
- } catch (err) {
115
- errors.push(err as Error);
116
- }
117
- }
118
-
119
- return { errors, htmlAssetsByPath, htmlAnalysisByPath };
120
- }
121
- }
@@ -1,170 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import {
4
- getProcessedExts,
5
- getSourceOutputPaths,
6
- getSourceTargetPaths,
7
- isBuildContentSource,
8
- shouldSkipContentFile,
9
- } from '../source-model';
10
- import { getPublicDir, toPosix } from './paths';
11
-
12
- function walkFiles(dir: string): string[] {
13
- return fs.readdirSync(dir).flatMap(file => {
14
- const fullPath = path.join(dir, file);
15
- if (fs.statSync(fullPath).isDirectory()) {
16
- return walkFiles(fullPath);
17
- }
18
- return [fullPath];
19
- });
20
- }
21
-
22
- export function getContentFiles(
23
- contentDir: string,
24
- codeExtensions: string[],
25
- ): string[] {
26
- const extensions = ['md', 'html', ...codeExtensions];
27
- const pattern = new RegExp(`\\.(${extensions.join('|')})$`);
28
-
29
- return walkFiles(contentDir).filter(filePath => {
30
- return pattern.test(path.basename(filePath));
31
- });
32
- }
33
-
34
- export function getBuildContentFiles(
35
- contentDir: string,
36
- codeExtensions: string[],
37
- ): string[] {
38
- const processedExts = getProcessedExts(codeExtensions);
39
-
40
- return getContentFiles(contentDir, codeExtensions).filter(filePath =>
41
- isBuildContentSource(filePath, processedExts),
42
- );
43
- }
44
-
45
- export function getContentSourceOutputRelPaths(
46
- contentDir: string,
47
- filePath: string,
48
- codeExtensions: string[],
49
- ): Set<string> {
50
- const processedExts = getProcessedExts(codeExtensions);
51
- const buildContentFiles = new Set(
52
- getBuildContentFiles(contentDir, codeExtensions),
53
- );
54
-
55
- return getSourceOutputPaths({
56
- contentDir,
57
- filePath,
58
- processedExts,
59
- buildContent: buildContentFiles.has(filePath),
60
- });
61
- }
62
-
63
- export function getContentOutputRelPaths(
64
- contentDir: string,
65
- codeExtensions: string[],
66
- ): Set<string> {
67
- const buildContentFiles = getBuildContentFiles(contentDir, codeExtensions);
68
- const processedExts = getProcessedExts(codeExtensions);
69
- const outputs = new Set<string>();
70
-
71
- for (const filePath of buildContentFiles) {
72
- for (const outputPath of getSourceOutputPaths({
73
- contentDir,
74
- filePath,
75
- processedExts,
76
- buildContent: true,
77
- })) {
78
- outputs.add(outputPath);
79
- }
80
- }
81
-
82
- for (const filePath of walkFiles(contentDir)) {
83
- const ext = path.extname(filePath).slice(1).toLowerCase();
84
- if (processedExts.has(ext)) {
85
- continue;
86
- }
87
- outputs.add(toPosix(path.relative(contentDir, filePath)));
88
- }
89
-
90
- return outputs;
91
- }
92
-
93
- function getPublicFiles(publicDir: string): string[] {
94
- if (!fs.existsSync(publicDir)) {
95
- return [];
96
- }
97
-
98
- return walkFiles(publicDir);
99
- }
100
-
101
- export function getFilesByExtensions(
102
- rootDir: string,
103
- extensions: string[],
104
- ): string[] {
105
- if (!fs.existsSync(rootDir)) {
106
- return [];
107
- }
108
-
109
- const extensionSet = new Set(extensions.map(ext => ext.toLowerCase()));
110
-
111
- return walkFiles(rootDir).filter(filePath => {
112
- const ext = path.extname(filePath).slice(1).toLowerCase();
113
- return extensionSet.has(ext);
114
- });
115
- }
116
-
117
- export function getValidInternalTargets(
118
- contentDir: string,
119
- contentFiles: string[],
120
- codeExtensions: string[],
121
- ): Set<string> {
122
- const targets = new Set<string>();
123
- const processedExts = getProcessedExts(codeExtensions);
124
-
125
- for (const filePath of contentFiles) {
126
- for (const targetPath of getSourceTargetPaths({
127
- kind: 'content',
128
- rootDir: contentDir,
129
- filePath,
130
- processedExts,
131
- buildContent: true,
132
- })) {
133
- targets.add(targetPath);
134
- }
135
- }
136
-
137
- // Include non-processed assets in content/ that are copied directly to dist/.
138
- for (const filePath of walkFiles(contentDir)) {
139
- const ext = path.extname(filePath).slice(1).toLowerCase();
140
- if (processedExts.has(ext)) {
141
- continue;
142
- }
143
- for (const targetPath of getSourceTargetPaths({
144
- kind: 'content',
145
- rootDir: contentDir,
146
- filePath,
147
- processedExts,
148
- buildContent: false,
149
- })) {
150
- targets.add(targetPath);
151
- }
152
- }
153
-
154
- const publicDir = getPublicDir();
155
- for (const filePath of getPublicFiles(publicDir)) {
156
- for (const targetPath of getSourceTargetPaths({
157
- kind: 'public',
158
- rootDir: publicDir,
159
- filePath,
160
- processedExts,
161
- buildContent: true,
162
- })) {
163
- targets.add(targetPath);
164
- }
165
- }
166
-
167
- return targets;
168
- }
169
-
170
- export { shouldSkipContentFile };