@abreen/tada 1.0.2 → 1.1.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 +29 -33
- package/bin/tada.ts +356 -0
- package/bin/validators.test.ts +204 -0
- package/bin/validators.ts +83 -0
- package/{webpack/apply-base-path-plugin.js → build/apply-base-path-plugin.ts} +16 -7
- package/build/bundle.ts +117 -0
- package/{webpack/code.test.js → build/code.test.ts} +6 -7
- package/build/colors.ts +25 -0
- package/build/content-watch.ts +107 -0
- package/build/copy.ts +118 -0
- package/{webpack/deflist-id-plugin.js → build/deflist-id-plugin.ts} +7 -6
- package/{webpack/external-links-plugin.js → build/external-links-plugin.ts} +14 -5
- package/build/features.ts +11 -0
- package/build/generate-content-assets.ts +315 -0
- package/build/generate-favicon.ts +165 -0
- package/build/generate-fonts.ts +31 -0
- package/{webpack/generate-manifest-plugin.js → build/generate-manifest.ts} +29 -36
- package/build/globals.test.ts +101 -0
- package/{webpack/globals.js → build/globals.ts} +28 -13
- package/{webpack/heading-subtitle-plugin.js → build/heading-subtitle-plugin.ts} +4 -2
- package/build/json-schema.test.ts +57 -0
- package/build/json-schema.ts +33 -0
- package/build/log.test.ts +111 -0
- package/build/log.ts +167 -0
- package/{webpack/markdown-plugins.test.js → build/markdown-plugins.test.ts} +94 -9
- package/{webpack/pagefind-plugin.test.js → build/pagefind.test.ts} +74 -13
- package/build/pagefind.ts +339 -0
- package/{webpack/pdf-text.js → build/pdf-text.ts} +47 -27
- package/build/pipeline.ts +93 -0
- package/{webpack/reachability.test.js → build/reachability.test.ts} +3 -3
- package/{webpack/reachability.js → build/reachability.ts} +77 -34
- package/build/serve.ts +112 -0
- package/{webpack/site-variables.js → build/site-variables.ts} +22 -15
- package/{webpack → build}/site.schema.json +3 -10
- package/{webpack/templates.js → build/templates.ts} +35 -33
- package/{webpack/text-to-id.js → build/text-to-id.ts} +2 -2
- package/build/toc-plugin.test.ts +105 -0
- package/{webpack/toc-plugin.js → build/toc-plugin.ts} +32 -13
- package/build/types.ts +172 -0
- package/build/util.ts +26 -0
- package/{webpack/utils/code.js → build/utils/code.ts} +119 -60
- package/{webpack/utils/content-files.js → build/utils/content-files.ts} +40 -35
- package/build/utils/derive-theme.test.ts +111 -0
- package/build/utils/derive-theme.ts +85 -0
- package/build/utils/file-types.test.ts +61 -0
- package/build/utils/file-types.ts +13 -0
- package/build/utils/front-matter.test.ts +80 -0
- package/{webpack/utils/front-matter.js → build/utils/front-matter.ts} +22 -9
- package/{webpack → build}/utils/jdi-runner/LiterateRunner.java +1 -1
- package/{webpack/utils/literate-java.js → build/utils/literate-java.ts} +63 -34
- package/{webpack/utils/markdown.js → build/utils/markdown.ts} +94 -49
- package/build/utils/paths.test.ts +91 -0
- package/{webpack/utils/paths.js → build/utils/paths.ts} +14 -22
- package/{webpack/utils/render.js → build/utils/render.ts} +188 -123
- package/build/utils/shiki-highlighter.ts +29 -0
- package/build/validate-internal-links-plugin.test.ts +106 -0
- package/{webpack/validate-internal-links-plugin.js → build/validate-internal-links-plugin.ts} +47 -20
- package/{webpack/watch-reachability-state.test.js → build/watch-reachability-state.test.ts} +8 -8
- package/{webpack/watch-reachability-state.js → build/watch-reachability-state.ts} +63 -24
- package/{webpack/watch-reload-client.js → build/watch-reload-client.ts} +3 -1
- package/build/watch.ts +573 -0
- package/content/index.md +9 -3
- package/content/markdown.md +2 -1
- package/content/problem_sets/index.html +14 -0
- package/fonts/google-sans-code/woff2/GoogleSansCodeVariable-Italic.woff2 +0 -0
- package/fonts/google-sans-code/woff2/GoogleSansCodeVariable.woff2 +0 -0
- package/fonts/inter/woff2/InterVariable-Italic.woff2 +0 -0
- package/fonts/inter/woff2/InterVariable.woff2 +0 -0
- package/package.json +28 -19
- package/src/_alerts.scss +92 -0
- package/src/_base.scss +106 -0
- package/src/{layout.scss → _layout.scss} +0 -2
- package/src/anchor/style.scss +1 -9
- package/src/code/index.ts +3 -3
- package/src/code.scss +1 -1
- package/src/critical.scss +5 -0
- package/src/header/_base.scss +129 -0
- package/src/header/style.scss +3 -131
- package/src/index.ts +1 -2
- package/src/question/style.scss +1 -1
- package/src/search/index.ts +36 -15
- package/src/search/style.scss +9 -15
- package/src/style.scss +6 -269
- package/src/toc/style.scss +5 -39
- package/src/util.ts +8 -5
- package/templates/_theme.scss +38 -14
- package/tsconfig.json +10 -6
- package/types/file-system-access.d.ts +5 -0
- package/types/markdown-it-plugins.d.ts +11 -0
- package/types/untyped-modules.d.ts +40 -0
- package/bin/tada.js +0 -361
- package/content/problem_sets/index.md +0 -6
- package/webpack/build-state.js +0 -97
- package/webpack/colors.js +0 -15
- package/webpack/config.base.js +0 -151
- package/webpack/config.dev.js +0 -23
- package/webpack/config.prod.js +0 -32
- package/webpack/content-watch-plugin.js +0 -153
- package/webpack/features.js +0 -5
- package/webpack/generate-content-assets-plugin.js +0 -308
- package/webpack/generate-favicon-plugin.js +0 -198
- package/webpack/generate-fonts-plugin.js +0 -69
- package/webpack/json-schema.js +0 -19
- package/webpack/log.js +0 -143
- package/webpack/pagefind-plugin.js +0 -379
- package/webpack/print-flair-plugin.js +0 -22
- package/webpack/serve.js +0 -104
- package/webpack/util.js +0 -49
- package/webpack/utils/define-plugin.js +0 -20
- package/webpack/utils/file-types.js +0 -26
- package/webpack/utils/parse-hsl.js +0 -8
- package/webpack/utils/shiki-highlighter.js +0 -26
- package/webpack/watch.js +0 -166
- /package/{webpack → build}/flair.json +0 -0
- /package/{webpack → build}/utils/jdi-runner/LiterateRunner.class +0 -0
- /package/fonts/google-sans-code/{GoogleSansCodeVariable-Italic.ttf → ttf/GoogleSansCodeVariable-Italic.ttf} +0 -0
- /package/fonts/google-sans-code/{GoogleSansCodeVariable.ttf → ttf/GoogleSansCodeVariable.ttf} +0 -0
- /package/fonts/inter/{InterVariable-Italic.ttf → ttf/InterVariable-Italic.ttf} +0 -0
- /package/fonts/inter/{InterVariable.ttf → ttf/InterVariable.ttf} +0 -0
- /package/types/{dev.ts → dev.d.ts} +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import {
|
|
4
|
+
getPackageDir,
|
|
5
|
+
getProjectDir,
|
|
6
|
+
getContentDir,
|
|
7
|
+
getDistDir,
|
|
8
|
+
getPublicDir,
|
|
9
|
+
getConfigDir,
|
|
10
|
+
createApplyBasePath,
|
|
11
|
+
normalizeOutputPath,
|
|
12
|
+
} from './paths.js';
|
|
13
|
+
import type { SiteVariables } from '../types.js';
|
|
14
|
+
|
|
15
|
+
describe('getPackageDir', () => {
|
|
16
|
+
test('returns an absolute path', () => {
|
|
17
|
+
expect(path.isAbsolute(getPackageDir())).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('points to repo root (contains package.json)', () => {
|
|
21
|
+
const dir = getPackageDir();
|
|
22
|
+
const pkg = path.join(dir, 'package.json');
|
|
23
|
+
expect(Bun.file(pkg).size).toBeGreaterThan(0);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('getProjectDir', () => {
|
|
28
|
+
test('returns process.cwd()', () => {
|
|
29
|
+
expect(getProjectDir()).toBe(process.cwd());
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('directory getters', () => {
|
|
34
|
+
test('getContentDir ends with /content', () => {
|
|
35
|
+
expect(getContentDir()).toEndWith('/content');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('getDistDir ends with /dist', () => {
|
|
39
|
+
expect(getDistDir()).toEndWith('/dist');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('getPublicDir ends with /public', () => {
|
|
43
|
+
expect(getPublicDir()).toEndWith('/public');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('getConfigDir equals getProjectDir', () => {
|
|
47
|
+
expect(getConfigDir()).toBe(getProjectDir());
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('createApplyBasePath', () => {
|
|
52
|
+
function apply(basePath: string, subPath: string): string {
|
|
53
|
+
const site = { base: '', basePath } as SiteVariables;
|
|
54
|
+
return createApplyBasePath(site)(subPath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test('prepends basePath to subPath', () => {
|
|
58
|
+
expect(apply('/course', '/page.html')).toBe('/course/page.html');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('handles basePath with trailing slash', () => {
|
|
62
|
+
expect(apply('/course/', '/page.html')).toBe('/course/page.html');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('handles root basePath', () => {
|
|
66
|
+
expect(apply('/', '/page.html')).toBe('/page.html');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('throws for subPath without leading slash', () => {
|
|
70
|
+
expect(() => apply('/course', 'page.html')).toThrow('must start with "/"');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('normalizeOutputPath', () => {
|
|
75
|
+
test('adds leading slash if missing', () => {
|
|
76
|
+
expect(normalizeOutputPath('page.html')).toBe('/page.html');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('preserves leading slash', () => {
|
|
80
|
+
expect(normalizeOutputPath('/page.html')).toBe('/page.html');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('normalizes dot-segments', () => {
|
|
84
|
+
expect(normalizeOutputPath('/a/../b/page.html')).toBe('/b/page.html');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('returns / for empty or dot path', () => {
|
|
88
|
+
expect(normalizeOutputPath('')).toBe('/');
|
|
89
|
+
expect(normalizeOutputPath('.')).toBe('/');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import type { SiteVariables } from '../types.js';
|
|
2
3
|
|
|
3
|
-
function getPackageDir() {
|
|
4
|
+
export function getPackageDir(): string {
|
|
4
5
|
return path.resolve(__dirname, '..', '..');
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
function getProjectDir() {
|
|
8
|
+
export function getProjectDir(): string {
|
|
8
9
|
return process.cwd();
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
function getContentDir() {
|
|
12
|
+
export function getContentDir(): string {
|
|
12
13
|
return path.resolve(getProjectDir(), 'content');
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function getDistDir() {
|
|
16
|
+
export function getDistDir(): string {
|
|
16
17
|
return path.resolve(getProjectDir(), 'dist');
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function getPublicDir() {
|
|
20
|
+
export function getPublicDir(): string {
|
|
20
21
|
return path.resolve(getProjectDir(), 'public');
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function getConfigDir() {
|
|
24
|
-
return
|
|
24
|
+
export function getConfigDir(): string {
|
|
25
|
+
return getProjectDir();
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
function createApplyBasePath(
|
|
28
|
-
|
|
28
|
+
export function createApplyBasePath(
|
|
29
|
+
siteVariables: SiteVariables,
|
|
30
|
+
): (subPath: string) => string {
|
|
31
|
+
return function applyBasePath(subPath: string): string {
|
|
29
32
|
if (!subPath.startsWith('/')) {
|
|
30
33
|
throw new Error('invalid internal path, must start with "/": ' + subPath);
|
|
31
34
|
}
|
|
@@ -38,21 +41,10 @@ function createApplyBasePath(siteVariables) {
|
|
|
38
41
|
};
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
function normalizeOutputPath(outputPath) {
|
|
44
|
+
export function normalizeOutputPath(outputPath: string): string {
|
|
42
45
|
const normalized = path.posix.normalize(outputPath);
|
|
43
46
|
if (normalized === '.' || normalized === '') {
|
|
44
47
|
return '/';
|
|
45
48
|
}
|
|
46
49
|
return normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
47
50
|
}
|
|
48
|
-
|
|
49
|
-
module.exports = {
|
|
50
|
-
createApplyBasePath,
|
|
51
|
-
getConfigDir,
|
|
52
|
-
getContentDir,
|
|
53
|
-
getDistDir,
|
|
54
|
-
getPackageDir,
|
|
55
|
-
getProjectDir,
|
|
56
|
-
getPublicDir,
|
|
57
|
-
normalizeOutputPath,
|
|
58
|
-
};
|
|
@@ -1,39 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import { stripHtml } from 'string-strip-html';
|
|
5
|
+
import { makeLogger } from '../log.js';
|
|
6
|
+
import { B } from '../colors.js';
|
|
7
|
+
import createGlobals from '../globals.js';
|
|
8
|
+
import { render, json } from '../templates.js';
|
|
9
|
+
import {
|
|
10
10
|
extractJavaMethodToc,
|
|
11
11
|
renderCodeSegment,
|
|
12
12
|
renderCodeWithComments,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
} from './code.js';
|
|
14
|
+
import { extensionIsMarkdown } from './file-types.js';
|
|
15
|
+
import {
|
|
16
|
+
createApplyBasePath,
|
|
17
|
+
normalizeOutputPath,
|
|
18
|
+
getDistDir,
|
|
19
|
+
} from './paths.js';
|
|
20
|
+
import { parseFrontMatterAndContent } from './front-matter.js';
|
|
21
|
+
import { createMarkdown } from './markdown.js';
|
|
22
|
+
import { generateTocHtml, generateCodeTocHtml } from '../toc-plugin.js';
|
|
23
|
+
import {
|
|
20
24
|
parseLiterateJava,
|
|
21
25
|
hasMainMethod,
|
|
22
26
|
deriveClassName,
|
|
23
27
|
compileJavaSource,
|
|
24
28
|
executeLiterateJava,
|
|
25
|
-
}
|
|
29
|
+
} from './literate-java.js';
|
|
30
|
+
import type {
|
|
31
|
+
Asset,
|
|
32
|
+
SiteVariables,
|
|
33
|
+
RenderPlainTextOptions,
|
|
34
|
+
RenderCodePageOptions,
|
|
35
|
+
RenderLiterateJavaOptions,
|
|
36
|
+
RenderCopiedContentOptions,
|
|
37
|
+
} from '../types.js';
|
|
26
38
|
|
|
27
39
|
const log = makeLogger(__filename);
|
|
28
40
|
|
|
29
41
|
const REQUIRED_FRONT_MATTER_FIELDS = ['title'];
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
interface TemplateParametersInput {
|
|
44
|
+
pageVariables: Record<string, unknown>;
|
|
45
|
+
siteVariables: SiteVariables;
|
|
46
|
+
content: string | null;
|
|
47
|
+
applyBasePath: (subPath: string) => string;
|
|
48
|
+
subPath: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveAuthor(
|
|
52
|
+
pageVariables: Record<string, unknown>,
|
|
53
|
+
filePath: string,
|
|
54
|
+
): void {
|
|
32
55
|
if (!pageVariables.author) {
|
|
33
56
|
return;
|
|
34
57
|
}
|
|
35
|
-
const authors = json('authors.json')
|
|
36
|
-
|
|
58
|
+
const authors = json('authors.json') as
|
|
59
|
+
| Record<string, Record<string, unknown>>
|
|
60
|
+
| undefined;
|
|
61
|
+
if (!authors) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`${filePath}: author "${pageVariables.author}" specified but no authors.json found`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const authorKey = pageVariables.author as string;
|
|
37
67
|
const authorEntry = authors[authorKey];
|
|
38
68
|
if (!authorEntry) {
|
|
39
69
|
throw new Error(
|
|
@@ -43,15 +73,20 @@ function resolveAuthor(pageVariables, filePath) {
|
|
|
43
73
|
pageVariables.author = authorEntry;
|
|
44
74
|
}
|
|
45
75
|
|
|
46
|
-
function validateFrontMatter(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
function validateFrontMatter(
|
|
77
|
+
pageVariables: Record<string, unknown>,
|
|
78
|
+
filePath: string,
|
|
79
|
+
): void {
|
|
80
|
+
const missing = REQUIRED_FRONT_MATTER_FIELDS.filter(
|
|
81
|
+
field => !pageVariables[field],
|
|
82
|
+
);
|
|
83
|
+
if (missing.length > 0) {
|
|
84
|
+
const noun = missing.length === 1 ? 'field' : 'fields';
|
|
85
|
+
const fields = missing.map(f => `"${f}"`).join(', ');
|
|
86
|
+
throw new Error(
|
|
87
|
+
`${filePath}: missing required front matter ${noun}: ${fields}`,
|
|
88
|
+
);
|
|
53
89
|
}
|
|
54
|
-
return valid;
|
|
55
90
|
}
|
|
56
91
|
|
|
57
92
|
function createTemplateParameters({
|
|
@@ -60,7 +95,7 @@ function createTemplateParameters({
|
|
|
60
95
|
content,
|
|
61
96
|
applyBasePath,
|
|
62
97
|
subPath,
|
|
63
|
-
}) {
|
|
98
|
+
}: TemplateParametersInput): Record<string, unknown> {
|
|
64
99
|
return {
|
|
65
100
|
...(siteVariables.vars || {}),
|
|
66
101
|
...createGlobals(pageVariables, siteVariables, subPath),
|
|
@@ -73,43 +108,54 @@ function createTemplateParameters({
|
|
|
73
108
|
};
|
|
74
109
|
}
|
|
75
110
|
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
.map(asset => asset.name);
|
|
111
|
+
export function injectAssetTags(
|
|
112
|
+
html: string,
|
|
113
|
+
assetFiles: string[],
|
|
114
|
+
applyBasePath: (subPath: string) => string,
|
|
115
|
+
): string {
|
|
116
|
+
const jsAssets = assetFiles.filter(f => f.endsWith('.js'));
|
|
117
|
+
const cssAssets = assetFiles.filter(f => f.endsWith('.css'));
|
|
84
118
|
|
|
85
119
|
const scriptTags = jsAssets
|
|
86
120
|
.map(asset => `<script defer src="${applyBasePath('/' + asset)}"></script>`)
|
|
87
121
|
.join('');
|
|
88
|
-
const
|
|
122
|
+
const criticalAssets = cssAssets.filter(f => f.includes('critical.bundle.'));
|
|
123
|
+
const asyncAssets = cssAssets.filter(f => !f.includes('critical.bundle.'));
|
|
124
|
+
|
|
125
|
+
const distDir = getDistDir();
|
|
126
|
+
const criticalTags = criticalAssets
|
|
127
|
+
.map(asset => {
|
|
128
|
+
const css = fs.readFileSync(path.join(distDir, asset), 'utf-8');
|
|
129
|
+
return `<style>${css}</style>`;
|
|
130
|
+
})
|
|
131
|
+
.join('');
|
|
132
|
+
const asyncLinkTags = asyncAssets
|
|
89
133
|
.map(
|
|
90
|
-
asset =>
|
|
134
|
+
asset =>
|
|
135
|
+
`<link href="${applyBasePath('/' + asset)}" rel="stylesheet" media="print" onload="this.media='all'">` +
|
|
136
|
+
`<noscript><link href="${applyBasePath('/' + asset)}" rel="stylesheet"></noscript>`,
|
|
91
137
|
)
|
|
92
138
|
.join('');
|
|
93
139
|
|
|
94
140
|
return html
|
|
95
|
-
.replace('<head>', `<head>${
|
|
141
|
+
.replace('<head>', `<head>${criticalTags}${asyncLinkTags}`)
|
|
96
142
|
.replace('</head>', `${scriptTags}</head>`);
|
|
97
143
|
}
|
|
98
144
|
|
|
99
|
-
function toContentAssetPath(contentDir, filePath) {
|
|
145
|
+
function toContentAssetPath(contentDir: string, filePath: string): string {
|
|
100
146
|
return path
|
|
101
147
|
.relative(contentDir, filePath)
|
|
102
148
|
.split(path.sep)
|
|
103
149
|
.join(path.posix.sep);
|
|
104
150
|
}
|
|
105
151
|
|
|
106
|
-
function renderPlainTextPageAsset({
|
|
152
|
+
export function renderPlainTextPageAsset({
|
|
107
153
|
filePath,
|
|
108
154
|
contentDir,
|
|
109
155
|
siteVariables,
|
|
110
156
|
validInternalTargets,
|
|
111
|
-
|
|
112
|
-
}) {
|
|
157
|
+
assetFiles,
|
|
158
|
+
}: RenderPlainTextOptions): Asset[] {
|
|
113
159
|
const { dir, name, ext } = path.parse(filePath);
|
|
114
160
|
const subPath = path.relative(contentDir, path.join(dir, name));
|
|
115
161
|
const applyBasePath = createApplyBasePath(siteVariables);
|
|
@@ -124,16 +170,16 @@ function renderPlainTextPageAsset({
|
|
|
124
170
|
{ validateInternalLinks: extensionIsMarkdown(ext.toLowerCase()) },
|
|
125
171
|
);
|
|
126
172
|
|
|
127
|
-
|
|
128
|
-
return [];
|
|
129
|
-
}
|
|
173
|
+
validateFrontMatter(pageVariables, filePath);
|
|
130
174
|
|
|
131
175
|
if (!pageVariables.template) {
|
|
132
176
|
pageVariables.template = 'default';
|
|
133
177
|
}
|
|
134
178
|
|
|
135
179
|
if (pageVariables.toc && tocItems) {
|
|
136
|
-
pageVariables.tocHtml = generateTocHtml(
|
|
180
|
+
pageVariables.tocHtml = generateTocHtml(
|
|
181
|
+
tocItems as Parameters<typeof generateTocHtml>[0],
|
|
182
|
+
);
|
|
137
183
|
}
|
|
138
184
|
|
|
139
185
|
const templateParameters = createTemplateParameters({
|
|
@@ -144,9 +190,9 @@ function renderPlainTextPageAsset({
|
|
|
144
190
|
subPath,
|
|
145
191
|
});
|
|
146
192
|
|
|
147
|
-
const html =
|
|
148
|
-
render(`${pageVariables.template}.html`, templateParameters),
|
|
149
|
-
|
|
193
|
+
const html = injectAssetTags(
|
|
194
|
+
render(`${pageVariables.template}.html`, templateParameters) as string,
|
|
195
|
+
assetFiles,
|
|
150
196
|
applyBasePath,
|
|
151
197
|
);
|
|
152
198
|
|
|
@@ -158,16 +204,16 @@ function renderPlainTextPageAsset({
|
|
|
158
204
|
];
|
|
159
205
|
}
|
|
160
206
|
|
|
161
|
-
function renderCodePageAsset({
|
|
207
|
+
export function renderCodePageAsset({
|
|
162
208
|
filePath,
|
|
163
209
|
contentDir,
|
|
164
210
|
siteVariables,
|
|
165
|
-
|
|
166
|
-
}) {
|
|
211
|
+
assetFiles,
|
|
212
|
+
}: RenderCodePageOptions): Asset[] {
|
|
167
213
|
const { dir, name, ext } = path.parse(filePath);
|
|
168
214
|
const subPath = path.relative(contentDir, path.join(dir, name));
|
|
169
215
|
const applyBasePath = createApplyBasePath(siteVariables);
|
|
170
|
-
const lang = siteVariables.codeLanguages[ext.slice(1).toLowerCase()];
|
|
216
|
+
const lang = siteVariables.codeLanguages![ext.slice(1).toLowerCase()];
|
|
171
217
|
const sourceCode = fs.readFileSync(filePath, 'utf-8');
|
|
172
218
|
|
|
173
219
|
log.info`Rendering code page ${B`${subPath + ext}`}`;
|
|
@@ -178,7 +224,7 @@ function renderCodePageAsset({
|
|
|
178
224
|
const titleHtml = `<tt>${name + ext}</tt>`;
|
|
179
225
|
const tocItems = lang === 'java' ? extractJavaMethodToc(sourceCode) : [];
|
|
180
226
|
const tocHtml = generateCodeTocHtml(tocItems);
|
|
181
|
-
const pageVariables = {
|
|
227
|
+
const pageVariables: Record<string, unknown> = {
|
|
182
228
|
template: 'code',
|
|
183
229
|
filePath,
|
|
184
230
|
title: `${name}${ext}`,
|
|
@@ -197,9 +243,9 @@ function renderCodePageAsset({
|
|
|
197
243
|
subPath,
|
|
198
244
|
});
|
|
199
245
|
|
|
200
|
-
const html =
|
|
201
|
-
render('code.html', templateParameters),
|
|
202
|
-
|
|
246
|
+
const html = injectAssetTags(
|
|
247
|
+
render('code.html', templateParameters) as string,
|
|
248
|
+
assetFiles,
|
|
203
249
|
applyBasePath,
|
|
204
250
|
);
|
|
205
251
|
|
|
@@ -211,9 +257,11 @@ function renderCodePageAsset({
|
|
|
211
257
|
];
|
|
212
258
|
}
|
|
213
259
|
|
|
214
|
-
function renderCopiedContentAsset({
|
|
215
|
-
|
|
216
|
-
|
|
260
|
+
export function renderCopiedContentAsset({
|
|
261
|
+
filePath,
|
|
262
|
+
contentDir,
|
|
263
|
+
}: RenderCopiedContentOptions): Asset[] {
|
|
264
|
+
const label = 'Copying source file';
|
|
217
265
|
const relPath = toContentAssetPath(contentDir, filePath);
|
|
218
266
|
|
|
219
267
|
log.info`${label} ${B`${relPath}`}`;
|
|
@@ -222,13 +270,17 @@ function renderCopiedContentAsset({ filePath, contentDir }) {
|
|
|
222
270
|
|
|
223
271
|
/** Parses the file, renders using template, returns HTML & params used to generate page */
|
|
224
272
|
function renderPlainTextContent(
|
|
225
|
-
filePath,
|
|
226
|
-
subPath,
|
|
227
|
-
siteVariables,
|
|
228
|
-
applyBasePath,
|
|
229
|
-
validInternalTargets
|
|
273
|
+
filePath: string,
|
|
274
|
+
subPath: string,
|
|
275
|
+
siteVariables: SiteVariables,
|
|
276
|
+
applyBasePath: (subPath: string) => string,
|
|
277
|
+
validInternalTargets: Set<string>,
|
|
230
278
|
{ validateInternalLinks = true } = {},
|
|
231
|
-
) {
|
|
279
|
+
): {
|
|
280
|
+
content: string | null;
|
|
281
|
+
pageVariables: Record<string, unknown>;
|
|
282
|
+
tocItems: unknown[] | null;
|
|
283
|
+
} {
|
|
232
284
|
const sourceUrlPath = `/${subPath}.html`;
|
|
233
285
|
const md = createMarkdown(siteVariables, {
|
|
234
286
|
validatorOptions: {
|
|
@@ -239,7 +291,7 @@ function renderPlainTextContent(
|
|
|
239
291
|
codeExtensions:
|
|
240
292
|
siteVariables.features?.code === false
|
|
241
293
|
? []
|
|
242
|
-
: Object.keys(siteVariables.codeLanguages),
|
|
294
|
+
: Object.keys(siteVariables.codeLanguages!),
|
|
243
295
|
},
|
|
244
296
|
});
|
|
245
297
|
|
|
@@ -256,25 +308,32 @@ function renderPlainTextContent(
|
|
|
256
308
|
applyBasePath,
|
|
257
309
|
subPath,
|
|
258
310
|
});
|
|
259
|
-
const pageVariablesProcessed = Object.entries(
|
|
311
|
+
const pageVariablesProcessed: Record<string, unknown> = Object.entries(
|
|
312
|
+
pageVariables,
|
|
313
|
+
)
|
|
260
314
|
.map(([k, v]) => {
|
|
261
315
|
const newValue =
|
|
262
316
|
typeof v === 'string' ? _.template(v)(siteOnlyParams) : v;
|
|
263
|
-
return [k, newValue];
|
|
317
|
+
return [k, newValue] as [string, unknown];
|
|
264
318
|
})
|
|
265
|
-
.reduce(
|
|
266
|
-
acc[k]
|
|
267
|
-
|
|
268
|
-
|
|
319
|
+
.reduce(
|
|
320
|
+
(acc, [k, v]) => {
|
|
321
|
+
acc[k] = v;
|
|
322
|
+
return acc;
|
|
323
|
+
},
|
|
324
|
+
{} as Record<string, unknown>,
|
|
325
|
+
);
|
|
269
326
|
|
|
270
327
|
// Render title and description as inline Markdown
|
|
271
328
|
if (pageVariablesProcessed.title) {
|
|
272
|
-
const titleHtml = md.renderInline(pageVariablesProcessed.title);
|
|
329
|
+
const titleHtml = md.renderInline(pageVariablesProcessed.title as string);
|
|
273
330
|
pageVariablesProcessed.titleHtml = titleHtml;
|
|
274
331
|
pageVariablesProcessed.title = stripHtml(titleHtml).result;
|
|
275
332
|
}
|
|
276
333
|
if (pageVariablesProcessed.description) {
|
|
277
|
-
const descriptionHtml = md.renderInline(
|
|
334
|
+
const descriptionHtml = md.renderInline(
|
|
335
|
+
pageVariablesProcessed.description as string,
|
|
336
|
+
);
|
|
278
337
|
pageVariablesProcessed.descriptionHtml = descriptionHtml;
|
|
279
338
|
pageVariablesProcessed.description = stripHtml(descriptionHtml).result;
|
|
280
339
|
}
|
|
@@ -291,35 +350,41 @@ function renderPlainTextContent(
|
|
|
291
350
|
subPath,
|
|
292
351
|
});
|
|
293
352
|
|
|
294
|
-
let html
|
|
353
|
+
let html: string;
|
|
295
354
|
try {
|
|
296
355
|
html = _.template(strippedContent)(params);
|
|
297
|
-
} catch (err) {
|
|
356
|
+
} catch (err: unknown) {
|
|
298
357
|
throw new Error(
|
|
299
|
-
`${filePath}: Lodash template error in page or template: ${err.message}`,
|
|
358
|
+
`${filePath}: Lodash template error in page or template: ${(err as Error).message}`,
|
|
359
|
+
{ cause: err },
|
|
300
360
|
);
|
|
301
361
|
}
|
|
302
362
|
|
|
303
|
-
let tocItems = null;
|
|
363
|
+
let tocItems: unknown[] | null = null;
|
|
304
364
|
if (extensionIsMarkdown(ext)) {
|
|
305
|
-
const env = {};
|
|
306
|
-
html = md.render(html
|
|
307
|
-
tocItems = env.tocItems || null;
|
|
365
|
+
const env: Record<string, unknown> = {};
|
|
366
|
+
html = md.render(html!, env);
|
|
367
|
+
tocItems = (env.tocItems as unknown[] | undefined) || null;
|
|
308
368
|
}
|
|
309
369
|
|
|
310
|
-
return {
|
|
370
|
+
return {
|
|
371
|
+
content: html,
|
|
372
|
+
pageVariables: params.page as Record<string, unknown>,
|
|
373
|
+
tocItems,
|
|
374
|
+
};
|
|
311
375
|
}
|
|
312
376
|
|
|
313
|
-
function stripHtmlComments(str) {
|
|
377
|
+
export function stripHtmlComments(str: string): string {
|
|
314
378
|
return str.replace(/<!---[\s\S]*?-->/g, '');
|
|
315
379
|
}
|
|
316
380
|
|
|
317
|
-
function renderLiterateJavaPageAsset({
|
|
381
|
+
export function renderLiterateJavaPageAsset({
|
|
318
382
|
filePath,
|
|
319
383
|
contentDir,
|
|
320
384
|
siteVariables,
|
|
321
|
-
|
|
322
|
-
|
|
385
|
+
assetFiles,
|
|
386
|
+
skipExecution,
|
|
387
|
+
}: RenderLiterateJavaOptions): Asset[] {
|
|
323
388
|
const { dir, name } = path.parse(filePath);
|
|
324
389
|
const className = deriveClassName(filePath);
|
|
325
390
|
const subPath = path.relative(contentDir, path.join(dir, className));
|
|
@@ -336,24 +401,30 @@ function renderLiterateJavaPageAsset({
|
|
|
336
401
|
visibleBlockIndices,
|
|
337
402
|
} = parseLiterateJava(raw, siteVariables);
|
|
338
403
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
404
|
+
validateFrontMatter(pageVariables, filePath);
|
|
405
|
+
|
|
406
|
+
// Compile and execute the concatenated Java source
|
|
407
|
+
let tempDir: string | undefined;
|
|
408
|
+
let blockOutputMap: Map<number, string> | null = null;
|
|
409
|
+
if (!skipExecution) {
|
|
410
|
+
try {
|
|
411
|
+
tempDir = compileJavaSource(javaSource, className);
|
|
412
|
+
|
|
413
|
+
// Execute if there is a main() method
|
|
414
|
+
if (hasMainMethod(javaSource)) {
|
|
415
|
+
const outputEntries = executeLiterateJava(
|
|
416
|
+
className,
|
|
417
|
+
tempDir,
|
|
418
|
+
codeBlocks,
|
|
419
|
+
);
|
|
420
|
+
blockOutputMap = new Map(
|
|
421
|
+
outputEntries.map(e => [e.blockIndex, e.output]),
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
} finally {
|
|
425
|
+
if (tempDir) {
|
|
426
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
427
|
+
}
|
|
357
428
|
}
|
|
358
429
|
}
|
|
359
430
|
|
|
@@ -376,7 +447,7 @@ function renderLiterateJavaPageAsset({
|
|
|
376
447
|
if (line.trim().length === 0) {
|
|
377
448
|
return min;
|
|
378
449
|
}
|
|
379
|
-
const indent = line.match(/^(\s*)/)[1].length;
|
|
450
|
+
const indent = line.match(/^(\s*)/)![1].length;
|
|
380
451
|
return Math.min(min, indent);
|
|
381
452
|
}, Infinity);
|
|
382
453
|
const dedented =
|
|
@@ -404,8 +475,8 @@ function renderLiterateJavaPageAsset({
|
|
|
404
475
|
return codeHtml;
|
|
405
476
|
};
|
|
406
477
|
|
|
407
|
-
const env = {};
|
|
408
|
-
const contentHtml = md.render(content, env);
|
|
478
|
+
const env: Record<string, unknown> = {};
|
|
479
|
+
const contentHtml = md.render(stripHtmlComments(content), env);
|
|
409
480
|
|
|
410
481
|
// Build page variables
|
|
411
482
|
const javaFileName = `${className}.java`;
|
|
@@ -415,7 +486,7 @@ function renderLiterateJavaPageAsset({
|
|
|
415
486
|
),
|
|
416
487
|
);
|
|
417
488
|
|
|
418
|
-
const titleHtml = md.renderInline(pageVariables.title);
|
|
489
|
+
const titleHtml = md.renderInline(pageVariables.title as string);
|
|
419
490
|
pageVariables.titleHtml = titleHtml;
|
|
420
491
|
pageVariables.title = stripHtml(titleHtml).result;
|
|
421
492
|
pageVariables.template = 'literate';
|
|
@@ -423,7 +494,9 @@ function renderLiterateJavaPageAsset({
|
|
|
423
494
|
pageVariables.downloadName = javaFileName;
|
|
424
495
|
|
|
425
496
|
if (pageVariables.toc && env.tocItems) {
|
|
426
|
-
pageVariables.tocHtml = generateTocHtml(
|
|
497
|
+
pageVariables.tocHtml = generateTocHtml(
|
|
498
|
+
env.tocItems as Parameters<typeof generateTocHtml>[0],
|
|
499
|
+
);
|
|
427
500
|
}
|
|
428
501
|
|
|
429
502
|
resolveAuthor(pageVariables, filePath);
|
|
@@ -436,9 +509,9 @@ function renderLiterateJavaPageAsset({
|
|
|
436
509
|
subPath,
|
|
437
510
|
});
|
|
438
511
|
|
|
439
|
-
const html =
|
|
440
|
-
render('literate.html', templateParameters),
|
|
441
|
-
|
|
512
|
+
const html = injectAssetTags(
|
|
513
|
+
render('literate.html', templateParameters) as string,
|
|
514
|
+
assetFiles,
|
|
442
515
|
applyBasePath,
|
|
443
516
|
);
|
|
444
517
|
|
|
@@ -456,11 +529,3 @@ function renderLiterateJavaPageAsset({
|
|
|
456
529
|
},
|
|
457
530
|
];
|
|
458
531
|
}
|
|
459
|
-
|
|
460
|
-
module.exports = {
|
|
461
|
-
injectWebpackAssets,
|
|
462
|
-
renderCodePageAsset,
|
|
463
|
-
renderCopiedContentAsset,
|
|
464
|
-
renderLiterateJavaPageAsset,
|
|
465
|
-
renderPlainTextPageAsset,
|
|
466
|
-
};
|