mdeck 3.10.0 → 3.10.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.10.1 — 2026-10-11
4
+
5
+ - Theme settings can be text: `type = "text"` for words a theme shows, such as Terminal's prompt. Authors write them as they should appear (`prompt: "~/demo $ "`), spaces included, and mdeck quotes them for the stylesheet; the editor shows a text box with the theme's own words. Before, such words could only be fixed in the theme.
6
+
3
7
  ## 3.10.0 — 2026-10-11
4
8
 
5
9
  - Glass has a `motion` setting: `motion: 0` holds its light still, for recordings and calm rooms. The guide for theme authors names the settings themes share: `motion` for what moves while presenting and `texture` for grain, paper and scanlines.
@@ -82,7 +82,7 @@ params:
82
82
 
83
83
  Available params depend on the theme; `mdeck list themes` shows them with the
84
84
  kind of value each takes: a size in pixels, a number, a switch (`1` or `0`), a
85
- choice or a font. `0` or `0px` turns a feature off.
85
+ choice, a font or a text, written as it should appear (`prompt: "~/demo $ "`). `0` or `0px` turns a feature off.
86
86
  A font named this way is not downloaded: it has to be installed on the
87
87
  computer that shows the slides, or the browser uses the next font in the
88
88
  list; PDFs and single files embed only the theme's own fonts. A value that does not fit
@@ -201,7 +201,7 @@ Keep headings to a few words; three or four points make a good slide.
201
201
  `:root` block from them, so the stylesheet only contains rules.
202
202
  - `params` name the settings authors may change under `params:` in the deck.
203
203
  Each points at a token; its default is the token's value, so nothing is
204
- written twice. `type` (`size`, `number`, `switch`, `choice` or `font`) says
204
+ written twice. `type` (`size`, `number`, `switch`, `choice`, `font` or `text`) says
205
205
  what kind of value it takes; `min`, `max` and `step` go with a size (in
206
206
  pixels) or a number, `choices` with a choice. The token's value must be of
207
207
  that type. The editor shows a slider, a box to tick or a list for it, and
@@ -79,7 +79,8 @@ params:
79
79
 
80
80
  A parameter's default is its token's value. `type` says what kind of value it
