mdeck 3.3.0 → 3.5.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 +25 -0
- package/README.md +1 -1
- package/assets/base.css +23 -2
- package/assets/extensions/palettes/cobalt/extension.toml +30 -0
- package/assets/extensions/palettes/greyscale/extension.toml +57 -0
- package/assets/extensions/themes/academic/styles.css +18 -5
- package/assets/extensions/themes/glass/extension.toml +59 -0
- package/assets/extensions/themes/glass/styles.css +754 -0
- package/assets/extensions/themes/minimal/styles.css +16 -4
- package/assets/extensions/themes/neue/styles.css +17 -4
- package/assets/extensions/themes/plain/extension.toml +58 -0
- package/assets/extensions/themes/plain/styles.css +471 -0
- package/assets/extensions/themes/work/extension.toml +57 -0
- package/assets/extensions/themes/work/styles.css +512 -0
- package/docs/reference/authoring.md +50 -10
- package/docs/reference/palettes.md +4 -3
- package/docs/reference/themes.md +5 -3
- package/docs/site/content/appearance.md +24 -10
- package/docs/site/content/audience.md +49 -4
- package/docs/site/content/commands.md +2 -0
- package/docs/site/content/theme-repository.md +7 -7
- package/docs/site/images/themes/academic-dark.webp +0 -0
- package/docs/site/images/themes/academic-light.webp +0 -0
- package/docs/site/images/themes/academic.webp +0 -0
- package/docs/site/images/themes/glass-dark.webp +0 -0
- package/docs/site/images/themes/glass-light.webp +0 -0
- package/docs/site/images/themes/glass.webp +0 -0
- package/docs/site/images/themes/minimal-dark.webp +0 -0
- package/docs/site/images/themes/minimal-light.webp +0 -0
- package/docs/site/images/themes/minimal.webp +0 -0
- package/docs/site/images/themes/neue-dark.webp +0 -0
- package/docs/site/images/themes/neue-light.webp +0 -0
- package/docs/site/images/themes/neue.webp +0 -0
- package/docs/site/images/themes/plain-dark.webp +0 -0
- package/docs/site/images/themes/plain-light.webp +0 -0
- package/docs/site/images/themes/plain.webp +0 -0
- package/docs/site/images/themes/work-dark.webp +0 -0
- package/docs/site/images/themes/work-light.webp +0 -0
- package/docs/site/images/themes/work.webp +0 -0
- package/examples/showcase/slides.md +2 -2
- package/package.json +4 -1
- package/skills/write-slides/SKILL.md +2 -2
- package/src/build/bibliography.js +29 -11
- package/src/build/resultsPlugin.js +77 -0
- package/src/build/slidesPlugin.js +42 -18
- package/src/build/themeCheck.js +2 -1
- package/src/cli/args.js +2 -1
- package/src/cli/index.js +17 -4
- package/src/components/Numeric.jsx +59 -0
- package/src/components/Poll.jsx +72 -57
- package/src/components/Question.jsx +10 -6
- package/src/components/Scale.jsx +13 -8
- package/src/components/WordCloud.jsx +10 -6
- package/src/components/activity.jsx +88 -7
- package/src/components/inlineText.js +63 -0
- package/src/components/numbers.js +39 -0
- package/src/components/poll.css +26 -13
- package/src/core/editDeck.js +5 -6
- package/src/core/icons.js +2 -0
- package/src/core/labels.js +32 -0
- package/src/core/pollList.js +66 -0
- package/src/core/results.js +64 -0
- package/src/core/tags.js +195 -0
- package/src/editor/sampleDeck.js +34 -8
- package/src/extensions/discover.js +2 -2
- package/src/extensions/moved.js +4 -4
- package/src/layouts/layoutApi.jsx +24 -13
- package/src/live/answer/answer.css +19 -0
- package/src/live/answer/answer.js +147 -24
- package/src/live/client.js +51 -6
- package/src/live/roomTag.js +11 -15
- package/src/live/rooms.js +6 -1
- package/src/runtime/citations.js +15 -5
- package/src/runtime/ink/bus.js +7 -13
- package/src/runtime/main.jsx +5 -2
- package/src/runtime/markedSetup.js +3 -0
- package/src/runtime/registry.jsx +2 -0
- package/assets/extensions/palettes/neon/extension.toml +0 -30
- package/assets/extensions/themes/aurora/extension.toml +0 -40
- package/assets/extensions/themes/aurora/styles.css +0 -644
- package/docs/site/images/themes/aurora-dark.webp +0 -0
- package/docs/site/images/themes/aurora-light.webp +0 -0
- package/docs/site/images/themes/aurora.webp +0 -0
|
@@ -5,7 +5,7 @@ A **theme** chooses the fonts and the style of the slides. A **palette** chooses
|
|
|
5
5
|
Ask your assistant by name or by description:
|
|
6
6
|
|
|
7
7
|
```prompt
|
|
8
|
-
Use the
|
|
8
|
+
Use the glass theme, light. Or suggest a look that suits a talk for
|
|
9
9
|
a design conference.
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -26,9 +26,11 @@ If you already have a settings block there, change the existing lines rather tha
|
|
|
26
26
|
| Name to use | What it looks like |
|
|
27
27
|
|---|---|
|
|
28
28
|
| `neue` | Swiss style: large type, clear lines, red on white |
|
|
29
|
-
| `
|
|
29
|
+
| `plain` | Black on white and lots of room, the heading beside the content |
|
|
30
30
|
| `minimal` | Nothing but the content: system fonts, calm type, no decoration; works offline |
|
|
31
31
|
| `academic` | A lecture: compact slides, theorem and definition boxes, tables like in a paper |
|
|
32
|
+
| `work` | An everyday company template: a coloured band, your logo, numbered sections, a footer |
|
|
33
|
+
| `glass` | Liquid glass: soft light behind frosted panes; it starts dark |
|
|
32
34
|
|
|
33
35
|
Each theme comes with colours that suit it, so you do not have to choose any. More themes are in the theme repository (see “Themes that moved to the repository” below).
|
|
34
36
|
|
|
@@ -42,11 +44,11 @@ Each theme in its own colours, on the title slide of the tour example. Click a p
|
|
|
42
44
|
|
|
43
45
|
Default colours: `swiss`.
|
|
44
46
|
|
|
45
|
-
###
|
|
47
|
+
### Plain
|
|
46
48
|
|
|
47
|
-
[](images/themes/plain.webp)
|
|
48
50
|
|
|
49
|
-
Default colours: `
|
|
51
|
+
Default colours: `greyscale`. In the dark it is the same, inverted.
|
|
50
52
|
|
|
51
53
|
### Academic
|
|
52
54
|
|
|
@@ -60,6 +62,18 @@ Default colours: `nordic`.
|
|
|
60
62
|
|
|
61
63
|
Default colours: `paper`. It uses the fonts already on the computer, so it needs no internet connection and looks plain on purpose.
|
|
62
64
|
|
|
65
|
+
### Work
|
|
66
|
+
|
|
67
|
+
[](images/themes/work.webp)
|
|
68
|
+
|
|
69
|
+
Default colours: `cobalt`. Set `meta.logo` and it shows top left; any palette makes it your company's colours.
|
|
70
|
+
|
|
71
|
+
### Glass
|
|
72
|
+
|
|
73
|
+
[](images/themes/glass.webp)
|
|
74
|
+
|
|
75
|
+
Default colours: `nordic`, dark.
|
|
76
|
+
|
|
63
77
|
## Choose colours
|
|
64
78
|
|
|
65
79
|
To use other colours, set `palette` to one of these:
|
|
@@ -67,8 +81,9 @@ To use other colours, set `palette` to one of these:
|
|
|
67
81
|
| Name to use | Colours |
|
|
68
82
|
|---|---|
|
|
69
83
|
| `swiss` | White, black and signal red (neue's own) |
|
|
70
|
-
| `nordic` | Ice grey, deep navy and a cool steel blue (academic's own) |
|
|
71
|
-
| `
|
|
84
|
+
| `nordic` | Ice grey, deep navy and a cool steel blue (academic's and glass's own) |
|
|
85
|
+
| `cobalt` | Strong ultramarine on white with vivid orange (work's own) |
|
|
86
|
+
| `greyscale` | Black, white and greys only, good for printing (plain's own) |
|
|
72
87
|
| `lagoon` | Teal on warm stone |
|
|
73
88
|
| `paper` | Off-white and near-black with one orange accent (minimal's own) |
|
|
74
89
|
|
|
@@ -97,8 +112,6 @@ Some slides use the other version on purpose: neue's statement slides are dark i
|
|
|
97
112
|
|
|
98
113
|
The first chapter slide of the tour in each palette a theme offers. Choose a theme above the pictures; click a sheet to see it larger. A theme that offers only its own colours is not shown here.
|
|
99
114
|
|
|
100
|
-
Some themes turn their chapter slides around on purpose: aurora's are a night sky in a light talk, and light in a dark one.
|
|
101
|
-
|
|
102
115
|
### Light
|
|
103
116
|
|
|
104
117
|
<!-- theme-sheets light -->
|
|
@@ -127,7 +140,8 @@ Earlier versions of mdeck came with more themes and palettes. They are now in th
|
|
|
127
140
|
|
|
128
141
|
| Theme | What it looks like (its palette) |
|
|
129
142
|
|---|---|
|
|
130
|
-
| `
|
|
143
|
+
| `aurora` | Northern lights: curtains of light, night-sky chapters (`neon`) |
|
|
144
|
+
| `duet` | Two accent colours that take turns, slab headings (`cobalt`, which comes with mdeck) |
|
|
131
145
|
| `editorial` | A magazine spread with serif headlines and pull quotes (`terra`) |
|
|
132
146
|
| `fhnw` | The FHNW visual identity, with its own `brand` palette only |
|
|
133
147
|
| `terminal` | Headings typed at a prompt, a status bar; it starts dark (`phosphor`) |
|
|
@@ -28,19 +28,61 @@ Every poll in a presentation shows the same code, so people scan once. Their pho
|
|
|
28
28
|
| Setting | Meaning |
|
|
29
29
|
|---|---|
|
|
30
30
|
| `room` | A short name for this question, different for each poll in the deck. Letters, digits, hyphens and underscores. |
|
|
31
|
-
| `answer` | Optional: the right answer, written exactly like the option. A button with a tick then appears under the bars; tapping it outlines the right answer, its bar and its number in green, and
|
|
31
|
+
| `answer` | Optional: the right answer, written exactly like the option (several separated by `\|`). A button with a tick then appears under the bars; tapping it outlines the right answer, its bar and its number in green, and each phone says whether its vote was right. Tapping again hides it. |
|
|
32
32
|
| `options` | The answers, separated by `\|` |
|
|
33
33
|
| `question` | Optional question text. Without it, phones show the slide's heading. |
|
|
34
|
+
| `multiple` | Each phone may pick several options. The slide and the phones say “Several answers possible”. Without it, one option per phone. |
|
|
35
|
+
| `buttons` | `letters` puts A, B, C … before the options on the slide, and the phones show only those letters; `numbers` does the same with 1, 2, 3 … For options that are pictures or long formulas, better read on the big screen. |
|
|
36
|
+
| `results` | `hidden` starts with the bars hidden, e.g. to let people vote before they see what others chose. |
|
|
34
37
|
| `qr` | `false` leaves out the QR code, when an earlier slide showed it with `<qrcode join />`. |
|
|
35
38
|
|
|
39
|
+
The question and the options may contain formatting and formulas, written as on a slide, so a maths lecture can ask:
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
<poll room="derivative" question="What is $\frac{d}{dx}\, x^2$?" options="$2x$|$x^2$|$\frac{x^3}{3}$" answer="$2x$" />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Both the slide and the phones show the formulas. A `|` inside a formula stays in it, so `$|x|$` is one option; to put a `|` in plain text, write `\|`.
|
|
46
|
+
|
|
47
|
+
When the options get long, write them as a list inside the tag instead, one option per line, and tick the right one with `[x]`:
|
|
48
|
+
|
|
49
|
+
```markdown
|
|
50
|
+
<poll room="derivative" question="What is $\frac{d}{dx}\, x^2$?">
|
|
51
|
+
|
|
52
|
+
- [x] $2x$
|
|
53
|
+
- [ ] $x^2$
|
|
54
|
+
- [ ] $\frac{x^3}{3}$
|
|
55
|
+
|
|
56
|
+
</poll>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Nothing needs escaping there, and the right answer is marked where it stands.
|
|
60
|
+
|
|
61
|
+
Options can also be pictures. Show only letters on the phones then:
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
<poll room="graph" question="Which graph shows $f(x) = e^{-x}$?" options="||" answer="" buttons="letters" />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### During the poll
|
|
68
|
+
|
|
69
|
+
Below the bars, the slide counts the answers, “12 of 31 answered” while phones are connected. The presenter has three buttons there (the audience window does not show them):
|
|
70
|
+
|
|
71
|
+
- **The lock** closes the poll. Answers after that do not count, and the phones say the poll is closed until you open it again.
|
|
72
|
+
- **The eye** hides or shows the bars. Hidden, the audience sees no bars; your own screens show them faint.
|
|
73
|
+
- **The tick** (with `answer`) shows the right answer: on the slide, and on each phone, which says whether its answer was right.
|
|
74
|
+
|
|
75
|
+
Together they make a peer-instruction round: start with `results="hidden"`, let people vote, close, show the bars, let them discuss, and show the answer. **Reset** clears the answers and opens the poll again.
|
|
76
|
+
|
|
36
77
|
With `lang: de` in the settings, the slide and the phones show German words, such as “Scannen und abstimmen”. The phones use the presentation's colours and fonts; if you pick another theme or palette in the presenter view, the phones change with it.
|
|
37
78
|
|
|
38
79
|
## Other kinds of questions
|
|
39
80
|
|
|
40
|
-
Besides a poll,
|
|
81
|
+
Besides a poll, four more kinds work the same way: one line on a slide, answered on the phones.
|
|
41
82
|
|
|
42
83
|
```markdown
|
|
43
84
|
<scale room="pace" min="1" max="5" low="Too slow" high="Too fast" />
|
|
85
|
+
<numeric room="limit" question="What is $\lim_{x \to 0} \frac{\sin x}{x}$?" answer="1" />
|
|
44
86
|
<wordcloud room="mood" placeholder="One word" />
|
|
45
87
|
<question room="ask" placeholder="Your question" />
|
|
46
88
|
```
|
|
@@ -48,10 +90,11 @@ Besides a poll, three more kinds work the same way: one line on a slide, answere
|
|
|
48
90
|
| Tag | Phones show | The slide shows |
|
|
49
91
|
|---|---|---|
|
|
50
92
|
| `<scale>` | The numbers from `min` to `max` (1 to 5 unless set, at most 11 steps), with `low` and `high` as labels at the ends | How many chose each number, and the average. Each phone's latest answer counts |
|
|
93
|
+
| `<numeric>` | A field for a number: `0.5`, `0,5`, `1/2` or `1e-3` | The most frequent answers as bars, six unless `limit` says otherwise, and the rest as Other. With `answer`, the tick marks the right ones and counts them, and each phone hears whether it was right; `tolerance` (0 unless set) says how far off still counts. Each phone's latest answer counts |
|
|
51
94
|
| `<wordcloud>` | A short text field | A cloud of every answer, packed around the middle, larger the more often it came in and some upright. Capitals and extra spaces do not matter. People may send several. `height` sets its height (520). Colours come from the palette; `--cloud-1` to `--cloud-6` override them |
|
|
52
95
|
| `<question>` | A text field | The newest answers as cards, eight unless `limit` says otherwise. People may send several |
|
|
53
96
|
|
|
54
|
-
All of them take `room`, `question` and `qr` like a poll, and `placeholder` for the text fields.
|
|
97
|
+
All of them take `room`, `question` and `qr` like a poll, and `placeholder` for the text fields. Their `question`, and the scale's `low` and `high`, may contain formatting and formulas like a poll's options. All of them have the lock that closes them; `<numeric>` also has the eye and the tick, and takes `results="hidden"`.
|
|
55
98
|
|
|
56
99
|
## Show the code once
|
|
57
100
|
|
|
@@ -175,9 +218,11 @@ Two presentations with the same title share a session code. Give one of them `se
|
|
|
175
218
|
|
|
176
219
|
Answers are anonymous. Each phone gets a random number so it can change its vote; nothing else identifies it. The server keeps answers in memory only. It forgets a room after twelve hours without visitors and everything when it restarts. Each phone can send a limited number of answers in a short time.
|
|
177
220
|
|
|
221
|
+
While you present with `mdeck run`, the answers are also kept beside the presentation, in `my-talk.results.json`. It holds each answer with its time, and the phones numbered 1, 2, 3 … per question instead of their random numbers. Once a question was closed, only the answers that counted. **Reset** takes a question's answers out of the file; a new server that starts empty leaves the file as it is. `mdeck results my-talk.md` prints the answers as CSV (question, phone, time, answer), to open in a spreadsheet; `-o answers.csv` saves them to a file. A deck hosted with its own `server` keeps nothing beside it.
|
|
222
|
+
|
|
178
223
|
## In a PDF or a shared file
|
|
179
224
|
|
|
180
|
-
A PDF, a file made with `mdeck send` and a folder hosted without a poll server keep your questions as a record of the talk
|
|
225
|
+
A PDF, a file made with `mdeck send` and a folder hosted without a poll server keep your questions as a record of the talk, without a join code. With answers kept in `my-talk.results.json`, each shows its results as they were at the end of the talk; without, its question and options with a note that it was answered live. `--no-results` leaves the kept answers out. If the deck's `server` still answers when the file is opened, the results it holds appear instead. Printed slides never show the join code or the presenter's buttons.
|
|
181
226
|
|
|
182
227
|
## Make your own activity
|
|
183
228
|
|
|
@@ -97,6 +97,8 @@ The design page shows themes and palettes on a sample deck with every kind of sl
|
|
|
97
97
|
| `mdeck send my-talk.md --no-pdf` | The same, without rendering the PDF |
|
|
98
98
|
| `mdeck pdf my-talk.md -o talk.pdf` | Render the slides to a PDF file (`--no-polls` leaves out the slides with polls) |
|
|
99
99
|
| `--no-drawings` | Leave out the drawings from `my-talk.drawings.json`, for `build`, `send` and `pdf` |
|
|
100
|
+
| `--no-results` | Leave out the answers kept in `my-talk.results.json`, for `build`, `send` and `pdf`; the polls then show their questions only |
|
|
101
|
+
| `mdeck results my-talk.md` | The answers kept during the talk, as CSV; `-o answers.csv` saves them to a file |
|
|
100
102
|
|
|
101
103
|
See [Send, host or print your slides](sharing.html) for help choosing between them. Unknown options, missing option values and extra filenames stop the command before it changes any output. `--output=path` and `-o path` are both accepted. `mdeck preview --no-open --port 4200` previews without opening a browser and chooses a port.
|
|
102
104
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Install, remove or share a theme
|
|
2
2
|
|
|
3
|
-
mdeck comes with
|
|
3
|
+
mdeck comes with six themes, `neue`, `plain`, `academic`, `minimal`, `work` and `glass`, and six palettes, `swiss`, `nordic`, `cobalt`, `greyscale`, `paper` and `lagoon`. They work right away, also without an internet connection. More themes and palettes are in the **theme repository**; try every one on a sample deck, light and dark, at <https://gh.tschieber.de/mdeck-themes/>.
|
|
4
4
|
|
|
5
5
|
## Find and install one
|
|
6
6
|
|
|
@@ -15,16 +15,16 @@ mdeck palettes search
|
|
|
15
15
|
Install a theme:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
mdeck themes install
|
|
18
|
+
mdeck themes install terminal
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
A theme comes with its default palette, so this installs the palette `
|
|
21
|
+
A theme comes with its default palette, so this installs the palette `phosphor` too. A palette installs on its own:
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
24
|
mdeck palettes install solarized
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
They are installed for every deck on this computer, in a folder in your home folder (`~/.mdeck/extensions`). Then choose one at the top of a slide file (`theme:
|
|
27
|
+
They are installed for every deck on this computer, in a folder in your home folder (`~/.mdeck/extensions`). Then choose one at the top of a slide file (`theme: terminal`, `palette: solarized`) or in the editor's **Deck** tab.
|
|
28
28
|
|
|
29
29
|
The design page has the same: open it with `mdeck design`, choose **Show what you can install**, and press **Install** beside a theme or palette.
|
|
30
30
|
|
|
@@ -44,15 +44,15 @@ The slide folder then carries the theme and its palette, so it works on a comput
|
|
|
44
44
|
mdeck themes list # what comes with mdeck, and what is installed
|
|
45
45
|
mdeck themes update # newer versions of the installed themes
|
|
46
46
|
mdeck palettes update
|
|
47
|
-
mdeck themes remove
|
|
48
|
-
mdeck palettes remove
|
|
47
|
+
mdeck themes remove terminal
|
|
48
|
+
mdeck palettes remove phosphor
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Add a slide file, or `--local`, to work beside that deck. If you changed an installed file by hand, `update` and `remove` stop and tell you which one, so your change is not lost; add `--force` to go ahead anyway.
|
|
52
52
|
|
|
53
53
|
A palette that an installed theme uses by default stays until that theme is removed. A palette made for one theme only (like FHNW's `brand`) comes and goes with that theme.
|
|
54
54
|
|
|
55
|
-
The themes and palettes that come with mdeck can be removed too: `mdeck themes remove
|
|
55
|
+
The themes and palettes that come with mdeck can be removed too: `mdeck themes remove glass` hides it from every deck and from the lists, and `mdeck themes install glass` brings it back, without the internet. The last theme always stays. A deck without a `theme:` setting uses `neue`; if you removed it, `mdeck check` tells you.
|
|
56
56
|
|
|
57
57
|
## What may be in one
|
|
58
58
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
theme:
|
|
2
|
+
theme: glass
|
|
3
3
|
meta:
|
|
4
4
|
title: "A tour of mdeck"
|
|
5
5
|
author: "Tilman Schieber"
|
|
@@ -380,7 +380,7 @@ layout: split
|
|
|
380
380
|
:::slot left
|
|
381
381
|
```markdown
|
|
382
382
|
---
|
|
383
|
-
theme:
|
|
383
|
+
theme: work
|
|
384
384
|
palette: lagoon
|
|
385
385
|
---
|
|
386
386
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdeck",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Write slides as a plain text file, present them in the browser, share them as one HTML file or PDF.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,5 +75,8 @@
|
|
|
75
75
|
"vite": "^8.3.1",
|
|
76
76
|
"vite-plugin-singlefile": "^2.3.3",
|
|
77
77
|
"ws": "^8.22.0"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"parse5": "^8.0.1"
|
|
78
81
|
}
|
|
79
82
|
}
|
|
@@ -34,7 +34,7 @@ If `mdeck` is not installed, rely on the quick reference below.
|
|
|
34
34
|
```markdown
|
|
35
35
|
---
|
|
36
36
|
theme: neue # theme id from `mdeck list themes`
|
|
37
|
-
palette: nordic # optional: swiss, nordic,
|
|
37
|
+
palette: nordic # optional: swiss, nordic, cobalt, greyscale, lagoon, paper, or one installed from the theme repository (`mdeck themes search`)
|
|
38
38
|
appearance: light # optional: light or dark
|
|
39
39
|
meta:
|
|
40
40
|
title: "Talk title"
|
|
@@ -94,7 +94,7 @@ Right column
|
|
|
94
94
|
- Named areas use `:::slot name … :::`; the layout's regions and settings come from `mdeck list layouts --json`.
|
|
95
95
|
- `:::notes … :::` holds speaker notes. `:::steps … :::` reveals list items one at a time. `:::note`, `:::tip`, `:::important`, `:::warning` and `:::caution` are callouts (`mdeck check` warns about other names); for lectures, `:::definition`, `:::theorem`, `:::lemma`, `:::proof`, `:::example` and `:::remark`, numbered in the title (`::: theorem Theorem 2.4 (Lagrange)`). `:::columns … +++ … :::` makes columns inside a slide.
|
|
96
96
|
- Code fences get syntax highlighting; `$…$` and `$$…$$` render math.
|
|
97
|
-
- `<poll room="lunch" options="Mensa|Thai|Pizza" />` lets the audience vote on their phones through a QR code on the slide; each poll needs its own `room`. `<scale room="pace" min="1" max="5" low="Too slow" high="Too fast" />`, `<wordcloud room="mood" />` and `<question room="ask" />` ask for a rating, short words or open answers the same way. With several of them, put `<qrcode join />` (the deck's join code) on an early slide to show the QR code once, and add `qr="false"` to the questions. It works when the deck is presented with `mdeck run <deck>.md --network` or with a server set as `server:` in the deck settings.
|
|
97
|
+
- `<poll room="lunch" options="Mensa|Thai|Pizza" />` lets the audience vote on their phones through a QR code on the slide; each poll needs its own `room`. `question` and `options` may use Markdown and `$…$` maths (`options="$2x$|$x^2$"`); `answer` repeats the right option's text. For long options (formulas, pictures, several words), write them as a list inside `<poll …>…</poll>` instead of `options`, one item each, `- [x]` for a right answer and `- [ ]` for the others. `multiple` allows several picks; `buttons="letters"` shows only A, B, C on the phones, for options that are pictures or long formulas. `<numeric room="lim" question="…" answer="1/2" tolerance="0.01" />` asks for a number. `<scale room="pace" min="1" max="5" low="Too slow" high="Too fast" />`, `<wordcloud room="mood" />` and `<question room="ask" />` ask for a rating, short words or open answers the same way. With several of them, put `<qrcode join />` (the deck's join code) on an early slide to show the QR code once, and add `qr="false"` to the questions. It works when the deck is presented with `mdeck run <deck>.md --network` or with a server set as `server:` in the deck settings.
|
|
98
98
|
|
|
99
99
|
## 3. Write the deck
|
|
100
100
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFileSync, statSync } from 'fs'
|
|
2
|
-
import { resolve, dirname, extname } from 'path'
|
|
1
|
+
import { readFileSync, statSync, existsSync } from 'fs'
|
|
2
|
+
import { resolve, dirname, extname, basename } from 'path'
|
|
3
3
|
import yaml from 'js-yaml'
|
|
4
4
|
import { parseSlides } from '../core/parseSlides.js'
|
|
5
5
|
import { scanCitations, citedKeys } from '../core/citations.js'
|
|
6
|
+
import { sampleDeck, SAMPLE_REFERENCES, SAMPLE_REFERENCES_FILE } from '../editor/sampleDeck.js'
|
|
6
7
|
|
|
7
8
|
// The deck's references, formatted here in Node with citeproc so that none of
|
|
8
9
|
// it travels in the deck: the page receives finished HTML for every citation
|
|
@@ -26,8 +27,14 @@ async function readItems(file) {
|
|
|
26
27
|
const { mtimeMs } = statSync(file)
|
|
27
28
|
const cached = parsed.get(file)
|
|
28
29
|
if (cached?.mtimeMs === mtimeMs) return cached.items
|
|
29
|
-
const
|
|
30
|
-
|
|
30
|
+
const items = await parseReferences(readFileSync(file, 'utf-8'), extname(file))
|
|
31
|
+
parsed.set(file, { mtimeMs, items })
|
|
32
|
+
return items
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Reference file text as CSL-JSON items; `ext` names the format.
|
|
36
|
+
export async function parseReferences(text, ext) {
|
|
37
|
+
ext = ext.toLowerCase()
|
|
31
38
|
let items
|
|
32
39
|
if (ext === '.json') items = JSON.parse(text)
|
|
33
40
|
else if (ext === '.yml' || ext === '.yaml') {
|
|
@@ -40,18 +47,20 @@ async function readItems(file) {
|
|
|
40
47
|
items = new Cite(text, { forceType: '@biblatex/text' }).data
|
|
41
48
|
} else throw new Error(`unknown reference format "${ext}" (use .bib, .json or .yml)`)
|
|
42
49
|
if (!Array.isArray(items)) throw new Error('expected a list of references')
|
|
43
|
-
|
|
44
|
-
parsed.set(file, { mtimeMs, items })
|
|
45
|
-
return items
|
|
50
|
+
return items.filter(item => item && item.id != null).map(item => ({ ...item, id: String(item.id) }))
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
// `sample`: the sample deck's references stand in for a missing file of
|
|
54
|
+
// theirs (the dev and edit servers show the sample deck without a folder).
|
|
55
|
+
export async function loadBibliography(files, { sample = false } = {}) {
|
|
49
56
|
const items = []
|
|
50
57
|
const seen = new Set()
|
|
51
58
|
const diagnostics = []
|
|
52
59
|
for (const file of files) {
|
|
53
60
|
let found
|
|
54
|
-
try {
|
|
61
|
+
try {
|
|
62
|
+
found = sample && basename(file) === SAMPLE_REFERENCES_FILE && !existsSync(file) ? await parseReferences(SAMPLE_REFERENCES, '.bib') : await readItems(file)
|
|
63
|
+
} catch (error) {
|
|
55
64
|
diagnostics.push({ severity: 'warning', code: 'bibliography-file', message: error.code === 'ENOENT' ? `Bibliography ${file} not found` : `Bibliography ${file}: ${error.message}` })
|
|
56
65
|
continue
|
|
57
66
|
}
|
|
@@ -275,15 +284,24 @@ export async function formatBibliography({ items, texts, csl, lang, nocite = [],
|
|
|
275
284
|
|
|
276
285
|
// Everything the deck's `bibliography`, `csl`, `nocite` and `lang` ask for,
|
|
277
286
|
// or null when the deck has no bibliography.
|
|
278
|
-
export async function resolveBibliography(deckPath, source) {
|
|
287
|
+
export async function resolveBibliography(deckPath, source, { sample = false } = {}) {
|
|
279
288
|
let deckConfig
|
|
280
289
|
try { deckConfig = parseSlides(source).deckConfig ?? {} } catch { return null }
|
|
281
290
|
const files = bibliographyFiles(deckPath, deckConfig)
|
|
282
291
|
if (!files.length) return null
|
|
283
|
-
const loaded = await loadBibliography(files)
|
|
292
|
+
const loaded = await loadBibliography(files, { sample })
|
|
284
293
|
const formatted = await formatBibliography({
|
|
285
294
|
items: loaded.items, texts: deckTexts(source), csl: deckConfig.csl, lang: deckConfig.lang,
|
|
286
295
|
nocite: deckConfig.nocite ?? [], deckDir: dirname(resolve(deckPath)),
|
|
287
296
|
})
|
|
288
297
|
return { ...formatted, diagnostics: [...loaded.diagnostics, ...formatted.diagnostics], files, style: styleFile(deckPath, deckConfig) }
|
|
289
298
|
}
|
|
299
|
+
|
|
300
|
+
// The sample deck's citations, formatted once: the design page previews it
|
|
301
|
+
// in the editor of any deck, whose own references are another matter.
|
|
302
|
+
let sampleFormatted = null
|
|
303
|
+
export function sampleBibliography() {
|
|
304
|
+
sampleFormatted ??= parseReferences(SAMPLE_REFERENCES, '.bib')
|
|
305
|
+
.then(items => formatBibliography({ items, texts: deckTexts(sampleDeck()) }))
|
|
306
|
+
return sampleFormatted
|
|
307
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Keeping the answers while `mdeck run` runs: the presenter's screens send
|
|
2
|
+
// each activity's answers as they come in (src/live/client.js), and they go
|
|
3
|
+
// into <deck>.results.json beside the deck (src/core/results.js).
|
|
4
|
+
//
|
|
5
|
+
// POST /__mdeck/results { room, closed, answers } → { ok }
|
|
6
|
+
//
|
|
7
|
+
// Answers of none take the room out of the file, as Reset does. Writes wait
|
|
8
|
+
// a moment, so a burst of votes is one write, and do not reload the open
|
|
9
|
+
// windows (ownWrites.js).
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from 'node:fs'
|
|
11
|
+
import { resolve } from 'node:path'
|
|
12
|
+
import { resultsFileFor, normalizeResults, withRoom, serializeResults } from '../core/results.js'
|
|
13
|
+
import { isAllowedRequest, send, readJson } from './editorPlugin.js'
|
|
14
|
+
import { markOwnWrite } from './ownWrites.js'
|
|
15
|
+
|
|
16
|
+
export function createResultsFile(deckPath, { debounceMs = 500 } = {}) {
|
|
17
|
+
const path = resultsFileFor(resolve(deckPath))
|
|
18
|
+
let results = null, timer = null
|
|
19
|
+
|
|
20
|
+
const current = () => {
|
|
21
|
+
if (results) return results
|
|
22
|
+
try { results = existsSync(path) ? normalizeResults(JSON.parse(readFileSync(path, 'utf8'))) : normalizeResults({}) }
|
|
23
|
+
catch { results = normalizeResults({}) }
|
|
24
|
+
return results
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function flush() {
|
|
28
|
+
clearTimeout(timer)
|
|
29
|
+
timer = null
|
|
30
|
+
if (!results) return
|
|
31
|
+
// A deck that never kept answers gets no empty file.
|
|
32
|
+
if (!Object.keys(results.rooms).length && !existsSync(path)) return
|
|
33
|
+
const text = serializeResults(results)
|
|
34
|
+
markOwnWrite(path, text)
|
|
35
|
+
const temp = `${path}.${process.pid}.tmp`
|
|
36
|
+
writeFileSync(temp, text, 'utf8')
|
|
37
|
+
renameSync(temp, path)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
path,
|
|
42
|
+
read: current,
|
|
43
|
+
save(room, entry) {
|
|
44
|
+
results = withRoom(current(), room, entry)
|
|
45
|
+
clearTimeout(timer)
|
|
46
|
+
timer = setTimeout(flush, debounceMs)
|
|
47
|
+
},
|
|
48
|
+
flush,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resultsMiddleware(file, { authorize = isAllowedRequest } = {}) {
|
|
53
|
+
return async (request, response, next) => {
|
|
54
|
+
const { pathname } = new URL(request.url, 'http://localhost')
|
|
55
|
+
if (pathname !== '/') return next()
|
|
56
|
+
if (request.method !== 'POST') { response.setHeader('Allow', 'POST'); return send(response, 405, { error: 'Method not allowed' }) }
|
|
57
|
+
if (!authorize(request)) return send(response, 403, { error: 'Only the presenter keeps the results' })
|
|
58
|
+
try {
|
|
59
|
+
const { room, closed, answers } = await readJson(request)
|
|
60
|
+
file.save(String(room ?? ''), { closed, answers })
|
|
61
|
+
return send(response, 200, { ok: true })
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return send(response, error.status ?? 500, { error: error.message })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resultsPlugin(slidesPath, { authorize } = {}) {
|
|
69
|
+
return {
|
|
70
|
+
name: 'vite-plugin-mdeck-results',
|
|
71
|
+
configureServer(server) {
|
|
72
|
+
const file = createResultsFile(slidesPath)
|
|
73
|
+
server.middlewares.use('/__mdeck/results', resultsMiddleware(file, { authorize }))
|
|
74
|
+
server.httpServer?.once('close', () => file.flush())
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|