explico 0.11.1 → 0.13.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.
- package/CHANGELOG.md +1292 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +11 -4
- package/dist/compiler/astro.js +48 -15
- package/dist/compiler/cli.d.ts +27 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +33 -2
- package/dist/compiler/collections.js +91 -19
- 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 +15 -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 +115 -8
- package/dist/compiler/manifest.js +221 -39
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +3 -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 +8 -2
- package/dist/reference/base-operators.mjs +8 -10
- 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/figure-symbols.d.ts +32 -0
- package/dist/reference/figure-symbols.js +55 -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-non-chapter-pages.d.ts +39 -0
- package/dist/reference/gate-non-chapter-pages.js +156 -0
- package/dist/reference/gate-part-pages.d.ts +4 -2
- package/dist/reference/gate-part-pages.js +2 -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-katex-validate.d.ts +43 -0
- package/dist/reference/lab-katex-validate.js +96 -0
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- package/dist/reference/lint-ignore.js +1 -1
- 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 +39 -7
- package/dist/reference/page-notation.js +62 -10
- package/dist/reference/raw-floats.mjs +37 -13
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +115 -11
- 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 +275 -23
- package/dist/reference/render-lab-math.d.ts +13 -2
- package/dist/reference/render-lab-math.js +18 -6
- 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 +223 -32
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +100 -27
- 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 +33 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +115 -10
- package/docs/authoring/migrating.md +878 -0
- package/docs/authoring/notation.md +285 -30
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +84 -45
- package/docs/authoring/structure.md +67 -2
- package/docs/authoring/visualizations.md +167 -25
- package/package.json +12 -7
- package/src/compiler/astro.ts +72 -21
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +158 -27
- 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 +387 -40
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/CourseContents.astro +6 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +18 -9
- package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
- 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/disclosure/print-as-shown.ts +42 -6
- package/src/components/figure/Figure.astro +17 -1
- package/src/components/figure/FloatCaption.astro +4 -17
- package/src/components/figure/figure-state.ts +26 -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 +300 -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 +66 -8
- 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 +14 -22
- 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 +8 -10
- 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/figure-symbols.ts +117 -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-non-chapter-pages.ts +241 -0
- package/src/reference/gate-part-pages.ts +1 -1
- 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-katex-validate.ts +156 -0
- package/src/reference/lab-math-scope.ts +15 -1
- package/src/reference/lint-ignore.ts +4 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +114 -14
- package/src/reference/raw-floats.mjs +37 -13
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +170 -9
- 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 +275 -23
- package/src/reference/render-lab-math.ts +40 -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 +241 -31
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +322 -83
|
@@ -1,42 +1,107 @@
|
|
|
1
1
|
---
|
|
2
|
+
/**
|
|
3
|
+
* The page's export actions in the header's right cluster: a Download button
|
|
4
|
+
* that opens a menu of the page's downloads, and a Print button.
|
|
5
|
+
*
|
|
6
|
+
* **Download.** The menu is a native popover (Escape and an outside click
|
|
7
|
+
* close it; it sits in the top layer, above explico's cards). Its rows are
|
|
8
|
+
* the scopes that contain the page, its columns the formats, and each cell a
|
|
9
|
+
* plain link to a file the build wrote. The build lists what it wrote, with
|
|
10
|
+
* sizes, in `downloads/index.json`; the menu reads it the first time it
|
|
11
|
+
* opens, shows each size and hides a cell whose file is not there. During
|
|
12
|
+
* `astro dev` only a chapter's HTML builds on request; the others say they
|
|
13
|
+
* are built when the site is deployed.
|
|
14
|
+
*
|
|
15
|
+
* **Print.** The button loads every lab that will be on paper before the
|
|
16
|
+
* print dialog opens — the browser's own `beforeprint` cannot wait for that —
|
|
17
|
+
* and Ctrl+P / Cmd+P do the same. The browser's own File → Print still gets
|
|
18
|
+
* the print-as-shown preparation, without loading labs.
|
|
19
|
+
*
|
|
20
|
+
* A downloaded page carries none of this markup (`data-explico-export-chrome`);
|
|
21
|
+
* the print controller itself (`print-controller.ts`) is installed from the
|
|
22
|
+
* page's footer too (`PrintController.astro`), so Ctrl+P prepares a
|
|
23
|
+
* downloaded page the same way.
|
|
24
|
+
*/
|
|
25
|
+
export type DownloadFormat = 'html' | 'pdf' | 'latex' | 'markdown';
|
|
26
|
+
|
|
27
|
+
export interface DownloadCell {
|
|
28
|
+
readonly format: DownloadFormat;
|
|
29
|
+
/** Path below `downloads/`, as `downloads/index.json` lists it. */
|
|
30
|
+
readonly path: string;
|
|
31
|
+
/** The name the file is saved under. */
|
|
32
|
+
readonly filename: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type DownloadScope = 'chapter' | 'part' | 'course';
|
|
36
|
+
|
|
37
|
+
export interface DownloadRow {
|
|
38
|
+
readonly scope: DownloadScope;
|
|
39
|
+
/** "This chapter", "This part", "Whole course". */
|
|
40
|
+
readonly label: string;
|
|
41
|
+
/** The chapter's title, the part's label or the course's name. */
|
|
42
|
+
readonly title: string;
|
|
43
|
+
readonly cells: readonly DownloadCell[];
|
|
44
|
+
}
|
|
45
|
+
|
|
2
46
|
interface Props {
|
|
3
|
-
|
|
4
|
-
readonly href: string;
|
|
5
|
-
readonly filename: string;
|
|
6
|
-
};
|
|
47
|
+
downloads: readonly DownloadRow[];
|
|
7
48
|
print: boolean;
|
|
8
|
-
canonicalBaseUrl?: string;
|
|
9
49
|
siteBaseUrl: string;
|
|
10
50
|
}
|
|
11
51
|
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
52
|
+
const { downloads, print, siteBaseUrl } = Astro.props;
|
|
53
|
+
|
|
54
|
+
// Starlight renders the header's icon cluster twice (the header and the
|
|
55
|
+
// mobile menu), so each menu needs an id of its own for `popovertarget`.
|
|
56
|
+
const locals = Astro.locals as { explicoDownloadMenus?: number };
|
|
57
|
+
locals.explicoDownloadMenus = (locals.explicoDownloadMenus ?? 0) + 1;
|
|
58
|
+
const menuId = `explico-downloads-${locals.explicoDownloadMenus}`;
|
|
59
|
+
|
|
60
|
+
const base = siteBaseUrl.replace(/\/$/u, '');
|
|
61
|
+
const development = import.meta.env.DEV;
|
|
62
|
+
/** What a download's accessible name calls each scope. */
|
|
63
|
+
const SCOPE_NAMES: Readonly<Record<DownloadScope, string>> = {
|
|
64
|
+
chapter: 'this chapter',
|
|
65
|
+
part: 'this part',
|
|
66
|
+
course: 'the whole course',
|
|
67
|
+
};
|
|
68
|
+
const FORMATS: readonly { readonly format: DownloadFormat; readonly label: string; readonly name: string }[] = [
|
|
69
|
+
{ format: 'html', label: 'HTML', name: 'as standalone HTML' },
|
|
70
|
+
{ format: 'pdf', label: 'PDF', name: 'as PDF' },
|
|
71
|
+
{ format: 'latex', label: 'LaTeX', name: 'as LaTeX source' },
|
|
72
|
+
{ format: 'markdown', label: 'Markdown', name: 'as Markdown' },
|
|
73
|
+
];
|
|
74
|
+
const columns = FORMATS.filter(({ format }) =>
|
|
75
|
+
downloads.some((row) => row.cells.some((cell) => cell.format === format)),
|
|
76
|
+
);
|
|
77
|
+
const notes = [
|
|
78
|
+
columns.some((column) => column.format === 'html') && 'HTML opens offline with working figures.',
|
|
79
|
+
columns.some((column) => column.format === 'pdf') &&
|
|
80
|
+
(columns.some((column) => column.format === 'latex')
|
|
81
|
+
? 'PDF is typeset for print, and LaTeX is its source.'
|
|
82
|
+
: 'PDF is typeset for print.'),
|
|
83
|
+
columns.some((column) => column.format === 'markdown') && 'Markdown is plain text with images.',
|
|
84
|
+
].filter(Boolean);
|
|
18
85
|
---
|
|
19
86
|
|
|
20
87
|
<nav
|
|
21
88
|
class="lesson-export-actions not-content"
|
|
22
|
-
aria-label="
|
|
89
|
+
aria-label="Page exports"
|
|
23
90
|
data-explico-export-chrome="lesson-actions"
|
|
24
|
-
data-explico-print-controller={print ? '' : undefined}
|
|
25
|
-
data-canonical-base-url={canonicalBaseUrl}
|
|
26
|
-
data-site-base-url={siteBaseUrl}
|
|
27
91
|
>
|
|
28
92
|
{
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
93
|
+
downloads.length > 0 && (
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
class="lesson-export-actions__download"
|
|
97
|
+
popovertarget={menuId}
|
|
98
|
+
aria-label="Download this page"
|
|
99
|
+
title="Download this page"
|
|
35
100
|
>
|
|
36
101
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
37
102
|
<path d="M12 3v12m0 0 4-4m-4 4-4-4M5 19h14" />
|
|
38
103
|
</svg>
|
|
39
|
-
</
|
|
104
|
+
</button>
|
|
40
105
|
)
|
|
41
106
|
}
|
|
42
107
|
{
|
|
@@ -44,8 +109,8 @@ const {
|
|
|
44
109
|
<button
|
|
45
110
|
type="button"
|
|
46
111
|
data-explico-print-action
|
|
47
|
-
aria-label="Print or save this
|
|
48
|
-
title="Print or save this
|
|
112
|
+
aria-label="Print or save this page as PDF"
|
|
113
|
+
title="Print or save this page as PDF"
|
|
49
114
|
>
|
|
50
115
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
51
116
|
<path d="M7 9V3h10v6M7 17H5a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2M7 14h10v7H7z" />
|
|
@@ -53,15 +118,87 @@ const {
|
|
|
53
118
|
</button>
|
|
54
119
|
)
|
|
55
120
|
}
|
|
121
|
+
{
|
|
122
|
+
downloads.length > 0 && (
|
|
123
|
+
<div
|
|
124
|
+
id={menuId}
|
|
125
|
+
class="explico-downloads"
|
|
126
|
+
popover
|
|
127
|
+
role="dialog"
|
|
128
|
+
aria-label="Downloads"
|
|
129
|
+
data-explico-downloads
|
|
130
|
+
data-index-href={`${base}/downloads/index.json`}
|
|
131
|
+
>
|
|
132
|
+
<p class="explico-downloads__title">Download</p>
|
|
133
|
+
<table class="explico-downloads__grid">
|
|
134
|
+
<thead>
|
|
135
|
+
<tr>
|
|
136
|
+
<th scope="col"><span class="sr-only">Scope</span></th>
|
|
137
|
+
{columns.map((column) => <th scope="col">{column.label}</th>)}
|
|
138
|
+
</tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
{downloads.map((row) => (
|
|
142
|
+
<tr>
|
|
143
|
+
<th scope="row">
|
|
144
|
+
{row.label}
|
|
145
|
+
<span class="explico-downloads__scope">{row.title}</span>
|
|
146
|
+
</th>
|
|
147
|
+
{columns.map((column) => {
|
|
148
|
+
const cell = row.cells.find((entry) => entry.format === column.format);
|
|
149
|
+
if (!cell) return <td><span class="explico-downloads__none" aria-hidden="true">—</span></td>;
|
|
150
|
+
const name = `Download ${SCOPE_NAMES[row.scope]} ${column.name}`;
|
|
151
|
+
// `astro dev` builds a chapter's HTML on request; the rest
|
|
152
|
+
// exists only once the site is built.
|
|
153
|
+
if (development && !(row.scope === 'chapter' && column.format === 'html')) {
|
|
154
|
+
return (
|
|
155
|
+
<td>
|
|
156
|
+
<span
|
|
157
|
+
class="explico-downloads__cell explico-downloads__cell--pending"
|
|
158
|
+
title="Built when the site is deployed"
|
|
159
|
+
>
|
|
160
|
+
<span class="explico-downloads__format">{column.label}</span>
|
|
161
|
+
<span class="explico-downloads__size">not in dev</span>
|
|
162
|
+
</span>
|
|
163
|
+
</td>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return (
|
|
167
|
+
<td>
|
|
168
|
+
<a
|
|
169
|
+
class="explico-downloads__cell"
|
|
170
|
+
href={`${base}/downloads/${cell.path}`}
|
|
171
|
+
download={cell.filename}
|
|
172
|
+
aria-label={name}
|
|
173
|
+
title={name}
|
|
174
|
+
data-download-path={cell.path}
|
|
175
|
+
>
|
|
176
|
+
<span class="explico-downloads__format">{column.label}</span>
|
|
177
|
+
<span class="explico-downloads__size" data-download-size>
|
|
178
|
+
<span aria-hidden="true">↓</span>
|
|
179
|
+
</span>
|
|
180
|
+
</a>
|
|
181
|
+
</td>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
</tr>
|
|
185
|
+
))}
|
|
186
|
+
</tbody>
|
|
187
|
+
</table>
|
|
188
|
+
{notes.length > 0 && <p class="explico-downloads__note">{notes.join(' ')}</p>}
|
|
189
|
+
</div>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
56
192
|
</nav>
|
|
57
193
|
|
|
58
194
|
<style>
|
|
59
195
|
.lesson-export-actions {
|
|
60
196
|
display: inline-flex;
|
|
197
|
+
gap: 0.4rem;
|
|
61
198
|
}
|
|
62
199
|
|
|
63
|
-
.lesson-export-actions a,
|
|
64
|
-
.lesson-export-actions button {
|
|
200
|
+
.lesson-export-actions > a,
|
|
201
|
+
.lesson-export-actions > button {
|
|
65
202
|
align-items: center;
|
|
66
203
|
background: transparent;
|
|
67
204
|
border: 1px solid var(--sl-color-gray-5);
|
|
@@ -87,8 +224,9 @@ const {
|
|
|
87
224
|
width: 1rem;
|
|
88
225
|
}
|
|
89
226
|
|
|
90
|
-
.lesson-export-actions :is(a, button):hover,
|
|
91
|
-
.lesson-export-actions :is(a, button):focus-visible
|
|
227
|
+
.lesson-export-actions > :is(a, button):hover,
|
|
228
|
+
.lesson-export-actions > :is(a, button):focus-visible,
|
|
229
|
+
.lesson-export-actions__download[aria-expanded='true'] {
|
|
92
230
|
border-color: var(--sl-color-accent);
|
|
93
231
|
color: var(--sl-color-text-accent);
|
|
94
232
|
}
|
|
@@ -97,264 +235,152 @@ const {
|
|
|
97
235
|
outline: 2px solid var(--sl-color-accent);
|
|
98
236
|
outline-offset: 2px;
|
|
99
237
|
}
|
|
100
|
-
</style>
|
|
101
238
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
type PreparedStaticExport = {
|
|
121
|
-
readonly mode: StaticExportMode;
|
|
122
|
-
readonly disclosures: PreparedDisclosures;
|
|
123
|
-
readonly hidden: readonly SavedHidden[];
|
|
124
|
-
readonly links: readonly SavedLink[];
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const root = document.documentElement;
|
|
128
|
-
let prepared: PreparedStaticExport | null = null;
|
|
129
|
-
let pendingMarkerCleanup: (() => void) | null = null;
|
|
130
|
-
|
|
131
|
-
function controller(): HTMLElement | null {
|
|
132
|
-
return document.querySelector<HTMLElement>('[data-explico-print-controller]');
|
|
239
|
+
/* The popover is placed under its button by the script; these undo the
|
|
240
|
+
user agent's centred placement. */
|
|
241
|
+
.explico-downloads {
|
|
242
|
+
background: var(--sl-color-bg-nav, var(--sl-color-bg));
|
|
243
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
244
|
+
border-radius: 0.6rem;
|
|
245
|
+
box-shadow: var(--sl-shadow-md);
|
|
246
|
+
box-sizing: border-box;
|
|
247
|
+
color: var(--sl-color-text);
|
|
248
|
+
inset: auto;
|
|
249
|
+
margin: 0;
|
|
250
|
+
max-width: calc(100vw - 1rem);
|
|
251
|
+
overflow-y: auto;
|
|
252
|
+
overscroll-behavior: contain;
|
|
253
|
+
padding: 0.85rem 0.85rem 0.75rem;
|
|
254
|
+
position: fixed;
|
|
255
|
+
width: max-content;
|
|
133
256
|
}
|
|
134
257
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const rawHref = link.getAttribute('href');
|
|
141
|
-
if (!rawHref || rawHref.startsWith('#') || link.hasAttribute('download')) {
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
258
|
+
.explico-downloads__title {
|
|
259
|
+
font-size: var(--sl-text-sm);
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
margin: 0 0 0.5rem;
|
|
262
|
+
}
|
|
144
263
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
if (target.protocol !== 'http:' && target.protocol !== 'https:') return undefined;
|
|
152
|
-
|
|
153
|
-
const current = new URL(location.href);
|
|
154
|
-
if (
|
|
155
|
-
target.origin === current.origin &&
|
|
156
|
-
target.pathname === current.pathname &&
|
|
157
|
-
target.search === current.search
|
|
158
|
-
) {
|
|
159
|
-
return undefined;
|
|
160
|
-
}
|
|
264
|
+
.explico-downloads__grid {
|
|
265
|
+
border-collapse: collapse;
|
|
266
|
+
font-size: var(--sl-text-xs);
|
|
267
|
+
}
|
|
161
268
|
|
|
162
|
-
|
|
269
|
+
.explico-downloads__grid th,
|
|
270
|
+
.explico-downloads__grid td {
|
|
271
|
+
padding: 0.35rem 0.25rem;
|
|
272
|
+
text-align: left;
|
|
273
|
+
vertical-align: middle;
|
|
163
274
|
}
|
|
164
275
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
siteBaseUrl: string,
|
|
171
|
-
): string {
|
|
172
|
-
if (!canonicalBaseUrl || target.origin !== location.origin) return target.href;
|
|
173
|
-
const normalizedSiteBase = `/${siteBaseUrl.replace(/^\/+|\/+$/gu, '')}/`;
|
|
174
|
-
const relativePath = target.pathname.startsWith(normalizedSiteBase)
|
|
175
|
-
? target.pathname.slice(normalizedSiteBase.length)
|
|
176
|
-
: target.pathname.replace(/^\/+/, '');
|
|
177
|
-
return new URL(
|
|
178
|
-
`${relativePath}${target.search}${target.hash}`,
|
|
179
|
-
canonicalBaseUrl,
|
|
180
|
-
).href;
|
|
276
|
+
/* Normal case: an uppercased heading would print "LaTeX" as "LATEX". */
|
|
277
|
+
.explico-downloads__grid thead th {
|
|
278
|
+
color: var(--sl-color-gray-3);
|
|
279
|
+
font-size: 0.72rem;
|
|
280
|
+
font-weight: 600;
|
|
181
281
|
}
|
|
182
282
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
root.dataset.explicoStaticExport = mode;
|
|
190
|
-
|
|
191
|
-
const canonicalBaseUrl = printController.dataset.canonicalBaseUrl;
|
|
192
|
-
const siteBaseUrl = printController.dataset.siteBaseUrl ?? '/';
|
|
193
|
-
|
|
194
|
-
// Print follows the screen (see `../disclosure/print-as-shown`); a
|
|
195
|
-
// Markdown export needs every word whatever the reader collapsed.
|
|
196
|
-
const disclosures = prepareDisclosures(
|
|
197
|
-
mode === 'print' ? 'as-shown' : 'expanded',
|
|
198
|
-
{
|
|
199
|
-
noteHref: (anchorId) => {
|
|
200
|
-
const page = new URL(location.href);
|
|
201
|
-
page.hash = anchorId ? `#${anchorId}` : '';
|
|
202
|
-
return canonicalUrl(page, canonicalBaseUrl, siteBaseUrl);
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
// Rows beyond a long table's fold and an open example set's inactive
|
|
208
|
-
// tabs are hidden only to save screen space, so they are exposed for
|
|
209
|
-
// print too. A closed short note or question is a reader's disclosure:
|
|
210
|
-
// print leaves it closed (with its note), only Markdown export opens it.
|
|
211
|
-
const hiddenSelector =
|
|
212
|
-
mode === 'print'
|
|
213
|
-
? 'main :is(.compact-example, .keyed-table__body tr:not(.keyed-table__more-row))[hidden]'
|
|
214
|
-
: 'main :is(.assessment-panel, .short-note, .compact-example, .keyed-table__body tr:not(.keyed-table__more-row))[hidden]';
|
|
215
|
-
const hidden = Array.from(
|
|
216
|
-
document.querySelectorAll<HTMLElement>(hiddenSelector),
|
|
217
|
-
(element): SavedHidden => {
|
|
218
|
-
const saved = { element, hidden: element.hidden };
|
|
219
|
-
element.hidden = false;
|
|
220
|
-
return saved;
|
|
221
|
-
},
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
const links =
|
|
225
|
-
mode === 'print'
|
|
226
|
-
? Array.from(
|
|
227
|
-
document.querySelectorAll<HTMLAnchorElement>('main a[href]'),
|
|
228
|
-
).flatMap((element): SavedLink[] => {
|
|
229
|
-
const printHref = canonicalPrintHref(
|
|
230
|
-
element,
|
|
231
|
-
canonicalBaseUrl,
|
|
232
|
-
siteBaseUrl,
|
|
233
|
-
);
|
|
234
|
-
if (!printHref) return [];
|
|
235
|
-
const saved = {
|
|
236
|
-
element,
|
|
237
|
-
href: element.getAttribute('href'),
|
|
238
|
-
};
|
|
239
|
-
// PDF viewers retain anchors as clickable annotations. Point the
|
|
240
|
-
// anchor itself at the deployed lesson while printing; no visible
|
|
241
|
-
// URL suffix is needed in the document text.
|
|
242
|
-
element.setAttribute('href', printHref);
|
|
243
|
-
return [saved];
|
|
244
|
-
})
|
|
245
|
-
: [];
|
|
246
|
-
|
|
247
|
-
prepared = { mode, disclosures, hidden, links };
|
|
248
|
-
if (mode === 'print') root.dataset.explicoPrintReady = '';
|
|
283
|
+
.explico-downloads__grid tbody th {
|
|
284
|
+
font-weight: 600;
|
|
285
|
+
line-height: 1.25;
|
|
286
|
+
max-width: 11rem;
|
|
287
|
+
padding-right: 0.75rem;
|
|
249
288
|
}
|
|
250
289
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
290
|
+
.explico-downloads__scope {
|
|
291
|
+
color: var(--sl-color-gray-3);
|
|
292
|
+
display: block;
|
|
293
|
+
font-weight: 400;
|
|
294
|
+
}
|
|
255
295
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const cleanupMarkers = () => {
|
|
268
|
-
if (pendingMarkerCleanup !== cleanupMarkers) return;
|
|
269
|
-
saved.disclosures.releaseMarkers();
|
|
270
|
-
delete root.dataset.explicoStaticExport;
|
|
271
|
-
pendingMarkerCleanup = null;
|
|
272
|
-
};
|
|
273
|
-
pendingMarkerCleanup = cleanupMarkers;
|
|
274
|
-
window.setTimeout(cleanupMarkers, 0);
|
|
296
|
+
.explico-downloads__cell {
|
|
297
|
+
align-items: center;
|
|
298
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
299
|
+
border-radius: 0.4rem;
|
|
300
|
+
color: var(--sl-color-text-accent);
|
|
301
|
+
display: inline-flex;
|
|
302
|
+
font-variant-numeric: tabular-nums;
|
|
303
|
+
gap: 0.35rem;
|
|
304
|
+
padding: 0.2rem 0.45rem;
|
|
305
|
+
text-decoration: none;
|
|
306
|
+
white-space: nowrap;
|
|
275
307
|
}
|
|
276
308
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
});
|
|
309
|
+
a.explico-downloads__cell:hover,
|
|
310
|
+
a.explico-downloads__cell:focus-visible {
|
|
311
|
+
border-color: var(--sl-color-accent);
|
|
281
312
|
}
|
|
282
313
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return new Promise((resolve) => {
|
|
287
|
-
let timeout = 0;
|
|
288
|
-
const finish = () => {
|
|
289
|
-
window.clearTimeout(timeout);
|
|
290
|
-
island.removeEventListener('astro:hydrate', finish);
|
|
291
|
-
island.removeEventListener('astro:hydration-error', finish);
|
|
292
|
-
resolve();
|
|
293
|
-
};
|
|
294
|
-
island.addEventListener('astro:hydrate', finish, { once: true });
|
|
295
|
-
island.addEventListener('astro:hydration-error', finish, { once: true });
|
|
296
|
-
timeout = window.setTimeout(finish, 10_000);
|
|
297
|
-
requestAnimationFrame(() => {
|
|
298
|
-
if (!island.hasAttribute('ssr')) finish();
|
|
299
|
-
});
|
|
300
|
-
});
|
|
314
|
+
.explico-downloads__cell--pending {
|
|
315
|
+
color: var(--sl-color-gray-3);
|
|
316
|
+
cursor: default;
|
|
301
317
|
}
|
|
302
318
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
document.querySelectorAll<HTMLElement>('main astro-island[ssr][client="visible"]'),
|
|
310
|
-
).filter((island) => !island.closest('details:not([open]), [hidden], [data-print-omit]'));
|
|
311
|
-
if (islands.length === 0) return;
|
|
312
|
-
|
|
313
|
-
const scrollX = window.scrollX;
|
|
314
|
-
const scrollY = window.scrollY;
|
|
315
|
-
const scrollBehavior = root.style.scrollBehavior;
|
|
316
|
-
root.style.scrollBehavior = 'auto';
|
|
317
|
-
try {
|
|
318
|
-
// `client:visible` deliberately does not hydrate an off-screen plot.
|
|
319
|
-
// Visit each island briefly so its normal Astro directive runs before
|
|
320
|
-
// the browser snapshots the document for printing.
|
|
321
|
-
for (const island of islands) {
|
|
322
|
-
if (!island.isConnected || !island.hasAttribute('ssr')) continue;
|
|
323
|
-
const hydrated = waitForIslandHydration(island);
|
|
324
|
-
// Astro's `client:visible` directive observes an island's children,
|
|
325
|
-
// while the custom-element wrapper itself may have no layout box.
|
|
326
|
-
(island.firstElementChild ?? island).scrollIntoView({
|
|
327
|
-
block: 'center',
|
|
328
|
-
inline: 'nearest',
|
|
329
|
-
});
|
|
330
|
-
await hydrated;
|
|
331
|
-
}
|
|
332
|
-
} finally {
|
|
333
|
-
window.scrollTo({ left: scrollX, top: scrollY, behavior: 'auto' });
|
|
334
|
-
root.style.scrollBehavior = scrollBehavior;
|
|
335
|
-
}
|
|
319
|
+
/* Column headings name the format on a wide menu; the cell itself does
|
|
320
|
+
on a narrow one, where the grid turns into rows of labelled buttons. */
|
|
321
|
+
.explico-downloads__format {
|
|
322
|
+
display: none;
|
|
323
|
+
color: var(--sl-color-text);
|
|
324
|
+
font-weight: 600;
|
|
336
325
|
}
|
|
337
326
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
await document.fonts?.ready;
|
|
341
|
-
await Promise.all(
|
|
342
|
-
Array.from(document.querySelectorAll<HTMLImageElement>('main img'), (image) => {
|
|
343
|
-
if (!image.complete) {
|
|
344
|
-
return new Promise<void>((resolve) => {
|
|
345
|
-
image.addEventListener('load', () => resolve(), { once: true });
|
|
346
|
-
image.addEventListener('error', () => resolve(), { once: true });
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
return typeof image.decode === 'function'
|
|
350
|
-
? image.decode().catch(() => undefined)
|
|
351
|
-
: Promise.resolve();
|
|
352
|
-
}),
|
|
353
|
-
);
|
|
354
|
-
// React chart effects and SVG layout settle after island hydration.
|
|
355
|
-
await afterNextPaint();
|
|
327
|
+
.explico-downloads__none {
|
|
328
|
+
color: var(--sl-color-gray-4);
|
|
356
329
|
}
|
|
357
330
|
|
|
331
|
+
.explico-downloads__note {
|
|
332
|
+
color: var(--sl-color-gray-3);
|
|
333
|
+
font-size: 0.7rem;
|
|
334
|
+
line-height: 1.4;
|
|
335
|
+
margin: 0.55rem 0 0;
|
|
336
|
+
max-width: 24rem;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
@media (max-width: 30rem) {
|
|
340
|
+
.explico-downloads {
|
|
341
|
+
width: auto;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Without columns, a format a scope lacks is simply not listed. */
|
|
345
|
+
.explico-downloads__grid td:has(> .explico-downloads__none) {
|
|
346
|
+
display: none;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.explico-downloads__grid thead {
|
|
350
|
+
display: none;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.explico-downloads__grid tbody tr {
|
|
354
|
+
display: grid;
|
|
355
|
+
gap: 0.35rem;
|
|
356
|
+
grid-template-columns: 1fr 1fr;
|
|
357
|
+
padding: 0.4rem 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.explico-downloads__grid tbody th {
|
|
361
|
+
grid-column: 1 / -1;
|
|
362
|
+
max-width: none;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.explico-downloads__grid td {
|
|
366
|
+
padding: 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.explico-downloads__cell {
|
|
370
|
+
box-sizing: border-box;
|
|
371
|
+
justify-content: space-between;
|
|
372
|
+
width: 100%;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.explico-downloads__format {
|
|
376
|
+
display: inline;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
</style>
|
|
380
|
+
|
|
381
|
+
<script>
|
|
382
|
+
import { installPrintController, printPage } from './print-controller';
|
|
383
|
+
|
|
358
384
|
function wirePrintActions(): void {
|
|
359
385
|
document
|
|
360
386
|
.querySelectorAll<HTMLButtonElement>(
|
|
@@ -362,56 +388,104 @@ const {
|
|
|
362
388
|
)
|
|
363
389
|
.forEach((button) => {
|
|
364
390
|
button.dataset.explicoPrintWired = '';
|
|
365
|
-
button.addEventListener('click',
|
|
366
|
-
button.disabled = true;
|
|
367
|
-
button.setAttribute('aria-busy', 'true');
|
|
368
|
-
window.dispatchEvent(
|
|
369
|
-
new CustomEvent('explico:prepare-static-export', {
|
|
370
|
-
detail: { mode: 'print' },
|
|
371
|
-
}),
|
|
372
|
-
);
|
|
373
|
-
try {
|
|
374
|
-
await waitForPrintVisuals();
|
|
375
|
-
window.print();
|
|
376
|
-
} finally {
|
|
377
|
-
button.disabled = false;
|
|
378
|
-
button.removeAttribute('aria-busy');
|
|
379
|
-
}
|
|
380
|
-
});
|
|
391
|
+
button.addEventListener('click', () => void printPage(button));
|
|
381
392
|
});
|
|
382
393
|
}
|
|
383
394
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
395
|
+
/** "1.9 MB" — what the menu shows beside a download. */
|
|
396
|
+
function formatSize(bytes: number): string {
|
|
397
|
+
if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
|
398
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const downloadIndexes = new Map<string, Promise<Record<string, number> | null>>();
|
|
402
|
+
|
|
403
|
+
function downloadIndex(href: string): Promise<Record<string, number> | null> {
|
|
404
|
+
let index = downloadIndexes.get(href);
|
|
405
|
+
if (!index) {
|
|
406
|
+
index = fetch(href)
|
|
407
|
+
.then((response) => (response.ok ? response.json() : null))
|
|
408
|
+
.then((body: { files?: Record<string, number> } | null) => body?.files ?? null)
|
|
409
|
+
.catch(() => null);
|
|
410
|
+
downloadIndexes.set(href, index);
|
|
411
|
+
}
|
|
412
|
+
return index;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Sizes beside each download; a download the build did not write disappears. */
|
|
416
|
+
async function fillDownloadSizes(menu: HTMLElement): Promise<void> {
|
|
417
|
+
if (menu.dataset.explicoSizes !== undefined) return;
|
|
418
|
+
menu.dataset.explicoSizes = '';
|
|
419
|
+
const href = menu.dataset.indexHref;
|
|
420
|
+
const files = href ? await downloadIndex(href) : null;
|
|
421
|
+
if (!files) return;
|
|
422
|
+
menu.querySelectorAll<HTMLAnchorElement>('a[data-download-path]').forEach((link) => {
|
|
423
|
+
const size = files[link.dataset.downloadPath ?? ''];
|
|
424
|
+
if (size === undefined) {
|
|
425
|
+
const none = document.createElement('span');
|
|
426
|
+
none.className = 'explico-downloads__none';
|
|
427
|
+
none.setAttribute('aria-hidden', 'true');
|
|
428
|
+
none.textContent = '—';
|
|
429
|
+
link.replaceWith(none);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const label = link.querySelector<HTMLElement>('[data-download-size]');
|
|
433
|
+
if (label) label.textContent = `↓ ${formatSize(size)}`;
|
|
434
|
+
link.setAttribute('aria-label', `${link.getAttribute('aria-label') ?? ''} (${formatSize(size)})`);
|
|
393
435
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** Under its button, right-aligned, scrolling in a short window; a bottom sheet on a phone. */
|
|
439
|
+
function placeDownloadMenu(menu: HTMLElement): void {
|
|
440
|
+
const toggle = document.querySelector<HTMLElement>(`[popovertarget="${menu.id}"]`);
|
|
441
|
+
if (!toggle) return;
|
|
442
|
+
// On a phone the button sits low in Starlight's mobile menu, with little
|
|
443
|
+
// room under it: the menu is a sheet along the bottom of the screen.
|
|
444
|
+
if (window.innerWidth <= 480) {
|
|
445
|
+
Object.assign(menu.style, {
|
|
446
|
+
top: 'auto',
|
|
447
|
+
bottom: '0.5rem',
|
|
448
|
+
left: '0.5rem',
|
|
449
|
+
right: '0.5rem',
|
|
450
|
+
maxHeight: 'calc(100dvh - 1rem)',
|
|
451
|
+
});
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const box = toggle.getBoundingClientRect();
|
|
455
|
+
const top = Math.round(box.bottom + 8);
|
|
456
|
+
Object.assign(menu.style, {
|
|
457
|
+
top: `${top}px`,
|
|
458
|
+
bottom: 'auto',
|
|
459
|
+
left: 'auto',
|
|
460
|
+
right: `${Math.max(8, Math.round(window.innerWidth - box.right))}px`,
|
|
461
|
+
maxHeight: `calc(100dvh - ${top + 8}px)`,
|
|
414
462
|
});
|
|
415
|
-
if (printMedia.matches) requestPrintPreparation();
|
|
416
463
|
}
|
|
464
|
+
|
|
465
|
+
function wireDownloadMenus(): void {
|
|
466
|
+
document
|
|
467
|
+
.querySelectorAll<HTMLElement>('[data-explico-downloads]:not([data-explico-downloads-wired])')
|
|
468
|
+
.forEach((menu) => {
|
|
469
|
+
menu.dataset.explicoDownloadsWired = '';
|
|
470
|
+
menu.addEventListener('beforetoggle', (event) => {
|
|
471
|
+
if ((event as ToggleEvent).newState !== 'open') return;
|
|
472
|
+
placeDownloadMenu(menu);
|
|
473
|
+
void fillDownloadSizes(menu);
|
|
474
|
+
});
|
|
475
|
+
menu.addEventListener('toggle', (event) => {
|
|
476
|
+
document
|
|
477
|
+
.querySelector(`[popovertarget="${menu.id}"]`)
|
|
478
|
+
?.setAttribute('aria-expanded', String((event as ToggleEvent).newState === 'open'));
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
wirePrintActions();
|
|
484
|
+
wireDownloadMenus();
|
|
485
|
+
document.addEventListener('astro:page-load', () => {
|
|
486
|
+
wirePrintActions();
|
|
487
|
+
wireDownloadMenus();
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
installPrintController();
|
|
417
491
|
</script>
|