mdeck 2.3.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 +259 -0
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/assets/base.css +196 -0
- package/assets/extensions/layouts/chapter/extension.toml +26 -0
- package/assets/extensions/layouts/chapter/layout.jsx +21 -0
- package/assets/extensions/layouts/chapter/starter.md +8 -0
- package/assets/extensions/layouts/focus/extension.toml +8 -0
- package/assets/extensions/layouts/focus/layout.jsx +10 -0
- package/assets/extensions/layouts/focus/starter.md +4 -0
- package/assets/extensions/layouts/full-bleed-image/extension.toml +31 -0
- package/assets/extensions/layouts/full-bleed-image/layout.jsx +19 -0
- package/assets/extensions/layouts/full-bleed-image/starter.md +6 -0
- package/assets/extensions/layouts/generic/extension.toml +8 -0
- package/assets/extensions/layouts/generic/layout.jsx +8 -0
- package/assets/extensions/layouts/generic/starter.md +4 -0
- package/assets/extensions/layouts/image-text/extension.toml +25 -0
- package/assets/extensions/layouts/image-text/layout.jsx +17 -0
- package/assets/extensions/layouts/image-text/starter.md +6 -0
- package/assets/extensions/layouts/split/extension.toml +25 -0
- package/assets/extensions/layouts/split/layout.jsx +19 -0
- package/assets/extensions/layouts/split/starter.md +14 -0
- package/assets/extensions/layouts/title/extension.toml +26 -0
- package/assets/extensions/layouts/title/layout.jsx +14 -0
- package/assets/extensions/layouts/title/starter.md +5 -0
- package/assets/extensions/palettes/brand/extension.toml +29 -0
- package/assets/extensions/palettes/cobalt/extension.toml +27 -0
- package/assets/extensions/palettes/ember/extension.toml +27 -0
- package/assets/extensions/palettes/forest/extension.toml +27 -0
- package/assets/extensions/palettes/graphite/extension.toml +27 -0
- package/assets/extensions/palettes/lagoon/extension.toml +27 -0
- package/assets/extensions/palettes/neon/extension.toml +27 -0
- package/assets/extensions/palettes/nordic/extension.toml +27 -0
- package/assets/extensions/palettes/pastel/extension.toml +30 -0
- package/assets/extensions/palettes/phosphor/extension.toml +27 -0
- package/assets/extensions/palettes/swiss/extension.toml +27 -0
- package/assets/extensions/palettes/terra/extension.toml +27 -0
- package/assets/extensions/themes/academic/extension.toml +53 -0
- package/assets/extensions/themes/academic/styles.css +631 -0
- package/assets/extensions/themes/aurora/extension.toml +37 -0
- package/assets/extensions/themes/aurora/styles.css +644 -0
- package/assets/extensions/themes/duet/extension.toml +46 -0
- package/assets/extensions/themes/duet/styles.css +573 -0
- package/assets/extensions/themes/editorial/extension.toml +46 -0
- package/assets/extensions/themes/editorial/styles.css +574 -0
- package/assets/extensions/themes/fhnw/extension.toml +61 -0
- package/assets/extensions/themes/fhnw/styles.css +604 -0
- package/assets/extensions/themes/neue/extension.toml +46 -0
- package/assets/extensions/themes/neue/styles.css +489 -0
- package/assets/extensions/themes/sketch/extension.toml +48 -0
- package/assets/extensions/themes/sketch/styles.css +633 -0
- package/assets/extensions/themes/terminal/extension.toml +38 -0
- package/assets/extensions/themes/terminal/styles.css +550 -0
- package/assets/logo/avatar.png +0 -0
- package/assets/logo/logo-dark.svg +10 -0
- package/assets/logo/logo-light.svg +10 -0
- package/assets/logo/mark.svg +15 -0
- package/bin/mdeck.js +2 -0
- package/docs/README.md +8 -0
- package/docs/reference/authoring.md +795 -0
- package/docs/reference/claude-skill.md +96 -0
- package/docs/reference/extensions.md +250 -0
- package/docs/reference/layouts.md +153 -0
- package/docs/reference/palettes.md +106 -0
- package/docs/reference/structured-slides.md +120 -0
- package/docs/reference/themes.md +133 -0
- package/docs/site/assets/favicon.svg +15 -0
- package/docs/site/assets/github-logo.svg +1 -0
- package/docs/site/assets/site.css +162 -0
- package/docs/site/assets/site.js +123 -0
- package/docs/site/build.js +148 -0
- package/docs/site/content/appearance.md +203 -0
- package/docs/site/content/audience.md +178 -0
- package/docs/site/content/commands.md +153 -0
- package/docs/site/content/components.md +134 -0
- package/docs/site/content/drawing.md +72 -0
- package/docs/site/content/editing.md +59 -0
- package/docs/site/content/getting-started.md +103 -0
- package/docs/site/content/layouts.md +126 -0
- package/docs/site/content/media.md +107 -0
- package/docs/site/content/more-content.md +130 -0
- package/docs/site/content/presenting.md +93 -0
- package/docs/site/content/reusable-layouts.md +74 -0
- package/docs/site/content/sharing.md +88 -0
- package/docs/site/content/theme-authoring.md +144 -0
- package/docs/site/content/theme-repository.md +76 -0
- package/docs/site/content/troubleshooting.md +92 -0
- package/docs/site/content/views.md +56 -0
- package/docs/site/content/welcome.md +71 -0
- package/docs/site/content/writing.md +121 -0
- package/docs/site/examples/first-talk.md +88 -0
- package/docs/site/examples/notebook.svg +58 -0
- 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/aurora-dark.webp +0 -0
- package/docs/site/images/themes/aurora-light.webp +0 -0
- package/docs/site/images/themes/aurora.webp +0 -0
- package/docs/site/images/themes/duet-dark.webp +0 -0
- package/docs/site/images/themes/duet-light.webp +0 -0
- package/docs/site/images/themes/duet.webp +0 -0
- package/docs/site/images/themes/editorial-dark.webp +0 -0
- package/docs/site/images/themes/editorial-light.webp +0 -0
- package/docs/site/images/themes/editorial.webp +0 -0
- package/docs/site/images/themes/fhnw.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/sketch-dark.webp +0 -0
- package/docs/site/images/themes/sketch-light.webp +0 -0
- package/docs/site/images/themes/sketch.webp +0 -0
- package/docs/site/images/themes/terminal-dark.webp +0 -0
- package/docs/site/images/themes/terminal-light.webp +0 -0
- package/docs/site/images/themes/terminal.webp +0 -0
- package/docs/site/package.json +9 -0
- package/docs/site/pages.js +27 -0
- package/docs/site/server.js +58 -0
- package/examples/custom-layouts/extensions/comparison/extension.toml +35 -0
- package/examples/custom-layouts/extensions/comparison/layout.jsx +15 -0
- package/examples/custom-layouts/extensions/comparison/starter.md +17 -0
- package/examples/custom-layouts/extensions/comparison/styles.css +4 -0
- package/examples/custom-layouts/slides.md +60 -0
- package/examples/poll/slides.md +127 -0
- package/examples/python/.mdeck-backups/slides.drawings-2026-10-06T08-36-47Z.json +19 -0
- package/examples/python/.mdeck-backups/slides.drawings-2026-10-06T11-27-30Z.json +6 -0
- package/examples/python/slides.drawings.json +6 -0
- package/examples/python/slides.md +493 -0
- package/examples/showcase/.mdeck-backups/slides.drawings-2026-10-06T08-08-30Z.json +10 -0
- package/examples/showcase/.mdeck-backups/slides.drawings-2026-10-06T11-41-35Z.json +13 -0
- package/examples/showcase/img/image.jpg +0 -0
- package/examples/showcase/img/mdeck.svg +66 -0
- package/examples/showcase/slides.drawings.json +11 -0
- package/examples/showcase/slides.md +427 -0
- package/package.json +75 -0
- package/skills/write-slides/SKILL.md +144 -0
- package/src/build/check.js +49 -0
- package/src/build/chrome.js +130 -0
- package/src/build/components.js +44 -0
- package/src/build/config.js +61 -0
- package/src/build/drawings.js +14 -0
- package/src/build/editorPlugin.js +272 -0
- package/src/build/editorServer.js +36 -0
- package/src/build/fonts.js +84 -0
- package/src/build/homePlugin.js +208 -0
- package/src/build/inkPlugin.js +192 -0
- package/src/build/ownWrites.js +18 -0
- package/src/build/pairing.js +81 -0
- package/src/build/pdf.js +34 -0
- package/src/build/relayAccess.js +85 -0
- package/src/build/slidesPlugin.js +371 -0
- package/src/build/themeCheck.js +151 -0
- package/src/build/tunnelClient.js +184 -0
- package/src/cli/args.js +76 -0
- package/src/cli/index.js +758 -0
- package/src/cli/migrate.js +123 -0
- package/src/cli/skill.js +40 -0
- package/src/cli/themes.js +140 -0
- package/src/components/CodeBlock.jsx +168 -0
- package/src/components/ConnectedViews.jsx +25 -0
- package/src/components/Icon.jsx +8 -0
- package/src/components/Poll.jsx +67 -0
- package/src/components/QrCode.jsx +29 -0
- package/src/components/QrCodeTag.jsx +27 -0
- package/src/components/Question.jsx +28 -0
- package/src/components/Scale.jsx +47 -0
- package/src/components/VideoPlayer.jsx +108 -0
- package/src/components/WordCloud.jsx +106 -0
- package/src/components/activity.jsx +35 -0
- package/src/components/callout.css +78 -0
- package/src/components/code-block.css +123 -0
- package/src/components/columns.css +11 -0
- package/src/components/poll.css +81 -0
- package/src/components/video-player.css +14 -0
- package/src/core/devPath.js +4 -0
- package/src/core/editDeck.js +230 -0
- package/src/core/icons.js +69 -0
- package/src/core/ink.js +221 -0
- package/src/core/labels.js +144 -0
- package/src/core/outline.js +33 -0
- package/src/core/parseSlides.js +152 -0
- package/src/core/source.js +137 -0
- package/src/core/urls.js +34 -0
- package/src/core/validateDeck.js +139 -0
- package/src/editor/api.js +67 -0
- package/src/editor/components/App.jsx +75 -0
- package/src/editor/components/ConflictBanner.jsx +10 -0
- package/src/editor/components/DeckSettings.jsx +108 -0
- package/src/editor/components/DesignApp.jsx +252 -0
- package/src/editor/components/Diagnostics.jsx +10 -0
- package/src/editor/components/ExtensionEditor.jsx +66 -0
- package/src/editor/components/ExtensionForms.jsx +134 -0
- package/src/editor/components/ExtensionsPanel.jsx +114 -0
- package/src/editor/components/Field.jsx +24 -0
- package/src/editor/components/Inspector.jsx +36 -0
- package/src/editor/components/LayoutPicker.jsx +23 -0
- package/src/editor/components/LayoutSelect.jsx +25 -0
- package/src/editor/components/LookThumbnail.jsx +8 -0
- package/src/editor/components/MetaFields.jsx +24 -0
- package/src/editor/components/Outline.jsx +28 -0
- package/src/editor/components/PalettePicker.jsx +32 -0
- package/src/editor/components/Preview.jsx +63 -0
- package/src/editor/components/PropsForm.jsx +46 -0
- package/src/editor/components/RawSlideEditor.jsx +9 -0
- package/src/editor/components/RegionEditor.jsx +15 -0
- package/src/editor/components/Resizer.jsx +18 -0
- package/src/editor/designLink.js +19 -0
- package/src/editor/editor.css +139 -0
- package/src/editor/extensions.js +86 -0
- package/src/editor/keyboard.js +21 -0
- package/src/editor/model.js +38 -0
- package/src/editor/sampleDeck.js +165 -0
- package/src/editor/saveQueue.js +47 -0
- package/src/editor/schema.js +36 -0
- package/src/editor/state.js +92 -0
- package/src/editor/useDeckDocument.js +67 -0
- package/src/extensions/appearance.js +79 -0
- package/src/extensions/contrast.js +57 -0
- package/src/extensions/discover.js +126 -0
- package/src/extensions/manifest.js +209 -0
- package/src/extensions/packs.js +268 -0
- package/src/extensions/tokens.js +25 -0
- package/src/home/Home.jsx +362 -0
- package/src/home/home.css +148 -0
- package/src/layouts/layoutApi.jsx +205 -0
- package/src/layouts/layoutProps.js +30 -0
- package/src/layouts/renderSlide.jsx +17 -0
- package/src/live/answer/answer.css +34 -0
- package/src/live/answer/answer.html +14 -0
- package/src/live/answer/answer.js +130 -0
- package/src/live/client.js +290 -0
- package/src/live/code.js +19 -0
- package/src/live/follow.js +17 -0
- package/src/live/pairing.js +34 -0
- package/src/live/presence.js +33 -0
- package/src/live/roomTag.js +38 -0
- package/src/live/rooms.js +249 -0
- package/src/live/server.js +82 -0
- package/src/live/tunnel.js +180 -0
- package/src/paths.js +9 -0
- package/src/runtime/ReaderView.jsx +193 -0
- package/src/runtime/SlideErrorBoundary.jsx +28 -0
- package/src/runtime/chrome.jsx +55 -0
- package/src/runtime/deck-stage.js +1533 -0
- package/src/runtime/design.html +12 -0
- package/src/runtime/design.jsx +5 -0
- package/src/runtime/editor.html +12 -0
- package/src/runtime/editor.jsx +5 -0
- package/src/runtime/editorBridge.js +58 -0
- package/src/runtime/fit.js +149 -0
- package/src/runtime/home.html +13 -0
- package/src/runtime/home.jsx +5 -0
- package/src/runtime/index.html +15 -0
- package/src/runtime/ink/InkLayer.jsx +17 -0
- package/src/runtime/ink/Toolbar.jsx +145 -0
- package/src/runtime/ink/attach.js +69 -0
- package/src/runtime/ink/bus.js +126 -0
- package/src/runtime/ink/controller.js +132 -0
- package/src/runtime/ink/room.js +101 -0
- package/src/runtime/ink/select.js +124 -0
- package/src/runtime/ink/server.js +75 -0
- package/src/runtime/ink/store.js +55 -0
- package/src/runtime/ink/toolbar.css +79 -0
- package/src/runtime/main.jsx +863 -0
- package/src/runtime/markedSetup.js +147 -0
- package/src/runtime/reader.css +59 -0
- package/src/runtime/registry.jsx +26 -0
- package/src/runtime/syncOrder.js +32 -0
- package/src/runtime/themeLoader.js +68 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, copyFile, cp } from 'node:fs/promises'
|
|
2
|
+
import { resolve, dirname } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { execFile } from 'node:child_process'
|
|
5
|
+
import { promisify } from 'node:util'
|
|
6
|
+
import { Marked, Renderer } from 'marked'
|
|
7
|
+
import Prism from 'prismjs'
|
|
8
|
+
import 'prismjs/components/prism-markdown.js'
|
|
9
|
+
import 'prismjs/components/prism-yaml.js'
|
|
10
|
+
import 'prismjs/components/prism-jsx.js'
|
|
11
|
+
import 'prismjs/components/prism-bash.js'
|
|
12
|
+
import { pages } from './pages.js'
|
|
13
|
+
import { frameworkRoot as projectRoot } from '../../src/paths.js'
|
|
14
|
+
import { loadRegistry, manifestsOf } from '../../src/extensions/discover.js'
|
|
15
|
+
import { palettesFor } from '../../src/extensions/tokens.js'
|
|
16
|
+
import { createRequire } from 'node:module'
|
|
17
|
+
|
|
18
|
+
const REPO = 'https://github.com/tilman-schieber/mdeck'
|
|
19
|
+
const LINKS = [['Repository', REPO], ['Releases', `${REPO}/releases`], ['Package', 'https://www.npmjs.com/package/mdeck'], ['Issues', `${REPO}/issues`]]
|
|
20
|
+
const version = createRequire(import.meta.url)('../../package.json').version
|
|
21
|
+
const projectLinks = () => LINKS.map(([label, href]) => `<a href="${href}" rel="noopener">${label}</a>`).join('')
|
|
22
|
+
import { parseSlides } from '../../src/core/parseSlides.js'
|
|
23
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
24
|
+
|
|
25
|
+
export const docsRoot = dirname(fileURLToPath(import.meta.url))
|
|
26
|
+
const exec = promisify(execFile)
|
|
27
|
+
export const escape = value => String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
28
|
+
const plain = html => html.replace(/<[^>]*>/g, '').replace(/&(?:amp|lt|gt|quot|#39);/g, value => ({ '&': '&', '<': '<', '>': '>', '"': '"', ''': "'" })[value])
|
|
29
|
+
|
|
30
|
+
function navigation(current) {
|
|
31
|
+
const groups = [...new Set(pages.map(p => p.group))]
|
|
32
|
+
return groups.map(group => {
|
|
33
|
+
const links = pages.filter(p => p.group === group).map(p => `<a href="${p.slug}.html"${p.slug === current.slug ? ' aria-current="page"' : ''}>${escape(p.title)}</a>`).join('')
|
|
34
|
+
if (group === 'Advanced customization') return `<details class="advanced-nav"${current.group === group ? ' open' : ''}><summary>${group}</summary><div>${links}</div></details>`
|
|
35
|
+
return `<div class="nav-group"><p>${group}</p>${links}</div>`
|
|
36
|
+
}).join('')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const exampleFiles = { 'first-talk': resolve(docsRoot, 'examples/first-talk.md'), 'custom-layouts': resolve(projectRoot, 'examples/custom-layouts/slides.md') }
|
|
40
|
+
|
|
41
|
+
// The theme picker lists every registered theme and starts on the example's own design.
|
|
42
|
+
function themeOptions(exampleFile) {
|
|
43
|
+
const current = parseSlides(readFileSync(exampleFile, 'utf8')).deckConfig.theme ?? 'neue'
|
|
44
|
+
const themes = loadRegistry(exampleFile).themes
|
|
45
|
+
return Object.values(themes).map(theme => `<option value="${theme.id}"${theme.id === current ? ' selected' : ''}>${escape(theme.title)}</option>`).join('')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function preview(page) {
|
|
49
|
+
if (!page.preview) return ''
|
|
50
|
+
const custom = page.preview === 'custom-layouts'
|
|
51
|
+
return `<figure class="slide-example" data-preview>
|
|
52
|
+
<figcaption><span>${custom ? 'A reusable comparison design' : 'A presentation you can try'}</span><a href="examples/${page.preview}.html?palette=&appearance=" target="_blank" rel="noopener">Open slides</a></figcaption>
|
|
53
|
+
<iframe title="${custom ? 'Comparison slide example' : 'Example presentation'}" src="examples/${page.preview}.html?embedded=1&palette=&appearance=" loading="lazy"></iframe>
|
|
54
|
+
<div class="preview-controls"><div><button type="button" data-control="prev" aria-label="Previous slide or point">Previous</button><button type="button" data-control="next" aria-label="Next slide or point">Next</button><output aria-live="polite">Slide 1</output></div><label>Look <select aria-label="Example theme">${themeOptions(exampleFiles[page.preview])}</select></label></div>
|
|
55
|
+
</figure>`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// <!-- theme-sheets light --> (or dark) becomes one tab per theme with a
|
|
59
|
+
// contact sheet of its chapter slide in every palette (tools/theme-images.mjs).
|
|
60
|
+
function themeSheets(appearance) {
|
|
61
|
+
const registry = loadRegistry(exampleFiles['first-talk'])
|
|
62
|
+
const palettes = manifestsOf(registry, 'palette')
|
|
63
|
+
const themes = Object.values(manifestsOf(registry, 'theme'))
|
|
64
|
+
.filter(theme => palettesFor(theme, palettes).length > 1 && existsSync(resolve(docsRoot, `images/themes/${theme.id}-${appearance}.webp`)))
|
|
65
|
+
const id = theme => `sheet-${appearance}-${theme.id}`
|
|
66
|
+
const tabs = themes.map((theme, i) => `<button type="button" role="tab" id="${id(theme)}-tab" aria-controls="${id(theme)}" aria-selected="${i === 0}">${escape(theme.title)}</button>`).join('')
|
|
67
|
+
const panels = themes.map(theme => {
|
|
68
|
+
const src = `images/themes/${theme.id}-${appearance}.webp`
|
|
69
|
+
return `<div class="theme-sheet" role="tabpanel" id="${id(theme)}" aria-labelledby="${id(theme)}-tab"><a href="${src}"><img src="${src}" alt="The ${escape(theme.title)} theme's chapter slide in every palette, ${appearance}" loading="lazy"></a></div>`
|
|
70
|
+
}).join('')
|
|
71
|
+
return `<div class="theme-sheets" data-tabs><div class="theme-tabs" role="tablist" aria-label="Theme, ${appearance}">${tabs}</div>${panels}</div>`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function renderPage(page, markdown) {
|
|
75
|
+
const headings = [], used = new Map()
|
|
76
|
+
let title = page.title
|
|
77
|
+
const firstHeading = markdown.match(/^# (.+)\r?$/m)
|
|
78
|
+
if (firstHeading) { title = firstHeading[1]; markdown = markdown.replace(firstHeading[0], '') }
|
|
79
|
+
const renderer = {
|
|
80
|
+
heading({ tokens, depth: level }) {
|
|
81
|
+
const text = this.parser.parseInline(tokens)
|
|
82
|
+
const base = plain(text).toLowerCase().replace(/[^a-z0-9\s-]/g, '').trim().replace(/\s+/g, '-') || 'section'
|
|
83
|
+
const count = used.get(base) ?? 0
|
|
84
|
+
used.set(base, count + 1)
|
|
85
|
+
const id = base + (count ? `-${count + 1}` : '')
|
|
86
|
+
if (level === 2) headings.push({ id, text: plain(text) })
|
|
87
|
+
return `<h${level} id="${id}">${text}<a class="heading-link" href="#${id}" aria-label="Link to ${escape(plain(text))}">#</a></h${level}>\n`
|
|
88
|
+
},
|
|
89
|
+
code({ text: code, lang: info }) {
|
|
90
|
+
const language = (info ?? '').split(/\s/)[0]
|
|
91
|
+
const grammar = Prism.languages[{ sh: 'bash', html: 'markup', text: 'plain' }[language] ?? language]
|
|
92
|
+
const html = grammar ? Prism.highlight(code, grammar, language) : escape(code)
|
|
93
|
+
return `<figure class="code-example"><figcaption><span>${{ sh: 'In your terminal', prompt: 'Ask your assistant' }[language] ?? 'Example'}</span><button type="button" data-copy aria-label="Copy example">Copy</button></figcaption><pre><code>${html}</code></pre></figure>\n`
|
|
94
|
+
},
|
|
95
|
+
table(token) { return `<div class="table-scroll" tabindex="0" role="region" aria-label="Reference table">${Renderer.prototype.table.call(this, token)}</div>` },
|
|
96
|
+
link({ href, title: linkTitle, tokens }) {
|
|
97
|
+
const text = this.parser.parseInline(tokens)
|
|
98
|
+
const aliases = { 'layouts.md': 'custom-layouts.html', 'extensions.md': 'extensions.html', 'themes.md': 'theme-authoring.html', 'palettes.md': 'theme-authoring.html', '../../examples/custom-layouts/slides.md': 'downloads/comparison.md' }
|
|
99
|
+
href = href.replace(/^extensions\.md#/, 'extensions.html#')
|
|
100
|
+
href = aliases[href] ?? href
|
|
101
|
+
return `<a href="${escape(href)}"${linkTitle ? ` title="${escape(linkTitle)}"` : ''}>${text}</a>`
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
const parser = new Marked({ renderer })
|
|
105
|
+
const article = parser.parse(markdown).replace('<!-- preview -->', preview(page))
|
|
106
|
+
.replace(/<!-- theme-sheets (light|dark) -->/g, (_, appearance) => themeSheets(appearance))
|
|
107
|
+
const index = pages.indexOf(page)
|
|
108
|
+
const previous = pages[index - 1], next = pages[index + 1]
|
|
109
|
+
const footerLinks = `${previous ? `<a href="${previous.slug}.html"><small>Previous guide</small>${escape(previous.title)}</a>` : '<span></span>'}${next ? `<a href="${next.slug}.html"><small>Next guide</small>${escape(next.title)}</a>` : ''}`
|
|
110
|
+
const html = `<!doctype html>
|
|
111
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content="${escape(page.description)}"><title>${escape(page.title)} · mdeck guide</title><link rel="icon" href="assets/favicon.svg" type="image/svg+xml"><link rel="stylesheet" href="assets/site.css?v=${escape(version)}"><script src="assets/site.js?v=${escape(version)}" defer></script></head>
|
|
112
|
+
<body${page.slug === 'index' ? ' class="welcome"' : ''}>
|
|
113
|
+
<a class="skip-link" href="#main">Skip to the guide</a>
|
|
114
|
+
<header class="site-header"><a class="brand" href="index.html" aria-label="mdeck documentation home"><img src="assets/favicon.svg" alt="" width="26" height="26"><span>mdeck</span><span class="brand-guide">Guide</span></a>
|
|
115
|
+
<div class="header-tools"><div class="search-box" role="search"><label for="search" class="sr-only">Search the guides</label><input id="search" type="search" placeholder="Search the guides" autocomplete="off" aria-controls="search-results" aria-expanded="false"><kbd aria-hidden="true">/</kbd><div id="search-results" hidden></div><span id="search-status" class="sr-only" aria-live="polite"></span></div>
|
|
116
|
+
<a class="repo-link" href="${REPO}" rel="noopener"><img src="assets/github-logo.svg" alt="" width="20" height="20">GitHub</a></div></header>
|
|
117
|
+
<div class="site-layout"><details class="nav-shell" open><summary>Browse the guides</summary><nav aria-label="Documentation">${navigation(page)}<a class="nav-help" href="commands.html">Open these docs: <code>mdeck docs</code></a><div class="nav-group nav-project"><p>mdeck ${escape(version)} on GitHub</p>${projectLinks()}</div></nav></details>
|
|
118
|
+
<main id="main" tabindex="-1"><article><header class="article-header"><p class="breadcrumb">${escape(page.group)}</p><h1>${escape(title)}</h1>${page.group === 'Advanced customization' ? '<p class="advanced-notice">This section is for people comfortable with code. You can make and present slides without it.</p>' : ''}</header>${article}</article><nav class="page-navigation" aria-label="Previous and next guide">${footerLinks}</nav><footer class="article-footer"><span>mdeck ${escape(version)}</span><span class="footer-links">${projectLinks()}</span></footer></main>
|
|
119
|
+
<aside class="page-outline" aria-label="On this page"><p>On this page</p>${headings.map(h => `<a href="#${h.id}">${escape(h.text)}</a>`).join('')}</aside></div></body></html>`
|
|
120
|
+
return { html, headings, search: { title: page.title, href: `${page.slug}.html`, description: page.description, text: plain(article).replace(/\s+/g, ' ').trim() } }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function buildDocs({ outDir = resolve(docsRoot, 'dist'), examples = true } = {}) {
|
|
124
|
+
await mkdir(resolve(outDir, 'assets'), { recursive: true })
|
|
125
|
+
await mkdir(resolve(outDir, 'downloads'), { recursive: true })
|
|
126
|
+
const search = []
|
|
127
|
+
for (const page of pages) {
|
|
128
|
+
const file = page.source ? resolve(docsRoot, page.source) : resolve(docsRoot, 'content', page.file)
|
|
129
|
+
const { html, search: entry } = renderPage(page, await readFile(file, 'utf8'))
|
|
130
|
+
await writeFile(resolve(outDir, `${page.slug}.html`), html)
|
|
131
|
+
search.push(entry)
|
|
132
|
+
}
|
|
133
|
+
for (const file of ['site.css', 'site.js', 'favicon.svg', 'github-logo.svg']) await copyFile(resolve(docsRoot, 'assets', file), resolve(outDir, 'assets', file))
|
|
134
|
+
await cp(resolve(docsRoot, 'images'), resolve(outDir, 'images'), { recursive: true })
|
|
135
|
+
await writeFile(resolve(outDir, 'search.json'), JSON.stringify(search))
|
|
136
|
+
await copyFile(resolve(projectRoot, 'examples/custom-layouts/slides.md'), resolve(outDir, 'downloads/comparison.md'))
|
|
137
|
+
await copyFile(resolve(docsRoot, 'examples/first-talk.md'), resolve(outDir, 'downloads/first-talk.md'))
|
|
138
|
+
if (examples) {
|
|
139
|
+
for (const [name, file] of Object.entries(exampleFiles)) {
|
|
140
|
+
await exec(process.execPath, [resolve(projectRoot, 'bin/mdeck.js'), 'build', file, '--single-file', '-o', resolve(outDir, 'examples', `${name}.html`)], { cwd: projectRoot, maxBuffer: 4 * 1024 * 1024 })
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return outDir
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
147
|
+
console.log(`Documentation built: ${await buildDocs()}`)
|
|
148
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Change the look
|
|
2
|
+
|
|
3
|
+
A **theme** chooses the fonts and the style of the slides. A **palette** chooses the colours, in a light and a dark version. You can change either without rewriting your slides.
|
|
4
|
+
|
|
5
|
+
Ask your assistant by name or by description:
|
|
6
|
+
|
|
7
|
+
```prompt
|
|
8
|
+
Use the aurora theme, dark. Or suggest a look that suits a talk for
|
|
9
|
+
a design conference.
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The settings it changes are the ones described below. To try looks before deciding, use the presenter view (see “Try a look before saving it”).
|
|
13
|
+
|
|
14
|
+
## Choose a theme
|
|
15
|
+
|
|
16
|
+
Put the theme name under `theme` in the settings at the very top of your file:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
---
|
|
20
|
+
theme: neue
|
|
21
|
+
---
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If you already have a settings block there, change the existing lines rather than adding a second block.
|
|
25
|
+
|
|
26
|
+
| Name to use | What it looks like |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `neue` | Swiss style: large type, clear lines, red on white |
|
|
29
|
+
| `aurora` | Northern lights: curtains of light, night-sky chapters and glass |
|
|
30
|
+
| `duet` | Two voices: two accent colours that take turns, slab headings |
|
|
31
|
+
| `editorial` | A magazine spread: serif headlines, drop caps, pull quotes |
|
|
32
|
+
| `fhnw` | The FHNW visual identity |
|
|
33
|
+
| `terminal` | A terminal session: headings typed at a prompt, a blinking cursor, a status bar |
|
|
34
|
+
| `academic` | A lecture: compact slides, theorem and definition boxes, tables like in a paper |
|
|
35
|
+
| `sketch` | A sketchbook: handwriting, marker scribbles, sticky notes; a chalkboard in the dark |
|
|
36
|
+
|
|
37
|
+
Each theme comes with colours that suit it, so you do not have to choose any.
|
|
38
|
+
|
|
39
|
+
## See the themes
|
|
40
|
+
|
|
41
|
+
Each theme in its own colours, on the title slide of the tour example. Click a picture to see it larger.
|
|
42
|
+
|
|
43
|
+
### Neue
|
|
44
|
+
|
|
45
|
+
[](images/themes/neue.webp)
|
|
46
|
+
|
|
47
|
+
Default colours: `swiss`.
|
|
48
|
+
|
|
49
|
+
### Aurora
|
|
50
|
+
|
|
51
|
+
[](images/themes/aurora.webp)
|
|
52
|
+
|
|
53
|
+
Default colours: `neon`.
|
|
54
|
+
|
|
55
|
+
### Duet
|
|
56
|
+
|
|
57
|
+
[](images/themes/duet.webp)
|
|
58
|
+
|
|
59
|
+
Default colours: `cobalt`.
|
|
60
|
+
|
|
61
|
+
### Editorial
|
|
62
|
+
|
|
63
|
+
[](images/themes/editorial.webp)
|
|
64
|
+
|
|
65
|
+
Default colours: `terra`.
|
|
66
|
+
|
|
67
|
+
### FHNW
|
|
68
|
+
|
|
69
|
+
[](images/themes/fhnw.webp)
|
|
70
|
+
|
|
71
|
+
Default colours: its own FHNW colours.
|
|
72
|
+
|
|
73
|
+
### Terminal
|
|
74
|
+
|
|
75
|
+
[](images/themes/terminal.webp)
|
|
76
|
+
|
|
77
|
+
Default colours: `phosphor`, dark.
|
|
78
|
+
|
|
79
|
+
### Academic
|
|
80
|
+
|
|
81
|
+
[](images/themes/academic.webp)
|
|
82
|
+
|
|
83
|
+
Default colours: `nordic`.
|
|
84
|
+
|
|
85
|
+
### Sketch
|
|
86
|
+
|
|
87
|
+
[](images/themes/sketch.webp)
|
|
88
|
+
|
|
89
|
+
Default colours: `pastel`.
|
|
90
|
+
|
|
91
|
+
## Choose colours
|
|
92
|
+
|
|
93
|
+
To use other colours, set `palette` to one of these:
|
|
94
|
+
|
|
95
|
+
| Name to use | Colours |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `lagoon` | Teal on warm stone |
|
|
98
|
+
| `swiss` | White, black and signal red (neue's own) |
|
|
99
|
+
| `cobalt` | Strong ultramarine on white |
|
|
100
|
+
| `nordic` | Ice grey, deep navy and a cool steel blue |
|
|
101
|
+
| `graphite` | Black and white with a steel-blue accent |
|
|
102
|
+
| `terra` | Cream, deep navy and brick red |
|
|
103
|
+
| `forest` | Deep green with moss and ochre |
|
|
104
|
+
| `ember` | Sand and espresso with orange |
|
|
105
|
+
| `neon` | Violet and magenta, loud |
|
|
106
|
+
| `phosphor` | Green on black, like an old terminal screen |
|
|
107
|
+
| `pastel` | Soft pastels, mauve and peach (in the style of Catppuccin) |
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
---
|
|
111
|
+
theme: neue
|
|
112
|
+
palette: terra
|
|
113
|
+
---
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The FHNW theme always uses the FHNW colours.
|
|
117
|
+
|
|
118
|
+
## Light or dark
|
|
119
|
+
|
|
120
|
+
Every palette has a light and a dark version. Choose dark for a dark lecture hall, light for a bright room:
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
---
|
|
124
|
+
theme: neue
|
|
125
|
+
palette: terra
|
|
126
|
+
appearance: dark
|
|
127
|
+
---
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Some slides use the other version on purpose: neue's statement slides are dark in a light talk and light in a dark one.
|
|
131
|
+
|
|
132
|
+
## Every theme in every palette
|
|
133
|
+
|
|
134
|
+
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. The FHNW theme has only its own colours, so it is not shown here.
|
|
135
|
+
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
### Light
|
|
139
|
+
|
|
140
|
+
<!-- theme-sheets light -->
|
|
141
|
+
|
|
142
|
+
### Dark
|
|
143
|
+
|
|
144
|
+
<!-- theme-sheets dark -->
|
|
145
|
+
|
|
146
|
+
## Try a look before saving it
|
|
147
|
+
|
|
148
|
+
Open the launch page and choose **Presenter view**:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
mdeck run my-talk.md
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Its theme, palette and light or dark controls, under the notes, let you try other looks (fold them away with the arrow beside them). These changes are a preview: they do not save back to your text file. Once you find a look you like, put its settings in the file and save.
|
|
155
|
+
|
|
156
|
+
## More themes from other people
|
|
157
|
+
|
|
158
|
+
The theme repository has themes and palettes others made. `mdeck themes search` lists them, and `mdeck themes install <pack> my-talk.md` puts one beside your slides; see [Install or share a theme](theme-repository.html).
|
|
159
|
+
|
|
160
|
+
## Add your name and organization
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
---
|
|
164
|
+
theme: neue
|
|
165
|
+
meta:
|
|
166
|
+
title: "A place to grow"
|
|
167
|
+
author: "Alex Morgan"
|
|
168
|
+
organization: "The garden team"
|
|
169
|
+
date: "12 June 2026"
|
|
170
|
+
---
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The text on the title slide still comes from the heading you write on that slide. The settings above supply information for the slide's header and footer.
|
|
174
|
+
|
|
175
|
+
You can also add `logo: ./img/logo.png` inside the `meta` group.
|
|
176
|
+
|
|
177
|
+
## Control headers and page numbers
|
|
178
|
+
|
|
179
|
+
These settings go together under `show`, in the deck settings:
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
show:
|
|
183
|
+
organization: none
|
|
184
|
+
numbers: all
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
| Setting | Choices | Usual behavior |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `show.organization` | `title`, `all`, `none` | Organization appears on the title slide |
|
|
190
|
+
| `show.author` | `title`, `all`, `none` | Author and date appear on the title slide |
|
|
191
|
+
| `show.numbers` | `slides`, `all`, `none` | Numbers appear after the title slide |
|
|
192
|
+
| `show.sections` | `all`, `none` | Section labels appear when provided |
|
|
193
|
+
|
|
194
|
+
## Change the slide shape
|
|
195
|
+
|
|
196
|
+
The usual shape is widescreen, with a width of 1920 and height of 1080. For a classic 4:3 shape, set:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
width: 1920
|
|
200
|
+
height: 1440
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Check your slides after changing their shape. A layout that fit before may need fewer words or a different picture crop.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Ask your audience
|
|
2
|
+
|
|
3
|
+
A slide can ask a question that people answer on their phones, with the results appearing on the slide as they come in.
|
|
4
|
+
|
|
5
|
+
Your assistant can add these for you. Say what you want to find out and where in the talk:
|
|
6
|
+
|
|
7
|
+
```prompt
|
|
8
|
+
Before the chapter on methods, ask which of the three methods people
|
|
9
|
+
have used, and end with a slide for open questions. Show the QR code
|
|
10
|
+
once at the start.
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The rest of this page shows what it writes, and how to run the questions in a real room.
|
|
14
|
+
|
|
15
|
+
## Add a poll
|
|
16
|
+
|
|
17
|
+
```markdown
|
|
18
|
+
---
|
|
19
|
+
# Where do we eat?
|
|
20
|
+
|
|
21
|
+
<poll room="lunch" options="Mensa|Thai|Pizza|Salad" />
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The slide shows one bar per option, the number of answers, a QR code and a short link. Scanning the code opens a small answer page on the phone with one button per option. Each phone has one vote and can change it; the bars follow the latest answer.
|
|
25
|
+
|
|
26
|
+
Every poll in a presentation shows the same code, so people scan once. Their phones then follow along: when you reach a slide with a poll, its buttons appear on every phone; between polls, the phones say that the next question will appear there. You can show the code at the start of the talk, or on the first poll.
|
|
27
|
+
|
|
28
|
+
| Setting | Meaning |
|
|
29
|
+
|---|---|
|
|
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 tapping again hides it. The audience window shows it too. |
|
|
32
|
+
| `options` | The answers, separated by `\|` |
|
|
33
|
+
| `question` | Optional question text. Without it, phones show the slide's heading. |
|
|
34
|
+
| `qr` | `false` leaves out the QR code, when an earlier slide showed it with `<qrcode join />`. |
|
|
35
|
+
|
|
36
|
+
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
|
+
|
|
38
|
+
## Other kinds of questions
|
|
39
|
+
|
|
40
|
+
Besides a poll, three more kinds work the same way: one line on a slide, answered on the phones.
|
|
41
|
+
|
|
42
|
+
```markdown
|
|
43
|
+
<scale room="pace" min="1" max="5" low="Too slow" high="Too fast" />
|
|
44
|
+
<wordcloud room="mood" placeholder="One word" />
|
|
45
|
+
<question room="ask" placeholder="Your question" />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Tag | Phones show | The slide shows |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `<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 |
|
|
51
|
+
| `<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
|
+
| `<question>` | A text field | The newest answers as cards, eight unless `limit` says otherwise. People may send several |
|
|
53
|
+
|
|
54
|
+
All of them take `room`, `question` and `qr` like a poll, and `placeholder` for the text fields.
|
|
55
|
+
|
|
56
|
+
## Show the code once
|
|
57
|
+
|
|
58
|
+
Each question shows the QR code unless you say otherwise. To invite everybody once, at the start, put the code on a slide of its own and leave it out of the questions:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
---
|
|
62
|
+
# Take out your phone.
|
|
63
|
+
|
|
64
|
+
<qrcode join />
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
# How do you make slides today?
|
|
68
|
+
|
|
69
|
+
<poll room="tools" options="PowerPoint|Keynote|Markdown" qr="false" />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`<qrcode join />` shows the presentation's join code large, with the link below it. Phones that scanned it follow along to every question. With `qr="false"`, a question leaves out its own code. Leave the join slide up for a while, or come back to it, for people who arrive late. `size` sets the size of the code on the join slide.
|
|
73
|
+
|
|
74
|
+
The code is drawn in the slide's colours, so it fits the theme; phone cameras also read light codes on dark slides.
|
|
75
|
+
|
|
76
|
+
Hover over the results to see **Reset**, which clears the answers, for example before the real session. Only the presenter can reset.
|
|
77
|
+
|
|
78
|
+
## How phones and slides meet
|
|
79
|
+
|
|
80
|
+
The phones never load your slides. A small **server** connects your screen and the phones: your screen tells it which poll is showing and what the phones should display, and the server shows that on its own answer page, at an address like `example.org/482113`. The six digits are the presentation's session code; they stay the same for the whole talk.
|
|
81
|
+
|
|
82
|
+
So you present from your own computer with `mdeck run`, always with the slides as they are right now, and nothing needs to be uploaded. Only the server has to be reachable by the phones.
|
|
83
|
+
|
|
84
|
+
## Try it on your computer
|
|
85
|
+
|
|
86
|
+
`mdeck run` has a server built in:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
mdeck run my-talk.md --network
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`--network` makes it reachable from phones and tablets in the same network, and the QR code then points at your computer. Without `--network`, the slide says so and offers a link to try the answer page in another browser tab on your computer. The launch page shows the join link and the session code too.
|
|
93
|
+
|
|
94
|
+
Some networks, often large Wi-Fi networks at universities, do not let devices reach each other. If phones cannot open the page, connect your laptop and the phones to a phone hotspot, or use a server on the internet (below).
|
|
95
|
+
|
|
96
|
+
## Use it in a real session
|
|
97
|
+
|
|
98
|
+
For a lecture hall, run your own server once on a web server; it can serve all your presentations, and it also lets an iPad present from any network (see [Draw on your slides](drawing.html#present-from-an-ipad)). On that machine, install mdeck and start it:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
MDECK_SERVER_KEY=choose-a-secret mdeck server
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
It listens on `127.0.0.1:8787`. Give it a host name of its own, such as `rooms.example.org`, and make your web server pass everything on that name to it, for example with nginx:
|
|
105
|
+
|
|
106
|
+
```nginx
|
|
107
|
+
# in the http block
|
|
108
|
+
map $http_upgrade $connection_upgrade { default upgrade; '' ''; }
|
|
109
|
+
|
|
110
|
+
server {
|
|
111
|
+
server_name rooms.example.org;
|
|
112
|
+
# your usual listen and certificate lines here
|
|
113
|
+
|
|
114
|
+
location / {
|
|
115
|
+
proxy_pass http://127.0.0.1:8787;
|
|
116
|
+
proxy_http_version 1.1;
|
|
117
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
118
|
+
proxy_set_header Connection $connection_upgrade;
|
|
119
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
120
|
+
proxy_buffering off;
|
|
121
|
+
proxy_read_timeout 1h;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The slides and phones keep a connection open to receive answers live, which is why buffering is off and the timeout long. The `Upgrade` lines let WebSocket connections through, which presenting from an iPad needs; polls alone work without them. `X-Forwarded-For` lets the server limit each phone, not the web server as a whole. A server on a path of a shared host name, such as `example.org/rooms/`, works for polls too, but presenting from an iPad needs a name of its own.
|
|
127
|
+
|
|
128
|
+
`MDECK_SERVER_KEY` is required for presenting from an iPad: a server without a key does not offer it, and a laptop that does not know the key cannot connect. The server only passes requests along: it keeps nothing of your slides, and a presentation shared through it disappears when the laptop disconnects or the server restarts.
|
|
129
|
+
|
|
130
|
+
To keep the server running and start it with the machine, a systemd service works well. Put the key in a file only root can read, for example `/etc/mdeck-server.env` with the line `MDECK_SERVER_KEY=choose-a-secret`, and create `/etc/systemd/system/mdeck-server.service`:
|
|
131
|
+
|
|
132
|
+
```ini
|
|
133
|
+
[Unit]
|
|
134
|
+
Description=mdeck server
|
|
135
|
+
After=network.target
|
|
136
|
+
|
|
137
|
+
[Service]
|
|
138
|
+
EnvironmentFile=/etc/mdeck-server.env
|
|
139
|
+
ExecStart=/usr/local/bin/mdeck server --port 8787
|
|
140
|
+
User=www-data
|
|
141
|
+
Restart=on-failure
|
|
142
|
+
NoNewPrivileges=true
|
|
143
|
+
ProtectSystem=strict
|
|
144
|
+
ProtectHome=true
|
|
145
|
+
|
|
146
|
+
[Install]
|
|
147
|
+
WantedBy=multi-user.target
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Adjust `ExecStart` to where `which mdeck` points; if Node is installed in a home folder, for example with nvm, also remove `ProtectHome=true`. Then run `sudo systemctl enable --now mdeck-server`. `journalctl -u mdeck-server` shows its log. After updating mdeck, `sudo systemctl restart mdeck-server`; rooms start empty again.
|
|
151
|
+
|
|
152
|
+
Then tell the presentation where the server is:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
---
|
|
156
|
+
server: https://rooms.example.org
|
|
157
|
+
---
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
To keep the address out of the deck, leave the setting out and set `MDECK_SERVER=https://rooms.example.org` in your shell instead; `mdeck run --server` then finds it. A built or sent presentation reads the address from the deck only, because it has to carry it to wherever it is opened.
|
|
161
|
+
|
|
162
|
+
`MDECK_SERVER_KEY` is the server’s master key. `mdeck run` keeps it on your computer and lets your local browser, or an iPad with a revocable pairing token, move the phones along and reset polls for this deck. Pairing QR codes never include the master key. Start `mdeck run` with the same key (`MDECK_SERVER_KEY=choose-a-secret mdeck run my-talk.md`), and the launch page checks the server, shows the join link and opens the presenter view. Paired devices remember only their token, scoped to this presentation; it never appears in the address bar or in the slide file. A hosted presenter can still unlock the server by opening the presenter view once with `?serverkey=…` in its address. That browser remembers the master key. Without permission to control the server, phones wait. The presenter view says whether the phones follow it, and if not, why.
|
|
163
|
+
|
|
164
|
+
The presenter's screen repeats the current poll every few seconds, so phones that join late, or a server that restarted, catch up. While a presenter view is open, other windows of the presentation in the same browser leave the phones alone. If the presentation is open on several screens, even in other browsers, the phones follow the one where the slides were changed last; just opening it elsewhere does not take them away. A presenter view that has lost the phones says so, and changing the slide there takes them back.
|
|
165
|
+
|
|
166
|
+
Two presentations with the same title share a session code. Give one of them `session: id:`, or set the digits yourself with `session: code:`.
|
|
167
|
+
|
|
168
|
+
## What is stored
|
|
169
|
+
|
|
170
|
+
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.
|
|
171
|
+
|
|
172
|
+
## In a PDF or a shared file
|
|
173
|
+
|
|
174
|
+
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: each shows its question and options, with a note that it was answered live, and no join code. If the deck's `server` still answers when the file is made or opened, the results it holds appear instead. Printed slides never show the join code or the presenter's buttons.
|
|
175
|
+
|
|
176
|
+
## Make your own activity
|
|
177
|
+
|
|
178
|
+
Polls, scales, word clouds and questions are ordinary components. The [interactive content](components.html#audience-interaction) guide shows how to write your own, such as a quiz, or ask your assistant to write one.
|