react-cheminfo 0.7.0 → 0.9.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/README.md +60 -2
- package/bin/check-tokens.mjs +105 -0
- package/lib/about/core/about.d.ts +95 -0
- package/lib/about/core/about.d.ts.map +1 -0
- package/lib/about/core/about.js +78 -0
- package/lib/about/core/about.js.map +1 -0
- package/lib/about/core/index.d.ts +3 -0
- package/lib/about/core/index.d.ts.map +1 -0
- package/lib/about/core/index.js +2 -0
- package/lib/about/core/index.js.map +1 -0
- package/lib/about/ui/AboutCitations.d.ts +14 -0
- package/lib/about/ui/AboutCitations.d.ts.map +1 -0
- package/lib/about/ui/AboutCitations.js +40 -0
- package/lib/about/ui/AboutCitations.js.map +1 -0
- package/lib/about/ui/AboutPage.d.ts +32 -0
- package/lib/about/ui/AboutPage.d.ts.map +1 -0
- package/lib/about/ui/AboutPage.js +70 -0
- package/lib/about/ui/AboutPage.js.map +1 -0
- package/lib/about/ui/AboutSection.d.ts +23 -0
- package/lib/about/ui/AboutSection.d.ts.map +1 -0
- package/lib/about/ui/AboutSection.js +28 -0
- package/lib/about/ui/AboutSection.js.map +1 -0
- package/lib/about/ui/index.d.ts +7 -0
- package/lib/about/ui/index.d.ts.map +1 -0
- package/lib/about/ui/index.js +4 -0
- package/lib/about/ui/index.js.map +1 -0
- package/lib/chrome/ui/index.d.ts +1 -0
- package/lib/chrome/ui/index.d.ts.map +1 -1
- package/lib/chrome/ui/index.js +3 -0
- package/lib/chrome/ui/index.js.map +1 -1
- package/lib/citation/core/index.d.ts +1 -0
- package/lib/citation/core/index.d.ts.map +1 -1
- package/lib/citation/core/index.js +1 -0
- package/lib/citation/core/index.js.map +1 -1
- package/lib/citation/core/teachingPaper.d.ts +15 -0
- package/lib/citation/core/teachingPaper.d.ts.map +1 -0
- package/lib/citation/core/teachingPaper.js +32 -0
- package/lib/citation/core/teachingPaper.js.map +1 -0
- package/lib/clipboard/ui/CodeBlock.d.ts.map +1 -1
- package/lib/clipboard/ui/CodeBlock.js +4 -1
- package/lib/clipboard/ui/CodeBlock.js.map +1 -1
- package/lib/color/ui/ColorScaleLegend.js +1 -1
- package/lib/color/ui/ColorScaleLegend.js.map +1 -1
- package/lib/core.d.ts +3 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +5 -0
- package/lib/core.js.map +1 -1
- package/lib/credits/core/credits.d.ts +36 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +42 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/credits/ui/CreditsList.js +1 -1
- package/lib/credits/ui/CreditsList.js.map +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.js +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +10 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/tokens.d.ts.map +1 -1
- package/lib/ecosystem/core/tokens.js +1 -0
- package/lib/ecosystem/core/tokens.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.js +2 -2
- package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.js +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -1
- package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -1
- package/lib/ecosystem/ui/SiteTile.js +5 -5
- package/lib/ecosystem/ui/SiteTile.js.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +3 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/orbital/core/index.d.ts +1 -1
- package/lib/orbital/core/index.d.ts.map +1 -1
- package/lib/orbital/core/index.js +1 -1
- package/lib/orbital/core/index.js.map +1 -1
- package/lib/orbital/core/realHarmonics.d.ts +5 -2
- package/lib/orbital/core/realHarmonics.d.ts.map +1 -1
- package/lib/orbital/core/realHarmonics.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts +18 -0
- package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.js +84 -7
- package/lib/orbital/ui/AtomicOrbitalCanvas.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.d.ts +13 -0
- package/lib/orbital/ui/AtomicOrbitalViewer.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.js +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.js.map +1 -1
- package/lib/orbital/ui/camera.d.ts +15 -0
- package/lib/orbital/ui/camera.d.ts.map +1 -1
- package/lib/orbital/ui/camera.js +68 -0
- package/lib/orbital/ui/camera.js.map +1 -1
- package/lib/orbital/ui/renderVolume.d.ts.map +1 -1
- package/lib/orbital/ui/renderVolume.js +8 -1
- package/lib/orbital/ui/renderVolume.js.map +1 -1
- package/lib/orbital/ui/serial.d.ts +25 -0
- package/lib/orbital/ui/serial.d.ts.map +1 -0
- package/lib/orbital/ui/serial.js +25 -0
- package/lib/orbital/ui/serial.js.map +1 -0
- package/lib/orbital/ui/viewer.d.ts +23 -0
- package/lib/orbital/ui/viewer.d.ts.map +1 -1
- package/lib/orbital/ui/viewer.js +70 -17
- package/lib/orbital/ui/viewer.js.map +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.js +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryText.js +2 -2
- package/lib/pedagogy/ui/GlossaryText.js.map +1 -1
- package/lib/pedagogy/ui/ReferenceSectionBlock.js +5 -2
- package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
- package/lib/pedagogy/ui/SyntaxTooltip.js +2 -2
- package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.d.ts +15 -2
- package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.js +19 -5
- package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
- package/lib/pedagogy/ui/index.d.ts +1 -1
- package/lib/pedagogy/ui/index.d.ts.map +1 -1
- package/lib/pedagogy/ui/index.js.map +1 -1
- package/lib/periodic/ui/CategoryLegend.js +1 -1
- package/lib/periodic/ui/CategoryLegend.js.map +1 -1
- package/lib/periodic/ui/PeriodicTableChrome.js +2 -2
- package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
- package/lib/seo/vite/ogCard.d.ts.map +1 -1
- package/lib/seo/vite/ogCard.js +1 -0
- package/lib/seo/vite/ogCard.js.map +1 -1
- package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
- package/lib/share/ui/ShareDialogContent.js +5 -2
- package/lib/share/ui/ShareDialogContent.js.map +1 -1
- package/lib/share/ui/SharePartOptions.js +1 -1
- package/lib/share/ui/SharePartOptions.js.map +1 -1
- package/lib/slides/core/demoLinks.d.ts +43 -0
- package/lib/slides/core/demoLinks.d.ts.map +1 -0
- package/lib/slides/core/demoLinks.js +104 -0
- package/lib/slides/core/demoLinks.js.map +1 -0
- package/lib/slides/core/index.d.ts +13 -0
- package/lib/slides/core/index.d.ts.map +1 -0
- package/lib/slides/core/index.js +7 -0
- package/lib/slides/core/index.js.map +1 -0
- package/lib/slides/core/sectionHeading.d.ts +19 -0
- package/lib/slides/core/sectionHeading.d.ts.map +1 -0
- package/lib/slides/core/sectionHeading.js +19 -0
- package/lib/slides/core/sectionHeading.js.map +1 -0
- package/lib/slides/core/slideNavigation.d.ts +38 -0
- package/lib/slides/core/slideNavigation.d.ts.map +1 -0
- package/lib/slides/core/slideNavigation.js +65 -0
- package/lib/slides/core/slideNavigation.js.map +1 -0
- package/lib/slides/core/talk.d.ts +82 -0
- package/lib/slides/core/talk.d.ts.map +1 -0
- package/lib/slides/core/talk.js +134 -0
- package/lib/slides/core/talk.js.map +1 -0
- package/lib/slides/core/talkManifest.d.ts +90 -0
- package/lib/slides/core/talkManifest.d.ts.map +1 -0
- package/lib/slides/core/talkManifest.js +109 -0
- package/lib/slides/core/talkManifest.js.map +1 -0
- package/lib/slides/core/talkOrigin.d.ts +50 -0
- package/lib/slides/core/talkOrigin.d.ts.map +1 -0
- package/lib/slides/core/talkOrigin.js +62 -0
- package/lib/slides/core/talkOrigin.js.map +1 -0
- package/lib/slides/ui/BackToSlides.d.ts +26 -0
- package/lib/slides/ui/BackToSlides.d.ts.map +1 -0
- package/lib/slides/ui/BackToSlides.js +24 -0
- package/lib/slides/ui/BackToSlides.js.map +1 -0
- package/lib/slides/ui/DemoLink.d.ts +42 -0
- package/lib/slides/ui/DemoLink.d.ts.map +1 -0
- package/lib/slides/ui/DemoLink.js +17 -0
- package/lib/slides/ui/DemoLink.js.map +1 -0
- package/lib/slides/ui/EmbedSlide.d.ts +19 -0
- package/lib/slides/ui/EmbedSlide.d.ts.map +1 -0
- package/lib/slides/ui/EmbedSlide.js +56 -0
- package/lib/slides/ui/EmbedSlide.js.map +1 -0
- package/lib/slides/ui/SectionSlide.d.ts +18 -0
- package/lib/slides/ui/SectionSlide.d.ts.map +1 -0
- package/lib/slides/ui/SectionSlide.js +18 -0
- package/lib/slides/ui/SectionSlide.js.map +1 -0
- package/lib/slides/ui/Slide.d.ts +52 -0
- package/lib/slides/ui/Slide.d.ts.map +1 -0
- package/lib/slides/ui/Slide.js +58 -0
- package/lib/slides/ui/Slide.js.map +1 -0
- package/lib/slides/ui/SlideBody.d.ts +32 -0
- package/lib/slides/ui/SlideBody.d.ts.map +1 -0
- package/lib/slides/ui/SlideBody.js +27 -0
- package/lib/slides/ui/SlideBody.js.map +1 -0
- package/lib/slides/ui/Slideshow.d.ts +93 -0
- package/lib/slides/ui/Slideshow.d.ts.map +1 -0
- package/lib/slides/ui/Slideshow.js +122 -0
- package/lib/slides/ui/Slideshow.js.map +1 -0
- package/lib/slides/ui/SlideshowBar.d.ts +33 -0
- package/lib/slides/ui/SlideshowBar.d.ts.map +1 -0
- package/lib/slides/ui/SlideshowBar.js +20 -0
- package/lib/slides/ui/SlideshowBar.js.map +1 -0
- package/lib/slides/ui/TalkList.d.ts +37 -0
- package/lib/slides/ui/TalkList.d.ts.map +1 -0
- package/lib/slides/ui/TalkList.js +68 -0
- package/lib/slides/ui/TalkList.js.map +1 -0
- package/lib/slides/ui/index.d.ts +19 -0
- package/lib/slides/ui/index.d.ts.map +1 -0
- package/lib/slides/ui/index.js +10 -0
- package/lib/slides/ui/index.js.map +1 -0
- package/lib/slides/vite/index.d.ts +3 -0
- package/lib/slides/vite/index.d.ts.map +1 -0
- package/lib/slides/vite/index.js +2 -0
- package/lib/slides/vite/index.js.map +1 -0
- package/lib/slides/vite/talks.d.ts +50 -0
- package/lib/slides/vite/talks.d.ts.map +1 -0
- package/lib/slides/vite/talks.js +87 -0
- package/lib/slides/vite/talks.js.map +1 -0
- package/lib/slides.d.ts +3 -0
- package/lib/slides.d.ts.map +1 -0
- package/lib/slides.js +3 -0
- package/lib/slides.js.map +1 -0
- package/lib/structure/ui/StructureEditor.js +2 -2
- package/lib/structure/ui/StructureEditor.js.map +1 -1
- package/lib/structure/ui/StructurePlaceholder.js +1 -1
- package/lib/structure/ui/StructurePlaceholder.js.map +1 -1
- package/lib/tokens/core/checkTokens.d.ts +51 -0
- package/lib/tokens/core/checkTokens.d.ts.map +1 -0
- package/lib/tokens/core/checkTokens.js +188 -0
- package/lib/tokens/core/checkTokens.js.map +1 -0
- package/lib/tokens/core/colors.d.ts +42 -0
- package/lib/tokens/core/colors.d.ts.map +1 -0
- package/lib/tokens/core/colors.js +119 -0
- package/lib/tokens/core/colors.js.map +1 -0
- package/lib/tokens/core/index.d.ts +3 -0
- package/lib/tokens/core/index.d.ts.map +1 -0
- package/lib/tokens/core/index.js +2 -0
- package/lib/tokens/core/index.js.map +1 -0
- package/lib/tokens/core/rules.d.ts +23 -0
- package/lib/tokens/core/rules.d.ts.map +1 -0
- package/lib/tokens/core/rules.js +90 -0
- package/lib/tokens/core/rules.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/lib/vite.d.ts +1 -0
- package/lib/vite.d.ts.map +1 -1
- package/lib/vite.js +1 -0
- package/lib/vite.js.map +1 -1
- package/package.json +11 -3
- package/src/about/core/about.ts +176 -0
- package/src/about/core/index.ts +2 -0
- package/src/about/ui/AboutCitations.tsx +79 -0
- package/src/about/ui/AboutPage.tsx +177 -0
- package/src/about/ui/AboutSection.tsx +56 -0
- package/src/about/ui/index.ts +6 -0
- package/src/chrome/ui/index.ts +3 -0
- package/src/citation/core/index.ts +1 -0
- package/src/citation/core/teachingPaper.ts +36 -0
- package/src/clipboard/ui/CodeBlock.tsx +4 -1
- package/src/color/ui/ColorScaleLegend.tsx +1 -1
- package/src/core.ts +5 -0
- package/src/credits/core/credits.ts +42 -0
- package/src/credits/ui/CreditsList.tsx +1 -1
- package/src/delimited/ui/DelimitedTextPanel.tsx +1 -1
- package/src/ecosystem/core/sites.ts +12 -0
- package/src/ecosystem/core/tokens.ts +1 -0
- package/src/ecosystem/ui/EcosystemLinks.tsx +2 -2
- package/src/ecosystem/ui/EcosystemMenu.tsx +1 -1
- package/src/ecosystem/ui/SiteTile.tsx +7 -5
- package/src/ecosystem/ui/glyphs.tsx +40 -0
- package/src/orbital/core/index.ts +4 -1
- package/src/orbital/core/realHarmonics.ts +5 -2
- package/src/orbital/ui/AtomicOrbitalCanvas.tsx +126 -4
- package/src/orbital/ui/AtomicOrbitalViewer.tsx +14 -1
- package/src/orbital/ui/camera.ts +81 -0
- package/src/orbital/ui/renderVolume.ts +8 -1
- package/src/orbital/ui/serial.ts +35 -0
- package/src/orbital/ui/viewer.ts +72 -13
- package/src/pedagogy/ui/ExerciseProgressHeader.tsx +1 -1
- package/src/pedagogy/ui/GlossaryText.tsx +2 -2
- package/src/pedagogy/ui/ReferenceSectionBlock.tsx +5 -2
- package/src/pedagogy/ui/SyntaxTooltip.tsx +2 -2
- package/src/pedagogy/ui/TutorialStepStrip.tsx +39 -10
- package/src/pedagogy/ui/index.ts +4 -1
- package/src/periodic/ui/CategoryLegend.tsx +1 -1
- package/src/periodic/ui/PeriodicTableChrome.tsx +2 -2
- package/src/seo/vite/ogCard.ts +1 -0
- package/src/share/ui/ShareDialogContent.tsx +5 -2
- package/src/share/ui/SharePartOptions.tsx +1 -1
- package/src/slides/core/demoLinks.ts +113 -0
- package/src/slides/core/index.ts +22 -0
- package/src/slides/core/sectionHeading.ts +29 -0
- package/src/slides/core/slideNavigation.ts +77 -0
- package/src/slides/core/talk.ts +203 -0
- package/src/slides/core/talkManifest.ts +158 -0
- package/src/slides/core/talkOrigin.ts +83 -0
- package/src/slides/ui/BackToSlides.tsx +49 -0
- package/src/slides/ui/DemoLink.tsx +67 -0
- package/src/slides/ui/EmbedSlide.tsx +85 -0
- package/src/slides/ui/SectionSlide.tsx +44 -0
- package/src/slides/ui/Slide.tsx +143 -0
- package/src/slides/ui/SlideBody.tsx +64 -0
- package/src/slides/ui/Slideshow.tsx +282 -0
- package/src/slides/ui/SlideshowBar.tsx +83 -0
- package/src/slides/ui/TalkList.tsx +146 -0
- package/src/slides/ui/index.ts +18 -0
- package/src/slides/vite/index.ts +2 -0
- package/src/slides/vite/talks.ts +126 -0
- package/src/slides.ts +2 -0
- package/src/structure/ui/StructureEditor.tsx +2 -2
- package/src/structure/ui/StructurePlaceholder.tsx +1 -1
- package/src/tokens/core/checkTokens.ts +242 -0
- package/src/tokens/core/colors.ts +122 -0
- package/src/tokens/core/index.ts +6 -0
- package/src/tokens/core/rules.ts +103 -0
- package/src/ui.ts +1 -0
- package/src/vite.ts +1 -0
- package/styles/chrome.css +1 -0
- package/styles/slides.css +667 -0
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { Button, Tag, Tooltip } from '@blueprintjs/core';
|
|
2
2
|
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
import type { ExerciseLevel
|
|
4
|
+
import type { ExerciseLevel } from '../core/types.ts';
|
|
5
5
|
|
|
6
6
|
import { LEVEL_ORDER } from './exerciseMeta.ts';
|
|
7
7
|
import type { TutorialLevelColours } from './tutorialLevels.ts';
|
|
8
8
|
import { TUTORIAL_LEVEL_COLOURS } from './tutorialLevels.ts';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The little a strip needs of a step: what to call it, and which coloured group
|
|
12
|
+
* it belongs to. A tool passes its own steps, whatever else they carry.
|
|
13
|
+
*/
|
|
14
|
+
export interface TutorialStripStep {
|
|
15
|
+
/**
|
|
16
|
+
* What keys the button, when the tool gives its steps one.
|
|
17
|
+
* @default undefined — the position in the tour keys it instead
|
|
18
|
+
*/
|
|
19
|
+
id?: string;
|
|
20
|
+
title: string;
|
|
21
|
+
level: ExerciseLevel;
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
export interface TutorialStepStripProps {
|
|
11
25
|
/** The whole tour, in teaching order. */
|
|
12
|
-
steps: readonly
|
|
26
|
+
steps: readonly TutorialStripStep[];
|
|
13
27
|
/**
|
|
14
28
|
* Which step is open, as its position in `steps`. A position outside the
|
|
15
29
|
* tour is read as one of its ends, so a hand-edited link still opens.
|
|
@@ -110,7 +124,7 @@ export function TutorialStepStrip(props: TutorialStepStripProps): ReactElement {
|
|
|
110
124
|
}
|
|
111
125
|
|
|
112
126
|
interface LevelStripProps {
|
|
113
|
-
steps: readonly
|
|
127
|
+
steps: readonly TutorialStripStep[];
|
|
114
128
|
positions: readonly number[];
|
|
115
129
|
colours: TutorialLevelColours;
|
|
116
130
|
label: string;
|
|
@@ -122,11 +136,11 @@ function LevelStrip(props: LevelStripProps): ReactElement {
|
|
|
122
136
|
const { steps, positions, colours, label, activeIndex, onSelect } = props;
|
|
123
137
|
const buttons: ReactNode[] = [];
|
|
124
138
|
for (const position of positions) {
|
|
125
|
-
const step = steps[position] as
|
|
139
|
+
const step = steps[position] as TutorialStripStep;
|
|
126
140
|
const active = position === activeIndex;
|
|
127
141
|
buttons.push(
|
|
128
142
|
<Tooltip
|
|
129
|
-
key={step.id}
|
|
143
|
+
key={step.id ?? position}
|
|
130
144
|
content={step.title}
|
|
131
145
|
hoverOpenDelay={HOVER_OPEN_DELAY}
|
|
132
146
|
placement="bottom"
|
|
@@ -155,7 +169,7 @@ function LevelStrip(props: LevelStripProps): ReactElement {
|
|
|
155
169
|
}
|
|
156
170
|
|
|
157
171
|
function positionsOfLevel(
|
|
158
|
-
steps: readonly
|
|
172
|
+
steps: readonly TutorialStripStep[],
|
|
159
173
|
level: ExerciseLevel,
|
|
160
174
|
): number[] {
|
|
161
175
|
const positions: number[] = [];
|
|
@@ -172,6 +186,9 @@ function clampIndex(index: number, total: number): number {
|
|
|
172
186
|
|
|
173
187
|
/**
|
|
174
188
|
* The open step takes the darker shade of its own level, plus a border.
|
|
189
|
+
*
|
|
190
|
+
* Every button is the same square whatever it holds, so the numbers of one
|
|
191
|
+
* strip stand above those of the next and the tour reads as a grid.
|
|
175
192
|
* @param colours - The two shades of the level the step belongs to.
|
|
176
193
|
* @param active - Whether this is the step on screen.
|
|
177
194
|
* @returns The style of one numbered button.
|
|
@@ -181,20 +198,29 @@ function numberStyle(
|
|
|
181
198
|
active: boolean,
|
|
182
199
|
): CSSProperties {
|
|
183
200
|
return {
|
|
184
|
-
|
|
201
|
+
display: 'inline-flex',
|
|
202
|
+
width: STEP_SIZE,
|
|
203
|
+
height: STEP_SIZE,
|
|
204
|
+
alignItems: 'center',
|
|
205
|
+
justifyContent: 'center',
|
|
206
|
+
padding: 0,
|
|
185
207
|
border: active ? '2px solid #1c2127' : '1px solid rgb(17 20 24 / 20%)',
|
|
186
208
|
borderRadius: 4,
|
|
209
|
+
background: active ? colours.activeBackground : '#ffffff',
|
|
210
|
+
boxSizing: 'border-box',
|
|
187
211
|
color: '#1c2127',
|
|
188
212
|
cursor: 'pointer',
|
|
189
213
|
font: 'inherit',
|
|
190
214
|
fontSize: 12,
|
|
215
|
+
fontVariantNumeric: 'tabular-nums',
|
|
191
216
|
fontWeight: active ? 700 : 500,
|
|
192
217
|
lineHeight: 1,
|
|
193
|
-
minWidth: 26,
|
|
194
|
-
padding: active ? '5px 6px' : '6px 7px',
|
|
195
218
|
};
|
|
196
219
|
}
|
|
197
220
|
|
|
221
|
+
/** Wide enough for two digits, which every tour of ours stays under a hundred of. */
|
|
222
|
+
const STEP_SIZE = 28;
|
|
223
|
+
|
|
198
224
|
/** Long enough that the pointer can cross the strip without opening a title. */
|
|
199
225
|
const HOVER_OPEN_DELAY = 150;
|
|
200
226
|
|
|
@@ -227,4 +253,7 @@ const PAGER_STYLE: CSSProperties = {
|
|
|
227
253
|
paddingTop: 2,
|
|
228
254
|
};
|
|
229
255
|
|
|
230
|
-
const HINT_STYLE: CSSProperties = {
|
|
256
|
+
const HINT_STYLE: CSSProperties = {
|
|
257
|
+
color: 'var(--text-muted, #5b6875)',
|
|
258
|
+
fontSize: 11,
|
|
259
|
+
};
|
package/src/pedagogy/ui/index.ts
CHANGED
|
@@ -31,7 +31,10 @@ export type {
|
|
|
31
31
|
export { SyntaxTooltip } from './SyntaxTooltip.tsx';
|
|
32
32
|
export type { TestCaseListProps } from './TestCaseList.tsx';
|
|
33
33
|
export { TestCaseList } from './TestCaseList.tsx';
|
|
34
|
-
export type {
|
|
34
|
+
export type {
|
|
35
|
+
TutorialStepStripProps,
|
|
36
|
+
TutorialStripStep,
|
|
37
|
+
} from './TutorialStepStrip.tsx';
|
|
35
38
|
export { TutorialStepStrip } from './TutorialStepStrip.tsx';
|
|
36
39
|
export type { TutorialLevelColours } from './tutorialLevels.ts';
|
|
37
40
|
export { TUTORIAL_LEVEL_COLOURS } from './tutorialLevels.ts';
|
|
@@ -145,7 +145,7 @@ function HeaderCell(props: HeaderCellProps): ReactElement {
|
|
|
145
145
|
|
|
146
146
|
const headerStyle = {
|
|
147
147
|
alignItems: 'center',
|
|
148
|
-
color: 'rgb(95 107 124)',
|
|
148
|
+
color: 'var(--text-muted, rgb(95 107 124))',
|
|
149
149
|
display: 'flex',
|
|
150
150
|
fontSize: 'clamp(0.4rem, 1.9cqw, 0.62rem)',
|
|
151
151
|
justifyContent: 'center',
|
|
@@ -163,7 +163,7 @@ const markerStyle = {
|
|
|
163
163
|
alignItems: 'center',
|
|
164
164
|
border: '1px dashed rgb(182 191 204)',
|
|
165
165
|
borderRadius: 3,
|
|
166
|
-
color: 'rgb(95 107 124)',
|
|
166
|
+
color: 'var(--text-muted, rgb(95 107 124))',
|
|
167
167
|
display: 'flex',
|
|
168
168
|
fontSize: 'clamp(0.36rem, 1.7cqw, 0.55rem)',
|
|
169
169
|
justifyContent: 'center',
|
package/src/seo/vite/ogCard.ts
CHANGED
|
@@ -28,7 +28,10 @@ import type { ShareDialogProps, ShareDraft } from './ShareDialog.tsx';
|
|
|
28
28
|
import { SharePartOptions } from './SharePartOptions.tsx';
|
|
29
29
|
import { withPart } from './draft.ts';
|
|
30
30
|
|
|
31
|
-
const LEAD_STYLE: CSSProperties = {
|
|
31
|
+
const LEAD_STYLE: CSSProperties = {
|
|
32
|
+
marginTop: 0,
|
|
33
|
+
color: 'var(--text-muted, #5b6875)',
|
|
34
|
+
};
|
|
32
35
|
const SECTION_STYLE: CSSProperties = { marginBottom: 18 };
|
|
33
36
|
const ACTIONS_STYLE: CSSProperties = {
|
|
34
37
|
display: 'flex',
|
|
@@ -39,7 +42,7 @@ const ACTIONS_STYLE: CSSProperties = {
|
|
|
39
42
|
const HINT_STYLE: CSSProperties = {
|
|
40
43
|
display: 'block',
|
|
41
44
|
marginLeft: 26,
|
|
42
|
-
color: '#5b6875',
|
|
45
|
+
color: 'var(--text-muted, #5b6875)',
|
|
43
46
|
fontSize: 12,
|
|
44
47
|
};
|
|
45
48
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The links a slide hands over to the live tool.
|
|
3
|
+
*
|
|
4
|
+
* A demo link is a paragraph that is exactly one Markdown link to an in-app
|
|
5
|
+
* route. It is parsed by hand rather than by a Markdown library because the
|
|
6
|
+
* labels are chemistry — `[M+H]⁺` opens a bracket the label closes itself, and
|
|
7
|
+
* a formula such as `H(OCH2CH2)10` puts parentheses inside the URL — and both
|
|
8
|
+
* defeat a naive `\[(.*)\]\((.*)\)` pattern.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** One link a slide offers as a live demo. */
|
|
12
|
+
export interface DemoLinkSpec {
|
|
13
|
+
/** The link text, which may itself contain balanced brackets. */
|
|
14
|
+
label: string;
|
|
15
|
+
/** Where it goes; a demo link always targets an in-app route. */
|
|
16
|
+
href: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read a paragraph that is exactly one Markdown link `[label](url)` and nothing
|
|
21
|
+
* else, counting brackets so a label may hold its own pair, and accepting the
|
|
22
|
+
* angle-bracketed URL form `[label](<url>)` for a URL holding parentheses.
|
|
23
|
+
* @param text - The paragraph text.
|
|
24
|
+
* @returns The link, or `null` when the paragraph is anything else.
|
|
25
|
+
*/
|
|
26
|
+
export function parseSingleLink(text: string): DemoLinkSpec | null {
|
|
27
|
+
const source = text.trim();
|
|
28
|
+
if (!source.startsWith('[')) return null;
|
|
29
|
+
|
|
30
|
+
let depth = 0;
|
|
31
|
+
let labelEnd = -1;
|
|
32
|
+
for (let index = 0; index < source.length; index++) {
|
|
33
|
+
const char = source[index];
|
|
34
|
+
if (char === '[') {
|
|
35
|
+
depth++;
|
|
36
|
+
} else if (char === ']') {
|
|
37
|
+
depth--;
|
|
38
|
+
if (depth === 0) {
|
|
39
|
+
labelEnd = index;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (labelEnd === -1 || source[labelEnd + 1] !== '(') return null;
|
|
45
|
+
|
|
46
|
+
const label = source.slice(1, labelEnd);
|
|
47
|
+
let rest = source.slice(labelEnd + 2);
|
|
48
|
+
let href: string;
|
|
49
|
+
|
|
50
|
+
if (rest.startsWith('<')) {
|
|
51
|
+
const close = rest.indexOf('>');
|
|
52
|
+
if (close === -1 || rest[close + 1] !== ')') return null;
|
|
53
|
+
href = rest.slice(1, close);
|
|
54
|
+
rest = rest.slice(close + 2);
|
|
55
|
+
} else {
|
|
56
|
+
let parenDepth = 1;
|
|
57
|
+
let urlEnd = -1;
|
|
58
|
+
for (let index = 0; index < rest.length; index++) {
|
|
59
|
+
const char = rest[index];
|
|
60
|
+
if (char === '(') {
|
|
61
|
+
parenDepth++;
|
|
62
|
+
} else if (char === ')') {
|
|
63
|
+
parenDepth--;
|
|
64
|
+
if (parenDepth === 0) {
|
|
65
|
+
urlEnd = index;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (urlEnd === -1) return null;
|
|
71
|
+
href = rest.slice(0, urlEnd);
|
|
72
|
+
rest = rest.slice(urlEnd + 1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (rest.trim() !== '') return null;
|
|
76
|
+
return { label, href };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Split a slide body into its Markdown and the run of demo links that closes
|
|
81
|
+
* it. External links and ordinary paragraphs stay in the body, so a slide that
|
|
82
|
+
* merely cites a URL keeps it where the author wrote it.
|
|
83
|
+
* @param body - The slide Markdown body.
|
|
84
|
+
* @returns The body without its trailing demo links, and those links in order.
|
|
85
|
+
*/
|
|
86
|
+
export function splitDemoLinks(body: string): {
|
|
87
|
+
body: string;
|
|
88
|
+
demos: DemoLinkSpec[];
|
|
89
|
+
} {
|
|
90
|
+
const blocks = body.split(/\n{2,}/);
|
|
91
|
+
let firstDemo = blocks.length;
|
|
92
|
+
const demos: DemoLinkSpec[] = [];
|
|
93
|
+
for (let index = blocks.length - 1; index >= 0; index--) {
|
|
94
|
+
const link = parseSingleLink(blocks[index] ?? '');
|
|
95
|
+
if (!link?.href.startsWith('/')) break;
|
|
96
|
+
demos.unshift(link);
|
|
97
|
+
firstDemo = index;
|
|
98
|
+
}
|
|
99
|
+
return { body: blocks.slice(0, firstDemo).join('\n\n').trim(), demos };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The displayed form of a demo link label: the `Demo:` prefix an author writes
|
|
104
|
+
* to mark the paragraph is dropped, and the first character is uppercased.
|
|
105
|
+
* @param label - The raw link label.
|
|
106
|
+
* @returns What the button shows.
|
|
107
|
+
*/
|
|
108
|
+
export function demoLabel(label: string): string {
|
|
109
|
+
const text = label.replace(DEMO_PREFIX, '');
|
|
110
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const DEMO_PREFIX = /^Demo:\s*/i;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type { DemoLinkSpec } from './demoLinks.ts';
|
|
2
|
+
export { demoLabel, parseSingleLink, splitDemoLinks } from './demoLinks.ts';
|
|
3
|
+
export type { SectionHeading } from './sectionHeading.ts';
|
|
4
|
+
export { parseSectionHeading } from './sectionHeading.ts';
|
|
5
|
+
export type { SlideAction } from './slideNavigation.ts';
|
|
6
|
+
export { clampSlideIndex, slideActionForKey } from './slideNavigation.ts';
|
|
7
|
+
export type { Slide, Talk, TalkMeta } from './talk.ts';
|
|
8
|
+
export { parseTalk } from './talk.ts';
|
|
9
|
+
export type { TalkManifest, TalkSummary } from './talkManifest.ts';
|
|
10
|
+
export {
|
|
11
|
+
buildTalkManifest,
|
|
12
|
+
parseTalkManifest,
|
|
13
|
+
talkSourceUrl,
|
|
14
|
+
talkSummary,
|
|
15
|
+
} from './talkManifest.ts';
|
|
16
|
+
export type { TalkOrigin } from './talkOrigin.ts';
|
|
17
|
+
export {
|
|
18
|
+
TALK_ORIGIN_PARAM,
|
|
19
|
+
formatTalkOrigin,
|
|
20
|
+
parseTalkOrigin,
|
|
21
|
+
withTalkOrigin,
|
|
22
|
+
} from './talkOrigin.ts';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The parts of a section divider's heading. */
|
|
2
|
+
export interface SectionHeading {
|
|
3
|
+
/** The section number, as written; empty when the heading carries none. */
|
|
4
|
+
number: string;
|
|
5
|
+
/** The section title, without its number; empty when the heading carries none. */
|
|
6
|
+
title: string;
|
|
7
|
+
/** Whatever the body holds besides that heading. */
|
|
8
|
+
rest: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Split a section-divider body into its leading number, its title and the rest
|
|
13
|
+
* of the Markdown, so a template can set the number apart from the title.
|
|
14
|
+
* `·`, `.`, `:`, `—` and `-` all separate the two.
|
|
15
|
+
* @param body - Markdown body, normally starting with `# N · Title`.
|
|
16
|
+
* @returns The parsed heading; a body with no numbered heading keeps all of
|
|
17
|
+
* itself in `rest`, leaving `number` and `title` empty.
|
|
18
|
+
*/
|
|
19
|
+
export function parseSectionHeading(body: string): SectionHeading {
|
|
20
|
+
const match = HEAD_RE.exec(body);
|
|
21
|
+
const number = match?.groups?.number;
|
|
22
|
+
const title = match?.groups?.title;
|
|
23
|
+
if (match === null || number === undefined || title === undefined) {
|
|
24
|
+
return { number: '', title: '', rest: body.trim() };
|
|
25
|
+
}
|
|
26
|
+
return { number, title, rest: body.replace(match[0], '').trim() };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const HEAD_RE = /^#\s+(?<number>\d+)\s*[.:·—-]\s*(?<title>.+?)\s*$/m;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a key does during a presentation, decided without a DOM.
|
|
3
|
+
*
|
|
4
|
+
* The current slide lives in the address, so it arrives as whatever a link
|
|
5
|
+
* carried — a missing number, a negative one, one past the end of a talk that
|
|
6
|
+
* has since been shortened. Every move is therefore clamped here rather than
|
|
7
|
+
* trusted, and the player only ever renders a slide that exists.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The slide a requested index resolves to.
|
|
12
|
+
* @param index - The index asked for, from a link or a keystroke.
|
|
13
|
+
* @param total - How many slides the talk holds.
|
|
14
|
+
* @returns A zero-based index that exists, or `0` for a talk with no slides.
|
|
15
|
+
*/
|
|
16
|
+
export function clampSlideIndex(index: number, total: number): number {
|
|
17
|
+
if (!Number.isFinite(total) || total < 1) return 0;
|
|
18
|
+
if (Number.isNaN(index) || index < 0) return 0;
|
|
19
|
+
const last = total - 1;
|
|
20
|
+
return index > last ? last : Math.trunc(index);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What a key press asks the player to do. */
|
|
24
|
+
export type SlideAction =
|
|
25
|
+
| { kind: 'go'; index: number }
|
|
26
|
+
| { kind: 'blank'; color: 'black' | 'white' }
|
|
27
|
+
| { kind: 'fullscreen' }
|
|
28
|
+
| { kind: 'notes' }
|
|
29
|
+
| null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The action a key press means, with every move already clamped: stepping past
|
|
33
|
+
* the last slide, or back from the first, stays on the slide it is on rather
|
|
34
|
+
* than leaving the talk.
|
|
35
|
+
* @param key - The `key` of the keyboard event.
|
|
36
|
+
* @param index - Which slide is showing.
|
|
37
|
+
* @param total - How many slides the talk holds.
|
|
38
|
+
* @returns The action, or `null` when the key means nothing here.
|
|
39
|
+
*/
|
|
40
|
+
export function slideActionForKey(
|
|
41
|
+
key: string,
|
|
42
|
+
index: number,
|
|
43
|
+
total: number,
|
|
44
|
+
): SlideAction {
|
|
45
|
+
switch (key) {
|
|
46
|
+
case 'ArrowRight':
|
|
47
|
+
case 'ArrowDown':
|
|
48
|
+
case 'PageDown':
|
|
49
|
+
case ' ':
|
|
50
|
+
return { kind: 'go', index: clampSlideIndex(index + 1, total) };
|
|
51
|
+
case 'ArrowLeft':
|
|
52
|
+
case 'ArrowUp':
|
|
53
|
+
case 'PageUp':
|
|
54
|
+
return { kind: 'go', index: clampSlideIndex(index - 1, total) };
|
|
55
|
+
case 'Home':
|
|
56
|
+
return { kind: 'go', index: 0 };
|
|
57
|
+
case 'End':
|
|
58
|
+
return { kind: 'go', index: clampSlideIndex(total - 1, total) };
|
|
59
|
+
default:
|
|
60
|
+
return letterAction(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function letterAction(key: string): SlideAction {
|
|
65
|
+
switch (key.toLowerCase()) {
|
|
66
|
+
case 'f':
|
|
67
|
+
return { kind: 'fullscreen' };
|
|
68
|
+
case 'b':
|
|
69
|
+
return { kind: 'blank', color: 'black' };
|
|
70
|
+
case 'w':
|
|
71
|
+
return { kind: 'blank', color: 'white' };
|
|
72
|
+
case 'n':
|
|
73
|
+
return { kind: 'notes' };
|
|
74
|
+
default:
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The talk format: one Markdown file is one talk, and this reads it.
|
|
3
|
+
*
|
|
4
|
+
* A talk is authored as prose rather than as data — an optional front-matter
|
|
5
|
+
* block, then slides separated by a `---` line. Which template draws a slide is
|
|
6
|
+
* a comment the author writes, and the name is deliberately not a closed list:
|
|
7
|
+
* every site registers the layouts it knows how to draw.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Talk-level metadata, read from the front-matter block. */
|
|
11
|
+
export interface TalkMeta {
|
|
12
|
+
/** Talk title, shown on the title slide and in a talk index. */
|
|
13
|
+
title: string;
|
|
14
|
+
/**
|
|
15
|
+
* A second line under the title.
|
|
16
|
+
* @default undefined
|
|
17
|
+
*/
|
|
18
|
+
subtitle?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Conference or event the talk was given at.
|
|
21
|
+
* @default undefined
|
|
22
|
+
*/
|
|
23
|
+
event?: string;
|
|
24
|
+
/**
|
|
25
|
+
* When it was given, as an ISO date (`YYYY-MM-DD`).
|
|
26
|
+
* @default undefined
|
|
27
|
+
*/
|
|
28
|
+
date?: string;
|
|
29
|
+
/**
|
|
30
|
+
* City or venue.
|
|
31
|
+
* @default undefined
|
|
32
|
+
*/
|
|
33
|
+
location?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Who presented it.
|
|
36
|
+
* @default undefined
|
|
37
|
+
*/
|
|
38
|
+
author?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Logo image URLs shown on the title slide, written comma-separated in the
|
|
41
|
+
* front-matter.
|
|
42
|
+
* @default undefined
|
|
43
|
+
*/
|
|
44
|
+
logos?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* One sentence about the talk, for a listing or a page description.
|
|
47
|
+
* @default undefined
|
|
48
|
+
*/
|
|
49
|
+
description?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One slide of a talk. */
|
|
53
|
+
export interface Slide {
|
|
54
|
+
/**
|
|
55
|
+
* Which template draws the body. A site maps these names to its own
|
|
56
|
+
* components, so any lowercase identifier is accepted here.
|
|
57
|
+
*/
|
|
58
|
+
layout: string;
|
|
59
|
+
/** The Markdown body, with the layout marker and the notes comment removed. */
|
|
60
|
+
body: string;
|
|
61
|
+
/**
|
|
62
|
+
* What the presenter says over this slide, never drawn on it.
|
|
63
|
+
* @default undefined
|
|
64
|
+
*/
|
|
65
|
+
notes?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A whole talk: its metadata and its slides, in order. */
|
|
69
|
+
export interface Talk {
|
|
70
|
+
/** What the front-matter said about the talk. */
|
|
71
|
+
meta: TalkMeta;
|
|
72
|
+
/** The slides, in the order they are presented. */
|
|
73
|
+
slides: Slide[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse a talk written in Markdown into its metadata and its slides.
|
|
78
|
+
*
|
|
79
|
+
* The source is read defensively: CRLF endings, a missing front-matter block,
|
|
80
|
+
* an empty file and a slide holding nothing but whitespace all resolve to
|
|
81
|
+
* something a player can render rather than to an error.
|
|
82
|
+
* @param source - The raw Markdown talk source.
|
|
83
|
+
* @returns The parsed talk; an empty source yields an untitled talk with no slides.
|
|
84
|
+
*/
|
|
85
|
+
export function parseTalk(source: string): Talk {
|
|
86
|
+
const normalized = source.replaceAll(/\r\n?/g, '\n');
|
|
87
|
+
const { meta, body } = extractFrontMatter(normalized);
|
|
88
|
+
const slides: Slide[] = [];
|
|
89
|
+
for (const chunk of splitSlides(body)) {
|
|
90
|
+
slides.push(toSlide(chunk));
|
|
91
|
+
}
|
|
92
|
+
return { meta, slides };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extractFrontMatter(source: string): { meta: TalkMeta; body: string } {
|
|
96
|
+
const lines = source.split('\n');
|
|
97
|
+
let index = 0;
|
|
98
|
+
while (index < lines.length && lines[index]?.trim() === '') index++;
|
|
99
|
+
if (lines[index]?.trim() !== '---') {
|
|
100
|
+
return { meta: { title: '' }, body: source };
|
|
101
|
+
}
|
|
102
|
+
index++;
|
|
103
|
+
const metaLines: string[] = [];
|
|
104
|
+
while (index < lines.length && lines[index]?.trim() !== '---') {
|
|
105
|
+
metaLines.push(lines[index] ?? '');
|
|
106
|
+
index++;
|
|
107
|
+
}
|
|
108
|
+
index++;
|
|
109
|
+
return { meta: parseMeta(metaLines), body: lines.slice(index).join('\n') };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseMeta(metaLines: readonly string[]): TalkMeta {
|
|
113
|
+
const meta: TalkMeta = { title: '' };
|
|
114
|
+
for (const line of metaLines) {
|
|
115
|
+
const match = META_RE.exec(line);
|
|
116
|
+
const key = match?.groups?.key?.toLowerCase();
|
|
117
|
+
const value = match?.groups?.value?.trim();
|
|
118
|
+
if (key === undefined || value === undefined) continue;
|
|
119
|
+
if (key === 'logos') {
|
|
120
|
+
meta.logos = splitLogos(value);
|
|
121
|
+
} else if (TEXT_KEYS.has(key)) {
|
|
122
|
+
meta[key as TextMetaKey] = value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return meta;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function splitLogos(value: string): string[] {
|
|
129
|
+
const logos: string[] = [];
|
|
130
|
+
for (const logo of value.split(',')) {
|
|
131
|
+
const trimmed = logo.trim();
|
|
132
|
+
if (trimmed !== '') logos.push(trimmed);
|
|
133
|
+
}
|
|
134
|
+
return logos;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function splitSlides(body: string): string[] {
|
|
138
|
+
const chunks: string[][] = [[]];
|
|
139
|
+
for (const line of body.split('\n')) {
|
|
140
|
+
if (SEPARATOR_RE.test(line)) {
|
|
141
|
+
chunks.push([]);
|
|
142
|
+
} else {
|
|
143
|
+
chunks.at(-1)?.push(line);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const slides: string[] = [];
|
|
147
|
+
for (const chunk of chunks) {
|
|
148
|
+
const text = chunk.join('\n').trim();
|
|
149
|
+
if (text !== '') slides.push(text);
|
|
150
|
+
}
|
|
151
|
+
return slides;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function toSlide(chunk: string): Slide {
|
|
155
|
+
const layoutMatch = LAYOUT_RE.exec(chunk);
|
|
156
|
+
const { body, notes } = extractNotes(chunk.replace(LAYOUT_RE, ''));
|
|
157
|
+
const layout =
|
|
158
|
+
layoutMatch?.groups?.layout?.toLowerCase() ?? detectLayout(body);
|
|
159
|
+
const slide: Slide = { layout, body };
|
|
160
|
+
if (notes !== '') slide.notes = notes;
|
|
161
|
+
return slide;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function extractNotes(chunk: string): { body: string; notes: string } {
|
|
165
|
+
const notes: string[] = [];
|
|
166
|
+
for (const match of chunk.matchAll(NOTES_RE)) {
|
|
167
|
+
const text = match.groups?.notes?.trim();
|
|
168
|
+
if (text !== undefined && text !== '') notes.push(text);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
body: chunk.replaceAll(NOTES_RE, '').trim(),
|
|
172
|
+
notes: notes.join('\n\n'),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function detectLayout(body: string): string {
|
|
177
|
+
let headings = 0;
|
|
178
|
+
for (const line of body.split('\n')) {
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (trimmed === '') continue;
|
|
181
|
+
if (!trimmed.startsWith('#')) return 'content';
|
|
182
|
+
headings++;
|
|
183
|
+
}
|
|
184
|
+
return headings > 0 ? 'title' : 'content';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Which keys of {@link TalkMeta} a front-matter line writes as plain text. */
|
|
188
|
+
type TextMetaKey = Exclude<keyof TalkMeta, 'logos'>;
|
|
189
|
+
|
|
190
|
+
const TEXT_KEYS = new Set<string>([
|
|
191
|
+
'title',
|
|
192
|
+
'subtitle',
|
|
193
|
+
'event',
|
|
194
|
+
'date',
|
|
195
|
+
'location',
|
|
196
|
+
'author',
|
|
197
|
+
'description',
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
const LAYOUT_RE = /<!--\s*layout:\s*(?<layout>[a-z][\da-z-]*)\s*-->/i;
|
|
201
|
+
const NOTES_RE = /<!--\s*notes:\s*(?<notes>.*?)-->/gis;
|
|
202
|
+
const SEPARATOR_RE = /^---[\t ]*$/;
|
|
203
|
+
const META_RE = /^(?<key>[a-z]+)\s*:\s*(?<value>.+)$/i;
|