@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/utils/trace.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { execFileSync } from 'child_process';
|
|
|
5
5
|
import { makeLogger } from '../log';
|
|
6
6
|
import { B } from '../colors';
|
|
7
7
|
import { normalizeOutputPath, toPosix } from './paths';
|
|
8
|
-
import { hasMainMethod } from './literate-java';
|
|
8
|
+
import { hasMainMethod, runJavac, splitLines } from './literate-java';
|
|
9
9
|
import { renderCodeSegment } from './code';
|
|
10
10
|
import { computeLayout } from './trace-layout';
|
|
11
11
|
import { generateStepSvg } from './trace-svg';
|
|
@@ -90,19 +90,10 @@ function ensureTracerCompiled(): string {
|
|
|
90
90
|
|
|
91
91
|
log.debug`Compiling TraceRunner.java into ${tempDir}`;
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
} catch (err: unknown) {
|
|
99
|
-
const execErr = err as { stderr?: Buffer; message: string };
|
|
100
|
-
const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
101
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
102
|
-
throw new Error(`Failed to compile TraceRunner.java:\n${stderr}`, {
|
|
103
|
-
cause: err,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
93
|
+
runJavac(['-d', tempDir, sourceFile], {
|
|
94
|
+
tempDir,
|
|
95
|
+
label: 'Failed to compile TraceRunner.java',
|
|
96
|
+
});
|
|
106
97
|
|
|
107
98
|
tracerClassDir = tempDir;
|
|
108
99
|
return tempDir;
|
|
@@ -111,27 +102,17 @@ function ensureTracerCompiled(): string {
|
|
|
111
102
|
function compileTargetFile(javaFilePath: string): string {
|
|
112
103
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-trace-target-'));
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{ timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'] },
|
|
126
|
-
);
|
|
127
|
-
} catch (err: unknown) {
|
|
128
|
-
const execErr = err as { stderr?: Buffer; message: string };
|
|
129
|
-
const stderr = execErr.stderr ? execErr.stderr.toString() : execErr.message;
|
|
130
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
131
|
-
throw new Error(`Compilation failed for ${javaFilePath}:\n${stderr}`, {
|
|
132
|
-
cause: err,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
105
|
+
runJavac(
|
|
106
|
+
[
|
|
107
|
+
'-g',
|
|
108
|
+
'-d',
|
|
109
|
+
tempDir,
|
|
110
|
+
'-sourcepath',
|
|
111
|
+
path.dirname(javaFilePath),
|
|
112
|
+
javaFilePath,
|
|
113
|
+
],
|
|
114
|
+
{ tempDir, label: `Compilation failed for ${javaFilePath}` },
|
|
115
|
+
);
|
|
135
116
|
|
|
136
117
|
return tempDir;
|
|
137
118
|
}
|
|
@@ -224,14 +205,10 @@ export interface TraceResult {
|
|
|
224
205
|
mtime: number;
|
|
225
206
|
}
|
|
226
207
|
|
|
227
|
-
function highlightSource(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
const ext = path.extname(javaFile).slice(1).toLowerCase();
|
|
233
|
-
const lang = ext === 'java' ? 'java' : 'text';
|
|
234
|
-
return renderCodeSegment(sourceLines, 1, lang, { linkLineNumbers: false });
|
|
208
|
+
function highlightSource(source: string): string {
|
|
209
|
+
return renderCodeSegment(splitLines(source), 1, 'java', {
|
|
210
|
+
linkLineNumbers: false,
|
|
211
|
+
});
|
|
235
212
|
}
|
|
236
213
|
|
|
237
214
|
function renderWidgetHtml({
|
|
@@ -322,7 +299,7 @@ export function createTraceHelpers(context: TraceContext): {
|
|
|
322
299
|
{ timeout: 60000, encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 },
|
|
323
300
|
);
|
|
324
301
|
|
|
325
|
-
const highlightedSource = highlightSource(
|
|
302
|
+
const highlightedSource = highlightSource(source);
|
|
326
303
|
|
|
327
304
|
const relDir = toPosix(path.relative(contentDir, pageDir));
|
|
328
305
|
const traceOutputDir = path.join(distDir, relDir, '_traces', className);
|
|
@@ -367,9 +344,7 @@ export function createTraceHelpers(context: TraceContext): {
|
|
|
367
344
|
throw new Error(`Trace target not found: ${javaFilePath}`);
|
|
368
345
|
}
|
|
369
346
|
const source = fs.readFileSync(javaFilePath, 'utf-8');
|
|
370
|
-
return renderWidgetHtml({
|
|
371
|
-
highlightedSource: highlightSource(javaFile, source),
|
|
372
|
-
});
|
|
347
|
+
return renderWidgetHtml({ highlightedSource: highlightSource(source) });
|
|
373
348
|
}
|
|
374
349
|
const { manifestUrl, highlightedSource, totalSteps } =
|
|
375
350
|
getOrRunTrace(javaFile);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type MarkdownIt from 'markdown-it';
|
|
2
2
|
import type Token from 'markdown-it/lib/token.mjs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { isInternalLink } from './utils/link';
|
|
4
5
|
import { makeLogger } from './log';
|
|
5
6
|
|
|
6
7
|
const log = makeLogger(import.meta.url);
|
|
@@ -17,14 +18,6 @@ function stripQueryAndHash(href: string): string {
|
|
|
17
18
|
return href.split('#')[0].split('?')[0];
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function isExternalOrAnchor(href: string): boolean {
|
|
21
|
-
if (!href || href.startsWith('#') || href.startsWith('//')) {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
function normalizePathname(pathname: string): string {
|
|
29
22
|
const normalized = path.posix.normalize(pathname);
|
|
30
23
|
if (normalized === '.') {
|
|
@@ -142,7 +135,7 @@ export default function validateInternalLinks(
|
|
|
142
135
|
}
|
|
143
136
|
|
|
144
137
|
const href = rawHref.trim();
|
|
145
|
-
if (!href ||
|
|
138
|
+
if (!href || !isInternalLink(href)) {
|
|
146
139
|
return;
|
|
147
140
|
}
|
|
148
141
|
|
|
@@ -2,12 +2,10 @@ declare const __WEBSOCKET_PORT__: number;
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
const style = document.createElement('style');
|
|
5
|
-
style.textContent =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'}',
|
|
10
|
-
].join('\n');
|
|
5
|
+
style.textContent = `body.loading-cursor,
|
|
6
|
+
body.loading-cursor * {
|
|
7
|
+
cursor: wait !important;
|
|
8
|
+
}`;
|
|
11
9
|
document.head.appendChild(style);
|
|
12
10
|
|
|
13
11
|
const ws = new WebSocket(`ws://localhost:${__WEBSOCKET_PORT__}`);
|
package/build/watch.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chokidar from 'chokidar';
|
|
4
4
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
5
|
-
import type { SiteVariables, WatchState } from './types';
|
|
5
|
+
import type { SiteVariables, WatchState, ContentRenderResult } from './types';
|
|
6
6
|
import { B } from './colors';
|
|
7
7
|
import { makeLogger, printFlair } from './log';
|
|
8
8
|
import { getDevSiteVariables } from './site-variables';
|
|
@@ -36,7 +36,7 @@ export async function runWatch(options: {
|
|
|
36
36
|
httpPort?: number;
|
|
37
37
|
wsPort?: number;
|
|
38
38
|
}): Promise<void> {
|
|
39
|
-
const httpPort = options.httpPort
|
|
39
|
+
const httpPort = options.httpPort;
|
|
40
40
|
const WEBSOCKET_PORT = options.wsPort ?? 35729;
|
|
41
41
|
const DEBOUNCE_MS = 300;
|
|
42
42
|
const RELOAD_CLIENT_PATH = path.resolve(
|
|
@@ -69,7 +69,6 @@ export async function runWatch(options: {
|
|
|
69
69
|
|
|
70
70
|
let webSocketsReady = false;
|
|
71
71
|
let watcherReady = false;
|
|
72
|
-
let webServerReady = false;
|
|
73
72
|
let webServerTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
74
73
|
let serveStarted = false;
|
|
75
74
|
|
|
@@ -117,30 +116,23 @@ export async function runWatch(options: {
|
|
|
117
116
|
// Dev server
|
|
118
117
|
|
|
119
118
|
function serve(): void {
|
|
119
|
+
webServerTimeout = setTimeout(() => {
|
|
120
|
+
log.error`Web server failed to report within 10 seconds, exiting`;
|
|
121
|
+
process.exit(3);
|
|
122
|
+
}, 10000);
|
|
123
|
+
|
|
120
124
|
startServer({
|
|
121
|
-
port: httpPort
|
|
125
|
+
port: httpPort,
|
|
122
126
|
distDir,
|
|
123
|
-
onReady:
|
|
124
|
-
webServerReady = true;
|
|
127
|
+
onReady: () => {
|
|
125
128
|
clearTimeout(webServerTimeout);
|
|
126
129
|
},
|
|
127
130
|
});
|
|
128
|
-
|
|
129
|
-
webServerTimeout = setTimeout(() => {
|
|
130
|
-
if (webServerReady) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
log.error`Web server failed to report within 10 seconds, exiting`;
|
|
134
|
-
process.exit(3);
|
|
135
|
-
}, 10000);
|
|
136
131
|
}
|
|
137
132
|
|
|
138
133
|
// Path helpers
|
|
139
134
|
|
|
140
135
|
function toContentMarkdownPath(filePath: string): string | null {
|
|
141
|
-
if (!filePath) {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
136
|
const ext = path.extname(filePath).toLowerCase();
|
|
145
137
|
if (!['.md', '.markdown'].includes(ext)) {
|
|
146
138
|
return null;
|
|
@@ -156,9 +148,6 @@ export async function runWatch(options: {
|
|
|
156
148
|
}
|
|
157
149
|
|
|
158
150
|
function toPublicRelativePath(filePath: string): string | null {
|
|
159
|
-
if (!filePath) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
151
|
const normalizedPublicDir = path.resolve(publicDir) + path.sep;
|
|
163
152
|
const normalizedFilePath = path.resolve(filePath);
|
|
164
153
|
if (!normalizedFilePath.startsWith(normalizedPublicDir)) {
|
|
@@ -185,14 +174,39 @@ export async function runWatch(options: {
|
|
|
185
174
|
const pendingChanges = new Set<string>();
|
|
186
175
|
let rebuilding = false;
|
|
187
176
|
|
|
188
|
-
|
|
189
|
-
|
|
177
|
+
function onBuildSuccess(result?: ContentRenderResult): void {
|
|
178
|
+
printFlair();
|
|
179
|
+
if (!serveStarted) {
|
|
180
|
+
serveStarted = true;
|
|
181
|
+
serve();
|
|
182
|
+
}
|
|
183
|
+
broadcast('reload');
|
|
184
|
+
if (pagefindRunner && result) {
|
|
185
|
+
pagefindRunner.update(distDir, result.htmlAssetsByPath);
|
|
186
|
+
setImmediate(() => pagefindRunner!.run());
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function copyChangedPublicFiles(changes: Set<string>): void {
|
|
191
|
+
for (const filePath of changes) {
|
|
192
|
+
if (classifyChange(filePath) !== 'public') {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const absPath = path.resolve(filePath);
|
|
196
|
+
if (fs.existsSync(absPath)) {
|
|
197
|
+
copyPublicFile(publicDir, distDir, absPath, contentAssetRelPaths);
|
|
198
|
+
const rel = toPosix(path.relative(publicDir, absPath));
|
|
199
|
+
publicRelPaths.add(rel);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function buildFromConfig(
|
|
205
|
+
copyAssets: boolean,
|
|
206
|
+
): Promise<ContentRenderResult> {
|
|
190
207
|
processedExtSet = new Set(
|
|
191
208
|
getProcessedExtensions(Object.keys(siteVariables.codeLanguages || {})),
|
|
192
209
|
);
|
|
193
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
194
|
-
|
|
195
|
-
compileTemplates(siteVariables);
|
|
196
210
|
contentRenderer = new ContentRenderer(siteVariables);
|
|
197
211
|
changeDetector = new ContentChangeDetector(siteVariables);
|
|
198
212
|
|
|
@@ -200,6 +214,7 @@ export async function runWatch(options: {
|
|
|
200
214
|
pagefindRunner = new WatchPagefindRunner(siteVariables);
|
|
201
215
|
}
|
|
202
216
|
|
|
217
|
+
compileTemplates(siteVariables);
|
|
203
218
|
await contentRenderer.initHighlighter();
|
|
204
219
|
|
|
205
220
|
assetFiles = [
|
|
@@ -215,15 +230,24 @@ export async function runWatch(options: {
|
|
|
215
230
|
generateWebAppManifest(siteVariables, distDir);
|
|
216
231
|
}
|
|
217
232
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
233
|
+
if (copyAssets) {
|
|
234
|
+
publicRelPaths = copyPublicFiles(publicDir, distDir);
|
|
235
|
+
contentAssetRelPaths = copyContentAssets(
|
|
236
|
+
contentDir,
|
|
237
|
+
distDir,
|
|
238
|
+
[...processedExtSet],
|
|
239
|
+
publicRelPaths,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return contentRenderer.processContent({ distDir, assetFiles });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function initialBuild(): Promise<void> {
|
|
247
|
+
siteVariables = getDevSiteVariables();
|
|
248
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
225
249
|
|
|
226
|
-
const result =
|
|
250
|
+
const result = await buildFromConfig(true);
|
|
227
251
|
|
|
228
252
|
for (const err of result.errors) {
|
|
229
253
|
log.error`${err.message}`;
|
|
@@ -231,12 +255,10 @@ export async function runWatch(options: {
|
|
|
231
255
|
|
|
232
256
|
if (result.errors.length === 0) {
|
|
233
257
|
printFlair();
|
|
234
|
-
|
|
235
258
|
if (pagefindRunner) {
|
|
236
259
|
pagefindRunner.update(distDir, result.htmlAssetsByPath);
|
|
237
260
|
setImmediate(() => pagefindRunner!.run());
|
|
238
261
|
}
|
|
239
|
-
|
|
240
262
|
if (!serveStarted) {
|
|
241
263
|
serveStarted = true;
|
|
242
264
|
serve();
|
|
@@ -293,47 +315,13 @@ export async function runWatch(options: {
|
|
|
293
315
|
if (categories.has('config')) {
|
|
294
316
|
log.event`Site config changed, restarting`;
|
|
295
317
|
siteVariables = getDevSiteVariables();
|
|
296
|
-
processedExtSet = new Set(
|
|
297
|
-
getProcessedExtensions(
|
|
298
|
-
Object.keys(siteVariables.codeLanguages || {}),
|
|
299
|
-
),
|
|
300
|
-
);
|
|
301
|
-
contentRenderer = new ContentRenderer(siteVariables);
|
|
302
|
-
changeDetector = new ContentChangeDetector(siteVariables);
|
|
303
|
-
if (isFeatureEnabled(siteVariables, 'search')) {
|
|
304
|
-
pagefindRunner = new WatchPagefindRunner(siteVariables);
|
|
305
|
-
}
|
|
306
|
-
compileTemplates(siteVariables);
|
|
307
|
-
await contentRenderer.initHighlighter();
|
|
308
|
-
|
|
309
|
-
assetFiles = [
|
|
310
|
-
...(await bundle(siteVariables, { mode: 'development' })),
|
|
311
|
-
...(await bundleReloadClient()),
|
|
312
|
-
];
|
|
313
|
-
|
|
314
|
-
copyFonts(distDir);
|
|
315
|
-
copyKatexAssets(distDir);
|
|
316
318
|
|
|
317
|
-
|
|
318
|
-
await generateFavicons(siteVariables, distDir);
|
|
319
|
-
generateWebAppManifest(siteVariables, distDir);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const result = contentRenderer.processContent({ distDir, assetFiles });
|
|
319
|
+
const result = await buildFromConfig(false);
|
|
323
320
|
for (const err of result.errors) {
|
|
324
321
|
log.error`${err.message}`;
|
|
325
322
|
}
|
|
326
323
|
if (result.errors.length === 0) {
|
|
327
|
-
|
|
328
|
-
if (!serveStarted) {
|
|
329
|
-
serveStarted = true;
|
|
330
|
-
serve();
|
|
331
|
-
}
|
|
332
|
-
broadcast('reload');
|
|
333
|
-
if (pagefindRunner) {
|
|
334
|
-
pagefindRunner.update(distDir, result.htmlAssetsByPath);
|
|
335
|
-
setImmediate(() => pagefindRunner!.run());
|
|
336
|
-
}
|
|
324
|
+
onBuildSuccess(result);
|
|
337
325
|
succeeded = true;
|
|
338
326
|
}
|
|
339
327
|
return;
|
|
@@ -341,18 +329,8 @@ export async function runWatch(options: {
|
|
|
341
329
|
|
|
342
330
|
// Public file changed, copy just that file
|
|
343
331
|
if (categories.has('public') && !categories.has('content')) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const absPath = path.resolve(filePath);
|
|
347
|
-
if (fs.existsSync(absPath)) {
|
|
348
|
-
copyPublicFile(publicDir, distDir, absPath, contentAssetRelPaths);
|
|
349
|
-
const rel = toPosix(path.relative(publicDir, absPath));
|
|
350
|
-
publicRelPaths.add(rel);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
printFlair();
|
|
355
|
-
broadcast('reload');
|
|
332
|
+
copyChangedPublicFiles(changes);
|
|
333
|
+
onBuildSuccess();
|
|
356
334
|
succeeded = true;
|
|
357
335
|
return;
|
|
358
336
|
}
|
|
@@ -381,16 +359,7 @@ export async function runWatch(options: {
|
|
|
381
359
|
|
|
382
360
|
// Copy any changed public files too
|
|
383
361
|
if (categories.has('public')) {
|
|
384
|
-
|
|
385
|
-
if (classifyChange(filePath) === 'public') {
|
|
386
|
-
const absPath = path.resolve(filePath);
|
|
387
|
-
if (fs.existsSync(absPath)) {
|
|
388
|
-
copyPublicFile(publicDir, distDir, absPath, contentAssetRelPaths);
|
|
389
|
-
const rel = toPosix(path.relative(publicDir, absPath));
|
|
390
|
-
publicRelPaths.add(rel);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
362
|
+
copyChangedPublicFiles(changes);
|
|
394
363
|
}
|
|
395
364
|
|
|
396
365
|
// Copy any changed non-processed content files (images, PDFs, etc.)
|
|
@@ -428,17 +397,7 @@ export async function runWatch(options: {
|
|
|
428
397
|
}
|
|
429
398
|
|
|
430
399
|
if (result.errors.length === 0) {
|
|
431
|
-
|
|
432
|
-
if (!serveStarted) {
|
|
433
|
-
serveStarted = true;
|
|
434
|
-
serve();
|
|
435
|
-
}
|
|
436
|
-
broadcast('reload');
|
|
437
|
-
|
|
438
|
-
if (pagefindRunner) {
|
|
439
|
-
pagefindRunner.update(distDir, result.htmlAssetsByPath);
|
|
440
|
-
setImmediate(() => pagefindRunner!.run());
|
|
441
|
-
}
|
|
400
|
+
onBuildSuccess(result);
|
|
442
401
|
succeeded = true;
|
|
443
402
|
}
|
|
444
403
|
} catch (err) {
|
|
@@ -511,11 +470,10 @@ export async function runWatch(options: {
|
|
|
511
470
|
watcher.on('add', onFileChange);
|
|
512
471
|
watcher.on('unlink', onFileChange);
|
|
513
472
|
|
|
514
|
-
let
|
|
515
|
-
let contentWatcherReady = false;
|
|
473
|
+
let watchersReady = 0;
|
|
516
474
|
|
|
517
475
|
function onWatcherReady(): void {
|
|
518
|
-
if (
|
|
476
|
+
if (++watchersReady < 2) {
|
|
519
477
|
return;
|
|
520
478
|
}
|
|
521
479
|
watcherReady = true;
|
|
@@ -526,15 +484,8 @@ export async function runWatch(options: {
|
|
|
526
484
|
log.info`Watching for changes...`;
|
|
527
485
|
}
|
|
528
486
|
|
|
529
|
-
configWatcher.on('ready',
|
|
530
|
-
|
|
531
|
-
onWatcherReady();
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
watcher.on('ready', () => {
|
|
535
|
-
contentWatcherReady = true;
|
|
536
|
-
onWatcherReady();
|
|
537
|
-
});
|
|
487
|
+
configWatcher.on('ready', onWatcherReady);
|
|
488
|
+
watcher.on('ready', onWatcherReady);
|
|
538
489
|
});
|
|
539
490
|
|
|
540
491
|
// Return a promise that never resolves (watch mode runs until killed)
|
package/init/content/index.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
---
|
|
1
2
|
parent: /labs/00/index.html
|
|
2
3
|
parentLabel: Lab 0
|
|
3
4
|
title: Counting vowels
|
|
4
5
|
author: alex
|
|
5
6
|
description: An example of a literate Java program that takes predefined input.
|
|
6
7
|
stdin: "hello, world!\n"
|
|
8
|
+
---
|
|
7
9
|
|
|
8
10
|
A Java program can process input in different ways: it could open a file and
|
|
9
11
|
read it; it could open a network connection and wait for data from another
|
package/init/content/markdown.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
+
---
|
|
1
2
|
title: Markdown Examples
|
|
2
3
|
description: Examples of Markdown syntax supported by Tada.
|
|
3
4
|
author: alex
|
|
4
5
|
published: 2026-03-20
|
|
5
6
|
toc: true
|
|
7
|
+
---
|
|
6
8
|
|
|
7
9
|
Markdown and HTML files in the `content/` directory
|
|
8
10
|
must contain ["front matter"][front-matter] (YAML-formatted metadata).
|
|
9
11
|
For example, this file's first five lines look like this:
|
|
10
12
|
|
|
11
13
|
```text
|
|
14
|
+
---
|
|
12
15
|
title: Markdown Examples
|
|
13
|
-
description: Examples of Markdown syntax supported by
|
|
16
|
+
description: Examples of Markdown syntax supported by Tada.
|
|
14
17
|
toc: true
|
|
15
18
|
author: alex
|
|
19
|
+
---
|
|
16
20
|
```
|
|
17
21
|
|
|
18
22
|
This page contains a table of contents because the front matter
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abreen/tada",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A static site generator",
|
|
6
6
|
"license": "MIT",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@eslint/js": "^10.0.1",
|
|
81
|
-
"@playwright/test": "1.
|
|
81
|
+
"@playwright/test": "^1.59.1",
|
|
82
82
|
"@saintedlama/lcov-parse": "^1.0.1",
|
|
83
83
|
"@types/bun": "^1.3.10",
|
|
84
84
|
"@types/culori": "^4.0.1",
|
package/src/_alerts.scss
CHANGED
package/src/_base.scss
CHANGED
|
@@ -92,7 +92,7 @@ code {
|
|
|
92
92
|
font-size: var(--mono-font-size);
|
|
93
93
|
background: var(--bg2-color);
|
|
94
94
|
border-radius: var(--mono-border-radius);
|
|
95
|
-
padding:
|
|
95
|
+
padding: var(--mono-padding);
|
|
96
96
|
overflow-wrap: anywhere;
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -106,6 +106,7 @@ pre {
|
|
|
106
106
|
padding: var(--gap);
|
|
107
107
|
|
|
108
108
|
code {
|
|
109
|
+
font-size: inherit;
|
|
109
110
|
padding: 0;
|
|
110
111
|
}
|
|
111
112
|
}
|
|
@@ -139,18 +140,16 @@ details .content {
|
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
@media (hover: hover) {
|
|
142
|
-
details summary {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
@include link-underline;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
&:active {
|
|
149
|
-
color: var(--link-active-color);
|
|
150
|
-
}
|
|
143
|
+
details summary:hover {
|
|
144
|
+
color: var(--fg2-color);
|
|
145
|
+
@include link-underline;
|
|
151
146
|
}
|
|
152
147
|
}
|
|
153
148
|
|
|
149
|
+
details summary:active {
|
|
150
|
+
color: var(--link-active-color);
|
|
151
|
+
}
|
|
152
|
+
|
|
154
153
|
main details {
|
|
155
154
|
margin-bottom: 1rem;
|
|
156
155
|
border-radius: var(--border-radius);
|
package/src/_mixins.scss
CHANGED
|
@@ -42,4 +42,8 @@
|
|
|
42
42
|
-webkit-mask-position: center center;
|
|
43
43
|
mask-position: center center;
|
|
44
44
|
background-color: currentColor;
|
|
45
|
+
// The icon's visible fill is the background color, which browsers strip
|
|
46
|
+
// when printing by default. Preserve it so masked icons remain visible.
|
|
47
|
+
-webkit-print-color-adjust: exact;
|
|
48
|
+
print-color-adjust: exact;
|
|
45
49
|
}
|