@tycoworks/tycoslide 0.7.0 → 0.8.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 +6 -6
- package/SKILL.md +2 -1
- package/dist/cli.js +5 -94
- package/dist/engine/dom.d.ts +5 -0
- package/dist/engine/dom.js +19 -3
- package/dist/engine/fillers/filler.js +1 -4
- package/dist/engine/fillers/image.d.ts +46 -7
- package/dist/engine/fillers/image.js +78 -36
- package/dist/engine/fillers/table.d.ts +4 -3
- package/dist/engine/fillers/table.js +58 -9
- package/dist/engine/generate.d.ts +11 -1
- package/dist/engine/generate.js +84 -2
- package/dist/engine/index.d.ts +2 -1
- package/dist/engine/index.js +1 -1
- package/dist/engine/notes.d.ts +76 -0
- package/dist/engine/notes.js +313 -0
- package/dist/engine/types.d.ts +18 -15
- package/dist/engine/types.js +11 -7
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -9
- package/dist/manifest.js +3 -5
- package/dist/markdown/deckCompiler.d.ts +5 -14
- package/dist/markdown/deckCompiler.js +43 -20
- package/dist/markdown/index.d.ts +3 -3
- package/dist/markdown/index.js +3 -3
- package/dist/markdown/resolvers/mermaid.js +19 -27
- package/dist/markdown/types.d.ts +20 -6
- package/dist/markdown/types.js +12 -1
- package/package.json +3 -2
- package/syntax.md +25 -5
package/dist/index.js
CHANGED
|
@@ -42,6 +42,8 @@ export async function resolveDeck(deck, config) {
|
|
|
42
42
|
const resolvedStep = { layout: step.layout };
|
|
43
43
|
if (step.content)
|
|
44
44
|
resolvedStep.content = narrowContent(step.content);
|
|
45
|
+
if (step.notes !== undefined)
|
|
46
|
+
resolvedStep.notes = step.notes;
|
|
45
47
|
return resolvedStep;
|
|
46
48
|
}),
|
|
47
49
|
};
|
|
@@ -72,12 +74,8 @@ function toEngineSlot(slot) {
|
|
|
72
74
|
result.startAt = slot.startAt;
|
|
73
75
|
return result;
|
|
74
76
|
}
|
|
75
|
-
case CompilerSlotType.Table:
|
|
76
|
-
|
|
77
|
-
if (slot.columns !== undefined)
|
|
78
|
-
result.columns = slot.columns;
|
|
79
|
-
return result;
|
|
80
|
-
}
|
|
77
|
+
case CompilerSlotType.Table:
|
|
78
|
+
return { key: slot.key, shapeName: slot.shapeName, type: SlotType.Table };
|
|
81
79
|
case CompilerSlotType.Code:
|
|
82
80
|
// Highlighter resolves the code fence into StyledParagraph[]; engine
|
|
83
81
|
// fills it via fillText.
|
|
@@ -135,12 +133,12 @@ export function toEngineConfig(config) {
|
|
|
135
133
|
* Mermaid PNGs are cached under `<outputDir>/.tycoslide-cache/mermaid/` so no
|
|
136
134
|
* post-write cleanup is needed.
|
|
137
135
|
*/
|
|
138
|
-
export async function buildDeck(deck, config) {
|
|
136
|
+
export async function buildDeck(deck, config, options = {}) {
|
|
139
137
|
const resolved = await resolveDeck(deck, config);
|
|
140
|
-
await generate(resolved, toEngineConfig(config));
|
|
138
|
+
await generate(resolved, toEngineConfig(config), options);
|
|
141
139
|
}
|
|
142
140
|
// Engine — primitives-only public surface.
|
|
143
|
-
export {
|
|
141
|
+
export { fillImage, fillTable, fillTemplate, fillText, generate, SlotType } from "./engine/index.js";
|
|
144
142
|
export { generateManifest } from "./manifest.js";
|
|
145
143
|
// Markdown / Compiler
|
|
146
144
|
export { CompilerSlotType, compileMarkdownDeck, FenceType, ParameterType, resolveFences } from "./markdown/index.js";
|
package/dist/manifest.js
CHANGED
|
@@ -21,7 +21,7 @@ function stripParameter(param) {
|
|
|
21
21
|
return result;
|
|
22
22
|
});
|
|
23
23
|
case ParameterType.Image: {
|
|
24
|
-
const result = { key: param.key, type: param.type
|
|
24
|
+
const result = { key: param.key, type: param.type };
|
|
25
25
|
if (param.required)
|
|
26
26
|
result.required = true;
|
|
27
27
|
if (param.limit)
|
|
@@ -38,11 +38,8 @@ function stripSlot(slot) {
|
|
|
38
38
|
result.limit = slot.limit;
|
|
39
39
|
switch (slot.type) {
|
|
40
40
|
case CompilerSlotType.Text:
|
|
41
|
-
// startAt is a fill hint, not a manifest surface concern.
|
|
42
|
-
break;
|
|
43
41
|
case CompilerSlotType.Table:
|
|
44
|
-
|
|
45
|
-
result.columns = slot.columns;
|
|
42
|
+
// No fields beyond the shared key/type/required/limit.
|
|
46
43
|
break;
|
|
47
44
|
case CompilerSlotType.Code:
|
|
48
45
|
result.codeTheme = slot.codeTheme;
|
|
@@ -69,6 +66,7 @@ export function generateManifest(config, options) {
|
|
|
69
66
|
for (const [name, entry] of Object.entries(entries)) {
|
|
70
67
|
const manifestEntry = {
|
|
71
68
|
path: entry.path,
|
|
69
|
+
type: entry.type,
|
|
72
70
|
description: entry.description,
|
|
73
71
|
};
|
|
74
72
|
if (entry.whenToUse)
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ImageFill } from "../engine/index.js";
|
|
2
2
|
import type { ParsedDocument } from "./slideParser.js";
|
|
3
|
-
import { type
|
|
4
|
-
|
|
5
|
-
* Wrap a resolved image path as an ImageFill using the parameter's declared
|
|
6
|
-
* fit. `param` is narrowed to `CompilerImageParameter` — only image parameters
|
|
7
|
-
* carry a raw path. Mermaid slots never reach here (their content flows as
|
|
8
|
-
* fences through MermaidResolver, which wraps the rendered PNG directly).
|
|
9
|
-
*
|
|
10
|
-
* `path` must be absolute. Callers are responsible for resolution (see
|
|
11
|
-
* `resolve(rootDir, ...)` in the compiler / cli.ts).
|
|
12
|
-
*/
|
|
13
|
-
export declare function toImageFill(param: CompilerImageParameter, path: string): ImageFill;
|
|
3
|
+
import { type AssetCatalog, AssetType, type CompilerDeck, type CompilerLayout } from "./types.js";
|
|
4
|
+
export declare function toImageFill(path: string, type: AssetType): ImageFill;
|
|
14
5
|
/**
|
|
15
6
|
* Reserved keys in a deck's frontmatter — global (theme, output) and per-slide
|
|
16
7
|
* (layout, body). Exported so callers (e.g. cli.ts) reference the constants
|
|
@@ -21,6 +12,7 @@ export declare const RESERVED_KEY: {
|
|
|
21
12
|
readonly BODY: "body";
|
|
22
13
|
readonly OUTPUT: "output";
|
|
23
14
|
readonly THEME: "theme";
|
|
15
|
+
readonly NOTES: "notes";
|
|
24
16
|
};
|
|
25
17
|
/**
|
|
26
18
|
* Compile a parsed deck document against a set of layouts.
|
|
@@ -32,5 +24,4 @@ export declare const RESERVED_KEY: {
|
|
|
32
24
|
* can rely on the pass-through. When provided, relative paths are resolved
|
|
33
25
|
* to absolute via `path.resolve(rootDir, path)`; absolute paths pass through.
|
|
34
26
|
*/
|
|
35
|
-
export declare function compileDeck(doc: ParsedDocument, layouts: CompilerLayout[], rootDir?: string): CompilerDeck;
|
|
36
|
-
export { FitMode };
|
|
27
|
+
export declare function compileDeck(doc: ParsedDocument, layouts: CompilerLayout[], rootDir?: string, assets?: AssetCatalog): CompilerDeck;
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { FILLERS,
|
|
2
|
+
import { FILLERS, ImageFit, SlotType } from "../engine/index.js";
|
|
3
3
|
import { parseGfmTable, parseStyledParagraph } from "./parsers.js";
|
|
4
4
|
import { RESOLVERS } from "./resolvers/resolver.js";
|
|
5
5
|
import { templateKeys, templateToSegments } from "./textTemplate.js";
|
|
6
|
-
import { CompilerSlotType, FenceType, ParameterType, } from "./types.js";
|
|
6
|
+
import { AssetType, CompilerSlotType, FenceType, ParameterType, } from "./types.js";
|
|
7
7
|
/**
|
|
8
|
-
* Wrap
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* fences through MermaidResolver, which wraps the rendered PNG directly).
|
|
12
|
-
*
|
|
13
|
-
* `path` must be absolute. Callers are responsible for resolution (see
|
|
14
|
-
* `resolve(rootDir, ...)` in the compiler / cli.ts).
|
|
8
|
+
* Wrap an absolute image path as an ImageFill, expanding the resolved asset
|
|
9
|
+
* `type` into the engine's scaling constraints. Callers resolve the path (see
|
|
10
|
+
* `resolveImagePath`) and the type (from the catalog) first.
|
|
15
11
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
/** Map each semantic asset type to the engine's object-fit directive. */
|
|
13
|
+
const FIT_FOR = {
|
|
14
|
+
[AssetType.Icon]: ImageFit.ScaleDown,
|
|
15
|
+
[AssetType.Image]: ImageFit.Contain,
|
|
16
|
+
[AssetType.Background]: ImageFit.Cover,
|
|
17
|
+
};
|
|
18
|
+
export function toImageFill(path, type) {
|
|
19
|
+
return { type: SlotType.Image, path, fit: FIT_FOR[type] };
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Reserved keys in a deck's frontmatter — global (theme, output) and per-slide
|
|
@@ -26,6 +28,7 @@ export const RESERVED_KEY = {
|
|
|
26
28
|
BODY: "body",
|
|
27
29
|
OUTPUT: "output",
|
|
28
30
|
THEME: "theme",
|
|
31
|
+
NOTES: "notes",
|
|
29
32
|
};
|
|
30
33
|
const CODE_FENCE_RE = /^```(\w+)\n([\s\S]*?)```\s*$/;
|
|
31
34
|
function toTextFill(text) {
|
|
@@ -140,12 +143,18 @@ function validateLayout(layout) {
|
|
|
140
143
|
claimContentKey(slot.key, "slot");
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
|
-
function compileStep(slide, layouts, rootDir) {
|
|
146
|
+
function compileStep(slide, layouts, rootDir, assetTypeByPath) {
|
|
144
147
|
const { frontmatter, body, slots, index } = slide;
|
|
145
148
|
const layout = frontmatter[RESERVED_KEY.LAYOUT];
|
|
146
149
|
if (layout === undefined) {
|
|
147
150
|
throw new Error(`Slide ${index}: missing required "${RESERVED_KEY.LAYOUT}" in frontmatter`);
|
|
148
151
|
}
|
|
152
|
+
// Speaker notes are slide-level metadata, stripped from frontmatter before
|
|
153
|
+
// slot/param resolution — exactly like `layout`. Coerce to string if present.
|
|
154
|
+
// An empty `notes:` key parses as YAML null; treat it as absent (loose `==`)
|
|
155
|
+
// so it doesn't write the literal string "null".
|
|
156
|
+
const notesRaw = frontmatter[RESERVED_KEY.NOTES];
|
|
157
|
+
const notes = notesRaw == null ? undefined : String(notesRaw);
|
|
149
158
|
const layoutName = String(layout);
|
|
150
159
|
const layoutDef = layouts.find((l) => l.name === layoutName);
|
|
151
160
|
if (!layoutDef) {
|
|
@@ -174,11 +183,17 @@ function compileStep(slide, layouts, rootDir) {
|
|
|
174
183
|
// (gathered per parameter, expanded together once every line is read).
|
|
175
184
|
const valuesByTemplateParam = new Map();
|
|
176
185
|
for (const [key, value] of Object.entries(frontmatter)) {
|
|
177
|
-
if (key === RESERVED_KEY.LAYOUT)
|
|
186
|
+
if (key === RESERVED_KEY.LAYOUT || key === RESERVED_KEY.NOTES)
|
|
178
187
|
continue;
|
|
179
188
|
const image = imageByKey.get(key);
|
|
180
189
|
if (image) {
|
|
181
|
-
|
|
190
|
+
const imgPath = resolveImagePath(rootDir, String(value));
|
|
191
|
+
const assetType = assetTypeByPath.get(imgPath);
|
|
192
|
+
if (assetType === undefined) {
|
|
193
|
+
throw new Error(`Slide image "${image.key}": "${value}" has no asset-catalog entry, so no type. ` +
|
|
194
|
+
`Add it to the theme's assets with a type (icon | image | background).`);
|
|
195
|
+
}
|
|
196
|
+
content[image.key] = toImageFill(imgPath, assetType);
|
|
182
197
|
continue;
|
|
183
198
|
}
|
|
184
199
|
const templateParam = templateParamByKey.get(key);
|
|
@@ -248,7 +263,10 @@ function compileStep(slide, layouts, rootDir) {
|
|
|
248
263
|
// CompilerDeckStep.content values are MarkdownBlock — CodeFence and
|
|
249
264
|
// MermaidFence are legal in transit until the resolvers narrow them into
|
|
250
265
|
// StyledParagraph[] / ImageFill before the engine sees the deck.
|
|
251
|
-
|
|
266
|
+
const step = { layout: layoutName, content };
|
|
267
|
+
if (notes !== undefined)
|
|
268
|
+
step.notes = notes;
|
|
269
|
+
return step;
|
|
252
270
|
}
|
|
253
271
|
const KNOWN_GLOBAL_KEYS = new Set([RESERVED_KEY.THEME, RESERVED_KEY.OUTPUT]);
|
|
254
272
|
/**
|
|
@@ -261,7 +279,7 @@ const KNOWN_GLOBAL_KEYS = new Set([RESERVED_KEY.THEME, RESERVED_KEY.OUTPUT]);
|
|
|
261
279
|
* can rely on the pass-through. When provided, relative paths are resolved
|
|
262
280
|
* to absolute via `path.resolve(rootDir, path)`; absolute paths pass through.
|
|
263
281
|
*/
|
|
264
|
-
export function compileDeck(doc, layouts, rootDir = "") {
|
|
282
|
+
export function compileDeck(doc, layouts, rootDir = "", assets = {}) {
|
|
265
283
|
const theme = doc.global[RESERVED_KEY.THEME];
|
|
266
284
|
if (theme === undefined) {
|
|
267
285
|
throw new Error(`Missing required "${RESERVED_KEY.THEME}" in global frontmatter`);
|
|
@@ -274,9 +292,17 @@ export function compileDeck(doc, layouts, rootDir = "") {
|
|
|
274
292
|
// theme fails fast regardless of which layouts this deck's slides use.
|
|
275
293
|
for (const layout of layouts)
|
|
276
294
|
validateLayout(layout);
|
|
295
|
+
// Index each catalog asset's resolved path → its declared type, so an image
|
|
296
|
+
// filled by path inherits the scaling tolerance intrinsic to its pixels.
|
|
297
|
+
const assetTypeByPath = new Map();
|
|
298
|
+
for (const group of Object.values(assets)) {
|
|
299
|
+
for (const entry of Object.values(group)) {
|
|
300
|
+
assetTypeByPath.set(resolveImagePath(rootDir, entry.path), entry.type);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
277
303
|
const deck = {
|
|
278
304
|
theme: String(theme),
|
|
279
|
-
steps: doc.slides.map((slide) => compileStep(slide, layouts, rootDir)),
|
|
305
|
+
steps: doc.slides.map((slide) => compileStep(slide, layouts, rootDir, assetTypeByPath)),
|
|
280
306
|
};
|
|
281
307
|
const output = doc.global[RESERVED_KEY.OUTPUT];
|
|
282
308
|
if (output !== undefined) {
|
|
@@ -284,6 +310,3 @@ export function compileDeck(doc, layouts, rootDir = "") {
|
|
|
284
310
|
}
|
|
285
311
|
return deck;
|
|
286
312
|
}
|
|
287
|
-
// Re-export FitMode so callers that build ImageFills by hand can import it
|
|
288
|
-
// through the compiler surface without reaching into the engine.
|
|
289
|
-
export { FitMode };
|
package/dist/markdown/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CompilerDeck, CompilerLayout } from "./types.js";
|
|
2
|
-
export declare function compileMarkdownDeck(source: string, layouts: CompilerLayout[], rootDir?: string): CompilerDeck;
|
|
1
|
+
import type { AssetCatalog, CompilerDeck, CompilerLayout } from "./types.js";
|
|
2
|
+
export declare function compileMarkdownDeck(source: string, layouts: CompilerLayout[], rootDir?: string, assets?: AssetCatalog): CompilerDeck;
|
|
3
3
|
export { compileDeck, RESERVED_KEY } from "./deckCompiler.js";
|
|
4
4
|
export { parseGfmTable, parseInlineRuns, parseProseLine, parseStyledParagraph } from "./parsers.js";
|
|
5
5
|
export { CodeResolver, highlightCode, isCodeBlock } from "./resolvers/code.js";
|
|
@@ -10,4 +10,4 @@ export { isFence, RESOLVERS, resolveFences } from "./resolvers/resolver.js";
|
|
|
10
10
|
export type { ParsedDocument, RawSlide } from "./slideParser.js";
|
|
11
11
|
export { parseSlideDocument } from "./slideParser.js";
|
|
12
12
|
export type { AssetCatalog, AssetEntry, CodeFence, CompilerCodeSlot, CompilerConfig, CompilerDeck, CompilerDeckStep, CompilerImageParameter, CompilerLayout, CompilerMermaidSlot, CompilerParameter, CompilerSlot, CompilerTableSlot, CompilerTemplateParameter, CompilerTextSlot, CompilerThemeConfig, MarkdownBlock, MermaidFence, ResolvedCompilerDeck, ResolvedCompilerDeckStep, } from "./types.js";
|
|
13
|
-
export { CompilerSlotType, FenceType, ParameterType } from "./types.js";
|
|
13
|
+
export { AssetType, CompilerSlotType, FenceType, ParameterType } from "./types.js";
|
package/dist/markdown/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { compileDeck } from "./deckCompiler.js";
|
|
2
2
|
import { parseSlideDocument } from "./slideParser.js";
|
|
3
|
-
export function compileMarkdownDeck(source, layouts, rootDir = "") {
|
|
3
|
+
export function compileMarkdownDeck(source, layouts, rootDir = "", assets = {}) {
|
|
4
4
|
const doc = parseSlideDocument(source);
|
|
5
|
-
return compileDeck(doc, layouts, rootDir);
|
|
5
|
+
return compileDeck(doc, layouts, rootDir, assets);
|
|
6
6
|
}
|
|
7
7
|
export { compileDeck, RESERVED_KEY } from "./deckCompiler.js";
|
|
8
8
|
export { parseGfmTable, parseInlineRuns, parseProseLine, parseStyledParagraph } from "./parsers.js";
|
|
@@ -10,4 +10,4 @@ export { CodeResolver, highlightCode, isCodeBlock } from "./resolvers/code.js";
|
|
|
10
10
|
export { isMermaidBlock, MermaidResolver } from "./resolvers/mermaid.js";
|
|
11
11
|
export { isFence, RESOLVERS, resolveFences } from "./resolvers/resolver.js";
|
|
12
12
|
export { parseSlideDocument } from "./slideParser.js";
|
|
13
|
-
export { CompilerSlotType, FenceType, ParameterType } from "./types.js";
|
|
13
|
+
export { AssetType, CompilerSlotType, FenceType, ParameterType } from "./types.js";
|
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
2
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { tmpdir } from "node:os";
|
|
5
4
|
import { join, resolve } from "node:path";
|
|
6
|
-
import {
|
|
5
|
+
import { ImageFit, SlotType } from "../../engine/index.js";
|
|
7
6
|
import { CompilerSlotType, FenceType } from "../types.js";
|
|
8
7
|
import { buildMermaidRenderConfig, injectClassDefs, validateMermaidDefinition, } from "./mermaidTheme.js";
|
|
9
8
|
/** Discriminator for MermaidFence values. Doubles as `MermaidResolver.matches`. */
|
|
10
9
|
export function isMermaidBlock(v) {
|
|
11
10
|
return (typeof v === "object" && v !== null && !Array.isArray(v) && v.type === FenceType.Mermaid);
|
|
12
11
|
}
|
|
13
|
-
function findMmdc() {
|
|
14
|
-
try {
|
|
15
|
-
const resolved = execFileSync("npx", ["--no-install", "which", "mmdc"], {
|
|
16
|
-
encoding: "utf-8",
|
|
17
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
18
|
-
}).trim();
|
|
19
|
-
if (resolved)
|
|
20
|
-
return resolved;
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
// fall through
|
|
24
|
-
}
|
|
25
|
-
throw new Error("mermaid-cli is required for mermaid diagrams. Install it: npm i -D @mermaid-js/mermaid-cli");
|
|
26
|
-
}
|
|
27
12
|
function hashKey(definition, variantName) {
|
|
28
13
|
return createHash("sha256").update(variantName).update("\n").update(definition).digest("hex").slice(0, 16);
|
|
29
14
|
}
|
|
@@ -32,7 +17,7 @@ function ensureCacheDir(config) {
|
|
|
32
17
|
mkdirSync(base, { recursive: true });
|
|
33
18
|
return base;
|
|
34
19
|
}
|
|
35
|
-
function renderOne(definition, variantName, variant, cacheDir
|
|
20
|
+
async function renderOne(definition, variantName, variant, cacheDir) {
|
|
36
21
|
const validated = validateMermaidDefinition(definition);
|
|
37
22
|
const processed = injectClassDefs(validated, variant.accents, variant.accentOpacity, variant.accentTextColor, variant.surface, variant.groupCornerRadius);
|
|
38
23
|
const key = hashKey(processed, variantName);
|
|
@@ -40,17 +25,25 @@ function renderOne(definition, variantName, variant, cacheDir, mmdcPath) {
|
|
|
40
25
|
if (existsSync(outputPath))
|
|
41
26
|
return outputPath;
|
|
42
27
|
const config = buildMermaidRenderConfig(variant);
|
|
43
|
-
const
|
|
44
|
-
const configPath = join(scratch, `tycoslide-mermaid-config-${key}.json`);
|
|
45
|
-
const inputPath = join(scratch, `tycoslide-mermaid-${key}.mmd`);
|
|
46
|
-
writeFileSync(configPath, JSON.stringify(config));
|
|
28
|
+
const inputPath = join(tmpdir(), `tycoslide-mermaid-${key}.mmd`);
|
|
47
29
|
writeFileSync(inputPath, processed);
|
|
30
|
+
// mermaid-cli's programmatic API (lazy-imported so puppeteer only loads when a
|
|
31
|
+
// deck actually renders mermaid). Mirrors the old CLI flags: --configFile →
|
|
32
|
+
// mermaidConfig, -b transparent → backgroundColor, -s 2 → deviceScaleFactor.
|
|
33
|
+
const { run } = await import("@mermaid-js/mermaid-cli");
|
|
48
34
|
try {
|
|
49
|
-
|
|
35
|
+
await run(inputPath, outputPath, {
|
|
36
|
+
quiet: true,
|
|
37
|
+
outputFormat: "png",
|
|
38
|
+
parseMMDOptions: {
|
|
39
|
+
mermaidConfig: config,
|
|
40
|
+
backgroundColor: "transparent",
|
|
41
|
+
viewport: { width: 800, height: 600, deviceScaleFactor: 2 },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
50
44
|
}
|
|
51
45
|
catch (e) {
|
|
52
|
-
|
|
53
|
-
throw new Error(`Mermaid render failed:\n${stderr}`);
|
|
46
|
+
throw new Error(`Mermaid render failed:\n${e?.message ?? e}`);
|
|
54
47
|
}
|
|
55
48
|
return outputPath;
|
|
56
49
|
}
|
|
@@ -81,9 +74,8 @@ export const MermaidResolver = {
|
|
|
81
74
|
throw new Error(`Slide layout "${ctx.layout.name}": mermaid variant "${variantName}" not found in theme. ` +
|
|
82
75
|
`Available variants: ${Object.keys(config.mermaid).join(", ")}`);
|
|
83
76
|
}
|
|
84
|
-
const mmdcPath = findMmdc();
|
|
85
77
|
const cacheDir = ensureCacheDir(config);
|
|
86
|
-
const pngPath = renderOne(fence.definition, variantName, variant, cacheDir
|
|
87
|
-
return { type: SlotType.Image, path: pngPath, fit:
|
|
78
|
+
const pngPath = await renderOne(fence.definition, variantName, variant, cacheDir);
|
|
79
|
+
return { type: SlotType.Image, path: pngPath, fit: ImageFit.Contain };
|
|
88
80
|
},
|
|
89
81
|
};
|
package/dist/markdown/types.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ImageFill, type TableFill, type TemplateFill, type TextFill } from "../engine/index.js";
|
|
2
2
|
import type { MermaidConfig } from "./resolvers/mermaidTheme.js";
|
|
3
|
+
/**
|
|
4
|
+
* An asset's scaling/cropping tolerance, declared in the theme catalog. The
|
|
5
|
+
* compiler maps it to the engine's object-fit `fit`. `icon`: never enlarge,
|
|
6
|
+
* never crop. `image`: never crop, may scale. `background`: crop and scale freely.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AssetType: {
|
|
9
|
+
readonly Icon: "icon";
|
|
10
|
+
readonly Image: "image";
|
|
11
|
+
readonly Background: "background";
|
|
12
|
+
};
|
|
13
|
+
export type AssetType = (typeof AssetType)[keyof typeof AssetType];
|
|
3
14
|
/**
|
|
4
15
|
* A theme's declaration of a reusable image asset. Purely compiler-facing —
|
|
5
16
|
* the engine never sees this type; the compiler resolves an entry's `path`
|
|
@@ -8,6 +19,8 @@ import type { MermaidConfig } from "./resolvers/mermaidTheme.js";
|
|
|
8
19
|
*/
|
|
9
20
|
export type AssetEntry = {
|
|
10
21
|
path: string;
|
|
22
|
+
/** Required — a missing type is a fail-fast error. */
|
|
23
|
+
type: AssetType;
|
|
11
24
|
description: string;
|
|
12
25
|
whenToUse?: string;
|
|
13
26
|
};
|
|
@@ -86,6 +99,8 @@ export type MarkdownBlock = TextFill | TableFill | CodeFence | MermaidFence | Im
|
|
|
86
99
|
export type CompilerDeckStep = {
|
|
87
100
|
layout: string;
|
|
88
101
|
content?: Record<string, MarkdownBlock>;
|
|
102
|
+
/** Slide-level speaker notes, stripped from frontmatter. Plain text. */
|
|
103
|
+
notes?: string;
|
|
89
104
|
};
|
|
90
105
|
/**
|
|
91
106
|
* The intermediate deck shape produced by compileDeck. buildDeck runs the
|
|
@@ -104,6 +119,8 @@ export type CompilerDeck = {
|
|
|
104
119
|
export type ResolvedCompilerDeckStep = {
|
|
105
120
|
layout: string;
|
|
106
121
|
content?: Record<string, TextFill | TableFill | ImageFill | TemplateFill>;
|
|
122
|
+
/** Slide-level speaker notes, threaded through to the engine. Plain text. */
|
|
123
|
+
notes?: string;
|
|
107
124
|
};
|
|
108
125
|
/**
|
|
109
126
|
* A CompilerDeck whose steps have been passed through the resolvers via
|
|
@@ -155,11 +172,10 @@ export type CompilerTemplateParameter = CompilerShapeBase & {
|
|
|
155
172
|
/** The shape's text as one template with `{key}` placeholders; newlines are line breaks. */
|
|
156
173
|
template: string;
|
|
157
174
|
};
|
|
158
|
-
/** Image parameter: one frontmatter path filled by fillImage.
|
|
175
|
+
/** Image parameter: one frontmatter path filled by fillImage. Sizing/crop
|
|
176
|
+
* behaviour comes from the resolved asset's `type`, not the slot. */
|
|
159
177
|
export type CompilerImageParameter = CompilerSlotBase & {
|
|
160
178
|
type: typeof ParameterType.Image;
|
|
161
|
-
/** How the picture scales inside its frame (required — no silent default). */
|
|
162
|
-
fit: FitMode;
|
|
163
179
|
};
|
|
164
180
|
/**
|
|
165
181
|
* Compiler-facing parameter. A layout's frontmatter inputs — each written as a
|
|
@@ -175,8 +191,6 @@ export type CompilerTextSlot = CompilerSlotBase & {
|
|
|
175
191
|
/** Table shape backed by an `<a:tbl>` with header + data specimen rows. */
|
|
176
192
|
export type CompilerTableSlot = CompilerSlotBase & {
|
|
177
193
|
type: typeof CompilerSlotType.Table;
|
|
178
|
-
/** Enforced column count. */
|
|
179
|
-
columns?: number;
|
|
180
194
|
};
|
|
181
195
|
/**
|
|
182
196
|
* Text slot that consumes a fenced code block. The resolver (`CodeResolver`)
|
package/dist/markdown/types.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { SlotType
|
|
1
|
+
import { SlotType } from "../engine/index.js";
|
|
2
|
+
// ── Asset catalog (compiler / theme-metadata only) ───────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* An asset's scaling/cropping tolerance, declared in the theme catalog. The
|
|
5
|
+
* compiler maps it to the engine's object-fit `fit`. `icon`: never enlarge,
|
|
6
|
+
* never crop. `image`: never crop, may scale. `background`: crop and scale freely.
|
|
7
|
+
*/
|
|
8
|
+
export const AssetType = {
|
|
9
|
+
Icon: "icon",
|
|
10
|
+
Image: "image",
|
|
11
|
+
Background: "background",
|
|
12
|
+
};
|
|
2
13
|
// ── FenceType discriminator ───────────────────────────────────────────────────
|
|
3
14
|
/**
|
|
4
15
|
* Discriminator strings for the compiler-internal fence shapes produced by
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tycoworks/tycoslide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"format": "biome format --write ."
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"@mermaid-js/mermaid-cli": "^11.15.0",
|
|
26
|
+
"@xmldom/xmldom": "^0.9.10",
|
|
25
27
|
"commander": "^15.0.0",
|
|
26
28
|
"image-size": "^2.0.2",
|
|
27
29
|
"pptx-automizer": "^0.8.2",
|
|
@@ -35,7 +37,6 @@
|
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@biomejs/biome": "^2.5.1",
|
|
37
39
|
"@types/node": "^22.20.0",
|
|
38
|
-
"@xmldom/xmldom": "^0.9.10",
|
|
39
40
|
"typescript": "^5.8.0"
|
|
40
41
|
}
|
|
41
42
|
}
|
package/syntax.md
CHANGED
|
@@ -91,7 +91,7 @@ A layout advertises two kinds of author-facing input, split by one rule: **a par
|
|
|
91
91
|
"parameters": [
|
|
92
92
|
{ "key": "title", "type": "template" },
|
|
93
93
|
{ "key": "subtitle", "type": "template" },
|
|
94
|
-
{ "key": "logo", "type": "image", "
|
|
94
|
+
{ "key": "logo", "type": "image", "required": true }
|
|
95
95
|
],
|
|
96
96
|
"slots": [
|
|
97
97
|
{ "key": "body", "type": "text" },
|
|
@@ -114,7 +114,7 @@ Fill a parameter by putting a value under its key in the slide's frontmatter.
|
|
|
114
114
|
jobTitle: CEO, Acme Corp
|
|
115
115
|
```
|
|
116
116
|
The engine substitutes each value into the run that carries its style, so if the designer made the name bold and the job title grey, the filled name stays bold and the filled title stays grey.
|
|
117
|
-
- **`image`** -- a picture placeholder. Set it in frontmatter with the image path (from an asset catalog entry, or an absolute path).
|
|
117
|
+
- **`image`** -- a picture placeholder. Set it in frontmatter with the image path (from an asset catalog entry, or an absolute path). How it is scaled and cropped is set by the **asset's `type`** in the catalog (`icon` never enlarges past native and never crops; `image` fits the whole picture without cropping; `background` fills the frame and center-crops).
|
|
118
118
|
```yaml
|
|
119
119
|
hero: assets/diagrams/architecture.png
|
|
120
120
|
```
|
|
@@ -136,13 +136,13 @@ Fill a slot by writing a region in the body: the default (unmarked) region maps
|
|
|
136
136
|
```
|
|
137
137
|
````
|
|
138
138
|
The language tag (e.g. `sql`, `python`, `typescript`) is required -- it drives syntax highlighting. Colors are applied as native text runs in the output, not images.
|
|
139
|
-
- **`mermaid`** -- a mermaid diagram rendered as a themed PNG (see below). Written as a fenced `mermaid` region; the resulting PNG
|
|
139
|
+
- **`mermaid`** -- a mermaid diagram rendered as a themed PNG (see below). Written as a fenced `mermaid` region; the resulting PNG is shown contained (in its entirety).
|
|
140
140
|
|
|
141
141
|
---
|
|
142
142
|
|
|
143
143
|
## Mermaid diagrams
|
|
144
144
|
|
|
145
|
-
Mermaid diagrams are rendered as themed PNGs and delivered to any slot declared with `type: mermaid`. Write a fenced code block with the `mermaid` language tag in a named slot whose layout declares that slot as `type: mermaid` (with a `mermaidVariant` naming the theme's color variant). The resulting PNG behaves like any other image in the slot -- always shown in its entirety
|
|
145
|
+
Mermaid diagrams are rendered as themed PNGs and delivered to any slot declared with `type: mermaid`. Write a fenced code block with the `mermaid` language tag in a named slot whose layout declares that slot as `type: mermaid` (with a `mermaidVariant` naming the theme's color variant). The resulting PNG behaves like any other image in the slot -- always shown in its entirety.
|
|
146
146
|
|
|
147
147
|
To let the same physical slide accept either an image or a diagram, the theme author declares two layouts with the same `slideNumber` -- one exposing the fill as an `image` parameter (frontmatter path), one as a `mermaid` slot (a fenced region). Authors pick between them by naming the layout in frontmatter; the compiler routes content based on the layout's declaration, so there is no ambiguity.
|
|
148
148
|
|
|
@@ -214,7 +214,7 @@ hero: assets/diagrams/architecture.png
|
|
|
214
214
|
Each parameter or slot in the layout definition may declare:
|
|
215
215
|
- **`type`** (required) -- parameters: `template`, `image`; slots: `text`, `table`, `code`, `mermaid`.
|
|
216
216
|
- **`required: true`** -- the slide has no usable default and the build fails if the parameter/slot has no value (e.g. team-member photos, icon-grid icons, the quote logo). If you don't have a suitable image, ask the user for one.
|
|
217
|
-
-
|
|
217
|
+
- **image sizing** -- each catalog asset declares a `type` (`icon` | `image` | `background`) that determines how it is scaled and cropped: `icon` never enlarges past native and never crops; `image` fits the whole picture (no crop, may scale); `background` fills and center-crops. Mermaid renders as `image` (contained).
|
|
218
218
|
- **`codeTheme`** -- code slots only (required): the Shiki theme id used to syntax-highlight fenced code that lands in this slot (e.g. `"github-dark"`).
|
|
219
219
|
- **`mermaidVariant`** -- mermaid slots only (required): names the color variant from `theme.mermaid` (e.g. `"dark"`). See [Mermaid diagrams](#mermaid-diagrams) above.
|
|
220
220
|
|
|
@@ -222,6 +222,26 @@ Each layout also declares a `slideNumber` pointing at the physical slide in the
|
|
|
222
222
|
|
|
223
223
|
---
|
|
224
224
|
|
|
225
|
+
## Speaker notes
|
|
226
|
+
|
|
227
|
+
Any slide may carry a `notes:` key in its frontmatter -- a plain-text speaker-notes block attached to that slide's notes page. It is slide-level metadata, not a parameter or a slot: it is never routed to a shape and never appears on the slide face, only in the presenter/notes view.
|
|
228
|
+
|
|
229
|
+
Write multiple lines with a YAML block scalar (`|`); each line becomes one notes paragraph. Blank template notes on the underlying slide are always stripped, so only what you author here shows up.
|
|
230
|
+
|
|
231
|
+
```yaml
|
|
232
|
+
---
|
|
233
|
+
layout: Body
|
|
234
|
+
title: Key Achievements
|
|
235
|
+
notes: |
|
|
236
|
+
Open by thanking the regional teams.
|
|
237
|
+
Land the 23% number, then pause before the churn stat.
|
|
238
|
+
---
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
To build with all speaker notes omitted, pass `--no-notes` to `tycoslide build` (see [README](README.md#cli)).
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
225
245
|
## Slides with no body
|
|
226
246
|
|
|
227
247
|
Slides that have all their content in frontmatter (common for title slides, section dividers) need no body:
|