rikiki-deck 0.6.0 → 0.7.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/.claude/skills/rikiki-debug/SKILL.md +17 -7
- package/.claude/skills/rikiki-deck/SKILL.md +362 -77
- package/.claude/skills/rikiki-theme/SKILL.md +1 -1
- package/README.md +69 -50
- package/bin/lib/assemble.mjs +154 -0
- package/bin/lib/box-geometry.mjs +66 -0
- package/bin/lib/browser.mjs +279 -0
- package/bin/lib/check.mjs +992 -0
- package/bin/lib/cli-error.mjs +26 -0
- package/bin/lib/diff.mjs +275 -0
- package/bin/lib/export-pdf.mjs +42 -0
- package/bin/lib/graph-hit.mjs +86 -0
- package/bin/lib/inline.mjs +71 -8
- package/bin/lib/prune-icons.mjs +81 -0
- package/bin/lib/render.mjs +195 -0
- package/bin/lib/scan-external.mjs +126 -0
- package/bin/lib/starter.mjs +27 -14
- package/bin/lib/visual.mjs +120 -0
- package/bin/rikiki.mjs +375 -28
- package/dist/annotation-marks.js +1 -0
- package/dist/application/deep-link.d.ts +29 -0
- package/dist/application/keymap.d.ts +40 -0
- package/dist/application/mouse-nav.d.ts +12 -0
- package/dist/atoms/deck-punch.d.ts +6 -0
- package/dist/atoms/deck-source.d.ts +12 -0
- package/dist/bar-segments.js +1 -0
- package/dist/browser-location.js +1 -0
- package/dist/cards-syntax.js +6 -0
- package/dist/contrast.js +1 -0
- package/dist/deck-agenda.js +6 -0
- package/dist/deck-annotate.js +18 -0
- package/dist/deck-bar.js +19 -0
- package/dist/deck-bento.js +4 -0
- package/dist/deck-callout.js +1 -1
- package/dist/deck-cell.js +1 -0
- package/dist/deck-checklist.js +1 -0
- package/dist/deck-cover.js +6 -6
- package/dist/deck-csv.js +15 -0
- package/dist/deck-feature-cards.js +2 -2
- package/dist/deck-feature.js +2 -2
- package/dist/deck-figure.js +8 -0
- package/dist/deck-fit.js +1 -0
- package/dist/deck-flow.js +7 -0
- package/dist/deck-graph.js +25 -0
- package/dist/deck-grid.js +1 -1
- package/dist/deck-icon.js +1 -0
- package/dist/deck-kpi-grid.js +4 -0
- package/dist/deck-link.js +1 -0
- package/dist/deck-md.js +8 -3
- package/dist/deck-mermaid.js +15 -3
- package/dist/deck-outline.js +1 -0
- package/dist/deck-overview.js +15 -2
- package/dist/deck-persona.js +6 -0
- package/dist/deck-photo.js +1 -1
- package/dist/deck-point.js +1 -0
- package/dist/deck-presenter.js +43 -14
- package/dist/deck-pull.js +1 -0
- package/dist/deck-punch.js +1 -1
- package/dist/deck-quote.js +6 -0
- package/dist/deck-root.js +17 -13
- package/dist/deck-section.js +2 -2
- package/dist/deck-source.js +2 -0
- package/dist/deck-split.js +5 -3
- package/dist/deck-stat.js +2 -2
- package/dist/deck-step-list.js +4 -2
- package/dist/deck-table.js +1 -0
- package/dist/deck-takeaway.js +2 -2
- package/dist/deck-timeline.js +5 -0
- package/dist/deck-transition.js +3 -3
- package/dist/deck-versus.js +9 -0
- package/dist/deep-link.js +1 -0
- package/dist/domain/deck-link.d.ts +21 -0
- package/dist/domain/deck-outline.d.ts +50 -0
- package/dist/domain/navigation.d.ts +25 -0
- package/dist/domain/viewport.d.ts +42 -0
- package/dist/escape-html.js +1 -0
- package/dist/extras/deck-agenda.d.ts +25 -0
- package/dist/extras/deck-annotate.d.ts +108 -0
- package/dist/extras/deck-bar.d.ts +32 -0
- package/dist/extras/deck-checklist.d.ts +20 -0
- package/dist/extras/deck-figure.d.ts +26 -0
- package/dist/extras/deck-flow.d.ts +41 -0
- package/dist/extras/deck-graph.d.ts +92 -0
- package/dist/extras/deck-icon.d.ts +20 -0
- package/dist/extras/deck-kpi-grid.d.ts +26 -0
- package/dist/extras/deck-persona.d.ts +31 -0
- package/dist/extras/deck-pull.d.ts +13 -0
- package/dist/extras/deck-quote.d.ts +28 -0
- package/dist/extras/deck-table.d.ts +26 -0
- package/dist/extras/deck-timeline.d.ts +31 -0
- package/dist/extras/deck-versus.d.ts +18 -0
- package/dist/extras/signature.d.ts +2 -0
- package/dist/fit-controller.js +1 -0
- package/dist/graph-layout.js +1 -0
- package/dist/grid-tracks.js +1 -0
- package/dist/icon-set.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +92 -49
- package/dist/infrastructure/browser-location.d.ts +3 -0
- package/dist/keymap.js +1 -0
- package/dist/layouts/deck-bento.d.ts +38 -0
- package/dist/layouts/deck-cover.d.ts +6 -0
- package/dist/layouts/deck-feature.d.ts +7 -0
- package/dist/layouts/deck-split.d.ts +12 -0
- package/dist/layouts/deck-takeaway.d.ts +7 -0
- package/dist/molecules/deck-callout.d.ts +2 -0
- package/dist/molecules/deck-cell.d.ts +19 -0
- package/dist/molecules/deck-csv.d.ts +38 -0
- package/dist/molecules/deck-fit.d.ts +14 -0
- package/dist/molecules/deck-md.d.ts +3 -0
- package/dist/molecules/deck-point.d.ts +22 -0
- package/dist/molecules/deck-stat.d.ts +2 -0
- package/dist/molecules/deck-step-list.d.ts +8 -0
- package/dist/mouse-nav.js +1 -0
- package/dist/navigation.js +1 -0
- package/dist/parse-csv.js +3 -0
- package/dist/plugins/shiki.d.ts +2 -2
- package/dist/runtime/deck-root.d.ts +74 -5
- package/dist/shared/annotation-marks.d.ts +60 -0
- package/dist/shared/bar-segments.d.ts +28 -0
- package/dist/shared/cards-syntax.d.ts +31 -0
- package/dist/shared/contrast.d.ts +52 -0
- package/dist/shared/escape-html.d.ts +3 -0
- package/dist/shared/fit-controller.d.ts +27 -0
- package/dist/shared/graph-layout.d.ts +35 -0
- package/dist/shared/grid-tracks.d.ts +17 -0
- package/dist/shared/icon-set.d.ts +6 -0
- package/dist/shared/parse-csv.d.ts +9 -0
- package/dist/shared/slide-fill.d.ts +8 -0
- package/dist/shared-styles.js +1 -1
- package/dist/signature.js +1 -0
- package/dist/slide-fill.js +1 -0
- package/dist/standalone.js +180 -95
- package/dist/vendor/THIRD-PARTY-NOTICES.txt +4347 -0
- package/dist/vendor/inventory.json +3029 -0
- package/dist/vendor/lit.js +62 -2
- package/dist/vendor/shiki.js +1 -57
- package/dist/viewport.js +1 -0
- package/docs/llms/rikiki-reference.md +927 -63
- package/docs/llms/rikiki-workflow.md +536 -0
- package/llms.txt +39 -12
- package/package.json +21 -7
- package/themes/rikiki.css +173 -47
- package/themes/siliceum.css +171 -51
package/README.md
CHANGED
|
@@ -6,15 +6,48 @@
|
|
|
6
6
|
|
|
7
7
|
A tiny **Lit Web Components** framework for technical presentations. Drop a folder anywhere, open `index.html`, give the talk. No build step on the consumer side · the framework itself is built from TypeScript, but the output is plain ES modules you import directly.
|
|
8
8
|
|
|
9
|
-
This documentation tracks rikiki v0.
|
|
9
|
+
This documentation tracks rikiki v0.7.1.
|
|
10
10
|
|
|
11
11
|
## TL;DR
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
|
|
14
|
+
npm install rikiki-deck
|
|
15
|
+
npx rikiki init my-deck.html
|
|
16
|
+
python3 -m http.server # ES modules need http://, not file://
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
`init` writes the deck and copies the runtime it loads into `./rikiki/` beside
|
|
20
|
+
it. Edit `my-deck.html`. Each slide is a custom element. Markdown is available anywhere via `<deck-md>`. Navigate with `←` / `→` (or click, or the scroll wheel), `O` for the overview grid. Decks are linear by default; add `nav="2d"` on `<deck-root>` for chapter/slide grid navigation.
|
|
21
|
+
|
|
22
|
+
## Look at it, and measure it
|
|
23
|
+
|
|
24
|
+
You cannot review a deck you cannot see. Two commands stand in for eyes, both
|
|
25
|
+
needing the optional peer `playwright`:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npx rikiki render talk.html # one PNG per slide + a gallery + a manifest
|
|
29
|
+
npx rikiki render talk.html --steps # every revealed state, not just the first
|
|
30
|
+
npx rikiki render talk.html --out after/ --baseline before/ # what moved since last time
|
|
31
|
+
npx rikiki check talk.html # what is wrong, where, and what to try
|
|
32
|
+
npx rikiki check talk.html --json # the same, as a versioned report
|
|
33
|
+
npx rikiki check talk.html --steps # measure every revealed state, not just the first
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`render --baseline <dir>` compares the fresh pictures to an earlier set, slide
|
|
37
|
+
by slide, and ranks them by how much changed, with the bounding box of what
|
|
38
|
+
moved and a `diff.json` beside the manifest. It exits 1 on a slide that
|
|
39
|
+
changed, disappeared or changed size. The default `--threshold 0.5` (percent of
|
|
40
|
+
pixels) keeps anti-aliasing noise out of the report; `--threshold 0` lists
|
|
41
|
+
every pixel change.
|
|
42
|
+
|
|
43
|
+
`check` reports a runtime that never loaded, a file that did not arrive, a
|
|
44
|
+
misspelled `deck-*` element that renders as nothing, content the slide clips
|
|
45
|
+
away, text too small for a room, and duplicate slide ids. It exits 0 when
|
|
46
|
+
nothing blocks, 1 on defects, 2 when it could not look at the deck at all. It
|
|
47
|
+
also lists what it did **not** check, because silence would read as approval.
|
|
48
|
+
Under `--steps`, each slide is measured in its opening state and in every
|
|
49
|
+
state its own reveals step through, and each diagnostic says which one
|
|
50
|
+
(`state: 2`).
|
|
18
51
|
|
|
19
52
|
## For LLMs / coding assistants
|
|
20
53
|
|
|
@@ -24,6 +57,10 @@ npm package:
|
|
|
24
57
|
|
|
25
58
|
- **[`llms.txt`](./llms.txt)** — concise capability map and entry points (the
|
|
26
59
|
[llms.txt convention](https://llmstxt.org)).
|
|
60
|
+
- **[`docs/llms/rikiki-workflow.md`](./docs/llms/rikiki-workflow.md)** — the
|
|
61
|
+
working guide: brief to plan to slides to checks to delivery, with nine
|
|
62
|
+
compositions by intent whose HTML is verified by the test suite. The one to
|
|
63
|
+
read first.
|
|
27
64
|
- **[`docs/llms/rikiki-reference.md`](./docs/llms/rikiki-reference.md)** — every
|
|
28
65
|
tag, attribute, slot, design token, plugin, and recipe in one file. Have the
|
|
29
66
|
assistant read this first; tell it not to invent tags or tokens outside it.
|
|
@@ -31,42 +68,30 @@ npm package:
|
|
|
31
68
|
`.claude/skills/` (see [Claude Code skills](#claude-code-skills) to install
|
|
32
69
|
them); they teach an assistant the authoring/theming/debugging workflows.
|
|
33
70
|
|
|
34
|
-
##
|
|
71
|
+
## What the install gives you
|
|
35
72
|
|
|
36
73
|
```
|
|
37
|
-
rikiki/
|
|
74
|
+
node_modules/rikiki-deck/
|
|
38
75
|
├── tokens.css ← entry point · re-exports the default theme
|
|
39
76
|
├── themes/
|
|
40
77
|
│ ├── rikiki.css ← default theme (acid greens + mango, on dark)
|
|
41
78
|
│ ├── siliceum.css ← alternative theme (warm paper + yellow)
|
|
42
79
|
│ └── siliceum-fonts.css ← self-hosted Source Sans Pro + JetBrains Mono
|
|
43
80
|
├── fonts/ ← woff2 files used by the Siliceum theme
|
|
44
|
-
├──
|
|
45
|
-
│
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
│ │ deck-presenter, deck-transition, deck-notes
|
|
50
|
-
│ ├── layouts/ ← deck-cover, deck-section, deck-feature,
|
|
51
|
-
│ │ deck-split, deck-feature-cards, deck-takeaway,
|
|
52
|
-
│ │ deck-photo
|
|
53
|
-
│ ├── molecules/ ← deck-callout, deck-card, deck-md, deck-mermaid,
|
|
54
|
-
│ │ deck-stat, deck-metric, deck-tier-list,
|
|
55
|
-
│ │ deck-step-list, deck-shortcut, deck-stack,
|
|
56
|
-
│ │ deck-grid
|
|
57
|
-
│ ├── atoms/ ← deck-badge, deck-kicker, deck-punch, deck-code
|
|
58
|
-
│ └── plugins/ ← opt-in (shiki for advanced syntax highlighting)
|
|
59
|
-
├── dist/ ← built output · FLAT regardless of src bucket
|
|
60
|
-
│ (deck-root's dynamic imports rely on it)
|
|
61
|
-
├── build.mjs ← esbuild script
|
|
62
|
-
├── tsconfig.json
|
|
63
|
-
└── starter.html ← blank template, one slide per layout type
|
|
81
|
+
├── dist/ ← the runtime · plain ES modules, FLAT layout
|
|
82
|
+
│ └── vendor/ ← lit and marked · mermaid and Shiki when asked
|
|
83
|
+
├── bin/ ← the rikiki CLI
|
|
84
|
+
├── docs/llms/ ← the full agent reference
|
|
85
|
+
└── .claude/skills/ ← three agent skills · install with `rikiki skills`
|
|
64
86
|
```
|
|
65
87
|
|
|
88
|
+
`init` copies the first five of those next to your deck, minus the heavy
|
|
89
|
+
plugin payloads unless you ask for them.
|
|
90
|
+
|
|
66
91
|
## Three-layer styling
|
|
67
92
|
|
|
68
93
|
1. **Theme tokens** (`themes/<name>.css`) at `:root` · custom properties cross the Shadow DOM, so they reach every component.
|
|
69
|
-
2. **Shared styles**
|
|
94
|
+
2. **Shared styles** · base typography and helpers, compiled into every layout's own Shadow DOM.
|
|
70
95
|
3. **Layout-specific CSS** · each component's own Shadow DOM.
|
|
71
96
|
|
|
72
97
|
To re-theme: copy a theme file, change the values, that's it. All components follow.
|
|
@@ -92,10 +117,14 @@ To re-theme: copy a theme file, change the values, that's it. All components fol
|
|
|
92
117
|
| `<deck-code lang="js" hero?>` | Code block with light syntax highlighting |
|
|
93
118
|
| `<deck-callout type="info\|warn\|danger\|ok">` | Information callout |
|
|
94
119
|
| `<deck-card color="yellow\|orange\|green\|red?">` | Tinted card |
|
|
95
|
-
| `<deck-mermaid>` | Diagram via
|
|
120
|
+
| `<deck-mermaid>` | Diagram via the vendored Mermaid runtime (opt-in bundle) |
|
|
121
|
+
| `<deck-figure>` | An image with its own caption and credit (opt-in bundle) |
|
|
122
|
+
| `<deck-source>` | The credit line under a table, chart, screenshot or quote |
|
|
96
123
|
|
|
97
124
|
Plus `<deck-badge>`, `<deck-metric>`, `<deck-tier-list>`, `<deck-step-list>`, `<deck-kicker>`, `<deck-stack>`, `<deck-grid>`, `<deck-punch>`.
|
|
98
125
|
|
|
126
|
+
Opt-in components add `<deck-annotate>` (numbered badges on a screenshot, placed by `above` / `below` / `left` / `right` anchors as well as pixel offsets) and `<deck-versus slide>` (a before/after slide with its own `title`, `lead` and `footer` slots).
|
|
127
|
+
|
|
99
128
|
## Common patterns
|
|
100
129
|
|
|
101
130
|
### Slide with markdown + code
|
|
@@ -139,7 +168,7 @@ Plus `<deck-badge>`, `<deck-metric>`, `<deck-tier-list>`, `<deck-step-list>`, `<
|
|
|
139
168
|
|
|
140
169
|
## Markdown support (`<deck-md>`)
|
|
141
170
|
|
|
142
|
-
Parser: `marked` 12
|
|
171
|
+
Parser: vendored `marked` 12. Supports GFM (tables, task lists), code blocks, inline code, **bold**, *italic*, lists, blockquotes, links, `---`.
|
|
143
172
|
|
|
144
173
|
## Navigation
|
|
145
174
|
|
|
@@ -233,37 +262,28 @@ deck-cover::part(brand) { font-family: 'Comic Sans'; }
|
|
|
233
262
|
|
|
234
263
|
### Add a layout
|
|
235
264
|
|
|
236
|
-
|
|
265
|
+
Adding a `deck-*` element means changing the framework itself, which lives in
|
|
266
|
+
the repository rather than in this package. See
|
|
267
|
+
[CONTRIBUTING](https://gitlab.com/tordu-jardin/rikiki/-/blob/main/CONTRIBUTING.md).
|
|
237
268
|
|
|
238
|
-
##
|
|
269
|
+
## No build step
|
|
239
270
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
npm run build # node build.mjs (esbuild) + tsc --emitDeclarationOnly
|
|
243
|
-
npm run watch # esbuild watch mode
|
|
244
|
-
npm run typecheck # tsc --noEmit
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
`dist/` is versioned · consumers don't run a build.
|
|
271
|
+
The published runtime is plain ES modules. Nothing here needs compiling,
|
|
272
|
+
bundling or transpiling to author, serve or present a deck.
|
|
248
273
|
|
|
249
274
|
## Claude Code skills
|
|
250
275
|
|
|
251
276
|
The package ships three Claude Code skills so an assistant authoring your deck
|
|
252
277
|
knows the framework: `rikiki-deck` (build a deck), `rikiki-theme` (theming), and
|
|
253
|
-
`rikiki-debug` (diagnose a deck).
|
|
254
|
-
your project (or `~/.claude/skills` for all projects):
|
|
278
|
+
`rikiki-debug` (diagnose a deck).
|
|
255
279
|
|
|
256
280
|
```sh
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
cp -r node_modules/rikiki-deck/.claude/skills/* .claude/skills/
|
|
260
|
-
|
|
261
|
-
# or global · available in every project
|
|
262
|
-
cp -r node_modules/rikiki-deck/.claude/skills/* ~/.claude/skills/
|
|
281
|
+
npx rikiki skills # into ./.claude/skills/
|
|
282
|
+
npx rikiki skills --dir ~/.claude/skills # or once, for every project
|
|
263
283
|
```
|
|
264
284
|
|
|
265
|
-
Claude Code discovers them
|
|
266
|
-
after
|
|
285
|
+
Claude Code discovers them on the next session. Re-run the command with
|
|
286
|
+
`--force` after upgrading the package to pick up skill changes.
|
|
267
287
|
|
|
268
288
|
## Reveals & animations
|
|
269
289
|
|
|
@@ -271,8 +291,7 @@ Per-element click-through builds are an opt-in plugin (`installClickStages()` fr
|
|
|
271
291
|
`dist/click-stages.js`): annotate elements with `data-click`, `data-click-hide`,
|
|
272
292
|
`data-click-auto`, `data-click-stagger`, and `data-morph` (Keynote-style Magic
|
|
273
293
|
Move via View Transitions). Slide transitions are driven by `transition="…"` on
|
|
274
|
-
`<deck-root>`. See `docs/llms/rikiki-reference.md` §7 for the full attribute set
|
|
275
|
-
and `decks/tests/demo.html` for a runnable feature tour.
|
|
294
|
+
`<deck-root>`. See `docs/llms/rikiki-reference.md` §7 for the full attribute set.
|
|
276
295
|
|
|
277
296
|
## Known limits
|
|
278
297
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// ════════════════════════════════════════════════════════════════
|
|
2
|
+
// rikiki assemble · build one deck HTML from ordered partials.
|
|
3
|
+
//
|
|
4
|
+
// A long talk is easier to write, review and diff in pieces. This joins them
|
|
5
|
+
// back into the single HTML file everything else in rikiki expects: `bundle`
|
|
6
|
+
// folds it, `export` prints it, a browser serves it.
|
|
7
|
+
//
|
|
8
|
+
// deck.config.{js,json} shape:
|
|
9
|
+
// export default {
|
|
10
|
+
// title: 'My talk',
|
|
11
|
+
// theme: 'rikiki/tokens.css', // href, relative to the OUTPUT file
|
|
12
|
+
// bundle: 'rikiki/dist/index.js', // runtime href, relative to OUTPUT
|
|
13
|
+
// transition: 'slide', // optional <deck-root transition="…">
|
|
14
|
+
// lang: 'fr', // optional <html lang="…">
|
|
15
|
+
// slides: ['parts/cover.html', 'parts/intro.md', 'parts/closing.html'],
|
|
16
|
+
// };
|
|
17
|
+
//
|
|
18
|
+
// .html partials are inlined verbatim (one or more <deck-*> elements).
|
|
19
|
+
// .md partials become slides · a line that is exactly `---` starts a new one.
|
|
20
|
+
// ════════════════════════════════════════════════════════════════
|
|
21
|
+
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
24
|
+
import { dirname, extname, resolve } from 'node:path';
|
|
25
|
+
import { pathToFileURL } from 'node:url';
|
|
26
|
+
import { ExpectedError } from './cli-error.mjs';
|
|
27
|
+
|
|
28
|
+
// What `rikiki init` writes, so an assembled deck bundles like any other.
|
|
29
|
+
const DEFAULT_THEME = 'rikiki/tokens.css';
|
|
30
|
+
const DEFAULT_RUNTIME = 'rikiki/dist/index.js';
|
|
31
|
+
|
|
32
|
+
const escapeHtml = (s) =>
|
|
33
|
+
String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
34
|
+
|
|
35
|
+
/** Split a markdown file into slides on lines that are exactly `---`
|
|
36
|
+
* (reveal.js convention). One file, many slides. A blank chunk collapses. */
|
|
37
|
+
function splitMarkdownSlides(body) {
|
|
38
|
+
const chunks = [];
|
|
39
|
+
let current = [];
|
|
40
|
+
for (const line of body.split(/\r?\n/)) {
|
|
41
|
+
if (/^[ \t]*---[ \t]*$/.test(line)) {
|
|
42
|
+
chunks.push(current.join('\n'));
|
|
43
|
+
current = [];
|
|
44
|
+
} else current.push(line);
|
|
45
|
+
}
|
|
46
|
+
chunks.push(current.join('\n'));
|
|
47
|
+
return chunks.map((c) => c.trim()).filter((c) => c.length > 0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderPartial(absPath) {
|
|
51
|
+
const body = readFileSync(absPath, 'utf8');
|
|
52
|
+
if (extname(absPath) !== '.md') return body.trimEnd();
|
|
53
|
+
// <deck-md> deindents and parses the raw markdown at runtime, so the body is
|
|
54
|
+
// inlined verbatim. `***` stays available as an in-slide rule.
|
|
55
|
+
return splitMarkdownSlides(body)
|
|
56
|
+
.map((slide) => `<deck-feature>\n<deck-md>\n${slide}\n</deck-md>\n</deck-feature>`)
|
|
57
|
+
.join('\n\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function loadConfig(absPath) {
|
|
61
|
+
if (!existsSync(absPath) || !statSync(absPath).isFile()) {
|
|
62
|
+
throw new ExpectedError(`assemble · config not found: ${absPath}`);
|
|
63
|
+
}
|
|
64
|
+
if (extname(absPath) === '.json') {
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(readFileSync(absPath, 'utf8'));
|
|
67
|
+
} catch (cause) {
|
|
68
|
+
throw new ExpectedError(`assemble · ${absPath} is not valid JSON · ${cause.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
73
|
+
return mod.default ?? mod;
|
|
74
|
+
} catch (cause) {
|
|
75
|
+
// A `.js` file is read as CommonJS or as an ES module depending on the
|
|
76
|
+
// nearest package.json, and `npm init -y` writes "type": "commonjs". Both
|
|
77
|
+
// dialects are legitimate here; only the mismatch is worth a message.
|
|
78
|
+
if (cause?.code === 'ERR_REQUIRE_ESM' || cause instanceof SyntaxError) {
|
|
79
|
+
throw new ExpectedError(
|
|
80
|
+
`assemble · ${absPath} looks like an ES module, but the nearest package.json\n` +
|
|
81
|
+
' does not declare "type": "module". Use `module.exports = {…}`, rename the\n' +
|
|
82
|
+
' file to .mjs, or use a .json config.',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
throw cause;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The default output path: the title, slugged, next to the config. */
|
|
90
|
+
export function defaultOutputPath(configPath, title) {
|
|
91
|
+
const slug = String(title || 'deck')
|
|
92
|
+
.toLowerCase()
|
|
93
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
94
|
+
.replace(/^-|-$/g, '');
|
|
95
|
+
return resolve(dirname(configPath), (slug || 'deck') + '.html');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Assemble a deck from its config.
|
|
100
|
+
* @returns {Promise<{ html: string, outputPath: string, slides: number, unbundleable: string[] }>}
|
|
101
|
+
* `outputPath` is null when the caller asked for stdout · `unbundleable`
|
|
102
|
+
* lists the hrefs `rikiki bundle` will not be able to inline.
|
|
103
|
+
*/
|
|
104
|
+
export async function assembleDeck(configPath, outputPath) {
|
|
105
|
+
const absConfig = resolve(process.cwd(), configPath);
|
|
106
|
+
const config = await loadConfig(absConfig);
|
|
107
|
+
const configDir = dirname(absConfig);
|
|
108
|
+
|
|
109
|
+
if (!Array.isArray(config.slides) || config.slides.length === 0) {
|
|
110
|
+
throw new ExpectedError(`assemble · ${configPath} · \`slides\` must be a non-empty array`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const slidesHtml = config.slides
|
|
114
|
+
.map((rel) => {
|
|
115
|
+
const abs = resolve(configDir, rel);
|
|
116
|
+
if (!existsSync(abs)) throw new ExpectedError(`assemble · partial not found: ${rel}`);
|
|
117
|
+
return renderPartial(abs);
|
|
118
|
+
})
|
|
119
|
+
.join('\n\n');
|
|
120
|
+
|
|
121
|
+
const transitionAttr = config.transition ? ` transition="${escapeHtml(config.transition)}"` : '';
|
|
122
|
+
const html = `<!doctype html>
|
|
123
|
+
<html lang="${escapeHtml(config.lang ?? 'en')}">
|
|
124
|
+
<head>
|
|
125
|
+
<meta charset="UTF-8">
|
|
126
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
127
|
+
<title>${escapeHtml(config.title ?? 'Rikiki deck')}</title>
|
|
128
|
+
<link rel="stylesheet" href="${escapeHtml(config.theme ?? DEFAULT_THEME)}">
|
|
129
|
+
<script type="module" src="${escapeHtml(config.bundle ?? DEFAULT_RUNTIME)}"></script>
|
|
130
|
+
</head>
|
|
131
|
+
<body>
|
|
132
|
+
<deck-root${transitionAttr}>
|
|
133
|
+
${slidesHtml}
|
|
134
|
+
</deck-root>
|
|
135
|
+
</body>
|
|
136
|
+
</html>
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
// A href that does not follow the `rikiki/…` spelling serves fine but will
|
|
140
|
+
// not inline · saying so here beats a silent surprise at bundle time.
|
|
141
|
+
const hrefs = [config.theme ?? DEFAULT_THEME, config.bundle ?? DEFAULT_RUNTIME];
|
|
142
|
+
const unbundleable = hrefs.filter((href) => !/(^|\/)rikiki\//.test(href));
|
|
143
|
+
|
|
144
|
+
if (outputPath === '-') {
|
|
145
|
+
return { html, outputPath: null, slides: config.slides.length, unbundleable };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const absOutput = outputPath
|
|
149
|
+
? resolve(process.cwd(), outputPath)
|
|
150
|
+
: defaultOutputPath(absConfig, config.title);
|
|
151
|
+
mkdirSync(dirname(absOutput), { recursive: true });
|
|
152
|
+
writeFileSync(absOutput, html);
|
|
153
|
+
return { html, outputPath: absOutput, slides: config.slides.length, unbundleable };
|
|
154
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ════════════════════════════════════════════════════════════════
|
|
2
|
+
// Does a painted box leave its container, or land on top of a sibling?
|
|
3
|
+
//
|
|
4
|
+
// Pure rect arithmetic, kept out of `check.mjs` for the same reason as
|
|
5
|
+
// `graph-hit.mjs`: the answer is the whole diagnostic, so the thresholds
|
|
6
|
+
// belong beside tests that pin them, not scattered through the DOM walk that
|
|
7
|
+
// calls them.
|
|
8
|
+
//
|
|
9
|
+
// The functions are also serialized into the browser by `check.mjs` (see
|
|
10
|
+
// BOX_GEOMETRY_READER), so they must stay self-contained: no imports, no
|
|
11
|
+
// closure over anything in this module.
|
|
12
|
+
// ════════════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How far `rect` sits outside `container`, per side and at its worst side.
|
|
16
|
+
*
|
|
17
|
+
* Only the sides where `rect` actually spills are counted · a box that sits
|
|
18
|
+
* fully inside its container reports zero on every side, whatever slack it
|
|
19
|
+
* leaves.
|
|
20
|
+
*/
|
|
21
|
+
export function escapeOf(rect, container) {
|
|
22
|
+
const sides = {
|
|
23
|
+
left: Math.max(0, container.left - rect.left),
|
|
24
|
+
right: Math.max(0, rect.right - container.right),
|
|
25
|
+
top: Math.max(0, container.top - rect.top),
|
|
26
|
+
bottom: Math.max(0, rect.bottom - container.bottom),
|
|
27
|
+
};
|
|
28
|
+
return { pixels: Math.max(sides.left, sides.right, sides.top, sides.bottom), sides };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How much two rects intersect, on each axis.
|
|
33
|
+
*
|
|
34
|
+
* A negative value means the rects do not touch on that axis at all · the
|
|
35
|
+
* caller compares both axes against its own tolerance rather than this
|
|
36
|
+
* function deciding what counts as an overlap.
|
|
37
|
+
*/
|
|
38
|
+
export function overlapOf(a, b) {
|
|
39
|
+
return {
|
|
40
|
+
x: Math.min(a.right, b.right) - Math.max(a.left, b.left),
|
|
41
|
+
y: Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Does `outer` fully contain `inner`, within `tolerance` px on each side?
|
|
47
|
+
*
|
|
48
|
+
* The tolerance absorbs sub-pixel layout rounding · two rects that agree to
|
|
49
|
+
* the pixel should not read as "neither encloses the other".
|
|
50
|
+
*/
|
|
51
|
+
export function encloses(outer, inner, tolerance = 1) {
|
|
52
|
+
return (
|
|
53
|
+
outer.left <= inner.left + tolerance &&
|
|
54
|
+
outer.right >= inner.right - tolerance &&
|
|
55
|
+
outer.top <= inner.top + tolerance &&
|
|
56
|
+
outer.bottom >= inner.bottom - tolerance
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The same three functions, as source, for `page.evaluate` · the inspector
|
|
61
|
+
* runs in the browser and cannot import from here. */
|
|
62
|
+
export const BOX_GEOMETRY_READER = `({
|
|
63
|
+
escapeOf: ${escapeOf},
|
|
64
|
+
overlapOf: ${overlapOf},
|
|
65
|
+
encloses: ${encloses},
|
|
66
|
+
})`;
|