explico 0.13.1 → 0.15.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 +990 -1
- package/README.md +200 -30
- package/dist/compiler/astro.d.ts +15 -2
- package/dist/compiler/astro.js +40 -11
- package/dist/compiler/cli-main.d.ts +100 -0
- package/dist/compiler/cli-main.js +433 -0
- package/dist/compiler/cli-reference.d.ts +50 -0
- package/dist/compiler/cli-reference.js +213 -0
- package/dist/compiler/cli-verbs.d.ts +45 -0
- package/dist/compiler/cli-verbs.js +224 -0
- package/dist/compiler/cli-verify.d.ts +51 -0
- package/dist/compiler/cli-verify.js +151 -0
- package/dist/compiler/cli.d.ts +408 -24
- package/dist/compiler/cli.js +1328 -8
- package/dist/compiler/collections.d.ts +88 -1
- package/dist/compiler/collections.js +170 -25
- package/dist/compiler/content-release.d.ts +49 -0
- package/dist/compiler/content-release.js +63 -0
- package/dist/compiler/content-schemas.d.ts +2 -2
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +105 -1
- package/dist/compiler/decks.d.ts +161 -0
- package/dist/compiler/decks.js +235 -0
- package/dist/compiler/exports/combined-html.d.ts +5 -1
- package/dist/compiler/exports/deck-fit.d.ts +55 -0
- package/dist/compiler/exports/deck-fit.js +90 -0
- package/dist/compiler/exports/export-chapters.d.ts +37 -6
- package/dist/compiler/exports/export-chapters.js +64 -5
- package/dist/compiler/exports/export-pages.d.ts +30 -3
- package/dist/compiler/exports/export-pages.js +24 -1
- package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/dist/compiler/exports/latex/cache.d.ts +78 -0
- package/dist/compiler/exports/latex/cache.js +129 -0
- package/dist/compiler/exports/latex/capture.d.ts +10 -1
- package/dist/compiler/exports/latex/capture.js +3 -1
- package/dist/compiler/exports/latex/compile.d.ts +13 -2
- package/dist/compiler/exports/latex/compile.js +12 -1
- package/dist/compiler/exports/latex/deck.d.ts +92 -0
- package/dist/compiler/exports/latex/deck.js +349 -0
- package/dist/compiler/exports/latex/document.d.ts +51 -6
- package/dist/compiler/exports/latex/document.js +71 -12
- package/dist/compiler/exports/latex/explico-common.sty +198 -0
- package/dist/compiler/exports/latex/explico.cls +13 -183
- package/dist/compiler/exports/latex/serialize.d.ts +60 -4
- package/dist/compiler/exports/latex/serialize.js +4 -2
- package/dist/compiler/exports/latex-export.d.ts +24 -2
- package/dist/compiler/exports/latex-export.js +107 -26
- package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
- package/dist/compiler/exports/markdown/serialize.js +85 -8
- package/dist/compiler/exports/markdown-export.d.ts +6 -1
- package/dist/compiler/exports/markdown-export.js +50 -11
- package/dist/compiler/exports/standalone-html.d.ts +10 -5
- package/dist/compiler/exports/standalone-html.js +3 -3
- package/dist/compiler/exports/static-tree.d.ts +38 -1
- package/dist/compiler/exports/static-tree.js +120 -4
- package/dist/compiler/knowledge-version.d.ts +26 -2
- package/dist/compiler/knowledge-version.js +27 -17
- package/dist/compiler/lesson-derivation.d.ts +143 -6
- package/dist/compiler/lesson-derivation.js +145 -13
- package/dist/compiler/manifest.d.ts +133 -4
- package/dist/compiler/manifest.js +364 -58
- package/dist/compiler/page-versions.d.ts +184 -0
- package/dist/compiler/page-versions.js +217 -0
- package/dist/compiler/render-diagnostics.d.ts +5 -1
- package/dist/compiler/resolution-report.d.ts +24 -6
- package/dist/compiler/resolution-report.js +22 -4
- package/dist/compiler/validate-content.d.ts +7 -0
- package/dist/compiler/validate-content.js +160 -0
- package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
- package/dist/curriculum/competency-graph.d.ts +54 -0
- package/dist/curriculum/competency-graph.js +41 -0
- package/dist/reference/base-library.d.mts +8 -0
- package/dist/reference/base-library.mjs +19 -0
- package/dist/reference/consistency.d.ts +8 -4
- package/dist/reference/consistency.js +82 -26
- package/dist/reference/deck-remark.d.mts +123 -0
- package/dist/reference/deck-remark.mjs +600 -0
- package/dist/reference/deck-source.d.ts +107 -0
- package/dist/reference/deck-source.js +140 -0
- package/dist/reference/decks-validate.d.ts +133 -0
- package/dist/reference/decks-validate.js +359 -0
- package/dist/reference/derived-entries.d.ts +5 -1
- package/dist/reference/diagram-fit.d.ts +6 -2
- package/dist/reference/diagram-fit.js +5 -1
- package/dist/reference/figure-symbols.d.ts +23 -6
- package/dist/reference/figure-symbols.js +51 -15
- package/dist/reference/float-name.d.ts +43 -0
- package/dist/reference/float-name.js +58 -0
- package/dist/reference/folds-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-indices.js +5 -4
- package/dist/reference/gate-math.d.ts +29 -2
- package/dist/reference/gate-math.js +99 -18
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/lab-katex-validate.d.ts +10 -6
- package/dist/reference/lab-katex-validate.js +17 -9
- package/dist/reference/lab-math-scope.js +29 -4
- package/dist/reference/math-glyphs.d.mts +25 -0
- package/dist/reference/math-glyphs.mjs +189 -25
- package/dist/reference/page-notation.d.ts +22 -3
- package/dist/reference/page-notation.js +32 -10
- package/dist/reference/references.d.ts +14 -1
- package/dist/reference/references.js +13 -0
- package/dist/reference/registry-scope.d.mts +33 -0
- package/dist/reference/registry-scope.mjs +34 -0
- package/dist/reference/registry.js +29 -10
- package/dist/reference/rehype-deck.d.mts +6 -0
- package/dist/reference/rehype-deck.mjs +286 -0
- package/dist/reference/rehype-heading-anchors.d.mts +20 -0
- package/dist/reference/rehype-heading-anchors.mjs +48 -6
- package/dist/reference/remark-notation.mjs +249 -40
- package/dist/reference/source-lines.d.mts +12 -0
- package/dist/reference/source-lines.mjs +65 -0
- package/dist/reference/types.d.ts +28 -0
- package/dist/rules/catalog.d.ts +21 -4
- package/dist/rules/catalog.js +237 -2
- package/dist/rules/course-rules.d.ts +5 -1
- package/dist/rules/course-rules.js +17 -3
- package/dist/rules/course-rules.schema.json +12 -0
- package/dist/rules/in-effect.d.ts +13 -3
- package/dist/rules/in-effect.js +34 -10
- package/dist/rules/rules.schema.json +280 -1
- package/dist/rules/settings.d.ts +1 -1
- package/dist/session/PreferenceStore.d.ts +6 -0
- package/dist/session/PreferenceStore.js +18 -1
- package/dist/session/deck-memory.d.ts +116 -0
- package/dist/session/deck-memory.js +143 -0
- package/dist/session/reading-memory.d.ts +96 -0
- package/dist/session/reading-memory.js +133 -0
- package/docs/VERSIONING.md +21 -18
- package/docs/authoring/README.md +31 -9
- package/docs/authoring/agents.md +72 -18
- package/docs/authoring/cli-reference.json +2172 -0
- package/docs/authoring/cli.md +887 -0
- package/docs/authoring/components.md +15 -0
- package/docs/authoring/deck.md +612 -0
- package/docs/authoring/migrating.md +1195 -16
- package/docs/authoring/notation.md +49 -6
- package/docs/authoring/structure.md +80 -1
- package/docs/authoring/versioning.md +56 -3
- package/docs/authoring/visualizations.md +7 -2
- package/docs/course-agent/AGENTS.md +40 -0
- package/docs/course-agent/agents/rules-reviewer.md +33 -0
- package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
- package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
- package/docs/course-agent/skills/release-content/SKILL.md +26 -0
- package/docs/diagram-scaling.md +148 -0
- package/docs/privacy.md +59 -0
- package/package.json +8 -2
- package/src/compiler/astro.ts +60 -21
- package/src/compiler/cli-main.ts +557 -0
- package/src/compiler/cli-reference.ts +285 -0
- package/src/compiler/cli-verbs.ts +270 -0
- package/src/compiler/cli-verify.ts +241 -0
- package/src/compiler/cli.ts +2119 -170
- package/src/compiler/collections.ts +284 -25
- package/src/compiler/content-release.ts +91 -0
- package/src/compiler/course-config.ts +267 -0
- package/src/compiler/decks.ts +455 -0
- package/src/compiler/exports/deck-fit.ts +127 -0
- package/src/compiler/exports/export-chapters.ts +105 -6
- package/src/compiler/exports/export-pages.ts +46 -2
- package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
- package/src/compiler/exports/latex/cache.ts +183 -0
- package/src/compiler/exports/latex/capture.ts +5 -2
- package/src/compiler/exports/latex/compile.ts +18 -0
- package/src/compiler/exports/latex/deck.ts +419 -0
- package/src/compiler/exports/latex/document.ts +117 -21
- package/src/compiler/exports/latex/explico-common.sty +198 -0
- package/src/compiler/exports/latex/explico.cls +13 -183
- package/src/compiler/exports/latex/serialize.ts +25 -21
- package/src/compiler/exports/latex/texlive-packages.txt +6 -2
- package/src/compiler/exports/latex-export.ts +187 -45
- package/src/compiler/exports/markdown/serialize.ts +118 -9
- package/src/compiler/exports/markdown-export.ts +56 -11
- package/src/compiler/exports/standalone-html.ts +8 -7
- package/src/compiler/exports/static-tree.ts +175 -3
- package/src/compiler/knowledge-version.ts +56 -21
- package/src/compiler/lesson-derivation.ts +282 -16
- package/src/compiler/manifest.ts +568 -63
- package/src/compiler/page-versions.ts +401 -0
- package/src/compiler/resolution-report.ts +38 -7
- package/src/compiler/validate-content.ts +215 -0
- package/src/components/CourseContents.astro +124 -6
- package/src/components/CurriculumMap.astro +49 -1
- package/src/components/FloatIndex.astro +1 -1
- package/src/components/PartOverview.astro +51 -5
- package/src/components/assessments/AssessmentSet.astro +41 -15
- package/src/components/block-heading.ts +15 -6
- package/src/components/card-carry.ts +7 -4
- package/src/components/card-manager.ts +14 -2
- package/src/components/card-memory.ts +13 -9
- package/src/components/deck/DeckEnhancer.astro +13 -0
- package/src/components/deck/deck-context.ts +104 -0
- package/src/components/deck/deck-controller.ts +1287 -0
- package/src/components/deck/deck-map.ts +91 -0
- package/src/components/deck-lane.ts +163 -0
- package/src/components/diagram/Diagram.astro +131 -86
- package/src/components/disclosure/persisted-disclosure.ts +9 -1
- package/src/components/figure/Figure.astro +7 -2
- package/src/components/figure/FloatCaption.astro +5 -3
- package/src/components/fold/Fold.astro +2 -2
- package/src/components/notation/NotationFormula.astro +81 -11
- package/src/components/notation/NotationGlossary.astro +132 -1
- package/src/components/notation/NotationLayer.astro +15 -7
- package/src/components/starlight/AboutPanel.astro +186 -3
- package/src/components/starlight/CollapsibleSections.astro +24 -3
- package/src/components/starlight/DiagramEnhancer.astro +5 -1
- package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
- package/src/components/starlight/LayoutHeader.astro +17 -0
- package/src/components/starlight/LayoutPageTitle.astro +110 -0
- package/src/components/starlight/LayoutSidebar.astro +807 -21
- package/src/components/starlight/LayoutSocialIcons.astro +62 -6
- package/src/components/starlight/LessonExportActions.astro +27 -9
- package/src/components/starlight/LessonFooter.astro +59 -0
- package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
- package/src/components/starlight/TextSizeControl.astro +24 -13
- package/src/components/starlight/page-marks.ts +139 -0
- package/src/components/starlight/section-summary.ts +1 -1
- package/src/components/starlight/sidebar-editions.ts +245 -0
- package/src/components/table/Table.astro +15 -7
- package/src/content-config.ts +87 -1
- package/src/curriculum/competency-graph.ts +95 -0
- package/src/reference/base-library.mjs +19 -0
- package/src/reference/consistency.ts +137 -39
- package/src/reference/deck-remark.mjs +600 -0
- package/src/reference/deck-source.ts +273 -0
- package/src/reference/decks-validate.ts +604 -0
- package/src/reference/diagram-fit.ts +18 -4
- package/src/reference/diagram-layout.ts +3 -1
- package/src/reference/figure-symbols.ts +108 -31
- package/src/reference/folds-validate.ts +1 -1
- package/src/reference/gate-indices.ts +5 -3
- package/src/reference/gate-math.ts +169 -25
- package/src/reference/lab-katex-validate.ts +30 -15
- package/src/reference/lab-math-scope.ts +34 -3
- package/src/reference/math-glyphs.mjs +189 -25
- package/src/reference/page-notation.ts +69 -15
- package/src/reference/references.ts +28 -0
- package/src/reference/registry-scope.mjs +34 -0
- package/src/reference/registry.ts +41 -11
- package/src/reference/rehype-deck.mjs +286 -0
- package/src/reference/rehype-heading-anchors.mjs +48 -6
- package/src/reference/remark-notation.mjs +249 -40
- package/src/reference/source-lines.mjs +65 -0
- package/src/reference/types.ts +28 -0
- package/src/rules/catalog.ts +274 -4
- package/src/rules/course-rules.ts +21 -1
- package/src/rules/in-effect.ts +58 -11
- package/src/session/PreferenceStore.ts +23 -0
- package/src/session/deck-memory.ts +263 -0
- package/src/session/reading-memory.ts +243 -0
- package/src/styles/deck.css +1150 -0
- package/src/styles/global.css +958 -27
- package/src/styles/zones.css +277 -0
- /package/src/{components → reference}/float-name.ts +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deck's source read as frames (`docs/authoring/deck.md`, rule D1): `##`
|
|
3
|
+
* opens a section, which is a divider frame of its own; `###` is a frame,
|
|
4
|
+
* whose title is the heading; a frame's body runs from its heading to the next
|
|
5
|
+
* `##` or `###`. Pure — the manifest, the deck rules (`decks-validate.ts`),
|
|
6
|
+
* the `content` CLI and the tests read a deck through this one reader, so a
|
|
7
|
+
* frame is the same frame for all of them.
|
|
8
|
+
*
|
|
9
|
+
* A heading inside a component or a code block is not an outline heading
|
|
10
|
+
* (`scanOutlineBlocks`), so a `###` inside `<Notes>` never starts a frame.
|
|
11
|
+
*/
|
|
12
|
+
import { stripFencedCode } from './equations.mjs';
|
|
13
|
+
import { headingChapter, headingMorphs } from './rehype-heading-anchors.mjs';
|
|
14
|
+
import { scanOutlineBlocks, scanSectionHeadings } from './sections.mjs';
|
|
15
|
+
|
|
16
|
+
/** A step mark, `<Pause />`, with the props of a driven figure or a spotlight. */
|
|
17
|
+
export interface DeckPause {
|
|
18
|
+
/** Offset of the tag in the deck body. */
|
|
19
|
+
readonly index: number;
|
|
20
|
+
/** The label of the lab control a driven figure moves. */
|
|
21
|
+
readonly control?: string;
|
|
22
|
+
readonly from?: string;
|
|
23
|
+
readonly to?: string;
|
|
24
|
+
readonly seconds?: string;
|
|
25
|
+
/** A notation key whose every occurrence on the frame lights up. */
|
|
26
|
+
readonly highlight?: string;
|
|
27
|
+
/** Any prop the deck grammar does not know, by name. */
|
|
28
|
+
readonly unknown: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DeckRecall {
|
|
32
|
+
readonly index: number;
|
|
33
|
+
/** As written: `eq-range`, `fig-projectile-trajectory`, `tab-…`, `dia-…`. */
|
|
34
|
+
readonly ref?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DeckFloatTag {
|
|
38
|
+
readonly index: number;
|
|
39
|
+
readonly name: 'Figure' | 'Table' | 'Diagram' | 'CompactExamples' | 'AssessmentSet';
|
|
40
|
+
readonly id?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface DeckMath {
|
|
44
|
+
readonly index: number;
|
|
45
|
+
readonly latex: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DeckFrameSource {
|
|
49
|
+
/** `section`: a `##` divider frame; `frame`: a `###` frame. */
|
|
50
|
+
readonly kind: 'section' | 'frame';
|
|
51
|
+
/** What the reader sees: math as it reads, a `[[key]]` term as its label. */
|
|
52
|
+
readonly title: string;
|
|
53
|
+
/** The heading's anchor, as the page gives it. */
|
|
54
|
+
readonly slug: string;
|
|
55
|
+
/** `### Title [morph]`: the frame morphs from the one before it. */
|
|
56
|
+
readonly morph: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* `## Title [chapter: <slug>]` on a part's deck: the chapter the section
|
|
59
|
+
* covers, as written (the chapter's name in the part's directory). A `###`
|
|
60
|
+
* frame carries its section's.
|
|
61
|
+
*/
|
|
62
|
+
readonly chapter?: string;
|
|
63
|
+
/** Offset of the heading line in the deck body. */
|
|
64
|
+
readonly index: number;
|
|
65
|
+
/** Offset where the frame's body ends (the next frame's heading, or the end). */
|
|
66
|
+
readonly end: number;
|
|
67
|
+
/** The slug of the `##` section the frame is in, if any. */
|
|
68
|
+
readonly section?: string;
|
|
69
|
+
/** The frame's body source, after its heading line. */
|
|
70
|
+
readonly body: string;
|
|
71
|
+
/** Offset of {@link body} in the deck body. */
|
|
72
|
+
readonly bodyIndex: number;
|
|
73
|
+
readonly pauses: readonly DeckPause[];
|
|
74
|
+
/** Each `<Notes>…</Notes>` block's inner text. */
|
|
75
|
+
readonly notes: readonly { readonly index: number; readonly text: string }[];
|
|
76
|
+
readonly recalls: readonly DeckRecall[];
|
|
77
|
+
readonly floats: readonly DeckFloatTag[];
|
|
78
|
+
/** Display equations in the frame's own text (not its notes). */
|
|
79
|
+
readonly displayMath: readonly DeckMath[];
|
|
80
|
+
/** Words a reader sees on the frame: its prose, not its math, tags or notes. */
|
|
81
|
+
readonly words: number;
|
|
82
|
+
/** List items on the frame. */
|
|
83
|
+
readonly bullets: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface DeckSource {
|
|
87
|
+
/** Every section divider and frame, in order. */
|
|
88
|
+
readonly frames: readonly DeckFrameSource[];
|
|
89
|
+
/** Headings a deck cannot hold: `#`, and `####` or deeper (rule D1). */
|
|
90
|
+
readonly deepHeadings: readonly { readonly index: number; readonly level: number; readonly text: string }[];
|
|
91
|
+
/**
|
|
92
|
+
* Content that belongs to no frame (rule D1): before the first heading, or
|
|
93
|
+
* under a `##` before its first `###`.
|
|
94
|
+
*/
|
|
95
|
+
readonly loose: readonly { readonly index: number; readonly where: 'before' | 'section' }[];
|
|
96
|
+
/** A heading marker on the wrong level: `[chapter: …]` on a `###`, `[morph]` on a `##`. */
|
|
97
|
+
readonly misplacedMarkers: readonly {
|
|
98
|
+
readonly index: number;
|
|
99
|
+
readonly marker: 'chapter' | 'morph';
|
|
100
|
+
readonly title: string;
|
|
101
|
+
}[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const FLOAT_NAMES = ['Figure', 'Table', 'Diagram', 'CompactExamples', 'AssessmentSet'] as const;
|
|
105
|
+
const FLOAT_TAG = new RegExp(`<(${FLOAT_NAMES.join('|')})\\b((?:"[^"]*"|'[^']*'|[^>"'])*)>`, 'g');
|
|
106
|
+
const PAUSE_TAG = /<Pause\b((?:"[^"]*"|'[^']*'|[^>"'])*)\/?>/g;
|
|
107
|
+
const RECALL_TAG = /<Recall\b((?:"[^"]*"|'[^']*'|[^>"'])*)\/?>/g;
|
|
108
|
+
const NOTES_BLOCK = /<Notes\s*>([\s\S]*?)<\/Notes\s*>/g;
|
|
109
|
+
const DISPLAY_MATH = /\$\$([\s\S]+?)\$\$/g;
|
|
110
|
+
const PROP = /([A-Za-z][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
111
|
+
const PAUSE_PROPS = new Set(['control', 'from', 'to', 'seconds', 'highlight']);
|
|
112
|
+
|
|
113
|
+
/** A component tag's string props, by name. */
|
|
114
|
+
export function tagProps(source: string): Map<string, string> {
|
|
115
|
+
const props = new Map<string, string>();
|
|
116
|
+
for (const match of source.matchAll(PROP)) {
|
|
117
|
+
props.set(match[1] ?? '', match[2] ?? match[3] ?? '');
|
|
118
|
+
}
|
|
119
|
+
return props;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** `text` with every match of `pattern` blanked to spaces, newlines kept. */
|
|
123
|
+
function blankMatches(text: string, pattern: RegExp): string {
|
|
124
|
+
return text.replace(pattern, (match) => match.replace(/[^\n]/g, ' '));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The words a frame shows: its prose, without math, tags, comments, citations or notes. */
|
|
128
|
+
export function visibleWords(text: string): number {
|
|
129
|
+
const prose = text
|
|
130
|
+
.replace(NOTES_BLOCK, ' ')
|
|
131
|
+
.replace(/\{\/\*[\s\S]*?\*\/\}/g, ' ')
|
|
132
|
+
.replace(/\$\$[\s\S]+?\$\$/g, ' ')
|
|
133
|
+
.replace(/\$[^$\n]+\$/g, ' ')
|
|
134
|
+
.replace(/\[@[^\]]*\]/g, ' ')
|
|
135
|
+
.replace(/\[\[\s*[^\][]+?\s*\]\]/g, ' term ')
|
|
136
|
+
.replace(/<\/?[A-Za-z][^>]*>/g, ' ')
|
|
137
|
+
.replace(/^[ \t]*(?:import|export)\s.*$/gm, ' ')
|
|
138
|
+
.replace(/[#>*_`|~-]/g, ' ');
|
|
139
|
+
return (prose.match(/[\p{L}\p{N}][\p{L}\p{N}'’.-]*/gu) ?? []).length;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** List items in a frame's own text (not its notes). */
|
|
143
|
+
function bulletCount(text: string): number {
|
|
144
|
+
return (text.replace(NOTES_BLOCK, ' ').match(/^[ \t]*(?:[-*+]|\d+[.)])[ \t]+\S/gm) ?? []).length;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Read a deck body as frames. `labelFor` names a `[[key]]` term in a heading
|
|
149
|
+
* as the reader sees it, as for a chapter's outline.
|
|
150
|
+
*/
|
|
151
|
+
export function readDeckSource(
|
|
152
|
+
body: string,
|
|
153
|
+
options: { readonly labelFor?: (key: string) => string | undefined } = {},
|
|
154
|
+
): DeckSource {
|
|
155
|
+
const source = String(body);
|
|
156
|
+
const blocks = scanOutlineBlocks(source) as Array<
|
|
157
|
+
| { kind: 'heading'; level: number; text: string; index: number }
|
|
158
|
+
| { kind: 'element'; name: string; index: number }
|
|
159
|
+
| { kind: 'content'; index: number }
|
|
160
|
+
>;
|
|
161
|
+
const headings = scanSectionHeadings(source, { labelFor: options.labelFor }) as Array<{
|
|
162
|
+
level: number;
|
|
163
|
+
text: string;
|
|
164
|
+
slug: string;
|
|
165
|
+
}>;
|
|
166
|
+
// Code is not frame text: tags and math are looked for with it blanked.
|
|
167
|
+
const scanText = stripFencedCode(source);
|
|
168
|
+
|
|
169
|
+
const deepHeadings: { index: number; level: number; text: string }[] = [];
|
|
170
|
+
const loose: { index: number; where: 'before' | 'section' }[] = [];
|
|
171
|
+
const starts: { index: number; level: 2 | 3; heading: (typeof headings)[number]; lineEnd: number }[] = [];
|
|
172
|
+
let headingCursor = -1;
|
|
173
|
+
let current: 2 | 3 | undefined;
|
|
174
|
+
for (const block of blocks) {
|
|
175
|
+
if (block.kind === 'heading') {
|
|
176
|
+
headingCursor += 1;
|
|
177
|
+
const heading = headings[headingCursor];
|
|
178
|
+
if (heading === undefined) continue;
|
|
179
|
+
if (block.level === 2 || block.level === 3) {
|
|
180
|
+
const lineEnd = source.indexOf('\n', block.index);
|
|
181
|
+
starts.push({
|
|
182
|
+
index: block.index,
|
|
183
|
+
level: block.level,
|
|
184
|
+
heading,
|
|
185
|
+
lineEnd: lineEnd === -1 ? source.length : lineEnd + 1,
|
|
186
|
+
});
|
|
187
|
+
current = block.level;
|
|
188
|
+
} else {
|
|
189
|
+
deepHeadings.push({ index: block.index, level: block.level, text: heading.text });
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (current === undefined) loose.push({ index: block.index, where: 'before' });
|
|
194
|
+
else if (current === 2) loose.push({ index: block.index, where: 'section' });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const frames: DeckFrameSource[] = [];
|
|
198
|
+
const misplacedMarkers: { index: number; marker: 'chapter' | 'morph'; title: string }[] = [];
|
|
199
|
+
let section: string | undefined;
|
|
200
|
+
let sectionChapter: string | undefined;
|
|
201
|
+
starts.forEach((start, position) => {
|
|
202
|
+
const end = starts[position + 1]?.index ?? source.length;
|
|
203
|
+
const bodyIndex = Math.min(start.lineEnd, end);
|
|
204
|
+
const frameBody = source.slice(bodyIndex, end);
|
|
205
|
+
const frameScan = scanText.slice(bodyIndex, end);
|
|
206
|
+
const withoutNotes = blankMatches(frameScan, NOTES_BLOCK);
|
|
207
|
+
const at = (offset: number | undefined) => bodyIndex + (offset ?? 0);
|
|
208
|
+
const headingText = source.slice(start.index, start.lineEnd).replace(/^[ \t]*#{1,6}[ \t]+/, '').trimEnd();
|
|
209
|
+
const morphs = headingMorphs(headingText);
|
|
210
|
+
const named = headingChapter(headingText) ?? undefined;
|
|
211
|
+
if (start.level === 2) {
|
|
212
|
+
section = start.heading.slug;
|
|
213
|
+
sectionChapter = named;
|
|
214
|
+
if (morphs) misplacedMarkers.push({ index: start.index, marker: 'morph', title: start.heading.text });
|
|
215
|
+
} else if (named !== undefined) {
|
|
216
|
+
misplacedMarkers.push({ index: start.index, marker: 'chapter', title: start.heading.text });
|
|
217
|
+
}
|
|
218
|
+
frames.push({
|
|
219
|
+
kind: start.level === 2 ? 'section' : 'frame',
|
|
220
|
+
title: start.heading.text,
|
|
221
|
+
slug: start.heading.slug,
|
|
222
|
+
morph: start.level === 3 && morphs,
|
|
223
|
+
...(sectionChapter === undefined ? {} : { chapter: sectionChapter }),
|
|
224
|
+
index: start.index,
|
|
225
|
+
end,
|
|
226
|
+
...(start.level === 3 && section !== undefined ? { section } : {}),
|
|
227
|
+
body: frameBody,
|
|
228
|
+
bodyIndex,
|
|
229
|
+
pauses: [...withoutNotes.matchAll(PAUSE_TAG)].map((match) => {
|
|
230
|
+
const props = tagProps(match[1] ?? '');
|
|
231
|
+
const value = (name: string) => props.get(name);
|
|
232
|
+
return {
|
|
233
|
+
index: at(match.index),
|
|
234
|
+
...(value('control') === undefined ? {} : { control: value('control') }),
|
|
235
|
+
...(value('from') === undefined ? {} : { from: value('from') }),
|
|
236
|
+
...(value('to') === undefined ? {} : { to: value('to') }),
|
|
237
|
+
...(value('seconds') === undefined ? {} : { seconds: value('seconds') }),
|
|
238
|
+
...(value('highlight') === undefined ? {} : { highlight: value('highlight') }),
|
|
239
|
+
unknown: [...props.keys()].filter((name) => !PAUSE_PROPS.has(name)),
|
|
240
|
+
};
|
|
241
|
+
}),
|
|
242
|
+
notes: [...frameScan.matchAll(NOTES_BLOCK)].map((match) => ({
|
|
243
|
+
index: at(match.index),
|
|
244
|
+
text: match[1] ?? '',
|
|
245
|
+
})),
|
|
246
|
+
recalls: [...withoutNotes.matchAll(RECALL_TAG)].map((match) => {
|
|
247
|
+
const ref = tagProps(match[1] ?? '').get('ref');
|
|
248
|
+
return { index: at(match.index), ...(ref === undefined ? {} : { ref }) };
|
|
249
|
+
}),
|
|
250
|
+
floats: [...withoutNotes.matchAll(FLOAT_TAG)].map((match) => {
|
|
251
|
+
const id = tagProps(match[2] ?? '').get('id');
|
|
252
|
+
return {
|
|
253
|
+
index: at(match.index),
|
|
254
|
+
name: match[1] as DeckFloatTag['name'],
|
|
255
|
+
...(id === undefined ? {} : { id }),
|
|
256
|
+
};
|
|
257
|
+
}),
|
|
258
|
+
displayMath: [...withoutNotes.matchAll(DISPLAY_MATH)].map((match) => ({
|
|
259
|
+
index: at(match.index),
|
|
260
|
+
latex: match[1] ?? '',
|
|
261
|
+
})),
|
|
262
|
+
words: start.level === 3 ? visibleWords(withoutNotes) : 0,
|
|
263
|
+
bullets: start.level === 3 ? bulletCount(withoutNotes) : 0,
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return { frames, deepHeadings, loose, misplacedMarkers };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The frame an offset of the deck body falls in, if any. */
|
|
271
|
+
export function frameAt(source: DeckSource, index: number): DeckFrameSource | undefined {
|
|
272
|
+
return source.frames.find((frame) => index >= frame.index && index < frame.end);
|
|
273
|
+
}
|