@hajimism/dek 0.2.0 → 0.2.2
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 +32 -0
- package/README.ja.md +3 -1
- package/README.md +3 -1
- package/package.json +3 -1
- package/skills/dek/SKILL.md +44 -0
- package/src/cli/annotations.ts +26 -0
- package/src/cli/commands.ts +66 -0
- package/src/cli/contract.ts +75 -4
- package/src/cli/files.ts +3 -3
- package/src/cli/flags.ts +1 -1
- package/src/cli/format.ts +3 -0
- package/src/cli/pptx.ts +20 -0
- package/src/cli/serve.ts +1 -1
- package/src/cli/show.ts +17 -2
- package/src/cli/text.ts +43 -0
- package/src/cli/theme.ts +7 -1
- package/src/core/annotation-rows.ts +176 -0
- package/src/core/annotations.ts +362 -0
- package/src/core/chrome.ts +3 -1
- package/src/core/diagnostic.ts +1 -0
- package/src/core/document.ts +17 -2
- package/src/core/html-scan.ts +54 -0
- package/src/core/html.ts +144 -14
- package/src/core/lint/slide-html.ts +92 -2
- package/src/core/lint.ts +8 -1
- package/src/core/live-protocol.ts +4 -1
- package/src/core/page.ts +7 -0
- package/src/core/playwright-module.d.ts +4 -1
- package/src/core/playwright-visual.ts +74 -1
- package/src/core/playwright.ts +51 -1
- package/src/core/pptx-package.ts +312 -0
- package/src/core/pptx-text.ts +363 -0
- package/src/core/pptx.ts +75 -0
- package/src/core/slide-measure.ts +1 -0
- package/src/core/sync.ts +13 -5
- package/src/core/theme-excerpt.ts +61 -9
- package/src/core/theme-facts.ts +12 -2
- package/src/core/zip.ts +77 -0
- package/src/runtime/annotate-pick.ts +144 -0
- package/src/runtime/annotate-place.ts +46 -0
- package/src/runtime/annotate-view.ts +752 -0
- package/src/runtime/annotate.ts +19 -0
- package/src/runtime/browser.ts +5 -1
- package/src/runtime/live.ts +1 -0
- package/src/runtime/player.ts +15 -7
- package/src/runtime/routes.ts +3 -0
- package/src/server/auth.ts +3 -1
- package/src/server/dev.ts +2 -1
- package/src/server/pages.ts +2 -1
- package/src/server/routes.ts +77 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for `@hajimism/dek`. From 0.1.0 on, release-please writes this file from the Conventional Commits on `main`.
|
|
4
4
|
|
|
5
|
+
## [0.2.2](https://github.com/hajimism/dek/compare/v0.2.1...v0.2.2) (2026-10-06)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **agents:** generate llms.txt and ship the official dek skill ([79d8abe](https://github.com/hajimism/dek/commit/79d8abea5d3c8822a865e2e58b687ec898e9f729))
|
|
11
|
+
* **agents:** generate llms.txt and ship the official dek skill ([b9b66ba](https://github.com/hajimism/dek/commit/b9b66ba43b556e35738a66ed492ad699556573ce))
|
|
12
|
+
* **dev:** keep annotations in .dek/ and list them with dekc annotations ([e22671f](https://github.com/hajimism/dek/commit/e22671f2816b517c62b827ca111aadd2edc02dfe))
|
|
13
|
+
* **dev:** keep annotations in .dek/ and list them with dekc annotations ([10195e3](https://github.com/hajimism/dek/commit/10195e3060d5754d36e91f5276b9964ed620ccb8))
|
|
14
|
+
* **lint:** keep runtime state classes out of a theme's vocabulary and slide markup ([83fd36c](https://github.com/hajimism/dek/commit/83fd36c8d2312c709d939bdc2e344d906b13ff82))
|
|
15
|
+
* **lint:** require every picture to say what it shows (DEK034) ([95ebbc2](https://github.com/hajimism/dek/commit/95ebbc2830b67a13288709d593b7d39c006ea806))
|
|
16
|
+
* **lint:** require every picture to say what it shows (DEK034) ([9926f4b](https://github.com/hajimism/dek/commit/9926f4b22c3d9be1abf6fd8681fdc101383c11b3))
|
|
17
|
+
* **pptx:** export a talk as PPTX with editable text over each slide ([12c2ec2](https://github.com/hajimism/dek/commit/12c2ec21b67e0b713e326d9300eff787a5a8ed16))
|
|
18
|
+
* **pptx:** export a talk as PPTX with editable text over each slide ([46d5468](https://github.com/hajimism/dek/commit/46d54686e5ce60d4718015a964791401a2345b3b))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* **agents:** write the agent guidance as what the checks and commands do ([97447ca](https://github.com/hajimism/dek/commit/97447cabf543c66cc6231b9b0e5a2378d7060615))
|
|
24
|
+
|
|
25
|
+
## [0.2.1](https://github.com/hajimism/dek/compare/v0.2.0...v0.2.1) (2026-10-06)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* **dev:** annotate slide elements and copy the notes for an agent ([8565bd5](https://github.com/hajimism/dek/commit/8565bd5a8d385a84c10d4fa78cab1b7460a86389))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Documentation
|
|
34
|
+
|
|
35
|
+
* **sample:** show annotate mode on a roadmap in with-agents ([c1d9c4c](https://github.com/hajimism/dek/commit/c1d9c4c6ddd6306748bdd5544b868fbf3d170a6c))
|
|
36
|
+
|
|
5
37
|
## [0.2.0](https://github.com/hajimism/dek/compare/v0.1.0...v0.2.0) (2026-10-01)
|
|
6
38
|
|
|
7
39
|
|
package/README.ja.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[ドキュメント](https://hajimism.github.io/dek/ja/) · [English README](./README.md)
|
|
8
8
|
|
|
9
|
-
dek は、ビルドシステムがコードを扱うように発表を扱う CLI です。ソースは Markdown で書くトークの台本で、`##` の見出しひとつが 1 枚の HTML スライドになります。同じソースから、尺を見積もり、どのスライドも lint して描画を測り、1 ファイルの HTML、PDF
|
|
9
|
+
dek は、ビルドシステムがコードを扱うように発表を扱う CLI です。ソースは Markdown で書くトークの台本で、`##` の見出しひとつが 1 枚の HTML スライドになります。同じソースから、尺を見積もり、どのスライドも lint して描画を測り、1 ファイルの HTML、PDF、文字を編集できる PowerPoint ファイル、読み上げ付きの動画をビルドします。プロジェクトが複数のデッキを持つので、テーマも規約も前回のトークから次のトークへ引き継がれます。AI エージェントと一緒に作ることを、最初から前提に設計しています。
|
|
10
10
|
|
|
11
11
|
## なぜ dek か
|
|
12
12
|
|
|
@@ -44,6 +44,8 @@ bunx dekc build # 3. dist/2026-04-vite.html — トーク全体がこの 1
|
|
|
44
44
|
|
|
45
45
|
台本を書いて `dekc` を叩けば、HTML を一行も書かずに同梱テーマで発表できます。
|
|
46
46
|
|
|
47
|
+
エージェントと作るなら、公式スキルを `npx skills add hajimism/dek` で、Claude Code なら `/plugin marketplace add hajimism/dek` と `/plugin install dek@dek` で入れてください。プロジェクトの外にいるエージェントは [llms.txt](https://hajimism.github.io/dek/llms.txt) から始められます。[AI エージェントと作る](https://hajimism.github.io/dek/ja/guide/ai.html)を参照してください。
|
|
48
|
+
|
|
47
49
|
続きは [はじめる](https://hajimism.github.io/dek/ja/guide/getting-started.html)へ。通しで 1 本作るなら [チュートリアル](https://hajimism.github.io/dek/ja/guide/tutorial.html)。コマンドの一覧は [CLI リファレンス](https://hajimism.github.io/dek/ja/reference/cli.html)にあります。
|
|
48
50
|
|
|
49
51
|
## サンプル
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[Documentation](https://hajimism.github.io/dek/) · [日本語 README](./README.ja.md)
|
|
8
8
|
|
|
9
|
-
dek treats a talk the way a build system treats code. The source is your talk script, in Markdown; every `##` heading becomes one HTML slide, and from the same source dek estimates the timing, lints and measures every slide, and builds a single HTML file, a PDF, and a narrated video. A project holds many decks, so the theme and conventions carry forward from one talk to the next. It is designed from the start to be worked on together with AI agents.
|
|
9
|
+
dek treats a talk the way a build system treats code. The source is your talk script, in Markdown; every `##` heading becomes one HTML slide, and from the same source dek estimates the timing, lints and measures every slide, and builds a single HTML file, a PDF, a PowerPoint file with its text editable, and a narrated video. A project holds many decks, so the theme and conventions carry forward from one talk to the next. It is designed from the start to be worked on together with AI agents.
|
|
10
10
|
|
|
11
11
|
## Why
|
|
12
12
|
|
|
@@ -44,6 +44,8 @@ bunx dekc build # 3. dist/2026-04-vite.html — the whole talk in one file
|
|
|
44
44
|
|
|
45
45
|
Write the script, run `dekc`, and you can present with the bundled theme without writing a line of HTML.
|
|
46
46
|
|
|
47
|
+
Working with an agent? Install the official skill with `npx skills add hajimism/dek`, or in Claude Code with `/plugin marketplace add hajimism/dek` and `/plugin install dek@dek`; an agent outside any project can start from [llms.txt](https://hajimism.github.io/dek/llms.txt). See [Working with AI Agents](https://hajimism.github.io/dek/guide/ai.html).
|
|
48
|
+
|
|
47
49
|
Continue with [Getting Started](https://hajimism.github.io/dek/guide/getting-started.html), or build a real deck end to end in the [Tutorial](https://hajimism.github.io/dek/guide/tutorial.html). Every command is listed in the [CLI reference](https://hajimism.github.io/dek/reference/cli.html).
|
|
48
50
|
|
|
49
51
|
## Sample
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hajimism/dek",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A build system for talks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hajime Nakagawa (https://github.com/hajimism)",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"src",
|
|
13
|
+
"skills",
|
|
13
14
|
"LICENSE",
|
|
14
15
|
"README.md",
|
|
15
16
|
"README.ja.md",
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"typecheck": "tsc --noEmit",
|
|
47
48
|
"knip": "knip",
|
|
48
49
|
"schema": "bun scripts/cli-schema.ts",
|
|
50
|
+
"llms": "bun scripts/llms.ts",
|
|
49
51
|
"format": "biome format --write .",
|
|
50
52
|
"lint": "biome lint .",
|
|
51
53
|
"check": "biome check --write .",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dek
|
|
3
|
+
description: Write and fix talks with dek, a build system for talks run as `dekc` on Bun - script first in script.md, one small HTML file per slide, checked by lint and screenshots until lint passes. Use when a project has dek.toml or decks/*/script.md, or when asked to make, fix, or hand over slides for a talk with dek.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dek
|
|
7
|
+
|
|
8
|
+
dek builds a talk from its script. `script.md` holds what will be said, in order; each `##` heading is one slide, written as one small HTML file, and lint checks everything a rule can measure. This skill is the loop. The installed dek tells you the rest, and it wins whenever the two disagree.
|
|
9
|
+
|
|
10
|
+
## Run it as `dekc`, on Bun
|
|
11
|
+
|
|
12
|
+
- The package is `@hajimism/dek`; the command is `dekc`. Never `bunx dek` or `npx dek`: `dek` on npm is an unrelated package that would be downloaded and run.
|
|
13
|
+
- dek runs on Bun. Inside a project, `bunx dekc`; outside one, `bunx @hajimism/dek`. Not `npx`, not `node`.
|
|
14
|
+
- No project yet: `bunx @hajimism/dek init <dir>`, then `cd <dir>`; where dek is already installed, `bunx dekc init <dir>` does the same.
|
|
15
|
+
- Decide from `--json`: every result command takes it. Branch on `ok`; an error carries a `hint` with the next command to run. Never scrape a verdict from the text output. `show` and `theme` print text meant to be read, and take `--json` too.
|
|
16
|
+
|
|
17
|
+
## First, ask the dek you have
|
|
18
|
+
|
|
19
|
+
1. `bunx dekc --version` and `bunx dekc help --agent`: every command and flag of the dek installed in this project. This file names few on purpose; use only what that list shows.
|
|
20
|
+
2. Read dek's block in `AGENTS.md`. dek rewrites it for the installed version: the conventions lint checks, how to check a slide, and what to do before reporting a deck as done. Follow it as written.
|
|
21
|
+
3. In the deck, `bunx dekc theme`: the classes, tokens, and layouts this deck's theme defines, and `bunx dekc theme <layout>` for a layout's markup. Use nothing else.
|
|
22
|
+
|
|
23
|
+
## Script first
|
|
24
|
+
|
|
25
|
+
Write or change `script.md` before any slide: `##` is a slide, `###` a beat, and paragraphs are what is said. `bunx dekc sync` writes a skeleton for each new section, and removes the skeletons nobody edited whose section is gone; it never touches a slide you edited. Keep the order and what is said in the script; a slide shows it. `bunx dekc ls` estimates the talk's length from the script against the `duration` in its frontmatter.
|
|
26
|
+
|
|
27
|
+
## One slide at a time
|
|
28
|
+
|
|
29
|
+
1. `bunx dekc show <slug>`: the slide's section of the script, its files, and the theme rules it uses.
|
|
30
|
+
2. Edit `slides/<slug>.html`, and `slides/<slug>.css` for what only this slide needs.
|
|
31
|
+
3. `bunx dekc check <slug> --shot --json`: fix every error, then open the shot it names. Read its `fill` before judging the shot.
|
|
32
|
+
4. Next slide. When all are written, `bunx dekc lint --visual --json`, which reports every problem in one run.
|
|
33
|
+
|
|
34
|
+
A hint that sends a fix to `theme.css` means every slide of the deck shares the problem: fix it in the deck's own `theme.css`.
|
|
35
|
+
|
|
36
|
+
## When to stop
|
|
37
|
+
|
|
38
|
+
- `bunx dekc lint --visual` reports no errors. Warnings do not fail it; read them and decide. A check listed in `skipped` did not run: say so, with its hint, rather than count it as passed.
|
|
39
|
+
- You have looked at `bunx dekc shot --sheet` and judged the deck's balance.
|
|
40
|
+
- Say what you could not judge, such as the argument and how the timing feels when spoken, and hand it back to the author. Passing lint means nothing measurable is wrong, not that the talk is good.
|
|
41
|
+
|
|
42
|
+
## When the human points at something
|
|
43
|
+
|
|
44
|
+
If the human marked beats while rehearsing or pointed at elements of a slide, `bunx dekc help --agent` lists the commands that read those notes, and `AGENTS.md` says how to use them. Leave clearing them to the human.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnnotationRow } from "../core/annotation-rows.ts";
|
|
2
|
+
import { clearAnnotations, listAnnotations } from "../core/annotations.ts";
|
|
3
|
+
import type { DeckTarget } from "./scope.ts";
|
|
4
|
+
|
|
5
|
+
export type AnnotationsCliResult =
|
|
6
|
+
| { action: "list"; annotations: AnnotationRow[] }
|
|
7
|
+
| { action: "clear"; cleared: number };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `dekc annotations`: the notes a human wrote on elements of the slides in annotate mode, each
|
|
11
|
+
* element where the file has it now. It reads the project's file, so no dev server is needed.
|
|
12
|
+
*/
|
|
13
|
+
export async function annotationsCommand({
|
|
14
|
+
project,
|
|
15
|
+
deck,
|
|
16
|
+
}: DeckTarget): Promise<AnnotationsCliResult> {
|
|
17
|
+
return { action: "list", annotations: listAnnotations(project.root, deck) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `dekc annotations clear`: drop the deck's notes, once the human has seen them dealt with. */
|
|
21
|
+
export async function clearAnnotationsCommand({
|
|
22
|
+
project,
|
|
23
|
+
deck,
|
|
24
|
+
}: DeckTarget): Promise<AnnotationsCliResult> {
|
|
25
|
+
return { action: "clear", cleared: clearAnnotations(project.root, deck) };
|
|
26
|
+
}
|
package/src/cli/commands.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { DeckScope, DecksTarget, ReadableDeck } from "./scope.ts";
|
|
|
9
9
|
import {
|
|
10
10
|
countSummary,
|
|
11
11
|
displayDiagnostics,
|
|
12
|
+
formatAnnotations,
|
|
12
13
|
formatBuild,
|
|
13
14
|
formatCheck,
|
|
14
15
|
formatCues,
|
|
@@ -540,6 +541,51 @@ export const COMMANDS = {
|
|
|
540
541
|
: data,
|
|
541
542
|
},
|
|
542
543
|
}),
|
|
544
|
+
annotations: result({
|
|
545
|
+
scope: "deck",
|
|
546
|
+
args: [],
|
|
547
|
+
flags: [],
|
|
548
|
+
usage: ["dekc annotations [deck]"],
|
|
549
|
+
summary:
|
|
550
|
+
"List the notes written on elements of the slides in annotate mode, by pressing a on the\ndev server's page: each element where the file has it now, and whether the slide changed since.",
|
|
551
|
+
group: "Slide",
|
|
552
|
+
overview: [
|
|
553
|
+
["dekc annotations", "list the notes written on slide elements in annotate mode (a)"],
|
|
554
|
+
["dekc annotations clear", "drop the deck's annotations"],
|
|
555
|
+
],
|
|
556
|
+
agent: [
|
|
557
|
+
"dekc annotations [clear] notes a human wrote on slide elements (a): path:line:column, text, shot, status open|edited|gone",
|
|
558
|
+
],
|
|
559
|
+
run: async ({ target }) => {
|
|
560
|
+
const { annotationsCommand } = await import("./annotations.ts");
|
|
561
|
+
return annotationsCommand(target);
|
|
562
|
+
},
|
|
563
|
+
subcommands: {
|
|
564
|
+
clear: {
|
|
565
|
+
args: [],
|
|
566
|
+
flags: [],
|
|
567
|
+
usage: ["dekc annotations [deck] clear"],
|
|
568
|
+
run: async ({ target }) => {
|
|
569
|
+
const { clearAnnotationsCommand } = await import("./annotations.ts");
|
|
570
|
+
return clearAnnotationsCommand(target);
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
output: {
|
|
575
|
+
text: formatAnnotations,
|
|
576
|
+
paths: (data, display) =>
|
|
577
|
+
data.action === "list"
|
|
578
|
+
? {
|
|
579
|
+
...data,
|
|
580
|
+
annotations: data.annotations.map((row) => ({
|
|
581
|
+
...row,
|
|
582
|
+
targets: row.targets.map((target) => ({ ...target, path: display(target.path) })),
|
|
583
|
+
changed: row.changed.map(display),
|
|
584
|
+
})),
|
|
585
|
+
}
|
|
586
|
+
: data,
|
|
587
|
+
},
|
|
588
|
+
}),
|
|
543
589
|
sync: result({
|
|
544
590
|
scope: "decks",
|
|
545
591
|
args: [],
|
|
@@ -738,6 +784,26 @@ export const COMMANDS = {
|
|
|
738
784
|
},
|
|
739
785
|
output: { text: formatPdf },
|
|
740
786
|
}),
|
|
787
|
+
pptx: result({
|
|
788
|
+
scope: "decks",
|
|
789
|
+
args: [],
|
|
790
|
+
flags: ["root-dist", "public"],
|
|
791
|
+
usage: ["dekc pptx [deck] [--root-dist] [--public]"],
|
|
792
|
+
summary:
|
|
793
|
+
"Write dist/<deck>.pptx, one slide per slide at its last beat: a picture of it with its text\nin text boxes to edit, and its script in the notes. Needs Playwright.",
|
|
794
|
+
group: "Output",
|
|
795
|
+
overview: [
|
|
796
|
+
["dekc pptx [--root-dist] [--public]", "write a PowerPoint file with editable text"],
|
|
797
|
+
],
|
|
798
|
+
agent: [
|
|
799
|
+
"dekc pptx [--root-dist] [--public] text editable over a picture of each slide; script in notes",
|
|
800
|
+
],
|
|
801
|
+
run: async ({ target, flags }) => {
|
|
802
|
+
const { pptxCommand } = await import("./pptx.ts");
|
|
803
|
+
return pptxCommand(target, { rootDist: flags["root-dist"], public: flags.public });
|
|
804
|
+
},
|
|
805
|
+
output: { text: formatPdf },
|
|
806
|
+
}),
|
|
741
807
|
help: {
|
|
742
808
|
kind: "help",
|
|
743
809
|
args: ["command?"],
|
package/src/cli/contract.ts
CHANGED
|
@@ -189,13 +189,25 @@ export const RESULT_FIELDS = {
|
|
|
189
189
|
html: z.string().nullable(),
|
|
190
190
|
css: z.string().nullable(),
|
|
191
191
|
ts: z.string().nullable(),
|
|
192
|
-
theme: z
|
|
192
|
+
theme: z
|
|
193
|
+
.string()
|
|
194
|
+
.nullable()
|
|
195
|
+
.describe(
|
|
196
|
+
"The rules of the deck's theme.css this slide uses: those its classes, layout, elements, and attributes match; with a slide script, every element and attribute rule under .slide.",
|
|
197
|
+
),
|
|
193
198
|
assets: paths,
|
|
194
199
|
ref: RefInfo.optional(),
|
|
195
200
|
}),
|
|
196
201
|
theme: z.strictObject({
|
|
197
202
|
path: z.string(),
|
|
198
|
-
classes: z
|
|
203
|
+
classes: z
|
|
204
|
+
.array(z.string())
|
|
205
|
+
.describe("The classes slide markup may use; what max_classes counts."),
|
|
206
|
+
stateClasses: z
|
|
207
|
+
.array(z.string())
|
|
208
|
+
.describe(
|
|
209
|
+
"State classes the theme selects that the player sets at runtime (is-current, is-shown); never written in markup.",
|
|
210
|
+
),
|
|
199
211
|
tokens: z.array(z.strictObject({ name: z.string(), value: z.string() })),
|
|
200
212
|
layouts: z.array(z.strictObject({ name: z.string(), example: z.string().optional() })),
|
|
201
213
|
layout: z.strictObject({ name: z.string(), example: z.string() }).optional(),
|
|
@@ -249,7 +261,9 @@ export const RESULT_FIELDS = {
|
|
|
249
261
|
.optional(),
|
|
250
262
|
}),
|
|
251
263
|
shot: z.strictObject({
|
|
252
|
-
shots: z
|
|
264
|
+
shots: z
|
|
265
|
+
.array(shotFile)
|
|
266
|
+
.describe("Each still or morph frame; with --motion, empty: the frames are in motion."),
|
|
253
267
|
sheets: paths.optional().describe("Contact sheets, from --sheet or --motion."),
|
|
254
268
|
motion: z
|
|
255
269
|
.array(
|
|
@@ -260,7 +274,8 @@ export const RESULT_FIELDS = {
|
|
|
260
274
|
),
|
|
261
275
|
}),
|
|
262
276
|
)
|
|
263
|
-
.optional()
|
|
277
|
+
.optional()
|
|
278
|
+
.describe("With --motion, each beat in order with its frames, the settled end last."),
|
|
264
279
|
}),
|
|
265
280
|
mv: z.strictObject({
|
|
266
281
|
from: z.string(),
|
|
@@ -294,6 +309,61 @@ export const RESULT_FIELDS = {
|
|
|
294
309
|
}),
|
|
295
310
|
z.strictObject({ action: z.literal("clear"), cleared: z.number().int() }),
|
|
296
311
|
]),
|
|
312
|
+
annotations: z.union([
|
|
313
|
+
z.strictObject({
|
|
314
|
+
action: z.literal("list"),
|
|
315
|
+
annotations: z.array(
|
|
316
|
+
z.strictObject({
|
|
317
|
+
number: z.number().int().describe("As the dev page's markers and Copy number it."),
|
|
318
|
+
id: z.string(),
|
|
319
|
+
slug: z.string(),
|
|
320
|
+
step: z
|
|
321
|
+
.string()
|
|
322
|
+
.describe("The stop as `dekc shot --step` takes it; 0 as the slide arrives."),
|
|
323
|
+
status: z
|
|
324
|
+
.enum(["open", "edited", "gone"])
|
|
325
|
+
.describe(
|
|
326
|
+
"edited once the slide's own html, css, or ts changed since the note; gone once none of its elements is on the slide.",
|
|
327
|
+
),
|
|
328
|
+
text: z.string().describe("What the human wrote."),
|
|
329
|
+
targets: z.array(
|
|
330
|
+
z.strictObject({
|
|
331
|
+
path: z.string(),
|
|
332
|
+
line: z
|
|
333
|
+
.number()
|
|
334
|
+
.int()
|
|
335
|
+
.describe("Where the start tag is now; where it was last found when not found."),
|
|
336
|
+
column: z.number().int().describe("1-based, in UTF-16 units as editors count."),
|
|
337
|
+
found: z.boolean(),
|
|
338
|
+
name: z
|
|
339
|
+
.string()
|
|
340
|
+
.describe(
|
|
341
|
+
"The tag and its classes as written where it was last found, as div.chevron.",
|
|
342
|
+
),
|
|
343
|
+
was: z.string().describe("Its text as written when the note was written, cut short."),
|
|
344
|
+
text: z.string().nullable().describe("Its text as written now; null when not found."),
|
|
345
|
+
box: z
|
|
346
|
+
.strictObject({
|
|
347
|
+
x: z.number(),
|
|
348
|
+
y: z.number(),
|
|
349
|
+
width: z.number(),
|
|
350
|
+
height: z.number(),
|
|
351
|
+
})
|
|
352
|
+
.describe("On the slide in logical pixels, when the note was written."),
|
|
353
|
+
point: z
|
|
354
|
+
.strictObject({ x: z.number(), y: z.number() })
|
|
355
|
+
.optional()
|
|
356
|
+
.describe("Where the slide itself was clicked."),
|
|
357
|
+
}),
|
|
358
|
+
),
|
|
359
|
+
changed: paths.describe("The slide's own files that changed since the note."),
|
|
360
|
+
shot: z.string().describe("The command that shows the slide at the note's beat."),
|
|
361
|
+
createdAt: z.string(),
|
|
362
|
+
}),
|
|
363
|
+
),
|
|
364
|
+
}),
|
|
365
|
+
z.strictObject({ action: z.literal("clear"), cleared: z.number().int() }),
|
|
366
|
+
]),
|
|
297
367
|
sync: z.strictObject({ created: paths, updated: paths, removed: paths, kept: paths }),
|
|
298
368
|
lint: z.strictObject({ diagnostics, skipped }),
|
|
299
369
|
cues: z.strictObject({
|
|
@@ -342,6 +412,7 @@ export const RESULT_FIELDS = {
|
|
|
342
412
|
credits: z.string().optional(),
|
|
343
413
|
}),
|
|
344
414
|
pdf: z.strictObject({ outs: paths }),
|
|
415
|
+
pptx: z.strictObject({ outs: paths }),
|
|
345
416
|
} satisfies Record<ResultCommand, FieldsSchema>;
|
|
346
417
|
|
|
347
418
|
type FieldsSchema = z.ZodObject | z.ZodUnion<readonly z.ZodObject[]>;
|
package/src/cli/files.ts
CHANGED
|
@@ -18,10 +18,10 @@ export function defaultToml(): string {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* What dek keeps for itself under .dek/ while it runs: the dev server's lock,
|
|
22
|
-
* while rehearsing.
|
|
21
|
+
* What dek keeps for itself under .dek/ while it runs: the dev server's lock, the marks left
|
|
22
|
+
* while rehearsing, and the notes written in annotate mode. None belongs in the repository.
|
|
23
23
|
*/
|
|
24
|
-
const LOCAL_STATE = [".dek/server.json", ".dek/marks.json"];
|
|
24
|
+
const LOCAL_STATE = [".dek/server.json", ".dek/marks.json", ".dek/annotations.json"];
|
|
25
25
|
|
|
26
26
|
export function defaultGitignore(): string {
|
|
27
27
|
return `# dek
|
package/src/cli/flags.ts
CHANGED
|
@@ -89,7 +89,7 @@ export const FLAGS = {
|
|
|
89
89
|
},
|
|
90
90
|
public: {
|
|
91
91
|
type: "boolean",
|
|
92
|
-
text: "
|
|
92
|
+
text: "for anyone the file reaches: the script without its stage directions and comments",
|
|
93
93
|
},
|
|
94
94
|
} as const satisfies Record<string, FlagSpec>;
|
|
95
95
|
|
package/src/cli/format.ts
CHANGED
|
@@ -141,6 +141,9 @@ function formatLiveEvent(event: LiveEvent, cwd: string | undefined): string | nu
|
|
|
141
141
|
return event.diagnostics.length === 0 ? null : formatDiagnostics(event.diagnostics, { cwd });
|
|
142
142
|
case "timeline":
|
|
143
143
|
return "timeline";
|
|
144
|
+
// The page that changed them already shows it; the terminal has nothing to add.
|
|
145
|
+
case "annotations":
|
|
146
|
+
return null;
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
|
package/src/cli/pptx.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pptxDeck } from "../core/pptx.ts";
|
|
2
|
+
import type { DecksTarget } from "./scope.ts";
|
|
3
|
+
|
|
4
|
+
/** One file per deck, a single deck included, as `dekc pdf` answers. */
|
|
5
|
+
export type PptxCliResult = { outs: string[] };
|
|
6
|
+
|
|
7
|
+
export async function pptxCommand(
|
|
8
|
+
{ project, decks }: DecksTarget,
|
|
9
|
+
options: { rootDist?: boolean; public?: boolean } = {},
|
|
10
|
+
): Promise<PptxCliResult> {
|
|
11
|
+
const outs: string[] = [];
|
|
12
|
+
for (const entry of decks) {
|
|
13
|
+
const done = await pptxDeck(
|
|
14
|
+
{ project, deck: entry },
|
|
15
|
+
{ rootDist: options.rootDist, public: options.public },
|
|
16
|
+
);
|
|
17
|
+
outs.push(done.outPath);
|
|
18
|
+
}
|
|
19
|
+
return { outs };
|
|
20
|
+
}
|
package/src/cli/serve.ts
CHANGED
|
@@ -121,5 +121,5 @@ export function devBanner(
|
|
|
121
121
|
remoteUrls: string[],
|
|
122
122
|
options: Parameters<typeof remoteBanner>[2],
|
|
123
123
|
): string {
|
|
124
|
-
return `${remoteBanner(url, remoteUrls, options)}\n\np presenter view · s slide rail · Ctrl-C stops the server`;
|
|
124
|
+
return `${remoteBanner(url, remoteUrls, options)}\n\np presenter view · s slide rail · a annotate · Ctrl-C stops the server`;
|
|
125
125
|
}
|
package/src/cli/show.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { classifyAssetRef } from "../core/assets.ts";
|
|
2
2
|
import { cssUrls, parseCss } from "../core/css.ts";
|
|
3
3
|
import { deckPaths } from "../core/deck-paths.ts";
|
|
4
|
-
import { scanSlideHtml } from "../core/html-scan.ts";
|
|
4
|
+
import { type HtmlScan, scanSlideHtml } from "../core/html-scan.ts";
|
|
5
5
|
import { sectionChunks, splitLines } from "../core/lines.ts";
|
|
6
6
|
import { readDeckFile } from "../core/resolve.ts";
|
|
7
7
|
import type { Section } from "../core/schema.ts";
|
|
@@ -24,7 +24,11 @@ export type ShowResult = {
|
|
|
24
24
|
css: string | null;
|
|
25
25
|
/** `slides/<slug>.ts`. */
|
|
26
26
|
ts: string | null;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* The rules of the deck's theme.css this slide uses, for the classes, layout, elements, and
|
|
29
|
+
* attributes its markup has; with a slide script, every element and attribute rule under
|
|
30
|
+
* `.slide`. Null when the deck has no theme.css.
|
|
31
|
+
*/
|
|
28
32
|
theme: string | null;
|
|
29
33
|
/** Deck-relative paths of the files the slide references that exist. */
|
|
30
34
|
assets: string[];
|
|
@@ -52,6 +56,8 @@ export function showCommand({ deck, ref }: ReadableDeck, slug: string): ShowResu
|
|
|
52
56
|
classes: scan?.classes ?? [],
|
|
53
57
|
...(scan?.layout !== undefined ? { layout: scan.layout } : {}),
|
|
54
58
|
...(sheet ? { css: sheet } : {}),
|
|
59
|
+
// A slide script may add elements and attributes the markup lacks: keep their rules.
|
|
60
|
+
...(scan && ts === null ? { markup: slideMarkup(scan) } : {}),
|
|
55
61
|
});
|
|
56
62
|
|
|
57
63
|
const refs = [
|
|
@@ -73,6 +79,15 @@ export function showCommand({ deck, ref }: ReadableDeck, slug: string): ShowResu
|
|
|
73
79
|
};
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
/** The tags and attribute names of the slide and the elements inside it. */
|
|
83
|
+
function slideMarkup(scan: HtmlScan): { tags: string[]; attributes: string[] } {
|
|
84
|
+
const elements = scan.elements.filter((element) => element.inSlide);
|
|
85
|
+
return {
|
|
86
|
+
tags: elements.map((element) => element.tag),
|
|
87
|
+
attributes: elements.flatMap((element) => element.attributes.map((attr) => attr.name)),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
76
91
|
/**
|
|
77
92
|
* Local references that name a file inside the deck, as sorted deck-relative
|
|
78
93
|
* paths. theme.css and the slide's own files are left out: show returns them.
|
package/src/cli/text.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// The pieces the dev server's log shares, such as diagnostics and tables, are in format.ts.
|
|
3
3
|
import type { Diagnostic, SkippedCheck } from "../core/diagnostic.ts";
|
|
4
4
|
import { formatClock } from "../core/timing.ts";
|
|
5
|
+
import type { AnnotationsCliResult } from "./annotations.ts";
|
|
5
6
|
import type { BuildCliResult } from "./build.ts";
|
|
6
7
|
import type { CheckCliResult } from "./check.ts";
|
|
7
8
|
import type { CuesResult } from "./cues.ts";
|
|
@@ -171,6 +172,41 @@ export function formatMarks(data: MarksCliResult): string {
|
|
|
171
172
|
.join("\n");
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Each note by its number, as the page's markers show it, with its slide and beat, each element
|
|
177
|
+
* at its line the way an editor jumps to it, the human's words, and the shot that shows it.
|
|
178
|
+
*/
|
|
179
|
+
export function formatAnnotations(data: AnnotationsCliResult): string {
|
|
180
|
+
if (data.action === "clear") {
|
|
181
|
+
return data.cleared === 1 ? "cleared 1 annotation" : `cleared ${data.cleared} annotations`;
|
|
182
|
+
}
|
|
183
|
+
if (data.annotations.length === 0) {
|
|
184
|
+
return "no annotations: press a on the dev server's page to point at an element and write a note";
|
|
185
|
+
}
|
|
186
|
+
return data.annotations
|
|
187
|
+
.flatMap((row) => {
|
|
188
|
+
const beat = row.step === "0" ? row.slug : `${row.slug} › ${row.step}`;
|
|
189
|
+
const status = row.status === "open" ? "" : ` (${row.status})`;
|
|
190
|
+
const targets = row.targets.map((target) => {
|
|
191
|
+
const where = `${target.path}:${target.line}:${target.column}`;
|
|
192
|
+
if (!target.found) {
|
|
193
|
+
return ` ${where} ${target.name} (not found)`;
|
|
194
|
+
}
|
|
195
|
+
const text =
|
|
196
|
+
target.text === "" || target.text === null ? "" : ` ${JSON.stringify(target.text)}`;
|
|
197
|
+
const was = target.text !== target.was ? ` (was ${JSON.stringify(target.was)})` : "";
|
|
198
|
+
return ` ${where} ${target.name}${text}${was}`;
|
|
199
|
+
});
|
|
200
|
+
const words = row.text
|
|
201
|
+
.trim()
|
|
202
|
+
.split(/\r?\n/)
|
|
203
|
+
.filter((line) => line.trim() !== "")
|
|
204
|
+
.map((line) => ` > ${line}`);
|
|
205
|
+
return [`${row.number} ${beat}${status}`, ...targets, ...words, ` ${row.shot}`];
|
|
206
|
+
})
|
|
207
|
+
.join("\n");
|
|
208
|
+
}
|
|
209
|
+
|
|
174
210
|
/** The contact sheets when there are any, else each screenshot. */
|
|
175
211
|
export function formatShot(data: ShotCliResult): string {
|
|
176
212
|
return (data.sheets ?? data.shots.map((shot) => shot.path)).join("\n");
|
|
@@ -307,6 +343,13 @@ export function formatTheme(data: ThemeResult): string {
|
|
|
307
343
|
"CLASSES",
|
|
308
344
|
` ${data.classes.join(" ")}`,
|
|
309
345
|
"",
|
|
346
|
+
...(data.stateClasses.length > 0
|
|
347
|
+
? [
|
|
348
|
+
"STATE CLASSES (the player sets these; select them in CSS, never write them in markup)",
|
|
349
|
+
` ${data.stateClasses.join(" ")}`,
|
|
350
|
+
"",
|
|
351
|
+
]
|
|
352
|
+
: []),
|
|
310
353
|
"TOKENS",
|
|
311
354
|
...data.tokens.map((token) => ` ${token.name.padEnd(width)} ${token.value}`),
|
|
312
355
|
"",
|
package/src/cli/theme.ts
CHANGED
|
@@ -9,7 +9,10 @@ import type { ReadableDeck, RefInfo } from "./scope.ts";
|
|
|
9
9
|
export type ThemeResult = {
|
|
10
10
|
/** The deck's theme.css: the one lint and the slides use. */
|
|
11
11
|
path: string;
|
|
12
|
+
/** The classes slide markup may use; what `max_classes` counts. */
|
|
12
13
|
classes: string[];
|
|
14
|
+
/** The state classes the theme selects that the player sets at runtime; never in markup. */
|
|
15
|
+
stateClasses: string[];
|
|
13
16
|
/** The tokens every slide can `var()`. */
|
|
14
17
|
tokens: CssToken[];
|
|
15
18
|
layouts: ThemeLayout[];
|
|
@@ -28,10 +31,13 @@ export function themeCommand({ deck, ref }: ReadableDeck, name?: string): ThemeR
|
|
|
28
31
|
hint: "copy the project theme.css into the deck, or run `dekc new <name>` for a fresh deck",
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
|
-
const { classes, layouts, tokens } = themeFacts(
|
|
34
|
+
const { classes, stateClasses, layouts, tokens } = themeFacts(
|
|
35
|
+
parseCss(readDeckFile(deck.dir, path) ?? ""),
|
|
36
|
+
);
|
|
32
37
|
const result: ThemeResult = {
|
|
33
38
|
path,
|
|
34
39
|
classes: [...classes].sort(),
|
|
40
|
+
stateClasses: [...stateClasses].sort(),
|
|
35
41
|
tokens,
|
|
36
42
|
layouts,
|
|
37
43
|
...(ref ? { ref } : {}),
|