@abreen/tada 1.11.3 → 1.13.2
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 +65 -64
- package/bin/tada.ts +61 -66
- package/build/build-manifest.ts +14 -3
- package/build/bundle.ts +0 -1
- package/build/config-files.ts +183 -0
- package/build/config-loader.ts +159 -0
- package/build/copy.ts +0 -83
- package/build/generate-content-assets.ts +75 -160
- package/build/globals.ts +48 -89
- package/build/log.ts +27 -18
- package/build/pagefind.ts +33 -26
- package/build/pipeline.ts +98 -79
- package/build/reachability.ts +51 -248
- package/build/serve.ts +35 -6
- package/build/site-variables.ts +20 -11
- package/build/source-model.ts +400 -0
- package/build/{watch/records.ts → source-records.ts} +111 -49
- package/build/template-globals.ts +91 -0
- package/build/templates.ts +50 -40
- package/build/test-helpers.ts +15 -0
- package/build/toc-plugin.ts +5 -1
- package/build/types.d.ts +9 -1
- package/build/util.ts +8 -0
- package/build/utils/code.ts +6 -16
- package/build/utils/content-files.ts +57 -126
- package/build/utils/final-html.ts +239 -0
- package/build/utils/markdown.ts +237 -17
- package/build/utils/paths.ts +2 -1
- package/build/utils/render.ts +108 -99
- package/build/validate-config-links.ts +91 -14
- package/build/watch/assets.ts +0 -2
- package/build/watch/build-full.ts +14 -15
- package/build/watch/build-incremental.ts +15 -8
- package/build/watch/compiler-types.d.ts +6 -1
- package/build/watch/compiler.ts +27 -15
- package/build/watch/index.ts +2 -7
- package/build/watch/planner.ts +32 -16
- package/build/watch/reload.ts +4 -0
- package/build/watch/runtime.ts +29 -45
- package/build/watch/snapshot.ts +50 -402
- package/build/watch/validation.ts +6 -3
- package/build/watch-reachability-state.ts +16 -23
- package/build/watch-reload-client.ts +11 -4
- package/init/content/index.md +1 -1
- package/init/content/labs/02/index.md +61 -0
- package/init/content/labs/02/selection_sort.py +23 -0
- package/init/content/labs/index.md +4 -2
- package/init/content/lectures/01/index.md +1 -1
- package/init/content/markdown.md +1 -1
- package/package.json +3 -4
- package/src/code/index.ts +46 -18
- package/src/code.scss +2 -1
- package/src/globals.ts +47 -0
- package/src/index.ts +1 -0
- package/src/navigate/index.ts +2 -5
- package/src/navigate/lifecycle.ts +3 -1
- package/src/navigate/runtime.ts +6 -5
- package/src/page-update/index.ts +20 -16
- package/src/search/index.ts +53 -25
- package/src/slides/index.ts +467 -0
- package/src/slides/style.scss +104 -0
- package/src/style.scss +8 -3
- package/src/timezone/index.ts +16 -20
- package/src/top/index.ts +2 -4
- package/src/trace/index.ts +91 -7
- package/src/trace/style.scss +1 -1
- package/src/util.ts +6 -2
- package/templates/_author.html +2 -2
- package/templates/_heading.html +1 -1
- package/templates/_nav.html +2 -2
- package/templates/_theme.scss +3 -3
- package/templates/_top.html +14 -14
- package/templates/default.html +8 -0
- package/types/dev.d.ts +1 -1
- package/types/site-variables.d.ts +4 -5
- package/watch/fs-commit.ts +63 -10
- package/build/apply-base-path-plugin.ts +0 -186
- package/build/content-watch.ts +0 -104
- package/build/validate-internal-links-plugin.ts +0 -207
package/build/watch/snapshot.ts
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { normalizeOutputPath } from '../util';
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '../util';
|
|
11
|
-
import { getExtensionToShikiLanguage } from '../site-variables';
|
|
12
|
-
import {
|
|
13
|
-
extensionIsMarkdown,
|
|
14
|
-
getProcessedExtensions,
|
|
15
|
-
isLiterateJava,
|
|
16
|
-
isPartial,
|
|
17
|
-
} from '../utils/file-types';
|
|
18
|
-
import type { ChangeBatch } from '../../watch/types';
|
|
19
|
-
import type { Asset, SiteVariables } from '../types';
|
|
5
|
+
type TadaProjectScan,
|
|
6
|
+
assertNoOutputPathConflicts,
|
|
7
|
+
scanProject,
|
|
8
|
+
updateProjectScan,
|
|
9
|
+
} from '../source-model';
|
|
10
|
+
import type { Asset, HtmlOutputAnalysis, SiteVariables } from '../types';
|
|
20
11
|
|
|
21
12
|
export interface TadaSourceRecord {
|
|
22
13
|
sourcePath: string;
|
|
23
14
|
kind: 'content' | 'public';
|
|
24
15
|
outputs: Map<string, string | Buffer>;
|
|
16
|
+
htmlAnalysisByOutputPath?: Map<string, HtmlOutputAnalysis>;
|
|
25
17
|
partialDeps: Set<string>;
|
|
26
18
|
traceDeps: Set<string>;
|
|
27
19
|
internalTargets: Set<string>;
|
|
@@ -53,39 +45,6 @@ export interface TadaSnapshot {
|
|
|
53
45
|
validTargets: Set<string>;
|
|
54
46
|
}
|
|
55
47
|
|
|
56
|
-
export interface TadaProjectScan {
|
|
57
|
-
contentDir: string;
|
|
58
|
-
publicDir: string;
|
|
59
|
-
distDir: string;
|
|
60
|
-
contentFiles: Set<string>;
|
|
61
|
-
buildContentFiles: Set<string>;
|
|
62
|
-
publicFiles: Set<string>;
|
|
63
|
-
validTargets: Set<string>;
|
|
64
|
-
literateJavaOutputPaths: Set<string>;
|
|
65
|
-
processedExts: Set<string>;
|
|
66
|
-
contentOwners: Map<string, string>;
|
|
67
|
-
publicOwners: Map<string, string>;
|
|
68
|
-
sourceOutputPaths: Map<string, Set<string>>;
|
|
69
|
-
sourceTargetPaths: Map<string, Set<string>>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function walkFiles(dir: string): string[] {
|
|
73
|
-
if (!fs.existsSync(dir)) {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
78
|
-
const fullPath = path.join(dir, entry.name);
|
|
79
|
-
if (entry.isDirectory()) {
|
|
80
|
-
return walkFiles(fullPath);
|
|
81
|
-
}
|
|
82
|
-
if (entry.isFile()) {
|
|
83
|
-
return [fullPath];
|
|
84
|
-
}
|
|
85
|
-
return [];
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
48
|
function buildReverseMap(
|
|
90
49
|
records: Iterable<TadaSourceRecord>,
|
|
91
50
|
selector: (record: TadaSourceRecord) => Iterable<string>,
|
|
@@ -104,139 +63,6 @@ function buildReverseMap(
|
|
|
104
63
|
return reverse;
|
|
105
64
|
}
|
|
106
65
|
|
|
107
|
-
function getProcessedExts(siteVariables: SiteVariables): Set<string> {
|
|
108
|
-
return new Set(
|
|
109
|
-
getProcessedExtensions(
|
|
110
|
-
Object.keys(getExtensionToShikiLanguage(siteVariables)),
|
|
111
|
-
).map(ext => ext.toLowerCase()),
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function isBuildContentSource(
|
|
116
|
-
filePath: string,
|
|
117
|
-
processedExts: Set<string>,
|
|
118
|
-
): boolean {
|
|
119
|
-
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
120
|
-
if (!processedExts.has(ext) || isPartial(filePath)) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
return !shouldSkipContentFile(filePath);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function addGeneratedRouteAliases(
|
|
127
|
-
targets: Set<string>,
|
|
128
|
-
outputPath: string,
|
|
129
|
-
): void {
|
|
130
|
-
const normalizedPath = normalizeOutputPath(outputPath);
|
|
131
|
-
targets.add(normalizedPath);
|
|
132
|
-
if (!normalizedPath.endsWith('/index.html')) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const base = normalizedPath.slice(0, -'index.html'.length);
|
|
136
|
-
targets.add(base);
|
|
137
|
-
if (base.endsWith('/') && base.length > 1) {
|
|
138
|
-
targets.add(base.slice(0, -1));
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function getContentOutputPathsForSource({
|
|
143
|
-
contentDir,
|
|
144
|
-
filePath,
|
|
145
|
-
processedExts,
|
|
146
|
-
buildContent,
|
|
147
|
-
}: {
|
|
148
|
-
contentDir: string;
|
|
149
|
-
filePath: string;
|
|
150
|
-
processedExts: Set<string>;
|
|
151
|
-
buildContent: boolean;
|
|
152
|
-
}): Set<string> {
|
|
153
|
-
const relPath = toPosix(path.relative(contentDir, filePath));
|
|
154
|
-
const parsed = path.parse(relPath);
|
|
155
|
-
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
156
|
-
const outputs = new Set<string>();
|
|
157
|
-
|
|
158
|
-
if (!processedExts.has(ext)) {
|
|
159
|
-
outputs.add(relPath);
|
|
160
|
-
return outputs;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (!buildContent) {
|
|
164
|
-
return outputs;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (isLiterateJava(filePath)) {
|
|
168
|
-
outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
|
|
169
|
-
outputs.add(toPosix(path.join(parsed.dir, parsed.name)));
|
|
170
|
-
return outputs;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
extensionIsMarkdown(parsed.ext.toLowerCase()) ||
|
|
175
|
-
parsed.ext.toLowerCase() === '.html'
|
|
176
|
-
) {
|
|
177
|
-
outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
|
|
178
|
-
return outputs;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
outputs.add(relPath);
|
|
182
|
-
outputs.add(`${relPath}.html`);
|
|
183
|
-
return outputs;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function getTargetPathsForSource({
|
|
187
|
-
kind,
|
|
188
|
-
rootDir,
|
|
189
|
-
filePath,
|
|
190
|
-
processedExts,
|
|
191
|
-
buildContent,
|
|
192
|
-
}: {
|
|
193
|
-
kind: 'content' | 'public';
|
|
194
|
-
rootDir: string;
|
|
195
|
-
filePath: string;
|
|
196
|
-
processedExts: Set<string>;
|
|
197
|
-
buildContent: boolean;
|
|
198
|
-
}): Set<string> {
|
|
199
|
-
const targets = new Set<string>();
|
|
200
|
-
const relPath = toPosix(path.relative(rootDir, filePath));
|
|
201
|
-
|
|
202
|
-
if (kind === 'public') {
|
|
203
|
-
targets.add(normalizeOutputPath(`/${relPath}`));
|
|
204
|
-
return targets;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
208
|
-
if (!processedExts.has(ext)) {
|
|
209
|
-
targets.add(normalizeOutputPath(`/${relPath}`));
|
|
210
|
-
return targets;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (!buildContent || isPartial(filePath)) {
|
|
214
|
-
return targets;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const parsed = path.parse(relPath);
|
|
218
|
-
const subPath = toPosix(path.join(parsed.dir, parsed.name));
|
|
219
|
-
|
|
220
|
-
if (isLiterateJava(filePath)) {
|
|
221
|
-
const javaSubPath = toPosix(path.join(parsed.dir, parsed.name));
|
|
222
|
-
addGeneratedRouteAliases(targets, `/${javaSubPath}.html`);
|
|
223
|
-
targets.add(normalizeOutputPath(`/${javaSubPath}`));
|
|
224
|
-
return targets;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
extensionIsMarkdown(parsed.ext.toLowerCase()) ||
|
|
229
|
-
parsed.ext.toLowerCase() === '.html'
|
|
230
|
-
) {
|
|
231
|
-
addGeneratedRouteAliases(targets, `/${subPath}.html`);
|
|
232
|
-
return targets;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
addGeneratedRouteAliases(targets, `/${relPath}.html`);
|
|
236
|
-
targets.add(normalizeOutputPath(`/${relPath}`));
|
|
237
|
-
return targets;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
66
|
function cloneSetMap(
|
|
241
67
|
source: Map<string, Set<string>>,
|
|
242
68
|
): Map<string, Set<string>> {
|
|
@@ -245,219 +71,6 @@ function cloneSetMap(
|
|
|
245
71
|
);
|
|
246
72
|
}
|
|
247
73
|
|
|
248
|
-
export function scanProject(siteVariables: SiteVariables): TadaProjectScan {
|
|
249
|
-
const contentDir = getContentDir();
|
|
250
|
-
const publicDir = getPublicDir();
|
|
251
|
-
const distDir = getDistDir();
|
|
252
|
-
const processedExts = getProcessedExts(siteVariables);
|
|
253
|
-
const contentFiles = new Set(walkFiles(contentDir).sort());
|
|
254
|
-
const publicFiles = new Set(walkFiles(publicDir).sort());
|
|
255
|
-
const buildContentFiles = new Set<string>();
|
|
256
|
-
const literateJavaOutputPaths = new Set<string>();
|
|
257
|
-
const contentOwners = new Map<string, string>();
|
|
258
|
-
const publicOwners = new Map<string, string>();
|
|
259
|
-
const sourceOutputPaths = new Map<string, Set<string>>();
|
|
260
|
-
const sourceTargetPaths = new Map<string, Set<string>>();
|
|
261
|
-
const validTargets = new Set<string>();
|
|
262
|
-
|
|
263
|
-
for (const filePath of contentFiles) {
|
|
264
|
-
const buildContent = isBuildContentSource(filePath, processedExts);
|
|
265
|
-
if (buildContent) {
|
|
266
|
-
buildContentFiles.add(filePath);
|
|
267
|
-
}
|
|
268
|
-
if (buildContent && isLiterateJava(filePath)) {
|
|
269
|
-
const parsed = path.parse(path.relative(contentDir, filePath));
|
|
270
|
-
literateJavaOutputPaths.add(
|
|
271
|
-
`/${toPosix(path.join(parsed.dir, parsed.name))}`,
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const outputs = getContentOutputPathsForSource({
|
|
276
|
-
contentDir,
|
|
277
|
-
filePath,
|
|
278
|
-
processedExts,
|
|
279
|
-
buildContent,
|
|
280
|
-
});
|
|
281
|
-
sourceOutputPaths.set(filePath, outputs);
|
|
282
|
-
for (const outputPath of outputs) {
|
|
283
|
-
contentOwners.set(outputPath, filePath);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const targets = getTargetPathsForSource({
|
|
287
|
-
kind: 'content',
|
|
288
|
-
rootDir: contentDir,
|
|
289
|
-
filePath,
|
|
290
|
-
processedExts,
|
|
291
|
-
buildContent,
|
|
292
|
-
});
|
|
293
|
-
sourceTargetPaths.set(filePath, targets);
|
|
294
|
-
for (const target of targets) {
|
|
295
|
-
validTargets.add(target);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
for (const filePath of publicFiles) {
|
|
300
|
-
const relPath = toPosix(path.relative(publicDir, filePath));
|
|
301
|
-
publicOwners.set(relPath, filePath);
|
|
302
|
-
sourceOutputPaths.set(filePath, new Set([relPath]));
|
|
303
|
-
const targets = getTargetPathsForSource({
|
|
304
|
-
kind: 'public',
|
|
305
|
-
rootDir: publicDir,
|
|
306
|
-
filePath,
|
|
307
|
-
processedExts,
|
|
308
|
-
buildContent: false,
|
|
309
|
-
});
|
|
310
|
-
sourceTargetPaths.set(filePath, targets);
|
|
311
|
-
for (const target of targets) {
|
|
312
|
-
validTargets.add(target);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
contentDir,
|
|
318
|
-
publicDir,
|
|
319
|
-
distDir,
|
|
320
|
-
contentFiles,
|
|
321
|
-
buildContentFiles,
|
|
322
|
-
publicFiles,
|
|
323
|
-
validTargets,
|
|
324
|
-
literateJavaOutputPaths,
|
|
325
|
-
processedExts,
|
|
326
|
-
contentOwners,
|
|
327
|
-
publicOwners,
|
|
328
|
-
sourceOutputPaths,
|
|
329
|
-
sourceTargetPaths,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export function updateProjectScan(
|
|
334
|
-
snapshot: TadaSnapshot,
|
|
335
|
-
batch: ChangeBatch,
|
|
336
|
-
): TadaProjectScan {
|
|
337
|
-
const contentDir = getContentDir();
|
|
338
|
-
const publicDir = getPublicDir();
|
|
339
|
-
const distDir = getDistDir();
|
|
340
|
-
const contentFiles = new Set(snapshot.contentFiles);
|
|
341
|
-
const buildContentFiles = new Set(snapshot.buildContentFiles);
|
|
342
|
-
const publicFiles = new Set(snapshot.publicFiles);
|
|
343
|
-
const literateJavaOutputPaths = new Set(snapshot.literateJavaOutputPaths);
|
|
344
|
-
const contentOwners = new Map(snapshot.contentOwners);
|
|
345
|
-
const publicOwners = new Map(snapshot.publicOwners);
|
|
346
|
-
const sourceOutputPaths = cloneSetMap(snapshot.sourceOutputPaths);
|
|
347
|
-
const sourceTargetPaths = cloneSetMap(snapshot.sourceTargetPaths);
|
|
348
|
-
|
|
349
|
-
for (const change of batch.changes) {
|
|
350
|
-
const sourcePath = path.resolve(change.path);
|
|
351
|
-
const inContent = sourcePath.startsWith(`${contentDir}${path.sep}`);
|
|
352
|
-
const inPublic = sourcePath.startsWith(`${publicDir}${path.sep}`);
|
|
353
|
-
if (!inContent && !inPublic) {
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
for (const outputPath of sourceOutputPaths.get(sourcePath) || []) {
|
|
358
|
-
if (inContent) {
|
|
359
|
-
contentOwners.delete(outputPath);
|
|
360
|
-
} else {
|
|
361
|
-
publicOwners.delete(outputPath);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
sourceOutputPaths.delete(sourcePath);
|
|
365
|
-
sourceTargetPaths.delete(sourcePath);
|
|
366
|
-
contentFiles.delete(sourcePath);
|
|
367
|
-
buildContentFiles.delete(sourcePath);
|
|
368
|
-
publicFiles.delete(sourcePath);
|
|
369
|
-
|
|
370
|
-
if (inContent && isLiterateJava(sourcePath)) {
|
|
371
|
-
const parsed = path.parse(path.relative(contentDir, sourcePath));
|
|
372
|
-
literateJavaOutputPaths.delete(
|
|
373
|
-
`/${toPosix(path.join(parsed.dir, parsed.name))}`,
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (inContent) {
|
|
382
|
-
const buildContent = isBuildContentSource(
|
|
383
|
-
sourcePath,
|
|
384
|
-
snapshot.processedExts,
|
|
385
|
-
);
|
|
386
|
-
contentFiles.add(sourcePath);
|
|
387
|
-
if (buildContent) {
|
|
388
|
-
buildContentFiles.add(sourcePath);
|
|
389
|
-
}
|
|
390
|
-
if (buildContent && isLiterateJava(sourcePath)) {
|
|
391
|
-
const parsed = path.parse(path.relative(contentDir, sourcePath));
|
|
392
|
-
literateJavaOutputPaths.add(
|
|
393
|
-
`/${toPosix(path.join(parsed.dir, parsed.name))}`,
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const outputs = getContentOutputPathsForSource({
|
|
398
|
-
contentDir,
|
|
399
|
-
filePath: sourcePath,
|
|
400
|
-
processedExts: snapshot.processedExts,
|
|
401
|
-
buildContent,
|
|
402
|
-
});
|
|
403
|
-
sourceOutputPaths.set(sourcePath, outputs);
|
|
404
|
-
for (const outputPath of outputs) {
|
|
405
|
-
contentOwners.set(outputPath, sourcePath);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
sourceTargetPaths.set(
|
|
409
|
-
sourcePath,
|
|
410
|
-
getTargetPathsForSource({
|
|
411
|
-
kind: 'content',
|
|
412
|
-
rootDir: contentDir,
|
|
413
|
-
filePath: sourcePath,
|
|
414
|
-
processedExts: snapshot.processedExts,
|
|
415
|
-
buildContent,
|
|
416
|
-
}),
|
|
417
|
-
);
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const relPath = toPosix(path.relative(publicDir, sourcePath));
|
|
422
|
-
publicFiles.add(sourcePath);
|
|
423
|
-
publicOwners.set(relPath, sourcePath);
|
|
424
|
-
sourceOutputPaths.set(sourcePath, new Set([relPath]));
|
|
425
|
-
sourceTargetPaths.set(
|
|
426
|
-
sourcePath,
|
|
427
|
-
getTargetPathsForSource({
|
|
428
|
-
kind: 'public',
|
|
429
|
-
rootDir: publicDir,
|
|
430
|
-
filePath: sourcePath,
|
|
431
|
-
processedExts: snapshot.processedExts,
|
|
432
|
-
buildContent: false,
|
|
433
|
-
}),
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const validTargets = new Set<string>();
|
|
438
|
-
for (const targets of sourceTargetPaths.values()) {
|
|
439
|
-
for (const target of targets) {
|
|
440
|
-
validTargets.add(target);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return {
|
|
445
|
-
contentDir,
|
|
446
|
-
publicDir,
|
|
447
|
-
distDir,
|
|
448
|
-
contentFiles,
|
|
449
|
-
buildContentFiles,
|
|
450
|
-
publicFiles,
|
|
451
|
-
validTargets,
|
|
452
|
-
literateJavaOutputPaths,
|
|
453
|
-
processedExts: snapshot.processedExts,
|
|
454
|
-
contentOwners,
|
|
455
|
-
publicOwners,
|
|
456
|
-
sourceOutputPaths,
|
|
457
|
-
sourceTargetPaths,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
|
|
461
74
|
export function collectOutputOwners(snapshot: {
|
|
462
75
|
contentRecords: Map<string, TadaSourceRecord>;
|
|
463
76
|
publicRecords: Map<string, TadaSourceRecord>;
|
|
@@ -545,13 +158,6 @@ export function createSnapshot({
|
|
|
545
158
|
};
|
|
546
159
|
}
|
|
547
160
|
|
|
548
|
-
export function assertNoOutputPathConflicts(scan: TadaProjectScan): string[] {
|
|
549
|
-
const conflicts = [...scan.contentOwners.keys()].filter(relPath =>
|
|
550
|
-
scan.publicOwners.has(relPath),
|
|
551
|
-
);
|
|
552
|
-
return conflicts.sort();
|
|
553
|
-
}
|
|
554
|
-
|
|
555
161
|
export function collectSourceOutputs(
|
|
556
162
|
assets: Asset[],
|
|
557
163
|
generatedOutputPaths: Set<string>,
|
|
@@ -567,6 +173,28 @@ export function collectSourceOutputs(
|
|
|
567
173
|
return outputs;
|
|
568
174
|
}
|
|
569
175
|
|
|
176
|
+
function cloneHtmlOutputAnalysis(
|
|
177
|
+
analysis: HtmlOutputAnalysis,
|
|
178
|
+
): HtmlOutputAnalysis {
|
|
179
|
+
return { outgoingTargets: new Set(analysis.outgoingTargets) };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function collectSourceHtmlAnalysis(
|
|
183
|
+
assets: Asset[],
|
|
184
|
+
): Map<string, HtmlOutputAnalysis> {
|
|
185
|
+
const htmlAnalysisByOutputPath = new Map<string, HtmlOutputAnalysis>();
|
|
186
|
+
for (const asset of assets) {
|
|
187
|
+
if (!asset.assetPath.endsWith('.html') || !asset.htmlAnalysis) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
htmlAnalysisByOutputPath.set(
|
|
191
|
+
asset.assetPath,
|
|
192
|
+
cloneHtmlOutputAnalysis(asset.htmlAnalysis),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return htmlAnalysisByOutputPath;
|
|
196
|
+
}
|
|
197
|
+
|
|
570
198
|
export function collectHtmlAssetsByPath(
|
|
571
199
|
contentRecords: Map<string, TadaSourceRecord>,
|
|
572
200
|
): Map<string, string> {
|
|
@@ -581,6 +209,26 @@ export function collectHtmlAssetsByPath(
|
|
|
581
209
|
return htmlAssetsByPath;
|
|
582
210
|
}
|
|
583
211
|
|
|
212
|
+
export function collectHtmlAnalysisByPath(
|
|
213
|
+
contentRecords: Map<string, TadaSourceRecord>,
|
|
214
|
+
): Map<string, HtmlOutputAnalysis> {
|
|
215
|
+
const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
|
|
216
|
+
for (const record of contentRecords.values()) {
|
|
217
|
+
for (const [outputPath, analysis] of record.htmlAnalysisByOutputPath ||
|
|
218
|
+
new Map<string, HtmlOutputAnalysis>()) {
|
|
219
|
+
htmlAnalysisByPath.set(outputPath, cloneHtmlOutputAnalysis(analysis));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return htmlAnalysisByPath;
|
|
223
|
+
}
|
|
224
|
+
|
|
584
225
|
export function normalizeInternalTarget(target: string): string {
|
|
585
226
|
return normalizeOutputPath(target);
|
|
586
227
|
}
|
|
228
|
+
|
|
229
|
+
export {
|
|
230
|
+
assertNoOutputPathConflicts,
|
|
231
|
+
scanProject,
|
|
232
|
+
updateProjectScan,
|
|
233
|
+
type TadaProjectScan,
|
|
234
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { B } from '../colors';
|
|
2
2
|
import { makeLogger } from '../log';
|
|
3
|
-
import {
|
|
3
|
+
import { config, getConfigFileName } from '../templates';
|
|
4
4
|
import { validateConfigLinks } from '../validate-config-links';
|
|
5
5
|
import type { WatchDiagnostic } from '../../watch/types';
|
|
6
6
|
import { assertNoOutputPathConflicts, type TadaProjectScan } from './snapshot';
|
|
@@ -27,10 +27,13 @@ export function validateConfig(scan: TadaProjectScan): WatchDiagnostic[] {
|
|
|
27
27
|
];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export function
|
|
30
|
+
export function validateProjectConfigLinks(
|
|
31
31
|
validTargets: Set<string>,
|
|
32
32
|
): WatchDiagnostic[] {
|
|
33
33
|
return diagnosticsFromMessages(
|
|
34
|
-
validateConfigLinks(validTargets,
|
|
34
|
+
validateConfigLinks(validTargets, config('nav'), config('authors'), {
|
|
35
|
+
navFileName: getConfigFileName('nav'),
|
|
36
|
+
authorsFileName: getConfigFileName('authors'),
|
|
37
|
+
}),
|
|
35
38
|
);
|
|
36
39
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collectOutgoingHtmlAssetPaths } from './reachability';
|
|
2
|
+
import type { HtmlOutputAnalysis } from './types';
|
|
2
3
|
|
|
3
|
-
type
|
|
4
|
+
type ReadAnalysisForAsset = (assetPath: string) => HtmlOutputAnalysis;
|
|
4
5
|
|
|
5
6
|
function cloneSet(values: Iterable<string> | undefined): Set<string> {
|
|
6
7
|
return new Set(values || []);
|
|
@@ -9,24 +10,19 @@ function cloneSet(values: Iterable<string> | undefined): Set<string> {
|
|
|
9
10
|
interface IncrementalOptions {
|
|
10
11
|
changedAssetPaths?: Iterable<string>;
|
|
11
12
|
removedAssetPaths?: Iterable<string>;
|
|
12
|
-
|
|
13
|
+
readAnalysisForAsset: ReadAnalysisForAsset;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
class WatchReachabilityState {
|
|
16
17
|
private rootPath: string;
|
|
17
|
-
private basePath: string;
|
|
18
18
|
private knownAssets: Set<string>;
|
|
19
19
|
private outgoingBySource: Map<string, Set<string>>;
|
|
20
20
|
private incomingByTarget: Map<string, Set<string>>;
|
|
21
21
|
private reachable: Set<string>;
|
|
22
22
|
private initialized: boolean;
|
|
23
23
|
|
|
24
|
-
constructor({
|
|
25
|
-
rootPath = 'index.html',
|
|
26
|
-
basePath = '/',
|
|
27
|
-
}: { rootPath?: string; basePath?: string } = {}) {
|
|
24
|
+
constructor({ rootPath = 'index.html' }: { rootPath?: string } = {}) {
|
|
28
25
|
this.rootPath = rootPath;
|
|
29
|
-
this.basePath = basePath;
|
|
30
26
|
this.knownAssets = new Set();
|
|
31
27
|
this.outgoingBySource = new Map();
|
|
32
28
|
this.incomingByTarget = new Map();
|
|
@@ -66,7 +62,7 @@ class WatchReachabilityState {
|
|
|
66
62
|
this.rebuildIncomingByTarget();
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
rebuild(
|
|
65
|
+
rebuild(readAnalysisForAsset: ReadAnalysisForAsset): void {
|
|
70
66
|
if (!this.knownAssets.has(this.rootPath)) {
|
|
71
67
|
throw new Error(`Pagefind reachability root not found: ${this.rootPath}`);
|
|
72
68
|
}
|
|
@@ -79,7 +75,7 @@ class WatchReachabilityState {
|
|
|
79
75
|
for (const sourcePath of knownAssetPaths) {
|
|
80
76
|
const outgoingPaths = this.readOutgoingPaths(
|
|
81
77
|
sourcePath,
|
|
82
|
-
|
|
78
|
+
readAnalysisForAsset,
|
|
83
79
|
);
|
|
84
80
|
this.outgoingBySource.set(sourcePath, outgoingPaths);
|
|
85
81
|
this.addIncomingEdges(sourcePath, outgoingPaths);
|
|
@@ -92,10 +88,10 @@ class WatchReachabilityState {
|
|
|
92
88
|
applyIncremental({
|
|
93
89
|
changedAssetPaths,
|
|
94
90
|
removedAssetPaths,
|
|
95
|
-
|
|
91
|
+
readAnalysisForAsset,
|
|
96
92
|
}: IncrementalOptions): void {
|
|
97
93
|
if (!this.initialized) {
|
|
98
|
-
this.rebuild(
|
|
94
|
+
this.rebuild(readAnalysisForAsset);
|
|
99
95
|
return;
|
|
100
96
|
}
|
|
101
97
|
|
|
@@ -136,7 +132,10 @@ class WatchReachabilityState {
|
|
|
136
132
|
|
|
137
133
|
const changedAssetList = [...normalizedChanged].sort();
|
|
138
134
|
for (const assetPath of changedAssetList) {
|
|
139
|
-
const outgoingPaths = this.readOutgoingPaths(
|
|
135
|
+
const outgoingPaths = this.readOutgoingPaths(
|
|
136
|
+
assetPath,
|
|
137
|
+
readAnalysisForAsset,
|
|
138
|
+
);
|
|
140
139
|
this.replaceOutgoing(assetPath, outgoingPaths);
|
|
141
140
|
}
|
|
142
141
|
|
|
@@ -196,20 +195,14 @@ class WatchReachabilityState {
|
|
|
196
195
|
|
|
197
196
|
private readOutgoingPaths(
|
|
198
197
|
sourcePath: string,
|
|
199
|
-
|
|
198
|
+
readAnalysisForAsset: ReadAnalysisForAsset,
|
|
200
199
|
): Set<string> {
|
|
201
200
|
if (!this.knownAssets.has(sourcePath)) {
|
|
202
201
|
return new Set();
|
|
203
202
|
}
|
|
204
203
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
html,
|
|
208
|
-
fromAssetPath: sourcePath,
|
|
209
|
-
knownAssets: this.knownAssets,
|
|
210
|
-
basePath: this.basePath,
|
|
211
|
-
});
|
|
212
|
-
return new Set(htmlAssetPaths);
|
|
204
|
+
const analysis = readAnalysisForAsset(sourcePath);
|
|
205
|
+
return new Set(collectOutgoingHtmlAssetPaths(analysis, this.knownAssets));
|
|
213
206
|
}
|
|
214
207
|
|
|
215
208
|
private addIncomingEdges(
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
WATCH_RELOAD_MESSAGE_REBUILDING,
|
|
3
|
+
WATCH_RELOAD_MESSAGE_RELOAD,
|
|
4
|
+
WATCH_RELOAD_PATH,
|
|
5
|
+
} from './watch/reload';
|
|
2
6
|
|
|
3
7
|
(function () {
|
|
4
8
|
function setLoading(loading: boolean): void {
|
|
@@ -16,17 +20,20 @@ body.loading-cursor * {
|
|
|
16
20
|
}`;
|
|
17
21
|
document.head.appendChild(style);
|
|
18
22
|
|
|
19
|
-
const
|
|
23
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
24
|
+
const ws = new WebSocket(
|
|
25
|
+
`${protocol}//${window.location.host}${WATCH_RELOAD_PATH}`,
|
|
26
|
+
);
|
|
20
27
|
|
|
21
28
|
ws.onopen = () => {
|
|
22
29
|
console.log('[watch-reload] connected to watcher');
|
|
23
30
|
};
|
|
24
31
|
|
|
25
32
|
ws.onmessage = event => {
|
|
26
|
-
if (event.data ===
|
|
33
|
+
if (event.data === WATCH_RELOAD_MESSAGE_REBUILDING) {
|
|
27
34
|
console.log('[watch-reload] Rebuilding...');
|
|
28
35
|
setLoading(true);
|
|
29
|
-
} else if (event.data ===
|
|
36
|
+
} else if (event.data === WATCH_RELOAD_MESSAGE_RELOAD) {
|
|
30
37
|
console.log('[watch-reload] Reloading page...');
|
|
31
38
|
setLoading(false);
|
|
32
39
|
window.history.scrollRestoration = 'auto';
|
package/init/content/index.md
CHANGED
|
@@ -14,7 +14,7 @@ This is an example site built with <%= page.toolName %>.
|
|
|
14
14
|
## Getting started
|
|
15
15
|
|
|
16
16
|
Edit the files in `content/` to add your own pages. To configure the nav,
|
|
17
|
-
update `nav.
|
|
17
|
+
update `nav.yaml`. Update `authors.yaml` with your information, or delete it
|
|
18
18
|
if you won't use the `author` front matter field.
|
|
19
19
|
|
|
20
20
|
See the [Markdown examples page](/markdown.html) for syntax examples.
|