@abreen/tada 1.18.0 → 1.18.2
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 +1 -1
- package/assets/material-symbols/chevron_right_24px.svg +1 -0
- package/build/material-symbols.ts +7 -4
- package/build/types.d.ts +6 -0
- package/build/utils/render.ts +38 -19
- package/build/validate-config-links.ts +55 -15
- package/init/content/labs/00/VowelCounter.java.md +6 -3
- package/init/content/labs/00/index.md +3 -2
- package/init/content/labs/01/index.md +3 -2
- package/init/content/labs/02/index.md +3 -2
- package/init/content/lectures/01/index.md +3 -2
- package/init/content/lectures/02/index.md +3 -2
- package/package.json +1 -1
- package/src/_content.scss +26 -10
- package/src/_layout.scss +5 -3
- package/src/_material-symbols.scss +7 -0
- package/src/navigate/_index.scss +30 -6
- package/src/navigate/runtime.ts +23 -46
- package/templates/_heading.html +15 -2
- package/templates/_theme.scss +4 -2
- package/assets/material-symbols/south_east_20px.svg +0 -1
package/README.md
CHANGED
|
@@ -383,7 +383,7 @@ list of variables parsed using the [`front-matter`][front-matter] library).
|
|
|
383
383
|
| `description` | Meta description for the page |
|
|
384
384
|
| `toc` | Set to `true` to show a table of contents |
|
|
385
385
|
| `slides` | Set to `true` on Markdown pages to treat top-level `---` as slide separators and add presentation controls |
|
|
386
|
-
| `
|
|
386
|
+
| `breadcrumbs` | Ordered ancestor links with `label` and `url`; the page title completes the trail |
|
|
387
387
|
| `published` | Year, month, and day of publishing (e.g, `2025-09-09`) |
|
|
388
388
|
|
|
389
389
|
You may also add arbitrary fields in a page's front matter, and access them
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z"/></svg>
|
|
@@ -48,10 +48,13 @@ export const MATERIAL_SYMBOLS = Object.freeze({
|
|
|
48
48
|
symbol: 'warning',
|
|
49
49
|
opticalSize: 20,
|
|
50
50
|
}),
|
|
51
|
-
|
|
52
|
-
cssVariable: '--icon-
|
|
53
|
-
symbol: '
|
|
54
|
-
opticalSize:
|
|
51
|
+
breadcrumbSeparator: Object.freeze({
|
|
52
|
+
cssVariable: '--icon-breadcrumb-separator',
|
|
53
|
+
symbol: 'chevron_right',
|
|
54
|
+
opticalSize: 24,
|
|
55
|
+
// The authored selection differs from the shared weight/grade defaults.
|
|
56
|
+
weight: 400,
|
|
57
|
+
grade: 0,
|
|
55
58
|
}),
|
|
56
59
|
search: Object.freeze({
|
|
57
60
|
cssVariable: '--icon-search',
|
package/build/types.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { BundledLanguage } from 'shiki';
|
|
2
2
|
import type { TraceCache } from './build-types';
|
|
3
3
|
|
|
4
|
+
/** An explicitly authored ancestor in a page's breadcrumb trail. */
|
|
5
|
+
export interface BreadcrumbEntry {
|
|
6
|
+
label: string;
|
|
7
|
+
url: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export type PlainTextLanguage = 'text' | 'txt' | 'plain';
|
|
5
11
|
|
|
6
12
|
export interface SerifFontTuning {
|
package/build/utils/render.ts
CHANGED
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
getSupportedConfigFileNamesText,
|
|
16
16
|
} from '../config-files';
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
resolveBreadcrumbLinkTarget,
|
|
19
|
+
validateBreadcrumbs,
|
|
20
20
|
} from '../validate-config-links';
|
|
21
21
|
import {
|
|
22
22
|
extractJavaMethodToc,
|
|
@@ -66,8 +66,8 @@ export { stripHtmlComments } from './html-comments';
|
|
|
66
66
|
|
|
67
67
|
const REQUIRED_FRONT_MATTER_FIELDS = ['title'];
|
|
68
68
|
|
|
69
|
-
//
|
|
70
|
-
//
|
|
69
|
+
// Internal page variables and retired front matter keys. Internal values would
|
|
70
|
+
// be overwritten or, for `template`, select a nonexistent template.
|
|
71
71
|
const RESERVED_FRONT_MATTER_KEYS: readonly string[] = [
|
|
72
72
|
'template',
|
|
73
73
|
'titleHtml',
|
|
@@ -77,6 +77,8 @@ const RESERVED_FRONT_MATTER_KEYS: readonly string[] = [
|
|
|
77
77
|
'codeFilePath',
|
|
78
78
|
'downloadName',
|
|
79
79
|
'filePath',
|
|
80
|
+
'parent',
|
|
81
|
+
'parentLabel',
|
|
80
82
|
];
|
|
81
83
|
|
|
82
84
|
function renderInlineField(
|
|
@@ -456,7 +458,7 @@ interface PreparePageVariablesInput {
|
|
|
456
458
|
* Turns parsed front matter into page variables. Shared by every renderer
|
|
457
459
|
* whose source has front matter, so each one applies the same rules:
|
|
458
460
|
* Lodash processing of string values, inline Markdown for the title and
|
|
459
|
-
* description, required fields, author lookup, and
|
|
461
|
+
* description, required fields, author lookup, and breadcrumb validation.
|
|
460
462
|
*/
|
|
461
463
|
function preparePageVariables({
|
|
462
464
|
rawPageVariables,
|
|
@@ -484,12 +486,29 @@ function preparePageVariables({
|
|
|
484
486
|
subPath,
|
|
485
487
|
isWatchMode,
|
|
486
488
|
});
|
|
489
|
+
const templateString = (value: unknown): unknown =>
|
|
490
|
+
typeof value === 'string' ? compileTemplate(value)(siteOnlyParams) : value;
|
|
487
491
|
const pageVariables: Record<string, unknown> = Object.fromEntries(
|
|
488
|
-
Object.entries(rawPageVariables).map(([
|
|
489
|
-
|
|
490
|
-
|
|
492
|
+
Object.entries(rawPageVariables).map(([key, value]) => [
|
|
493
|
+
key,
|
|
494
|
+
templateString(value),
|
|
491
495
|
]),
|
|
492
496
|
);
|
|
497
|
+
if (Array.isArray(pageVariables.breadcrumbs)) {
|
|
498
|
+
pageVariables.breadcrumbs = pageVariables.breadcrumbs.map(
|
|
499
|
+
(entry: unknown) => {
|
|
500
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
501
|
+
return entry;
|
|
502
|
+
}
|
|
503
|
+
const breadcrumb = entry as Record<string, unknown>;
|
|
504
|
+
return {
|
|
505
|
+
...breadcrumb,
|
|
506
|
+
label: templateString(breadcrumb.label),
|
|
507
|
+
url: templateString(breadcrumb.url),
|
|
508
|
+
};
|
|
509
|
+
},
|
|
510
|
+
);
|
|
511
|
+
}
|
|
493
512
|
|
|
494
513
|
// Render title and description as inline Markdown
|
|
495
514
|
const frontMatterMd = createMarkdown(siteVariables, {
|
|
@@ -502,21 +521,21 @@ function preparePageVariables({
|
|
|
502
521
|
validateFrontMatter(pageVariables, filePath);
|
|
503
522
|
resolveAuthor(pageVariables, filePath, dependencyCollector);
|
|
504
523
|
|
|
505
|
-
const
|
|
506
|
-
|
|
524
|
+
const breadcrumbs = pageVariables.breadcrumbs;
|
|
525
|
+
validateBreadcrumbs(
|
|
526
|
+
breadcrumbs,
|
|
507
527
|
filePath,
|
|
508
528
|
validInternalTargets,
|
|
509
529
|
sourceUrlPath,
|
|
510
530
|
);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
dependencyCollector?.internalTargets?.add(resolvedParentTarget);
|
|
531
|
+
for (const entry of breadcrumbs ?? []) {
|
|
532
|
+
const resolvedTarget = resolveBreadcrumbLinkTarget(
|
|
533
|
+
entry.url,
|
|
534
|
+
sourceUrlPath,
|
|
535
|
+
);
|
|
536
|
+
if (resolvedTarget) {
|
|
537
|
+
dependencyCollector?.internalTargets?.add(resolvedTarget);
|
|
538
|
+
}
|
|
520
539
|
}
|
|
521
540
|
|
|
522
541
|
return pageVariables;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import type { BreadcrumbEntry } from './types';
|
|
2
3
|
import { encodeAuthoredUrl } from './template-globals';
|
|
3
4
|
import { normalizeOutputPath } from './utils/paths';
|
|
4
5
|
|
|
@@ -208,47 +209,86 @@ function splitHref(href: string): { pathname: string; suffix: string } {
|
|
|
208
209
|
return { pathname: match ? match[1] : href, suffix: match ? match[2] : '' };
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
export function
|
|
212
|
-
|
|
212
|
+
export function validateBreadcrumbs(
|
|
213
|
+
breadcrumbs: unknown,
|
|
213
214
|
filePath: string,
|
|
214
215
|
validTargets: ReadonlySet<string>,
|
|
215
216
|
sourceUrlPath: string,
|
|
216
|
-
):
|
|
217
|
-
if (
|
|
218
|
-
return
|
|
217
|
+
): asserts breadcrumbs is BreadcrumbEntry[] | undefined {
|
|
218
|
+
if (breadcrumbs === undefined) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (!Array.isArray(breadcrumbs)) {
|
|
222
|
+
throw new Error(`${filePath}: breadcrumbs must be a list`);
|
|
219
223
|
}
|
|
224
|
+
breadcrumbs.forEach((entry: unknown, index) => {
|
|
225
|
+
const context = `${filePath}: breadcrumb entry ${index + 1}`;
|
|
226
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
227
|
+
throw new Error(`${context} must be an object with label and url fields`);
|
|
228
|
+
}
|
|
229
|
+
const { label, url } = entry as Record<string, unknown>;
|
|
230
|
+
for (const [field, value] of Object.entries({ label, url })) {
|
|
231
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
232
|
+
throw new Error(`${context}: ${field} must be a nonempty string`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const error = validateBreadcrumbLink(
|
|
236
|
+
url as string,
|
|
237
|
+
filePath,
|
|
238
|
+
validTargets,
|
|
239
|
+
sourceUrlPath,
|
|
240
|
+
index + 1,
|
|
241
|
+
);
|
|
242
|
+
if (error) {
|
|
243
|
+
throw new Error(error);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
220
247
|
|
|
221
|
-
|
|
248
|
+
export function validateBreadcrumbLink(
|
|
249
|
+
url: string,
|
|
250
|
+
filePath: string,
|
|
251
|
+
validTargets: ReadonlySet<string>,
|
|
252
|
+
sourceUrlPath: string,
|
|
253
|
+
entryNumber = 1,
|
|
254
|
+
): string | null {
|
|
255
|
+
const context = `${filePath}: breadcrumb entry ${entryNumber}`;
|
|
256
|
+
const resolvedTarget = resolveBreadcrumbLinkTarget(url, sourceUrlPath);
|
|
222
257
|
if (!resolvedTarget) {
|
|
223
|
-
return
|
|
258
|
+
return `${context}: broken breadcrumb link: "${url}" (must be an internal site link with a pathname)`;
|
|
224
259
|
}
|
|
225
260
|
|
|
226
261
|
const directoryError = validateDirectoryLink(
|
|
227
|
-
|
|
262
|
+
url,
|
|
228
263
|
resolvedTarget,
|
|
229
264
|
validTargets,
|
|
230
|
-
|
|
265
|
+
context,
|
|
231
266
|
);
|
|
232
267
|
if (directoryError) {
|
|
233
268
|
return directoryError;
|
|
234
269
|
}
|
|
235
270
|
|
|
236
271
|
if (!validTargets.has(resolvedTarget)) {
|
|
237
|
-
return `${
|
|
272
|
+
return `${context}: broken breadcrumb link: "${url}"`;
|
|
238
273
|
}
|
|
239
274
|
|
|
240
275
|
return null;
|
|
241
276
|
}
|
|
242
277
|
|
|
243
|
-
export function
|
|
244
|
-
|
|
278
|
+
export function resolveBreadcrumbLinkTarget(
|
|
279
|
+
url: string,
|
|
245
280
|
sourceUrlPath: string,
|
|
246
281
|
): string | null {
|
|
247
|
-
|
|
282
|
+
// Breadcrumbs always point into this site. Backslashes also have special
|
|
283
|
+
// URL semantics in browsers, so they cannot be used as path separators.
|
|
284
|
+
if (
|
|
285
|
+
/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url) ||
|
|
286
|
+
url.startsWith('//') ||
|
|
287
|
+
url.includes('\\')
|
|
288
|
+
) {
|
|
248
289
|
return null;
|
|
249
290
|
}
|
|
250
|
-
|
|
251
|
-
const { pathname } = splitHref(parent);
|
|
291
|
+
const { pathname } = splitHref(url);
|
|
252
292
|
if (!pathname) {
|
|
253
293
|
return null;
|
|
254
294
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
breadcrumbs:
|
|
3
|
+
- label: Labs
|
|
4
|
+
url: /labs/index.html
|
|
5
|
+
- label: Lab 0
|
|
6
|
+
url: /labs/00/index.html
|
|
4
7
|
title: Counting vowels
|
|
5
8
|
author: alex
|
|
6
9
|
description: An example of a literate Java program that takes predefined input.
|
|
@@ -15,7 +18,7 @@ The <dfn>standard in</dfn> (or `stdin`) is one of three streams available to a
|
|
|
15
18
|
running program. The other two streams are the standard out and standard error.
|
|
16
19
|
|
|
17
20
|
<figure>
|
|
18
|
-
<svg viewBox="
|
|
21
|
+
<svg viewBox="15 50 380 90" xmlns="http://www.w3.org/2000/svg" font-size="14" fill="none" style="margin:1rem auto;display:block;max-width:380px">
|
|
19
22
|
<defs>
|
|
20
23
|
<marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="4" orient="auto">
|
|
21
24
|
<path d="M0,0 L8,4 L0,8 Z" stroke="currentColor" fill="currentColor"></path>
|
package/package.json
CHANGED
package/src/_content.scss
CHANGED
|
@@ -364,18 +364,34 @@ dfn {
|
|
|
364
364
|
color: var(--theme-color-text);
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
.breadcrumbs {
|
|
368
|
+
ol {
|
|
369
|
+
display: flex;
|
|
370
|
+
flex-wrap: wrap;
|
|
371
|
+
gap: 0.25em;
|
|
372
|
+
padding: 0;
|
|
373
|
+
margin: 0;
|
|
374
|
+
list-style: none;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
li {
|
|
378
|
+
display: flex;
|
|
379
|
+
gap: 0.25em;
|
|
380
|
+
align-items: center;
|
|
381
|
+
min-width: 0;
|
|
382
|
+
overflow-wrap: anywhere;
|
|
383
|
+
}
|
|
370
384
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
385
|
+
a {
|
|
386
|
+
@include subdued-link;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
[aria-current='page'] {
|
|
390
|
+
font-style: italic;
|
|
391
|
+
}
|
|
377
392
|
|
|
378
|
-
|
|
393
|
+
.material-symbol-icon {
|
|
394
|
+
flex-shrink: 0;
|
|
379
395
|
}
|
|
380
396
|
}
|
|
381
397
|
|
package/src/_layout.scss
CHANGED
|
@@ -152,14 +152,16 @@ body.literate main table {
|
|
|
152
152
|
.info {
|
|
153
153
|
@include user-select(none);
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
// Flex instead of a `1fr auto` grid: WebKit keeps the auto column's width
|
|
156
|
+
// after the font changes, which wraps the author avatar onto its own line.
|
|
157
|
+
display: flex;
|
|
157
158
|
align-items: baseline;
|
|
159
|
+
justify-content: space-between;
|
|
158
160
|
font-size: var(--font-size-smaller);
|
|
159
161
|
color: var(--fg2-color);
|
|
160
162
|
|
|
161
163
|
@media (width < 450px) {
|
|
162
|
-
|
|
164
|
+
flex-direction: column;
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
aside.author {
|
|
@@ -22,3 +22,10 @@
|
|
|
22
22
|
--material-symbol-icon: var(--icon-contrast-high);
|
|
23
23
|
--material-symbol-icon-size: var(--icon-contrast-high-size);
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
.material-symbol-icon-breadcrumb-separator {
|
|
27
|
+
--material-symbol-icon: var(--icon-breadcrumb-separator);
|
|
28
|
+
--material-symbol-icon-size: var(--icon-breadcrumb-separator-size);
|
|
29
|
+
|
|
30
|
+
color: var(--fg2-color);
|
|
31
|
+
}
|
package/src/navigate/_index.scss
CHANGED
|
@@ -2,6 +2,11 @@ $shift: 8px;
|
|
|
2
2
|
$duration: 150ms;
|
|
3
3
|
$movement-easing: cubic-bezier(0.2, 0, 0, 1);
|
|
4
4
|
|
|
5
|
+
:root {
|
|
6
|
+
--page-transition-duration: #{$duration};
|
|
7
|
+
--page-transition-movement-easing: #{$movement-easing};
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
@keyframes page-fade-out {
|
|
6
11
|
to {
|
|
7
12
|
opacity: 0;
|
|
@@ -38,19 +43,38 @@ $movement-easing: cubic-bezier(0.2, 0, 0, 1);
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
// Page heading
|
|
46
|
+
// Page heading parts: crossfade in place instead of sliding with the page
|
|
42
47
|
// view-transition-name is set via inline style by the navigator
|
|
43
|
-
// only when .title-and-info is visible in the viewport
|
|
44
|
-
|
|
48
|
+
// only when .title-and-info is visible in the viewport. All parts share one
|
|
49
|
+
// duration and easing so they move in step when the layout changes; the
|
|
50
|
+
// crossfades inherit the group timing.
|
|
51
|
+
::view-transition-group(page-breadcrumbs),
|
|
52
|
+
::view-transition-old(page-breadcrumbs),
|
|
53
|
+
::view-transition-new(page-breadcrumbs),
|
|
54
|
+
::view-transition-group(page-title),
|
|
45
55
|
::view-transition-old(page-title),
|
|
46
56
|
::view-transition-new(page-title),
|
|
57
|
+
::view-transition-group(page-info),
|
|
47
58
|
::view-transition-old(page-info),
|
|
48
|
-
::view-transition-new(page-info)
|
|
49
|
-
::view-transition-old(page-breadcrumb),
|
|
50
|
-
::view-transition-new(page-breadcrumb) {
|
|
59
|
+
::view-transition-new(page-info) {
|
|
51
60
|
animation-duration: $duration;
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
::view-transition-group(page-breadcrumbs),
|
|
64
|
+
::view-transition-group(page-title),
|
|
65
|
+
::view-transition-group(page-info) {
|
|
66
|
+
animation-timing-function: $movement-easing;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Breadcrumbs: the whole trail is one group, so every item moves together when
|
|
70
|
+
// the layout changes. The default plus-lighter crossfade keeps unchanged items
|
|
71
|
+
// opaque while added or removed items fade. Snapshots keep their natural size
|
|
72
|
+
// so a change in trail width does not stretch the text.
|
|
73
|
+
::view-transition-old(page-breadcrumbs),
|
|
74
|
+
::view-transition-new(page-breadcrumbs) {
|
|
75
|
+
inline-size: auto;
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
// The site-wide banner does not change between pages. Keep it out of the root
|
|
55
79
|
// slide and pin its named transition layer in place without a crossfade.
|
|
56
80
|
.site-banner {
|
package/src/navigate/runtime.ts
CHANGED
|
@@ -117,25 +117,28 @@ function dispatchNavigationEvent(
|
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
newInfo.style.viewTransitionName = '';
|
|
120
|
+
// Heading parts that get their own layer instead of sliding with the page. The
|
|
121
|
+
// breadcrumb trail is one group so every item, including the current one,
|
|
122
|
+
// moves together when the page layout changes.
|
|
123
|
+
const TITLE_TRANSITION_NAMES = [
|
|
124
|
+
['nav.breadcrumbs', 'page-breadcrumbs'],
|
|
125
|
+
['h1', 'page-title'],
|
|
126
|
+
['.info', 'page-info'],
|
|
127
|
+
] as const;
|
|
128
|
+
|
|
129
|
+
function setTitleTransitionNames(titleEl: Element, enabled: boolean): void {
|
|
130
|
+
for (const [selector, name] of TITLE_TRANSITION_NAMES) {
|
|
131
|
+
const element = titleEl.querySelector<HTMLElement>(selector);
|
|
132
|
+
if (element) {
|
|
133
|
+
element.style.viewTransitionName = enabled ? name : '';
|
|
134
|
+
}
|
|
136
135
|
}
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function cleanupViewTransitionNames(document: Document): void {
|
|
139
|
+
const titleEl = document.querySelector('.title-and-info');
|
|
140
|
+
if (titleEl) {
|
|
141
|
+
setTitleTransitionNames(titleEl, false);
|
|
139
142
|
}
|
|
140
143
|
}
|
|
141
144
|
|
|
@@ -343,20 +346,7 @@ export async function navigateToUrl(
|
|
|
343
346
|
titleEl != null && titleEl.getBoundingClientRect().top >= headerHeight;
|
|
344
347
|
|
|
345
348
|
if (titleVisible) {
|
|
346
|
-
|
|
347
|
-
const info = titleEl.querySelector('.info') as HTMLElement | null;
|
|
348
|
-
const breadcrumb = titleEl.querySelector(
|
|
349
|
-
'a.breadcrumb',
|
|
350
|
-
) as HTMLElement | null;
|
|
351
|
-
if (h1) {
|
|
352
|
-
h1.style.viewTransitionName = 'page-title';
|
|
353
|
-
}
|
|
354
|
-
if (info) {
|
|
355
|
-
info.style.viewTransitionName = 'page-info';
|
|
356
|
-
}
|
|
357
|
-
if (breadcrumb) {
|
|
358
|
-
breadcrumb.style.viewTransitionName = 'page-breadcrumb';
|
|
359
|
-
}
|
|
349
|
+
setTitleTransitionNames(titleEl, true);
|
|
360
350
|
}
|
|
361
351
|
|
|
362
352
|
document.documentElement.classList.add(
|
|
@@ -376,20 +366,7 @@ export async function navigateToUrl(
|
|
|
376
366
|
newTitleEl.getBoundingClientRect().top >= newHeaderHeight;
|
|
377
367
|
|
|
378
368
|
if (newTitleVisible && newTitleEl) {
|
|
379
|
-
|
|
380
|
-
const info = newTitleEl.querySelector('.info') as HTMLElement | null;
|
|
381
|
-
const breadcrumb = newTitleEl.querySelector(
|
|
382
|
-
'a.breadcrumb',
|
|
383
|
-
) as HTMLElement | null;
|
|
384
|
-
if (h1) {
|
|
385
|
-
h1.style.viewTransitionName = 'page-title';
|
|
386
|
-
}
|
|
387
|
-
if (info) {
|
|
388
|
-
info.style.viewTransitionName = 'page-info';
|
|
389
|
-
}
|
|
390
|
-
if (breadcrumb) {
|
|
391
|
-
breadcrumb.style.viewTransitionName = 'page-breadcrumb';
|
|
392
|
-
}
|
|
369
|
+
setTitleTransitionNames(newTitleEl, true);
|
|
393
370
|
}
|
|
394
371
|
});
|
|
395
372
|
|
package/templates/_heading.html
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
<div class="title-and-info" data-pagefind-body>
|
|
2
|
-
<% if (page.
|
|
3
|
-
<
|
|
2
|
+
<% if (page.breadcrumbs && page.breadcrumbs.length) { %>
|
|
3
|
+
<nav class="breadcrumbs" aria-label="Breadcrumb" data-pagefind-ignore>
|
|
4
|
+
<ol>
|
|
5
|
+
<% page.breadcrumbs.forEach((entry, index) => { %>
|
|
6
|
+
<li>
|
|
7
|
+
<% if (index > 0) { %><span class="material-symbol-icon material-symbol-icon-breadcrumb-separator" aria-hidden="true"></span><% } %>
|
|
8
|
+
<a href="<%- encodeAuthoredUrl(entry.url) %>"><%- entry.label %></a>
|
|
9
|
+
</li>
|
|
10
|
+
<% }); %>
|
|
11
|
+
<li>
|
|
12
|
+
<span class="material-symbol-icon material-symbol-icon-breadcrumb-separator" aria-hidden="true"></span>
|
|
13
|
+
<span aria-current="page"><%- page.title %></span>
|
|
14
|
+
</li>
|
|
15
|
+
</ol>
|
|
16
|
+
</nav>
|
|
4
17
|
<% } %>
|
|
5
18
|
<h1 data-pagefind-weight="10" data-pagefind-meta="title_html[data-title-html]" data-title-html="<%- page.titleHtml %>"><%= page.titleHtml %></h1>
|
|
6
19
|
|
package/templates/_theme.scss
CHANGED
|
@@ -33,8 +33,10 @@
|
|
|
33
33
|
|
|
34
34
|
// Main background color (the color of the page background)
|
|
35
35
|
--bg-color: hsl(var(--tint-hue) calc(5% * var(--tint-amount)) 97%);
|
|
36
|
+
|
|
37
|
+
// Same saturation as --bg-color, so frosting doesn't add color to the page
|
|
36
38
|
--bg-color-translucent: hsl(
|
|
37
|
-
var(--tint-hue) calc(
|
|
39
|
+
var(--tint-hue) calc(5% * var(--tint-amount)) 95% / 66%
|
|
38
40
|
);
|
|
39
41
|
|
|
40
42
|
// Secondary background color (lighter)
|
|
@@ -117,7 +119,7 @@
|
|
|
117
119
|
--link-color-hover: <%= linkColorHoverDark %>;
|
|
118
120
|
--bg-color: hsl(var(--tint-hue) calc(3% * var(--tint-amount)) 5%);
|
|
119
121
|
--bg-color-translucent: hsl(
|
|
120
|
-
var(--tint-hue) calc(
|
|
122
|
+
var(--tint-hue) calc(3% * var(--tint-amount)) 10% / 66%
|
|
121
123
|
);
|
|
122
124
|
--bg2-color: hsl(var(--tint-hue) calc(12% * var(--tint-amount)) 12%);
|
|
123
125
|
--bg2-color-translucent: hsl(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M386-224v-58h250.69L200-718.69 241.31-760 678-323.31V-574h58v350H386Z"/></svg>
|