mulmocast 2.2.5 → 2.2.6
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/index.d.ts +3 -2
- package/lib/slide/index.js +1 -1
- package/lib/slide/render.d.ts +15 -1
- package/lib/slide/render.js +45 -3
- package/lib/slide/schema.d.ts +103 -0
- package/lib/slide/schema.js +32 -0
- package/lib/types/schema.d.ts +410 -0
- package/lib/types/schema.js +2 -1
- package/lib/types/slide.d.ts +103 -0
- package/lib/types/slide.js +32 -0
- package/lib/utils/context.d.ts +155 -0
- package/lib/utils/image_plugins/slide.js +50 -2
- package/package.json +1 -1
- package/scripts/test/branding/banner.jpg +0 -0
- package/scripts/test/branding/logo.svg +3 -0
- package/scripts/test/test_slide_branding.json +107 -0
package/lib/slide/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { generateSlideHTML } from "./render.js";
|
|
2
|
+
export type { ResolvedBranding } from "./render.js";
|
|
2
3
|
export { renderSlideContent } from "./layouts/index.js";
|
|
3
4
|
export { renderContentBlock, renderContentBlocks } from "./blocks.js";
|
|
4
|
-
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, } from "./schema.js";
|
|
5
|
-
export type { MulmoSlideMedia, SlideLayout, SlideTheme, SlideThemeColors, SlideThemeFonts, ContentBlock, ImageRefBlock, ChartBlock, MermaidBlock, AccentColorKey, TitleSlide, ColumnsSlide, ComparisonSlide, GridSlide, BigQuoteSlide, StatsSlide, TimelineSlide, SplitSlide, MatrixSlide, TableSlide, FunnelSlide, Card, CalloutBar, SlideStyle, } from "./schema.js";
|
|
5
|
+
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, slideBrandingLogoSchema, slideBrandingSchema, } from "./schema.js";
|
|
6
|
+
export type { MulmoSlideMedia, SlideLayout, SlideTheme, SlideThemeColors, SlideThemeFonts, ContentBlock, ImageRefBlock, ChartBlock, MermaidBlock, AccentColorKey, TitleSlide, ColumnsSlide, ComparisonSlide, GridSlide, BigQuoteSlide, StatsSlide, TimelineSlide, SplitSlide, MatrixSlide, TableSlide, FunnelSlide, Card, CalloutBar, SlideStyle, SlideBrandingLogo, SlideBranding, } from "./schema.js";
|
package/lib/slide/index.js
CHANGED
|
@@ -4,4 +4,4 @@ export { generateSlideHTML } from "./render.js";
|
|
|
4
4
|
export { renderSlideContent } from "./layouts/index.js";
|
|
5
5
|
export { renderContentBlock, renderContentBlocks } from "./blocks.js";
|
|
6
6
|
// Schemas
|
|
7
|
-
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, } from "./schema.js";
|
|
7
|
+
export { mulmoSlideMediaSchema, slideLayoutSchema, slideThemeSchema, contentBlockSchema, imageRefBlockSchema, chartBlockSchema, mermaidBlockSchema, accentColorKeySchema, slideBrandingLogoSchema, slideBrandingSchema, } from "./schema.js";
|
package/lib/slide/render.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import type { SlideTheme, SlideLayout } from "./schema.js";
|
|
2
|
+
/** Pre-resolved branding data (all sources converted to data URLs) */
|
|
3
|
+
export type ResolvedBranding = {
|
|
4
|
+
logo?: {
|
|
5
|
+
dataUrl: string;
|
|
6
|
+
position: string;
|
|
7
|
+
width: number;
|
|
8
|
+
};
|
|
9
|
+
backgroundImage?: {
|
|
10
|
+
dataUrl: string;
|
|
11
|
+
size: string;
|
|
12
|
+
opacity: number;
|
|
13
|
+
bgOpacity?: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
2
16
|
/** Generate a complete HTML document for a single slide */
|
|
3
|
-
export declare const generateSlideHTML: (theme: SlideTheme, slide: SlideLayout, reference?: string) => string;
|
|
17
|
+
export declare const generateSlideHTML: (theme: SlideTheme, slide: SlideLayout, reference?: string, branding?: ResolvedBranding) => string;
|
package/lib/slide/render.js
CHANGED
|
@@ -21,18 +21,56 @@ const buildCdnScripts = (theme, slide) => {
|
|
|
21
21
|
}
|
|
22
22
|
return scripts.join("\n");
|
|
23
23
|
};
|
|
24
|
+
/** Map branding logo position to Tailwind CSS classes */
|
|
25
|
+
const logoPositionClasses = {
|
|
26
|
+
"top-left": "top-5 left-6",
|
|
27
|
+
"top-right": "top-5 right-6",
|
|
28
|
+
"bottom-left": "bottom-5 left-6",
|
|
29
|
+
"bottom-right": "bottom-5 right-6",
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Render branding background layers.
|
|
33
|
+
* - Without bgOpacity: image overlaid on slide bg at given opacity
|
|
34
|
+
* - With bgOpacity: image at full opacity, then slide bg color as semi-transparent overlay
|
|
35
|
+
*/
|
|
36
|
+
const renderBrandingBackground = (branding, bgHex) => {
|
|
37
|
+
if (!branding.backgroundImage)
|
|
38
|
+
return "";
|
|
39
|
+
const { dataUrl, size, opacity, bgOpacity } = branding.backgroundImage;
|
|
40
|
+
const bgSize = size === "fill" ? "100% 100%" : size;
|
|
41
|
+
if (bgOpacity !== undefined) {
|
|
42
|
+
const parts = [];
|
|
43
|
+
parts.push(`<div class="absolute inset-0 z-0" style="background-image:url('${dataUrl}');background-size:${bgSize};background-position:center;background-repeat:no-repeat;opacity:${opacity}"></div>`);
|
|
44
|
+
parts.push(`<div class="absolute inset-0 z-0" style="background-color:#${bgHex};opacity:${bgOpacity}"></div>`);
|
|
45
|
+
return parts.join("\n");
|
|
46
|
+
}
|
|
47
|
+
return `<div class="absolute inset-0 z-0" style="background-image:url('${dataUrl}');background-size:${bgSize};background-position:center;background-repeat:no-repeat;opacity:${opacity}"></div>`;
|
|
48
|
+
};
|
|
49
|
+
/** Render branding logo element */
|
|
50
|
+
const renderBrandingLogo = (branding) => {
|
|
51
|
+
if (!branding.logo)
|
|
52
|
+
return "";
|
|
53
|
+
const { dataUrl, position, width } = branding.logo;
|
|
54
|
+
const posClasses = logoPositionClasses[position] ?? logoPositionClasses["top-right"];
|
|
55
|
+
return `<img class="absolute ${posClasses} z-10" src="${dataUrl}" width="${width}" alt="" style="pointer-events:none">`;
|
|
56
|
+
};
|
|
24
57
|
/** Generate a complete HTML document for a single slide */
|
|
25
|
-
export const generateSlideHTML = (theme, slide, reference) => {
|
|
58
|
+
export const generateSlideHTML = (theme, slide, reference, branding) => {
|
|
26
59
|
const content = renderSlideContent(slide);
|
|
27
60
|
const twConfig = buildTailwindConfig(theme);
|
|
28
61
|
const cdnScripts = buildCdnScripts(theme, slide);
|
|
29
62
|
const slideStyle = slide.style;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
63
|
+
const hasBgOpacity = branding?.backgroundImage?.bgOpacity !== undefined;
|
|
64
|
+
const bgCls = hasBgOpacity || slideStyle?.bgColor ? "" : "bg-d-bg";
|
|
65
|
+
const bgColorStyle = slideStyle?.bgColor ? ` style="background-color:#${sanitizeHex(slideStyle.bgColor)}"` : "";
|
|
66
|
+
const inlineStyle = hasBgOpacity ? "" : bgColorStyle;
|
|
32
67
|
const footer = slideStyle?.footer ? `<p class="absolute bottom-2 right-4 text-xs text-d-dim font-body">${escapeHtml(slideStyle.footer)}</p>` : "";
|
|
33
68
|
const referenceHtml = reference
|
|
34
69
|
? `<div class="mt-auto px-4 pb-2"><p class="text-sm text-d-muted font-body opacity-80">${escapeHtml(reference)}</p></div>`
|
|
35
70
|
: "";
|
|
71
|
+
const bgHex = sanitizeHex(slideStyle?.bgColor ?? theme.colors.bg);
|
|
72
|
+
const brandingBg = branding ? renderBrandingBackground(branding, bgHex) : "";
|
|
73
|
+
const brandingLogo = branding ? renderBrandingLogo(branding) : "";
|
|
36
74
|
return `<!DOCTYPE html>
|
|
37
75
|
<html lang="en" class="h-full">
|
|
38
76
|
<head>
|
|
@@ -47,10 +85,14 @@ ${cdnScripts}
|
|
|
47
85
|
</head>
|
|
48
86
|
<body class="h-full">
|
|
49
87
|
<div class="relative overflow-hidden ${bgCls} w-full h-full flex flex-col"${inlineStyle}>
|
|
88
|
+
${brandingBg}
|
|
89
|
+
<div class="relative z-[1] flex flex-col flex-1">
|
|
50
90
|
${content}
|
|
51
91
|
${referenceHtml}
|
|
52
92
|
${footer}
|
|
53
93
|
</div>
|
|
94
|
+
${brandingLogo}
|
|
95
|
+
</div>
|
|
54
96
|
</body>
|
|
55
97
|
</html>`;
|
|
56
98
|
};
|
package/lib/slide/schema.d.ts
CHANGED
|
@@ -4351,6 +4351,66 @@ export declare const funnelSlideSchema: z.ZodObject<{
|
|
|
4351
4351
|
}, z.core.$strip>>;
|
|
4352
4352
|
layout: z.ZodLiteral<"funnel">;
|
|
4353
4353
|
}, z.core.$strip>;
|
|
4354
|
+
export declare const slideBrandingLogoSchema: z.ZodObject<{
|
|
4355
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4356
|
+
kind: z.ZodLiteral<"url">;
|
|
4357
|
+
url: z.ZodURL;
|
|
4358
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4359
|
+
kind: z.ZodLiteral<"base64">;
|
|
4360
|
+
data: z.ZodString;
|
|
4361
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4362
|
+
kind: z.ZodLiteral<"path">;
|
|
4363
|
+
path: z.ZodString;
|
|
4364
|
+
}, z.core.$strict>], "kind">;
|
|
4365
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
4366
|
+
"top-left": "top-left";
|
|
4367
|
+
"top-right": "top-right";
|
|
4368
|
+
"bottom-left": "bottom-left";
|
|
4369
|
+
"bottom-right": "bottom-right";
|
|
4370
|
+
}>>;
|
|
4371
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
4372
|
+
}, z.core.$strict>;
|
|
4373
|
+
export declare const slideBrandingSchema: z.ZodObject<{
|
|
4374
|
+
logo: z.ZodOptional<z.ZodObject<{
|
|
4375
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4376
|
+
kind: z.ZodLiteral<"url">;
|
|
4377
|
+
url: z.ZodURL;
|
|
4378
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4379
|
+
kind: z.ZodLiteral<"base64">;
|
|
4380
|
+
data: z.ZodString;
|
|
4381
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4382
|
+
kind: z.ZodLiteral<"path">;
|
|
4383
|
+
path: z.ZodString;
|
|
4384
|
+
}, z.core.$strict>], "kind">;
|
|
4385
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
4386
|
+
"top-left": "top-left";
|
|
4387
|
+
"top-right": "top-right";
|
|
4388
|
+
"bottom-left": "bottom-left";
|
|
4389
|
+
"bottom-right": "bottom-right";
|
|
4390
|
+
}>>;
|
|
4391
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
4392
|
+
}, z.core.$strict>>;
|
|
4393
|
+
backgroundImage: z.ZodOptional<z.ZodObject<{
|
|
4394
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4395
|
+
kind: z.ZodLiteral<"url">;
|
|
4396
|
+
url: z.ZodURL;
|
|
4397
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4398
|
+
kind: z.ZodLiteral<"base64">;
|
|
4399
|
+
data: z.ZodString;
|
|
4400
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
4401
|
+
kind: z.ZodLiteral<"path">;
|
|
4402
|
+
path: z.ZodString;
|
|
4403
|
+
}, z.core.$strict>], "kind">;
|
|
4404
|
+
size: z.ZodOptional<z.ZodEnum<{
|
|
4405
|
+
contain: "contain";
|
|
4406
|
+
cover: "cover";
|
|
4407
|
+
fill: "fill";
|
|
4408
|
+
auto: "auto";
|
|
4409
|
+
}>>;
|
|
4410
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
4411
|
+
bgOpacity: z.ZodOptional<z.ZodNumber>;
|
|
4412
|
+
}, z.core.$strip>>;
|
|
4413
|
+
}, z.core.$strict>;
|
|
4354
4414
|
export declare const slideLayoutSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4355
4415
|
title: z.ZodString;
|
|
4356
4416
|
subtitle: z.ZodOptional<z.ZodString>;
|
|
@@ -8985,6 +9045,47 @@ export declare const mulmoSlideMediaSchema: z.ZodObject<{
|
|
|
8985
9045
|
layout: z.ZodLiteral<"funnel">;
|
|
8986
9046
|
}, z.core.$strip>], "layout">;
|
|
8987
9047
|
reference: z.ZodOptional<z.ZodString>;
|
|
9048
|
+
branding: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
9049
|
+
logo: z.ZodOptional<z.ZodObject<{
|
|
9050
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
9051
|
+
kind: z.ZodLiteral<"url">;
|
|
9052
|
+
url: z.ZodURL;
|
|
9053
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9054
|
+
kind: z.ZodLiteral<"base64">;
|
|
9055
|
+
data: z.ZodString;
|
|
9056
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9057
|
+
kind: z.ZodLiteral<"path">;
|
|
9058
|
+
path: z.ZodString;
|
|
9059
|
+
}, z.core.$strict>], "kind">;
|
|
9060
|
+
position: z.ZodDefault<z.ZodEnum<{
|
|
9061
|
+
"top-left": "top-left";
|
|
9062
|
+
"top-right": "top-right";
|
|
9063
|
+
"bottom-left": "bottom-left";
|
|
9064
|
+
"bottom-right": "bottom-right";
|
|
9065
|
+
}>>;
|
|
9066
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
9067
|
+
}, z.core.$strict>>;
|
|
9068
|
+
backgroundImage: z.ZodOptional<z.ZodObject<{
|
|
9069
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
9070
|
+
kind: z.ZodLiteral<"url">;
|
|
9071
|
+
url: z.ZodURL;
|
|
9072
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9073
|
+
kind: z.ZodLiteral<"base64">;
|
|
9074
|
+
data: z.ZodString;
|
|
9075
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
9076
|
+
kind: z.ZodLiteral<"path">;
|
|
9077
|
+
path: z.ZodString;
|
|
9078
|
+
}, z.core.$strict>], "kind">;
|
|
9079
|
+
size: z.ZodOptional<z.ZodEnum<{
|
|
9080
|
+
contain: "contain";
|
|
9081
|
+
cover: "cover";
|
|
9082
|
+
fill: "fill";
|
|
9083
|
+
auto: "auto";
|
|
9084
|
+
}>>;
|
|
9085
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
9086
|
+
bgOpacity: z.ZodOptional<z.ZodNumber>;
|
|
9087
|
+
}, z.core.$strip>>;
|
|
9088
|
+
}, z.core.$strict>>>;
|
|
8988
9089
|
}, z.core.$strict>;
|
|
8989
9090
|
export type AccentColorKey = z.infer<typeof accentColorKeySchema>;
|
|
8990
9091
|
export type SlideThemeColors = z.infer<typeof slideThemeColorsSchema>;
|
|
@@ -9027,4 +9128,6 @@ export type TableCellValue = z.infer<typeof tableCellValueSchema>;
|
|
|
9027
9128
|
export type TableSlide = z.infer<typeof tableSlideSchema>;
|
|
9028
9129
|
export type FunnelStage = z.infer<typeof funnelStageSchema>;
|
|
9029
9130
|
export type FunnelSlide = z.infer<typeof funnelSlideSchema>;
|
|
9131
|
+
export type SlideBrandingLogo = z.infer<typeof slideBrandingLogoSchema>;
|
|
9132
|
+
export type SlideBranding = z.infer<typeof slideBrandingSchema>;
|
|
9030
9133
|
export type MulmoSlideMedia = z.infer<typeof mulmoSlideMediaSchema>;
|
package/lib/slide/schema.js
CHANGED
|
@@ -358,6 +358,37 @@ export const funnelSlideSchema = z.object({
|
|
|
358
358
|
callout: calloutBarSchema.optional(),
|
|
359
359
|
});
|
|
360
360
|
// ═══════════════════════════════════════════════════════════
|
|
361
|
+
// Branding — logo & background image overlay
|
|
362
|
+
// ═══════════════════════════════════════════════════════════
|
|
363
|
+
/**
|
|
364
|
+
* Media source for branding assets (self-contained definition to avoid
|
|
365
|
+
* circular dependency with src/types/schema.ts).
|
|
366
|
+
*/
|
|
367
|
+
const slideMediaSourceSchema = z.discriminatedUnion("kind", [
|
|
368
|
+
z.object({ kind: z.literal("url"), url: z.url() }).strict(),
|
|
369
|
+
z.object({ kind: z.literal("base64"), data: z.string().min(1) }).strict(),
|
|
370
|
+
z.object({ kind: z.literal("path"), path: z.string().min(1) }).strict(),
|
|
371
|
+
]);
|
|
372
|
+
const slideBackgroundImageSourceSchema = z.object({
|
|
373
|
+
source: slideMediaSourceSchema,
|
|
374
|
+
size: z.enum(["cover", "contain", "fill", "auto"]).optional(),
|
|
375
|
+
opacity: z.number().min(0).max(1).optional(),
|
|
376
|
+
bgOpacity: z.number().min(0).max(1).optional(),
|
|
377
|
+
});
|
|
378
|
+
export const slideBrandingLogoSchema = z
|
|
379
|
+
.object({
|
|
380
|
+
source: slideMediaSourceSchema,
|
|
381
|
+
position: z.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).default("top-right"),
|
|
382
|
+
width: z.number().positive().default(120),
|
|
383
|
+
})
|
|
384
|
+
.strict();
|
|
385
|
+
export const slideBrandingSchema = z
|
|
386
|
+
.object({
|
|
387
|
+
logo: slideBrandingLogoSchema.optional(),
|
|
388
|
+
backgroundImage: slideBackgroundImageSourceSchema.optional(),
|
|
389
|
+
})
|
|
390
|
+
.strict();
|
|
391
|
+
// ═══════════════════════════════════════════════════════════
|
|
361
392
|
// Slide Union & Media Schema
|
|
362
393
|
// ═══════════════════════════════════════════════════════════
|
|
363
394
|
export const slideLayoutSchema = z.discriminatedUnion("layout", [
|
|
@@ -380,5 +411,6 @@ export const mulmoSlideMediaSchema = z
|
|
|
380
411
|
theme: slideThemeSchema.optional(),
|
|
381
412
|
slide: slideLayoutSchema,
|
|
382
413
|
reference: z.string().optional(),
|
|
414
|
+
branding: slideBrandingSchema.nullable().optional(),
|
|
383
415
|
})
|
|
384
416
|
.strict();
|