@abreen/tada 1.17.1 → 1.18.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.
Files changed (111) hide show
  1. package/README.md +38 -7
  2. package/assets/material-symbols/chevron_right_24px.svg +1 -0
  3. package/bin/tada.ts +16 -7
  4. package/bin/validators.ts +33 -0
  5. package/build/branding.ts +41 -0
  6. package/build/build-manifest.ts +9 -4
  7. package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
  8. package/build/build-validation.ts +176 -0
  9. package/build/bundle.ts +75 -65
  10. package/build/config-loader.ts +3 -3
  11. package/build/custom-fonts.ts +8 -5
  12. package/build/generate-favicon.ts +7 -6
  13. package/build/generate-fonts.ts +8 -15
  14. package/build/generate-katex-assets.ts +19 -17
  15. package/build/generate-web-app-manifest.ts +3 -11
  16. package/build/lodash-template.ts +14 -0
  17. package/build/material-symbols.ts +7 -4
  18. package/build/output-publication.ts +238 -0
  19. package/build/pagefind.ts +49 -21
  20. package/build/pdf-text.ts +41 -1
  21. package/build/pipeline.ts +95 -101
  22. package/build/site-assets.ts +46 -0
  23. package/build/site-build.ts +267 -0
  24. package/build/source-model.ts +223 -244
  25. package/build/source-records.ts +69 -81
  26. package/build/template-globals.ts +3 -0
  27. package/build/templates.ts +5 -2
  28. package/build/toc-plugin.ts +21 -21
  29. package/build/types.d.ts +44 -38
  30. package/build/util.ts +0 -4
  31. package/build/utils/code.ts +38 -101
  32. package/build/utils/final-html.ts +171 -47
  33. package/build/utils/literate-java.ts +11 -7
  34. package/build/utils/markdown-partials.ts +4 -3
  35. package/build/utils/markdown.ts +6 -17
  36. package/build/utils/paths.ts +3 -0
  37. package/build/utils/plain-text.ts +18 -0
  38. package/build/utils/render.ts +227 -165
  39. package/build/utils/shiki-highlighter.ts +11 -0
  40. package/build/utils/source-template.ts +2 -2
  41. package/build/utils/trace-core.ts +8 -83
  42. package/build/utils/trace.ts +38 -95
  43. package/build/validate-config-links.ts +60 -20
  44. package/build/watch/build-incremental.ts +46 -103
  45. package/build/watch/compiler.ts +158 -37
  46. package/build/watch/engine.ts +207 -176
  47. package/build/watch/index.ts +9 -2
  48. package/build/watch/planner.ts +88 -144
  49. package/build/watch/runtime.ts +21 -34
  50. package/build/watch/types.d.ts +32 -64
  51. package/init/content/labs/00/VowelCounter.java.md +5 -2
  52. package/init/content/labs/00/index.md +3 -2
  53. package/init/content/labs/01/index.md +3 -2
  54. package/init/content/labs/02/index.md +3 -2
  55. package/init/content/lectures/01/index.md +3 -2
  56. package/init/content/lectures/02/index.md +3 -2
  57. package/package.json +4 -4
  58. package/schema/site.schema.json +18 -2
  59. package/src/_content.scss +463 -0
  60. package/src/_fonts.scss +90 -0
  61. package/src/_layout.scss +50 -35
  62. package/src/{material-symbols.scss → _material-symbols.scss} +7 -0
  63. package/src/appearance-picker/{style.scss → _index.scss} +1 -1
  64. package/src/code/{style.scss → _index.scss} +61 -0
  65. package/src/header/{_base.scss → _index.scss} +98 -6
  66. package/src/index.ts +1 -17
  67. package/src/navigate/{style.scss → _index.scss} +29 -3
  68. package/src/navigate/breadcrumbs.ts +129 -0
  69. package/src/navigate/eligible.ts +0 -40
  70. package/src/navigate/index.ts +38 -4
  71. package/src/navigate/lifecycle.ts +19 -2
  72. package/src/navigate/runtime.ts +57 -22
  73. package/src/page-update/index.ts +33 -20
  74. package/src/print/{style.scss → _index.scss} +3 -1
  75. package/src/search/index.ts +7 -0
  76. package/src/slides/{style.scss → _index.scss} +5 -0
  77. package/src/style.scss +24 -536
  78. package/src/toc/index.ts +2 -2
  79. package/templates/_author.html +2 -2
  80. package/templates/_download.html +1 -1
  81. package/templates/_footer.html +1 -1
  82. package/templates/_heading.html +17 -4
  83. package/templates/_nav.html +2 -2
  84. package/templates/_page-bottom.html +6 -0
  85. package/templates/_top.html +18 -14
  86. package/templates/code.html +22 -21
  87. package/templates/default.html +12 -11
  88. package/templates/literate.html +6 -5
  89. package/assets/material-symbols/south_east_20px.svg +0 -1
  90. package/build/copy.ts +0 -82
  91. package/build/generate-content-assets.ts +0 -121
  92. package/build/utils/content-files.ts +0 -170
  93. package/build/watch/assets.ts +0 -103
  94. package/build/watch/build-full.ts +0 -97
  95. package/build/watch/build-helpers.ts +0 -66
  96. package/build/watch/build-result.ts +0 -19
  97. package/build/watch/fs-commit.ts +0 -218
  98. package/build/watch/mutations.ts +0 -73
  99. package/build/watch/snapshot.ts +0 -201
  100. package/build/watch/validation.ts +0 -57
  101. package/src/code.scss +0 -60
  102. package/src/critical.scss +0 -5
  103. package/src/header/style.scss +0 -91
  104. /package/src/{literate.scss → _literate.scss} +0 -0
  105. /package/src/anchor/{style.scss → _index.scss} +0 -0
  106. /package/src/page-update/{style.scss → _index.scss} +0 -0
  107. /package/src/question/{style.scss → _index.scss} +0 -0
  108. /package/src/search/{style.scss → _index.scss} +0 -0
  109. /package/src/timezone/{style.scss → _index.scss} +0 -0
  110. /package/src/toc/{style.scss → _index.scss} +0 -0
  111. /package/src/trace/{style.scss → _index.scss} +0 -0
