@abreen/tada 1.11.3 → 1.13.4
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/utils/trace-core.ts +3 -7
- 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/_base.scss +67 -0
- package/src/anchor/README.md +9 -0
- package/src/anchor/index.ts +115 -16
- package/src/anchor/style.scss +70 -10
- 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/print/style.scss +0 -4
- package/src/search/index.ts +53 -25
- package/src/slides/index.ts +481 -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 +116 -14
- 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 +4 -3
- package/templates/_top.html +14 -14
- package/templates/default.html +9 -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/src/navigate/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
setCurrentPath,
|
|
13
13
|
setHistoryIndex,
|
|
14
14
|
} from './runtime';
|
|
15
|
+
import { globals } from '../globals';
|
|
15
16
|
|
|
16
17
|
function findAnchor(event: MouseEvent): HTMLAnchorElement | null {
|
|
17
18
|
const target = event.target as HTMLElement | null;
|
|
@@ -77,11 +78,7 @@ export default function mountNavigate(window: Window): () => void {
|
|
|
77
78
|
clearSearch(window.document);
|
|
78
79
|
closeHeaderDetails(window.document);
|
|
79
80
|
if (url.hash) {
|
|
80
|
-
|
|
81
|
-
// updates :target, fires hashchange (which the TOC listens for),
|
|
82
|
-
// creates a new history entry, and scrolls to the element.
|
|
83
|
-
// pushState would do none of those things.
|
|
84
|
-
window.location.hash = url.hash.slice(1);
|
|
81
|
+
globals.setLocationHash(window, url.hash);
|
|
85
82
|
}
|
|
86
83
|
return;
|
|
87
84
|
}
|
|
@@ -5,6 +5,7 @@ import mountTimeZone from '../timezone';
|
|
|
5
5
|
import mountCode from '../code';
|
|
6
6
|
import mountTrace from '../trace';
|
|
7
7
|
import mountPrint from '../print';
|
|
8
|
+
import mountSlides from '../slides';
|
|
8
9
|
import { scheduleTask } from '../util';
|
|
9
10
|
|
|
10
11
|
const PER_PAGE_COMPONENTS: Record<
|
|
@@ -17,6 +18,7 @@ const PER_PAGE_COMPONENTS: Record<
|
|
|
17
18
|
timeZone: mountTimeZone,
|
|
18
19
|
code: mountCode,
|
|
19
20
|
trace: mountTrace,
|
|
21
|
+
slides: mountSlides,
|
|
20
22
|
print: mountPrint,
|
|
21
23
|
};
|
|
22
24
|
|
|
@@ -39,7 +41,7 @@ export async function mountPerPageComponents(
|
|
|
39
41
|
}
|
|
40
42
|
resolve();
|
|
41
43
|
} catch (err) {
|
|
42
|
-
if (
|
|
44
|
+
if (__IS_DEV__) {
|
|
43
45
|
console.error(`Failed to mount ${name} component:`, String(err));
|
|
44
46
|
}
|
|
45
47
|
reject();
|
package/src/navigate/runtime.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mountPerPageComponents, teardownPerPageComponents } from './lifecycle';
|
|
2
|
+
import { globals } from '../globals';
|
|
2
3
|
|
|
3
4
|
export const NAVIGATION_EVENT = 'tada:navigation';
|
|
4
5
|
|
|
@@ -196,19 +197,19 @@ export async function navigateToUrl(
|
|
|
196
197
|
|
|
197
198
|
let response: Response;
|
|
198
199
|
try {
|
|
199
|
-
response = await fetch(url, { signal: controller.signal });
|
|
200
|
+
response = await globals.fetch(url, { signal: controller.signal });
|
|
200
201
|
} catch (err: unknown) {
|
|
201
202
|
header?.classList.remove('loading');
|
|
202
203
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
203
204
|
return;
|
|
204
205
|
}
|
|
205
|
-
window
|
|
206
|
+
globals.setLocationHref(window, url);
|
|
206
207
|
return;
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
if (!response.ok) {
|
|
210
211
|
header?.classList.remove('loading');
|
|
211
|
-
window
|
|
212
|
+
globals.setLocationHref(window, url);
|
|
212
213
|
return;
|
|
213
214
|
}
|
|
214
215
|
|
|
@@ -226,7 +227,7 @@ export async function navigateToUrl(
|
|
|
226
227
|
.querySelector('meta[name="generator"]')
|
|
227
228
|
?.getAttribute('content');
|
|
228
229
|
if (!currentGenerator || !newGenerator || newGenerator !== currentGenerator) {
|
|
229
|
-
window
|
|
230
|
+
globals.setLocationHref(window, url);
|
|
230
231
|
return;
|
|
231
232
|
}
|
|
232
233
|
|
|
@@ -246,7 +247,7 @@ export async function navigateToUrl(
|
|
|
246
247
|
|
|
247
248
|
if (typeof scrollTarget === 'string') {
|
|
248
249
|
const urlWithHash = parsed.pathname + parsed.search + '#' + scrollTarget;
|
|
249
|
-
|
|
250
|
+
globals.replaceLocation(window, urlWithHash);
|
|
250
251
|
window.history.replaceState({ navIndex: historyIndex }, '', urlWithHash);
|
|
251
252
|
} else if (typeof scrollTarget === 'number') {
|
|
252
253
|
window.scrollTo({ top: scrollTarget });
|
package/src/page-update/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ResponseValidators,
|
|
8
8
|
} from '../validators';
|
|
9
9
|
import { NAVIGATION_EVENT, refreshCurrentPage } from '../navigate/runtime';
|
|
10
|
+
import { globals } from '../globals';
|
|
10
11
|
|
|
11
12
|
const POLL_INTERVAL_MS = 600_000;
|
|
12
13
|
|
|
@@ -96,7 +97,7 @@ export function mountPageUpdate(
|
|
|
96
97
|
|
|
97
98
|
function scheduleNextCheck() {
|
|
98
99
|
clearTimer();
|
|
99
|
-
if (document
|
|
100
|
+
if (globals.isDocumentHidden(document)) {
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
103
|
timer = window.setTimeout(() => {
|
|
@@ -107,17 +108,20 @@ export function mountPageUpdate(
|
|
|
107
108
|
async function fetchValidators(
|
|
108
109
|
path: string,
|
|
109
110
|
): Promise<ResponseValidators | null> {
|
|
110
|
-
if (
|
|
111
|
+
if (__IS_DEV__) {
|
|
111
112
|
console.log(
|
|
112
|
-
`[page-update] HEAD ${path} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
113
|
+
`[page-update] HEAD ${path} hidden=${globals.isDocumentHidden(document)} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
113
114
|
);
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
const res = await fetch(path, {
|
|
117
|
+
const res = await globals.fetch(path, {
|
|
118
|
+
method: 'HEAD',
|
|
119
|
+
cache: 'no-cache',
|
|
120
|
+
});
|
|
117
121
|
|
|
118
|
-
if (
|
|
122
|
+
if (__IS_DEV__) {
|
|
119
123
|
console.log(
|
|
120
|
-
`[page-update] HEAD ${path} -> ${res.status} etag=${res.headers.get('ETag')} last-modified=${res.headers.get('Last-Modified')} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
124
|
+
`[page-update] HEAD ${path} -> ${res.status} etag=${res.headers.get('ETag')} last-modified=${res.headers.get('Last-Modified')} hidden=${globals.isDocumentHidden(document)} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
121
125
|
);
|
|
122
126
|
}
|
|
123
127
|
|
|
@@ -170,7 +174,7 @@ export function mountPageUpdate(
|
|
|
170
174
|
// best-effort
|
|
171
175
|
} finally {
|
|
172
176
|
checkInFlight = false;
|
|
173
|
-
if (rerunAfterCurrentCheck && !document
|
|
177
|
+
if (rerunAfterCurrentCheck && !globals.isDocumentHidden(document)) {
|
|
174
178
|
rerunAfterCurrentCheck = false;
|
|
175
179
|
shouldRerun = true;
|
|
176
180
|
} else {
|
|
@@ -191,20 +195,20 @@ export function mountPageUpdate(
|
|
|
191
195
|
hasBaseline = false;
|
|
192
196
|
hideToast();
|
|
193
197
|
clearTimer();
|
|
194
|
-
if (!document
|
|
198
|
+
if (!globals.isDocumentHidden(document)) {
|
|
195
199
|
void checkForUpdate();
|
|
196
200
|
}
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
function handleVisibilityChange() {
|
|
200
|
-
if (
|
|
204
|
+
if (__IS_DEV__) {
|
|
201
205
|
console.log(
|
|
202
|
-
`[page-update] visibilitychange hidden=${document
|
|
206
|
+
`[page-update] visibilitychange hidden=${globals.isDocumentHidden(document)} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
203
207
|
);
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
clearTimer();
|
|
207
|
-
if (document
|
|
211
|
+
if (globals.isDocumentHidden(document)) {
|
|
208
212
|
return;
|
|
209
213
|
}
|
|
210
214
|
|
|
@@ -212,7 +216,7 @@ export function mountPageUpdate(
|
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
function handleNavigation() {
|
|
215
|
-
if (
|
|
219
|
+
if (__IS_DEV__) {
|
|
216
220
|
console.log(
|
|
217
221
|
`[page-update] navigation path=${window.location.pathname} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
218
222
|
);
|
|
@@ -227,7 +231,7 @@ export function mountPageUpdate(
|
|
|
227
231
|
}
|
|
228
232
|
|
|
229
233
|
function handleWindowFocus() {
|
|
230
|
-
if (
|
|
234
|
+
if (__IS_DEV__) {
|
|
231
235
|
console.log(
|
|
232
236
|
`[page-update] window focus visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
233
237
|
);
|
|
@@ -235,7 +239,7 @@ export function mountPageUpdate(
|
|
|
235
239
|
}
|
|
236
240
|
|
|
237
241
|
function handleWindowBlur() {
|
|
238
|
-
if (
|
|
242
|
+
if (__IS_DEV__) {
|
|
239
243
|
console.log(
|
|
240
244
|
`[page-update] window blur visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
241
245
|
);
|
|
@@ -253,8 +257,8 @@ export function mountPageUpdate(
|
|
|
253
257
|
window.addEventListener('blur', handleWindowBlur);
|
|
254
258
|
window.addEventListener(NAVIGATION_EVENT, handleNavigation);
|
|
255
259
|
|
|
256
|
-
if (!document
|
|
257
|
-
if (
|
|
260
|
+
if (!globals.isDocumentHidden(document)) {
|
|
261
|
+
if (__IS_DEV__) {
|
|
258
262
|
console.log(
|
|
259
263
|
`[page-update] mount path=${currentPath} interval=${pollIntervalMs} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
|
|
260
264
|
);
|
package/src/print/style.scss
CHANGED
package/src/search/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
hasResponseValidatorsChanged,
|
|
7
7
|
type ResponseValidators,
|
|
8
8
|
} from '../validators';
|
|
9
|
+
import { globals } from '../globals';
|
|
9
10
|
|
|
10
11
|
const MAX_RESULTS = 24;
|
|
11
12
|
|
|
@@ -39,6 +40,11 @@ interface Pagefind {
|
|
|
39
40
|
|
|
40
41
|
let pagefind: Pagefind | null = null;
|
|
41
42
|
|
|
43
|
+
// Unit tests need to clear the module-scoped Pagefind instance between cases.
|
|
44
|
+
export function resetPagefindForTest() {
|
|
45
|
+
pagefind = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
type SubResult = { title: string; url: string; excerpt: string };
|
|
43
49
|
|
|
44
50
|
type State = {
|
|
@@ -48,18 +54,18 @@ type State = {
|
|
|
48
54
|
totalResults: number;
|
|
49
55
|
};
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
type SearchState = Pick<State, 'results' | 'totalResults'> | null;
|
|
58
|
+
|
|
59
|
+
async function doSearch(query: string, window: Window): Promise<SearchState> {
|
|
52
60
|
if (pagefind == null) {
|
|
53
|
-
return;
|
|
61
|
+
return null;
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
if (!
|
|
57
|
-
|
|
58
|
-
state.totalResults = 0;
|
|
59
|
-
return;
|
|
64
|
+
if (!query) {
|
|
65
|
+
return { results: [], totalResults: 0 };
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
const search = await pagefind.search(
|
|
68
|
+
const search = await pagefind.search(query);
|
|
63
69
|
const slice = search.results.slice(0, MAX_RESULTS);
|
|
64
70
|
const data = await Promise.all(slice.map(r => r.data()));
|
|
65
71
|
|
|
@@ -95,8 +101,7 @@ async function doSearch(state: State) {
|
|
|
95
101
|
});
|
|
96
102
|
|
|
97
103
|
const grouped = groupPdfResults(results);
|
|
98
|
-
|
|
99
|
-
state.results = grouped;
|
|
104
|
+
return { totalResults: grouped.length, results: grouped };
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
function applyHighlight(
|
|
@@ -295,15 +300,16 @@ export default (window: Window) => {
|
|
|
295
300
|
return;
|
|
296
301
|
}
|
|
297
302
|
|
|
298
|
-
pagefind = (await
|
|
299
|
-
applyBasePath('/pagefind/pagefind.js')
|
|
303
|
+
pagefind = (await globals.importModule(
|
|
304
|
+
applyBasePath('/pagefind/pagefind.js'),
|
|
300
305
|
)) as Pagefind;
|
|
301
306
|
|
|
302
307
|
await pagefind.init();
|
|
303
308
|
|
|
304
|
-
const res = await fetch(
|
|
305
|
-
|
|
306
|
-
|
|
309
|
+
const res = await globals.fetch(
|
|
310
|
+
applyBasePath('/pagefind/pagefind-entry.json'),
|
|
311
|
+
{ cache: 'no-cache' },
|
|
312
|
+
);
|
|
307
313
|
|
|
308
314
|
if (res.ok) {
|
|
309
315
|
entryValidators = getResponseValidators(res);
|
|
@@ -311,24 +317,26 @@ export default (window: Window) => {
|
|
|
311
317
|
}
|
|
312
318
|
|
|
313
319
|
async function checkForIndexUpdate() {
|
|
314
|
-
if (indexCheckInFlight ||
|
|
320
|
+
if (indexCheckInFlight || globals.now() - lastIndexCheck < 3000) {
|
|
315
321
|
return;
|
|
316
322
|
}
|
|
317
|
-
lastIndexCheck =
|
|
323
|
+
lastIndexCheck = globals.now();
|
|
318
324
|
indexCheckInFlight = true;
|
|
319
325
|
try {
|
|
320
|
-
const res = await fetch(
|
|
321
|
-
|
|
322
|
-
cache: 'no-cache',
|
|
323
|
-
|
|
326
|
+
const res = await globals.fetch(
|
|
327
|
+
applyBasePath('/pagefind/pagefind-entry.json'),
|
|
328
|
+
{ method: 'HEAD', cache: 'no-cache' },
|
|
329
|
+
);
|
|
324
330
|
if (!res.ok) {
|
|
325
331
|
return;
|
|
326
332
|
}
|
|
327
333
|
const newValidators = getResponseValidators(res);
|
|
328
334
|
if (hasResponseValidatorsChanged(entryValidators, newValidators)) {
|
|
335
|
+
invalidateUpdates();
|
|
329
336
|
pagefind = null;
|
|
330
337
|
await loadPagefind();
|
|
331
|
-
|
|
338
|
+
const updateId = ++latestUpdateId;
|
|
339
|
+
await update(updateId);
|
|
332
340
|
}
|
|
333
341
|
} catch {
|
|
334
342
|
// best-effort
|
|
@@ -348,17 +356,36 @@ export default (window: Window) => {
|
|
|
348
356
|
totalResults: 0,
|
|
349
357
|
};
|
|
350
358
|
|
|
351
|
-
|
|
359
|
+
let latestUpdateId = 0;
|
|
360
|
+
|
|
361
|
+
async function update(updateId: number) {
|
|
352
362
|
if (state.showResults) {
|
|
353
363
|
render(input!, resultsContainer, state, true);
|
|
354
364
|
}
|
|
355
|
-
|
|
365
|
+
const query = state.value;
|
|
366
|
+
const nextState = await doSearch(query, window);
|
|
367
|
+
if (updateId !== latestUpdateId) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (nextState) {
|
|
371
|
+
state.results = nextState.results;
|
|
372
|
+
state.totalResults = nextState.totalResults;
|
|
373
|
+
}
|
|
356
374
|
if (!state.showResults) {
|
|
357
375
|
return;
|
|
358
376
|
}
|
|
359
377
|
render(input!, resultsContainer, state, false);
|
|
360
378
|
}
|
|
361
379
|
|
|
380
|
+
function queueUpdate() {
|
|
381
|
+
const updateId = ++latestUpdateId;
|
|
382
|
+
update(updateId).catch(() => {});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function invalidateUpdates() {
|
|
386
|
+
latestUpdateId += 1;
|
|
387
|
+
}
|
|
388
|
+
|
|
362
389
|
function hide() {
|
|
363
390
|
if (!state.showResults) {
|
|
364
391
|
return;
|
|
@@ -374,13 +401,14 @@ export default (window: Window) => {
|
|
|
374
401
|
}
|
|
375
402
|
state.value = value;
|
|
376
403
|
if (value.length === 0) {
|
|
404
|
+
invalidateUpdates();
|
|
377
405
|
hide();
|
|
378
406
|
state.results = [];
|
|
379
407
|
state.totalResults = 0;
|
|
380
408
|
return;
|
|
381
409
|
}
|
|
382
410
|
state.showResults = true;
|
|
383
|
-
|
|
411
|
+
queueUpdate();
|
|
384
412
|
}
|
|
385
413
|
|
|
386
414
|
function handleWindowPointerMove() {
|
|
@@ -397,7 +425,7 @@ export default (window: Window) => {
|
|
|
397
425
|
checkForIndexUpdate().catch(() => {});
|
|
398
426
|
if (!state.showResults) {
|
|
399
427
|
state.showResults = true;
|
|
400
|
-
|
|
428
|
+
queueUpdate();
|
|
401
429
|
}
|
|
402
430
|
}
|
|
403
431
|
|