mulmocast 2.2.1 → 2.2.3
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/lib/slide/blocks.d.ts +7 -1
- package/lib/slide/blocks.js +118 -9
- package/lib/slide/layouts/big_quote.js +4 -4
- package/lib/slide/layouts/columns.js +9 -10
- package/lib/slide/layouts/comparison.js +5 -7
- package/lib/slide/layouts/funnel.js +4 -4
- package/lib/slide/layouts/grid.js +8 -8
- package/lib/slide/layouts/matrix.js +9 -9
- package/lib/slide/layouts/split.js +18 -5
- package/lib/slide/layouts/stats.js +21 -8
- package/lib/slide/layouts/table.js +3 -37
- package/lib/slide/layouts/timeline.js +20 -7
- package/lib/slide/layouts/title.js +7 -5
- package/lib/slide/render.d.ts +1 -1
- package/lib/slide/render.js +5 -1
- package/lib/slide/schema.d.ts +4905 -338
- package/lib/slide/schema.js +46 -11
- package/lib/slide/utils.d.ts +6 -0
- package/lib/slide/utils.js +35 -5
- package/lib/types/schema.d.ts +4852 -288
- package/lib/types/slide.d.ts +4905 -338
- package/lib/types/slide.js +46 -11
- package/lib/utils/context.d.ts +1300 -14
- package/lib/utils/image_plugins/slide.js +4 -2
- package/package.json +6 -3
- package/scripts/test/test_news_summary_rich.json +185 -0
- package/scripts/test/test_news_summary_rich2.json +100 -0
- package/scripts/test/test_slide_image_ref_en.json +2 -1
- package/scripts/test/test_slide_image_ref_gemini_en.json +289 -0
- package/scripts/test/test_slide_image_ref_gemini_en.json~ +289 -0
- package/scripts/test/test_tts_speed.json +252 -0
package/lib/slide/schema.js
CHANGED
|
@@ -42,9 +42,19 @@ export const textBlockSchema = z.object({
|
|
|
42
42
|
fontSize: z.number().optional(),
|
|
43
43
|
color: accentColorKeySchema.optional(),
|
|
44
44
|
});
|
|
45
|
+
/** Sub-bullet item: plain string or object with text */
|
|
46
|
+
const subBulletItemSchema = z.union([z.string(), z.object({ text: z.string() })]);
|
|
47
|
+
/** Bullet item: plain string or object with text and optional sub-items (2 levels max) */
|
|
48
|
+
export const bulletItemSchema = z.union([
|
|
49
|
+
z.string(),
|
|
50
|
+
z.object({
|
|
51
|
+
text: z.string(),
|
|
52
|
+
items: z.array(subBulletItemSchema).optional(),
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
45
55
|
export const bulletsBlockSchema = z.object({
|
|
46
56
|
type: z.literal("bullets"),
|
|
47
|
-
items: z.array(
|
|
57
|
+
items: z.array(bulletItemSchema),
|
|
48
58
|
ordered: z.boolean().optional(),
|
|
49
59
|
icon: z.string().optional(),
|
|
50
60
|
});
|
|
@@ -93,7 +103,25 @@ export const mermaidBlockSchema = z.object({
|
|
|
93
103
|
code: z.string(),
|
|
94
104
|
title: z.string().optional(),
|
|
95
105
|
});
|
|
96
|
-
export const
|
|
106
|
+
export const tableCellValueSchema = z.union([
|
|
107
|
+
z.string(),
|
|
108
|
+
z.object({
|
|
109
|
+
text: z.string(),
|
|
110
|
+
color: accentColorKeySchema.optional(),
|
|
111
|
+
bold: z.boolean().optional(),
|
|
112
|
+
badge: z.boolean().optional(),
|
|
113
|
+
}),
|
|
114
|
+
]);
|
|
115
|
+
export const tableBlockSchema = z.object({
|
|
116
|
+
type: z.literal("table"),
|
|
117
|
+
title: z.string().optional(),
|
|
118
|
+
headers: z.array(z.string()).optional(),
|
|
119
|
+
rows: z.array(z.array(tableCellValueSchema)),
|
|
120
|
+
rowHeaders: z.boolean().optional(),
|
|
121
|
+
striped: z.boolean().optional(),
|
|
122
|
+
});
|
|
123
|
+
/** Block schemas shared between contentBlockSchema and nonSectionContentBlockSchema */
|
|
124
|
+
const baseBlockSchemas = [
|
|
97
125
|
textBlockSchema,
|
|
98
126
|
bulletsBlockSchema,
|
|
99
127
|
codeBlockSchema,
|
|
@@ -104,7 +132,19 @@ export const contentBlockSchema = z.discriminatedUnion("type", [
|
|
|
104
132
|
imageRefBlockSchema,
|
|
105
133
|
chartBlockSchema,
|
|
106
134
|
mermaidBlockSchema,
|
|
107
|
-
|
|
135
|
+
tableBlockSchema,
|
|
136
|
+
];
|
|
137
|
+
/** All content block types except section (used inside section to prevent recursion) */
|
|
138
|
+
const nonSectionContentBlockSchema = z.discriminatedUnion("type", [...baseBlockSchemas]);
|
|
139
|
+
export const sectionBlockSchema = z.object({
|
|
140
|
+
type: z.literal("section"),
|
|
141
|
+
label: z.string(),
|
|
142
|
+
color: accentColorKeySchema.optional(),
|
|
143
|
+
content: z.array(nonSectionContentBlockSchema).optional(),
|
|
144
|
+
text: z.string().optional(),
|
|
145
|
+
sidebar: z.boolean().optional(),
|
|
146
|
+
});
|
|
147
|
+
export const contentBlockSchema = z.discriminatedUnion("type", [...baseBlockSchemas, sectionBlockSchema]);
|
|
108
148
|
// ═══════════════════════════════════════════════════════════
|
|
109
149
|
// Shared Components
|
|
110
150
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -244,10 +284,12 @@ export const splitPanelSchema = z.object({
|
|
|
244
284
|
title: z.string().optional(),
|
|
245
285
|
subtitle: z.string().optional(),
|
|
246
286
|
label: z.string().optional(),
|
|
287
|
+
labelBadge: z.boolean().optional(),
|
|
247
288
|
accentColor: accentColorKeySchema.optional(),
|
|
248
289
|
content: z.array(contentBlockSchema).optional(),
|
|
249
290
|
dark: z.boolean().optional(),
|
|
250
291
|
ratio: z.number().optional(),
|
|
292
|
+
valign: z.enum(["top", "center", "bottom"]).optional(),
|
|
251
293
|
});
|
|
252
294
|
export const splitSlideSchema = z.object({
|
|
253
295
|
layout: z.literal("split"),
|
|
@@ -287,14 +329,6 @@ export const matrixSlideSchema = z.object({
|
|
|
287
329
|
cells: z.array(matrixCellSchema),
|
|
288
330
|
});
|
|
289
331
|
// ─── table ───
|
|
290
|
-
export const tableCellValueSchema = z.union([
|
|
291
|
-
z.string(),
|
|
292
|
-
z.object({
|
|
293
|
-
text: z.string(),
|
|
294
|
-
color: accentColorKeySchema.optional(),
|
|
295
|
-
bold: z.boolean().optional(),
|
|
296
|
-
}),
|
|
297
|
-
]);
|
|
298
332
|
export const tableSlideSchema = z.object({
|
|
299
333
|
layout: z.literal("table"),
|
|
300
334
|
...slideBaseFields,
|
|
@@ -345,5 +379,6 @@ export const mulmoSlideMediaSchema = z
|
|
|
345
379
|
type: z.literal("slide"),
|
|
346
380
|
theme: slideThemeSchema.optional(),
|
|
347
381
|
slide: slideLayoutSchema,
|
|
382
|
+
reference: z.string().optional(),
|
|
348
383
|
})
|
|
349
384
|
.strict();
|
package/lib/slide/utils.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ import type { SlideTheme, SlideLayout } from "./schema.js";
|
|
|
3
3
|
export declare const escapeHtml: (s: string) => string;
|
|
4
4
|
/** Escape HTML and convert newlines to <br> */
|
|
5
5
|
export declare const nl2br: (s: string) => string;
|
|
6
|
+
/**
|
|
7
|
+
* Render inline markup: escape HTML first, then parse **bold** and {color:text}.
|
|
8
|
+
* Also converts newlines to <br>.
|
|
9
|
+
* Safe: escapeHtml runs before any markup parsing, so XSS is impossible.
|
|
10
|
+
*/
|
|
11
|
+
export declare const renderInlineMarkup: (s: string) => string;
|
|
6
12
|
/** Sanitize a hex color value (hex digits only) */
|
|
7
13
|
export declare const sanitizeHex: (s: string) => string;
|
|
8
14
|
/** Accent color key → Tailwind class segment: "primary" → "d-primary" */
|
package/lib/slide/utils.js
CHANGED
|
@@ -11,6 +11,28 @@ export const escapeHtml = (s) => {
|
|
|
11
11
|
export const nl2br = (s) => {
|
|
12
12
|
return escapeHtml(s).replace(/\n/g, "<br>");
|
|
13
13
|
};
|
|
14
|
+
/** Valid accent color keys for inline markup */
|
|
15
|
+
const inlineColorKeys = new Set(["primary", "accent", "success", "warning", "danger", "info", "highlight"]);
|
|
16
|
+
/**
|
|
17
|
+
* Render inline markup: escape HTML first, then parse **bold** and {color:text}.
|
|
18
|
+
* Also converts newlines to <br>.
|
|
19
|
+
* Safe: escapeHtml runs before any markup parsing, so XSS is impossible.
|
|
20
|
+
*/
|
|
21
|
+
export const renderInlineMarkup = (s) => {
|
|
22
|
+
let result = escapeHtml(s);
|
|
23
|
+
// **bold** → <strong>bold</strong>
|
|
24
|
+
result = result.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
25
|
+
// {color:text} → <span class="text-d-color">text</span>
|
|
26
|
+
result = result.replace(/\{([a-z]+):(.+?)\}/g, (_match, color, text) => {
|
|
27
|
+
if (inlineColorKeys.has(color)) {
|
|
28
|
+
return `<span class="text-${c(color)}">${text}</span>`;
|
|
29
|
+
}
|
|
30
|
+
return `{${color}:${text}}`;
|
|
31
|
+
});
|
|
32
|
+
// newlines to <br>
|
|
33
|
+
result = result.replace(/\n/g, "<br>");
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
14
36
|
/** Sanitize a value for safe use in CSS class names (alphanumeric + hyphens only) */
|
|
15
37
|
const sanitizeCssClass = (s) => {
|
|
16
38
|
return s.replace(/[^a-zA-Z0-9-]/g, "");
|
|
@@ -87,8 +109,8 @@ export const renderCalloutBar = (obj) => {
|
|
|
87
109
|
const leftBar = obj.leftBar ? `<div class="w-1 bg-${c(color)} shrink-0"></div>` : "";
|
|
88
110
|
const align = obj.align === "center" ? "text-center" : "";
|
|
89
111
|
const inner = obj.label
|
|
90
|
-
? `<span class="font-bold text-${c(color)}">${
|
|
91
|
-
: `<span class="text-d-muted">${
|
|
112
|
+
? `<span class="font-bold text-${c(color)}">${renderInlineMarkup(obj.label)}:</span> <span class="text-d-muted">${renderInlineMarkup(obj.text)}</span>`
|
|
113
|
+
: `<span class="text-d-muted">${renderInlineMarkup(obj.text)}</span>`;
|
|
92
114
|
return `<div class="mx-12 bg-d-card rounded flex overflow-hidden ${align}">
|
|
93
115
|
${leftBar}
|
|
94
116
|
<div class="px-4 py-3 text-sm font-body flex-1">${inner}</div>
|
|
@@ -101,11 +123,11 @@ export const slideHeader = (data) => {
|
|
|
101
123
|
lines.push(`<div class="h-[3px] bg-${c(accent)} shrink-0"></div>`);
|
|
102
124
|
lines.push(`<div class="px-12 pt-5 shrink-0">`);
|
|
103
125
|
if (data.stepLabel) {
|
|
104
|
-
lines.push(` <p class="text-sm font-bold text-${c(accent)} font-body">${
|
|
126
|
+
lines.push(` <p class="text-sm font-bold text-${c(accent)} font-body">${renderInlineMarkup(data.stepLabel)}</p>`);
|
|
105
127
|
}
|
|
106
|
-
lines.push(` <h2 class="text-[42px] leading-tight font-title font-bold text-d-text">${
|
|
128
|
+
lines.push(` <h2 class="text-[42px] leading-tight font-title font-bold text-d-text">${renderInlineMarkup(data.title)}</h2>`);
|
|
107
129
|
if (data.subtitle) {
|
|
108
|
-
lines.push(` <p class="text-[15px] text-d-dim mt-2 font-body">${
|
|
130
|
+
lines.push(` <p class="text-[15px] text-d-dim mt-2 font-body">${renderInlineMarkup(data.subtitle)}</p>`);
|
|
109
131
|
}
|
|
110
132
|
lines.push(`</div>`);
|
|
111
133
|
return lines.join("\n");
|
|
@@ -159,6 +181,14 @@ export const detectBlockTypes = (slide) => {
|
|
|
159
181
|
hasChart = true;
|
|
160
182
|
if (block.type === "mermaid")
|
|
161
183
|
hasMermaid = true;
|
|
184
|
+
if (block.type === "section" && block.content) {
|
|
185
|
+
block.content.forEach((inner) => {
|
|
186
|
+
if (inner.type === "chart")
|
|
187
|
+
hasChart = true;
|
|
188
|
+
if (inner.type === "mermaid")
|
|
189
|
+
hasMermaid = true;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
162
192
|
});
|
|
163
193
|
});
|
|
164
194
|
return { hasChart, hasMermaid };
|