explico 0.4.0 → 0.6.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 +382 -1
- package/README.md +103 -8
- package/dist/compiler/astro.d.ts +9 -1
- package/dist/compiler/astro.js +182 -3
- package/dist/compiler/cli.d.ts +13 -0
- package/dist/compiler/cli.js +28 -4
- package/dist/compiler/collections.d.ts +3 -2
- package/dist/compiler/collections.js +28 -1
- package/dist/compiler/course-config.d.ts +88 -1
- package/dist/compiler/course-config.js +35 -1
- package/dist/compiler/exports/standalone-html.d.ts +34 -0
- package/dist/compiler/exports/standalone-html.js +803 -0
- package/dist/compiler/manifest.d.ts +98 -6
- package/dist/compiler/manifest.js +154 -17
- package/dist/compiler/resolution-report.d.ts +4 -0
- package/dist/reference/base-abbreviations.d.mts +30 -0
- package/dist/reference/base-abbreviations.mjs +60 -0
- package/dist/reference/base-operators.d.mts +15 -0
- package/dist/reference/base-operators.mjs +40 -0
- package/dist/reference/base-roman-labels.d.mts +35 -0
- package/dist/reference/base-roman-labels.mjs +43 -0
- package/dist/reference/consistency.d.ts +8 -2
- package/dist/reference/consistency.js +50 -3
- package/dist/reference/diagrams-validate.d.ts +21 -0
- package/dist/reference/diagrams-validate.js +13 -0
- package/dist/reference/equations.d.mts +10 -15
- package/dist/reference/equations.mjs +67 -14
- package/dist/reference/gate-abbreviations.d.ts +17 -0
- package/dist/reference/gate-abbreviations.js +64 -0
- package/dist/reference/gate-heading-length.d.ts +12 -0
- package/dist/reference/gate-heading-length.js +79 -0
- package/dist/reference/gate-math.d.ts +32 -5
- package/dist/reference/gate-math.js +6 -2
- package/dist/reference/gate-prose-notation.d.ts +60 -0
- package/dist/reference/gate-prose-notation.js +125 -0
- package/dist/reference/keyed-float-validate.d.ts +5 -5
- package/dist/reference/keyed-jsx-floats.d.mts +7 -12
- package/dist/reference/keyed-jsx-floats.mjs +50 -25
- package/dist/reference/knowledge-checks-validate.d.ts +35 -0
- package/dist/reference/knowledge-checks-validate.js +78 -0
- package/dist/reference/knowledge-checks.d.mts +25 -0
- package/dist/reference/knowledge-checks.mjs +45 -0
- package/dist/reference/lab-math-scope.d.ts +9 -1
- package/dist/reference/lab-math-scope.js +32 -4
- package/dist/reference/math-glyphs.d.mts +42 -0
- package/dist/reference/math-glyphs.mjs +548 -40
- package/dist/reference/mdx-attributes.d.mts +13 -0
- package/dist/reference/mdx-attributes.mjs +38 -0
- package/dist/reference/mixed-content.d.ts +17 -0
- package/dist/reference/mixed-content.js +29 -0
- package/dist/reference/page-notation.d.ts +52 -0
- package/dist/reference/page-notation.js +171 -0
- package/dist/reference/raw-floats-validate.d.ts +18 -0
- package/dist/reference/raw-floats-validate.js +53 -0
- package/dist/reference/raw-floats.d.mts +18 -0
- package/dist/reference/raw-floats.mjs +129 -0
- package/dist/reference/rehype-heading-anchors.d.mts +1 -1
- package/dist/reference/rehype-heading-anchors.mjs +107 -1
- package/dist/reference/rehype-table-header-labels.d.mts +26 -0
- package/dist/reference/rehype-table-header-labels.mjs +152 -0
- package/dist/reference/remark-abbreviations.d.mts +7 -0
- package/dist/reference/remark-abbreviations.mjs +174 -0
- package/dist/reference/remark-citation.d.mts +5 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/remark-memo.d.mts +16 -0
- package/dist/reference/remark-memo.mjs +339 -0
- package/dist/reference/remark-notation.d.mts +57 -9
- package/dist/reference/remark-notation.mjs +354 -42
- package/dist/reference/remark-short-note.d.mts +4 -0
- package/dist/reference/remark-short-note.mjs +237 -0
- package/dist/reference/types.d.ts +16 -1
- package/dist/reference/units-vocab.js +4 -0
- package/docs/VERSIONING.md +53 -4
- package/docs/authoring/README.md +10 -0
- package/docs/authoring/agents.md +17 -2
- package/docs/authoring/components.md +138 -0
- package/docs/authoring/migrating.md +275 -7
- package/docs/authoring/notation.md +615 -1
- package/docs/authoring/structure-rules.md +69 -0
- package/docs/authoring/structure.md +120 -8
- package/docs/authoring/visualizations.md +36 -3
- package/package.json +5 -2
- package/src/compiler/astro.ts +208 -3
- package/src/compiler/cli.ts +29 -2
- package/src/compiler/collections.ts +45 -4
- package/src/compiler/course-config.ts +132 -0
- package/src/compiler/exports/standalone-html.ts +963 -0
- package/src/compiler/manifest.ts +281 -22
- package/src/components/HoverPanel.astro +45 -4
- package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
- package/src/components/assessments/AssessmentRunner.tsx +52 -17
- package/src/components/assessments/AssessmentSet.astro +98 -14
- package/src/components/assessments/RenderedContent.tsx +55 -0
- package/src/components/assessments/assessment-randomization.ts +16 -0
- package/src/components/diagram/Diagram.astro +409 -103
- package/src/components/disclosure/hover-preview.ts +202 -0
- package/src/components/disclosure/persisted-disclosure.ts +66 -0
- package/src/components/examples/CompactExamples.astro +2 -0
- package/src/components/figure/Figure.astro +279 -10
- package/src/components/memo/Memo.astro +453 -0
- package/src/components/memo/ShortNote.astro +223 -0
- package/src/components/notation/NotationFormula.astro +18 -1
- package/src/components/notation/NotationLayer.astro +260 -83
- package/src/components/notation/render-notation-math.ts +39 -2
- package/src/components/notation/safe-math-to-react.ts +50 -0
- package/src/components/reference/FloatRefLayer.astro +233 -15
- package/src/components/starlight/CollapsibleSections.astro +461 -0
- package/src/components/starlight/DiagramEnhancer.astro +385 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
- package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
- package/src/components/starlight/LayoutHeader.astro +50 -0
- package/src/components/starlight/LayoutSocialIcons.astro +41 -2
- package/src/components/starlight/LessonExportActions.astro +436 -0
- package/src/components/starlight/LessonFooter.astro +30 -3
- package/src/components/starlight/section-parts.ts +31 -0
- package/src/components/table/Table.astro +293 -7
- package/src/components/table/TableOverflowExpander.astro +489 -0
- package/src/content-config.ts +36 -1
- package/src/reference/base-abbreviations.mjs +60 -0
- package/src/reference/base-operators.mjs +40 -0
- package/src/reference/base-roman-labels.mjs +43 -0
- package/src/reference/consistency.ts +73 -2
- package/src/reference/diagrams-validate.ts +40 -0
- package/src/reference/equations.mjs +67 -14
- package/src/reference/gate-abbreviations.ts +128 -0
- package/src/reference/gate-heading-length.ts +131 -0
- package/src/reference/gate-math.ts +32 -7
- package/src/reference/gate-prose-notation.ts +224 -0
- package/src/reference/keyed-float-validate.ts +5 -5
- package/src/reference/keyed-jsx-floats.mjs +50 -25
- package/src/reference/knowledge-checks-validate.ts +130 -0
- package/src/reference/knowledge-checks.mjs +45 -0
- package/src/reference/lab-math-scope.ts +45 -3
- package/src/reference/math-glyphs.mjs +548 -40
- package/src/reference/mdx-attributes.mjs +38 -0
- package/src/reference/mixed-content.ts +69 -0
- package/src/reference/page-notation.ts +252 -0
- package/src/reference/raw-floats-validate.ts +103 -0
- package/src/reference/raw-floats.mjs +129 -0
- package/src/reference/rehype-heading-anchors.mjs +107 -1
- package/src/reference/rehype-table-header-labels.mjs +152 -0
- package/src/reference/remark-abbreviations.mjs +174 -0
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/remark-memo.mjs +339 -0
- package/src/reference/remark-notation.mjs +354 -42
- package/src/reference/remark-short-note.mjs +237 -0
- package/src/reference/types.ts +16 -0
- package/src/reference/units-vocab.ts +4 -0
- package/src/styles/global.css +996 -50
|
@@ -15,8 +15,15 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import NotationMath from './NotationMath.astro';
|
|
17
17
|
import SafeMathNode from './SafeMathNode.astro';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
renderNotationFormula,
|
|
20
|
+
stripNotationMarkers,
|
|
21
|
+
} from './render-notation-math';
|
|
19
22
|
import type { GlyphScopeEntry, ResolvedGloss } from '../../reference/gloss';
|
|
23
|
+
import {
|
|
24
|
+
notationKeysInMathNodes,
|
|
25
|
+
registerPageNotationDependencies,
|
|
26
|
+
} from '../../reference/page-notation';
|
|
20
27
|
|
|
21
28
|
export interface Props {
|
|
22
29
|
formula: string;
|
|
@@ -36,12 +43,18 @@ const {
|
|
|
36
43
|
entryLabel,
|
|
37
44
|
} = Astro.props;
|
|
38
45
|
const { nodes } = renderNotationFormula(formula, scope);
|
|
46
|
+
registerPageNotationDependencies(
|
|
47
|
+
Astro.locals,
|
|
48
|
+
notationKeysInMathNodes(nodes),
|
|
49
|
+
`NotationFormula formula "${formula}"`,
|
|
50
|
+
);
|
|
39
51
|
---
|
|
40
52
|
|
|
41
53
|
<div class="notation-formula-block">
|
|
42
54
|
<div
|
|
43
55
|
class="notation-formula-math"
|
|
44
56
|
data-notation-formula
|
|
57
|
+
data-latex={stripNotationMarkers(formula)}
|
|
45
58
|
role="group"
|
|
46
59
|
aria-label={entryLabel ? `${entryLabel} formula` : 'Formula'}
|
|
47
60
|
tabindex="0"
|
|
@@ -88,6 +101,10 @@ const { nodes } = renderNotationFormula(formula, scope);
|
|
|
88
101
|
container is the fix; this is its focus ring. */
|
|
89
102
|
.notation-formula-math {
|
|
90
103
|
overflow-x: auto;
|
|
104
|
+
/* `overflow-x: auto` alone makes the block axis compute to `auto` too
|
|
105
|
+
(CSS Overflow §3) — equations must never scroll vertically on their
|
|
106
|
+
own, only sideways. */
|
|
107
|
+
overflow-y: hidden;
|
|
91
108
|
padding-block: 0.15rem;
|
|
92
109
|
color: var(--sl-color-text);
|
|
93
110
|
}
|
|
@@ -42,10 +42,15 @@ export interface Props {
|
|
|
42
42
|
* links to reveal an explanation on hover.
|
|
43
43
|
*/
|
|
44
44
|
presentation?: 'full' | 'panel';
|
|
45
|
+
/** Author-chosen state for a reader with nothing saved yet. Default: `false` (closed) — see `docs/authoring` on `CourseConfig.disclosureDefaults`. */
|
|
46
|
+
defaultOpen?: boolean;
|
|
47
|
+
/** Persist the reader's open/closed choice across visits, per page. Default: `true`. */
|
|
48
|
+
rememberState?: boolean;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
import NotationMath from './NotationMath.astro';
|
|
48
52
|
import NotationFormula from './NotationFormula.astro';
|
|
53
|
+
import { stripNotationMarkers } from './render-notation-math';
|
|
49
54
|
import type { GlyphScopeEntry, ResolvedGloss } from '../../reference/gloss';
|
|
50
55
|
|
|
51
56
|
const {
|
|
@@ -54,6 +59,8 @@ const {
|
|
|
54
59
|
label = 'Notation used on this page',
|
|
55
60
|
glossaryHref = `${import.meta.env.BASE_URL.replace(/\/$/, '')}/glossary/`,
|
|
56
61
|
presentation = 'full',
|
|
62
|
+
defaultOpen = false,
|
|
63
|
+
rememberState = true,
|
|
57
64
|
} = Astro.props;
|
|
58
65
|
|
|
59
66
|
const panelOnly = presentation === 'panel';
|
|
@@ -110,16 +117,16 @@ for (const gloss of glossItems) {
|
|
|
110
117
|
class:list={['notation-layer', panelOnly && 'notation-layer--panel-only']}
|
|
111
118
|
data-notation-layer
|
|
112
119
|
data-notation-layer-id={id}
|
|
113
|
-
aria-
|
|
114
|
-
aria-label={panelOnly ? 'Notation explanations' : undefined}
|
|
120
|
+
aria-label={panelOnly ? 'Notation explanations' : label}
|
|
115
121
|
>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
<div class="notation-list-titlebar">
|
|
123
|
+
<details
|
|
124
|
+
class="notation-list"
|
|
125
|
+
id={id}
|
|
126
|
+
hidden={panelOnly}
|
|
127
|
+
data-remember-state={String(rememberState)}
|
|
128
|
+
open={defaultOpen}
|
|
129
|
+
>
|
|
123
130
|
<summary>
|
|
124
131
|
{label} <span class="notation-count">({items.length})</span>
|
|
125
132
|
</summary>
|
|
@@ -169,7 +176,11 @@ for (const gloss of glossItems) {
|
|
|
169
176
|
entryLabel={definition.label}
|
|
170
177
|
/>
|
|
171
178
|
) : (
|
|
172
|
-
<span
|
|
179
|
+
<span
|
|
180
|
+
class="notation-formula"
|
|
181
|
+
data-notation-formula
|
|
182
|
+
data-latex={stripNotationMarkers(definition.formula)}
|
|
183
|
+
>
|
|
173
184
|
<NotationMath latex={definition.formula} />
|
|
174
185
|
</span>
|
|
175
186
|
))}
|
|
@@ -178,16 +189,6 @@ for (const gloss of glossItems) {
|
|
|
178
189
|
<strong>Units:</strong> {definition.units}
|
|
179
190
|
</p>
|
|
180
191
|
)}
|
|
181
|
-
<button
|
|
182
|
-
class="notation-pin"
|
|
183
|
-
type="button"
|
|
184
|
-
data-notation-control={definition.key}
|
|
185
|
-
aria-expanded="false"
|
|
186
|
-
aria-pressed="false"
|
|
187
|
-
hidden
|
|
188
|
-
>
|
|
189
|
-
Pin explanation
|
|
190
|
-
</button>
|
|
191
192
|
</dd>
|
|
192
193
|
</div>
|
|
193
194
|
))}
|
|
@@ -211,14 +212,32 @@ for (const gloss of glossItems) {
|
|
|
211
212
|
</div>
|
|
212
213
|
))}
|
|
213
214
|
</details>
|
|
215
|
+
{!panelOnly && (
|
|
216
|
+
<a
|
|
217
|
+
class="notation-list-anchor"
|
|
218
|
+
href={`#${id}`}
|
|
219
|
+
aria-label={`${label}, titled section`}
|
|
220
|
+
>
|
|
221
|
+
<svg
|
|
222
|
+
aria-hidden="true"
|
|
223
|
+
width="16"
|
|
224
|
+
height="16"
|
|
225
|
+
viewBox="0 0 24 24"
|
|
226
|
+
fill="currentColor"
|
|
227
|
+
><path
|
|
228
|
+
d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"
|
|
229
|
+
></path></svg
|
|
230
|
+
>
|
|
231
|
+
</a>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
214
234
|
|
|
215
235
|
{!panelOnly && (
|
|
216
236
|
<p class="notation-enhancement-help" data-notation-help hidden>
|
|
217
237
|
Point to a marked symbol for a quick explanation, or use the list
|
|
218
238
|
above — pointing at a symbol inside an open card explains that one
|
|
219
|
-
too, alongside it. Click a symbol
|
|
220
|
-
|
|
221
|
-
Escape.
|
|
239
|
+
too, alongside it. Click a symbol to keep its card open; click the
|
|
240
|
+
card (or its “×”) to close it, or press Escape.
|
|
222
241
|
</p>
|
|
223
242
|
)}
|
|
224
243
|
|
|
@@ -244,6 +263,14 @@ for (const gloss of glossItems) {
|
|
|
244
263
|
</header>
|
|
245
264
|
<p data-slot-summary hidden />
|
|
246
265
|
<div data-slot-formula hidden />
|
|
266
|
+
<button
|
|
267
|
+
class="notation-popup-copy"
|
|
268
|
+
type="button"
|
|
269
|
+
data-popup-copy
|
|
270
|
+
hidden
|
|
271
|
+
>
|
|
272
|
+
Copy LaTeX
|
|
273
|
+
</button>
|
|
247
274
|
<p class="notation-units" data-slot-units hidden />
|
|
248
275
|
<a data-slot-link hidden>
|
|
249
276
|
Open the full definition
|
|
@@ -257,6 +284,7 @@ for (const gloss of glossItems) {
|
|
|
257
284
|
|
|
258
285
|
<script>
|
|
259
286
|
import { createPopupStack, closestElement } from '../popup-stack';
|
|
287
|
+
import { wirePersistedDetails } from '../disclosure/persisted-disclosure';
|
|
260
288
|
|
|
261
289
|
const triggerSelector = '[data-notation-key]';
|
|
262
290
|
|
|
@@ -283,10 +311,6 @@ for (const gloss of glossItems) {
|
|
|
283
311
|
);
|
|
284
312
|
if (!template) return;
|
|
285
313
|
|
|
286
|
-
const controls = Array.from(
|
|
287
|
-
root.querySelectorAll<HTMLButtonElement>('[data-notation-control]'),
|
|
288
|
-
);
|
|
289
|
-
|
|
290
314
|
// Static triggers, computed once: markers embedded in prose or an
|
|
291
315
|
// equation, and the symbol glyphs in the "Notation used on this page"
|
|
292
316
|
// list. These get the always-on `.notation-trigger` cursor/hover
|
|
@@ -311,20 +335,32 @@ for (const gloss of glossItems) {
|
|
|
311
335
|
for (const trigger of highlightTargets) {
|
|
312
336
|
trigger.classList.add('notation-trigger');
|
|
313
337
|
}
|
|
314
|
-
for (const control of controls) control.hidden = false;
|
|
315
338
|
const help = root.querySelector<HTMLElement>('[data-notation-help]');
|
|
316
339
|
if (help) help.hidden = false;
|
|
317
340
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
341
|
+
// The visible list's own symbol glyphs are the only keyboard path to
|
|
342
|
+
// pinning a card from this list (the old "Pin explanation" button was
|
|
343
|
+
// the other one, and is gone) — make them a real, labelled, Enter/Space-
|
|
344
|
+
// operable control rather than a mouse-only `<span>`. `symbolTriggers`
|
|
345
|
+
// (defined above) is already scoped to entries inside this list.
|
|
346
|
+
for (const trigger of symbolTriggers) {
|
|
347
|
+
const key = trigger.dataset.notationKey ?? '';
|
|
348
|
+
const entryLabel =
|
|
349
|
+
entries.get(key)?.querySelector<HTMLElement>('[data-notation-link]')
|
|
350
|
+
?.textContent?.trim() ?? key;
|
|
351
|
+
trigger.setAttribute('role', 'button');
|
|
352
|
+
trigger.tabIndex = 0;
|
|
353
|
+
trigger.setAttribute('aria-pressed', 'false');
|
|
354
|
+
trigger.setAttribute('aria-label', `Pin the ${entryLabel} explanation`);
|
|
355
|
+
trigger.addEventListener('keydown', (event) => {
|
|
356
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
357
|
+
event.preventDefault();
|
|
358
|
+
trigger.click();
|
|
359
|
+
});
|
|
360
|
+
}
|
|
323
361
|
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
return candidate && root.contains(candidate) ? candidate : null;
|
|
327
|
-
};
|
|
362
|
+
const keyFor = (element: HTMLElement | null) =>
|
|
363
|
+
element?.dataset.notationKey ?? element?.dataset.notationLink ?? null;
|
|
328
364
|
|
|
329
365
|
const staticLinkFrom = (node: EventTarget | null) => {
|
|
330
366
|
const candidate = closestElement(node, '[data-notation-link]');
|
|
@@ -344,7 +380,7 @@ for (const gloss of glossItems) {
|
|
|
344
380
|
};
|
|
345
381
|
|
|
346
382
|
const triggerFrom = (node: EventTarget | null) =>
|
|
347
|
-
|
|
383
|
+
staticLinkFrom(node) ?? symbolFrom(node);
|
|
348
384
|
|
|
349
385
|
const labelOf = (entry: HTMLElement, key: string) =>
|
|
350
386
|
entry
|
|
@@ -369,7 +405,18 @@ for (const gloss of glossItems) {
|
|
|
369
405
|
const units = fragment.querySelector<HTMLElement>('[data-slot-units]');
|
|
370
406
|
const link =
|
|
371
407
|
fragment.querySelector<HTMLAnchorElement>('[data-slot-link]');
|
|
372
|
-
|
|
408
|
+
const copyButton = fragment.querySelector<HTMLButtonElement>(
|
|
409
|
+
'[data-popup-copy]',
|
|
410
|
+
);
|
|
411
|
+
if (
|
|
412
|
+
!el ||
|
|
413
|
+
!symbolHost ||
|
|
414
|
+
!title ||
|
|
415
|
+
!summary ||
|
|
416
|
+
!formulaHost ||
|
|
417
|
+
!units ||
|
|
418
|
+
!copyButton
|
|
419
|
+
) {
|
|
373
420
|
return null;
|
|
374
421
|
}
|
|
375
422
|
|
|
@@ -395,6 +442,35 @@ for (const gloss of glossItems) {
|
|
|
395
442
|
...(sourceFormula ? [sourceFormula.cloneNode(true)] : []),
|
|
396
443
|
);
|
|
397
444
|
formulaHost.hidden = sourceFormula === null;
|
|
445
|
+
|
|
446
|
+
// The formula's own `data-latex` carries the plain, authored LaTeX —
|
|
447
|
+
// not the `\explain{key}{…}`-wrapped source KaTeX actually rendered —
|
|
448
|
+
// so what a reader copies is what they'd write themselves, not an
|
|
449
|
+
// internal marker macro. See `render-notation-math.ts:renderNotationFormula`.
|
|
450
|
+
const formulaLatex = sourceFormula?.dataset.latex ?? '';
|
|
451
|
+
copyButton.hidden = formulaLatex.length === 0;
|
|
452
|
+
if (formulaLatex.length > 0) {
|
|
453
|
+
copyButton.addEventListener('click', (event) => {
|
|
454
|
+
// Otherwise this bubbles to the document click handler in
|
|
455
|
+
// `popup-stack.ts`, which treats any click inside a popup that
|
|
456
|
+
// isn't a trigger or a link as "close it" — that would remove the
|
|
457
|
+
// card before the "Copied!" flash below ever paints.
|
|
458
|
+
event.stopPropagation();
|
|
459
|
+
if (!navigator.clipboard) return;
|
|
460
|
+
navigator.clipboard.writeText(formulaLatex).then(
|
|
461
|
+
() => {
|
|
462
|
+
copyButton.dataset.copied = 'true';
|
|
463
|
+
window.setTimeout(() => {
|
|
464
|
+
delete copyButton.dataset.copied;
|
|
465
|
+
}, 1200);
|
|
466
|
+
},
|
|
467
|
+
() => {
|
|
468
|
+
/* Clipboard blocked: nothing more we can do here. */
|
|
469
|
+
},
|
|
470
|
+
);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
398
474
|
setOptionalText(
|
|
399
475
|
units,
|
|
400
476
|
entry
|
|
@@ -412,21 +488,6 @@ for (const gloss of glossItems) {
|
|
|
412
488
|
return el;
|
|
413
489
|
};
|
|
414
490
|
|
|
415
|
-
const updateControls = (
|
|
416
|
-
open: ReadonlySet<string>,
|
|
417
|
-
pinned: ReadonlySet<string>,
|
|
418
|
-
) => {
|
|
419
|
-
for (const control of controls) {
|
|
420
|
-
const key = control.dataset.notationControl ?? '';
|
|
421
|
-
const selected = pinned.has(key);
|
|
422
|
-
control.setAttribute('aria-pressed', String(selected));
|
|
423
|
-
control.setAttribute('aria-expanded', String(open.has(key)));
|
|
424
|
-
control.textContent = selected
|
|
425
|
-
? 'Unpin explanation'
|
|
426
|
-
: 'Pin explanation';
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
|
|
430
491
|
createPopupStack({
|
|
431
492
|
// Popups are `position: fixed` and float above everything regardless
|
|
432
493
|
// of DOM placement — *except* z-index only ever wins within its own
|
|
@@ -440,7 +501,6 @@ for (const gloss of glossItems) {
|
|
|
440
501
|
triggerFrom,
|
|
441
502
|
keyFor,
|
|
442
503
|
createPopup,
|
|
443
|
-
pinControlFrom: controlFrom,
|
|
444
504
|
onChange: (open, pinned) => {
|
|
445
505
|
for (const trigger of highlightTargets) {
|
|
446
506
|
trigger.classList.toggle(
|
|
@@ -448,15 +508,44 @@ for (const gloss of glossItems) {
|
|
|
448
508
|
open.has(trigger.dataset.notationKey ?? ''),
|
|
449
509
|
);
|
|
450
510
|
}
|
|
451
|
-
|
|
511
|
+
for (const trigger of symbolTriggers) {
|
|
512
|
+
trigger.setAttribute(
|
|
513
|
+
'aria-pressed',
|
|
514
|
+
String(pinned.has(trigger.dataset.notationKey ?? '')),
|
|
515
|
+
);
|
|
516
|
+
}
|
|
452
517
|
},
|
|
453
518
|
});
|
|
454
519
|
}
|
|
455
520
|
|
|
521
|
+
function setupNotationDisclosure(details: HTMLDetailsElement) {
|
|
522
|
+
if (details.dataset.disclosureWired === 'true') return;
|
|
523
|
+
details.dataset.disclosureWired = 'true';
|
|
524
|
+
|
|
525
|
+
// Same convention as `Figure.astro`: the SSR-rendered `open` attribute
|
|
526
|
+
// already carries the author's `defaultOpen` prop verbatim.
|
|
527
|
+
const defaultOpen = details.open;
|
|
528
|
+
if (details.dataset.rememberState === 'true') {
|
|
529
|
+
wirePersistedDetails(details, `notation:${details.id}`, defaultOpen);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Deep-linking to `#page-notation` (the anchor link, or a shared URL)
|
|
533
|
+
// must reveal it even if a reader left it collapsed.
|
|
534
|
+
const openIfTargeted = () => {
|
|
535
|
+
if (location.hash === `#${details.id}`) details.open = true;
|
|
536
|
+
};
|
|
537
|
+
openIfTargeted();
|
|
538
|
+
window.addEventListener('hashchange', openIfTargeted);
|
|
539
|
+
}
|
|
540
|
+
|
|
456
541
|
for (const root of document.querySelectorAll<HTMLElement>(
|
|
457
542
|
'[data-notation-layer]',
|
|
458
543
|
)) {
|
|
459
544
|
setupNotationLayer(root);
|
|
545
|
+
const details = root.querySelector<HTMLDetailsElement>(
|
|
546
|
+
':scope > .notation-list-titlebar > .notation-list[id]',
|
|
547
|
+
);
|
|
548
|
+
if (details) setupNotationDisclosure(details);
|
|
460
549
|
}
|
|
461
550
|
</script>
|
|
462
551
|
|
|
@@ -473,9 +562,13 @@ for (const gloss of glossItems) {
|
|
|
473
562
|
margin: 0;
|
|
474
563
|
}
|
|
475
564
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
565
|
+
/* Positioning context for `.notation-list-anchor`, mirroring
|
|
566
|
+
`Figure.astro`'s `.keyed-figure__titlebar` — the anchor link is a DOM
|
|
567
|
+
sibling of `<details>`, not nested inside `<summary>` (a focusable `<a>`
|
|
568
|
+
inside `<summary>` trips axe's `nested-interactive` check the same way
|
|
569
|
+
it would there). */
|
|
570
|
+
.notation-list-titlebar {
|
|
571
|
+
position: relative;
|
|
479
572
|
}
|
|
480
573
|
|
|
481
574
|
.notation-list {
|
|
@@ -486,8 +579,52 @@ for (const gloss of glossItems) {
|
|
|
486
579
|
|
|
487
580
|
.notation-list > summary {
|
|
488
581
|
padding: 0.8rem 1rem;
|
|
582
|
+
padding-inline-end: 2.2rem;
|
|
489
583
|
cursor: pointer;
|
|
490
584
|
font-weight: 600;
|
|
585
|
+
font-size: var(--sl-text-lg);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.notation-list-anchor {
|
|
589
|
+
position: absolute;
|
|
590
|
+
top: 0.9rem;
|
|
591
|
+
inset-inline-end: 1rem;
|
|
592
|
+
display: inline-flex;
|
|
593
|
+
align-items: center;
|
|
594
|
+
justify-content: center;
|
|
595
|
+
width: 1em;
|
|
596
|
+
height: 1em;
|
|
597
|
+
font-size: var(--sl-text-lg);
|
|
598
|
+
color: var(--sl-color-gray-3);
|
|
599
|
+
text-decoration: none;
|
|
600
|
+
-webkit-user-select: none;
|
|
601
|
+
user-select: none;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.notation-list-anchor svg {
|
|
605
|
+
width: 0.7em;
|
|
606
|
+
height: 0.7em;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.notation-list-anchor:hover,
|
|
610
|
+
.notation-list-anchor:focus-visible {
|
|
611
|
+
color: var(--sl-color-text-accent);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.notation-list-anchor:focus-visible {
|
|
615
|
+
outline: 2px solid var(--sl-color-accent);
|
|
616
|
+
outline-offset: 0.15rem;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
@media (hover: hover) {
|
|
620
|
+
.notation-list-anchor {
|
|
621
|
+
opacity: 0;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.notation-list-anchor:focus-visible,
|
|
625
|
+
.notation-list-titlebar:hover .notation-list-anchor {
|
|
626
|
+
opacity: 1;
|
|
627
|
+
}
|
|
491
628
|
}
|
|
492
629
|
|
|
493
630
|
.notation-count,
|
|
@@ -541,14 +678,28 @@ for (const gloss of glossItems) {
|
|
|
541
678
|
cursor: help;
|
|
542
679
|
}
|
|
543
680
|
|
|
681
|
+
/* Entries in the visible list are keyboard-operable (Enter/Space pins
|
|
682
|
+
the same card a click would) now that the list has no separate "Pin
|
|
683
|
+
explanation" button of its own — see the setup script below. */
|
|
684
|
+
.notation-entry .notation-symbol-trigger {
|
|
685
|
+
cursor: pointer;
|
|
686
|
+
}
|
|
687
|
+
|
|
544
688
|
.notation-symbol-trigger:hover,
|
|
689
|
+
.notation-symbol-trigger:focus-visible,
|
|
545
690
|
.notation-symbol-trigger.is-notation-active {
|
|
546
691
|
background: var(--sl-color-accent-low);
|
|
547
692
|
box-shadow: 0 0 0 0.12em var(--sl-color-accent-low);
|
|
548
693
|
}
|
|
549
694
|
|
|
695
|
+
.notation-symbol-trigger:focus-visible {
|
|
696
|
+
outline: 2px solid var(--sl-color-accent);
|
|
697
|
+
outline-offset: 0.1em;
|
|
698
|
+
}
|
|
699
|
+
|
|
550
700
|
@media (forced-colors: active) {
|
|
551
701
|
.notation-symbol-trigger:hover,
|
|
702
|
+
.notation-symbol-trigger:focus-visible,
|
|
552
703
|
.notation-symbol-trigger.is-notation-active {
|
|
553
704
|
outline: 2px solid Highlight;
|
|
554
705
|
color: HighlightText;
|
|
@@ -573,28 +724,14 @@ for (const gloss of glossItems) {
|
|
|
573
724
|
|
|
574
725
|
.notation-formula {
|
|
575
726
|
overflow-x: auto;
|
|
727
|
+
/* `overflow-x: auto` alone makes the block axis compute to `auto` too
|
|
728
|
+
(CSS Overflow §3) — equations must never scroll vertically on their
|
|
729
|
+
own, only sideways. */
|
|
730
|
+
overflow-y: hidden;
|
|
576
731
|
padding-block: 0.15rem;
|
|
577
732
|
color: var(--sl-color-text);
|
|
578
733
|
}
|
|
579
734
|
|
|
580
|
-
.notation-pin {
|
|
581
|
-
justify-self: start;
|
|
582
|
-
padding: 0.25rem 0.55rem;
|
|
583
|
-
border: 1px solid var(--sl-color-gray-5);
|
|
584
|
-
border-radius: 0.35rem;
|
|
585
|
-
color: var(--sl-color-text-accent);
|
|
586
|
-
background: var(--sl-color-bg);
|
|
587
|
-
font: inherit;
|
|
588
|
-
font-size: var(--sl-text-sm);
|
|
589
|
-
cursor: pointer;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
.notation-pin:hover,
|
|
593
|
-
.notation-pin[aria-pressed='true'] {
|
|
594
|
-
border-color: var(--sl-color-accent);
|
|
595
|
-
background: var(--sl-color-accent-low);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
735
|
.notation-enhancement-help {
|
|
599
736
|
margin: 0.55rem 0 0;
|
|
600
737
|
font-size: var(--sl-text-xs);
|
|
@@ -612,15 +749,19 @@ for (const gloss of glossItems) {
|
|
|
612
749
|
margin: 0;
|
|
613
750
|
width: min(23rem, calc(100vw - 1.5rem));
|
|
614
751
|
overflow: auto;
|
|
615
|
-
padding:
|
|
616
|
-
padding-inline
|
|
752
|
+
padding-block: 0.75rem;
|
|
753
|
+
padding-inline: 1rem 2.4rem;
|
|
617
754
|
border: 1px solid var(--sl-color-gray-5);
|
|
618
755
|
border-top: 0.25rem solid var(--sl-color-accent);
|
|
619
756
|
border-radius: 0.65rem;
|
|
620
757
|
color: var(--sl-color-text);
|
|
621
758
|
background: var(--sl-color-bg);
|
|
622
759
|
box-shadow: var(--sl-shadow-lg);
|
|
623
|
-
font-size
|
|
760
|
+
/* Relative to the surrounding prose's own font-size, not a fixed
|
|
761
|
+
Starlight step — the card is mounted on `<body>`, alongside the
|
|
762
|
+
article rather than nested inside it, so `%` still resolves against
|
|
763
|
+
the same base the article's paragraphs use. */
|
|
764
|
+
font-size: 90%;
|
|
624
765
|
}
|
|
625
766
|
|
|
626
767
|
.notation-popup-close {
|
|
@@ -677,15 +818,51 @@ for (const gloss of glossItems) {
|
|
|
677
818
|
}
|
|
678
819
|
|
|
679
820
|
.notation-panel-body p {
|
|
680
|
-
margin-block: 0.
|
|
821
|
+
margin-block: 0.5rem;
|
|
681
822
|
color: var(--sl-color-gray-2);
|
|
682
|
-
font-size: var(--sl-text-sm);
|
|
683
823
|
line-height: 1.5;
|
|
684
824
|
}
|
|
685
825
|
|
|
826
|
+
.notation-popup-copy {
|
|
827
|
+
position: relative;
|
|
828
|
+
margin-block: 0.4rem 0.6rem;
|
|
829
|
+
padding: 0.2rem 0.55rem;
|
|
830
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
831
|
+
border-radius: 0.35rem;
|
|
832
|
+
color: var(--sl-color-text-accent);
|
|
833
|
+
background: var(--sl-color-bg);
|
|
834
|
+
font: inherit;
|
|
835
|
+
font-size: var(--sl-text-xs);
|
|
836
|
+
cursor: pointer;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.notation-popup-copy:hover {
|
|
840
|
+
border-color: var(--sl-color-accent);
|
|
841
|
+
background: var(--sl-color-accent-low);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
.notation-popup-copy[data-copied]::after {
|
|
845
|
+
content: 'Copied!';
|
|
846
|
+
position: absolute;
|
|
847
|
+
inset-block-end: calc(100% + 0.35rem);
|
|
848
|
+
inset-inline-start: 50%;
|
|
849
|
+
translate: -50% 0;
|
|
850
|
+
padding: 0.15rem 0.45rem;
|
|
851
|
+
border-radius: 0.35rem;
|
|
852
|
+
background: var(--sl-color-accent-low);
|
|
853
|
+
color: var(--sl-color-text-accent);
|
|
854
|
+
font-size: var(--sl-text-xs);
|
|
855
|
+
white-space: nowrap;
|
|
856
|
+
pointer-events: none;
|
|
857
|
+
}
|
|
858
|
+
|
|
686
859
|
.notation-panel-body [data-slot-formula] {
|
|
687
860
|
overflow-x: auto;
|
|
688
|
-
|
|
861
|
+
/* See `.notation-formula` above: pairs with `overflow-x` to stop the
|
|
862
|
+
auto-both-axes quirk from giving the formula its own vertical
|
|
863
|
+
scrollbar. */
|
|
864
|
+
overflow-y: hidden;
|
|
865
|
+
margin-block: 0.5rem;
|
|
689
866
|
color: var(--sl-color-text);
|
|
690
867
|
}
|
|
691
868
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import rehypeKatex from 'rehype-katex';
|
|
2
2
|
import { createNotationKatexOptions } from '../../reference/katex-options.mjs';
|
|
3
3
|
import { NOTATION_KEY_PATTERN } from '../../reference/remark-notation.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
GlyphResolutionError,
|
|
6
|
+
resolveMathGlyphs,
|
|
7
|
+
stripNotationMarkers,
|
|
8
|
+
} from '../../reference/math-glyphs.mjs';
|
|
5
9
|
import type { GlyphScopeEntry } from '../../reference/gloss';
|
|
6
10
|
|
|
7
11
|
interface HastText {
|
|
@@ -32,6 +36,19 @@ export type SafeMathNode =
|
|
|
32
36
|
children: readonly SafeMathNode[];
|
|
33
37
|
};
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* A string that mixes prose and math, already split and rendered — text runs
|
|
41
|
+
* verbatim, a math run as the same safe, structured tree `SafeMathNode` is.
|
|
42
|
+
* Produced by `AssessmentSet.astro` from `manifest.assessmentContent`
|
|
43
|
+
* (`ContentSegment[]`, unrendered LaTeX) via {@link renderNotationMath}, and
|
|
44
|
+
* consumed by `AssessmentRunner`'s React-side recursive renderer
|
|
45
|
+
* (`RenderedContent.tsx`) — the React equivalent of `SafeMathNode.astro`,
|
|
46
|
+
* since a React island can't render an `.astro` component.
|
|
47
|
+
*/
|
|
48
|
+
export type MixedContentNode =
|
|
49
|
+
| { type: 'text'; value: string }
|
|
50
|
+
| { type: 'math'; nodes: readonly SafeMathNode[] };
|
|
51
|
+
|
|
35
52
|
const allowedTags = new Set([
|
|
36
53
|
'annotation',
|
|
37
54
|
'math',
|
|
@@ -203,6 +220,14 @@ function sanitizeNode(node: HastNode): SafeMathNode {
|
|
|
203
220
|
});
|
|
204
221
|
}
|
|
205
222
|
|
|
223
|
+
// Re-exported so existing imports of `stripNotationMarkers` from this module
|
|
224
|
+
// (the notation popup card's "Copy LaTeX" button) keep working. The
|
|
225
|
+
// implementation lives in `math-glyphs.mjs` because `remark-notation.mjs`
|
|
226
|
+
// (a numbered display equation's own copy button, at build time) needs it
|
|
227
|
+
// too, and importing it from here would make that a circular dependency —
|
|
228
|
+
// this module already imports from `remark-notation.mjs`.
|
|
229
|
+
export { stripNotationMarkers };
|
|
230
|
+
|
|
206
231
|
/**
|
|
207
232
|
* Render repository-authored LaTeX through the same build-time KaTeX adapter
|
|
208
233
|
* as lesson math, then expose a constrained structured tree to Astro. No HTML
|
|
@@ -274,7 +299,19 @@ export function renderNotationFormula(
|
|
|
274
299
|
const result = resolveMathGlyphs(formula, [...scope]);
|
|
275
300
|
bound = result.latex;
|
|
276
301
|
unresolved = result.unresolved.map(({ token }: { token: string }) => token);
|
|
277
|
-
} catch {
|
|
302
|
+
} catch (error) {
|
|
303
|
+
// N13 (a nonlinear function with no parenthesized argument) is a hard,
|
|
304
|
+
// no-exceptions rule like N9 — never degrade it to inert math, so both
|
|
305
|
+
// `Diagram` (which turns `degraded` into a build error anyway, but with
|
|
306
|
+
// a misleading "notation scope" message) and `NotationFormula` (which
|
|
307
|
+
// otherwise keeps a degraded formula's inert-but-correct fallback) fail
|
|
308
|
+
// loudly with the actual fix instead.
|
|
309
|
+
if (
|
|
310
|
+
error instanceof GlyphResolutionError &&
|
|
311
|
+
error.code === 'unparenthesized-function'
|
|
312
|
+
) {
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
278
315
|
bound = formula;
|
|
279
316
|
degraded = true;
|
|
280
317
|
}
|