@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.
- package/README.md +41 -7
- package/bin/tada.ts +23 -9
- package/bin/validators.ts +34 -1
- package/build/branding.ts +41 -0
- package/build/build-manifest.ts +10 -5
- package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
- package/build/build-validation.ts +176 -0
- package/build/bundle.ts +75 -65
- package/build/columns-plugin.ts +27 -1
- package/build/config-loader.ts +3 -3
- package/build/custom-fonts.ts +8 -5
- package/build/deflist-id-plugin.ts +2 -2
- package/build/generate-favicon.ts +7 -6
- package/build/generate-fonts.ts +8 -15
- package/build/generate-katex-assets.ts +19 -17
- package/build/generate-web-app-manifest.ts +3 -11
- package/build/lodash-template.ts +14 -0
- package/build/output-publication.ts +238 -0
- package/build/pagefind.ts +49 -21
- package/build/pdf-text.ts +41 -1
- package/build/pipeline.ts +95 -101
- package/build/site-assets.ts +46 -0
- package/build/site-build.ts +267 -0
- package/build/source-model.ts +223 -229
- package/build/source-records.ts +69 -81
- package/build/template-globals.ts +3 -0
- package/build/templates.ts +12 -8
- package/build/toc-plugin.ts +21 -21
- package/build/types.d.ts +38 -38
- package/build/util.ts +0 -4
- package/build/utils/code.ts +51 -117
- package/build/utils/final-html.ts +171 -47
- package/build/utils/jdi-runner/LiterateRunner.java +9 -1
- package/build/utils/literate-java.ts +11 -7
- package/build/utils/markdown-partials.ts +4 -3
- package/build/utils/markdown.ts +6 -17
- package/build/utils/paths.ts +3 -0
- package/build/utils/plain-text.ts +18 -0
- package/build/utils/render.ts +197 -154
- package/build/utils/shiki-highlighter.ts +11 -0
- package/build/utils/source-template.ts +2 -2
- package/build/utils/trace-core.ts +8 -83
- package/build/utils/trace.ts +38 -95
- package/build/validate-config-links.ts +39 -6
- package/build/watch/build-incremental.ts +46 -103
- package/build/watch/compiler.ts +158 -37
- package/build/watch/engine.ts +207 -162
- package/build/watch/index.ts +9 -2
- package/build/watch/planner.ts +88 -144
- package/build/watch/runtime.ts +21 -34
- package/build/watch/types.d.ts +32 -64
- package/package.json +4 -5
- package/schema/site.schema.json +18 -2
- package/src/_content.scss +447 -0
- package/src/_fonts.scss +90 -0
- package/src/_layout.scss +50 -35
- package/src/appearance-picker/{style.scss → _index.scss} +1 -1
- package/src/code/{style.scss → _index.scss} +61 -0
- package/src/header/{_base.scss → _index.scss} +98 -6
- package/src/index.ts +1 -17
- package/src/navigate/{style.scss → _index.scss} +20 -0
- package/src/navigate/eligible.ts +0 -40
- package/src/navigate/index.ts +50 -5
- package/src/navigate/lifecycle.ts +19 -2
- package/src/navigate/runtime.ts +20 -4
- package/src/page-update/index.ts +33 -20
- package/src/print/{style.scss → _index.scss} +3 -1
- package/src/search/index.ts +30 -2
- package/src/slides/{style.scss → _index.scss} +5 -0
- package/src/style.scss +24 -536
- package/src/timezone/index.ts +29 -3
- package/src/timezone/time-format.ts +4 -0
- package/src/toc/index.ts +6 -5
- package/src/trace/index.ts +11 -1
- package/templates/_author.html +2 -2
- package/templates/_download.html +1 -1
- package/templates/_footer.html +1 -1
- package/templates/_heading.html +3 -3
- package/templates/_nav.html +2 -2
- package/templates/_page-bottom.html +6 -0
- package/templates/_top.html +18 -14
- package/templates/code.html +22 -21
- package/templates/default.html +12 -11
- package/templates/literate.html +6 -5
- package/build/copy.ts +0 -82
- package/build/generate-content-assets.ts +0 -121
- package/build/utils/content-files.ts +0 -170
- package/build/watch/assets.ts +0 -103
- package/build/watch/build-full.ts +0 -97
- package/build/watch/build-helpers.ts +0 -66
- package/build/watch/build-result.ts +0 -19
- package/build/watch/fs-commit.ts +0 -153
- package/build/watch/mutations.ts +0 -73
- package/build/watch/snapshot.ts +0 -201
- package/build/watch/validation.ts +0 -50
- package/src/code.scss +0 -60
- package/src/critical.scss +0 -5
- package/src/header/style.scss +0 -91
- /package/src/{literate.scss → _literate.scss} +0 -0
- /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
- /package/src/anchor/{style.scss → _index.scss} +0 -0
- /package/src/page-update/{style.scss → _index.scss} +0 -0
- /package/src/question/{style.scss → _index.scss} +0 -0
- /package/src/search/{style.scss → _index.scss} +0 -0
- /package/src/timezone/{style.scss → _index.scss} +0 -0
- /package/src/toc/{style.scss → _index.scss} +0 -0
- /package/src/trace/{style.scss → _index.scss} +0 -0
package/src/trace/index.ts
CHANGED
|
@@ -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 = {
|
|
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
|
|
package/templates/_author.html
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
<% if (page.author.url) { %>
|
|
4
4
|
<a href="<%- encodeAuthoredUrl(page.author.url) %>">
|
|
5
5
|
<% } %>
|
|
6
|
-
<span class="text"
|
|
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
|
|
12
|
+
alt="Picture of <%- page.author.name %>"
|
|
13
13
|
class="avatar"
|
|
14
14
|
/>
|
|
15
15
|
<% } else { %>
|
package/templates/_download.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<a class="button" href="<%= page.codeFilePath %>" download="
|
|
1
|
+
<a class="button" href="<%= page.codeFilePath %>" download="<%- page.downloadName %>" data-pagefind-ignore>Download</a>
|
package/templates/_footer.html
CHANGED
|
@@ -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>
|
|
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
|
<% } %>
|
package/templates/_heading.html
CHANGED
|
@@ -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
|
|
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="
|
|
11
|
-
|
|
10
|
+
<time datetime="<%- isoDate(page.published) %>">
|
|
11
|
+
<%- readableDate(page.published) %>
|
|
12
12
|
</time>
|
|
13
13
|
</div>
|
|
14
14
|
<% } %>
|
package/templates/_nav.html
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<nav>
|
|
2
2
|
<% config('nav').forEach(function(section) { %>
|
|
3
3
|
<div>
|
|
4
|
-
<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
|
-
|
|
13
|
+
<%- link.text %></a>
|
|
14
14
|
</li>
|
|
15
15
|
<% }) %>
|
|
16
16
|
</ul>
|
package/templates/_top.html
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en"
|
|
3
|
-
data-default-font-preference="
|
|
4
|
-
data-default-contrast-preference="
|
|
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="
|
|
26
|
+
<meta name="apple-mobile-web-app-title" content="<%- site.symbol %>">
|
|
23
27
|
<meta name="mobile-web-app-capable" content="yes">
|
|
24
28
|
<% } %>
|
|
25
|
-
|
|
26
|
-
<meta
|
|
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="
|
|
36
|
+
<meta name="description" content="<%- page.description %>" />
|
|
33
37
|
<% } %>
|
|
34
|
-
<meta property="og:title" content="
|
|
38
|
+
<meta property="og:title" content="<%- page.title %>" />
|
|
35
39
|
<% if (page.author) { %>
|
|
36
|
-
<meta name="author" content="
|
|
37
|
-
<meta property="og:author" content="
|
|
40
|
+
<meta name="author" content="<%- page.author.name %>" />
|
|
41
|
+
<meta property="og:author" content="<%- page.author.name %>" />
|
|
38
42
|
<% } %>
|
|
39
|
-
<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"
|
|
219
|
-
<span class="site-title" aria-hidden="true"
|
|
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') %>
|
package/templates/code.html
CHANGED
|
@@ -2,28 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="main-content">
|
|
4
4
|
<main class="body" data-pagefind-body>
|
|
5
|
-
<div class="
|
|
6
|
-
<
|
|
7
|
-
|
|
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
|
-
|
|
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>
|
package/templates/default.html
CHANGED
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
<div class="main-content">
|
|
4
4
|
<%= render('_heading.html') %>
|
|
5
5
|
<main class="body" data-pagefind-body>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
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>
|
package/templates/literate.html
CHANGED
|
@@ -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="
|
|
7
|
-
<
|
|
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
|
-
<%=
|
|
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 };
|