@tycoworks/tycoslide 0.10.0 → 0.11.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 +3 -1
- package/SKILL.md +28 -54
- package/dist/cli.js +8 -11
- package/dist/engine/fillers/filler.d.ts +31 -5
- package/dist/engine/fillers/filler.js +29 -21
- package/dist/engine/fillers/image.d.ts +1 -1
- package/dist/engine/fillers/image.js +2 -2
- package/dist/engine/fillers/table.d.ts +33 -16
- package/dist/engine/fillers/table.js +62 -27
- package/dist/engine/generate.d.ts +5 -4
- package/dist/engine/generate.js +51 -17
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/types.d.ts +41 -13
- package/dist/index.d.ts +3 -4
- package/dist/index.js +28 -15
- package/dist/manifest.d.ts +1 -6
- package/dist/manifest.js +1 -17
- package/dist/markdown/blocks/mermaid.d.ts +1 -1
- package/dist/markdown/blocks/mermaid.js +3 -3
- package/dist/markdown/blocks/table.js +1 -1
- package/dist/markdown/blocks/text.js +5 -6
- package/dist/markdown/deckCompiler.d.ts +2 -2
- package/dist/markdown/deckCompiler.js +7 -32
- package/dist/markdown/index.d.ts +1 -1
- package/dist/markdown/schema/deckSchema.d.ts +2 -2
- package/dist/markdown/schema/deckSchema.js +2 -2
- package/dist/markdown/schema/themeConfigSchema.d.ts +12 -21
- package/dist/markdown/schema/themeConfigSchema.js +22 -14
- package/dist/markdown/slideParser.d.ts +0 -1
- package/dist/markdown/slideParser.js +7 -4
- package/dist/markdown/types.d.ts +44 -46
- package/dist/markdown/types.js +2 -4
- package/dist/skillZip.d.ts +8 -8
- package/dist/skillZip.js +39 -10
- package/package.json +1 -1
- package/syntax.md +28 -25
package/dist/engine/generate.js
CHANGED
|
@@ -33,6 +33,7 @@ import { Automizer, modify } from "pptx-automizer";
|
|
|
33
33
|
import { FILLERS } from "./fillers/filler.js";
|
|
34
34
|
import { isImageFill } from "./fillers/image.js";
|
|
35
35
|
import { applyNotesToSlide, sweepOrphanNotes } from "./notes.js";
|
|
36
|
+
import { SlotType } from "./types.js";
|
|
36
37
|
/**
|
|
37
38
|
* Generate a PPTX file from a deck definition and a theme configuration.
|
|
38
39
|
*
|
|
@@ -46,9 +47,11 @@ import { applyNotesToSlide, sweepOrphanNotes } from "./notes.js";
|
|
|
46
47
|
* 4. Write the output PPTX.
|
|
47
48
|
*/
|
|
48
49
|
export async function generate(deck, config, options = {}) {
|
|
49
|
-
const { layouts, rootDir, template
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const { layouts, rootDir, template } = config;
|
|
51
|
+
// deck.output is the absolute output path; automizer takes a dir + a bare
|
|
52
|
+
// filename, so split it. The CLI resolves it next to the deck file.
|
|
53
|
+
const outDir = dirname(deck.output);
|
|
54
|
+
const outFile = basename(deck.output);
|
|
52
55
|
const automizer = new Automizer({
|
|
53
56
|
templateDir: resolve(rootDir, "template"),
|
|
54
57
|
outputDir: outDir,
|
|
@@ -98,11 +101,12 @@ export async function generate(deck, config, options = {}) {
|
|
|
98
101
|
// onto slides. When true, no notes are written but inherited notes are still
|
|
99
102
|
// stripped — the deck is guaranteed notes-free.
|
|
100
103
|
const excludeNotes = options.excludeNotes ?? false;
|
|
101
|
-
for (const step of deck.steps) {
|
|
104
|
+
for (const [index, step] of deck.steps.entries()) {
|
|
102
105
|
const layout = resolveLayout(step.layout);
|
|
106
|
+
const slideNumber = index + 1;
|
|
103
107
|
pres.addSlide(sourceAlias, layout.baseSlide, (slide) => {
|
|
104
108
|
captureFillErrors(slide);
|
|
105
|
-
fillSlide(slide, layout, step, sourceAlias);
|
|
109
|
+
fillSlide(slide, layout, step, sourceAlias, slideNumber);
|
|
106
110
|
// In-band notes pass: automizer runs this during write() and hands us the
|
|
107
111
|
// OUTPUT archive (parent.targetArchive) and the real output slide number
|
|
108
112
|
// (parent.targetNumber), so notes map 1:1 with no slide-number mapping.
|
|
@@ -179,22 +183,39 @@ function describeValue(v) {
|
|
|
179
183
|
/**
|
|
180
184
|
* Fill one cloned slide. Per slot the step supplies a value for: resolve WHICH
|
|
181
185
|
* shape realizes it (`resolveBlock`), build WHAT to write (`FILLERS[…].callbacks`),
|
|
182
|
-
* and place it WHERE/HOW (`applyBlock`).
|
|
183
|
-
*
|
|
186
|
+
* and place it WHERE/HOW (`applyBlock`). A slot the step leaves unfilled has its
|
|
187
|
+
* base-slide shape removed, so a layout with numbered slots renders exactly the
|
|
188
|
+
* ones supplied. Every ambiguity fails fast, naming layout + slot.
|
|
184
189
|
*
|
|
185
190
|
* Exported for tests; `generate()` calls it inside the `addSlide` callback.
|
|
186
191
|
*/
|
|
187
|
-
export function fillSlide(slide, layout, step, sourceAlias) {
|
|
192
|
+
export function fillSlide(slide, layout, step, sourceAlias, slideNumber) {
|
|
188
193
|
assertNoUnknownSlots(step, layout);
|
|
189
194
|
for (const slot of layout.slots) {
|
|
190
195
|
const value = step.content?.[slot.key];
|
|
191
|
-
if (value === undefined)
|
|
192
|
-
|
|
196
|
+
if (value === undefined) {
|
|
197
|
+
// An unfilled slot places no content — remove its base-slide shape (if it
|
|
198
|
+
// has one) so the clone carries nothing there. A transplant-only slot never
|
|
199
|
+
// placed a shape on the clone, so there is nothing to remove.
|
|
200
|
+
const baseBlock = slot.accepts.find((b) => b.sourceSlide === layout.baseSlide);
|
|
201
|
+
if (baseBlock)
|
|
202
|
+
slide.removeElement(baseBlock.shapeName);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
193
205
|
const block = resolveBlock(step, slot, value);
|
|
194
|
-
const
|
|
206
|
+
const target = targetOf(block, slotLabel(slideNumber, layout, slot));
|
|
207
|
+
const callbacks = FILLERS[block.type].callbacks(value, target);
|
|
195
208
|
applyBlock(slide, sourceAlias, layout.baseSlide, slot, block, callbacks);
|
|
196
209
|
}
|
|
197
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* The author-facing descriptor a filler prints in advisory warnings, so a
|
|
213
|
+
* message points at `slide 3, layout "Body", slot "photo"` rather than the raw
|
|
214
|
+
* PPTX shape id.
|
|
215
|
+
*/
|
|
216
|
+
function slotLabel(slideNumber, layout, slot) {
|
|
217
|
+
return `slide ${slideNumber}, layout "${layout.name}", slot "${slot.key}"`;
|
|
218
|
+
}
|
|
198
219
|
/** The SlotType a resolved `*Fill` value maps to, via the filler discriminators. */
|
|
199
220
|
function fillTypeOf(value) {
|
|
200
221
|
for (const type of Object.keys(FILLERS)) {
|
|
@@ -249,12 +270,25 @@ function resolveBlock(step, slot, value) {
|
|
|
249
270
|
}
|
|
250
271
|
return block;
|
|
251
272
|
}
|
|
252
|
-
/**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
/**
|
|
274
|
+
* The shape a filler targets, plus its diagnostic `label` and the intra-specimen
|
|
275
|
+
* options a block declares: `startAt` (text) and the required `bodyRows` (table).
|
|
276
|
+
* The block's `type` selects the matching target variant, so each filler's
|
|
277
|
+
* callback sees only its own options.
|
|
278
|
+
*/
|
|
279
|
+
function targetOf(block, label) {
|
|
280
|
+
switch (block.type) {
|
|
281
|
+
case SlotType.Template:
|
|
282
|
+
return { type: SlotType.Template, shapeName: block.shapeName, label };
|
|
283
|
+
case SlotType.Text:
|
|
284
|
+
return block.startAt !== undefined
|
|
285
|
+
? { type: SlotType.Text, shapeName: block.shapeName, label, startAt: block.startAt }
|
|
286
|
+
: { type: SlotType.Text, shapeName: block.shapeName, label };
|
|
287
|
+
case SlotType.Table:
|
|
288
|
+
return { type: SlotType.Table, shapeName: block.shapeName, label, bodyRows: block.bodyRows };
|
|
289
|
+
case SlotType.Image:
|
|
290
|
+
return { type: SlotType.Image, shapeName: block.shapeName, label };
|
|
291
|
+
}
|
|
258
292
|
}
|
|
259
293
|
/**
|
|
260
294
|
* WHERE/HOW: place the (already-built) fill callbacks. A base-slide block is
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -5,5 +5,5 @@ export { fillTemplate } from "./fillers/template.js";
|
|
|
5
5
|
export { fillText, isTextFill } from "./fillers/text.js";
|
|
6
6
|
export type { GenerateOptions } from "./generate.js";
|
|
7
7
|
export { generate } from "./generate.js";
|
|
8
|
-
export type { Block, Config, Deck, DeckStep, Frame, ImageFill, Layout, Slot, StyledParagraph, TableFill, TemplateFill, TemplateSegment, TextFill, TextRun, ThemeConfig, } from "./types.js";
|
|
8
|
+
export type { Block, BodyRows, Config, Deck, DeckStep, Frame, ImageFill, Layout, Slot, StyledParagraph, TableFill, TemplateFill, TemplateSegment, TextFill, TextRun, ThemeConfig, } from "./types.js";
|
|
9
9
|
export { ImageFit, SlotType } from "./types.js";
|
package/dist/engine/types.d.ts
CHANGED
|
@@ -14,6 +14,15 @@ export declare const SlotType: {
|
|
|
14
14
|
readonly Image: "image";
|
|
15
15
|
};
|
|
16
16
|
export type SlotType = (typeof SlotType)[keyof typeof SlotType];
|
|
17
|
+
/**
|
|
18
|
+
* The contiguous range of a table specimen's rows that repeat, as `[start, end]`
|
|
19
|
+
* (0-based, inclusive). Row 0 is always the header; rows `[1, start-1]` are top
|
|
20
|
+
* fixed rows rendered once (e.g. a distinctly-styled under-header row); rows
|
|
21
|
+
* `[start, end]` are the repeatable body, cycled to fill the deck's data; rows
|
|
22
|
+
* `[end+1, R-1]` are bottom fixed rows rendered once (e.g. a decorated total row).
|
|
23
|
+
* Only the body range repeats; every row outside it is rendered once, in place.
|
|
24
|
+
*/
|
|
25
|
+
export type BodyRows = [number, number];
|
|
17
26
|
/** A single styled span of text within a paragraph. */
|
|
18
27
|
export type TextRun = {
|
|
19
28
|
text: string;
|
|
@@ -102,23 +111,43 @@ export type Frame = {
|
|
|
102
111
|
};
|
|
103
112
|
/**
|
|
104
113
|
* A kind of content a slot accepts, and the real template shape that realizes
|
|
105
|
-
* it
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
114
|
+
* it — a union discriminated by `type` (the fill-strategy selector), so each
|
|
115
|
+
* variant carries only its own specimen options. `shapeName` names the shape on
|
|
116
|
+
* `sourceSlide` that carries the specimen styling; when `sourceSlide` equals the
|
|
117
|
+
* layout's `baseSlide` the shape is already on the cloned slide (fill in place),
|
|
118
|
+
* otherwise it is transplanted from `sourceSlide` into the slot's frame. A text
|
|
119
|
+
* block may pin `startAt` (leave the first N specimen paragraphs untouched); a
|
|
120
|
+
* table block MUST declare its `bodyRows` range (the repeatable specimen rows);
|
|
121
|
+
* a template or image block carries neither. `Template` reaches the engine only
|
|
122
|
+
* via a frontmatter parameter (`paramToEngineSlot`), never as an author body block.
|
|
111
123
|
*
|
|
112
|
-
* Named `Block` — a kind of content
|
|
113
|
-
*
|
|
114
|
-
*
|
|
124
|
+
* Named `Block` — a kind of content the way an author thinks of it. Distinct
|
|
125
|
+
* from the compiler's `MarkdownBlock` (a parsed markdown block); different layer,
|
|
126
|
+
* kept separate on purpose.
|
|
115
127
|
*/
|
|
116
|
-
export type
|
|
117
|
-
type: SlotType;
|
|
128
|
+
export type TemplateBlock = {
|
|
129
|
+
type: typeof SlotType.Template;
|
|
130
|
+
sourceSlide: number;
|
|
131
|
+
shapeName: string;
|
|
132
|
+
};
|
|
133
|
+
export type TextBlock = {
|
|
134
|
+
type: typeof SlotType.Text;
|
|
118
135
|
sourceSlide: number;
|
|
119
136
|
shapeName: string;
|
|
120
137
|
startAt?: number;
|
|
121
138
|
};
|
|
139
|
+
export type TableBlock = {
|
|
140
|
+
type: typeof SlotType.Table;
|
|
141
|
+
sourceSlide: number;
|
|
142
|
+
shapeName: string;
|
|
143
|
+
bodyRows: BodyRows;
|
|
144
|
+
};
|
|
145
|
+
export type ImageBlock = {
|
|
146
|
+
type: typeof SlotType.Image;
|
|
147
|
+
sourceSlide: number;
|
|
148
|
+
shapeName: string;
|
|
149
|
+
};
|
|
150
|
+
export type Block = TemplateBlock | TextBlock | TableBlock | ImageBlock;
|
|
122
151
|
/**
|
|
123
152
|
* An author-facing fill region. Not welded to one shape+type: a slot owns its
|
|
124
153
|
* `frame` and `accepts` a set of `Block`s; the supplied value's shape selects
|
|
@@ -147,14 +176,13 @@ export type DeckStep = {
|
|
|
147
176
|
};
|
|
148
177
|
export type Deck = {
|
|
149
178
|
theme: string;
|
|
150
|
-
/**
|
|
179
|
+
/** Absolute output path for the .pptx. Required — no silent default. */
|
|
151
180
|
output: string;
|
|
152
181
|
steps: DeckStep[];
|
|
153
182
|
};
|
|
154
183
|
export type ThemeConfig = {
|
|
155
184
|
layouts: Layout[];
|
|
156
185
|
template: string;
|
|
157
|
-
outputDir?: string;
|
|
158
186
|
};
|
|
159
187
|
export type Config = ThemeConfig & {
|
|
160
188
|
rootDir: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -28,13 +28,12 @@ export declare function toEngineConfig(config: CompilerConfig): Config;
|
|
|
28
28
|
* populates it before calling `buildDeck`; a programmatic caller that forgot to
|
|
29
29
|
* set it hits this error instead of a confusing engine-side failure.
|
|
30
30
|
*
|
|
31
|
-
* Mermaid PNGs are cached under `<
|
|
32
|
-
* post-write cleanup is needed.
|
|
31
|
+
* Mermaid PNGs are cached under `<rootDir>/.tycoslide-cache/mermaid/` (the theme
|
|
32
|
+
* directory) so no post-write cleanup is needed.
|
|
33
33
|
*/
|
|
34
34
|
export declare function buildDeck(deck: CompilerDeck, config: CompilerConfig, options?: GenerateOptions): Promise<void>;
|
|
35
35
|
export type { Config, Deck, DeckStep, GenerateOptions, ImageFill, Layout, Slot, StyledParagraph, TableFill, TextFill, TextRun, ThemeConfig, } from "./engine/index.js";
|
|
36
36
|
export { fillImage, fillTable, fillTemplate, fillText, generate, SlotType } from "./engine/index.js";
|
|
37
|
-
export type { ManifestOptions } from "./manifest.js";
|
|
38
37
|
export { generateManifest } from "./manifest.js";
|
|
39
|
-
export type { AssetCatalog, AssetEntry, CompilerBlock, CompilerConfig, CompilerDeck, CompilerDeckStep, CompilerLayout, CompilerParameter, CompilerSlot, CompilerThemeConfig, EngineFill,
|
|
38
|
+
export type { AssetCatalog, AssetEntry, CompilerBlock, CompilerConfig, CompilerDeck, CompilerDeckStep, CompilerLayout, CompilerParameter, CompilerSlot, CompilerThemeConfig, EngineFill, MermaidConfig, MermaidVariant, ParsedDocument, RawSlide, } from "./markdown/index.js";
|
|
40
39
|
export { AcceptType, compileMarkdownDeck, loadThemeConfig, ParameterType, parseThemeConfig } from "./markdown/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { generate, SlotType, } from "./engine/index.js";
|
|
2
|
-
import { ParameterType, } from "./markdown/types.js";
|
|
2
|
+
import { AcceptType, ParameterType, } from "./markdown/types.js";
|
|
3
3
|
/**
|
|
4
4
|
* A frontmatter parameter always fills one physical shape on the layout's own
|
|
5
5
|
* slide, so it projects to a single base `Block` (`sourceSlide === baseSlide`)
|
|
@@ -8,15 +8,22 @@ import { ParameterType, } from "./markdown/types.js";
|
|
|
8
8
|
*/
|
|
9
9
|
const NO_FRAME = { x: 0, y: 0, cx: 0, cy: 0 };
|
|
10
10
|
function paramToEngineSlot(param, baseSlide) {
|
|
11
|
-
const block = (type) => ({ type, sourceSlide: baseSlide, shapeName: param.shapeName });
|
|
12
11
|
switch (param.type) {
|
|
13
12
|
case ParameterType.Template:
|
|
14
13
|
// A text shape carries no top-level key — its template placeholders are the keys. The
|
|
15
14
|
// compiler emits its expanded content under shapeName, so the engine slot
|
|
16
15
|
// is keyed by shapeName too.
|
|
17
|
-
return {
|
|
16
|
+
return {
|
|
17
|
+
key: param.shapeName,
|
|
18
|
+
frame: NO_FRAME,
|
|
19
|
+
accepts: [{ type: SlotType.Template, sourceSlide: baseSlide, shapeName: param.shapeName }],
|
|
20
|
+
};
|
|
18
21
|
case ParameterType.Image:
|
|
19
|
-
return {
|
|
22
|
+
return {
|
|
23
|
+
key: param.key,
|
|
24
|
+
frame: NO_FRAME,
|
|
25
|
+
accepts: [{ type: SlotType.Image, sourceSlide: baseSlide, shapeName: param.shapeName }],
|
|
26
|
+
};
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
@@ -26,14 +33,21 @@ function paramToEngineSlot(param, baseSlide) {
|
|
|
26
33
|
* so projection is 1:1. A slot with no declared `frame` (a base-only slot that
|
|
27
34
|
* never transplants) gets `NO_FRAME`, which the engine never reads.
|
|
28
35
|
*/
|
|
36
|
+
/** Project one CompilerBlock variant onto its matching engine `Block` variant. */
|
|
37
|
+
function compilerBlockToEngineBlock(b) {
|
|
38
|
+
switch (b.type) {
|
|
39
|
+
case AcceptType.Text:
|
|
40
|
+
return b.startAt !== undefined
|
|
41
|
+
? { type: SlotType.Text, sourceSlide: b.sourceSlide, shapeName: b.shapeName, startAt: b.startAt }
|
|
42
|
+
: { type: SlotType.Text, sourceSlide: b.sourceSlide, shapeName: b.shapeName };
|
|
43
|
+
case AcceptType.Table:
|
|
44
|
+
return { type: SlotType.Table, sourceSlide: b.sourceSlide, shapeName: b.shapeName, bodyRows: b.bodyRows };
|
|
45
|
+
case AcceptType.Image:
|
|
46
|
+
return { type: SlotType.Image, sourceSlide: b.sourceSlide, shapeName: b.shapeName };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
29
49
|
function slotToEngineSlot(slot) {
|
|
30
|
-
|
|
31
|
-
const eb = { type: b.type, sourceSlide: b.sourceSlide, shapeName: b.shapeName };
|
|
32
|
-
if (b.startAt !== undefined)
|
|
33
|
-
eb.startAt = b.startAt;
|
|
34
|
-
return eb;
|
|
35
|
-
});
|
|
36
|
-
return { key: slot.key, frame: slot.frame ?? NO_FRAME, accepts };
|
|
50
|
+
return { key: slot.key, frame: slot.frame ?? NO_FRAME, accepts: slot.accepts.map(compilerBlockToEngineBlock) };
|
|
37
51
|
}
|
|
38
52
|
/**
|
|
39
53
|
* Compiler→engine boundary check: a slot with a transplant block (any block
|
|
@@ -74,8 +88,6 @@ export function toEngineThemeConfig(config) {
|
|
|
74
88
|
layouts: config.layouts.map(toEngineLayout),
|
|
75
89
|
template: config.template,
|
|
76
90
|
};
|
|
77
|
-
if (config.outputDir !== undefined)
|
|
78
|
-
result.outputDir = config.outputDir;
|
|
79
91
|
return result;
|
|
80
92
|
}
|
|
81
93
|
/**
|
|
@@ -102,8 +114,8 @@ export function toEngineConfig(config) {
|
|
|
102
114
|
* populates it before calling `buildDeck`; a programmatic caller that forgot to
|
|
103
115
|
* set it hits this error instead of a confusing engine-side failure.
|
|
104
116
|
*
|
|
105
|
-
* Mermaid PNGs are cached under `<
|
|
106
|
-
* post-write cleanup is needed.
|
|
117
|
+
* Mermaid PNGs are cached under `<rootDir>/.tycoslide-cache/mermaid/` (the theme
|
|
118
|
+
* directory) so no post-write cleanup is needed.
|
|
107
119
|
*/
|
|
108
120
|
export async function buildDeck(deck, config, options = {}) {
|
|
109
121
|
if (deck.output === undefined) {
|
|
@@ -114,6 +126,7 @@ export async function buildDeck(deck, config, options = {}) {
|
|
|
114
126
|
}
|
|
115
127
|
// Engine — primitives-only public surface.
|
|
116
128
|
export { fillImage, fillTable, fillTemplate, fillText, generate, SlotType } from "./engine/index.js";
|
|
129
|
+
// Authoring
|
|
117
130
|
export { generateManifest } from "./manifest.js";
|
|
118
131
|
// Markdown / Compiler
|
|
119
132
|
export { AcceptType, compileMarkdownDeck, loadThemeConfig, ParameterType, parseThemeConfig } from "./markdown/index.js";
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
import type { CompilerConfig } from "./markdown/types.js";
|
|
2
|
-
export
|
|
3
|
-
build: {
|
|
4
|
-
command: string;
|
|
5
|
-
};
|
|
6
|
-
};
|
|
7
|
-
export declare function generateManifest(config: CompilerConfig, options: ManifestOptions): string;
|
|
2
|
+
export declare function generateManifest(config: CompilerConfig): string;
|
package/dist/manifest.js
CHANGED
|
@@ -5,11 +5,6 @@ import { ParameterType } from "./markdown/types.js";
|
|
|
5
5
|
* A template parameter has no top-level key — its template's keys are the keys, so it
|
|
6
6
|
* flattens to one entry per key (shapeName/template stay manifest-internal). An
|
|
7
7
|
* image parameter is a single key.
|
|
8
|
-
*
|
|
9
|
-
* A template parameter's `limit` is deliberately NOT projected here: it measures the
|
|
10
|
-
* expanded run text (parameter-level), so surfacing it per key would misrepresent
|
|
11
|
-
* it as per-key. How to advertise a parameter-level limit is a Phase 2 decision;
|
|
12
|
-
* until then it stays manifest-internal.
|
|
13
8
|
*/
|
|
14
9
|
function stripParameter(param) {
|
|
15
10
|
switch (param.type) {
|
|
@@ -32,11 +27,9 @@ function stripSlot(slot) {
|
|
|
32
27
|
const result = { key: slot.key, accepts: slot.accepts.map((b) => b.type) };
|
|
33
28
|
if (slot.required)
|
|
34
29
|
result.required = true;
|
|
35
|
-
if (slot.limit)
|
|
36
|
-
result.limit = slot.limit;
|
|
37
30
|
return result;
|
|
38
31
|
}
|
|
39
|
-
export function generateManifest(config
|
|
32
|
+
export function generateManifest(config) {
|
|
40
33
|
const layouts = config.layouts.map((layout) => {
|
|
41
34
|
const ml = {
|
|
42
35
|
name: layout.name,
|
|
@@ -46,10 +39,6 @@ export function generateManifest(config, options) {
|
|
|
46
39
|
};
|
|
47
40
|
if (layout.description !== undefined)
|
|
48
41
|
ml.description = layout.description;
|
|
49
|
-
if (layout.whenToUse !== undefined)
|
|
50
|
-
ml.whenToUse = layout.whenToUse;
|
|
51
|
-
if (layout.whenNotToUse !== undefined)
|
|
52
|
-
ml.whenNotToUse = layout.whenNotToUse;
|
|
53
42
|
return ml;
|
|
54
43
|
});
|
|
55
44
|
const assets = {};
|
|
@@ -61,8 +50,6 @@ export function generateManifest(config, options) {
|
|
|
61
50
|
type: entry.type,
|
|
62
51
|
description: entry.description,
|
|
63
52
|
};
|
|
64
|
-
if (entry.whenToUse)
|
|
65
|
-
manifestEntry.whenToUse = entry.whenToUse;
|
|
66
53
|
assets[category][name] = manifestEntry;
|
|
67
54
|
}
|
|
68
55
|
}
|
|
@@ -70,9 +57,6 @@ export function generateManifest(config, options) {
|
|
|
70
57
|
version: 1,
|
|
71
58
|
layouts,
|
|
72
59
|
assets,
|
|
73
|
-
build: {
|
|
74
|
-
command: options.build.command,
|
|
75
|
-
},
|
|
76
60
|
};
|
|
77
61
|
return JSON.stringify(manifest, null, 2);
|
|
78
62
|
}
|
|
@@ -6,7 +6,7 @@ export declare const MERMAID_LANG = "mermaid";
|
|
|
6
6
|
/**
|
|
7
7
|
* Recognize a ```mermaid fenced block at a region's top level, folding it to an
|
|
8
8
|
* Image fill, and compile it by rendering the definition to a PNG (cached under
|
|
9
|
-
* `<
|
|
9
|
+
* `<rootDir>/.tycoslide-cache/mermaid/<hash>.png`, in the theme directory) and wrapping it as an
|
|
10
10
|
* ImageFill. Fit is always `contain` — mermaid diagrams are shown in their
|
|
11
11
|
* entirety. Resolution is strict: the theme MUST carry a `mermaid` block, MUST
|
|
12
12
|
* declare a `mermaidVariant`, and that variant MUST exist — each missing piece
|
|
@@ -14,7 +14,7 @@ export const MERMAID_LANG = "mermaid";
|
|
|
14
14
|
/**
|
|
15
15
|
* Recognize a ```mermaid fenced block at a region's top level, folding it to an
|
|
16
16
|
* Image fill, and compile it by rendering the definition to a PNG (cached under
|
|
17
|
-
* `<
|
|
17
|
+
* `<rootDir>/.tycoslide-cache/mermaid/<hash>.png`, in the theme directory) and wrapping it as an
|
|
18
18
|
* ImageFill. Fit is always `contain` — mermaid diagrams are shown in their
|
|
19
19
|
* entirety. Resolution is strict: the theme MUST carry a `mermaid` block, MUST
|
|
20
20
|
* declare a `mermaidVariant`, and that variant MUST exist — each missing piece
|
|
@@ -69,7 +69,7 @@ function hashKey(definition, variantName, renderConfig, fonts) {
|
|
|
69
69
|
return hash.digest("hex").slice(0, 16);
|
|
70
70
|
}
|
|
71
71
|
function ensureCacheDir(config) {
|
|
72
|
-
const base = resolve(config.
|
|
72
|
+
const base = resolve(config.rootDir, ".tycoslide-cache", "mermaid");
|
|
73
73
|
mkdirSync(base, { recursive: true });
|
|
74
74
|
return base;
|
|
75
75
|
}
|
|
@@ -198,7 +198,7 @@ ${fontFaceCss(fonts)}
|
|
|
198
198
|
</body></html>`;
|
|
199
199
|
// Serve the page from a real file:// URL, not setContent — an about:blank
|
|
200
200
|
// origin can't fetch the file:// font resources (@font-face silently fails),
|
|
201
|
-
// whereas a file://-origin document loads them.
|
|
201
|
+
// whereas a file://-origin document loads them.
|
|
202
202
|
const htmlPath = `${outputPath}.html`;
|
|
203
203
|
const { chromium } = await import("playwright");
|
|
204
204
|
let browser;
|
|
@@ -16,7 +16,7 @@ function compileTable(node) {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
/** One table cell → a StyledParagraph. An empty cell keeps a single empty run so
|
|
19
|
-
* downstream code sees a run to style
|
|
19
|
+
* downstream code always sees a run to style. */
|
|
20
20
|
function cellParagraph(cell) {
|
|
21
21
|
const runs = walkPhrasingChildren(cell.children, {});
|
|
22
22
|
return { runs: runs.length > 0 ? runs : [{ text: "" }] };
|
|
@@ -64,12 +64,11 @@ function listParagraphs(list, level) {
|
|
|
64
64
|
return out;
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
|
-
* Split a paragraph's runs on newlines into one `TextRun[]` per source line
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* `
|
|
71
|
-
*
|
|
72
|
-
* and empty lines produce no paragraph, matching the old blank-line filter.
|
|
67
|
+
* Split a paragraph's runs on newlines into one `TextRun[]` per source line, so
|
|
68
|
+
* one source line becomes one StyledParagraph. Both a soft break (`\n` inside a
|
|
69
|
+
* text run) and a markdown hard break (a `break` node the inline walk emits as a
|
|
70
|
+
* `"\n"` run in block context) split here. Empty segments (a style boundary
|
|
71
|
+
* landing on a line edge) are dropped, and empty lines produce no paragraph.
|
|
73
72
|
*/
|
|
74
73
|
function splitRunsIntoParagraphs(runs) {
|
|
75
74
|
const lines = [];
|
|
@@ -18,7 +18,7 @@ export declare function toImageFill(path: string, type: AssetType): ImageFill;
|
|
|
18
18
|
* returned unchanged — callers that already produce absolute paths (or don't need
|
|
19
19
|
* resolution, e.g. unit tests) rely on the pass-through. When set, relative paths
|
|
20
20
|
* are resolved to absolute via `path.resolve(rootDir, path)`; absolute paths pass
|
|
21
|
-
* through. `config.codeTheme` / `config.mermaid` / `config.mermaidVariant`
|
|
22
|
-
*
|
|
21
|
+
* through. `config.codeTheme` / `config.mermaid` / `config.mermaidVariant` feed
|
|
22
|
+
* the code and mermaid compiles.
|
|
23
23
|
*/
|
|
24
24
|
export declare function compileDeck(doc: ParsedDocument, config: CompilerConfig): Promise<CompilerDeck>;
|
|
@@ -18,7 +18,7 @@ const FIT_FOR = {
|
|
|
18
18
|
export function toImageFill(path, type) {
|
|
19
19
|
return { type: SlotType.Image, path, fit: FIT_FOR[type] };
|
|
20
20
|
}
|
|
21
|
-
const KNOWN_GLOBAL_KEYS = new Set([RESERVED_KEY.THEME
|
|
21
|
+
const KNOWN_GLOBAL_KEYS = new Set([RESERVED_KEY.THEME]);
|
|
22
22
|
// Anchored whole-field reference: the entire value is `$category.name` or it is
|
|
23
23
|
// not a reference at all. Anchored ⇒ no escaping concerns.
|
|
24
24
|
const ASSET_REF_RE = /^\$([a-zA-Z]\w*)\.([a-zA-Z]\w*)$/;
|
|
@@ -50,8 +50,8 @@ function resolveImagePath(rootDir, path) {
|
|
|
50
50
|
/**
|
|
51
51
|
* Assert no two layouts sample the same base slide. In the sampled-composition
|
|
52
52
|
* model one layout is one sampled base slide's region-arrangement, so a shared
|
|
53
|
-
* `slideNumber` means
|
|
54
|
-
*
|
|
53
|
+
* `slideNumber` means two layouts on one physical slide that should be one
|
|
54
|
+
* multi-`accepts` layout.
|
|
55
55
|
* `sourceSlide` inside a slot's `accepts` block is a distinct concept (a
|
|
56
56
|
* specimen source for one content type) and is legitimately reused across
|
|
57
57
|
* layouts, so it is deliberately not checked here.
|
|
@@ -123,7 +123,7 @@ function validateLayout(layout) {
|
|
|
123
123
|
}
|
|
124
124
|
async function compileStep(slide, config, assetTypeByPath, resolveAssetRef) {
|
|
125
125
|
const { layouts, rootDir } = config;
|
|
126
|
-
const { frontmatter,
|
|
126
|
+
const { frontmatter, slots, index } = slide;
|
|
127
127
|
const layout = frontmatter[RESERVED_KEY.LAYOUT];
|
|
128
128
|
if (layout === undefined) {
|
|
129
129
|
throw new Error(`Slide ${index}: missing required "${RESERVED_KEY.LAYOUT}" in frontmatter`);
|
|
@@ -211,26 +211,6 @@ async function compileStep(slide, config, assetTypeByPath, resolveAssetRef) {
|
|
|
211
211
|
lines: templateToSegments(templateParam.template, supplied, templateParam.shapeName),
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
|
-
// The default body region resolves against the layout's body slot.
|
|
215
|
-
if (body.trim()) {
|
|
216
|
-
const bodySlot = slotsByKey.get(RESERVED_KEY.BODY);
|
|
217
|
-
if (!bodySlot) {
|
|
218
|
-
throw new Error(`Slide ${index}: layout "${layoutName}" does not accept body content. Valid slots: ${[...slotsByKey.keys()].join(", ")}`);
|
|
219
|
-
}
|
|
220
|
-
const parsedBody = parseSlotContent(body, {
|
|
221
|
-
resolveAssetRef,
|
|
222
|
-
layoutName,
|
|
223
|
-
slideIdx: index,
|
|
224
|
-
source: "body content",
|
|
225
|
-
config,
|
|
226
|
-
layoutVariant: layoutDef.variant,
|
|
227
|
-
});
|
|
228
|
-
// Validate the slot accepts this region's type BEFORE running the (possibly
|
|
229
|
-
// expensive — Shiki, Playwright) fill: a mismatched region fails fast without
|
|
230
|
-
// spinning up a renderer.
|
|
231
|
-
assertSlotRegion(bodySlot, parsedBody.acceptType, layoutName, index, "body content");
|
|
232
|
-
content[RESERVED_KEY.BODY] = await parsedBody.fill();
|
|
233
|
-
}
|
|
234
214
|
// `::name::` regions resolve against the layout's slots.
|
|
235
215
|
for (const [name, text] of Object.entries(slots)) {
|
|
236
216
|
const slot = slotsByKey.get(name);
|
|
@@ -281,8 +261,8 @@ async function compileStep(slide, config, assetTypeByPath, resolveAssetRef) {
|
|
|
281
261
|
* returned unchanged — callers that already produce absolute paths (or don't need
|
|
282
262
|
* resolution, e.g. unit tests) rely on the pass-through. When set, relative paths
|
|
283
263
|
* are resolved to absolute via `path.resolve(rootDir, path)`; absolute paths pass
|
|
284
|
-
* through. `config.codeTheme` / `config.mermaid` / `config.mermaidVariant`
|
|
285
|
-
*
|
|
264
|
+
* through. `config.codeTheme` / `config.mermaid` / `config.mermaidVariant` feed
|
|
265
|
+
* the code and mermaid compiles.
|
|
286
266
|
*/
|
|
287
267
|
export async function compileDeck(doc, config) {
|
|
288
268
|
const { layouts, rootDir, assets } = config;
|
|
@@ -333,10 +313,5 @@ export async function compileDeck(doc, config) {
|
|
|
333
313
|
for (const slide of doc.slides) {
|
|
334
314
|
steps.push(await compileStep(slide, config, assetTypeByPath, resolveAssetRef));
|
|
335
315
|
}
|
|
336
|
-
|
|
337
|
-
const output = doc.global[RESERVED_KEY.OUTPUT];
|
|
338
|
-
if (output !== undefined) {
|
|
339
|
-
deck.output = String(output);
|
|
340
|
-
}
|
|
341
|
-
return deck;
|
|
316
|
+
return { theme: String(theme), steps };
|
|
342
317
|
}
|
package/dist/markdown/index.d.ts
CHANGED
|
@@ -9,5 +9,5 @@ export { parseRegion } from "./mdast.js";
|
|
|
9
9
|
export { loadThemeConfig, parseThemeConfig, ThemeConfigSchema } from "./schema/themeConfigSchema.js";
|
|
10
10
|
export type { ParsedDocument, RawSlide } from "./slideParser.js";
|
|
11
11
|
export { parseSlideDocument } from "./slideParser.js";
|
|
12
|
-
export type { AssetCatalog, AssetEntry, CompilerBlock, CompilerConfig, CompilerDeck, CompilerDeckStep, CompilerImageParameter, CompilerLayout, CompilerParameter, CompilerSlot, CompilerTemplateParameter, CompilerThemeConfig, EngineFill,
|
|
12
|
+
export type { AssetCatalog, AssetEntry, CompilerBlock, CompilerConfig, CompilerDeck, CompilerDeckStep, CompilerImageParameter, CompilerLayout, CompilerParameter, CompilerSlot, CompilerTemplateParameter, CompilerThemeConfig, EngineFill, } from "./types.js";
|
|
13
13
|
export { AcceptType, AssetType, ParameterType, RESERVED_KEY } from "./types.js";
|
|
@@ -15,8 +15,8 @@ import type { CompilerLayout } from "../types.js";
|
|
|
15
15
|
* and `.optional()` (`required` is per-parameter, enforced in `compileStep`, not
|
|
16
16
|
* "all a template's keys present"). Value-typing and required-encoding are deliberate
|
|
17
17
|
* later commits.
|
|
18
|
-
* The strict object IS the unknown-key check
|
|
19
|
-
*
|
|
18
|
+
* The strict object IS the unknown-key check: a stray frontmatter key throws
|
|
19
|
+
* instead of being silently ignored.
|
|
20
20
|
*/
|
|
21
21
|
export declare function deckFrontmatterSchema(layout: CompilerLayout): z.ZodObject<{
|
|
22
22
|
[x: string]: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
@@ -17,8 +17,8 @@ import { strict } from "./strict.js";
|
|
|
17
17
|
* and `.optional()` (`required` is per-parameter, enforced in `compileStep`, not
|
|
18
18
|
* "all a template's keys present"). Value-typing and required-encoding are deliberate
|
|
19
19
|
* later commits.
|
|
20
|
-
* The strict object IS the unknown-key check
|
|
21
|
-
*
|
|
20
|
+
* The strict object IS the unknown-key check: a stray frontmatter key throws
|
|
21
|
+
* instead of being silently ignored.
|
|
22
22
|
*/
|
|
23
23
|
export function deckFrontmatterSchema(layout) {
|
|
24
24
|
const shape = {};
|
|
@@ -5,19 +5,12 @@ export declare const ThemeConfigSchema: z.ZodObject<{
|
|
|
5
5
|
name: z.ZodString;
|
|
6
6
|
slideNumber: z.ZodNumber;
|
|
7
7
|
description: z.ZodOptional<z.ZodString>;
|
|
8
|
-
whenToUse: z.ZodOptional<z.ZodString>;
|
|
9
|
-
whenNotToUse: z.ZodOptional<z.ZodString>;
|
|
10
8
|
variant: z.ZodOptional<z.ZodEnum<{
|
|
11
9
|
light: "light";
|
|
12
10
|
dark: "dark";
|
|
13
11
|
}>>;
|
|
14
12
|
parameters: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
15
13
|
shapeName: z.ZodString;
|
|
16
|
-
limit: z.ZodOptional<z.ZodObject<{
|
|
17
|
-
maxChars: z.ZodOptional<z.ZodNumber>;
|
|
18
|
-
maxLines: z.ZodOptional<z.ZodNumber>;
|
|
19
|
-
maxItems: z.ZodOptional<z.ZodNumber>;
|
|
20
|
-
}, z.core.$strict>>;
|
|
21
14
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
22
15
|
type: z.ZodLiteral<"template">;
|
|
23
16
|
template: z.ZodString;
|
|
@@ -29,27 +22,27 @@ export declare const ThemeConfigSchema: z.ZodObject<{
|
|
|
29
22
|
}, z.core.$strict>], "type">>;
|
|
30
23
|
slots: z.ZodArray<z.ZodObject<{
|
|
31
24
|
key: z.ZodString;
|
|
32
|
-
accepts: z.ZodArray<z.ZodObject<{
|
|
33
|
-
type: z.
|
|
34
|
-
text: "text";
|
|
35
|
-
table: "table";
|
|
36
|
-
image: "image";
|
|
37
|
-
}>;
|
|
25
|
+
accepts: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
26
|
+
type: z.ZodLiteral<"text">;
|
|
38
27
|
sourceSlide: z.ZodNumber;
|
|
39
28
|
shapeName: z.ZodString;
|
|
40
29
|
startAt: z.ZodOptional<z.ZodNumber>;
|
|
41
|
-
}, z.core.$strict
|
|
30
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
31
|
+
type: z.ZodLiteral<"table">;
|
|
32
|
+
sourceSlide: z.ZodNumber;
|
|
33
|
+
shapeName: z.ZodString;
|
|
34
|
+
bodyRows: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
35
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
36
|
+
type: z.ZodLiteral<"image">;
|
|
37
|
+
sourceSlide: z.ZodNumber;
|
|
38
|
+
shapeName: z.ZodString;
|
|
39
|
+
}, z.core.$strict>], "type">>;
|
|
42
40
|
frame: z.ZodOptional<z.ZodObject<{
|
|
43
41
|
x: z.ZodNumber;
|
|
44
42
|
y: z.ZodNumber;
|
|
45
43
|
cx: z.ZodNumber;
|
|
46
44
|
cy: z.ZodNumber;
|
|
47
45
|
}, z.core.$strict>>;
|
|
48
|
-
limit: z.ZodOptional<z.ZodObject<{
|
|
49
|
-
maxChars: z.ZodOptional<z.ZodNumber>;
|
|
50
|
-
maxLines: z.ZodOptional<z.ZodNumber>;
|
|
51
|
-
maxItems: z.ZodOptional<z.ZodNumber>;
|
|
52
|
-
}, z.core.$strict>>;
|
|
53
46
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
54
47
|
}, z.core.$strict>>;
|
|
55
48
|
}, z.core.$strict>>;
|
|
@@ -61,10 +54,8 @@ export declare const ThemeConfigSchema: z.ZodObject<{
|
|
|
61
54
|
background: "background";
|
|
62
55
|
}>;
|
|
63
56
|
description: z.ZodString;
|
|
64
|
-
whenToUse: z.ZodOptional<z.ZodString>;
|
|
65
57
|
}, z.core.$strict>>>;
|
|
66
58
|
template: z.ZodString;
|
|
67
|
-
outputDir: z.ZodOptional<z.ZodString>;
|
|
68
59
|
mermaid: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
69
60
|
primary: z.ZodString;
|
|
70
61
|
primaryContrast: z.ZodString;
|