81
81
  takes: `size` (pixels, with `min`, `max` and `step`), `number` (with the same
82
- three), `switch` (1 or 0), `choice` (one of `choices`) or `font`. A deck value
82
+ three), `switch` (1 or 0), `choice` (one of `choices`), `font` or `text` (words to show,
83
+ which mdeck quotes; the token's value is a quoted CSS string). A deck value
83
84
  that does not fit is left out, and `mdeck check` reports it, like unknown
84
85
  parameter names; one outside `min` and `max` is used, with a warning. Without
85
86
  a `type`, a parameter takes any text.
@@ -159,8 +159,9 @@ description = "12px by default, from about 6px to 20px; 0px leaves headings with
159
159
  | `switch` | `1` or `0` (also `true`/`false`, `on`/`off`) | a box to tick | |
160
160
  | `choice` | one of its `choices` | a list | `choices = ["left", "right"]` |
161
161
  | `font` | a CSS font list | a text box | |
162
+ | `text` | words to show, such as a prompt; the author writes them plain and mdeck quotes them | a text box | |
162
163
 
163
- The token's value must be a value of that type: a `size` is written `"12px"`, a `switch` `"1"`. A value in the deck that does not fit is left out, and the theme's own value stays; `mdeck check` says why. A value outside `min` and `max` is used, with a warning: the range is what the theme was made for. A parameter without a `type` takes any text, as before.
164
+ The token's value must be a value of that type: a `size` is written `"12px"`, a `switch` `"1"`, a `text` as a quoted CSS string, `"'~/talk $ '"`, used as `content: var(--prompt)`. A value in the deck that does not fit is left out, and the theme's own value stays; `mdeck check` says why. A value outside `min` and `max` is used, with a warning: the range is what the theme was made for. A parameter without a `type` takes any text, as before.
164
165
 
165
166
  A few habits make parameters easy to use:
166
167
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdeck",
3
- "version": "3.10.0",
3
+ "version": "3.10.1",
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",
@@ -10,7 +10,7 @@ import { PalettePicker } from './PalettePicker.jsx'
10
10
  import { LookThumbnail } from './LookThumbnail.jsx'
11
11
  import { designUrl, DESIGN_TAB } from '../designLink.js'
12
12
  import { normalMode } from '../../runtime/hotspots.js'
13
- import { paramValue } from '../../extensions/params.js'
13
+ import { paramValue, paramDefault } from '../../extensions/params.js'
14
14
 
15
15
  // What the `show` setting controls: the label for each choice and its values.
16
16
  const SHOW = { organization: ['Organization', ['title', 'all', 'none']], author: ['Author and date', ['title', 'all', 'none']], numbers: ['Slide numbers', ['slides', 'all', 'none']], sections: ['Section labels', ['all', 'none']] }
@@ -112,8 +112,8 @@ function DesignLink({ kind, item }) {
112
112
 
113
113
  // The control for one theme parameter, by its type: a slider for a size or a
114
114
  // number with a range, a box to tick for a switch, a list for a choice, else
115
- // text (with a colour picker when the default is a colour). An empty value
116
- // means the theme's own.
115
+ // a text box (with a colour picker when the default is a colour). An empty
116
+ // value means the theme's own; a text shows the theme's without its quotes.
117
117
  function ParamInput({ def, value, onChange }) {
118
118
  const current = value != null && !paramValue(def, value).problem ? paramValue(def, value).value : def.default
119
119
  if (def.type === 'switch') return <>
@@ -137,6 +137,6 @@ function ParamInput({ def, value, onChange }) {
137
137
  const color = isColor(def.default)
138
138
  return <>
139
139
  {color ? <input type="color" value={isColor(value) ? value : def.default} onInput={e => onChange(e.currentTarget.value)} style={{ flex: '0 0 auto' }} /> : null}
140
- <input type="text" value={value ?? ''} placeholder={def.default} onInput={e => onChange(e.currentTarget.value)} />
140
+ <input type="text" value={value ?? ''} placeholder={paramDefault(def)} onInput={e => onChange(e.currentTarget.value)} />
141
141
  </>
142
142
  }
@@ -8,7 +8,7 @@ const isColor = value => /^#[0-9a-f]{3,8}$/i.test(String(value ?? '').trim())
8
8
  const update = (list, index, patch) => list.map((item, i) => i === index ? { ...item, ...patch } : item)
9
9
  const without = (list, index) => list.filter((_, i) => i !== index)
10
10
  // The kinds of value a theme parameter takes (see src/extensions/params.js).
11
- const PARAM_KINDS = [['', 'text'], ['size', 'size'], ['number', 'number'], ['switch', 'switch'], ['choice', 'choice'], ['font', 'font']]
11
+ const PARAM_KINDS = [['', 'CSS value'], ['size', 'size'], ['number', 'number'], ['switch', 'switch'], ['choice', 'choice'], ['font', 'font'], ['text', 'text']]
12
12
 
13
13
  function Identity({ model, onChange }) {
14
14
  return <>
@@ -7,11 +7,13 @@
7
7
  // switch 1 or 0 (also true and false, on and off, yes and no)
8
8
  // choice one of `choices`
9
9
  // font a CSS font list
10
+ // text words to show, such as a prompt: the author writes them plain,
11
+ // mdeck quotes them; the theme's own value is a quoted CSS string
10
12
  //
11
13
  // The manifest check, `mdeck check`, the slides and the editor all use these
12
14
  // functions, so they agree on what a value means.
13
15
 
14
- export const PARAM_TYPES = ['size', 'number', 'switch', 'choice', 'font']
16
+ export const PARAM_TYPES = ['size', 'number', 'switch', 'choice', 'font', 'text']
15
17
  export const PARAM_KEYS = ['token', 'title', 'description', 'type', 'min', 'max', 'step', 'choices']
16
18
  const RANGED = ['size', 'number']
17
19
  const NUMBER_RE = /^-?(\d+\.?\d*|\.\d+)$/
@@ -26,6 +28,25 @@ function pixels(value) {
26
28
 
27
29
  const unit = param => param.type === 'size' ? 'px' : ''
28
30
 
31
+ // A CSS string literal ('…' or "…") and the text in it.
32
+ const STRING_RE = /^(['"])((?:\\[^]|(?!\1)[^\\\n])*)\1$/
33
+
34
+ /** Text as a CSS string: quoted, with backslashes, quotes and line breaks escaped. */
35
+ export function cssString(text) {
36
+ return '"' + String(text).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r?\n/g, '\\A ') + '"'
37
+ }
38
+
39
+ /** The text in a CSS string literal, or null when it is not one. */
40
+ export function unquote(css) {
41
+ const match = STRING_RE.exec(String(css).trim())
42
+ return match ? match[2].replace(/\\A ?/g, '\n').replace(/\\([^])/g, '$1') : null
43
+ }
44
+
45
+ /** A parameter's default as an author would write it: a text without its quotes. */
46
+ export function paramDefault(param) {
47
+ return param.type === 'text' ? unquote(param.default) ?? param.default : param.default
48
+ }
49
+
29
50
  /** The range a parameter was made for, as text ("6px to 20px", "at least 0"), or ''. */
30
51
  export function paramRange(param) {
31
52
  const { min, max } = param
@@ -54,6 +75,8 @@ export function paramValue(param, raw) {
54
75
  const type = param.type
55
76
  if (raw == null || (typeof raw === 'object')) return { problem: 'needs a single value' }
56
77
  if (!type) return { value: String(raw) }
78
+ // Text keeps its spaces (a prompt ends in one) and may be empty.
79
+ if (type === 'text') return { value: cssString(raw) }
57
80
  const text = String(raw).trim()
58
81
  if (!text) return { problem: 'is empty' }
59
82
  if (type === 'font') return { value: text }
@@ -98,6 +121,10 @@ export function paramSettingProblems(param, fallback) {
98
121
  else if (new Set(choices).size !== choices.length) problems.push(['choices', 'lists a value twice'])
99
122
  } else if (param.choices != null) problems.push(['choices', `is for a choice, not a ${type}`])
100
123
  if (problems.length || fallback == null) return problems
124
+ if (type === 'text') {
125
+ if (unquote(fallback) == null) problems.push(['type', `a text's token is a quoted CSS string, such as "'~/talk $ '" (got ${JSON.stringify(fallback)})`])
126
+ return problems
127
+ }
101
128
  // The default is the token's value: it has to be a value of this type too.
102
129
  const result = paramValue(param, fallback)
103
130
  if (result.problem) problems.push(['type', `the token's value ${JSON.stringify(fallback)} ${result.problem}`])