@@ -1,11 +1,26 @@
1
- import { mountPerPageComponents, teardownPerPageComponents } from './lifecycle';
1
+ import { getResponseValidators, type ResponseValidators } from '../validators';
2
+ import {
3
+ mountAppearancePickerForPage,
4
+ mountPerPageComponents,
5
+ teardownPerPageComponents,
6
+ } from './lifecycle';
2
7
  import { globals } from '../globals';
8
+ import {
9
+ cleanupBreadcrumbTransitionNames,
10
+ getBreadcrumbItems,
11
+ prepareBreadcrumbTransitionStyles,
12
+ setBreadcrumbTransitionNames,
13
+ sharedBreadcrumbCount,
14
+ } from './breadcrumbs';
3
15
 
4
16
  export const NAVIGATION_EVENT = 'tada:navigation';
5
17
 
6
18
  let currentAbortController: AbortController | null = null;
7
19
  let historyIndex = 0;
8
20
  let currentPath = '';
21
+ // Unlike the browser URL (which already changes on popstate), this tracks the
22
+ // page whose content is currently displayed, for relative breadcrumb identity.
23
+ let renderedPageUrl = '';
9
24
 
10
25
  const scrollByIndex = new Map<number, number>();
11
26
  const scrollByLocation = new Map<string, number>();
@@ -93,12 +108,22 @@ export function closeHeaderDetails(document: Document): void {
93
108
  }
94
109
  }
95
110
 
