@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/utils/render.ts
CHANGED
|
@@ -5,10 +5,18 @@ import type MarkdownIt from 'markdown-it';
|
|
|
5
5
|
import { stripHtml } from 'string-strip-html';
|
|
6
6
|
import { makeLogger } from '../log';
|
|
7
7
|
import { B } from '../colors';
|
|
8
|
-
import
|
|
8
|
+
import createTemplateGlobals from '../template-globals';
|
|
9
9
|
import { getExtensionToShikiLanguage } from '../site-variables';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { config } from '../templates';
|
|
11
|
+
import { render } from '../templates';
|
|
12
|
+
import {
|
|
13
|
+
getProjectConfigBaseName,
|
|
14
|
+
getSupportedConfigFileNamesText,
|
|
15
|
+
} from '../config-files';
|
|
16
|
+
import {
|
|
17
|
+
resolveParentLinkTarget,
|
|
18
|
+
validateParentLink,
|
|
19
|
+
} from '../validate-config-links';
|
|
12
20
|
import {
|
|
13
21
|
extractJavaMethodToc,
|
|
14
22
|
renderCodeSegment,
|
|
@@ -18,6 +26,7 @@ import {
|
|
|
18
26
|
import { extensionIsMarkdown } from './file-types';
|
|
19
27
|
import { createTraceHelpers } from './trace';
|
|
20
28
|
import { createIncludeFunction } from './include';
|
|
29
|
+
import { finalizeHtmlPage } from './final-html';
|
|
21
30
|
import {
|
|
22
31
|
createApplyBasePath,
|
|
23
32
|
normalizeOutputPath,
|
|
@@ -75,7 +84,6 @@ interface TemplateParametersInput {
|
|
|
75
84
|
pageVariables: Record<string, unknown>;
|
|
76
85
|
siteVariables: SiteVariables;
|
|
77
86
|
content: string | null;
|
|
78
|
-
applyBasePath: (subPath: string) => string;
|
|
79
87
|
subPath: string;
|
|
80
88
|
isWatchMode: boolean;
|
|
81
89
|
}
|
|
@@ -88,12 +96,12 @@ function resolveAuthor(
|
|
|
88
96
|
if (!pageVariables.author) {
|
|
89
97
|
return;
|
|
90
98
|
}
|
|
91
|
-
const authors =
|
|
99
|
+
const authors = config('authors') as
|
|
92
100
|
| Record<string, Record<string, unknown>>
|
|
93
101
|
| undefined;
|
|
94
102
|
if (!authors) {
|
|
95
103
|
throw new Error(
|
|
96
|
-
`${filePath}: author "${pageVariables.author}" specified but no
|
|
104
|
+
`${filePath}: author "${pageVariables.author}" specified but no author config found (tried ${getSupportedConfigFileNamesText(getProjectConfigBaseName('authors'))})`,
|
|
97
105
|
);
|
|
98
106
|
}
|
|
99
107
|
const authorKey = pageVariables.author as string;
|
|
@@ -101,7 +109,7 @@ function resolveAuthor(
|
|
|
101
109
|
const authorEntry = authors[authorKey];
|
|
102
110
|
if (!authorEntry) {
|
|
103
111
|
throw new Error(
|
|
104
|
-
`${filePath}: unknown author "${authorKey}" (not found in
|
|
112
|
+
`${filePath}: unknown author "${authorKey}" (not found in author config)`,
|
|
105
113
|
);
|
|
106
114
|
}
|
|
107
115
|
pageVariables.author = authorEntry;
|
|
@@ -127,18 +135,18 @@ function createTemplateParameters({
|
|
|
127
135
|
pageVariables,
|
|
128
136
|
siteVariables,
|
|
129
137
|
content,
|
|
130
|
-
applyBasePath,
|
|
131
138
|
subPath,
|
|
132
139
|
isWatchMode,
|
|
133
140
|
}: TemplateParametersInput): Record<string, unknown> {
|
|
141
|
+
const applyBasePath = createApplyBasePath(siteVariables);
|
|
134
142
|
return {
|
|
135
143
|
vars: siteVariables.vars || {},
|
|
136
|
-
...
|
|
144
|
+
...createTemplateGlobals(pageVariables, siteVariables, subPath),
|
|
137
145
|
site: siteVariables,
|
|
138
146
|
page: pageVariables,
|
|
139
147
|
content,
|
|
140
|
-
applyBasePath,
|
|
141
148
|
isWatchMode,
|
|
149
|
+
speculationRulesHrefMatches: `${applyBasePath('/')}*`,
|
|
142
150
|
tadaVersion,
|
|
143
151
|
};
|
|
144
152
|
}
|
|
@@ -146,14 +154,16 @@ function createTemplateParameters({
|
|
|
146
154
|
export function injectAssetTags(
|
|
147
155
|
html: string,
|
|
148
156
|
assetFiles: string[],
|
|
149
|
-
applyBasePath: (subPath: string) => string,
|
|
150
157
|
distDir: string,
|
|
151
158
|
): string {
|
|
152
159
|
const jsAssets = assetFiles.filter(f => f.endsWith('.js'));
|
|
153
160
|
const cssAssets = assetFiles.filter(f => f.endsWith('.css'));
|
|
154
161
|
|
|
155
162
|
const scriptTags = jsAssets
|
|
156
|
-
.map(
|
|
163
|
+
.map(
|
|
164
|
+
asset =>
|
|
165
|
+
`<script defer src="${normalizeOutputPath('/' + asset)}"></script>`,
|
|
166
|
+
)
|
|
157
167
|
.join('');
|
|
158
168
|
const criticalAssets = cssAssets.filter(f => f.includes('critical.bundle.'));
|
|
159
169
|
const asyncAssets = cssAssets.filter(f => !f.includes('critical.bundle.'));
|
|
@@ -165,7 +175,8 @@ export function injectAssetTags(
|
|
|
165
175
|
.join('');
|
|
166
176
|
const asyncLinkTags = asyncAssets
|
|
167
177
|
.map(
|
|
168
|
-
asset =>
|
|
178
|
+
asset =>
|
|
179
|
+
`<link href="${normalizeOutputPath('/' + asset)}" rel="stylesheet">`,
|
|
169
180
|
)
|
|
170
181
|
.join('');
|
|
171
182
|
|
|
@@ -176,7 +187,7 @@ export function injectAssetTags(
|
|
|
176
187
|
.filter(f => fs.existsSync(path.join(distDir, f)))
|
|
177
188
|
.map(
|
|
178
189
|
f =>
|
|
179
|
-
`<link rel="preload" href="${
|
|
190
|
+
`<link rel="preload" href="${normalizeOutputPath('/' + f)}" as="font" type="font/woff2" crossorigin>`,
|
|
180
191
|
)
|
|
181
192
|
.join('');
|
|
182
193
|
|
|
@@ -188,15 +199,27 @@ export function injectAssetTags(
|
|
|
188
199
|
.replace('</head>', `${scriptTags}</head>`);
|
|
189
200
|
}
|
|
190
201
|
|
|
191
|
-
export function injectKatexStylesheet(
|
|
192
|
-
|
|
193
|
-
applyBasePath: (subPath: string) => string,
|
|
194
|
-
): string {
|
|
195
|
-
const href = applyBasePath('/katex/katex.min.css');
|
|
196
|
-
const tag = `<link href="${href}" rel="stylesheet">`;
|
|
202
|
+
export function injectKatexStylesheet(html: string): string {
|
|
203
|
+
const tag = `<link href="/katex/katex.min.css" rel="stylesheet">`;
|
|
197
204
|
return html.replace('<head>', `<head>${tag}`);
|
|
198
205
|
}
|
|
199
206
|
|
|
207
|
+
export function preparePageTemplateHtml({
|
|
208
|
+
templateHtml,
|
|
209
|
+
assetFiles,
|
|
210
|
+
distDir,
|
|
211
|
+
}: {
|
|
212
|
+
templateHtml: string;
|
|
213
|
+
assetFiles: string[];
|
|
214
|
+
distDir: string;
|
|
215
|
+
}): string {
|
|
216
|
+
let html = injectAssetTags(templateHtml, assetFiles, distDir);
|
|
217
|
+
if (templateHtml.includes('class="katex"')) {
|
|
218
|
+
html = injectKatexStylesheet(html);
|
|
219
|
+
}
|
|
220
|
+
return html;
|
|
221
|
+
}
|
|
222
|
+
|
|
200
223
|
function toContentAssetPath(contentDir: string, filePath: string): string {
|
|
201
224
|
return toPosix(path.relative(contentDir, filePath));
|
|
202
225
|
}
|
|
@@ -216,24 +239,22 @@ export function renderPlainTextPageAsset({
|
|
|
216
239
|
}: RenderPlainTextOptions): Asset[] {
|
|
217
240
|
const { dir, name, ext } = path.parse(filePath);
|
|
218
241
|
const subPath = toPosix(path.relative(contentDir, path.join(dir, name)));
|
|
219
|
-
const
|
|
242
|
+
const sourceUrlPath = normalizeOutputPath(`/${subPath}.html`);
|
|
220
243
|
|
|
221
244
|
log.info`Rendering page ${B`${subPath + ext}`}`;
|
|
222
245
|
const watchMode = isWatchMode(assetFiles);
|
|
223
246
|
const { content, pageVariables, tocItems, alertIds } = renderPlainTextContent(
|
|
224
247
|
filePath,
|
|
225
248
|
subPath,
|
|
249
|
+
sourceUrlPath,
|
|
226
250
|
siteVariables,
|
|
227
|
-
applyBasePath,
|
|
228
251
|
validInternalTargets,
|
|
229
252
|
watchMode,
|
|
230
253
|
{
|
|
231
|
-
validateInternalLinks: extensionIsMarkdown(ext.toLowerCase()),
|
|
232
254
|
dependencyCollector,
|
|
233
255
|
traceCache,
|
|
234
256
|
contentDir,
|
|
235
257
|
distDir,
|
|
236
|
-
literateJavaOutputPaths,
|
|
237
258
|
cachedTraceSourceDir,
|
|
238
259
|
traceToolAvailability,
|
|
239
260
|
},
|
|
@@ -256,7 +277,6 @@ export function renderPlainTextPageAsset({
|
|
|
256
277
|
pageVariables,
|
|
257
278
|
siteVariables,
|
|
258
279
|
content,
|
|
259
|
-
applyBasePath,
|
|
260
280
|
subPath,
|
|
261
281
|
isWatchMode: watchMode,
|
|
262
282
|
});
|
|
@@ -265,15 +285,21 @@ export function renderPlainTextPageAsset({
|
|
|
265
285
|
`${pageVariables.template}.html`,
|
|
266
286
|
templateParameters,
|
|
267
287
|
) as string;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
html
|
|
271
|
-
|
|
288
|
+
const finalized = finalizeHtmlPage({
|
|
289
|
+
filePath,
|
|
290
|
+
html: preparePageTemplateHtml({ templateHtml, assetFiles, distDir }),
|
|
291
|
+
siteVariables,
|
|
292
|
+
sourceUrlPath,
|
|
293
|
+
validInternalTargets,
|
|
294
|
+
literateJavaOutputPaths,
|
|
295
|
+
dependencyCollector,
|
|
296
|
+
});
|
|
272
297
|
|
|
273
298
|
return [
|
|
274
299
|
{
|
|
275
300
|
assetPath: toContentAssetPath(contentDir, path.join(dir, `${name}.html`)),
|
|
276
|
-
content: html,
|
|
301
|
+
content: finalized.html,
|
|
302
|
+
htmlAnalysis: finalized.analysis,
|
|
277
303
|
},
|
|
278
304
|
];
|
|
279
305
|
}
|
|
@@ -286,10 +312,10 @@ export function renderCodePageAsset({
|
|
|
286
312
|
assetFiles,
|
|
287
313
|
validInternalTargets,
|
|
288
314
|
literateJavaOutputPaths,
|
|
315
|
+
dependencyCollector,
|
|
289
316
|
}: RenderCodePageOptions): Asset[] {
|
|
290
317
|
const { dir, name, ext } = path.parse(filePath);
|
|
291
318
|
const subPath = toPosix(path.relative(contentDir, path.join(dir, name)));
|
|
292
|
-
const applyBasePath = createApplyBasePath(siteVariables);
|
|
293
319
|
const lang =
|
|
294
320
|
getExtensionToShikiLanguage(siteVariables)[ext.slice(1).toLowerCase()];
|
|
295
321
|
const rawSource = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -305,12 +331,9 @@ export function renderCodePageAsset({
|
|
|
305
331
|
lang,
|
|
306
332
|
siteVariables,
|
|
307
333
|
pageDirPath,
|
|
308
|
-
sourceUrlPath,
|
|
309
|
-
validInternalTargets,
|
|
310
|
-
literateJavaOutputPaths,
|
|
311
334
|
);
|
|
312
|
-
const codeFilePath =
|
|
313
|
-
|
|
335
|
+
const codeFilePath = normalizeOutputPath(
|
|
336
|
+
`/${toUrlPath(path.relative(contentDir, filePath))}`,
|
|
314
337
|
);
|
|
315
338
|
const titleHtml = `<code>${name + ext}</code>`;
|
|
316
339
|
const tocItems = lang === 'java' ? extractJavaMethodToc(sourceCode) : [];
|
|
@@ -330,17 +353,23 @@ export function renderCodePageAsset({
|
|
|
330
353
|
pageVariables,
|
|
331
354
|
siteVariables,
|
|
332
355
|
content,
|
|
333
|
-
applyBasePath,
|
|
334
356
|
subPath,
|
|
335
357
|
isWatchMode: isWatchMode(assetFiles),
|
|
336
358
|
});
|
|
337
359
|
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
360
|
+
const finalized = finalizeHtmlPage({
|
|
361
|
+
filePath,
|
|
362
|
+
html: injectAssetTags(
|
|
363
|
+
render('code.html', templateParameters) as string,
|
|
364
|
+
assetFiles,
|
|
365
|
+
distDir,
|
|
366
|
+
),
|
|
367
|
+
siteVariables,
|
|
368
|
+
sourceUrlPath,
|
|
369
|
+
validInternalTargets,
|
|
370
|
+
literateJavaOutputPaths,
|
|
371
|
+
dependencyCollector,
|
|
372
|
+
});
|
|
344
373
|
|
|
345
374
|
return [
|
|
346
375
|
{
|
|
@@ -348,7 +377,8 @@ export function renderCodePageAsset({
|
|
|
348
377
|
contentDir,
|
|
349
378
|
path.join(dir, `${name}${ext}.html`),
|
|
350
379
|
),
|
|
351
|
-
content: html,
|
|
380
|
+
content: finalized.html,
|
|
381
|
+
htmlAnalysis: finalized.analysis,
|
|
352
382
|
},
|
|
353
383
|
];
|
|
354
384
|
}
|
|
@@ -385,21 +415,18 @@ export function renderCopiedContentAsset({
|
|
|
385
415
|
function renderPlainTextContent(
|
|
386
416
|
filePath: string,
|
|
387
417
|
subPath: string,
|
|
418
|
+
sourceUrlPath: string,
|
|
388
419
|
siteVariables: SiteVariables,
|
|
389
|
-
applyBasePath: (subPath: string) => string,
|
|
390
420
|
validInternalTargets: Set<string>,
|
|
391
421
|
isWatchMode: boolean,
|
|
392
422
|
{
|
|
393
|
-
validateInternalLinks = true,
|
|
394
423
|
dependencyCollector,
|
|
395
424
|
traceCache,
|
|
396
425
|
contentDir,
|
|
397
426
|
distDir,
|
|
398
|
-
literateJavaOutputPaths,
|
|
399
427
|
cachedTraceSourceDir,
|
|
400
428
|
traceToolAvailability,
|
|
401
429
|
}: {
|
|
402
|
-
validateInternalLinks?: boolean;
|
|
403
430
|
dependencyCollector?: RenderDependencyCollector;
|
|
404
431
|
traceCache?: Map<
|
|
405
432
|
string,
|
|
@@ -412,7 +439,6 @@ function renderPlainTextContent(
|
|
|
412
439
|
>;
|
|
413
440
|
contentDir?: string;
|
|
414
441
|
distDir?: string;
|
|
415
|
-
literateJavaOutputPaths?: Set<string>;
|
|
416
442
|
cachedTraceSourceDir?: string;
|
|
417
443
|
traceToolAvailability?: TraceToolAvailability;
|
|
418
444
|
} = {},
|
|
@@ -422,35 +448,26 @@ function renderPlainTextContent(
|
|
|
422
448
|
tocItems: unknown[] | null;
|
|
423
449
|
alertIds: string[];
|
|
424
450
|
} {
|
|
425
|
-
const
|
|
426
|
-
const md = createMarkdown(siteVariables, {
|
|
427
|
-
filePath,
|
|
428
|
-
validatorOptions: {
|
|
429
|
-
enabled: validateInternalLinks,
|
|
430
|
-
filePath,
|
|
431
|
-
sourceUrlPath,
|
|
432
|
-
validTargets: validInternalTargets,
|
|
433
|
-
codeExtensions: Object.keys(getExtensionToShikiLanguage(siteVariables)),
|
|
434
|
-
onValidLink: (targetPath: string) => {
|
|
435
|
-
dependencyCollector?.internalTargets?.add(targetPath);
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
literateJavaOutputPaths,
|
|
439
|
-
sourceUrlPath,
|
|
440
|
-
validTargets: validInternalTargets,
|
|
441
|
-
});
|
|
451
|
+
const applyBasePath = createApplyBasePath(siteVariables);
|
|
442
452
|
|
|
443
453
|
const ext = path.extname(filePath);
|
|
444
454
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
445
|
-
|
|
446
455
|
const { pageVariables, content } = parseFrontMatterAndContent(raw, ext);
|
|
456
|
+
if (pageVariables.slides === true && !extensionIsMarkdown(ext)) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`${filePath}: slides mode is only supported on Markdown pages`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
const md = createMarkdown(siteVariables, {
|
|
462
|
+
filePath,
|
|
463
|
+
slides: pageVariables.slides === true,
|
|
464
|
+
});
|
|
447
465
|
|
|
448
466
|
// Handle substitutions inside front matter using siteVariables
|
|
449
467
|
const siteOnlyParams = createTemplateParameters({
|
|
450
468
|
pageVariables: {},
|
|
451
469
|
siteVariables,
|
|
452
470
|
content: null,
|
|
453
|
-
applyBasePath,
|
|
454
471
|
subPath,
|
|
455
472
|
isWatchMode,
|
|
456
473
|
});
|
|
@@ -471,14 +488,17 @@ function renderPlainTextContent(
|
|
|
471
488
|
pageVariablesProcessed.parent,
|
|
472
489
|
filePath,
|
|
473
490
|
validInternalTargets,
|
|
491
|
+
sourceUrlPath,
|
|
474
492
|
);
|
|
475
493
|
if (parentError) {
|
|
476
494
|
throw new Error(parentError);
|
|
477
495
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
496
|
+
const resolvedParentTarget = resolveParentLinkTarget(
|
|
497
|
+
pageVariablesProcessed.parent,
|
|
498
|
+
sourceUrlPath,
|
|
499
|
+
);
|
|
500
|
+
if (resolvedParentTarget) {
|
|
501
|
+
dependencyCollector?.internalTargets?.add(resolvedParentTarget);
|
|
482
502
|
}
|
|
483
503
|
|
|
484
504
|
const strippedContent = stripHtmlComments(content);
|
|
@@ -487,7 +507,6 @@ function renderPlainTextContent(
|
|
|
487
507
|
pageVariables: pageVariablesProcessed,
|
|
488
508
|
siteVariables,
|
|
489
509
|
content: strippedContent,
|
|
490
|
-
applyBasePath,
|
|
491
510
|
subPath,
|
|
492
511
|
isWatchMode,
|
|
493
512
|
});
|
|
@@ -553,7 +572,6 @@ export function renderLiterateJavaPageAsset({
|
|
|
553
572
|
const { dir, name } = path.parse(filePath);
|
|
554
573
|
const className = deriveClassName(filePath);
|
|
555
574
|
const subPath = toPosix(path.relative(contentDir, path.join(dir, className)));
|
|
556
|
-
const applyBasePath = createApplyBasePath(siteVariables);
|
|
557
575
|
|
|
558
576
|
log.info`Rendering literate Java page ${B`${name}`}`;
|
|
559
577
|
|
|
@@ -600,21 +618,7 @@ export function renderLiterateJavaPageAsset({
|
|
|
600
618
|
// Render full markdown with a custom fence rule that replaces fences
|
|
601
619
|
// with Shiki-highlighted code blocks and optional JDI output columns
|
|
602
620
|
const sourceUrlPath = `/${subPath}.java.html`;
|
|
603
|
-
const md = createMarkdown(siteVariables, {
|
|
604
|
-
filePath,
|
|
605
|
-
validatorOptions: {
|
|
606
|
-
filePath,
|
|
607
|
-
sourceUrlPath,
|
|
608
|
-
validTargets: validInternalTargets,
|
|
609
|
-
codeExtensions: Object.keys(getExtensionToShikiLanguage(siteVariables)),
|
|
610
|
-
onValidLink: (targetPath: string) => {
|
|
611
|
-
dependencyCollector?.internalTargets?.add(targetPath);
|
|
612
|
-
},
|
|
613
|
-
},
|
|
614
|
-
literateJavaOutputPaths,
|
|
615
|
-
sourceUrlPath,
|
|
616
|
-
validTargets: validInternalTargets,
|
|
617
|
-
});
|
|
621
|
+
const md = createMarkdown(siteVariables, { filePath });
|
|
618
622
|
let fenceIndex = 0;
|
|
619
623
|
const defaultFence = md.renderer.rules.fence!;
|
|
620
624
|
|
|
@@ -667,10 +671,8 @@ export function renderLiterateJavaPageAsset({
|
|
|
667
671
|
|
|
668
672
|
// Build page variables
|
|
669
673
|
const javaFileName = `${className}.java`;
|
|
670
|
-
const codeFilePath =
|
|
671
|
-
|
|
672
|
-
`/${toUrlPath(path.relative(contentDir, path.join(dir, javaFileName)))}`,
|
|
673
|
-
),
|
|
674
|
+
const codeFilePath = normalizeOutputPath(
|
|
675
|
+
`/${toUrlPath(path.relative(contentDir, path.join(dir, javaFileName)))}`,
|
|
674
676
|
);
|
|
675
677
|
|
|
676
678
|
renderInlineField(md, pageVariables, 'title');
|
|
@@ -691,16 +693,20 @@ export function renderLiterateJavaPageAsset({
|
|
|
691
693
|
pageVariables,
|
|
692
694
|
siteVariables,
|
|
693
695
|
content: contentHtml,
|
|
694
|
-
applyBasePath,
|
|
695
696
|
subPath,
|
|
696
697
|
isWatchMode: isWatchMode(assetFiles),
|
|
697
698
|
});
|
|
698
699
|
|
|
699
700
|
const templateHtml = render('literate.html', templateParameters) as string;
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
html
|
|
703
|
-
|
|
701
|
+
const finalized = finalizeHtmlPage({
|
|
702
|
+
filePath,
|
|
703
|
+
html: preparePageTemplateHtml({ templateHtml, assetFiles, distDir }),
|
|
704
|
+
siteVariables,
|
|
705
|
+
sourceUrlPath,
|
|
706
|
+
validInternalTargets,
|
|
707
|
+
literateJavaOutputPaths,
|
|
708
|
+
dependencyCollector,
|
|
709
|
+
});
|
|
704
710
|
|
|
705
711
|
return [
|
|
706
712
|
{
|
|
@@ -708,7 +714,8 @@ export function renderLiterateJavaPageAsset({
|
|
|
708
714
|
contentDir,
|
|
709
715
|
path.join(dir, `${className}.java.html`),
|
|
710
716
|
),
|
|
711
|
-
content: html,
|
|
717
|
+
content: finalized.html,
|
|
718
|
+
htmlAnalysis: finalized.analysis,
|
|
712
719
|
},
|
|
713
720
|
{
|
|
714
721
|
assetPath: toContentAssetPath(contentDir, path.join(dir, javaFileName)),
|
|
@@ -716,3 +723,5 @@ export function renderLiterateJavaPageAsset({
|
|
|
716
723
|
},
|
|
717
724
|
];
|
|
718
725
|
}
|
|
726
|
+
|
|
727
|
+
export { finalizeHtmlPage } from './final-html';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { normalizeOutputPath } from './utils/paths';
|
|
2
3
|
|
|
3
4
|
interface NavLink {
|
|
@@ -12,9 +13,21 @@ interface NavSection {
|
|
|
12
13
|
links: NavLink[];
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
function requireRootRelativePath(
|
|
17
|
+
value: string,
|
|
18
|
+
errorPrefix: string,
|
|
19
|
+
): string | null {
|
|
20
|
+
if (value.startsWith('/')) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return `${errorPrefix} must start with "/": "${value}"`;
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
export function validateNavLinks(
|
|
16
28
|
navData: unknown,
|
|
17
29
|
validTargets: Set<string>,
|
|
30
|
+
fileName: string = 'nav.yaml',
|
|
18
31
|
): string[] {
|
|
19
32
|
if (!Array.isArray(navData)) {
|
|
20
33
|
return [];
|
|
@@ -28,10 +41,19 @@ export function validateNavLinks(
|
|
|
28
41
|
continue;
|
|
29
42
|
}
|
|
30
43
|
|
|
44
|
+
const rootRelativeError = requireRootRelativePath(
|
|
45
|
+
link.internal,
|
|
46
|
+
`${fileName}: internal link in section "${section.title}"`,
|
|
47
|
+
);
|
|
48
|
+
if (rootRelativeError) {
|
|
49
|
+
errors.push(rootRelativeError);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
31
53
|
const normalized = normalizeOutputPath(link.internal);
|
|
32
54
|
if (!validTargets.has(normalized)) {
|
|
33
55
|
errors.push(
|
|
34
|
-
|
|
56
|
+
`${fileName}: broken internal link in section "${section.title}": "${link.internal}"`,
|
|
35
57
|
);
|
|
36
58
|
}
|
|
37
59
|
}
|
|
@@ -49,6 +71,7 @@ interface AuthorEntry {
|
|
|
49
71
|
export function validateAuthorLinks(
|
|
50
72
|
authorsData: unknown,
|
|
51
73
|
validTargets: Set<string>,
|
|
74
|
+
fileName: string = 'authors.yaml',
|
|
52
75
|
): string[] {
|
|
53
76
|
if (!authorsData || typeof authorsData !== 'object') {
|
|
54
77
|
return [];
|
|
@@ -58,17 +81,33 @@ export function validateAuthorLinks(
|
|
|
58
81
|
const authors = authorsData as Record<string, AuthorEntry>;
|
|
59
82
|
|
|
60
83
|
for (const [key, author] of Object.entries(authors)) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
const avatarRootRelativeError = requireRootRelativePath(
|
|
85
|
+
author.avatar,
|
|
86
|
+
`${fileName}: avatar path for "${key}"`,
|
|
87
|
+
);
|
|
88
|
+
if (avatarRootRelativeError) {
|
|
89
|
+
errors.push(avatarRootRelativeError);
|
|
90
|
+
} else {
|
|
91
|
+
const avatarPath = normalizeOutputPath(author.avatar);
|
|
92
|
+
if (!validTargets.has(avatarPath)) {
|
|
93
|
+
errors.push(
|
|
94
|
+
`${fileName}: broken avatar path for "${key}": "${author.avatar}"`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
66
97
|
}
|
|
67
98
|
|
|
68
99
|
if (author.url) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
100
|
+
const urlRootRelativeError = requireRootRelativePath(
|
|
101
|
+
author.url,
|
|
102
|
+
`${fileName}: url for "${key}"`,
|
|
103
|
+
);
|
|
104
|
+
if (urlRootRelativeError) {
|
|
105
|
+
errors.push(urlRootRelativeError);
|
|
106
|
+
} else {
|
|
107
|
+
const urlPath = normalizeOutputPath(author.url);
|
|
108
|
+
if (!validTargets.has(urlPath)) {
|
|
109
|
+
errors.push(`${fileName}: broken url for "${key}": "${author.url}"`);
|
|
110
|
+
}
|
|
72
111
|
}
|
|
73
112
|
}
|
|
74
113
|
}
|
|
@@ -80,26 +119,64 @@ export function validateConfigLinks(
|
|
|
80
119
|
validTargets: Set<string>,
|
|
81
120
|
navData: unknown,
|
|
82
121
|
authorsData: unknown,
|
|
122
|
+
{
|
|
123
|
+
navFileName = 'nav.yaml',
|
|
124
|
+
authorsFileName = 'authors.yaml',
|
|
125
|
+
}: { navFileName?: string; authorsFileName?: string } = {},
|
|
83
126
|
): string[] {
|
|
84
127
|
return [
|
|
85
|
-
...validateNavLinks(navData, validTargets),
|
|
86
|
-
...validateAuthorLinks(authorsData, validTargets),
|
|
128
|
+
...validateNavLinks(navData, validTargets, navFileName),
|
|
129
|
+
...validateAuthorLinks(authorsData, validTargets, authorsFileName),
|
|
87
130
|
];
|
|
88
131
|
}
|
|
89
132
|
|
|
133
|
+
function splitHref(href: string): { pathname: string; suffix: string } {
|
|
134
|
+
const match = href.match(/^([^?#]*)(.*)$/);
|
|
135
|
+
return { pathname: match ? match[1] : href, suffix: match ? match[2] : '' };
|
|
136
|
+
}
|
|
137
|
+
|
|
90
138
|
export function validateParentLink(
|
|
91
139
|
parent: unknown,
|
|
92
140
|
filePath: string,
|
|
93
141
|
validTargets: Set<string>,
|
|
142
|
+
sourceUrlPath: string,
|
|
94
143
|
): string | null {
|
|
95
|
-
if (
|
|
144
|
+
if (typeof parent === 'string' && splitHref(parent).pathname === '') {
|
|
145
|
+
return `${filePath}: broken parent link: "${parent}"`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const resolvedTarget = resolveParentLinkTarget(parent, sourceUrlPath);
|
|
149
|
+
if (!resolvedTarget) {
|
|
96
150
|
return null;
|
|
97
151
|
}
|
|
98
152
|
|
|
99
|
-
|
|
100
|
-
if (!validTargets.has(normalized)) {
|
|
153
|
+
if (!validTargets.has(resolvedTarget)) {
|
|
101
154
|
return `${filePath}: broken parent link: "${parent}"`;
|
|
102
155
|
}
|
|
103
156
|
|
|
104
157
|
return null;
|
|
105
158
|
}
|
|
159
|
+
|
|
160
|
+
export function resolveParentLinkTarget(
|
|
161
|
+
parent: unknown,
|
|
162
|
+
sourceUrlPath: string,
|
|
163
|
+
): string | null {
|
|
164
|
+
if (!parent || typeof parent !== 'string') {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const { pathname } = splitHref(parent);
|
|
169
|
+
if (!pathname) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const sourceDir = path.posix.dirname(sourceUrlPath);
|
|
173
|
+
const resolved = pathname.startsWith('/')
|
|
174
|
+
? normalizeOutputPath(pathname)
|
|
175
|
+
: normalizeOutputPath(path.posix.join(sourceDir, pathname));
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
return normalizeOutputPath(decodeURIComponent(resolved));
|
|
179
|
+
} catch {
|
|
180
|
+
return resolved;
|
|
181
|
+
}
|
|
182
|
+
}
|
package/build/watch/assets.ts
CHANGED
|
@@ -34,7 +34,6 @@ export async function ensureHighlighter(
|
|
|
34
34
|
export async function bundleWatchAssets(
|
|
35
35
|
outputDir: string,
|
|
36
36
|
siteVariables: SiteVariables,
|
|
37
|
-
wsPort: number,
|
|
38
37
|
): Promise<string[]> {
|
|
39
38
|
const appAssets = await bundle(siteVariables, {
|
|
40
39
|
mode: 'development',
|
|
@@ -45,7 +44,6 @@ export async function bundleWatchAssets(
|
|
|
45
44
|
outdir: outputDir,
|
|
46
45
|
naming: '[name].bundle.[ext]',
|
|
47
46
|
sourcemap: 'inline',
|
|
48
|
-
define: { __WEBSOCKET_PORT__: String(wsPort) },
|
|
49
47
|
});
|
|
50
48
|
return [
|
|
51
49
|
...appAssets,
|