@abreen/tada 1.17.1 → 1.18.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.
Files changed (101) hide show
  1. package/README.md +37 -6
  2. package/bin/tada.ts +16 -7
  3. package/bin/validators.ts +33 -0
  4. package/build/branding.ts +41 -0
  5. package/build/build-manifest.ts +9 -4
  6. package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
  7. package/build/build-validation.ts +176 -0
  8. package/build/bundle.ts +75 -65
  9. package/build/config-loader.ts +3 -3
  10. package/build/custom-fonts.ts +8 -5
  11. package/build/generate-favicon.ts +7 -6
  12. package/build/generate-fonts.ts +8 -15
  13. package/build/generate-katex-assets.ts +19 -17
  14. package/build/generate-web-app-manifest.ts +3 -11
  15. package/build/lodash-template.ts +14 -0
  16. package/build/output-publication.ts +238 -0
  17. package/build/pagefind.ts +49 -21
  18. package/build/pdf-text.ts +41 -1
  19. package/build/pipeline.ts +95 -101
  20. package/build/site-assets.ts +46 -0
  21. package/build/site-build.ts +267 -0
  22. package/build/source-model.ts +223 -244
  23. package/build/source-records.ts +69 -81
  24. package/build/template-globals.ts +3 -0
  25. package/build/templates.ts +5 -2
  26. package/build/toc-plugin.ts +21 -21
  27. package/build/types.d.ts +38 -38
  28. package/build/util.ts +0 -4
  29. package/build/utils/code.ts +38 -101
  30. package/build/utils/final-html.ts +171 -47
  31. package/build/utils/literate-java.ts +11 -7
  32. package/build/utils/markdown-partials.ts +4 -3
  33. package/build/utils/markdown.ts +6 -17
  34. package/build/utils/paths.ts +3 -0
  35. package/build/utils/plain-text.ts +18 -0
  36. package/build/utils/render.ts +197 -154
  37. package/build/utils/shiki-highlighter.ts +11 -0
  38. package/build/utils/source-template.ts +2 -2
  39. package/build/utils/trace-core.ts +8 -83
  40. package/build/utils/trace.ts +38 -95
  41. package/build/validate-config-links.ts +5 -5
  42. package/build/watch/build-incremental.ts +46 -103
  43. package/build/watch/compiler.ts +158 -37
  44. package/build/watch/engine.ts +207 -176
  45. package/build/watch/index.ts +9 -2
  46. package/build/watch/planner.ts +88 -144
  47. package/build/watch/runtime.ts +21 -34
  48. package/build/watch/types.d.ts +32 -64
  49. package/package.json +4 -4
  50. package/schema/site.schema.json +18 -2
  51. package/src/_content.scss +447 -0
  52. package/src/_fonts.scss +90 -0
  53. package/src/_layout.scss +50 -35
  54. package/src/appearance-picker/{style.scss → _index.scss} +1 -1
  55. package/src/code/{style.scss → _index.scss} +61 -0
  56. package/src/header/{_base.scss → _index.scss} +98 -6
  57. package/src/index.ts +1 -17
  58. package/src/navigate/{style.scss → _index.scss} +20 -0
  59. package/src/navigate/eligible.ts +0 -40
  60. package/src/navigate/index.ts +38 -4
  61. package/src/navigate/lifecycle.ts +19 -2
  62. package/src/navigate/runtime.ts +20 -4
  63. package/src/page-update/index.ts +33 -20
  64. package/src/print/{style.scss → _index.scss} +3 -1
  65. package/src/search/index.ts +7 -0
  66. package/src/slides/{style.scss → _index.scss} +5 -0
  67. package/src/style.scss +24 -536
  68. package/src/toc/index.ts +2 -2
  69. package/templates/_author.html +2 -2
  70. package/templates/_download.html +1 -1
  71. package/templates/_footer.html +1 -1
  72. package/templates/_heading.html +3 -3
  73. package/templates/_nav.html +2 -2
  74. package/templates/_page-bottom.html +6 -0
  75. package/templates/_top.html +18 -14
  76. package/templates/code.html +22 -21
  77. package/templates/default.html +12 -11
  78. package/templates/literate.html +6 -5
  79. package/build/copy.ts +0 -82
  80. package/build/generate-content-assets.ts +0 -121
  81. package/build/utils/content-files.ts +0 -170
  82. package/build/watch/assets.ts +0 -103
  83. package/build/watch/build-full.ts +0 -97
  84. package/build/watch/build-helpers.ts +0 -66
  85. package/build/watch/build-result.ts +0 -19
  86. package/build/watch/fs-commit.ts +0 -218
  87. package/build/watch/mutations.ts +0 -73
  88. package/build/watch/snapshot.ts +0 -201
  89. package/build/watch/validation.ts +0 -57
  90. package/src/code.scss +0 -60
  91. package/src/critical.scss +0 -5
  92. package/src/header/style.scss +0 -91
  93. /package/src/{literate.scss → _literate.scss} +0 -0
  94. /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
  95. /package/src/anchor/{style.scss → _index.scss} +0 -0
  96. /package/src/page-update/{style.scss → _index.scss} +0 -0
  97. /package/src/question/{style.scss → _index.scss} +0 -0
  98. /package/src/search/{style.scss → _index.scss} +0 -0
  99. /package/src/timezone/{style.scss → _index.scss} +0 -0
  100. /package/src/toc/{style.scss → _index.scss} +0 -0
  101. /package/src/trace/{style.scss → _index.scss} +0 -0
