explico 0.6.0 → 0.7.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 +180 -1
- package/README.md +16 -2
- package/dist/compiler/astro.d.ts +13 -2
- package/dist/compiler/astro.js +39 -0
- package/dist/compiler/cli.d.ts +22 -3
- package/dist/compiler/cli.js +71 -10
- package/dist/compiler/exports/standalone-html.js +13 -0
- package/dist/compiler/logging.d.ts +109 -0
- package/dist/compiler/logging.js +107 -0
- package/dist/compiler/manifest.d.ts +7 -3
- package/dist/compiler/manifest.js +1 -1
- package/dist/reference/base-abbreviations.mjs +8 -2
- package/dist/reference/consistency.d.ts +8 -10
- package/dist/reference/consistency.js +3 -47
- package/dist/reference/delimiter-size.d.ts +39 -0
- package/dist/reference/delimiter-size.js +177 -0
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +2 -2
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-math.d.ts +2 -0
- package/dist/reference/gate-math.js +28 -6
- package/dist/reference/keyed-float-validate.d.ts +2 -2
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/references.js +2 -1
- package/dist/reference/registry.js +1 -1
- package/dist/reference/rehype-base-links.d.mts +23 -0
- package/dist/reference/rehype-base-links.mjs +40 -0
- package/dist/reference/remark-abbreviations.mjs +10 -12
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/render-lab-math.d.ts +18 -1
- package/dist/reference/render-lab-math.js +10 -3
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +1 -1
- package/docs/VERSIONING.md +2 -2
- package/docs/authoring/README.md +12 -2
- package/docs/authoring/agents.md +7 -3
- package/docs/authoring/migrating.md +286 -0
- package/docs/authoring/notation.md +139 -7
- package/docs/authoring/prose.md +64 -0
- package/package.json +1 -1
- package/src/compiler/astro.ts +89 -0
- package/src/compiler/cli.ts +144 -12
- package/src/compiler/exports/standalone-html.ts +15 -0
- package/src/compiler/logging.ts +248 -0
- package/src/compiler/manifest.ts +7 -3
- package/src/components/memo/Memo.astro +2 -5
- package/src/components/memo/ShortNote.astro +2 -5
- package/src/components/notation/NotationFormula.astro +199 -10
- package/src/components/notation/NotationGlossary.astro +89 -58
- package/src/components/notation/NotationLayer.astro +83 -81
- package/src/components/scroll-animation.ts +160 -0
- package/src/components/starlight/AnchorScrollEnhancer.astro +102 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +2 -4
- package/src/components/starlight/KeyedFloatEnhancer.astro +17 -16
- package/src/components/starlight/LessonFooter.astro +2 -0
- package/src/reference/base-abbreviations.mjs +8 -2
- package/src/reference/consistency.ts +10 -66
- package/src/reference/delimiter-size.ts +301 -0
- package/src/reference/diagrams-validate.ts +1 -1
- package/src/reference/equations-validate.ts +3 -3
- package/src/reference/figures-validate.ts +1 -1
- package/src/reference/gate-math.ts +41 -6
- package/src/reference/keyed-float-validate.ts +3 -3
- package/src/reference/references.ts +2 -1
- package/src/reference/registry.ts +1 -1
- package/src/reference/rehype-base-links.mjs +40 -0
- package/src/reference/remark-abbreviations.mjs +10 -12
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/render-lab-math.ts +24 -1
- package/src/reference/tables-validate.ts +1 -1
- package/src/reference/types.ts +1 -1
- package/src/styles/global.css +119 -14
|
@@ -33,6 +33,10 @@ export interface Props {
|
|
|
33
33
|
glossLabel?: string;
|
|
34
34
|
/** The owning entry's display label, used to name the scrollable region. */
|
|
35
35
|
entryLabel?: string;
|
|
36
|
+
/** `G.<n>` — the glossary's own equation numbering. Omitted renders no number. */
|
|
37
|
+
number?: string;
|
|
38
|
+
/** Same-page anchor the number links to; ignored unless `number` is set. */
|
|
39
|
+
numberHref?: string;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const {
|
|
@@ -41,6 +45,8 @@ const {
|
|
|
41
45
|
glosses = [],
|
|
42
46
|
glossLabel = 'Symbols',
|
|
43
47
|
entryLabel,
|
|
48
|
+
number,
|
|
49
|
+
numberHref,
|
|
44
50
|
} = Astro.props;
|
|
45
51
|
const { nodes } = renderNotationFormula(formula, scope);
|
|
46
52
|
registerPageNotationDependencies(
|
|
@@ -48,20 +54,49 @@ registerPageNotationDependencies(
|
|
|
48
54
|
notationKeysInMathNodes(nodes),
|
|
49
55
|
`NotationFormula formula "${formula}"`,
|
|
50
56
|
);
|
|
57
|
+
const latex = stripNotationMarkers(formula);
|
|
58
|
+
const numbered = Boolean(number && numberHref);
|
|
51
59
|
---
|
|
52
60
|
|
|
53
61
|
<div class="notation-formula-block">
|
|
54
62
|
<div
|
|
55
|
-
class=
|
|
63
|
+
class:list={['notation-formula-math', numbered && 'notation-formula-math--numbered']}
|
|
56
64
|
data-notation-formula
|
|
57
|
-
data-latex={
|
|
65
|
+
data-latex={latex}
|
|
58
66
|
role="group"
|
|
59
67
|
aria-label={entryLabel ? `${entryLabel} formula` : 'Formula'}
|
|
60
|
-
tabindex="0"
|
|
61
68
|
>
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
<div class="notation-formula-scroll" tabindex="0">
|
|
70
|
+
<span class="notation-math">
|
|
71
|
+
{nodes.map((node) => <SafeMathNode node={node} />)}
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
{numbered && (
|
|
75
|
+
<div class="notation-formula-controls">
|
|
76
|
+
<a
|
|
77
|
+
class="notation-formula-number"
|
|
78
|
+
href={numberHref}
|
|
79
|
+
aria-label={`Equation ${number}`}
|
|
80
|
+
title="Anchor to this equation"
|
|
81
|
+
>
|
|
82
|
+
({number})
|
|
83
|
+
</a>
|
|
84
|
+
<button
|
|
85
|
+
class="notation-formula-copy"
|
|
86
|
+
type="button"
|
|
87
|
+
data-notation-formula-copy
|
|
88
|
+
data-latex={latex}
|
|
89
|
+
aria-label={`Copy LaTeX for Equation ${number}`}
|
|
90
|
+
title="Copy LaTeX"
|
|
91
|
+
>
|
|
92
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
93
|
+
<rect x="9" y="9" width="10" height="10" rx="1"></rect>
|
|
94
|
+
<path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"
|
|
95
|
+
></path>
|
|
96
|
+
</svg>
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
65
100
|
</div>
|
|
66
101
|
|
|
67
102
|
{
|
|
@@ -95,25 +130,179 @@ registerPageNotationDependencies(
|
|
|
95
130
|
gap: 0.5rem;
|
|
96
131
|
}
|
|
97
132
|
|
|
133
|
+
/* The outer box is deliberately never a scroll clip itself — it hosts the
|
|
134
|
+
absolutely-positioned number/copy controls and their "Copied!" flash,
|
|
135
|
+
which a clipping ancestor would cut off. Mirrors `.keyed-equation` in
|
|
136
|
+
`global.css`, which splits the same way (its own overflow lives on the
|
|
137
|
+
nested `.katex-display`, not on `.keyed-equation` itself). Its cursor,
|
|
138
|
+
hover highlight, and "Copied!" flash are the generic
|
|
139
|
+
`[data-notation-formula]` rules in `global.css` — shared with every
|
|
140
|
+
other producer of that attribute, not reimplemented here. */
|
|
141
|
+
.notation-formula-math {
|
|
142
|
+
color: var(--sl-color-text);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.notation-formula-math--numbered {
|
|
146
|
+
padding-inline-end: 5.25rem;
|
|
147
|
+
}
|
|
148
|
+
|
|
98
149
|
/* A wide equation scrolls inside its own box, which makes the box a
|
|
99
150
|
scrollable region — and a scrollable region a keyboard user cannot reach
|
|
100
151
|
is unreachable content, not just an awkward one. `tabindex` on the
|
|
101
|
-
|
|
102
|
-
|
|
152
|
+
scrolling element itself (not an ancestor) is the fix, same convention
|
|
153
|
+
as `.table-overflow-scroll` in `global.css`; this is its focus ring. */
|
|
154
|
+
.notation-formula-scroll {
|
|
103
155
|
overflow-x: auto;
|
|
104
156
|
/* `overflow-x: auto` alone makes the block axis compute to `auto` too
|
|
105
157
|
(CSS Overflow §3) — equations must never scroll vertically on their
|
|
106
158
|
own, only sideways. */
|
|
107
159
|
overflow-y: hidden;
|
|
108
160
|
padding-block: 0.15rem;
|
|
109
|
-
color: var(--sl-color-text);
|
|
110
161
|
}
|
|
111
162
|
|
|
112
|
-
.notation-formula-
|
|
163
|
+
.notation-formula-scroll:focus-visible {
|
|
113
164
|
outline: 3px solid var(--sl-color-accent);
|
|
114
165
|
outline-offset: 2px;
|
|
115
166
|
}
|
|
116
167
|
|
|
168
|
+
/* Numbered controls — a glossary entry's own equation number and copy
|
|
169
|
+
button, styled to match `.keyed-equation__controls`/`__number`/`__copy`
|
|
170
|
+
in `global.css` exactly (same values), so a reader sees one consistent
|
|
171
|
+
"numbered equation" affordance across lessons and the glossary. */
|
|
172
|
+
.notation-formula-controls {
|
|
173
|
+
position: absolute;
|
|
174
|
+
inset-inline-end: 0.25rem;
|
|
175
|
+
inset-block-start: 50%;
|
|
176
|
+
transform: translateY(-50%);
|
|
177
|
+
display: inline-flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 0.4rem;
|
|
180
|
+
/* Starlight's generic prose "sibling spacing" rule adds a margin-top
|
|
181
|
+
to any block-level element that follows another (here, following
|
|
182
|
+
`.notation-formula-scroll`) — harmless for flow content, but it
|
|
183
|
+
shifts an absolutely-positioned box's `top: 50%` off-center, since
|
|
184
|
+
margin still applies to an out-of-flow element's own offset. */
|
|
185
|
+
margin: 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.notation-formula-number {
|
|
189
|
+
font-variant-numeric: tabular-nums;
|
|
190
|
+
font-size: var(--sl-text-sm);
|
|
191
|
+
color: var(--sl-color-text-accent);
|
|
192
|
+
text-decoration: none;
|
|
193
|
+
/* Without this it would inherit the container's `cursor: copy` above
|
|
194
|
+
instead of the link's usual pointer, since an inherited value beats
|
|
195
|
+
the browser's own "auto → hand cursor for a link" special case. */
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
opacity: 0.45;
|
|
198
|
+
transition: opacity 120ms ease-out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.notation-formula-copy {
|
|
202
|
+
display: inline-flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
justify-content: center;
|
|
205
|
+
width: 1.5rem;
|
|
206
|
+
height: 1.5rem;
|
|
207
|
+
padding: 0;
|
|
208
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
209
|
+
border-radius: 999px;
|
|
210
|
+
color: var(--sl-color-gray-1);
|
|
211
|
+
background: transparent;
|
|
212
|
+
font: inherit;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
opacity: 0.45;
|
|
215
|
+
transition:
|
|
216
|
+
opacity 120ms ease-out,
|
|
217
|
+
border-color 120ms ease-out,
|
|
218
|
+
color 120ms ease-out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.notation-formula-copy svg {
|
|
222
|
+
width: 0.85rem;
|
|
223
|
+
height: 0.85rem;
|
|
224
|
+
fill: none;
|
|
225
|
+
stroke: currentColor;
|
|
226
|
+
stroke-width: 1.75;
|
|
227
|
+
stroke-linecap: round;
|
|
228
|
+
stroke-linejoin: round;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@media (hover: none) {
|
|
232
|
+
.notation-formula-number,
|
|
233
|
+
.notation-formula-copy {
|
|
234
|
+
opacity: 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Hovering (or focusing) anywhere in the equation reveals the number and
|
|
239
|
+
copy button together — the "highlight" that signals the whole box is
|
|
240
|
+
interactive, not just its own small controls corner. */
|
|
241
|
+
.notation-formula-math:hover .notation-formula-number,
|
|
242
|
+
.notation-formula-math:focus-within .notation-formula-number,
|
|
243
|
+
.notation-formula-number:focus-visible {
|
|
244
|
+
opacity: 1;
|
|
245
|
+
text-decoration: underline;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.notation-formula-math:hover .notation-formula-copy,
|
|
249
|
+
.notation-formula-math:focus-within .notation-formula-copy,
|
|
250
|
+
.notation-formula-copy:focus-visible {
|
|
251
|
+
opacity: 1;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.notation-formula-copy:hover,
|
|
255
|
+
.notation-formula-copy:focus-visible {
|
|
256
|
+
border-color: var(--sl-color-accent);
|
|
257
|
+
color: var(--sl-color-text-accent);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.notation-formula-copy:focus-visible {
|
|
261
|
+
outline: 2px solid var(--sl-color-accent);
|
|
262
|
+
outline-offset: 2px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* `data-copied` is set briefly by the click handler in
|
|
266
|
+
`NotationLayer.astro` — same "flash a tooltip, then remove the
|
|
267
|
+
attribute" pattern as `.keyed-equation__copy[data-copied]`. */
|
|
268
|
+
.notation-formula-copy[data-copied] {
|
|
269
|
+
opacity: 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.notation-formula-copy[data-copied]::after {
|
|
273
|
+
content: 'Copied!';
|
|
274
|
+
position: absolute;
|
|
275
|
+
inset-block-end: calc(100% + 0.4rem);
|
|
276
|
+
inset-inline-end: 0;
|
|
277
|
+
padding: 0.15rem 0.45rem;
|
|
278
|
+
border-radius: 0.35rem;
|
|
279
|
+
background: var(--sl-color-accent-low);
|
|
280
|
+
color: var(--sl-color-text-accent);
|
|
281
|
+
font-size: var(--sl-text-xs);
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
pointer-events: none;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* The non-numbered case (a formula inside a hover popup, the "Notation
|
|
287
|
+
used on this page" list, a `Diagram`, or a lab's interactive equation)
|
|
288
|
+
has no controls to flash against, so the box itself flashes instead —
|
|
289
|
+
the generic `[data-notation-formula][data-copied]::after` rule in
|
|
290
|
+
`global.css`, not reimplemented here. */
|
|
291
|
+
|
|
292
|
+
@media (prefers-reduced-motion: reduce) {
|
|
293
|
+
.notation-formula-number,
|
|
294
|
+
.notation-formula-copy {
|
|
295
|
+
transition: none;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@media (forced-colors: active) {
|
|
300
|
+
.notation-formula-number,
|
|
301
|
+
.notation-formula-copy {
|
|
302
|
+
opacity: 1;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
117
306
|
.notation-gloss-list {
|
|
118
307
|
display: flex;
|
|
119
308
|
flex-wrap: wrap;
|
|
@@ -19,6 +19,15 @@ const [notationEntries, lessonEntries, manifest] = await Promise.all([
|
|
|
19
19
|
getCollection('docs', ({ id }) => isLessonSlug(id)),
|
|
20
20
|
loadManifest(),
|
|
21
21
|
]);
|
|
22
|
+
// Starlight's `link-alt` icon — the same glyph its own heading anchors use
|
|
23
|
+
// (`rehype-heading-anchors.mjs`). Reimplemented here rather than reused via
|
|
24
|
+
// `.sl-anchor-link`/`.sl-heading-wrapper` because that CSS assumes a single
|
|
25
|
+
// inline prose heading, not this entry's three-column grid header (symbol,
|
|
26
|
+
// name, editorial status) — the glyph carries over, the layout doesn't, so
|
|
27
|
+
// this file supplies its own reveal styling (below).
|
|
28
|
+
const HEADING_LINK_ICON_PATH =
|
|
29
|
+
'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';
|
|
30
|
+
|
|
22
31
|
const labelOf = (data: { key: string; label?: string }) =>
|
|
23
32
|
resolveLabel(data.key, data.label);
|
|
24
33
|
const notationLabelByKey = new Map(
|
|
@@ -83,6 +92,18 @@ const renderedEntries = await Promise.all(
|
|
|
83
92
|
})),
|
|
84
93
|
);
|
|
85
94
|
|
|
95
|
+
// Every entry with a rigorous `formula` gets its own equation number, `G.<n>`
|
|
96
|
+
// in page order — the glossary's own numbering, distinct from a lesson's
|
|
97
|
+
// chapter-qualified `(2.1)` (`equations-validate.ts`), since a glossary
|
|
98
|
+
// formula has no lesson/part to qualify it against.
|
|
99
|
+
let equationCounter = 0;
|
|
100
|
+
const equationNumberByKey = new Map<string, string>();
|
|
101
|
+
for (const { entry } of renderedEntries) {
|
|
102
|
+
if (!entry.data.formula) continue;
|
|
103
|
+
equationCounter += 1;
|
|
104
|
+
equationNumberByKey.set(entry.data.key, `G.${equationCounter}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
// Drives the hover explanation for `[[key]]` links inside the definition
|
|
87
108
|
// bodies. Every entry is already shown in full on this page, so the layer
|
|
88
109
|
// renders in panel-only mode: no visible list, just the shared panel.
|
|
@@ -138,17 +159,26 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
138
159
|
<NotationMath class="glossary-symbol" latex={entry.data.latex} />
|
|
139
160
|
</span>
|
|
140
161
|
<div>
|
|
141
|
-
<h2>
|
|
162
|
+
<h2>
|
|
163
|
+
<a
|
|
164
|
+
class="glossary-term-link"
|
|
165
|
+
href={`#notation-${entry.data.key}`}
|
|
166
|
+
>
|
|
167
|
+
{labelOf(entry.data)}
|
|
168
|
+
</a>
|
|
169
|
+
<a
|
|
170
|
+
class="glossary-anchor"
|
|
171
|
+
href={`#notation-${entry.data.key}`}
|
|
172
|
+
aria-label={`Link to “${labelOf(entry.data)}”`}
|
|
173
|
+
>
|
|
174
|
+
<span class="glossary-anchor-icon" aria-hidden="true">
|
|
175
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
176
|
+
<path d={HEADING_LINK_ICON_PATH}></path>
|
|
177
|
+
</svg>
|
|
178
|
+
</span>
|
|
179
|
+
</a>
|
|
180
|
+
</h2>
|
|
142
181
|
<code>{entry.data.key}</code>
|
|
143
|
-
<a
|
|
144
|
-
class="glossary-anchor"
|
|
145
|
-
href={`#notation-${entry.data.key}`}
|
|
146
|
-
data-glossary-anchor
|
|
147
|
-
aria-label={`Copy link to “${labelOf(entry.data)}”`}
|
|
148
|
-
title="Copy link to this entry"
|
|
149
|
-
>
|
|
150
|
-
<span aria-hidden="true">#</span>
|
|
151
|
-
</a>
|
|
152
182
|
</div>
|
|
153
183
|
<span class="glossary-status">{entry.data.editorialStatus}</span>
|
|
154
184
|
</header>
|
|
@@ -193,6 +223,8 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
193
223
|
scope={formulaScopeByKey.get(entry.data.key) ?? []}
|
|
194
224
|
glosses={glossesByKey.get(entry.data.key)}
|
|
195
225
|
entryLabel={labelOf(entry.data)}
|
|
226
|
+
number={equationNumberByKey.get(entry.data.key)}
|
|
227
|
+
numberHref={`#notation-${entry.data.key}`}
|
|
196
228
|
/>
|
|
197
229
|
</div>
|
|
198
230
|
)}
|
|
@@ -264,34 +296,6 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
264
296
|
definitions={panelDefinitions}
|
|
265
297
|
/>
|
|
266
298
|
|
|
267
|
-
<script>
|
|
268
|
-
const RESET_MS = 1200;
|
|
269
|
-
|
|
270
|
-
for (const anchor of document.querySelectorAll<HTMLAnchorElement>(
|
|
271
|
-
'[data-glossary-anchor]',
|
|
272
|
-
)) {
|
|
273
|
-
anchor.addEventListener('click', () => {
|
|
274
|
-
const href = anchor.getAttribute('href');
|
|
275
|
-
if (!href || !navigator.clipboard) return;
|
|
276
|
-
|
|
277
|
-
// Keep the native hash navigation (scroll + history) and copy the
|
|
278
|
-
// absolute URL alongside it.
|
|
279
|
-
const url = new URL(href, window.location.href).href;
|
|
280
|
-
navigator.clipboard.writeText(url).then(
|
|
281
|
-
() => {
|
|
282
|
-
anchor.dataset.copied = 'true';
|
|
283
|
-
window.setTimeout(() => {
|
|
284
|
-
delete anchor.dataset.copied;
|
|
285
|
-
}, RESET_MS);
|
|
286
|
-
},
|
|
287
|
-
() => {
|
|
288
|
-
/* Clipboard blocked: the plain anchor link still works. */
|
|
289
|
-
},
|
|
290
|
-
);
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
</script>
|
|
294
|
-
|
|
295
299
|
<style>
|
|
296
300
|
.glossary-index ul {
|
|
297
301
|
display: grid;
|
|
@@ -326,6 +330,17 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
326
330
|
font-size: var(--sl-text-xl);
|
|
327
331
|
}
|
|
328
332
|
|
|
333
|
+
.glossary-term-link {
|
|
334
|
+
color: inherit;
|
|
335
|
+
text-decoration: none;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.glossary-term-link:hover,
|
|
339
|
+
.glossary-term-link:focus-visible {
|
|
340
|
+
color: var(--sl-color-text-accent);
|
|
341
|
+
text-decoration: underline;
|
|
342
|
+
}
|
|
343
|
+
|
|
329
344
|
.glossary-symbol {
|
|
330
345
|
color: var(--sl-color-accent-high);
|
|
331
346
|
font-size: 1.2rem;
|
|
@@ -353,14 +368,22 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
353
368
|
}
|
|
354
369
|
}
|
|
355
370
|
|
|
371
|
+
/* Mirrors the hover/focus-reveal treatment of a content heading's own
|
|
372
|
+
`.sl-anchor-link` (`rehype-heading-anchors.mjs`), reimplemented here
|
|
373
|
+
rather than reused because that class's CSS assumes a single inline
|
|
374
|
+
prose heading, not this entry's grid-based header. */
|
|
356
375
|
.glossary-anchor {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
376
|
+
display: inline-flex;
|
|
377
|
+
align-items: center;
|
|
378
|
+
margin-inline-start: 0.4rem;
|
|
360
379
|
color: var(--sl-color-gray-3);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
380
|
+
vertical-align: middle;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.glossary-anchor-icon svg {
|
|
384
|
+
width: 0.85em;
|
|
385
|
+
height: 0.85em;
|
|
386
|
+
display: block;
|
|
364
387
|
}
|
|
365
388
|
|
|
366
389
|
.glossary-anchor:hover,
|
|
@@ -368,20 +391,28 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
368
391
|
color: var(--sl-color-text-accent);
|
|
369
392
|
}
|
|
370
393
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
394
|
+
@media (hover: hover) {
|
|
395
|
+
.glossary-anchor {
|
|
396
|
+
opacity: 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
h2:hover .glossary-anchor,
|
|
400
|
+
h2:focus-within .glossary-anchor,
|
|
401
|
+
.glossary-anchor:focus-visible {
|
|
402
|
+
opacity: 1;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
407
|
+
.glossary-anchor {
|
|
408
|
+
transition: opacity 120ms ease-out, color 120ms ease-out;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@media (forced-colors: active) {
|
|
413
|
+
.glossary-anchor {
|
|
414
|
+
opacity: 1;
|
|
415
|
+
}
|
|
385
416
|
}
|
|
386
417
|
|
|
387
418
|
.glossary-status {
|
|
@@ -263,14 +263,6 @@ for (const gloss of glossItems) {
|
|
|
263
263
|
</header>
|
|
264
264
|
<p data-slot-summary hidden />
|
|
265
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>
|
|
274
266
|
<p class="notation-units" data-slot-units hidden />
|
|
275
267
|
<a data-slot-link hidden>
|
|
276
268
|
Open the full definition
|
|
@@ -405,18 +397,7 @@ for (const gloss of glossItems) {
|
|
|
405
397
|
const units = fragment.querySelector<HTMLElement>('[data-slot-units]');
|
|
406
398
|
const link =
|
|
407
399
|
fragment.querySelector<HTMLAnchorElement>('[data-slot-link]');
|
|
408
|
-
|
|
409
|
-
'[data-popup-copy]',
|
|
410
|
-
);
|
|
411
|
-
if (
|
|
412
|
-
!el ||
|
|
413
|
-
!symbolHost ||
|
|
414
|
-
!title ||
|
|
415
|
-
!summary ||
|
|
416
|
-
!formulaHost ||
|
|
417
|
-
!units ||
|
|
418
|
-
!copyButton
|
|
419
|
-
) {
|
|
400
|
+
if (!el || !symbolHost || !title || !summary || !formulaHost || !units) {
|
|
420
401
|
return null;
|
|
421
402
|
}
|
|
422
403
|
|
|
@@ -443,34 +424,6 @@ for (const gloss of glossItems) {
|
|
|
443
424
|
);
|
|
444
425
|
formulaHost.hidden = sourceFormula === null;
|
|
445
426
|
|
|
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
|
-
|
|
474
427
|
setOptionalText(
|
|
475
428
|
units,
|
|
476
429
|
entry
|
|
@@ -547,6 +500,84 @@ for (const gloss of glossItems) {
|
|
|
547
500
|
);
|
|
548
501
|
if (details) setupNotationDisclosure(details);
|
|
549
502
|
}
|
|
503
|
+
|
|
504
|
+
// "Copy LaTeX" for a notation formula (a glossary entry's own numbered
|
|
505
|
+
// formula, or one named inline via `\explain{}` inside a popup/`Diagram`),
|
|
506
|
+
// two ways: its own button (only a numbered formula has one —
|
|
507
|
+
// `data-notation-formula-copy`, `NotationFormula.astro`), or a click
|
|
508
|
+
// anywhere else in the box that isn't the number link or an explained
|
|
509
|
+
// symbol — the same region `.notation-formula-math:hover` highlights
|
|
510
|
+
// (`NotationFormula.astro`), which includes the reserved gutter between
|
|
511
|
+
// the equation and its number, not just the rendered `.katex-html`. This
|
|
512
|
+
// listener lives here, not in `NotationFormula.astro` or
|
|
513
|
+
// `KeyedFloatEnhancer.astro`, because this component loads on every page —
|
|
514
|
+
// it's the Starlight `Footer` override — while `KeyedFloatEnhancer` is
|
|
515
|
+
// lesson-only and a formula can appear inside a hover popup mounted
|
|
516
|
+
// outside `.sl-markdown-content`.
|
|
517
|
+
document.addEventListener('click', (event) => {
|
|
518
|
+
const origin = event.target instanceof Element ? event.target : null;
|
|
519
|
+
if (!origin) return;
|
|
520
|
+
|
|
521
|
+
const ownButton = origin.closest<HTMLButtonElement>(
|
|
522
|
+
'[data-notation-formula-copy]',
|
|
523
|
+
);
|
|
524
|
+
const container = ownButton
|
|
525
|
+
? null
|
|
526
|
+
: origin.closest<HTMLElement>('[data-notation-formula]');
|
|
527
|
+
if (!ownButton) {
|
|
528
|
+
if (origin.closest('[data-notation-key], .notation-formula-number')) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (!container) return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const feedbackTarget =
|
|
535
|
+
ownButton ??
|
|
536
|
+
container?.querySelector<HTMLElement>('[data-notation-formula-copy]') ??
|
|
537
|
+
container;
|
|
538
|
+
const latex =
|
|
539
|
+
ownButton?.dataset.latex ?? feedbackTarget?.dataset.latex ?? '';
|
|
540
|
+
if (!feedbackTarget || latex.length === 0 || !navigator.clipboard) return;
|
|
541
|
+
|
|
542
|
+
navigator.clipboard.writeText(latex).then(
|
|
543
|
+
() => {
|
|
544
|
+
feedbackTarget.dataset.copied = 'true';
|
|
545
|
+
window.setTimeout(() => {
|
|
546
|
+
delete feedbackTarget.dataset.copied;
|
|
547
|
+
}, 1200);
|
|
548
|
+
},
|
|
549
|
+
() => {
|
|
550
|
+
/* Clipboard blocked: nothing more we can do here. */
|
|
551
|
+
},
|
|
552
|
+
);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// A tooltip for the click gesture above, and only for the part of the
|
|
556
|
+
// equation it actually applies to — an explained symbol has its own hover
|
|
557
|
+
// card (created above) and needs no tooltip layered on top of it, and the
|
|
558
|
+
// copy button already carries a static `title`. `mouseover`/`mouseout` (not
|
|
559
|
+
// `mouseenter`/`mouseleave`) because they bubble, which this document-level
|
|
560
|
+
// delegation needs.
|
|
561
|
+
document.addEventListener('mouseover', (event) => {
|
|
562
|
+
const origin = event.target instanceof Element ? event.target : null;
|
|
563
|
+
if (!origin || origin.getAttribute('title') === 'Copy LaTeX') return;
|
|
564
|
+
if (
|
|
565
|
+
origin.closest(
|
|
566
|
+
'[data-notation-key], [data-notation-formula-copy], .notation-formula-number',
|
|
567
|
+
)
|
|
568
|
+
) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (!origin.closest('[data-notation-formula]')) return;
|
|
572
|
+
origin.setAttribute('title', 'Copy LaTeX');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
document.addEventListener('mouseout', (event) => {
|
|
576
|
+
const origin = event.target instanceof Element ? event.target : null;
|
|
577
|
+
if (origin?.getAttribute('title') === 'Copy LaTeX') {
|
|
578
|
+
origin.removeAttribute('title');
|
|
579
|
+
}
|
|
580
|
+
});
|
|
550
581
|
</script>
|
|
551
582
|
|
|
552
583
|
<style>
|
|
@@ -722,6 +753,10 @@ for (const gloss of glossItems) {
|
|
|
722
753
|
font-size: var(--sl-text-sm);
|
|
723
754
|
}
|
|
724
755
|
|
|
756
|
+
/* Cursor, hover highlight, and "Copied!" flash for this plain-span
|
|
757
|
+
formula fallback (a definition with no `formulaScope`) come from the
|
|
758
|
+
generic `[data-notation-formula]` rules in `global.css` — shared with
|
|
759
|
+
every other producer of that attribute, not reimplemented here. */
|
|
725
760
|
.notation-formula {
|
|
726
761
|
overflow-x: auto;
|
|
727
762
|
/* `overflow-x: auto` alone makes the block axis compute to `auto` too
|
|
@@ -823,39 +858,6 @@ for (const gloss of glossItems) {
|
|
|
823
858
|
line-height: 1.5;
|
|
824
859
|
}
|
|
825
860
|
|
|
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
|
-
|
|
859
861
|
.notation-panel-body [data-slot-formula] {
|
|
860
862
|
overflow-x: auto;
|
|
861
863
|
/* See `.notation-formula` above: pairs with `overflow-x` to stop the
|