mdeck 3.12.0 → 3.12.1
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 +7 -0
- package/docs/site/content/commands.md +1 -0
- package/examples/showcase/slides.de.results.json +6 -36
- package/package.json +1 -1
- package/src/cli/index.js +6 -0
- package/src/live/answer/answer.css +3 -1
- package/src/live/rooms.js +11 -1
- package/src/live/server.js +15 -1
- package/src/runtime/PresenterChrome.jsx +5 -5
- package/src/runtime/main.jsx +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.12.1 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
- Fixed: on an iPhone the reaction buttons sat below the visible part of the phones' page, behind Safari's bar. The page now fits the visible height. Update your poll server for this one.
|
|
6
|
+
- `mdeck --version` (or `-v`) shows which mdeck this is.
|
|
7
|
+
- Fixed: with polls on your own server, the presenter view counted no phones: they join that server, while the presenter view's room stays on your computer. `mdeck run` now asks the server how many phones joined.
|
|
8
|
+
- Shorter status lines in the presenter view: "Poll server needs an update to 3.12.1.", "Phones follow another screen. Change slide to take them back."
|
|
9
|
+
|
|
3
10
|
## 3.12.0 — 2026-10-12
|
|
4
11
|
|
|
5
12
|
- In a word cloud each phone counts with one word: a new one replaces its earlier word, and the phone says which it sent. Before, one phone could fill the cloud.
|
|
@@ -27,6 +27,7 @@ For how the commands, the views and the servers fit together, see [Views, comman
|
|
|
27
27
|
| `mdeck list`, `mdeck starter` | Look up layouts, themes and palettes |
|
|
28
28
|
| `mdeck docs` | Open this documentation |
|
|
29
29
|
| `mdeck --help` | Show a short list of commands |
|
|
30
|
+
| `mdeck --version` | Show which version of mdeck you have |
|
|
30
31
|
|
|
31
32
|
## The launch page
|
|
32
33
|
|
|
@@ -2,54 +2,24 @@
|
|
|
2
2
|
"version": 1,
|
|
3
3
|
"rooms": {
|
|
4
4
|
"tour": {
|
|
5
|
-
"savedAt": "2026-10-12T21:
|
|
5
|
+
"savedAt": "2026-10-12T21:42:08.047Z",
|
|
6
6
|
"closed": false,
|
|
7
7
|
"answers": [
|
|
8
8
|
{
|
|
9
9
|
"phone": 1,
|
|
10
|
-
"at": "2026-10-12T21:
|
|
11
|
-
"value": "
|
|
10
|
+
"at": "2026-10-12T21:37:57.968Z",
|
|
11
|
+
"value": "Zeichnen"
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
14
|
},
|
|
15
15
|
"mood": {
|
|
16
|
-
"savedAt": "2026-10-12T21:
|
|
16
|
+
"savedAt": "2026-10-12T21:42:08.391Z",
|
|
17
17
|
"closed": false,
|
|
18
18
|
"answers": [
|
|
19
19
|
{
|
|
20
20
|
"phone": 1,
|
|
21
|
-
"at": "2026-10-12T21:
|
|
22
|
-
"value": "
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"phone": 1,
|
|
26
|
-
"at": "2026-10-12T21:17:38.153Z",
|
|
27
|
-
"value": "Banjf"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"phone": 1,
|
|
31
|
-
"at": "2026-10-12T21:17:43.867Z",
|
|
32
|
-
"value": "Hallo Herr Doktor Müller"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"phone": 1,
|
|
36
|
-
"at": "2026-10-12T21:17:49.791Z",
|
|
37
|
-
"value": "Hallo Herr"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"phone": 1,
|
|
41
|
-
"at": "2026-10-12T21:17:57.260Z",
|
|
42
|
-
"value": "Hallo Herr Doktor Müller"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"phone": 1,
|
|
46
|
-
"at": "2026-10-12T21:18:03.177Z",
|
|
47
|
-
"value": "Ja 👍 und die sind"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"phone": 1,
|
|
51
|
-
"at": "2026-10-12T21:18:15.378Z",
|
|
52
|
-
"value": "Hallo Herr Müller"
|
|
21
|
+
"at": "2026-10-12T21:38:02.812Z",
|
|
22
|
+
"value": "Gggghh"
|
|
53
23
|
}
|
|
54
24
|
]
|
|
55
25
|
}
|
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -391,6 +391,7 @@ const HELP = `
|
|
|
391
391
|
through it (MDECK_SERVER_KEY); put a web server in front
|
|
392
392
|
|
|
393
393
|
Commands that take ${c.bold}slides.md${c.reset} use the one in the current folder when you leave it out.
|
|
394
|
+
${c.green}mdeck --version${c.reset} shows which mdeck this is.
|
|
394
395
|
|
|
395
396
|
${c.dim}Install the${c.reset} ${c.bold}mdeck${c.reset} ${c.dim}command globally:${c.reset}
|
|
396
397
|
npm link
|
|
@@ -402,6 +403,11 @@ const HELP = `
|
|
|
402
403
|
// ── Argument parsing ──────────────────────────────────────────────────────────
|
|
403
404
|
const [command, ...argv] = process.argv.slice(2)
|
|
404
405
|
|
|
406
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
407
|
+
console.log(JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version)
|
|
408
|
+
process.exit(0)
|
|
409
|
+
}
|
|
410
|
+
|
|
405
411
|
if (!command || command === '--help' || command === '-h') {
|
|
406
412
|
console.log(HELP)
|
|
407
413
|
process.exit(0)
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
}
|
|
7
7
|
* { box-sizing: border-box; }
|
|
8
8
|
html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--ink); }
|
|
9
|
-
|
|
9
|
+
/* The visible height (dvh): on a phone, 100vh is taller than what shows beside
|
|
10
|
+
the browser's bars, and the reactions at the bottom slid out of sight. */
|
|
11
|
+
body { min-height: 100vh; min-height: 100dvh; padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box; display: flex; flex-direction: column; font: 18px/1.45 var(--font-body); -webkit-font-smoothing: antialiased; }
|
|
10
12
|
#answer { flex: 1; display: flex; flex-direction: column; }
|
|
11
13
|
.answer { flex: 1; width: 100%; max-width: 520px; margin: 0 auto; padding: 32px 20px; }
|
|
12
14
|
.answer h1 {
|
package/src/live/rooms.js
CHANGED
|
@@ -43,13 +43,23 @@ export function createRooms({ maxMessages = 5000, maxInk = 5000, maxRooms = 500,
|
|
|
43
43
|
const broadcast = (room, event) => { for (const listener of room.listeners) listener(event) }
|
|
44
44
|
// A session is a room and the rooms named after it (`<code>.stage`, …).
|
|
45
45
|
const sessionRooms = id => { const session = id.split('.')[0]; return [...rooms].filter(([name]) => name === session || name.startsWith(`${session}.`)).map(([, room]) => room) }
|
|
46
|
+
// Phones connected elsewhere, by session: mdeck run keeps the stage room
|
|
47
|
+
// here but the phones join a standalone server (src/live/server.js).
|
|
48
|
+
const awayPhones = new Map()
|
|
46
49
|
const presence = id => {
|
|
47
50
|
const present = sessionRooms(id).flatMap(room => [...room.present.values()])
|
|
48
51
|
const views = present.filter(entry => entry.view !== 'phone').sort((a, b) => VIEWS.indexOf(a.view) - VIEWS.indexOf(b.view))
|
|
49
|
-
return { views, phones: present.length - views.length }
|
|
52
|
+
return { views, phones: present.length - views.length + (awayPhones.get(id.split('.')[0]) ?? 0) }
|
|
50
53
|
}
|
|
51
54
|
const announcePresence = id => { const event = { type: 'presence', presence: presence(id) }; for (const room of sessionRooms(id)) broadcast(room, event) }
|
|
52
55
|
return {
|
|
56
|
+
/** Phones of session `id` that joined another server; announced when the number changes. */
|
|
57
|
+
setAwayPhones(id, n) {
|
|
58
|
+
const session = id.split('.')[0]
|
|
59
|
+
if ((awayPhones.get(session) ?? 0) === n) return
|
|
60
|
+
awayPhones.set(session, n)
|
|
61
|
+
announcePresence(session)
|
|
62
|
+
},
|
|
53
63
|
post(id, { from = null, data }) {
|
|
54
64
|
const room = get(id)
|
|
55
65
|
// Reactions (src/core/reactions.js) are passed on and forgotten.
|
package/src/live/server.js
CHANGED
|
@@ -14,11 +14,25 @@ export function livePlugin({ pairing = null, upstream = null, key = null, sessio
|
|
|
14
14
|
return {
|
|
15
15
|
name: 'vite-plugin-mdeck-live',
|
|
16
16
|
configureServer(server) {
|
|
17
|
+
const rooms = createRooms()
|
|
17
18
|
const handler = liveHandler({
|
|
18
|
-
rooms
|
|
19
|
+
rooms,
|
|
19
20
|
canReset: (request, room) => isAllowedRequest(request) || (!!pairing?.allows(request) && (room == null || room === session() || room.startsWith(`${session()}.`))),
|
|
20
21
|
info: () => ({ network: server.resolvedUrls?.network?.[0] ?? null }),
|
|
21
22
|
})
|
|
23
|
+
// With polls on a standalone server, the phones join there: its count
|
|
24
|
+
// joins the local stage room's, so the presenter sees them.
|
|
25
|
+
const awayTimer = setInterval(async () => {
|
|
26
|
+
const address = typeof upstream === 'function' ? upstream() : upstream
|
|
27
|
+
const code = session()
|
|
28
|
+
if (!address || !code) return
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(`${address.replace(/\/+$/, '')}/rooms/${encodeURIComponent(code)}.stage/presence`, { signal: AbortSignal.timeout(3000) })
|
|
31
|
+
if (response.ok) rooms.setAwayPhones(code, Number((await response.json()).phones) || 0)
|
|
32
|
+
} catch {}
|
|
33
|
+
}, 4000)
|
|
34
|
+
awayTimer.unref?.()
|
|
35
|
+
server.httpServer?.once('close', () => clearInterval(awayTimer))
|
|
22
36
|
server.middlewares.use('/__mdeck/live', (request, response, next) => {
|
|
23
37
|
const address = typeof upstream === 'function' ? upstream() : upstream
|
|
24
38
|
// The stage room (position and live ink between the presenter's
|
|
@@ -99,9 +99,9 @@ const PAIR = {
|
|
|
99
99
|
function Polls({ live, recheck }) {
|
|
100
100
|
const [copied, setCopied] = useState(false)
|
|
101
101
|
const copy = () => navigator.clipboard?.writeText(live.joinUrl).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500) }, () => {})
|
|
102
|
-
const problem = live.server && !live.reachable ?
|
|
103
|
-
: !live.joinUrl ? <>Phones
|
|
104
|
-
: live.server && live.key && !live.keyAccepted ? '
|
|
102
|
+
const problem = live.server && !live.reachable ? 'Poll server not answering.'
|
|
103
|
+
: !live.joinUrl ? <>Phones can’t reach this computer: use <code>--network</code>.</>
|
|
104
|
+
: live.server && live.key && !live.keyAccepted ? 'Poll server refuses the key.'
|
|
105
105
|
: null
|
|
106
106
|
// A server on an older mdeck answers, but lacks what is new (reactions came with 3.12).
|
|
107
107
|
const outdated = !problem && live.server && live.serverOutdated
|
|
@@ -111,8 +111,8 @@ function Polls({ live, recheck }) {
|
|
|
111
111
|
<a href={live.joinUrl} target="_blank" rel="noopener" title="The join link for phones" style={{ color: '#ccc', textDecoration: 'none', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{live.joinUrl.replace(/^https?:\/\//, '')}</a>
|
|
112
112
|
<ToolButton class="presenter-copy-join" icon={copied ? 'check' : 'copy'} label={copied ? 'Copied' : 'Copy the join link'} size={15} style={{ minWidth: '30px', height: '30px', padding: 0, flexShrink: 0 }} onClick={copy} />
|
|
113
113
|
</span>}
|
|
114
|
-
{outdated && <span class="presenter-server-outdated" style={{ color: '#f5a524' }}>
|
|
115
|
-
|
|
114
|
+
{outdated && <span class="presenter-server-outdated" style={{ color: '#f5a524' }} title={`Server ${live.serverVersion ?? 'older'}, here ${live.version}: npm install -g mdeck there, then restart mdeck server`}>
|
|
115
|
+
Poll server needs an update to {live.version}.
|
|
116
116
|
</span>}
|
|
117
117
|
{problem && <span style={{ display: 'flex', gap: '8px', alignItems: 'center', color: '#f28b82' }}>
|
|
118
118
|
<span style={{ flex: 1 }}>{problem}</span>
|
package/src/runtime/main.jsx
CHANGED
|
@@ -148,10 +148,10 @@ function announceSlide(deck, index, { initial = false } = {}) {
|
|
|
148
148
|
// Whether this presenter screen moves the phones along, shown only for decks
|
|
149
149
|
// with activities: a failure used to be silent.
|
|
150
150
|
const STEERING = {
|
|
151
|
-
'no-code': 'Phones
|
|
152
|
-
'wrong-code':
|
|
153
|
-
unreachable: 'Phones
|
|
154
|
-
'other-screen': 'Phones follow another screen
|
|
151
|
+
'no-code': 'Phones don’t follow: no server key here.',
|
|
152
|
+
'wrong-code': 'Phones don’t follow: key refused.',
|
|
153
|
+
unreachable: 'Phones don’t follow: server not answering.',
|
|
154
|
+
'other-screen': 'Phones follow another screen. Change slide to take them back.',
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
function injectSpeakerNotes(slides) {
|