explico 0.12.0 → 0.13.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/CHANGELOG.md +1137 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +4 -3
- package/dist/compiler/astro.js +45 -15
- package/dist/compiler/cli.d.ts +26 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +9 -1
- package/dist/compiler/collections.js +55 -16
- package/dist/compiler/content-schema.d.ts +31 -0
- package/dist/compiler/content-schema.js +82 -0
- package/dist/compiler/content-schemas.d.ts +422 -0
- package/dist/compiler/content-schemas.js +229 -0
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +30 -2
- package/dist/compiler/exports/combined-html.d.ts +97 -0
- package/dist/compiler/exports/combined-html.js +492 -0
- package/dist/compiler/exports/download-index.d.ts +9 -0
- package/dist/compiler/exports/download-index.js +33 -0
- package/dist/compiler/exports/export-chapters.d.ts +98 -0
- package/dist/compiler/exports/export-chapters.js +164 -0
- package/dist/compiler/exports/export-pages.d.ts +48 -0
- package/dist/compiler/exports/export-pages.js +22 -0
- package/dist/compiler/exports/html-tree.d.ts +22 -0
- package/dist/compiler/exports/html-tree.js +47 -0
- package/dist/compiler/exports/latex/capture.d.ts +106 -0
- package/dist/compiler/exports/latex/capture.js +435 -0
- package/dist/compiler/exports/latex/compile.d.ts +73 -0
- package/dist/compiler/exports/latex/compile.js +115 -0
- package/dist/compiler/exports/latex/document.d.ts +102 -0
- package/dist/compiler/exports/latex/document.js +349 -0
- package/dist/compiler/exports/latex/explico.cls +415 -0
- package/dist/compiler/exports/latex/numbering.d.ts +18 -0
- package/dist/compiler/exports/latex/numbering.js +57 -0
- package/dist/compiler/exports/latex/serialize.d.ts +112 -0
- package/dist/compiler/exports/latex/serialize.js +606 -0
- package/dist/compiler/exports/latex-export.d.ts +44 -0
- package/dist/compiler/exports/latex-export.js +169 -0
- package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
- package/dist/compiler/exports/markdown/serialize.js +527 -0
- package/dist/compiler/exports/markdown-export.d.ts +39 -0
- package/dist/compiler/exports/markdown-export.js +240 -0
- package/dist/compiler/exports/standalone-html.d.ts +14 -5
- package/dist/compiler/exports/standalone-html.js +23 -51
- package/dist/compiler/exports/static-tree.d.ts +253 -0
- package/dist/compiler/exports/static-tree.js +625 -0
- package/dist/compiler/exports/zip.d.ts +16 -0
- package/dist/compiler/exports/zip.js +72 -0
- package/dist/compiler/lesson-derivation.d.ts +8 -2
- package/dist/compiler/lesson-derivation.js +6 -7
- package/dist/compiler/manifest.d.ts +87 -6
- package/dist/compiler/manifest.js +174 -31
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +2 -1
- package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
- package/dist/curriculum/validation.d.ts +9 -3
- package/dist/curriculum/validation.js +35 -17
- package/dist/reference/base-abbreviations.d.mts +2 -2
- package/dist/reference/base-abbreviations.mjs +2 -2
- package/dist/reference/base-operators.d.mts +2 -2
- package/dist/reference/base-operators.mjs +2 -2
- package/dist/reference/base-roman-labels.d.mts +4 -3
- package/dist/reference/base-roman-labels.mjs +4 -3
- package/dist/reference/citation-format.d.mts +35 -0
- package/dist/reference/citation-format.mjs +74 -0
- package/dist/reference/consistency.d.ts +5 -9
- package/dist/reference/consistency.js +34 -38
- package/dist/reference/curriculum-alignment.js +3 -24
- package/dist/reference/derived-entries.d.ts +45 -0
- package/dist/reference/derived-entries.js +50 -0
- package/dist/reference/derived-notation.d.mts +125 -0
- package/dist/reference/derived-notation.mjs +121 -0
- package/dist/reference/diagram-fit.d.ts +45 -0
- package/dist/reference/diagram-fit.js +54 -0
- package/dist/reference/diagram-layout.d.ts +59 -10
- package/dist/reference/diagram-layout.js +85 -24
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +1 -1
- package/dist/reference/equations-validate.js +26 -1
- package/dist/reference/equations.d.mts +27 -0
- package/dist/reference/equations.mjs +120 -22
- package/dist/reference/examples-validate.d.ts +1 -1
- package/dist/reference/figure-fit.d.ts +70 -0
- package/dist/reference/figure-fit.js +60 -0
- package/dist/reference/figure-pictures.d.ts +27 -0
- package/dist/reference/figure-pictures.js +49 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-abbreviations.js +1 -1
- package/dist/reference/gate-course-rules.d.ts +4 -3
- package/dist/reference/gate-course-rules.js +9 -4
- package/dist/reference/gate-heading-length.d.ts +2 -0
- package/dist/reference/gate-heading-length.js +7 -5
- package/dist/reference/gate-heading-structure.js +26 -0
- package/dist/reference/gate-indices.d.ts +35 -0
- package/dist/reference/gate-indices.js +299 -0
- package/dist/reference/gate-math.d.ts +32 -2
- package/dist/reference/gate-math.js +68 -12
- package/dist/reference/gate-mdx-expression.d.ts +17 -0
- package/dist/reference/gate-mdx-expression.js +29 -0
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/gate-prop-text.d.ts +28 -0
- package/dist/reference/gate-prop-text.js +106 -0
- package/dist/reference/gate-prose-notation.d.ts +3 -1
- package/dist/reference/gate-prose-notation.js +4 -2
- package/dist/reference/gate-retired-keys.d.ts +35 -0
- package/dist/reference/gate-retired-keys.js +58 -0
- package/dist/reference/gloss.d.ts +2 -0
- package/dist/reference/katex-measure.d.ts +17 -0
- package/dist/reference/katex-measure.js +223 -0
- package/dist/reference/keyed-float-validate.js +12 -0
- package/dist/reference/keyed-jsx-floats.d.mts +2 -2
- package/dist/reference/keyed-jsx-floats.mjs +11 -14
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- package/dist/reference/math-glyphs.d.mts +94 -2
- package/dist/reference/math-glyphs.mjs +851 -46
- package/dist/reference/mixed-content.d.ts +8 -3
- package/dist/reference/mixed-content.js +4 -2
- package/dist/reference/page-notation.d.ts +34 -4
- package/dist/reference/page-notation.js +54 -2
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +104 -10
- package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
- package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
- package/dist/reference/rehype-heading-anchors.mjs +11 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/remark-notation.mjs +84 -20
- package/dist/reference/render-lab-math.d.ts +2 -0
- package/dist/reference/render-lab-math.js +17 -5
- package/dist/reference/sections-validate.d.ts +28 -3
- package/dist/reference/sections-validate.js +31 -4
- package/dist/reference/sections.d.mts +24 -6
- package/dist/reference/sections.mjs +92 -17
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/tables-validate.js +24 -1
- package/dist/reference/text-measure.d.ts +10 -0
- package/dist/reference/text-measure.js +41 -0
- package/dist/reference/text-widths.d.ts +10 -0
- package/dist/reference/text-widths.js +1238 -0
- package/dist/reference/types.d.ts +64 -8
- package/dist/rules/catalog.js +191 -30
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +79 -24
- package/dist/rules/settings.d.ts +1 -1
- package/dist/rules/settings.js +7 -0
- package/dist/session/PreferenceStore.d.ts +18 -1
- package/dist/session/PreferenceStore.js +29 -1
- package/docs/authoring/README.md +32 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +111 -6
- package/docs/authoring/migrating.md +707 -0
- package/docs/authoring/notation.md +281 -29
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +10 -41
- package/docs/authoring/structure.md +53 -2
- package/docs/authoring/visualizations.md +144 -18
- package/package.json +12 -7
- package/src/compiler/astro.ts +57 -17
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +92 -19
- package/src/compiler/content-schema.ts +108 -0
- package/src/compiler/content-schemas.ts +391 -0
- package/src/compiler/course-config.ts +86 -15
- package/src/compiler/exports/combined-html.ts +669 -0
- package/src/compiler/exports/download-index.ts +36 -0
- package/src/compiler/exports/export-chapters.ts +233 -0
- package/src/compiler/exports/export-pages.ts +44 -0
- package/src/compiler/exports/html-tree.ts +62 -0
- package/src/compiler/exports/latex/capture.ts +651 -0
- package/src/compiler/exports/latex/compile.ts +168 -0
- package/src/compiler/exports/latex/document.ts +479 -0
- package/src/compiler/exports/latex/explico.cls +415 -0
- package/src/compiler/exports/latex/numbering.ts +74 -0
- package/src/compiler/exports/latex/serialize.ts +787 -0
- package/src/compiler/exports/latex/texlive-packages.txt +76 -0
- package/src/compiler/exports/latex-export.ts +255 -0
- package/src/compiler/exports/markdown/serialize.ts +632 -0
- package/src/compiler/exports/markdown-export.ts +329 -0
- package/src/compiler/exports/standalone-html.ts +39 -70
- package/src/compiler/exports/static-tree.ts +944 -0
- package/src/compiler/exports/zip.ts +88 -0
- package/src/compiler/lesson-derivation.ts +13 -8
- package/src/compiler/manifest.ts +299 -28
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +12 -9
- package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
- package/src/components/assessments/AssessmentRunner.tsx +7 -1
- package/src/components/assessments/AssessmentSet.astro +14 -1
- package/src/components/block-heading.ts +38 -0
- package/src/components/card-arrow.ts +18 -4
- package/src/components/card-carry.ts +406 -0
- package/src/components/card-drag.ts +1 -30
- package/src/components/card-fetch.ts +87 -0
- package/src/components/card-manager.ts +1222 -38
- package/src/components/card-memory.ts +5 -1
- package/src/components/card-placement.ts +211 -13
- package/src/components/card-side.ts +281 -31
- package/src/components/citation/CitationLayer.astro +45 -40
- package/src/components/diagram/Diagram.astro +123 -44
- package/src/components/figure/Figure.astro +11 -0
- package/src/components/figure/FloatCaption.astro +4 -17
- package/src/components/float-toolbar.ts +274 -0
- package/src/components/memo/Memo.astro +21 -10
- package/src/components/memo/ShortNote.astro +4 -0
- package/src/components/notation/NotationGlossary.astro +22 -17
- package/src/components/notation/NotationLayer.astro +321 -79
- package/src/components/notation/NotationMath.astro +8 -1
- package/src/components/notation/render-notation-math.ts +51 -5
- package/src/components/readable-text.ts +25 -0
- package/src/components/reference/FloatRefLayer.astro +162 -89
- package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
- package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
- package/src/components/starlight/LayoutPageSidebar.astro +17 -3
- package/src/components/starlight/LayoutSocialIcons.astro +79 -29
- package/src/components/starlight/LessonExportActions.astro +384 -310
- package/src/components/starlight/LessonFooter.astro +47 -9
- package/src/components/starlight/PrintController.astro +28 -0
- package/src/components/starlight/TextSizeControl.astro +137 -0
- package/src/components/starlight/print-controller.ts +380 -0
- package/src/components/starlight/section-summary.ts +13 -5
- package/src/components/table/Table.astro +13 -21
- package/src/components/table/table-truncation.ts +15 -0
- package/src/content-config.ts +20 -312
- package/src/curriculum/validation.ts +72 -24
- package/src/reference/base-abbreviations.mjs +2 -2
- package/src/reference/base-operators.mjs +2 -2
- package/src/reference/base-roman-labels.mjs +4 -3
- package/src/reference/citation-format.mjs +74 -0
- package/src/reference/consistency.ts +47 -61
- package/src/reference/curriculum-alignment.ts +4 -41
- package/src/reference/derived-entries.ts +68 -0
- package/src/reference/derived-notation.mjs +121 -0
- package/src/reference/diagram-fit.ts +104 -0
- package/src/reference/diagram-layout.ts +182 -51
- package/src/reference/diagrams-validate.ts +2 -1
- package/src/reference/equations-validate.ts +41 -2
- package/src/reference/equations.mjs +120 -22
- package/src/reference/examples-validate.ts +1 -0
- package/src/reference/figure-fit.ts +139 -0
- package/src/reference/figure-pictures.ts +96 -0
- package/src/reference/figures-validate.ts +2 -1
- package/src/reference/gate-abbreviations.ts +1 -1
- package/src/reference/gate-course-rules.ts +22 -7
- package/src/reference/gate-heading-length.ts +25 -7
- package/src/reference/gate-heading-structure.ts +40 -1
- package/src/reference/gate-indices.ts +470 -0
- package/src/reference/gate-math.ts +102 -13
- package/src/reference/gate-mdx-expression.ts +45 -0
- package/src/reference/gate-prop-text.ts +145 -0
- package/src/reference/gate-prose-notation.ts +19 -5
- package/src/reference/gate-retired-keys.ts +101 -0
- package/src/reference/gloss.ts +2 -0
- package/src/reference/katex-measure.ts +384 -0
- package/src/reference/keyed-float-validate.ts +22 -0
- package/src/reference/keyed-jsx-floats.mjs +11 -14
- package/src/reference/lab-math-scope.ts +15 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +100 -3
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +154 -8
- package/src/reference/rehype-fail-katex-errors.mjs +19 -0
- package/src/reference/rehype-heading-anchors.mjs +11 -1
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/remark-notation.mjs +84 -20
- package/src/reference/render-lab-math.ts +29 -5
- package/src/reference/sections-validate.ts +67 -3
- package/src/reference/sections.mjs +92 -17
- package/src/reference/tables-validate.ts +40 -2
- package/src/reference/text-measure.ts +63 -0
- package/src/reference/text-widths.ts +133 -0
- package/src/reference/types.ts +75 -8
- package/src/rules/catalog.ts +205 -29
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +291 -75
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics only a rendered page can give, kept from one `astro build` for
|
|
3
|
+
* `content warnings`.
|
|
4
|
+
*
|
|
5
|
+
* Most checks read the content tree, so the manifest carries them and every
|
|
6
|
+
* `content` command recomputes them. A few need the page as Astro renders it:
|
|
7
|
+
* a diagram drawn by a wrapper component lays out to a width only its render
|
|
8
|
+
* knows (rule V8, `reference/diagram-fit.ts`). `compiler/astro.ts` reports
|
|
9
|
+
* those as the build finishes and writes them to
|
|
10
|
+
* `build/render-diagnostics.json`, replacing the previous build's; the
|
|
11
|
+
* `content` CLI lists them beside the manifest's under the `render:` source.
|
|
12
|
+
* They are as current as the last build: run `pnpm build` (or `astro build`)
|
|
13
|
+
* after changing a diagram to refresh them.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
import type { DiagnosticSeverity } from '../reference/types';
|
|
20
|
+
import {
|
|
21
|
+
diagramTooWideMessage,
|
|
22
|
+
oversizedDiagrams,
|
|
23
|
+
} from '../reference/diagram-fit';
|
|
24
|
+
import type { Manifest } from './manifest';
|
|
25
|
+
|
|
26
|
+
export const RENDER_DIAGNOSTICS_PATH = join(
|
|
27
|
+
process.cwd(),
|
|
28
|
+
'build',
|
|
29
|
+
'render-diagnostics.json',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export interface RenderDiagnostic {
|
|
33
|
+
readonly code: string;
|
|
34
|
+
readonly severity: DiagnosticSeverity;
|
|
35
|
+
readonly message: string;
|
|
36
|
+
/** The lesson's source file, e.g. `content/docs/motion/range.mdx`; else the built page. */
|
|
37
|
+
readonly file: string;
|
|
38
|
+
readonly lessonId?: string;
|
|
39
|
+
/** The float the finding is about, e.g. a diagram's `id`. */
|
|
40
|
+
readonly key?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A built page's path (`motion/range/index.html`) → the slug it serves. */
|
|
44
|
+
function slugOfPage(page: string): string {
|
|
45
|
+
return page
|
|
46
|
+
.split(/[\\/]/u)
|
|
47
|
+
.join('/')
|
|
48
|
+
.replace(/(^|\/)index\.html$/u, '')
|
|
49
|
+
.replace(/\.html$/u, '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The render diagnostics of one built page. A diagram that appears on more
|
|
54
|
+
* than one page (a lesson and its standalone export) is reported once per
|
|
55
|
+
* lesson by {@link writeRenderDiagnostics}.
|
|
56
|
+
*/
|
|
57
|
+
export function pageRenderDiagnostics(
|
|
58
|
+
manifest: Pick<Manifest, 'lessons'>,
|
|
59
|
+
page: string,
|
|
60
|
+
html: string,
|
|
61
|
+
): RenderDiagnostic[] {
|
|
62
|
+
const oversized = oversizedDiagrams(html);
|
|
63
|
+
if (oversized.length === 0) return [];
|
|
64
|
+
const slug = slugOfPage(page);
|
|
65
|
+
const lesson = manifest.lessons.find((candidate) => candidate.slug === slug);
|
|
66
|
+
return oversized.map((diagram) => ({
|
|
67
|
+
code: diagram.code,
|
|
68
|
+
severity: 'warning',
|
|
69
|
+
message: diagramTooWideMessage(diagram.key, diagram.width, diagram.limit),
|
|
70
|
+
file: lesson?.file ?? page,
|
|
71
|
+
...(lesson ? { lessonId: lesson.id } : {}),
|
|
72
|
+
key: diagram.key,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Replace the last build's render diagnostics with these, deduplicated and sorted. */
|
|
77
|
+
export function writeRenderDiagnostics(
|
|
78
|
+
diagnostics: readonly RenderDiagnostic[],
|
|
79
|
+
path: string = RENDER_DIAGNOSTICS_PATH,
|
|
80
|
+
): readonly RenderDiagnostic[] {
|
|
81
|
+
const unique = new Map<string, RenderDiagnostic>();
|
|
82
|
+
for (const diagnostic of diagnostics) {
|
|
83
|
+
unique.set(`${diagnostic.file}\0${diagnostic.code}\0${diagnostic.key ?? diagnostic.message}`, diagnostic);
|
|
84
|
+
}
|
|
85
|
+
const sorted = [...unique.values()].sort(
|
|
86
|
+
(a, b) =>
|
|
87
|
+
a.file.localeCompare(b.file) ||
|
|
88
|
+
a.code.localeCompare(b.code) ||
|
|
89
|
+
(a.key ?? '').localeCompare(b.key ?? ''),
|
|
90
|
+
);
|
|
91
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
92
|
+
writeFileSync(path, `${JSON.stringify({ diagnostics: sorted }, null, 2)}\n`);
|
|
93
|
+
return sorted;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** What the last build reported; `[]` before the first build. */
|
|
97
|
+
export function readRenderDiagnostics(
|
|
98
|
+
path: string = RENDER_DIAGNOSTICS_PATH,
|
|
99
|
+
): readonly RenderDiagnostic[] {
|
|
100
|
+
if (!existsSync(path)) return [];
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as {
|
|
103
|
+
readonly diagnostics?: unknown;
|
|
104
|
+
};
|
|
105
|
+
return Array.isArray(parsed.diagnostics)
|
|
106
|
+
? (parsed.diagnostics as RenderDiagnostic[])
|
|
107
|
+
: [];
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* Pin toggle and a × — a note line under it, and the layer's own fields in
|
|
12
12
|
* the default slot. The card manager owns the buttons (`data-card-grip`,
|
|
13
13
|
* `data-card-side`, `data-card-pin`, `data-card-close`), the note
|
|
14
|
-
* (`data-card-note`), the
|
|
15
|
-
*
|
|
14
|
+
* (`data-card-note`), the carrying mark before the buttons (`data-card-mark`,
|
|
15
|
+
* rule K8), the mode (`data-card-mode`: hover, pinned, side or moved), the
|
|
16
|
+
* flash (`is-card-flashing`) and where the card goes
|
|
16
17
|
* (`./card-placement.ts`, `./card-side.ts`, `./card-drag.ts`).
|
|
17
18
|
*
|
|
18
19
|
* Extra attributes (`data-citation-panel`, `data-float-ref-panel`, …) go on
|
|
@@ -59,6 +60,33 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
59
60
|
</button>
|
|
60
61
|
<div class="hover-panel-heading"><slot name="heading" /></div>
|
|
61
62
|
<div class="hover-panel-tools">
|
|
63
|
+
{/* K8: with carrying on, whether a kept card means the same on every
|
|
64
|
+
page (a globe) or only on its own (`card-manager.ts`). */}
|
|
65
|
+
<span class="hover-panel-mark" data-card-mark role="img" hidden>
|
|
66
|
+
<svg
|
|
67
|
+
class="hover-panel-mark-global"
|
|
68
|
+
viewBox="0 0 16 16"
|
|
69
|
+
width="14"
|
|
70
|
+
height="14"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
>
|
|
73
|
+
<circle cx="8" cy="8" r="6.25"></circle>
|
|
74
|
+
<path
|
|
75
|
+
d="M1.75 8h12.5M8 1.75c1.8 1.9 2.7 4 2.7 6.25S9.8 12.35 8 14.25M8 1.75C6.2 3.65 5.3 5.75 5.3 8s.9 4.35 2.7 6.25"
|
|
76
|
+
></path>
|
|
77
|
+
</svg>
|
|
78
|
+
<svg
|
|
79
|
+
class="hover-panel-mark-page"
|
|
80
|
+
viewBox="0 0 16 16"
|
|
81
|
+
width="14"
|
|
82
|
+
height="14"
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
>
|
|
85
|
+
<path d="M3.75 1.75h5.5l3 3v9.5h-8.5z"></path>
|
|
86
|
+
<path d="M9.25 1.75v3h3"></path>
|
|
87
|
+
</svg>
|
|
88
|
+
<span class="hover-panel-mark-page" aria-hidden="true">this page</span>
|
|
89
|
+
</span>
|
|
62
90
|
<button
|
|
63
91
|
class="hover-panel-side"
|
|
64
92
|
type="button"
|
|
@@ -104,6 +132,16 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
104
132
|
<div class="hover-panel-scroll">
|
|
105
133
|
<slot />
|
|
106
134
|
</div>
|
|
135
|
+
<div
|
|
136
|
+
class="hover-panel-resize"
|
|
137
|
+
data-card-resize
|
|
138
|
+
role="separator"
|
|
139
|
+
aria-orientation="vertical"
|
|
140
|
+
aria-label="Card width"
|
|
141
|
+
title="Drag to widen this card. Arrow keys change it; Home or a double click puts it back."
|
|
142
|
+
tabindex="0"
|
|
143
|
+
>
|
|
144
|
+
</div>
|
|
107
145
|
</aside>
|
|
108
146
|
</template>
|
|
109
147
|
|
|
@@ -129,7 +167,10 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
129
167
|
color: var(--sl-color-text);
|
|
130
168
|
background: var(--sl-color-bg);
|
|
131
169
|
box-shadow: var(--sl-shadow-lg);
|
|
132
|
-
|
|
170
|
+
/* 14 px, the article's caption size; never under 12 px, which a
|
|
171
|
+
reader's smallest text size (70%, `TextSizeControl.astro`) would
|
|
172
|
+
otherwise take a card's text to. */
|
|
173
|
+
font-size: max(12px, var(--sl-text-sm));
|
|
133
174
|
}
|
|
134
175
|
|
|
135
176
|
/* `.hover-panel { display: flex }` above has the same specificity as the
|
|
@@ -360,11 +401,144 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
360
401
|
outline-offset: 2px;
|
|
361
402
|
}
|
|
362
403
|
|
|
404
|
+
/* A card's footer links ("Go to the full definition", "Go to the
|
|
405
|
+
reference list", …) are actions in a row of their own, not words in a
|
|
406
|
+
sentence: an outline button says "this goes somewhere" without the
|
|
407
|
+
underline, and its arrow says where. They stay links, so they open in a
|
|
408
|
+
new tab and announce as links. The card layer sits outside the
|
|
409
|
+
article, whose link colour never reached them, so they set the site's
|
|
410
|
+
accent here; a link inside a card's text keeps its underline. */
|
|
411
|
+
/* S11: a docked card's right edge widens it (`card-manager.ts`): a thin
|
|
412
|
+
grip along the edge, shown on hover and focus. Only a docked card has
|
|
413
|
+
it. */
|
|
414
|
+
.hover-panel-resize {
|
|
415
|
+
display: none;
|
|
416
|
+
position: absolute;
|
|
417
|
+
inset-block: 0.75rem;
|
|
418
|
+
inset-inline-end: 0;
|
|
419
|
+
width: 0.5rem;
|
|
420
|
+
cursor: col-resize;
|
|
421
|
+
touch-action: none;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.hover-panel[data-card-mode='side'] .hover-panel-resize {
|
|
425
|
+
display: block;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.hover-panel-resize::after {
|
|
429
|
+
content: '';
|
|
430
|
+
position: absolute;
|
|
431
|
+
inset-block: 0;
|
|
432
|
+
inset-inline-end: 0.15rem;
|
|
433
|
+
width: 2px;
|
|
434
|
+
border-radius: 1px;
|
|
435
|
+
background: var(--sl-color-accent);
|
|
436
|
+
opacity: 0;
|
|
437
|
+
transition: opacity 120ms ease-out;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.hover-panel-resize:hover::after,
|
|
441
|
+
.hover-panel-resize:focus-visible::after {
|
|
442
|
+
opacity: 1;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.hover-panel-resize:focus-visible {
|
|
446
|
+
outline: 2px solid var(--sl-color-accent);
|
|
447
|
+
outline-offset: -2px;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* Slim: about as tall as the text line it replaced, so a card keeps its
|
|
451
|
+
height and lands where it did. */
|
|
452
|
+
.hover-panel :global(.hover-panel-action) {
|
|
453
|
+
display: inline-flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
gap: 0.3em;
|
|
456
|
+
margin-block: 0.0625rem;
|
|
457
|
+
margin-inline-end: 0.4rem;
|
|
458
|
+
padding: 0.1rem 0.5rem;
|
|
459
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
460
|
+
border-radius: 0.375rem;
|
|
461
|
+
color: var(--sl-color-text-accent);
|
|
462
|
+
font-size: var(--sl-text-xs);
|
|
463
|
+
line-height: 1.3;
|
|
464
|
+
text-decoration: none;
|
|
465
|
+
vertical-align: middle;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.hover-panel :global(.hover-panel-action:hover) {
|
|
469
|
+
border-color: var(--sl-color-accent);
|
|
470
|
+
background: var(--sl-color-accent-low);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.hover-panel :global(.hover-panel-action[hidden]) {
|
|
474
|
+
display: none;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* ↗ is the one jump arrow with an emoji form, and a fallback font can
|
|
478
|
+
draw it as a coloured emoji box; ask for the text glyph, as ↑ ↓ → are
|
|
479
|
+
drawn (`card-jump.ts`). */
|
|
480
|
+
.hover-panel :global([data-jump-arrow]) {
|
|
481
|
+
font-variant-emoji: text;
|
|
482
|
+
}
|
|
483
|
+
|
|
363
484
|
@media (forced-colors: active) {
|
|
364
485
|
.hover-panel:is([data-card-mode='pinned'], [data-card-mode='side'], [data-card-mode='moved']) {
|
|
365
486
|
border-color: Highlight;
|
|
366
487
|
}
|
|
367
488
|
}
|
|
489
|
+
|
|
490
|
+
/* K3: a card held at the window's edge in a stack too tall for it shows
|
|
491
|
+
its header row only; a click on it, or the keyboard's focus, opens it
|
|
492
|
+
again (`card-manager.ts`). */
|
|
493
|
+
.hover-panel[data-card-collapsed] > :not(.hover-panel-header) {
|
|
494
|
+
display: none;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.hover-panel[data-card-collapsed] .hover-panel-header {
|
|
498
|
+
padding-block-end: 0.5rem;
|
|
499
|
+
cursor: pointer;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* K8: a globe, or a page and "this page", muted, before the buttons. */
|
|
503
|
+
.hover-panel-mark {
|
|
504
|
+
display: inline-flex;
|
|
505
|
+
align-items: center;
|
|
506
|
+
gap: 0.25rem;
|
|
507
|
+
margin-inline-end: 0.3rem;
|
|
508
|
+
font-size: var(--sl-text-xs);
|
|
509
|
+
line-height: 1;
|
|
510
|
+
white-space: nowrap;
|
|
511
|
+
color: var(--sl-color-gray-3);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.hover-panel-mark[hidden] {
|
|
515
|
+
display: none;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.hover-panel-mark svg {
|
|
519
|
+
fill: none;
|
|
520
|
+
stroke: currentColor;
|
|
521
|
+
stroke-width: 1.3;
|
|
522
|
+
stroke-linecap: round;
|
|
523
|
+
stroke-linejoin: round;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.hover-panel-mark[data-card-mark='global'] .hover-panel-mark-page,
|
|
527
|
+
.hover-panel-mark[data-card-mark='page'] .hover-panel-mark-global {
|
|
528
|
+
display: none;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* K5: a card carried in from another page has no trigger here to be
|
|
532
|
+
pinned next to; × ends its journey, Side still docks it. */
|
|
533
|
+
.hover-panel[data-card-away] .hover-panel-pin {
|
|
534
|
+
display: none;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/* P1: its trigger is folded away (`foldAway` in card-manager.ts); the card
|
|
538
|
+
keeps its size for when the trigger shows again. */
|
|
539
|
+
.hover-panel[data-card-folded] {
|
|
540
|
+
visibility: hidden;
|
|
541
|
+
}
|
|
368
542
|
}
|
|
369
543
|
|
|
370
544
|
/* D1: while a card is dragged, nothing on the page is selected and the
|
|
@@ -444,6 +618,12 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
444
618
|
:global(.explico-card-dock) {
|
|
445
619
|
display: none;
|
|
446
620
|
}
|
|
621
|
+
|
|
622
|
+
/* K2: a held card is fixed to the window, which print repeats on every
|
|
623
|
+
sheet; it belongs to the screen it was held on. */
|
|
624
|
+
:global(.hover-panel[data-card-held]) {
|
|
625
|
+
display: none !important;
|
|
626
|
+
}
|
|
447
627
|
}
|
|
448
628
|
|
|
449
629
|
/* S5: the arrow from a kept card to an occurrence of its item
|
|
@@ -467,6 +647,16 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
467
647
|
fill: var(--sl-color-accent);
|
|
468
648
|
}
|
|
469
649
|
|
|
650
|
+
/* D4: to a symbol that only looks like the card's, defined elsewhere. */
|
|
651
|
+
:global(.explico-card-arrow.is-lookalike .explico-card-arrow-line) {
|
|
652
|
+
stroke: var(--sl-color-gray-3);
|
|
653
|
+
stroke-dasharray: 5 4;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
:global(.explico-card-arrow.is-lookalike .explico-card-arrow-head) {
|
|
657
|
+
fill: var(--sl-color-gray-3);
|
|
658
|
+
}
|
|
659
|
+
|
|
470
660
|
@media (prefers-reduced-motion: reduce) {
|
|
471
661
|
:global(.explico-card-arrow) {
|
|
472
662
|
transition: none;
|
|
@@ -481,6 +671,14 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
481
671
|
:global(.explico-card-arrow-head) {
|
|
482
672
|
fill: Highlight;
|
|
483
673
|
}
|
|
674
|
+
|
|
675
|
+
:global(.explico-card-arrow.is-lookalike .explico-card-arrow-line) {
|
|
676
|
+
stroke: GrayText;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
:global(.explico-card-arrow.is-lookalike .explico-card-arrow-head) {
|
|
680
|
+
fill: GrayText;
|
|
681
|
+
}
|
|
484
682
|
}
|
|
485
683
|
|
|
486
684
|
@media print {
|
|
@@ -489,9 +687,9 @@ const { id, label, class: className, ...rest } = Astro.props;
|
|
|
489
687
|
}
|
|
490
688
|
}
|
|
491
689
|
|
|
492
|
-
/* The open mobile menu
|
|
493
|
-
|
|
494
|
-
:global(body
|
|
690
|
+
/* The open mobile menu (Starlight's `#starlight__sidebar` popover) covers
|
|
691
|
+
the page; the cards would stay focusable behind it. */
|
|
692
|
+
:global(body:has(#starlight__sidebar:popover-open) .explico-card-layer) {
|
|
495
693
|
display: none;
|
|
496
694
|
}
|
|
497
695
|
|
|
@@ -152,19 +152,21 @@ const hasRail =
|
|
|
152
152
|
---
|
|
153
153
|
|
|
154
154
|
<div class="part-overview not-content" data-rail={hasRail ? '' : undefined}>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{part.
|
|
159
|
-
{
|
|
155
|
+
{/* A list, not spans in a row: search and a screen reader keep the items
|
|
156
|
+
apart ("Part 1 of 2", "3 chapters"), where spans ran into "23 chapters". */}
|
|
157
|
+
<ul class="part-meta" aria-label="This part at a glance">
|
|
158
|
+
<li>Part {part.ordinal} of {manifest.parts.length}</li>
|
|
159
|
+
<li>{chapters.length} {chapters.length === 1 ? 'chapter' : 'chapters'}</li>
|
|
160
|
+
{part.studyMinutes && <li>{minutes(part.studyMinutes)}</li>}
|
|
161
|
+
{equationCount > 0 && <li>{equationCount} numbered equations</li>}
|
|
160
162
|
{
|
|
161
163
|
checkCount > 0 && (
|
|
162
|
-
<
|
|
164
|
+
<li>
|
|
163
165
|
{checkCount} knowledge {checkCount === 1 ? 'check' : 'checks'}
|
|
164
|
-
</
|
|
166
|
+
</li>
|
|
165
167
|
)
|
|
166
168
|
}
|
|
167
|
-
</
|
|
169
|
+
</ul>
|
|
168
170
|
|
|
169
171
|
<div class="part-body">
|
|
170
172
|
<ol class="part-chapters">
|
|
@@ -189,7 +191,6 @@ const hasRail =
|
|
|
189
191
|
{lesson.knowledgeChecks.length === 1 ? '' : 's'}
|
|
190
192
|
</span>
|
|
191
193
|
)}
|
|
192
|
-
{lesson.status !== 'reviewed' && <span>{lesson.status}</span>}
|
|
193
194
|
</p>
|
|
194
195
|
</li>
|
|
195
196
|
))
|
|
@@ -273,6 +274,8 @@ const hasRail =
|
|
|
273
274
|
flex-wrap: wrap;
|
|
274
275
|
gap: 0.3rem 1rem;
|
|
275
276
|
margin: 0 0 1.25rem;
|
|
277
|
+
padding: 0;
|
|
278
|
+
list-style: none;
|
|
276
279
|
color: var(--sl-color-gray-3);
|
|
277
280
|
font-size: var(--sl-text-xs);
|
|
278
281
|
letter-spacing: 0.04em;
|
|
@@ -24,7 +24,7 @@ const entries = await getCollection('abbreviations');
|
|
|
24
24
|
<p class="abbreviation-panel-term" data-panel-term slot="heading"></p>
|
|
25
25
|
<div class="abbreviation-panel-body">
|
|
26
26
|
<p class="abbreviation-panel-expansion" data-panel-expansion></p>
|
|
27
|
-
<a class="abbreviation-panel-jump" data-panel-jump
|
|
27
|
+
<a class="abbreviation-panel-jump hover-panel-action" data-panel-jump
|
|
28
28
|
>Go to the glossary<span aria-hidden="true" data-jump-arrow> →</span></a
|
|
29
29
|
>
|
|
30
30
|
</div>
|
|
@@ -40,6 +40,7 @@ const entries = await getCollection('abbreviations');
|
|
|
40
40
|
|
|
41
41
|
<script>
|
|
42
42
|
import { cloneCardShell, registerCardSource } from '../card-manager';
|
|
43
|
+
import { fetchDoc, hrefFrom } from '../card-fetch';
|
|
43
44
|
import { wireJumpArrow } from '../card-jump';
|
|
44
45
|
|
|
45
46
|
function closestElement(node: EventTarget | null, selector: string) {
|
|
@@ -51,18 +52,21 @@ const entries = await getCollection('abbreviations');
|
|
|
51
52
|
|
|
52
53
|
function setup() {
|
|
53
54
|
const template = document.getElementById('abbreviation-panel');
|
|
55
|
+
// Every page registers, with abbreviations or none: a card carried in
|
|
56
|
+
// from another page is built here (rule K5).
|
|
54
57
|
if (
|
|
55
58
|
!(template instanceof HTMLTemplateElement) ||
|
|
56
|
-
template.dataset.cardSourceReady === 'true'
|
|
57
|
-
!document.querySelector('a.abbreviation-term[data-abbr-key]')
|
|
59
|
+
template.dataset.cardSourceReady === 'true'
|
|
58
60
|
) {
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
template.dataset.cardSourceReady = 'true';
|
|
62
64
|
|
|
63
65
|
// Fill a card from the matching hidden `[data-abbr-entry]` block — never
|
|
64
|
-
// from a second serialized copy of the table.
|
|
65
|
-
|
|
66
|
+
// from a second serialized copy of the table. Every page has every
|
|
67
|
+
// entry; a card carried in from another page (K5) takes its term, as the
|
|
68
|
+
// text wrote it, from the marker there, and its link goes from there.
|
|
69
|
+
const render = (key: string, marker: HTMLElement, page?: string) => {
|
|
66
70
|
const entry = entryFor(key);
|
|
67
71
|
const el = cloneCardShell(template);
|
|
68
72
|
const term = el?.querySelector<HTMLElement>('[data-panel-term]');
|
|
@@ -72,7 +76,8 @@ const entries = await getCollection('abbreviations');
|
|
|
72
76
|
|
|
73
77
|
term.textContent = marker.textContent ?? '';
|
|
74
78
|
expansion.textContent = entry.textContent ?? '';
|
|
75
|
-
|
|
79
|
+
const jumpTo = marker.getAttribute('href') ?? '#abbreviations';
|
|
80
|
+
jump.href = page ? hrefFrom(jumpTo, page) : jumpTo;
|
|
76
81
|
wireJumpArrow(jump);
|
|
77
82
|
el.setAttribute('aria-label', `Abbreviation: ${term.textContent}`);
|
|
78
83
|
return el;
|
|
@@ -85,7 +90,16 @@ const entries = await getCollection('abbreviations');
|
|
|
85
90
|
const key = marker.dataset.abbrKey;
|
|
86
91
|
return key && entryFor(key) ? key : null;
|
|
87
92
|
},
|
|
88
|
-
render,
|
|
93
|
+
render: (key, marker) => render(key, marker),
|
|
94
|
+
build: (key, page) =>
|
|
95
|
+
fetchDoc(page).then((doc) => {
|
|
96
|
+
const marker = doc.querySelector<HTMLElement>(
|
|
97
|
+
`a.abbreviation-term[data-abbr-key="${CSS.escape(key)}"]`,
|
|
98
|
+
);
|
|
99
|
+
const el = marker ? render(key, marker, page) : null;
|
|
100
|
+
el?.setAttribute('data-card-foreign', '');
|
|
101
|
+
return el;
|
|
102
|
+
}),
|
|
89
103
|
});
|
|
90
104
|
}
|
|
91
105
|
|
|
@@ -116,10 +130,5 @@ const entries = await getCollection('abbreviations');
|
|
|
116
130
|
margin: 0;
|
|
117
131
|
font-weight: 600;
|
|
118
132
|
}
|
|
119
|
-
|
|
120
|
-
.abbreviation-panel-jump {
|
|
121
|
-
display: inline-block;
|
|
122
|
-
font-size: var(--sl-text-xs);
|
|
123
|
-
}
|
|
124
133
|
}
|
|
125
134
|
</style>
|
|
@@ -17,6 +17,8 @@ import { shuffled } from './assessment-randomization';
|
|
|
17
17
|
interface ChoiceOption {
|
|
18
18
|
id: string;
|
|
19
19
|
label: string;
|
|
20
|
+
/** The label split into text and rendered math (`AssessmentSet.astro`). */
|
|
21
|
+
labelContent?: MixedContentNode[];
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
interface AssessmentItemBase {
|
|
@@ -194,7 +196,11 @@ function Question({
|
|
|
194
196
|
disabled={checked}
|
|
195
197
|
onChange={() => setChoice(option.id)}
|
|
196
198
|
/>
|
|
197
|
-
{option.
|
|
199
|
+
{option.labelContent ? (
|
|
200
|
+
<RenderedContent content={option.labelContent} />
|
|
201
|
+
) : (
|
|
202
|
+
option.label
|
|
203
|
+
)}
|
|
198
204
|
</label>
|
|
199
205
|
))}
|
|
200
206
|
</fieldset>
|
|
@@ -67,7 +67,8 @@ const entries = ids.map((id, index) => {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
// `manifest.assessmentContent` (schema v8) already resolved every glyph in
|
|
70
|
-
// every item's `prompt` / `explanation`
|
|
70
|
+
// every item's `prompt` / `explanation` (and, v30, a choice's label) against
|
|
71
|
+
// this page's lesson scope —
|
|
71
72
|
// same `pageScope`, same relationship, as `gate-math.ts`'s assessment check.
|
|
72
73
|
// Rendering the resolved LaTeX to a `SafeMathNode` tree happens here, in an
|
|
73
74
|
// Astro component, not in `manifest.ts`: the compiler layer stays
|
|
@@ -127,6 +128,18 @@ function renderedContent(
|
|
|
127
128
|
item.explanation,
|
|
128
129
|
content?.explanationSegments,
|
|
129
130
|
),
|
|
131
|
+
// A choice's label renders its math like the prompt (schema v30).
|
|
132
|
+
...(item.type === 'single-choice'
|
|
133
|
+
? {
|
|
134
|
+
options: item.options.map((option: { id: string; label: string }) => ({
|
|
135
|
+
...option,
|
|
136
|
+
labelContent: renderedContent(
|
|
137
|
+
option.label,
|
|
138
|
+
content?.options?.find((entry) => entry.id === option.id)?.labelSegments,
|
|
139
|
+
),
|
|
140
|
+
})),
|
|
141
|
+
}
|
|
142
|
+
: {}),
|
|
130
143
|
};
|
|
131
144
|
})}
|
|
132
145
|
/>
|
|
@@ -59,6 +59,44 @@ export async function titledBlocksOf(
|
|
|
59
59
|
return lesson?.titledBlocks ?? [];
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The text a reader sees for each outline heading with inline math, by its
|
|
64
|
+
* anchor (`manifest.lessons[].headings`, v30). Astro lists a heading by all
|
|
65
|
+
* the text in it, and KaTeX writes a formula three ways — MathML, its LaTeX
|
|
66
|
+
* annotation and its glyphs — so its own list reads `R\explain{k}{R}R`.
|
|
67
|
+
*/
|
|
68
|
+
export async function mathHeadingTextsOf(lessonId: string): Promise<ReadonlyMap<string, string>> {
|
|
69
|
+
const lesson = (await manifestOnce()).lessons.find((entry) => entry.id === lessonId);
|
|
70
|
+
return new Map(
|
|
71
|
+
(lesson?.headings ?? [])
|
|
72
|
+
.filter((heading) => heading.math)
|
|
73
|
+
.map((heading) => [heading.slug, heading.text]),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `headings` with the manifest's text for each one in `texts`. */
|
|
78
|
+
export function withHeadingTexts<T extends OutlineHeading>(
|
|
79
|
+
headings: readonly T[],
|
|
80
|
+
texts: ReadonlyMap<string, string>,
|
|
81
|
+
): T[] {
|
|
82
|
+
return headings.map((heading) => {
|
|
83
|
+
const text = texts.get(heading.slug);
|
|
84
|
+
return text === undefined ? heading : { ...heading, text };
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** An "On this page" tree with the manifest's text for each entry in `texts`. */
|
|
89
|
+
export function tocWithHeadingTexts(
|
|
90
|
+
items: readonly TocItem[],
|
|
91
|
+
texts: ReadonlyMap<string, string>,
|
|
92
|
+
): TocItem[] {
|
|
93
|
+
return items.map((item) => ({
|
|
94
|
+
...item,
|
|
95
|
+
text: texts.get(item.slug) ?? item.text,
|
|
96
|
+
children: tocWithHeadingTexts(item.children, texts),
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
62
100
|
/** A heading as Astro and Starlight list it (`MarkdownHeading`). */
|
|
63
101
|
export interface OutlineHeading {
|
|
64
102
|
readonly depth: number;
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* arrowhead. It stays while the occurrence or the card is hovered or focused,
|
|
10
10
|
* and fades a while after both are left.
|
|
11
11
|
*
|
|
12
|
+
* D4, K6: from a card carried in from another page for one of that page's
|
|
13
|
+
* own symbols, the arrow to a symbol here that looks the same is dashed and
|
|
14
|
+
* grey: the same letters, defined elsewhere.
|
|
15
|
+
*
|
|
12
16
|
* `arrowShape` is the geometry, pure, over plain boxes, so
|
|
13
17
|
* `tests/unit/card-arrow.test.ts` can check it in Node. The overlay is one
|
|
14
18
|
* SVG at the bottom of the card layer (`./card-placement.ts`): above the
|
|
@@ -94,9 +98,14 @@ export function arrowShape(card: Box, target: Box): ArrowShape {
|
|
|
94
98
|
// The overlay.
|
|
95
99
|
// ---------------------------------------------------------------------------
|
|
96
100
|
|
|
101
|
+
export interface ArrowOptions {
|
|
102
|
+
/** D4: `target` only looks like the card's symbol: the arrow is dashed and grey. */
|
|
103
|
+
readonly lookalike?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
97
106
|
export interface ArrowOverlay {
|
|
98
107
|
/** Draw the arrow from `card` to `target`, replacing any other. */
|
|
99
|
-
show(card: HTMLElement, target: HTMLElement): void;
|
|
108
|
+
show(card: HTMLElement, target: HTMLElement, options?: ArrowOptions): void;
|
|
100
109
|
/** Draw it again after the card or the occurrence moved. */
|
|
101
110
|
redraw(): void;
|
|
102
111
|
/**
|
|
@@ -136,6 +145,7 @@ export function createArrowOverlay(options: ArrowOverlayOptions): ArrowOverlay {
|
|
|
136
145
|
let head: SVGPolygonElement | null = null;
|
|
137
146
|
let from: HTMLElement | null = null;
|
|
138
147
|
let to: HTMLElement | null = null;
|
|
148
|
+
let lookalike = false;
|
|
139
149
|
let holdTimer: ReturnType<typeof setTimeout> | null = null;
|
|
140
150
|
let fadeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
141
151
|
|
|
@@ -171,7 +181,7 @@ export function createArrowOverlay(options: ArrowOverlayOptions): ArrowOverlay {
|
|
|
171
181
|
from = null;
|
|
172
182
|
to = null;
|
|
173
183
|
svg?.remove();
|
|
174
|
-
svg?.classList.remove('is-fading');
|
|
184
|
+
svg?.classList.remove('is-fading', 'is-lookalike');
|
|
175
185
|
};
|
|
176
186
|
|
|
177
187
|
const draw = () => {
|
|
@@ -193,7 +203,10 @@ export function createArrowOverlay(options: ArrowOverlayOptions): ArrowOverlay {
|
|
|
193
203
|
mount();
|
|
194
204
|
line?.setAttribute('d', shape.path);
|
|
195
205
|
head?.setAttribute('points', shape.head);
|
|
196
|
-
if (svg)
|
|
206
|
+
if (svg) {
|
|
207
|
+
svg.dataset.cardArrow = from.dataset.cardKey ?? '';
|
|
208
|
+
svg.classList.toggle('is-lookalike', lookalike);
|
|
209
|
+
}
|
|
197
210
|
};
|
|
198
211
|
|
|
199
212
|
const fade = () => {
|
|
@@ -221,10 +234,11 @@ export function createArrowOverlay(options: ArrowOverlayOptions): ArrowOverlay {
|
|
|
221
234
|
};
|
|
222
235
|
|
|
223
236
|
return {
|
|
224
|
-
show(card, target) {
|
|
237
|
+
show(card, target, options = {}) {
|
|
225
238
|
hold();
|
|
226
239
|
from = card;
|
|
227
240
|
to = target;
|
|
241
|
+
lookalike = options.lookalike === true;
|
|
228
242
|
draw();
|
|
229
243
|
},
|
|
230
244
|
redraw: draw,
|