@@ -1,3 +1,64 @@
1
+ @use '../mixins' as *;
2
+
3
+ body.code {
4
+ --code-gutter-width: 4rem;
5
+ }
6
+
7
+ .file-header,
8
+ .slides-header {
9
+ display: flex;
10
+ gap: var(--gap);
11
+ align-items: center;
12
+ justify-content: center;
13
+ padding: var(--gap);
14
+ margin-bottom: var(--gap);
15
+ border: 1px solid var(--fg2-color);
16
+ border-radius: var(--border-radius);
17
+
18
+ tt,
19
+ code {
20
+ padding: 0;
21
+ background: none;
22
+ }
23
+ }
24
+
25
+ .code-row {
26
+ display: grid;
27
+ grid-template-columns: var(--code-gutter-width) 1fr;
28
+ }
29
+
30
+ .line-number {
31
+ position: sticky;
32
+ left: 0;
33
+ z-index: 1;
34
+ display: block;
35
+ padding: 0 0.75em;
36
+ margin-right: var(--gap);
37
+ font-family: var(--mono-font);
38
+ font-size-adjust: var(--mono-font-size-adjust);
39
+ font-feature-settings: var(--mono-font-feature-settings);
40
+ color: var(--fg2-color);
41
+ text-align: right;
42
+ text-decoration: none;
43
+ background: var(--bg2-color);
44
+ border-right: 1px solid var(--fg2-color);
45
+
46
+ @include user-select(none);
47
+ }
48
+
49
+ a.line-number {
50
+ cursor: pointer;
51
+
52
+ &:hover {
53
+ color: var(--fg-color);
54
+ }
55
+
56
+ &:target {
57
+ color: var(--bg-color);
58
+ background: var(--fg2-color);
59
+ }
60
+ }
61
+
1
62
  body.code .file-header {
2
63
  h1 {
3
64
  padding: 0;
@@ -191,6 +191,16 @@ header summary .logo {
191
191
  background: var(--theme-color);
192
192
  }
193
193
 
194
+ header summary .logo-image {
195
+ flex: 0 0 auto;
196
+ width: auto;
197
+ max-width: 96px;
198
+ height: 24px;
199
+ margin: 0 8px 0 0;
200
+ pointer-events: none;
201
+ object-fit: contain;
202
+ }
203
+
194
204
  header summary .site-title {
195
205
  flex: 1 1 0;
196
206
  min-width: 0;
@@ -230,12 +240,6 @@ header .to-top-container {
230
240
  }
231
241
  }
232
242
 
233
- // Hide until the full stylesheet loads (prevents FOUC where the container
234
- // is visible as a block element before it gets position: absolute)
235
- header .results-container {
236
- display: none;
237
- }
238
-
239
243
  header .search-controls {
240
244
  position: relative;
241
245
  display: flex;
@@ -298,3 +302,91 @@ header.loading::after {
298
302
  background-size: 200% 100%;
299
303
  animation: shimmer 3s ease-in-out infinite;
300
304
  }
305
+
306
+ header nav {
307
+ display: flex;
308
+ flex-wrap: wrap;
309
+
310
+ @include user-select(none);
311
+
312
+ gap: var(--gap);
313
+ margin: var(--gap);
314
+ }
315
+
316
+ header nav div {
317
+ flex: 1 1 150px;
318
+ }
319
+
320
+ header nav ul {
321
+ padding: 0;
322
+ margin: 8px 0 0;
323
+ list-style-type: none;
324
+ }
325
+
326
+ header nav ul li {
327
+ margin: 0;
328
+ }
329
+
330
+ header nav a {
331
+ display: inline;
332
+ border: none;
333
+
334
+ @include subdued-link;
335
+ }
336
+
337
+ header nav a.external {
338
+ border: none;
339
+ }
340
+
341
+ @media only screen and (width >= 740px) {
342
+ header details nav {
343
+ min-height: 100px;
344
+ }
345
+ }
346
+
347
+ header nav div > p {
348
+ padding-bottom: 2px;
349
+ font-style: italic;
350
+ border-bottom: 1px solid var(--fg-color);
351
+
352
+ @include user-select(none);
353
+ }
354
+
355
+ :root[data-contrast-preference='high'] header:has(details[open]) {
356
+ background: var(--bg2-color);
357
+ }
358
+
359
+ @media (hover: hover) {
360
+ header:has(details[open]):hover {
361
+ background: var(--bg-color-translucent);
362
+ }
363
+
364
+ :root[data-contrast-preference='high'] header:hover {
365
+ background: var(--bg2-color);
366
+ }
367
+ }
368
+
369
+ header .results-container {
370
+ position: absolute;
371
+ top: var(--header-collapsed-height);
372
+ right: 0;
373
+ left: 0;
374
+ z-index: 1000;
375
+ box-sizing: border-box;
376
+ max-width: 640px;
377
+ padding: var(--gap);
378
+ margin: 0 auto;
379
+ pointer-events: none;
380
+ }
381
+
382
+ header ol {
383
+ flex: 1;
384
+ min-height: 0;
385
+ padding: 0;
386
+ overflow-y: auto;
387
+
388
+ .excerpt {
389
+ height: 5ex;
390
+ line-height: 2.5ex;
391
+ }
392
+ }
package/src/index.ts CHANGED
@@ -1,22 +1,6 @@
1
+ // The single Sass entry point; it loads every other stylesheet
1
2
  import './style.scss';
2
3
 
3
- import './material-symbols.scss';
4
- import './anchor/style.scss';
5
- import './code.scss';
6
- import './code/style.scss';
7
- import './literate.scss';
8
- import './header/style.scss';
9
- import './print/style.scss';
10
- import './question/style.scss';
11
- import './search/style.scss';
12
- import './slides/style.scss';
13
- import './timezone/style.scss';
14
- import './toc/style.scss';
15
- import './trace/style.scss';
16
- import './appearance-picker/style.scss';
17
- import './navigate/style.scss';
18
- import './page-update/style.scss';
19
-
20
4
  import mountSearch from './search';
21
5
  import mountHeader from './header';
22
6
  import mountTop from './top';
@@ -68,6 +68,26 @@ $movement-easing: cubic-bezier(0.2, 0, 0, 1);
68
68
  mix-blend-mode: normal;
69
69
  }
70
70
 
71
+ // The footer and appearance controls get their own stationary layer. Show only
72
+ // the incoming snapshot so disabled defaults and stale hover states cannot blend.
73
+ .page-bottom {
74
+ view-transition-name: page-bottom;
75
+ }
76
+
77
+ ::view-transition-group(page-bottom),
78
+ ::view-transition-old(page-bottom),
79
+ ::view-transition-new(page-bottom) {
80
+ animation: none;
81
+ }
82
+
83
+ ::view-transition-old(page-bottom) {
84
+ display: none;
85
+ }
86
+
87
+ ::view-transition-new(page-bottom) {
88
+ mix-blend-mode: normal;
89
+ }
90
+
71
91
  // Forward navigation: old content slides left, new content slides in from right
72
92
  .nav-forward::view-transition-old(root) {
73
93
  animation:
@@ -1,34 +1,3 @@
1
- const NON_HTML_EXTENSIONS = new Set([
2
- '.pdf',
3
- '.java',
4
- '.py',
5
- '.png',
6
- '.jpg',
7
- '.jpeg',
8
- '.gif',
9
- '.svg',
10
- '.zip',
11
- '.tar',
12
- '.gz',
13
- '.json',
14
- '.xml',
15
- '.css',
16
- '.js',
17
- '.ts',
18
- '.c',
19
- '.h',
20
- '.cpp',
21
- '.rb',
22
- '.go',
23
- '.rs',
24
- '.txt',
25
- '.csv',
26
- '.ico',
27
- '.woff2',
28
- '.mp4',
29
- '.mp3',
30
- ]);
31
-
32
1
  export function isEligibleLink(
33
2
  href: string,
34
3
  origin: string,
@@ -56,14 +25,5 @@ export function isEligibleLink(
56
25
  return false;
57
26
  }
58
27
 
59
- const path = url.pathname;
60
- const dotIndex = path.lastIndexOf('.');
61
- if (dotIndex !== -1) {
62
- const ext = path.slice(dotIndex).toLowerCase();
63
- if (NON_HTML_EXTENSIONS.has(ext)) {
64
- return false;
65
- }
66
- }
67
-
68
28
  return true;
69
29
  }
@@ -24,11 +24,17 @@ function shouldIgnoreClick(
24
24
  event: MouseEvent,
25
25
  anchor: HTMLAnchorElement,
26
26
  ): boolean {
27
- if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
27
+ if (
28
+ event.button !== 0 ||
29
+ event.ctrlKey ||
30
+ event.metaKey ||
31
+ event.shiftKey ||
32
+ event.altKey
33
+ ) {
28
34
  return true;
29
35
  }
30
36
 
31
- if (anchor.target || anchor.hasAttribute('download')) {
37
+ if (anchor.hasAttribute('target') || anchor.hasAttribute('download')) {
32
38
  return true;
33
39
  }
34
40
 
@@ -42,6 +48,7 @@ function shouldIgnoreClick(
42
48
 
43
49
  export default function mountNavigate(window: Window): () => void {
44
50
  initNavigation(window);
51
+ let clearingFragment = false;
45
52
 
46
53
  // Track scroll position on every scroll event. We keep the latest
47
54
  // scrollY per navIndex so that back/forward navigation can restore
@@ -88,6 +95,10 @@ export default function mountNavigate(window: Window): () => void {
88
95
  saveScrollPosition(window);
89
96
  if (window.location.hash) {
90
97
  const state = window.history.state;
98
+ // WebKit fires popstate synchronously while clearing the fragment.
99
+ // Ignore that internal navigation so it cannot restore the previous
100
+ // fragment-free entry's saved scroll position.
101
+ clearingFragment = true;
91
102
  globals.setLocationHash(window, '');
92
103
  // Native fragment clearing leaves a trailing #. Keep the authored
93
104
  // URL and the current SPA history state on the new entry.
@@ -99,6 +110,10 @@ export default function mountNavigate(window: Window): () => void {
99
110
  return;
100
111
  }
101
112
 
113
+ if (!anchor.hasAttribute('data-tada-page')) {
114
+ return;
115
+ }
116
+
102
117
  event.preventDefault();
103
118
 
104
119
  // Clear search and close header immediately, before the fetch
@@ -119,11 +134,30 @@ export default function mountNavigate(window: Window): () => void {
119
134
  }
120
135
 
121
136
  function handlePopState(event: PopStateEvent) {
137
+ if (clearingFragment) {
138
+ clearingFragment = false;
139
+ if (!window.location.hash) {
140
+ return;
141
+ }
142
+ }
143
+
122
144
  const newPath = window.location.pathname + window.location.search;
123
145
  if (newPath === getCurrentPath()) {
124
- const savedY = getSavedLocationScroll(newPath + window.location.hash);
146
+ const locationKey = newPath + window.location.hash;
147
+ const savedY = getSavedLocationScroll(locationKey);
125
148
  if (typeof savedY === 'number') {
126
- window.scrollTo({ top: savedY });
149
+ // Fragment traversal can apply its native target scroll after popstate.
150
+ // Restore in the next task so our saved position wins. Capture savedY
151
+ // now because the native scroll event can update the location map first.
152
+ window.setTimeout(() => {
153
+ const currentLocationKey =
154
+ window.location.pathname +
155
+ window.location.search +
156
+ window.location.hash;
157
+ if (currentLocationKey === locationKey) {
158
+ window.scrollTo({ top: savedY });
159
+ }
160
+ }, 0);
127
161
  return;
128
162
  }
129
163
 
@@ -21,15 +21,31 @@ const PER_PAGE_COMPONENTS: Record<
21
21
  trace: mountTrace,
22
22
  slides: mountSlides,
23
23
  print: mountPrint,
24
- appearancePicker: mountAppearancePicker,
25
24
  };
26
25
 
27
26
  let cleanups: (() => void)[] = [];
27
+ let appearancePickerMounted = false;
28
+
29
+ // Navigation calls this during the swap so the new transition snapshot shows
30
+ // enabled switches with the visitor's preferences, rather than build defaults.
31
+ export function mountAppearancePickerForPage(window: Window): void {
32
+ if (appearancePickerMounted) {
33
+ return;
34
+ }
35
+ appearancePickerMounted = true;
36
+ try {
37
+ cleanups.push(mountAppearancePicker(window));
38
+ } catch (err) {
39
+ if (__IS_DEV__) {
40
+ console.error('Failed to mount appearancePicker component:', String(err));
41
+ }
42
+ }
43
+ }
28
44
 
29
45
  export async function mountPerPageComponents(
30
46
  window: Window,
31
47
  ): Promise<() => void> {
32
- cleanups = [];
48
+ mountAppearancePickerForPage(window);
33
49
 
34
50
  const entries = Object.entries(PER_PAGE_COMPONENTS);
35
51
 
@@ -66,4 +82,5 @@ export function teardownPerPageComponents(): void {
66
82
  }
67
83
  }
68
84
  cleanups = [];
85
+ appearancePickerMounted = false;
69
86
  }
@@ -1,4 +1,9 @@
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';
3
8
 
4
9
  export const NAVIGATION_EVENT = 'tada:navigation';
@@ -93,12 +98,22 @@ export function closeHeaderDetails(document: Document): void {
93
98
  }
94
99
  }
95
100
 
96
- function dispatchNavigationEvent(window: Window): void {
101
+ export interface NavigationEventDetail {
102
+ path: string;
103
+ validators: ResponseValidators;
104
+ }
105
+
106
+ function dispatchNavigationEvent(
107
+ window: Window,
108
+ validators: ResponseValidators,
109
+ ): void {
97
110
  const CustomEventCtor = (
98
111
  window as unknown as { CustomEvent: typeof CustomEvent }
99
112
  ).CustomEvent;
100
113
  window.dispatchEvent(
101
- new CustomEventCtor(NAVIGATION_EVENT, { detail: { path: currentPath } }),
114
+ new CustomEventCtor(NAVIGATION_EVENT, {
115
+ detail: { path: currentPath, validators },
116
+ }),
102
117
  );
103
118
  }
104
119
 
@@ -288,6 +303,7 @@ export async function navigateToUrl(
288
303
  teardownPerPageComponents();
289
304
  swapContent(document, newDoc);
290
305
  updateHead(document, newDoc);
306
+ mountAppearancePickerForPage(window);
291
307
  currentPath = parsed.pathname + parsed.search;
292
308
 
293
309
  if (pushHistory) {
@@ -396,7 +412,7 @@ export async function navigateToUrl(
396
412
  }
397
413
 
398
414
  currentAbortController = null;
399
- dispatchNavigationEvent(window);
415
+ dispatchNavigationEvent(window, getResponseValidators(response));
400
416
  }
401
417
 
402
418
  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
+ }