@classic-homes/slides 0.2.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/README.md +129 -0
- package/dist/chunk-CKVKX2ZD.js +76 -0
- package/dist/chunk-CKVKX2ZD.js.map +1 -0
- package/dist/chunk-TM3TNVHB.js +1520 -0
- package/dist/chunk-TM3TNVHB.js.map +1 -0
- package/dist/chunk-TXNQKBWJ.js +69 -0
- package/dist/chunk-TXNQKBWJ.js.map +1 -0
- package/dist/chunk-VONVGYRL.js +52 -0
- package/dist/chunk-VONVGYRL.js.map +1 -0
- package/dist/cli.js +167 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +190 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-5R6YD3QW.js +320 -0
- package/dist/mcp-5R6YD3QW.js.map +1 -0
- package/dist/node.d.ts +25 -0
- package/dist/node.js +19 -0
- package/dist/node.js.map +1 -0
- package/dist/render-CZ4g2fa9.d.ts +136 -0
- package/package.json +69 -0
- package/starters/basic.yaml +69 -0
- package/starters/journey.yaml +85 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeckSchema,
|
|
3
|
+
LAYOUTS,
|
|
4
|
+
baseSlide,
|
|
5
|
+
getLayout
|
|
6
|
+
} from "./chunk-TM3TNVHB.js";
|
|
7
|
+
|
|
8
|
+
// src/catalog.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
11
|
+
function listLayouts() {
|
|
12
|
+
return LAYOUTS.map((l) => ({ name: l.name, description: l.description, whenToUse: l.whenToUse }));
|
|
13
|
+
}
|
|
14
|
+
function describeLayout(name) {
|
|
15
|
+
const layout = getLayout(name);
|
|
16
|
+
if (!layout) return void 0;
|
|
17
|
+
const schema = z.object({ layout: z.literal(layout.name), ...layout.shape, ...baseSlide });
|
|
18
|
+
return {
|
|
19
|
+
name: layout.name,
|
|
20
|
+
description: layout.description,
|
|
21
|
+
whenToUse: layout.whenToUse,
|
|
22
|
+
defaults: layout.defaults ?? {},
|
|
23
|
+
schema: zodToJsonSchema(schema, { $refStrategy: "none" }),
|
|
24
|
+
example: layout.example
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function deckJsonSchema() {
|
|
28
|
+
return zodToJsonSchema(DeckSchema, { name: "ClassicDeck", $refStrategy: "none" });
|
|
29
|
+
}
|
|
30
|
+
var AUTHORING_GUIDE = `# Writing a Classic Homes deck
|
|
31
|
+
|
|
32
|
+
A deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide
|
|
33
|
+
picks a **layout** and fills its fields. Layouts own all styling \u2014 there is no way to
|
|
34
|
+
set colours, fonts or sizes directly, which is what keeps every deck on-brand.
|
|
35
|
+
|
|
36
|
+
## Principles
|
|
37
|
+
- **One idea per slide.** The headline states the point. Detail goes in \`notes\`.
|
|
38
|
+
- **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.
|
|
39
|
+
\`validate\` warns when a slide gets crowded \u2014 split it rather than shrinking text.
|
|
40
|
+
- **Speaker notes on every content slide.** Presenters press N to see them.
|
|
41
|
+
- **Real photos over decoration.** Every image needs \`alt\` text.
|
|
42
|
+
- **Tracks** colour the accent: \`value\` (red, default) for the homeowner/value story,
|
|
43
|
+
\`build\` (teal) for construction/process, \`neutral\` (navy) for framing slides.
|
|
44
|
+
- **Backgrounds**: \`paper\` (default) for content; solid \`navy\`/\`red\`/\`teal\`/\`plum\`/\`grey\`/\`stone\`
|
|
45
|
+
for section dividers and moments of emphasis. Don't put dense content on solid grounds.
|
|
46
|
+
|
|
47
|
+
## Inline text
|
|
48
|
+
\`**bold**\`, \`*highlight*\` (accent colour), \`\\\`code\\\`\`, \`[label](https://\u2026)\`. No HTML.
|
|
49
|
+
|
|
50
|
+
## Journey decks
|
|
51
|
+
Set \`journey.steps\` at deck level to get a numbered agenda. Then:
|
|
52
|
+
- a \`journey\` slide shows the full map,
|
|
53
|
+
- \`step: 3\` on any slide draws the vertical STEP rail,
|
|
54
|
+
- \`showJourney: true\` adds the compact "you are here" strip (great on \`section\` slides).
|
|
55
|
+
|
|
56
|
+
## Typical shape
|
|
57
|
+
1. \`title\`
|
|
58
|
+
2. \`statement\` or \`journey\` (why we're here / the agenda)
|
|
59
|
+
3. per part: \`section\` \u2192 2\u20135 content slides (\`bullets\`, \`split\`, \`cards\`, \`stats\`, \`photos\`, \`walkthrough\`)
|
|
60
|
+
4. \`qr\` (where to go next) \u2192 \`contacts\` \u2192 closing \`statement\`
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
listLayouts,
|
|
65
|
+
describeLayout,
|
|
66
|
+
deckJsonSchema,
|
|
67
|
+
AUTHORING_GUIDE
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=chunk-TXNQKBWJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["import { z } from 'zod';\nimport { zodToJsonSchema } from 'zod-to-json-schema';\nimport { LAYOUTS, getLayout } from './layouts/index.js';\nimport { baseSlide } from './schema/common.js';\nimport { DeckSchema } from './schema/deck.js';\n\nexport interface LayoutSummary {\n name: string;\n description: string;\n whenToUse: string;\n}\n\nexport function listLayouts(): LayoutSummary[] {\n return LAYOUTS.map((l) => ({ name: l.name, description: l.description, whenToUse: l.whenToUse }));\n}\n\n/** Full description of one layout: fields as JSON Schema plus a working example. */\nexport function describeLayout(name: string) {\n const layout = getLayout(name);\n if (!layout) return undefined;\n const schema = z.object({ layout: z.literal(layout.name), ...layout.shape, ...baseSlide });\n return {\n name: layout.name,\n description: layout.description,\n whenToUse: layout.whenToUse,\n defaults: layout.defaults ?? {},\n schema: zodToJsonSchema(schema, { $refStrategy: 'none' }),\n example: layout.example,\n };\n}\n\n/** JSON Schema for a whole deck (editor tooling, `$schema` in deck files). */\nexport function deckJsonSchema() {\n return zodToJsonSchema(DeckSchema, { name: 'ClassicDeck', $refStrategy: 'none' });\n}\n\n/** Authoring guidance shared by the CLI, MCP server and docs. */\nexport const AUTHORING_GUIDE = `# Writing a Classic Homes deck\n\nA deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide\npicks a **layout** and fills its fields. Layouts own all styling — there is no way to\nset colours, fonts or sizes directly, which is what keeps every deck on-brand.\n\n## Principles\n- **One idea per slide.** The headline states the point. Detail goes in \\`notes\\`.\n- **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.\n \\`validate\\` warns when a slide gets crowded — split it rather than shrinking text.\n- **Speaker notes on every content slide.** Presenters press N to see them.\n- **Real photos over decoration.** Every image needs \\`alt\\` text.\n- **Tracks** colour the accent: \\`value\\` (red, default) for the homeowner/value story,\n \\`build\\` (teal) for construction/process, \\`neutral\\` (navy) for framing slides.\n- **Backgrounds**: \\`paper\\` (default) for content; solid \\`navy\\`/\\`red\\`/\\`teal\\`/\\`plum\\`/\\`grey\\`/\\`stone\\`\n for section dividers and moments of emphasis. Don't put dense content on solid grounds.\n\n## Inline text\n\\`**bold**\\`, \\`*highlight*\\` (accent colour), \\`\\\\\\`code\\\\\\`\\`, \\`[label](https://…)\\`. No HTML.\n\n## Journey decks\nSet \\`journey.steps\\` at deck level to get a numbered agenda. Then:\n- a \\`journey\\` slide shows the full map,\n- \\`step: 3\\` on any slide draws the vertical STEP rail,\n- \\`showJourney: true\\` adds the compact \"you are here\" strip (great on \\`section\\` slides).\n\n## Typical shape\n1. \\`title\\`\n2. \\`statement\\` or \\`journey\\` (why we're here / the agenda)\n3. per part: \\`section\\` → 2–5 content slides (\\`bullets\\`, \\`split\\`, \\`cards\\`, \\`stats\\`, \\`photos\\`, \\`walkthrough\\`)\n4. \\`qr\\` (where to go next) → \\`contacts\\` → closing \\`statement\\`\n`;\n"],"mappings":";;;;;;;;AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAWzB,SAAS,cAA+B;AAC7C,SAAO,QAAQ,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,EAAE,aAAa,WAAW,EAAE,UAAU,EAAE;AAClG;AAGO,SAAS,eAAe,MAAc;AAC3C,QAAM,SAAS,UAAU,IAAI;AAC7B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,OAAO,IAAI,GAAG,GAAG,OAAO,OAAO,GAAG,UAAU,CAAC;AACzF,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,aAAa,OAAO;AAAA,IACpB,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO,YAAY,CAAC;AAAA,IAC9B,QAAQ,gBAAgB,QAAQ,EAAE,cAAc,OAAO,CAAC;AAAA,IACxD,SAAS,OAAO;AAAA,EAClB;AACF;AAGO,SAAS,iBAAiB;AAC/B,SAAO,gBAAgB,YAAY,EAAE,MAAM,eAAe,cAAc,OAAO,CAAC;AAClF;AAGO,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/edit.ts
|
|
2
|
+
function findSlide(deck, ref) {
|
|
3
|
+
const index = typeof ref === "number" || /^\d+$/.test(ref) ? Number(ref) - 1 : deck.slides.findIndex((s) => s.id === ref);
|
|
4
|
+
if (index < 0 || index >= deck.slides.length) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
`No slide "${ref}". The deck has ${deck.slides.length} slides; use a 1-based number or a slide id.`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
return index;
|
|
10
|
+
}
|
|
11
|
+
function addSlide(deck, slide, position) {
|
|
12
|
+
const at = position === void 0 ? deck.slides.length : Math.max(0, Math.min(deck.slides.length, position - 1));
|
|
13
|
+
deck.slides.splice(at, 0, slide);
|
|
14
|
+
return at + 1;
|
|
15
|
+
}
|
|
16
|
+
function updateSlide(deck, ref, patch) {
|
|
17
|
+
const i = findSlide(deck, ref);
|
|
18
|
+
const next = patch.layout && patch.layout !== deck.slides[i].layout ? {} : { ...deck.slides[i] };
|
|
19
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
20
|
+
if (v === null) delete next[k];
|
|
21
|
+
else next[k] = v;
|
|
22
|
+
}
|
|
23
|
+
deck.slides[i] = next;
|
|
24
|
+
return i + 1;
|
|
25
|
+
}
|
|
26
|
+
function moveSlide(deck, ref, to) {
|
|
27
|
+
const from = findSlide(deck, ref);
|
|
28
|
+
const [slide] = deck.slides.splice(from, 1);
|
|
29
|
+
const at = Math.max(0, Math.min(deck.slides.length, to - 1));
|
|
30
|
+
deck.slides.splice(at, 0, slide);
|
|
31
|
+
return at + 1;
|
|
32
|
+
}
|
|
33
|
+
function removeSlide(deck, ref) {
|
|
34
|
+
const i = findSlide(deck, ref);
|
|
35
|
+
return deck.slides.splice(i, 1)[0];
|
|
36
|
+
}
|
|
37
|
+
function outline(deck) {
|
|
38
|
+
return deck.slides.map((s, i) => {
|
|
39
|
+
const label = s.title ?? s.quote ?? s.kicker ?? "";
|
|
40
|
+
return `${i + 1}. [${s.layout}]${s.id ? ` #${s.id}` : ""} ${label}`.trim();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
findSlide,
|
|
46
|
+
addSlide,
|
|
47
|
+
updateSlide,
|
|
48
|
+
moveSlide,
|
|
49
|
+
removeSlide,
|
|
50
|
+
outline
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=chunk-VONVGYRL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/edit.ts"],"sourcesContent":["/**\n * Slide-level edits on a deck object. Pure functions shared by the MCP\n * server and (later) the hosted editor, so agents change one slide at a\n * time instead of rewriting whole files.\n */\nexport interface EditableDeck {\n slides: Record<string, unknown>[];\n [key: string]: unknown;\n}\n\n/** A slide reference: its `id`, or its 1-based position. */\nexport type SlideRef = string | number;\n\nexport function findSlide(deck: EditableDeck, ref: SlideRef): number {\n const index =\n typeof ref === 'number' || /^\\d+$/.test(ref)\n ? Number(ref) - 1\n : deck.slides.findIndex((s) => s.id === ref);\n if (index < 0 || index >= deck.slides.length) {\n throw new Error(\n `No slide \"${ref}\". The deck has ${deck.slides.length} slides; use a 1-based number or a slide id.`\n );\n }\n return index;\n}\n\n/** Inserts a slide. `position` is 1-based; omitted appends. Returns the new slide's position. */\nexport function addSlide(\n deck: EditableDeck,\n slide: Record<string, unknown>,\n position?: number\n): number {\n const at =\n position === undefined\n ? deck.slides.length\n : Math.max(0, Math.min(deck.slides.length, position - 1));\n deck.slides.splice(at, 0, slide);\n return at + 1;\n}\n\n/** Shallow-merges fields into a slide. A field set to null is removed. Changing `layout` replaces the slide. */\nexport function updateSlide(\n deck: EditableDeck,\n ref: SlideRef,\n patch: Record<string, unknown>\n): number {\n const i = findSlide(deck, ref);\n const next = patch.layout && patch.layout !== deck.slides[i].layout ? {} : { ...deck.slides[i] };\n for (const [k, v] of Object.entries(patch)) {\n if (v === null) delete (next as Record<string, unknown>)[k];\n else (next as Record<string, unknown>)[k] = v;\n }\n deck.slides[i] = next;\n return i + 1;\n}\n\nexport function moveSlide(deck: EditableDeck, ref: SlideRef, to: number): number {\n const from = findSlide(deck, ref);\n const [slide] = deck.slides.splice(from, 1);\n const at = Math.max(0, Math.min(deck.slides.length, to - 1));\n deck.slides.splice(at, 0, slide);\n return at + 1;\n}\n\nexport function removeSlide(deck: EditableDeck, ref: SlideRef): Record<string, unknown> {\n const i = findSlide(deck, ref);\n return deck.slides.splice(i, 1)[0];\n}\n\n/** One-line outline of each slide, for agents and CLIs. */\nexport function outline(deck: EditableDeck): string[] {\n return deck.slides.map((s, i) => {\n const label = (s.title ?? s.quote ?? s.kicker ?? '') as string;\n return `${i + 1}. [${s.layout}]${s.id ? ` #${s.id}` : ''} ${label}`.trim();\n });\n}\n"],"mappings":";AAaO,SAAS,UAAU,MAAoB,KAAuB;AACnE,QAAM,QACJ,OAAO,QAAQ,YAAY,QAAQ,KAAK,GAAG,IACvC,OAAO,GAAG,IAAI,IACd,KAAK,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG;AAC/C,MAAI,QAAQ,KAAK,SAAS,KAAK,OAAO,QAAQ;AAC5C,UAAM,IAAI;AAAA,MACR,aAAa,GAAG,mBAAmB,KAAK,OAAO,MAAM;AAAA,IACvD;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,SACd,MACA,OACA,UACQ;AACR,QAAM,KACJ,aAAa,SACT,KAAK,OAAO,SACZ,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,QAAQ,WAAW,CAAC,CAAC;AAC5D,OAAK,OAAO,OAAO,IAAI,GAAG,KAAK;AAC/B,SAAO,KAAK;AACd;AAGO,SAAS,YACd,MACA,KACA,OACQ;AACR,QAAM,IAAI,UAAU,MAAM,GAAG;AAC7B,QAAM,OAAO,MAAM,UAAU,MAAM,WAAW,KAAK,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,EAAE;AAC/F,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAI,MAAM,KAAM,QAAQ,KAAiC,CAAC;AAAA,QACrD,CAAC,KAAiC,CAAC,IAAI;AAAA,EAC9C;AACA,OAAK,OAAO,CAAC,IAAI;AACjB,SAAO,IAAI;AACb;AAEO,SAAS,UAAU,MAAoB,KAAe,IAAoB;AAC/E,QAAM,OAAO,UAAU,MAAM,GAAG;AAChC,QAAM,CAAC,KAAK,IAAI,KAAK,OAAO,OAAO,MAAM,CAAC;AAC1C,QAAM,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,CAAC,CAAC;AAC3D,OAAK,OAAO,OAAO,IAAI,GAAG,KAAK;AAC/B,SAAO,KAAK;AACd;AAEO,SAAS,YAAY,MAAoB,KAAwC;AACtF,QAAM,IAAI,UAAU,MAAM,GAAG;AAC7B,SAAO,KAAK,OAAO,OAAO,GAAG,CAAC,EAAE,CAAC;AACnC;AAGO,SAAS,QAAQ,MAA8B;AACpD,SAAO,KAAK,OAAO,IAAI,CAAC,GAAG,MAAM;AAC/B,UAAM,QAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU;AACjD,WAAO,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,EAAE,KAAK,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,KAAK,GAAG,KAAK;AAAA,EAC3E,CAAC;AACH;","names":[]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AUTHORING_GUIDE,
|
|
4
|
+
deckJsonSchema,
|
|
5
|
+
describeLayout,
|
|
6
|
+
listLayouts
|
|
7
|
+
} from "./chunk-TXNQKBWJ.js";
|
|
8
|
+
import {
|
|
9
|
+
buildDeckFile,
|
|
10
|
+
fileImageResolver,
|
|
11
|
+
loadDeckFile
|
|
12
|
+
} from "./chunk-CKVKX2ZD.js";
|
|
13
|
+
import {
|
|
14
|
+
DeckError,
|
|
15
|
+
formatIssues,
|
|
16
|
+
renderDeck,
|
|
17
|
+
validateDeck
|
|
18
|
+
} from "./chunk-TM3TNVHB.js";
|
|
19
|
+
|
|
20
|
+
// src/node/cli.ts
|
|
21
|
+
import { watch } from "fs";
|
|
22
|
+
import { access, copyFile, mkdir } from "fs/promises";
|
|
23
|
+
import { createServer } from "http";
|
|
24
|
+
import { basename, dirname, join, resolve } from "path";
|
|
25
|
+
import { fileURLToPath } from "url";
|
|
26
|
+
import { parseArgs } from "util";
|
|
27
|
+
var HELP = `classic-slides \u2014 branded Classic Homes presentations
|
|
28
|
+
|
|
29
|
+
Usage
|
|
30
|
+
classic-slides new <file.yaml> [--starter basic|journey] Start a deck from a starter
|
|
31
|
+
classic-slides build <file.yaml> [-o out.html] Render one self-contained HTML file
|
|
32
|
+
classic-slides dev <file.yaml> [--port 4321] Live preview; reloads on save
|
|
33
|
+
classic-slides validate <file.yaml> Check for errors and crowded slides
|
|
34
|
+
classic-slides layouts [name] List layouts, or show one in detail
|
|
35
|
+
classic-slides guide Print the authoring guide
|
|
36
|
+
classic-slides schema Print the deck JSON Schema
|
|
37
|
+
classic-slides mcp [--root dir] Run the MCP server on stdio
|
|
38
|
+
`;
|
|
39
|
+
var pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
40
|
+
async function main() {
|
|
41
|
+
const { positionals, values } = parseArgs({
|
|
42
|
+
allowPositionals: true,
|
|
43
|
+
options: {
|
|
44
|
+
out: { type: "string", short: "o" },
|
|
45
|
+
port: { type: "string", default: "4321" },
|
|
46
|
+
starter: { type: "string", default: "basic" },
|
|
47
|
+
root: { type: "string" },
|
|
48
|
+
help: { type: "boolean", short: "h" }
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const [cmd, arg] = positionals;
|
|
52
|
+
if (!cmd || values.help) return void process.stdout.write(HELP);
|
|
53
|
+
switch (cmd) {
|
|
54
|
+
case "new": {
|
|
55
|
+
if (!arg) throw new Error("Usage: classic-slides new <file.yaml>");
|
|
56
|
+
const target = resolve(arg.endsWith(".yaml") || arg.endsWith(".yml") ? arg : `${arg}.yaml`);
|
|
57
|
+
if (await access(target).then(
|
|
58
|
+
() => true,
|
|
59
|
+
() => false
|
|
60
|
+
))
|
|
61
|
+
throw new Error(`${target} already exists.`);
|
|
62
|
+
const starter = join(pkgRoot, "starters", `${values.starter}.yaml`);
|
|
63
|
+
await mkdir(dirname(target), { recursive: true });
|
|
64
|
+
await copyFile(starter, target);
|
|
65
|
+
console.log(`Created ${target}
|
|
66
|
+
Next: classic-slides dev ${basename(target)}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
case "build": {
|
|
70
|
+
if (!arg) throw new Error("Usage: classic-slides build <file.yaml>");
|
|
71
|
+
const r = await buildDeckFile(arg, {
|
|
72
|
+
out: values.out,
|
|
73
|
+
onWarning: (w) => console.warn(`\u26A0 ${w}`)
|
|
74
|
+
});
|
|
75
|
+
if (r.warnings.length) console.warn(formatIssues(r.warnings));
|
|
76
|
+
console.log(`\u2714 ${r.out} \u2014 ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
case "validate": {
|
|
80
|
+
if (!arg) throw new Error("Usage: classic-slides validate <file.yaml>");
|
|
81
|
+
const v = validateDeck(await loadDeckFile(resolve(arg)));
|
|
82
|
+
console.log(v.issues.length ? formatIssues(v.issues) : "\u2714 Valid, no warnings");
|
|
83
|
+
process.exitCode = v.ok ? 0 : 1;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
case "dev":
|
|
87
|
+
return dev(arg, Number(values.port));
|
|
88
|
+
case "layouts": {
|
|
89
|
+
if (arg) {
|
|
90
|
+
const d = describeLayout(arg);
|
|
91
|
+
if (!d) throw new Error(`Unknown layout "${arg}".`);
|
|
92
|
+
console.log(JSON.stringify(d, null, 2));
|
|
93
|
+
} else {
|
|
94
|
+
for (const l of listLayouts())
|
|
95
|
+
console.log(
|
|
96
|
+
`${l.name.padEnd(12)} ${l.description}
|
|
97
|
+
${"".padEnd(12)} Use for: ${l.whenToUse}
|
|
98
|
+
`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
case "guide":
|
|
104
|
+
return void console.log(AUTHORING_GUIDE);
|
|
105
|
+
case "schema":
|
|
106
|
+
return void console.log(JSON.stringify(deckJsonSchema(), null, 2));
|
|
107
|
+
case "mcp": {
|
|
108
|
+
const { runStdio } = await import("./mcp-5R6YD3QW.js");
|
|
109
|
+
return runStdio(values.root);
|
|
110
|
+
}
|
|
111
|
+
default:
|
|
112
|
+
throw new Error(`Unknown command "${cmd}".
|
|
113
|
+
|
|
114
|
+
${HELP}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function dev(file, port) {
|
|
118
|
+
if (!file) throw new Error("Usage: classic-slides dev <file.yaml>");
|
|
119
|
+
const abs = resolve(file);
|
|
120
|
+
const dir = dirname(abs);
|
|
121
|
+
const clients = /* @__PURE__ */ new Set();
|
|
122
|
+
const reload = `<script>new EventSource('/__reload').onmessage=()=>location.reload()</script>`;
|
|
123
|
+
const errorPage = (msg) => `<!doctype html><meta charset="utf-8"><title>Deck error</title><body style="font:15px/1.5 ui-monospace,monospace;padding:40px;background:#fdf2f2;color:#7f1d1d"><h2 style="font-family:system-ui">Deck has problems</h2><pre style="white-space:pre-wrap">${msg.replace(/&/g, "&").replace(/</g, "<")}</pre>${reload}`;
|
|
124
|
+
createServer(async (req, res) => {
|
|
125
|
+
if (req.url === "/__reload") {
|
|
126
|
+
res.writeHead(200, {
|
|
127
|
+
"content-type": "text/event-stream",
|
|
128
|
+
"cache-control": "no-cache",
|
|
129
|
+
connection: "keep-alive"
|
|
130
|
+
});
|
|
131
|
+
res.write(": ok\n\n");
|
|
132
|
+
clients.add(res);
|
|
133
|
+
req.on("close", () => clients.delete(res));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
let html;
|
|
137
|
+
try {
|
|
138
|
+
const deck = await loadDeckFile(abs);
|
|
139
|
+
const r = await renderDeck(deck, {
|
|
140
|
+
resolveImage: fileImageResolver(dir),
|
|
141
|
+
embedSource: false
|
|
142
|
+
});
|
|
143
|
+
html = r.html.replace("</body>", `${reload}</body>`);
|
|
144
|
+
if (r.warnings.length) console.warn(formatIssues(r.warnings));
|
|
145
|
+
} catch (e) {
|
|
146
|
+
html = errorPage(
|
|
147
|
+
e instanceof DeckError ? formatIssues(e.issues) : e instanceof Error ? e.message : String(e)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
151
|
+
res.end(html);
|
|
152
|
+
}).listen(
|
|
153
|
+
port,
|
|
154
|
+
() => console.log(`Previewing ${file} at http://localhost:${port} (Ctrl+C to stop)`)
|
|
155
|
+
);
|
|
156
|
+
let timer;
|
|
157
|
+
watch(dir, { recursive: true }, (_, name) => {
|
|
158
|
+
if (name && /\.html$/.test(name)) return;
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
timer = setTimeout(() => clients.forEach((c) => c.write("data: reload\n\n")), 120);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
main().catch((e) => {
|
|
164
|
+
console.error(e instanceof DeckError ? e.message : `\u2716 ${e instanceof Error ? e.message : e}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * classic-slides — build branded Classic Homes decks from YAML.\n */\nimport { watch } from 'node:fs';\nimport { access, copyFile, mkdir } from 'node:fs/promises';\nimport { createServer } from 'node:http';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { AUTHORING_GUIDE, deckJsonSchema, describeLayout, listLayouts } from '../catalog.js';\nimport { renderDeck } from '../render.js';\nimport { DeckError, formatIssues, validateDeck } from '../validate.js';\nimport { buildDeckFile, fileImageResolver, loadDeckFile } from './index.js';\n\nconst HELP = `classic-slides — branded Classic Homes presentations\n\nUsage\n classic-slides new <file.yaml> [--starter basic|journey] Start a deck from a starter\n classic-slides build <file.yaml> [-o out.html] Render one self-contained HTML file\n classic-slides dev <file.yaml> [--port 4321] Live preview; reloads on save\n classic-slides validate <file.yaml> Check for errors and crowded slides\n classic-slides layouts [name] List layouts, or show one in detail\n classic-slides guide Print the authoring guide\n classic-slides schema Print the deck JSON Schema\n classic-slides mcp [--root dir] Run the MCP server on stdio\n`;\n\nconst pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\n\nasync function main() {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n out: { type: 'string', short: 'o' },\n port: { type: 'string', default: '4321' },\n starter: { type: 'string', default: 'basic' },\n root: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [cmd, arg] = positionals;\n\n if (!cmd || values.help) return void process.stdout.write(HELP);\n\n switch (cmd) {\n case 'new': {\n if (!arg) throw new Error('Usage: classic-slides new <file.yaml>');\n const target = resolve(arg.endsWith('.yaml') || arg.endsWith('.yml') ? arg : `${arg}.yaml`);\n if (\n await access(target).then(\n () => true,\n () => false\n )\n )\n throw new Error(`${target} already exists.`);\n const starter = join(pkgRoot, 'starters', `${values.starter}.yaml`);\n await mkdir(dirname(target), { recursive: true });\n await copyFile(starter, target);\n console.log(`Created ${target}\\nNext: classic-slides dev ${basename(target)}`);\n return;\n }\n case 'build': {\n if (!arg) throw new Error('Usage: classic-slides build <file.yaml>');\n const r = await buildDeckFile(arg, {\n out: values.out,\n onWarning: (w) => console.warn(`⚠ ${w}`),\n });\n if (r.warnings.length) console.warn(formatIssues(r.warnings));\n console.log(`✔ ${r.out} — ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB`);\n return;\n }\n case 'validate': {\n if (!arg) throw new Error('Usage: classic-slides validate <file.yaml>');\n const v = validateDeck(await loadDeckFile(resolve(arg)));\n console.log(v.issues.length ? formatIssues(v.issues) : '✔ Valid, no warnings');\n process.exitCode = v.ok ? 0 : 1;\n return;\n }\n case 'dev':\n return dev(arg, Number(values.port));\n case 'layouts': {\n if (arg) {\n const d = describeLayout(arg);\n if (!d) throw new Error(`Unknown layout \"${arg}\".`);\n console.log(JSON.stringify(d, null, 2));\n } else {\n for (const l of listLayouts())\n console.log(\n `${l.name.padEnd(12)} ${l.description}\\n${''.padEnd(12)} Use for: ${l.whenToUse}\\n`\n );\n }\n return;\n }\n case 'guide':\n return void console.log(AUTHORING_GUIDE);\n case 'schema':\n return void console.log(JSON.stringify(deckJsonSchema(), null, 2));\n case 'mcp': {\n const { runStdio } = await import('./mcp.js');\n return runStdio(values.root);\n }\n default:\n throw new Error(`Unknown command \"${cmd}\".\\n\\n${HELP}`);\n }\n}\n\n/** Preview server: renders on every request and reloads the page when the deck folder changes. */\nasync function dev(file: string | undefined, port: number) {\n if (!file) throw new Error('Usage: classic-slides dev <file.yaml>');\n const abs = resolve(file);\n const dir = dirname(abs);\n const clients = new Set<import('node:http').ServerResponse>();\n const reload = `<script>new EventSource('/__reload').onmessage=()=>location.reload()</script>`;\n\n const errorPage = (msg: string) =>\n `<!doctype html><meta charset=\"utf-8\"><title>Deck error</title><body style=\"font:15px/1.5 ui-monospace,monospace;padding:40px;background:#fdf2f2;color:#7f1d1d\"><h2 style=\"font-family:system-ui\">Deck has problems</h2><pre style=\"white-space:pre-wrap\">${msg\n .replace(/&/g, '&')\n .replace(/</g, '<')}</pre>${reload}`;\n\n createServer(async (req, res) => {\n if (req.url === '/__reload') {\n res.writeHead(200, {\n 'content-type': 'text/event-stream',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n });\n res.write(': ok\\n\\n');\n clients.add(res);\n req.on('close', () => clients.delete(res));\n return;\n }\n let html: string;\n try {\n const deck = await loadDeckFile(abs);\n const r = await renderDeck(deck, {\n resolveImage: fileImageResolver(dir),\n embedSource: false,\n });\n html = r.html.replace('</body>', `${reload}</body>`);\n if (r.warnings.length) console.warn(formatIssues(r.warnings));\n } catch (e) {\n html = errorPage(\n e instanceof DeckError ? formatIssues(e.issues) : e instanceof Error ? e.message : String(e)\n );\n }\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });\n res.end(html);\n }).listen(port, () =>\n console.log(`Previewing ${file} at http://localhost:${port} (Ctrl+C to stop)`)\n );\n\n let timer: NodeJS.Timeout | undefined;\n watch(dir, { recursive: true }, (_, name) => {\n if (name && /\\.html$/.test(name)) return;\n clearTimeout(timer);\n timer = setTimeout(() => clients.forEach((c) => c.write('data: reload\\n\\n')), 120);\n });\n}\n\nmain().catch((e) => {\n console.error(e instanceof DeckError ? e.message : `✖ ${e instanceof Error ? e.message : e}`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export { D as Deck, a as DeckError, b as DeckInput, c as DeckSchema, I as ImageResolver, L as LAYOUTS, d as LayoutDef, e as LayoutIssue, f as LayoutName, R as RenderOptions, g as RenderResult, S as SlideData, h as SlideInput, i as SlideSchema, V as ValidationResult, j as collectImageSources, k as extractDeckSource, l as formatIssues, m as formatPath, n as getLayout, r as renderDeck, v as validateDeck } from './render-CZ4g2fa9.js';
|
|
2
|
+
import * as zod_to_json_schema from 'zod-to-json-schema';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
interface LayoutSummary {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
whenToUse: string;
|
|
9
|
+
}
|
|
10
|
+
declare function listLayouts(): LayoutSummary[];
|
|
11
|
+
/** Full description of one layout: fields as JSON Schema plus a working example. */
|
|
12
|
+
declare function describeLayout(name: string): {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
whenToUse: string;
|
|
16
|
+
defaults: {
|
|
17
|
+
background?: string;
|
|
18
|
+
track?: "value" | "build" | "neutral";
|
|
19
|
+
};
|
|
20
|
+
schema: zod_to_json_schema.JsonSchema7Type & {
|
|
21
|
+
$schema?: string | undefined;
|
|
22
|
+
definitions?: {
|
|
23
|
+
[key: string]: zod_to_json_schema.JsonSchema7Type;
|
|
24
|
+
} | undefined;
|
|
25
|
+
};
|
|
26
|
+
example: Record<string, unknown>;
|
|
27
|
+
} | undefined;
|
|
28
|
+
/** JSON Schema for a whole deck (editor tooling, `$schema` in deck files). */
|
|
29
|
+
declare function deckJsonSchema(): zod_to_json_schema.JsonSchema7Type & {
|
|
30
|
+
$schema?: string | undefined;
|
|
31
|
+
definitions?: {
|
|
32
|
+
[key: string]: zod_to_json_schema.JsonSchema7Type;
|
|
33
|
+
} | undefined;
|
|
34
|
+
};
|
|
35
|
+
/** Authoring guidance shared by the CLI, MCP server and docs. */
|
|
36
|
+
declare const AUTHORING_GUIDE = "# Writing a Classic Homes deck\n\nA deck is a YAML (or JSON) file: deck-level fields plus a list of slides. Each slide\npicks a **layout** and fills its fields. Layouts own all styling \u2014 there is no way to\nset colours, fonts or sizes directly, which is what keeps every deck on-brand.\n\n## Principles\n- **One idea per slide.** The headline states the point. Detail goes in `notes`.\n- **Short copy.** Titles under ~70 characters, bullets a few words, at most 6 bullets.\n `validate` warns when a slide gets crowded \u2014 split it rather than shrinking text.\n- **Speaker notes on every content slide.** Presenters press N to see them.\n- **Real photos over decoration.** Every image needs `alt` text.\n- **Tracks** colour the accent: `value` (red, default) for the homeowner/value story,\n `build` (teal) for construction/process, `neutral` (navy) for framing slides.\n- **Backgrounds**: `paper` (default) for content; solid `navy`/`red`/`teal`/`plum`/`grey`/`stone`\n for section dividers and moments of emphasis. Don't put dense content on solid grounds.\n\n## Inline text\n`**bold**`, `*highlight*` (accent colour), `\\`code\\``, `[label](https://\u2026)`. No HTML.\n\n## Journey decks\nSet `journey.steps` at deck level to get a numbered agenda. Then:\n- a `journey` slide shows the full map,\n- `step: 3` on any slide draws the vertical STEP rail,\n- `showJourney: true` adds the compact \"you are here\" strip (great on `section` slides).\n\n## Typical shape\n1. `title`\n2. `statement` or `journey` (why we're here / the agenda)\n3. per part: `section` \u2192 2\u20135 content slides (`bullets`, `split`, `cards`, `stats`, `photos`, `walkthrough`)\n4. `qr` (where to go next) \u2192 `contacts` \u2192 closing `statement`\n";
|
|
37
|
+
|
|
38
|
+
/** Parses deck source text. YAML is a superset of JSON, so both work. */
|
|
39
|
+
declare function parseDeckSource(text: string): unknown;
|
|
40
|
+
/** Serializes a deck back to YAML, the human-editable format. */
|
|
41
|
+
declare function stringifyDeck(deck: unknown): string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Inline text formatting for slide copy and speaker notes.
|
|
45
|
+
*
|
|
46
|
+
* Deck text is never raw HTML: authors and agents write a tiny markdown subset
|
|
47
|
+
* and everything else is escaped. That keeps decks on-brand (no inline styles)
|
|
48
|
+
* and makes rendered output safe to publish.
|
|
49
|
+
*
|
|
50
|
+
* **bold** → <b> (strong ink colour)
|
|
51
|
+
* *highlight* → <em class="hl"> (track accent colour)
|
|
52
|
+
* `code` → <code>
|
|
53
|
+
* [label](url) → <a> (http/https/mailto/tel only)
|
|
54
|
+
* single newline → <br>
|
|
55
|
+
*/
|
|
56
|
+
declare function escapeHtml(value: string): string;
|
|
57
|
+
/** Formats one run of inline text. Newlines become line breaks. */
|
|
58
|
+
declare function inline(text: string | undefined | null): string;
|
|
59
|
+
/**
|
|
60
|
+
* Formats a block of prose (speaker notes, long captions): blank lines split
|
|
61
|
+
* paragraphs, lines starting with "- " become a bullet list.
|
|
62
|
+
*/
|
|
63
|
+
declare function prose(text: string | undefined | null): string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Slide palette. Mostly brand tokens used as-is; the few surface tints
|
|
67
|
+
* (paper, soft accents) are deck-specific and derived here.
|
|
68
|
+
*/
|
|
69
|
+
declare const PALETTE: {
|
|
70
|
+
readonly red: "#a91c22";
|
|
71
|
+
readonly redLogo: "#e02726";
|
|
72
|
+
readonly teal: "#236265";
|
|
73
|
+
readonly tealFill: "#236265";
|
|
74
|
+
readonly navy: "#002e3f";
|
|
75
|
+
readonly plum: "#5e2c41";
|
|
76
|
+
readonly sage: "#969a82";
|
|
77
|
+
readonly stone: "#676d69";
|
|
78
|
+
readonly grey: "#4f4f4f";
|
|
79
|
+
readonly ink: "#002e3f";
|
|
80
|
+
readonly inkSoft: "#4f4f4f";
|
|
81
|
+
readonly inkFaint: "#7a7a7a";
|
|
82
|
+
};
|
|
83
|
+
/** Horizontal Classic Homes lockup (mark + wordmark), mirrors LogoMain in theme-svelte. Colours follow CSS. */
|
|
84
|
+
declare const LOGO_SVG: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Slide-level edits on a deck object. Pure functions shared by the MCP
|
|
88
|
+
* server and (later) the hosted editor, so agents change one slide at a
|
|
89
|
+
* time instead of rewriting whole files.
|
|
90
|
+
*/
|
|
91
|
+
interface EditableDeck {
|
|
92
|
+
slides: Record<string, unknown>[];
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
}
|
|
95
|
+
/** A slide reference: its `id`, or its 1-based position. */
|
|
96
|
+
type SlideRef = string | number;
|
|
97
|
+
declare function findSlide(deck: EditableDeck, ref: SlideRef): number;
|
|
98
|
+
/** Inserts a slide. `position` is 1-based; omitted appends. Returns the new slide's position. */
|
|
99
|
+
declare function addSlide(deck: EditableDeck, slide: Record<string, unknown>, position?: number): number;
|
|
100
|
+
/** Shallow-merges fields into a slide. A field set to null is removed. Changing `layout` replaces the slide. */
|
|
101
|
+
declare function updateSlide(deck: EditableDeck, ref: SlideRef, patch: Record<string, unknown>): number;
|
|
102
|
+
declare function moveSlide(deck: EditableDeck, ref: SlideRef, to: number): number;
|
|
103
|
+
declare function removeSlide(deck: EditableDeck, ref: SlideRef): Record<string, unknown>;
|
|
104
|
+
/** One-line outline of each slide, for agents and CLIs. */
|
|
105
|
+
declare function outline(deck: EditableDeck): string[];
|
|
106
|
+
|
|
107
|
+
type EditorField = FieldBase<'inline'> | FieldBase<'string'> | (FieldBase<'enum'> & {
|
|
108
|
+
options: (string | number)[];
|
|
109
|
+
}) | FieldBase<'boolean'> | (FieldBase<'number'> & {
|
|
110
|
+
min?: number;
|
|
111
|
+
max?: number;
|
|
112
|
+
integer: boolean;
|
|
113
|
+
}) | FieldBase<'image'> | FieldBase<'item'> | (FieldBase<'list'> & {
|
|
114
|
+
of: EditorField;
|
|
115
|
+
min?: number;
|
|
116
|
+
max?: number;
|
|
117
|
+
}) | (FieldBase<'group'> & {
|
|
118
|
+
fields: EditorField[];
|
|
119
|
+
});
|
|
120
|
+
interface FieldBase<K extends string> {
|
|
121
|
+
kind: K;
|
|
122
|
+
/** Property name ('' for list elements). */
|
|
123
|
+
key: string;
|
|
124
|
+
label: string;
|
|
125
|
+
description?: string;
|
|
126
|
+
required: boolean;
|
|
127
|
+
default?: unknown;
|
|
128
|
+
/** Suggest a textarea rather than a one-line input. */
|
|
129
|
+
multiline?: boolean;
|
|
130
|
+
}
|
|
131
|
+
interface LayoutEditorInfo {
|
|
132
|
+
name: string;
|
|
133
|
+
description: string;
|
|
134
|
+
whenToUse: string;
|
|
135
|
+
defaults: {
|
|
136
|
+
background?: string;
|
|
137
|
+
track?: string;
|
|
138
|
+
};
|
|
139
|
+
/** Layout-specific fields, in declaration order. */
|
|
140
|
+
fields: EditorField[];
|
|
141
|
+
/** A valid starting slide for this layout. */
|
|
142
|
+
example: Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
/** Fields shared by every slide (track, background, notes, step…). */
|
|
145
|
+
declare function commonSlideFields(): EditorField[];
|
|
146
|
+
declare function layoutEditorInfo(name: string): LayoutEditorInfo | undefined;
|
|
147
|
+
declare function allLayoutEditorInfo(): LayoutEditorInfo[];
|
|
148
|
+
|
|
149
|
+
export { AUTHORING_GUIDE, type EditableDeck, type EditorField, LOGO_SVG, type LayoutEditorInfo, PALETTE, type SlideRef, addSlide, allLayoutEditorInfo, commonSlideFields, deckJsonSchema, describeLayout, escapeHtml, findSlide, inline, layoutEditorInfo, listLayouts, moveSlide, outline, parseDeckSource, prose, removeSlide, stringifyDeck, updateSlide };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addSlide,
|
|
3
|
+
findSlide,
|
|
4
|
+
moveSlide,
|
|
5
|
+
outline,
|
|
6
|
+
removeSlide,
|
|
7
|
+
updateSlide
|
|
8
|
+
} from "./chunk-VONVGYRL.js";
|
|
9
|
+
import {
|
|
10
|
+
AUTHORING_GUIDE,
|
|
11
|
+
deckJsonSchema,
|
|
12
|
+
describeLayout,
|
|
13
|
+
listLayouts
|
|
14
|
+
} from "./chunk-TXNQKBWJ.js";
|
|
15
|
+
import {
|
|
16
|
+
DeckError,
|
|
17
|
+
DeckSchema,
|
|
18
|
+
Image,
|
|
19
|
+
Item,
|
|
20
|
+
LAYOUTS,
|
|
21
|
+
LOGO_SVG,
|
|
22
|
+
PALETTE,
|
|
23
|
+
SlideSchema,
|
|
24
|
+
Text,
|
|
25
|
+
baseSlide,
|
|
26
|
+
collectImageSources,
|
|
27
|
+
escapeHtml,
|
|
28
|
+
extractDeckSource,
|
|
29
|
+
formatIssues,
|
|
30
|
+
formatPath,
|
|
31
|
+
getLayout,
|
|
32
|
+
inline,
|
|
33
|
+
parseDeckSource,
|
|
34
|
+
prose,
|
|
35
|
+
renderDeck,
|
|
36
|
+
stringifyDeck,
|
|
37
|
+
validateDeck
|
|
38
|
+
} from "./chunk-TM3TNVHB.js";
|
|
39
|
+
|
|
40
|
+
// src/editor.ts
|
|
41
|
+
import { z } from "zod";
|
|
42
|
+
var MULTILINE = /* @__PURE__ */ new Set(["lead", "body", "text", "quote", "footnote", "sub", "notes", "caption"]);
|
|
43
|
+
var LABELS = {
|
|
44
|
+
eyebrowSub: "Eyebrow subtitle",
|
|
45
|
+
showJourney: "Show journey strip",
|
|
46
|
+
stepDone: "Step completed",
|
|
47
|
+
mediaSide: "Photo side",
|
|
48
|
+
imageAspect: "Photo shape",
|
|
49
|
+
numberFrom: "First badge number",
|
|
50
|
+
url: "URL",
|
|
51
|
+
id: "Slide id"
|
|
52
|
+
};
|
|
53
|
+
function labelFor(key) {
|
|
54
|
+
if (LABELS[key]) return LABELS[key];
|
|
55
|
+
const words = key.replace(/([A-Z])/g, " $1").toLowerCase();
|
|
56
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
57
|
+
}
|
|
58
|
+
function unwrap(schema) {
|
|
59
|
+
let s = schema;
|
|
60
|
+
let required = true;
|
|
61
|
+
let dflt;
|
|
62
|
+
let description = schema.description;
|
|
63
|
+
for (; ; ) {
|
|
64
|
+
description ??= s.description;
|
|
65
|
+
if (s instanceof z.ZodOptional || s instanceof z.ZodNullable) {
|
|
66
|
+
required = false;
|
|
67
|
+
s = s.unwrap();
|
|
68
|
+
} else if (s instanceof z.ZodDefault) {
|
|
69
|
+
required = false;
|
|
70
|
+
dflt = s._def.defaultValue();
|
|
71
|
+
s = s._def.innerType;
|
|
72
|
+
} else break;
|
|
73
|
+
}
|
|
74
|
+
return { inner: s, required, default: dflt, description };
|
|
75
|
+
}
|
|
76
|
+
function isText(s) {
|
|
77
|
+
return s === Text || s instanceof z.ZodString && s.description === Text.description;
|
|
78
|
+
}
|
|
79
|
+
function describeField(key, schema) {
|
|
80
|
+
const { inner, required, default: dflt, description } = unwrap(schema);
|
|
81
|
+
const base = {
|
|
82
|
+
key,
|
|
83
|
+
label: labelFor(key),
|
|
84
|
+
required,
|
|
85
|
+
...description ? { description } : {},
|
|
86
|
+
...dflt !== void 0 ? { default: dflt } : {},
|
|
87
|
+
...MULTILINE.has(key) ? { multiline: true } : {}
|
|
88
|
+
};
|
|
89
|
+
if (inner === Image) return { ...base, kind: "image" };
|
|
90
|
+
if (inner === Item) return { ...base, kind: "item" };
|
|
91
|
+
if (isText(inner)) return { ...base, kind: "inline" };
|
|
92
|
+
if (inner instanceof z.ZodString) return { ...base, kind: "string" };
|
|
93
|
+
if (inner instanceof z.ZodBoolean) return { ...base, kind: "boolean" };
|
|
94
|
+
if (inner instanceof z.ZodEnum) return { ...base, kind: "enum", options: [...inner.options] };
|
|
95
|
+
if (inner instanceof z.ZodLiteral) return { ...base, kind: "enum", options: [inner.value] };
|
|
96
|
+
if (inner instanceof z.ZodNumber) {
|
|
97
|
+
return {
|
|
98
|
+
...base,
|
|
99
|
+
kind: "number",
|
|
100
|
+
integer: inner.isInt,
|
|
101
|
+
...inner.minValue !== null ? { min: inner.minValue } : {},
|
|
102
|
+
...inner.maxValue !== null ? { max: inner.maxValue } : {}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (inner instanceof z.ZodUnion) {
|
|
106
|
+
const opts = inner.options;
|
|
107
|
+
if (opts.every((o) => o instanceof z.ZodLiteral)) {
|
|
108
|
+
return {
|
|
109
|
+
...base,
|
|
110
|
+
kind: "enum",
|
|
111
|
+
options: opts.map((o) => o.value)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return { ...base, kind: "string" };
|
|
115
|
+
}
|
|
116
|
+
if (inner instanceof z.ZodArray) {
|
|
117
|
+
const el = inner.element;
|
|
118
|
+
return {
|
|
119
|
+
...base,
|
|
120
|
+
kind: "list",
|
|
121
|
+
of: { ...describeField("", el), label: labelFor(key.replace(/s$/, "")) },
|
|
122
|
+
...inner._def.minLength ? { min: inner._def.minLength.value } : {},
|
|
123
|
+
...inner._def.maxLength ? { max: inner._def.maxLength.value } : {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (inner instanceof z.ZodObject) {
|
|
127
|
+
return {
|
|
128
|
+
...base,
|
|
129
|
+
kind: "group",
|
|
130
|
+
fields: Object.entries(inner.shape).map(
|
|
131
|
+
([k, v]) => describeField(k, v)
|
|
132
|
+
)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { ...base, kind: "string" };
|
|
136
|
+
}
|
|
137
|
+
function commonSlideFields() {
|
|
138
|
+
return Object.entries(baseSlide).map(([k, v]) => describeField(k, v));
|
|
139
|
+
}
|
|
140
|
+
function layoutEditorInfo(name) {
|
|
141
|
+
const layout = getLayout(name);
|
|
142
|
+
if (!layout) return void 0;
|
|
143
|
+
return {
|
|
144
|
+
name: layout.name,
|
|
145
|
+
description: layout.description,
|
|
146
|
+
whenToUse: layout.whenToUse,
|
|
147
|
+
defaults: layout.defaults ?? {},
|
|
148
|
+
fields: Object.entries(layout.shape).map(
|
|
149
|
+
([k, v]) => describeField(k, v)
|
|
150
|
+
),
|
|
151
|
+
example: layout.example
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function allLayoutEditorInfo() {
|
|
155
|
+
return LAYOUTS.map((l) => layoutEditorInfo(l.name));
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
AUTHORING_GUIDE,
|
|
159
|
+
DeckError,
|
|
160
|
+
DeckSchema,
|
|
161
|
+
LAYOUTS,
|
|
162
|
+
LOGO_SVG,
|
|
163
|
+
PALETTE,
|
|
164
|
+
SlideSchema,
|
|
165
|
+
addSlide,
|
|
166
|
+
allLayoutEditorInfo,
|
|
167
|
+
collectImageSources,
|
|
168
|
+
commonSlideFields,
|
|
169
|
+
deckJsonSchema,
|
|
170
|
+
describeLayout,
|
|
171
|
+
escapeHtml,
|
|
172
|
+
extractDeckSource,
|
|
173
|
+
findSlide,
|
|
174
|
+
formatIssues,
|
|
175
|
+
formatPath,
|
|
176
|
+
getLayout,
|
|
177
|
+
inline,
|
|
178
|
+
layoutEditorInfo,
|
|
179
|
+
listLayouts,
|
|
180
|
+
moveSlide,
|
|
181
|
+
outline,
|
|
182
|
+
parseDeckSource,
|
|
183
|
+
prose,
|
|
184
|
+
removeSlide,
|
|
185
|
+
renderDeck,
|
|
186
|
+
stringifyDeck,
|
|
187
|
+
updateSlide,
|
|
188
|
+
validateDeck
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=index.js.map
|