@abreen/tada 1.6.4 → 1.7.1
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/bin/tada.ts +4 -11
- package/build/apply-base-path-plugin.ts +11 -19
- package/build/colors.ts +19 -22
- package/build/content-watch.ts +3 -16
- package/build/copy.ts +42 -20
- package/build/deflist-id-plugin.ts +3 -16
- package/build/features.ts +3 -6
- package/build/generate-content-assets.ts +9 -5
- package/build/generate-favicon.ts +5 -15
- package/build/generate-web-app-manifest.ts +19 -57
- package/build/globals.ts +19 -24
- package/build/log.ts +30 -46
- package/build/pagefind.ts +2 -2
- package/build/reachability.ts +1 -1
- package/build/serve.ts +1 -5
- package/build/site-variables.ts +2 -2
- package/build/templates.ts +2 -2
- package/build/text-to-id.ts +6 -0
- package/build/types.d.ts +1 -7
- package/build/utils/code.ts +3 -7
- package/build/utils/front-matter.ts +28 -0
- package/build/utils/link.ts +8 -0
- package/build/utils/literate-java.ts +42 -33
- package/build/utils/markdown.ts +13 -17
- package/build/utils/paths.ts +3 -11
- package/build/utils/render.ts +25 -34
- package/build/utils/trace-layout.ts +308 -246
- package/build/utils/trace-svg.ts +1 -6
- package/build/utils/trace.ts +22 -47
- package/build/validate-internal-links-plugin.ts +2 -9
- package/build/watch-reload-client.ts +4 -6
- package/build/watch.ts +67 -116
- package/init/content/index.md +2 -0
- package/init/content/labs/00/VowelCounter.java.md +2 -0
- package/init/content/labs/00/index.md +2 -0
- package/init/content/labs/01/index.md +2 -0
- package/init/content/labs/index.md +2 -0
- package/init/content/lectures/01/Pair.java.md +2 -0
- package/init/content/lectures/01/index.md +2 -0
- package/init/content/lectures/02/index.md +2 -0
- package/init/content/lectures/index.md +2 -0
- package/init/content/markdown.md +5 -1
- package/init/content/problem_sets/index.html +2 -0
- package/package.json +2 -2
- package/src/_alerts.scss +1 -1
- package/src/_base.scss +9 -10
- package/src/_mixins.scss +4 -0
- package/src/literate/style.scss +1 -0
- package/src/print/style.scss +37 -20
- package/src/toc/style.scss +4 -3
- package/templates/_theme.scss +1 -1
package/build/log.ts
CHANGED
|
@@ -48,64 +48,58 @@ export function makeLogger(name: string, logLevel: string = 'info'): Logger {
|
|
|
48
48
|
logLevel = ENV_LOG_LEVEL;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
if (
|
|
52
|
-
name = '';
|
|
53
|
-
} else {
|
|
54
|
-
// Allow for passing import.meta.url (file:// URL) or a plain path
|
|
51
|
+
if (name) {
|
|
55
52
|
if (name.startsWith('file://')) {
|
|
56
53
|
name = new URL(name).pathname;
|
|
57
54
|
}
|
|
58
55
|
name = path.basename(name, path.extname(name));
|
|
59
56
|
}
|
|
60
57
|
|
|
58
|
+
function getArgs(
|
|
59
|
+
level: string,
|
|
60
|
+
strings: TemplateStringsArray | string | string[],
|
|
61
|
+
args: unknown[],
|
|
62
|
+
labelFn: (strings: TemplateStringsArray, ...args: unknown[]) => string,
|
|
63
|
+
): string[] {
|
|
64
|
+
return [
|
|
65
|
+
labelFn` ${level} ` + ' ' + (level === 'debug' ? name + ' ' : ''),
|
|
66
|
+
format(strings, ...args),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
const logger: Logger = {
|
|
62
71
|
minLogLevel: logLevel,
|
|
63
|
-
/** Don't log if the level is < minLogLevel */
|
|
64
72
|
setMinLogLevel(minLogLevel: string) {
|
|
65
73
|
this.minLogLevel = minLogLevel;
|
|
66
74
|
},
|
|
67
|
-
getArgs(
|
|
68
|
-
level: string,
|
|
69
|
-
strings: TemplateStringsArray | string | string[],
|
|
70
|
-
args: unknown[],
|
|
71
|
-
labelFn: (strings: TemplateStringsArray, ...args: unknown[]) => string,
|
|
72
|
-
): string[] {
|
|
73
|
-
const params: string[] = [];
|
|
74
|
-
params.push(
|
|
75
|
-
labelFn` ${level} ` + ' ' + (level === 'debug' ? name + ' ' : ''),
|
|
76
|
-
);
|
|
77
|
-
params.push(format(strings, ...args));
|
|
78
|
-
return params;
|
|
79
|
-
},
|
|
80
75
|
debug(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
81
76
|
if (shouldLog(this.minLogLevel, 'debug')) {
|
|
82
|
-
print(
|
|
77
|
+
print(getArgs('debug', strings, args, L), 'stderr');
|
|
83
78
|
}
|
|
84
79
|
},
|
|
85
80
|
info(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
86
81
|
if (shouldLog(this.minLogLevel, 'info')) {
|
|
87
|
-
print(
|
|
82
|
+
print(getArgs('info', strings, args, Li));
|
|
88
83
|
}
|
|
89
84
|
},
|
|
90
85
|
warn(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
91
86
|
if (shouldLog(this.minLogLevel, 'warn')) {
|
|
92
|
-
print(
|
|
87
|
+
print(getArgs('warn', strings, args, Yi));
|
|
93
88
|
}
|
|
94
89
|
},
|
|
95
90
|
error(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
96
91
|
if (shouldLog(this.minLogLevel, 'error')) {
|
|
97
|
-
print(
|
|
92
|
+
print(getArgs('error', strings, args, Ri));
|
|
98
93
|
}
|
|
99
94
|
},
|
|
100
95
|
event(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
101
|
-
print(
|
|
96
|
+
print(getArgs('event', strings, args, Gi));
|
|
102
97
|
},
|
|
103
98
|
followup(strings: string[]) {
|
|
104
99
|
print(strings);
|
|
105
100
|
},
|
|
106
101
|
};
|
|
107
102
|
|
|
108
|
-
logger.setMinLogLevel(logLevel);
|
|
109
103
|
return logger;
|
|
110
104
|
}
|
|
111
105
|
|
|
@@ -118,7 +112,7 @@ function format(
|
|
|
118
112
|
try {
|
|
119
113
|
return String.raw(strings as TemplateStringsArray, ...args.map(toString));
|
|
120
114
|
} catch {
|
|
121
|
-
|
|
115
|
+
return '';
|
|
122
116
|
}
|
|
123
117
|
} else {
|
|
124
118
|
if (Array.isArray(strings)) {
|
|
@@ -129,35 +123,25 @@ function format(
|
|
|
129
123
|
|
|
130
124
|
return args.map(toString).join(' ');
|
|
131
125
|
}
|
|
132
|
-
|
|
133
|
-
return '';
|
|
134
126
|
}
|
|
135
127
|
|
|
136
128
|
function toString(item: unknown): string {
|
|
137
|
-
if (item
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
if (item === null) {
|
|
141
|
-
return 'null';
|
|
129
|
+
if (item == null) {
|
|
130
|
+
return String(item);
|
|
142
131
|
}
|
|
143
132
|
if (typeof item === 'string') {
|
|
144
133
|
return item;
|
|
145
134
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
colors: true,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
throw new Error('not an object');
|
|
158
|
-
} catch {
|
|
159
|
-
return String(item);
|
|
135
|
+
if (typeof item === 'object') {
|
|
136
|
+
return inspect(item, {
|
|
137
|
+
compact: true,
|
|
138
|
+
depth: 2,
|
|
139
|
+
breakLength: 80,
|
|
140
|
+
maxStringLength: 250,
|
|
141
|
+
colors: true,
|
|
142
|
+
});
|
|
160
143
|
}
|
|
144
|
+
return String(item);
|
|
161
145
|
}
|
|
162
146
|
|
|
163
147
|
export function getFlair(): string {
|
package/build/pagefind.ts
CHANGED
|
@@ -101,7 +101,7 @@ function collectIndexTargets(
|
|
|
101
101
|
htmlAssetsByPath,
|
|
102
102
|
knownPdfPaths: new Set(pdfSourceByOutputPath.keys()),
|
|
103
103
|
rootPath: 'index.html',
|
|
104
|
-
basePath: siteVariables
|
|
104
|
+
basePath: siteVariables.basePath || '/',
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -264,7 +264,7 @@ export class WatchPagefindRunner {
|
|
|
264
264
|
private htmlCacheByAssetPath: Map<string, string>;
|
|
265
265
|
|
|
266
266
|
constructor(siteVariables: SiteVariables) {
|
|
267
|
-
this.siteVariables = siteVariables
|
|
267
|
+
this.siteVariables = siteVariables;
|
|
268
268
|
this.watchRunInProgress = false;
|
|
269
269
|
this.watchRunQueued = false;
|
|
270
270
|
this.distPath = null;
|
package/build/reachability.ts
CHANGED
|
@@ -24,7 +24,7 @@ function normalizeUrlPath(pathname: string): string {
|
|
|
24
24
|
|
|
25
25
|
function stripBasePath(pathname: string, basePath: string): string {
|
|
26
26
|
const normalizedPath = normalizeUrlPath(pathname);
|
|
27
|
-
const normalizedBasePath = normalizeUrlPath(basePath
|
|
27
|
+
const normalizedBasePath = normalizeUrlPath(basePath);
|
|
28
28
|
|
|
29
29
|
if (normalizedBasePath === '/') {
|
|
30
30
|
return normalizedPath;
|
package/build/serve.ts
CHANGED
|
@@ -29,10 +29,6 @@ export function resolvePathname(
|
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
if (stat.isDirectory()) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
32
|
if (!stat.isFile()) {
|
|
37
33
|
return null;
|
|
38
34
|
}
|
|
@@ -81,7 +77,7 @@ export function startServer(options: StartServerOptions): void {
|
|
|
81
77
|
|
|
82
78
|
log.info`Dev server: ${B`http://localhost:${server.port}/index.html`}`;
|
|
83
79
|
if (onReady) {
|
|
84
|
-
onReady(server.port
|
|
80
|
+
onReady(server.port!);
|
|
85
81
|
}
|
|
86
82
|
} catch (err) {
|
|
87
83
|
log.error`Failed to start server on port ${port}: ${err}`;
|
package/build/site-variables.ts
CHANGED
|
@@ -39,12 +39,12 @@ function getSiteVariables(env: string): SiteVariables {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Derive faviconColor from themeColor if not explicitly set
|
|
42
|
-
if (
|
|
42
|
+
if (!variables.faviconColor) {
|
|
43
43
|
variables.faviconColor = variables.themeColor;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// Derive titlePostfix from title if not explicitly set
|
|
47
|
-
if (
|
|
47
|
+
if (!variables.titlePostfix) {
|
|
48
48
|
variables.titlePostfix = ` - ${variables.title}`;
|
|
49
49
|
}
|
|
50
50
|
|
package/build/templates.ts
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import { compile as compileJsonSchema, doValidation } from './json-schema';
|
|
5
5
|
import { makeLogger } from './log';
|
|
6
|
-
import { getPackageDir,
|
|
6
|
+
import { getPackageDir, getProjectDir } from './utils/paths';
|
|
7
7
|
import type { ValidateFunction } from 'ajv';
|
|
8
8
|
import type { Logger, SiteVariables } from './types';
|
|
9
9
|
|
|
@@ -35,7 +35,7 @@ function getHtmlTemplatesDir(): string {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function getJsonDataDir(): string {
|
|
38
|
-
return
|
|
38
|
+
return getProjectDir();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function json(fileName: string): unknown {
|
package/build/text-to-id.ts
CHANGED
|
@@ -6,3 +6,9 @@ export default function textToId(value: unknown): string {
|
|
|
6
6
|
.replace(/\s+/g, '-')
|
|
7
7
|
.replace(/[^\w-]/g, '');
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
export function deduplicateId(used: Map<string, number>, id: string): string {
|
|
11
|
+
const count = (used.get(id) ?? 0) + 1;
|
|
12
|
+
used.set(id, count);
|
|
13
|
+
return count === 1 ? id : `${id}-${count}`;
|
|
14
|
+
}
|
package/build/types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface SiteVariables {
|
|
|
11
11
|
faviconFontWeight?: number;
|
|
12
12
|
internalDomains?: string[];
|
|
13
13
|
defaultTimeZone: string;
|
|
14
|
-
features
|
|
14
|
+
features: FeatureConfig;
|
|
15
15
|
codeLanguages?: Record<string, string>;
|
|
16
16
|
tintHue?: number;
|
|
17
17
|
tintAmount?: number;
|
|
@@ -57,12 +57,6 @@ export interface ContentRenderResult {
|
|
|
57
57
|
export interface Logger {
|
|
58
58
|
minLogLevel: string;
|
|
59
59
|
setMinLogLevel(level: string): void;
|
|
60
|
-
getArgs(
|
|
61
|
-
level: string,
|
|
62
|
-
strings: TemplateStringsArray | string | string[],
|
|
63
|
-
args: unknown[],
|
|
64
|
-
colorFn: (strings: TemplateStringsArray, ...args: unknown[]) => string,
|
|
65
|
-
): string[];
|
|
66
60
|
debug(strings: TemplateStringsArray, ...args: unknown[]): void;
|
|
67
61
|
info(strings: TemplateStringsArray, ...args: unknown[]): void;
|
|
68
62
|
warn(strings: TemplateStringsArray, ...args: unknown[]): void;
|
package/build/utils/code.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { getHighlighter } from './shiki-highlighter';
|
|
|
7
7
|
import externalLinksPlugin from '../external-links-plugin';
|
|
8
8
|
import applyBasePathPlugin from '../apply-base-path-plugin';
|
|
9
9
|
import { createApplyBasePath } from './paths';
|
|
10
|
+
import { splitLines } from './literate-java';
|
|
10
11
|
import type { JavaTocEntry, SiteVariables } from '../types';
|
|
11
12
|
|
|
12
13
|
interface CstNode {
|
|
@@ -73,9 +74,7 @@ export function rewriteProseLinks(
|
|
|
73
74
|
|
|
74
75
|
function rewriteHref(href: string): string {
|
|
75
76
|
// Separate pathname from query/fragment
|
|
76
|
-
const
|
|
77
|
-
const pathname = match ? match[1] : href;
|
|
78
|
-
const suffix = match ? match[2] : '';
|
|
77
|
+
const [, pathname, suffix] = href.match(/^([^?#]*)(.*)$/)!;
|
|
79
78
|
|
|
80
79
|
// Leave external, protocol-relative, and anchor-only links unchanged
|
|
81
80
|
if (
|
|
@@ -509,10 +508,7 @@ export function renderCodeWithComments(
|
|
|
509
508
|
pageDirPath?: string,
|
|
510
509
|
): string {
|
|
511
510
|
const md = createCodeMarkdown(siteVariables);
|
|
512
|
-
const lines = sourceCode
|
|
513
|
-
if (lines[lines.length - 1] === '') {
|
|
514
|
-
lines.pop();
|
|
515
|
-
}
|
|
511
|
+
const lines = splitLines(sourceCode);
|
|
516
512
|
|
|
517
513
|
// Group lines into segments
|
|
518
514
|
const segments: CodeSegment[] = [];
|
|
@@ -57,11 +57,39 @@ function parseFrontMatterPlainText(rawContent: string): RawParsedFrontMatter {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function parseFrontMatterStandard(rawContent: string): RawParsedFrontMatter {
|
|
61
|
+
const lines = rawContent.split(/\r?\n/);
|
|
62
|
+
|
|
63
|
+
// Caller has already verified lines[0] === '---'
|
|
64
|
+
for (let i = 1; i < lines.length; i++) {
|
|
65
|
+
if (lines[i].trimEnd() === '---') {
|
|
66
|
+
return {
|
|
67
|
+
frontMatter: lines.slice(1, i).join('\n'),
|
|
68
|
+
content: lines.slice(i + 1).join('\n'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new Error(
|
|
74
|
+
'Front matter starts with --- but no closing --- delimiter was found',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
export function parseFrontMatter(
|
|
61
79
|
rawContent: string,
|
|
62
80
|
ext: string,
|
|
63
81
|
): RawParsedFrontMatter {
|
|
64
82
|
if (extensionIsMarkdown(ext) || ext === '.html') {
|
|
83
|
+
// Detect standard YAML front matter (first line is exactly ---,
|
|
84
|
+
// ignoring trailing whitespace and \r)
|
|
85
|
+
const firstNewline = rawContent.indexOf('\n');
|
|
86
|
+
const firstLine =
|
|
87
|
+
firstNewline === -1 ? rawContent : rawContent.slice(0, firstNewline);
|
|
88
|
+
|
|
89
|
+
if (firstLine.trimEnd() === '---') {
|
|
90
|
+
return parseFrontMatterStandard(rawContent);
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
return parseFrontMatterPlainText(rawContent);
|
|
66
94
|
} else {
|
|
67
95
|
// unknown type, return raw
|
|
@@ -14,6 +14,37 @@ import type {
|
|
|
14
14
|
|
|
15
15
|
const log = makeLogger(import.meta.url);
|
|
16
16
|
|
|
17
|
+
export function runJavac(
|
|
18
|
+
args: string[],
|
|
19
|
+
opts: { cwd?: string; tempDir: string; label: string },
|
|
20
|
+
): void {
|
|
21
|
+
try {
|
|
22
|
+
execFileSync('javac', args, {
|
|
23
|
+
cwd: opts.cwd,
|
|
24
|
+
timeout: 30000,
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
+
});
|
|
27
|
+
} catch (err: unknown) {
|
|
28
|
+
const execErr = err as { stderr?: Buffer; message: string };
|
|
29
|
+
const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
30
|
+
fs.rmSync(opts.tempDir, { recursive: true, force: true });
|
|
31
|
+
throw new Error(`${opts.label}:\n${stderr}`, { cause: err });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getExecError(err: unknown): string {
|
|
36
|
+
const execErr = err as { stderr?: Buffer; message: string };
|
|
37
|
+
return execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function splitLines(source: string): string[] {
|
|
41
|
+
const lines = source.split('\n');
|
|
42
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
43
|
+
lines.pop();
|
|
44
|
+
}
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
|
|
17
48
|
let runnerClassDir: string | null = null;
|
|
18
49
|
|
|
19
50
|
const MAIN_PATTERN = /\bvoid\s+main\s*\(/m;
|
|
@@ -95,20 +126,11 @@ export function compileJavaSource(
|
|
|
95
126
|
|
|
96
127
|
log.debug`Compiling ${className}.java (${javaSource.split('\n').length} lines) in ${tempDir}`;
|
|
97
128
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
} catch (err: unknown) {
|
|
105
|
-
const execErr = err as { stderr?: Buffer; message: string };
|
|
106
|
-
const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
107
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
108
|
-
throw new Error(`Compilation failed for ${className}.java:\n${stderr}`, {
|
|
109
|
-
cause: err,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
129
|
+
runJavac([`${className}.java`], {
|
|
130
|
+
cwd: tempDir,
|
|
131
|
+
tempDir,
|
|
132
|
+
label: `Compilation failed for ${className}.java`,
|
|
133
|
+
});
|
|
112
134
|
|
|
113
135
|
return tempDir;
|
|
114
136
|
}
|
|
@@ -127,19 +149,10 @@ function ensureRunnerCompiled(): string {
|
|
|
127
149
|
|
|
128
150
|
log.debug`Compiling LiterateRunner.java into ${tempDir}`;
|
|
129
151
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
135
|
-
} catch (err: unknown) {
|
|
136
|
-
const execErr = err as { stderr?: Buffer; message: string };
|
|
137
|
-
const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
138
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
139
|
-
throw new Error(`Failed to compile LiterateRunner.java:\n${stderr}`, {
|
|
140
|
-
cause: err,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
152
|
+
runJavac(['-d', tempDir, sourceFile], {
|
|
153
|
+
tempDir,
|
|
154
|
+
label: 'Failed to compile LiterateRunner.java',
|
|
155
|
+
});
|
|
143
156
|
|
|
144
157
|
runnerClassDir = tempDir;
|
|
145
158
|
return tempDir;
|
|
@@ -174,12 +187,8 @@ export function executeLiterateJava(
|
|
|
174
187
|
log.debug`LiterateRunner returned ${entries.length} output entries`;
|
|
175
188
|
return entries;
|
|
176
189
|
} catch (err: unknown) {
|
|
177
|
-
const execErr = err as {
|
|
178
|
-
|
|
179
|
-
stdout?: Buffer | string;
|
|
180
|
-
message: string;
|
|
181
|
-
};
|
|
182
|
-
const stderr = execErr.stderr ? execErr.stderr.toString() : '';
|
|
190
|
+
const execErr = err as { stdout?: Buffer | string; message: string };
|
|
191
|
+
const stderr = getExecError(err);
|
|
183
192
|
const stdout = execErr.stdout ? execErr.stdout.toString() : '';
|
|
184
193
|
throw new Error(
|
|
185
194
|
`Execution failed for ${className}:\n${stderr || stdout || execErr.message}`,
|
package/build/utils/markdown.ts
CHANGED
|
@@ -7,7 +7,7 @@ import markdownItAnchor from 'markdown-it-anchor';
|
|
|
7
7
|
import markdownItFootnote from 'markdown-it-footnote';
|
|
8
8
|
import markdownItDeflist from 'markdown-it-deflist';
|
|
9
9
|
import markdownItContainer from 'markdown-it-container';
|
|
10
|
-
import textToId from '../text-to-id';
|
|
10
|
+
import textToId, { deduplicateId } from '../text-to-id';
|
|
11
11
|
import { getHighlighter } from './shiki-highlighter';
|
|
12
12
|
import headingSubtitlePlugin from '../heading-subtitle-plugin';
|
|
13
13
|
import deflistIdPlugin from '../deflist-id-plugin';
|
|
@@ -21,6 +21,10 @@ import katex from 'katex';
|
|
|
21
21
|
import renderA11yString from 'katex/contrib/render-a11y-string';
|
|
22
22
|
import type { SiteVariables } from '../types';
|
|
23
23
|
|
|
24
|
+
const DETAILS_PATTERN = /^details\s+(.*)$/;
|
|
25
|
+
const ALERT_PATTERN = /^(note|warning)(?:\s+"(.+)"|\s+(.+))?$/;
|
|
26
|
+
const QUESTION_PATTERN = /^question\s+(.+)$/;
|
|
27
|
+
|
|
24
28
|
interface CreateMarkdownOptions {
|
|
25
29
|
validatorOptions?: Record<string, unknown>;
|
|
26
30
|
literateJavaOutputPaths?: Set<string>;
|
|
@@ -29,11 +33,7 @@ interface CreateMarkdownOptions {
|
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
function capitalize(str: string): string {
|
|
32
|
-
|
|
33
|
-
return str;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return str[0].toUpperCase() + str.slice(1);
|
|
36
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const MAX_FOOTNOTES = 35;
|
|
@@ -81,11 +81,11 @@ export function createMarkdown(
|
|
|
81
81
|
.use(markdownItContainer, 'details', {
|
|
82
82
|
marker: '<',
|
|
83
83
|
validate: function (params: string) {
|
|
84
|
-
return
|
|
84
|
+
return DETAILS_PATTERN.test(params.trim());
|
|
85
85
|
},
|
|
86
86
|
|
|
87
87
|
render: function (tokens: Token[], idx: number) {
|
|
88
|
-
const m = tokens[idx].info.trim().match(
|
|
88
|
+
const m = tokens[idx].info.trim().match(DETAILS_PATTERN);
|
|
89
89
|
|
|
90
90
|
if (tokens[idx].nesting === 1) {
|
|
91
91
|
return (
|
|
@@ -144,7 +144,7 @@ export function createMarkdown(
|
|
|
144
144
|
markdown.use(markdownItContainer, 'alert', {
|
|
145
145
|
marker: '!',
|
|
146
146
|
validate: function (params: string) {
|
|
147
|
-
return
|
|
147
|
+
return ALERT_PATTERN.test(params.trim());
|
|
148
148
|
},
|
|
149
149
|
render: function (
|
|
150
150
|
tokens: Token[],
|
|
@@ -152,9 +152,7 @@ export function createMarkdown(
|
|
|
152
152
|
_options: unknown,
|
|
153
153
|
env: Record<string, unknown>,
|
|
154
154
|
) {
|
|
155
|
-
const matches = tokens[idx].info
|
|
156
|
-
.trim()
|
|
157
|
-
.match(/^(note|warning)(?:\s+"(.+)"|\s+(.+))?$/);
|
|
155
|
+
const matches = tokens[idx].info.trim().match(ALERT_PATTERN);
|
|
158
156
|
|
|
159
157
|
if (tokens[idx].nesting === 1) {
|
|
160
158
|
const classNames = ['alert'];
|
|
@@ -168,9 +166,7 @@ export function createMarkdown(
|
|
|
168
166
|
? markdown.utils.escapeHtml(curlyQuote(title))
|
|
169
167
|
: capitalize(type || '');
|
|
170
168
|
const baseId = textToId(title || type || '');
|
|
171
|
-
const
|
|
172
|
-
usedIds.set(baseId, count);
|
|
173
|
-
const titleId = count === 1 ? baseId : `${baseId}-${count}`;
|
|
169
|
+
const titleId = deduplicateId(usedIds, baseId);
|
|
174
170
|
|
|
175
171
|
if (!env.alertIds) {
|
|
176
172
|
env.alertIds = [];
|
|
@@ -190,10 +186,10 @@ export function createMarkdown(
|
|
|
190
186
|
markdown.use(markdownItContainer, 'question', {
|
|
191
187
|
marker: '?',
|
|
192
188
|
validate: function (params: string) {
|
|
193
|
-
return
|
|
189
|
+
return QUESTION_PATTERN.test(params.trim());
|
|
194
190
|
},
|
|
195
191
|
render: function (tokens: Token[], idx: number) {
|
|
196
|
-
const m = tokens[idx].info.trim().match(
|
|
192
|
+
const m = tokens[idx].info.trim().match(QUESTION_PATTERN);
|
|
197
193
|
if (tokens[idx].nesting === 1) {
|
|
198
194
|
const question = markdown.renderInline(m![1]);
|
|
199
195
|
return (
|
package/build/utils/paths.ts
CHANGED
|
@@ -25,23 +25,15 @@ export function getPublicDir(): string {
|
|
|
25
25
|
return path.resolve(getProjectDir(), 'public');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function getConfigDir(): string {
|
|
29
|
-
return getProjectDir();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
export function createApplyBasePath(
|
|
33
29
|
siteVariables: SiteVariables,
|
|
34
30
|
): (subPath: string) => string {
|
|
31
|
+
const base = (siteVariables.basePath || '/').replace(/\/$/, '');
|
|
35
32
|
return function applyBasePath(subPath: string): string {
|
|
36
33
|
if (!subPath.startsWith('/')) {
|
|
37
34
|
throw new Error('invalid internal path, must start with "/": ' + subPath);
|
|
38
35
|
}
|
|
39
|
-
|
|
40
|
-
let path = siteVariables.basePath || '/';
|
|
41
|
-
if (path.endsWith('/')) {
|
|
42
|
-
path = path.slice(0, -1);
|
|
43
|
-
}
|
|
44
|
-
return path + subPath;
|
|
36
|
+
return base + subPath;
|
|
45
37
|
};
|
|
46
38
|
}
|
|
47
39
|
|
|
@@ -62,7 +54,7 @@ export function toUrlPath(p: string): string {
|
|
|
62
54
|
|
|
63
55
|
export function normalizeOutputPath(outputPath: string): string {
|
|
64
56
|
const normalized = path.posix.normalize(toPosix(outputPath));
|
|
65
|
-
if (normalized === '.'
|
|
57
|
+
if (normalized === '.') {
|
|
66
58
|
return '/';
|
|
67
59
|
}
|
|
68
60
|
return normalized.startsWith('/') ? normalized : `/${normalized}`;
|
package/build/utils/render.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
+
import type MarkdownIt from 'markdown-it';
|
|
4
5
|
import { stripHtml } from 'string-strip-html';
|
|
5
6
|
import { isFeatureEnabled } from '../features';
|
|
6
7
|
import { makeLogger } from '../log';
|
|
@@ -50,6 +51,20 @@ function isWatchMode(assetFiles: string[]): boolean {
|
|
|
50
51
|
return assetFiles.some(f => f.includes('watch-reload-client'));
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
function renderInlineField(
|
|
55
|
+
md: MarkdownIt,
|
|
56
|
+
vars: Record<string, unknown>,
|
|
57
|
+
field: string,
|
|
58
|
+
): void {
|
|
59
|
+
const raw = vars[field];
|
|
60
|
+
if (!raw || typeof raw !== 'string') {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const html = md.renderInline(raw);
|
|
64
|
+
vars[`${field}Html`] = html;
|
|
65
|
+
vars[field] = stripHtml(html).result;
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
interface TemplateParametersInput {
|
|
54
69
|
pageVariables: Record<string, unknown>;
|
|
55
70
|
siteVariables: SiteVariables;
|
|
@@ -410,35 +425,16 @@ function renderPlainTextContent(
|
|
|
410
425
|
subPath,
|
|
411
426
|
isWatchMode,
|
|
412
427
|
});
|
|
413
|
-
const pageVariablesProcessed: Record<string, unknown> = Object.
|
|
414
|
-
pageVariables,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return [k, newValue] as [string, unknown];
|
|
420
|
-
})
|
|
421
|
-
.reduce(
|
|
422
|
-
(acc, [k, v]) => {
|
|
423
|
-
acc[k] = v;
|
|
424
|
-
return acc;
|
|
425
|
-
},
|
|
426
|
-
{} as Record<string, unknown>,
|
|
427
|
-
);
|
|
428
|
+
const pageVariablesProcessed: Record<string, unknown> = Object.fromEntries(
|
|
429
|
+
Object.entries(pageVariables).map(([k, v]) => [
|
|
430
|
+
k,
|
|
431
|
+
typeof v === 'string' ? _.template(v)(siteOnlyParams) : v,
|
|
432
|
+
]),
|
|
433
|
+
);
|
|
428
434
|
|
|
429
435
|
// Render title and description as inline Markdown
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
pageVariablesProcessed.titleHtml = titleHtml;
|
|
433
|
-
pageVariablesProcessed.title = stripHtml(titleHtml).result;
|
|
434
|
-
}
|
|
435
|
-
if (pageVariablesProcessed.description) {
|
|
436
|
-
const descriptionHtml = md.renderInline(
|
|
437
|
-
pageVariablesProcessed.description as string,
|
|
438
|
-
);
|
|
439
|
-
pageVariablesProcessed.descriptionHtml = descriptionHtml;
|
|
440
|
-
pageVariablesProcessed.description = stripHtml(descriptionHtml).result;
|
|
441
|
-
}
|
|
436
|
+
renderInlineField(md, pageVariablesProcessed, 'title');
|
|
437
|
+
renderInlineField(md, pageVariablesProcessed, 'description');
|
|
442
438
|
|
|
443
439
|
resolveAuthor(pageVariablesProcessed, filePath);
|
|
444
440
|
|
|
@@ -620,10 +616,7 @@ export function renderLiterateJavaPageAsset({
|
|
|
620
616
|
: null;
|
|
621
617
|
|
|
622
618
|
if (output) {
|
|
623
|
-
const escapedOutput = output
|
|
624
|
-
.replace(/&/g, '&')
|
|
625
|
-
.replace(/</g, '<')
|
|
626
|
-
.replace(/>/g, '>');
|
|
619
|
+
const escapedOutput = md.utils.escapeHtml(output);
|
|
627
620
|
return `<div class="literate-code-output">${codeHtml}<pre>${escapedOutput}</pre></div>`;
|
|
628
621
|
}
|
|
629
622
|
|
|
@@ -641,9 +634,7 @@ export function renderLiterateJavaPageAsset({
|
|
|
641
634
|
),
|
|
642
635
|
);
|
|
643
636
|
|
|
644
|
-
|
|
645
|
-
pageVariables.titleHtml = titleHtml;
|
|
646
|
-
pageVariables.title = stripHtml(titleHtml).result;
|
|
637
|
+
renderInlineField(md, pageVariables, 'title');
|
|
647
638
|
pageVariables.template = 'literate';
|
|
648
639
|
pageVariables.codeFilePath = codeFilePath;
|
|
649
640
|
pageVariables.downloadName = javaFileName;
|