96
- function dispatchNavigationEvent(window: Window): void {
111
+ export interface NavigationEventDetail {
112
+ path: string;
113
+ validators: ResponseValidators;
114
+ }
115
+
116
+ function dispatchNavigationEvent(
117
+ window: Window,
118
+ validators: ResponseValidators,
119
+ ): void {
97
120
  const CustomEventCtor = (
98
121
  window as unknown as { CustomEvent: typeof CustomEvent }
99
122
  ).CustomEvent;
100
123
  window.dispatchEvent(
101
- new CustomEventCtor(NAVIGATION_EVENT, { detail: { path: currentPath } }),
124
+ new CustomEventCtor(NAVIGATION_EVENT, {
125
+ detail: { path: currentPath, validators },
126
+ }),
102
127
  );
103
128
  }
104
129
 
@@ -109,9 +134,6 @@ function cleanupViewTransitionNames(document: Document): void {
109
134
  const newInfo = document.querySelector(
110
135
  '.title-and-info .info',
111
136
  ) as HTMLElement | null;
112
- const newBreadcrumb = document.querySelector(
113
- '.title-and-info a.breadcrumb',
114
- ) as HTMLElement | null;
115
137
 
116
138
  if (newH1) {
117
139
  newH1.style.viewTransitionName = '';
@@ -119,9 +141,7 @@ function cleanupViewTransitionNames(document: Document): void {
119
141
  if (newInfo) {
120
142
  newInfo.style.viewTransitionName = '';
121
143
  }
122
- if (newBreadcrumb) {
123
- newBreadcrumb.style.viewTransitionName = '';
124
- }
144
+ cleanupBreadcrumbTransitionNames(document);
125
145
  }
126
146
 
127
147
  function getLocationKey(window: Window): string {
@@ -157,6 +177,7 @@ export function initNavigation(window: Window): void {
157
177
  scrollByLocation.clear();
158
178
  window.history.scrollRestoration = 'manual';
159
179
  currentPath = window.location.pathname + window.location.search;
180
+ renderedPageUrl = window.location.href;
160
181
  scrollByIndex.set(historyIndex, window.scrollY);
161
182
  scrollByLocation.set(getLocationKey(window), window.scrollY);
162
183
  }
@@ -288,7 +309,9 @@ export async function navigateToUrl(
288
309
  teardownPerPageComponents();
289
310
  swapContent(document, newDoc);
290
311
  updateHead(document, newDoc);
312
+ mountAppearancePickerForPage(window);
291
313
  currentPath = parsed.pathname + parsed.search;
314
+ renderedPageUrl = parsed.href;
292
315
 
293
316
  if (pushHistory) {
294
317
  historyIndex++;
@@ -320,6 +343,18 @@ export async function navigateToUrl(
320
343
  !prefersReducedMotion(window) &&
321
344
  typeof transitionDoc.startViewTransition === 'function'
322
345
  ) {
346
+ const oldBreadcrumbItems = getBreadcrumbItems(document, renderedPageUrl);
347
+ const newBreadcrumbItems = getBreadcrumbItems(newDoc, parsed.href);
348
+ const sharedCount = sharedBreadcrumbCount(
349
+ oldBreadcrumbItems,
350
+ newBreadcrumbItems,
351
+ );
352
+ prepareBreadcrumbTransitionStyles(
353
+ document,
354
+ oldBreadcrumbItems.length,
355
+ newBreadcrumbItems.length,
356
+ sharedCount,
357
+ );
323
358
  const titleEl = document.querySelector('.title-and-info');
324
359
  const headerHeight =
325
360
  document.querySelector('header')?.getBoundingClientRect().height ?? 0;
@@ -329,18 +364,18 @@ export async function navigateToUrl(
329
364
  if (titleVisible) {
330
365
  const h1 = titleEl.querySelector('h1') as HTMLElement | null;
331
366
  const info = titleEl.querySelector('.info') as HTMLElement | null;
332
- const breadcrumb = titleEl.querySelector(
333
- 'a.breadcrumb',
334
- ) as HTMLElement | null;
335
367
  if (h1) {
336
368
  h1.style.viewTransitionName = 'page-title';
337
369
  }
338
370
  if (info) {
339
371
  info.style.viewTransitionName = 'page-info';
340
372
  }
341
- if (breadcrumb) {
342
- breadcrumb.style.viewTransitionName = 'page-breadcrumb';
343
- }
373
+ setBreadcrumbTransitionNames(
374
+ document,
375
+ renderedPageUrl,
376
+ sharedCount,
377
+ 'leave',
378
+ );
344
379
  }
345
380
 
346
381
  document.documentElement.classList.add(
@@ -362,18 +397,18 @@ export async function navigateToUrl(
362
397
  if (newTitleVisible && newTitleEl) {
363
398
  const h1 = newTitleEl.querySelector('h1') as HTMLElement | null;
364
399
  const info = newTitleEl.querySelector('.info') as HTMLElement | null;
365
- const breadcrumb = newTitleEl.querySelector(
366
- 'a.breadcrumb',
367
- ) as HTMLElement | null;
368
400
  if (h1) {
369
401
  h1.style.viewTransitionName = 'page-title';
370
402
  }
371
403
  if (info) {
372
404
  info.style.viewTransitionName = 'page-info';
373
405
  }
374
- if (breadcrumb) {
375
- breadcrumb.style.viewTransitionName = 'page-breadcrumb';
376
- }
406
+ setBreadcrumbTransitionNames(
407
+ document,
408
+ renderedPageUrl,
409
+ sharedCount,
410
+ 'enter',
411
+ );
377
412
  }
378
413
  });
379
414
 
@@ -396,7 +431,7 @@ export async function navigateToUrl(
396
431
  }
397
432
 
398
433
  currentAbortController = null;
399
- dispatchNavigationEvent(window);
434
+ dispatchNavigationEvent(window, getResponseValidators(response));
400
435
  }
401
436
 
402
437
  export async function refreshCurrentPage(window: Window): Promise<void> {
@@ -72,7 +72,9 @@ export function mountPageUpdate(
72
72
  const { container, toast, reloadButton, dismissButton } =
73
73
  createToast(document);
74
74
 
75
- let currentPath = window.location.pathname;
75
+ let currentPath = window.location.pathname + window.location.search;
76
+ let navigationGeneration = 0;
77
+ let disposed = false;
76
78
  let baseline = EMPTY_RESPONSE_VALIDATORS;
77
79
  let dismissedValidatorKey: string | null = null;
78
80
  let nextValidator: ResponseValidators | null = null;
@@ -98,7 +100,7 @@ export function mountPageUpdate(
98
100
 
99
101
  function scheduleNextCheck() {
100
102
  clearTimer();
101
- if (globals.isDocumentHidden(document)) {
103
+ if (disposed || globals.isDocumentHidden(document)) {
102
104
  return;
103
105
  }
104
106
  timer = window.setTimeout(() => {
@@ -145,11 +147,16 @@ export function mountPageUpdate(
145
147
  }
146
148
 
147
149
  const requestedPath = currentPath;
148
- let shouldRerun = false;
150
+ const requestedGeneration = navigationGeneration;
149
151
  checkInFlight = true;
150
152
  try {
151
153
  const validators = await fetchValidators(requestedPath);
152
- if (!validators || requestedPath !== currentPath) {
154
+ if (
155
+ !validators ||
156
+ disposed ||
157
+ requestedGeneration !== navigationGeneration ||
158
+ requestedPath !== currentPath
159
+ ) {
153
160
  return;
154
161
  }
155
162
 
@@ -175,30 +182,32 @@ export function mountPageUpdate(
175
182
  // best-effort
176
183
  } finally {
177
184
  checkInFlight = false;
178
- if (rerunAfterCurrentCheck && !globals.isDocumentHidden(document)) {
185
+ if (
186
+ rerunAfterCurrentCheck &&
187
+ !disposed &&
188
+ !globals.isDocumentHidden(document)
189
+ ) {
179
190
  rerunAfterCurrentCheck = false;
180
- shouldRerun = true;
181
- } else {
191
+ void checkForUpdate();
192
+ } else if (requestedGeneration === navigationGeneration) {
182
193
  scheduleNextCheck();
183
194
  }
184
195
  }
185
-
186
- if (shouldRerun) {
187
- void checkForUpdate();
188
- }
189
196
  }
190
197
 
191
- function resetState() {
192
- currentPath = window.location.pathname;
193
- baseline = EMPTY_RESPONSE_VALIDATORS;
198
+ function resetState(
199
+ validators: ResponseValidators = EMPTY_RESPONSE_VALIDATORS,
200
+ ) {
201
+ navigationGeneration++;
202
+ rerunAfterCurrentCheck = false;
203
+ currentPath = window.location.pathname + window.location.search;
204
+ baseline = validators;
194
205
  dismissedValidatorKey = null;
195
206
  nextValidator = null;
196
- hasBaseline = false;
207
+ hasBaseline = hasUsableResponseValidators(validators);
197
208
  hideToast();
198
209
  clearTimer();
199
- if (!globals.isDocumentHidden(document)) {
200
- void checkForUpdate();
201
- }
210
+ scheduleNextCheck();
202
211
  }
203
212
 
204
213
  function handleVisibilityChange() {
@@ -216,13 +225,16 @@ export function mountPageUpdate(
216
225
  void checkForUpdate();
217
226
  }
218
227
 
219
- function handleNavigation() {
228
+ function handleNavigation(event: Event) {
220
229
  if (__IS_DEV__) {
221
230
  console.log(
222
231
  `[page-update] navigation path=${window.location.pathname} visibility=${document.visibilityState} focus=${document.hasFocus()}`,
223
232
  );
224
233
  }
225
- resetState();
234
+ resetState(
235
+ (event as CustomEvent<{ validators?: ResponseValidators }>).detail
236
+ ?.validators,
237
+ );
226
238
  }
227
239
 
228
240
  function handleDismiss() {
@@ -272,6 +284,7 @@ export function mountPageUpdate(
272
284
  }
273
285
 
274
286
  return () => {
287
+ disposed = true;
275
288
  clearTimer();
276
289
  document.removeEventListener('visibilitychange', handleVisibilityChange);
277
290
  window.removeEventListener('focus', handleWindowFocus);
@@ -17,11 +17,13 @@
17
17
  }
18
18
 
19
19
  header,
20
- footer {
20
+ footer,
21
+ .page-bottom {
21
22
  display: none !important;
22
23
  }
23
24
 
24
25
  body > .container {
26
+ min-height: 0;
25
27
  margin-top: 0;
26
28
  }
27
29
 
@@ -141,6 +141,10 @@ function render(
141
141
  a.id = `result-${i}`;
142
142
  a.className = 'result';
143
143
  a.href = result.url;
144
+ const isGeneratedPage = Boolean(result.template);
145
+ if (isGeneratedPage) {
146
+ a.setAttribute('data-tada-page', '');
147
+ }
144
148
  a.tabIndex = 0;
145
149
 
146
150
  const titleEl = doc.createElement('div');
@@ -177,6 +181,9 @@ function render(
177
181
  for (const sub of subsToShow) {
178
182
  const subA = doc.createElement('a');
179
183
  subA.href = sub.url;
184
+ if (isGeneratedPage) {
185
+ subA.setAttribute('data-tada-page', '');
186
+ }
180
187
  subA.className = 'sub-result';
181
188
 
182
189
  const subTitle = doc.createElement('div');
@@ -93,6 +93,7 @@ body.is-presenting [data-slides-annotation-layer] {
93
93
  body.is-presenting header,
94
94
  body.is-presenting .title-and-info,
95
95
  body.is-presenting nav.toc,
96
+ body.is-presenting .page-bottom,
96
97
  body.is-presenting footer,
97
98
  body.is-presenting .appearance-pickers,
98
99
  body.is-presenting .slides-header,
@@ -177,3 +178,7 @@ body.is-presenting .slide:has(> .trace-widget) > .trace-widget > .trace-body {
177
178
  height: 100%;
178
179
  min-height: 0;
179
180
  }
181
+
182
+ body.is-presenting > .container {
183
+ min-height: 0;
184
+ }