mulmocast 2.20.0 → 2.22.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.
Files changed (36) hide show
  1. package/assets/schemas/mulmo_script.json +169 -0
  2. package/lib/data/animationRuntime.d.ts +5 -0
  3. package/lib/data/animationRuntime.js +639 -0
  4. package/lib/methods/mulmo_beat.d.ts +2 -7
  5. package/lib/methods/mulmo_beat.js +1 -12
  6. package/lib/methods/mulmo_beat_node.d.ts +2 -8
  7. package/lib/types/schema.d.ts +264 -0
  8. package/lib/utils/animation_config.d.ts +15 -0
  9. package/lib/utils/animation_config.js +17 -0
  10. package/lib/utils/beat_html/chart.d.ts +7 -11
  11. package/lib/utils/beat_html/chart.js +8 -18
  12. package/lib/utils/beat_html/decoration.d.ts +16 -0
  13. package/lib/utils/beat_html/decoration.js +55 -0
  14. package/lib/utils/beat_html/html_tailwind.d.ts +6 -6
  15. package/lib/utils/beat_html/html_tailwind.js +13 -7
  16. package/lib/utils/beat_html/index.js +5 -4
  17. package/lib/utils/beat_html/mermaid.d.ts +3 -6
  18. package/lib/utils/beat_html/mermaid.js +5 -10
  19. package/lib/utils/beat_html/slide.d.ts +13 -6
  20. package/lib/utils/beat_html/slide.js +8 -21
  21. package/lib/utils/beat_html/type.d.ts +47 -4
  22. package/lib/utils/context.d.ts +62 -0
  23. package/lib/utils/html_render.js +1 -1
  24. package/lib/utils/image_plugins/bg_image_css.d.ts +19 -0
  25. package/lib/utils/image_plugins/bg_image_css.js +75 -0
  26. package/lib/utils/image_plugins/bg_image_util.d.ts +0 -6
  27. package/lib/utils/image_plugins/bg_image_util.js +7 -69
  28. package/lib/utils/image_plugins/chart_html.d.ts +0 -10
  29. package/lib/utils/image_plugins/chart_html.js +5 -31
  30. package/lib/utils/image_plugins/html_tailwind.js +2 -39
  31. package/lib/utils/image_plugins/html_tailwind_script.d.ts +41 -0
  32. package/lib/utils/image_plugins/html_tailwind_script.js +111 -0
  33. package/lib/utils/image_plugins/slide.js +2 -5
  34. package/lib/utils/image_plugins/slide_branding.d.ts +7 -0
  35. package/lib/utils/image_plugins/slide_branding.js +10 -0
  36. package/package.json +2 -2
@@ -1,16 +1,5 @@
1
1
  import { ImageMediaType } from "../types/index.js";
2
- /** Type guard: checks if animation value is an object config like { fps: 30 } */
3
- const isAnimationObject = (animation) => {
4
- return typeof animation === "object" && animation !== null && !Array.isArray(animation);
5
- };
6
- /** Check if a value is a valid animation config (true or non-array object) */
7
- const isAnimationEnabled = (animation) => {
8
- return animation === true || isAnimationObject(animation);
9
- };
10
- /** Check if movie mode (CDP screencast) is enabled */
11
- const isMovieMode = (animation) => {
12
- return isAnimationObject(animation) && animation.movie === true;
13
- };
2
+ import { isAnimationEnabled, isAnimationObject, isMovieMode } from "../utils/animation_config.js";
14
3
  /** Check if a beat has html_tailwind animation enabled */
15
4
  const isAnimatedHtmlTailwind = (beat) => {
16
5
  if (!beat.image || beat.image.type !== "html_tailwind")
@@ -7,14 +7,8 @@ export declare const MulmoBeatMethods: {
7
7
  markdown?: (params: import("../types/type.js").BeatRenderParams) => string | undefined;
8
8
  html?: (params: import("../types/type.js").BeatRenderParams) => Promise<string | undefined>;
9
9
  };
10
- isAnimationEnabled: (animation: unknown) => animation is true | {
11
- fps?: number;
12
- movie?: boolean;
13
- };
14
- isAnimationObject: (animation: unknown) => animation is {
15
- fps?: number;
16
- movie?: boolean;
17
- };
10
+ isAnimationEnabled: (animation: unknown) => animation is true | import("../utils/animation_config.js").AnimationConfig;
11
+ isAnimationObject: (animation: unknown) => animation is import("../utils/animation_config.js").AnimationConfig;
18
12
  isAnimatedHtmlTailwind: (beat: MulmoBeat) => boolean;
19
13
  isPluginVideo: (beat: MulmoBeat) => boolean;
20
14
  isMovieMode: (animation: unknown) => boolean;
@@ -4207,6 +4207,72 @@ export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
4207
4207
  }>>;
4208
4208
  staggerMs: z.ZodOptional<z.ZodNumber>;
4209
4209
  layout: z.ZodLiteral<"manifesto">;
4210
+ }, z.core.$strip>, z.ZodObject<{
4211
+ title: z.ZodString;
4212
+ stepLabel: z.ZodOptional<z.ZodString>;
4213
+ subtitle: z.ZodOptional<z.ZodString>;
4214
+ figure: z.ZodDiscriminatedUnion<[z.ZodObject<{
4215
+ type: z.ZodLiteral<"chart">;
4216
+ chartData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
4217
+ title: z.ZodOptional<z.ZodString>;
4218
+ }, z.core.$strip>, z.ZodObject<{
4219
+ type: z.ZodLiteral<"mermaid">;
4220
+ code: z.ZodString;
4221
+ title: z.ZodOptional<z.ZodString>;
4222
+ }, z.core.$strip>], "type">;
4223
+ accentColor: z.ZodOptional<z.ZodEnum<{
4224
+ primary: "primary";
4225
+ accent: "accent";
4226
+ success: "success";
4227
+ warning: "warning";
4228
+ danger: "danger";
4229
+ info: "info";
4230
+ highlight: "highlight";
4231
+ }>>;
4232
+ style: z.ZodOptional<z.ZodObject<{
4233
+ bgColor: z.ZodOptional<z.ZodString>;
4234
+ bgGradient: z.ZodOptional<z.ZodString>;
4235
+ decorations: z.ZodOptional<z.ZodBoolean>;
4236
+ bgOpacity: z.ZodOptional<z.ZodNumber>;
4237
+ footer: z.ZodOptional<z.ZodString>;
4238
+ }, z.core.$strip>>;
4239
+ eyebrow: z.ZodOptional<z.ZodObject<{
4240
+ label: z.ZodString;
4241
+ color: z.ZodOptional<z.ZodEnum<{
4242
+ primary: "primary";
4243
+ accent: "accent";
4244
+ success: "success";
4245
+ warning: "warning";
4246
+ danger: "danger";
4247
+ info: "info";
4248
+ highlight: "highlight";
4249
+ }>>;
4250
+ }, z.core.$strip>>;
4251
+ density: z.ZodOptional<z.ZodEnum<{
4252
+ default: "default";
4253
+ compact: "compact";
4254
+ }>>;
4255
+ titleSize: z.ZodOptional<z.ZodEnum<{
4256
+ default: "default";
4257
+ small: "small";
4258
+ large: "large";
4259
+ hero: "hero";
4260
+ }>>;
4261
+ subtitleSize: z.ZodOptional<z.ZodEnum<{
4262
+ lead: "lead";
4263
+ big: "big";
4264
+ default: "default";
4265
+ }>>;
4266
+ intro: z.ZodOptional<z.ZodEnum<{
4267
+ fade: "fade";
4268
+ "fade-up": "fade-up";
4269
+ "fade-down": "fade-down";
4270
+ "slide-left": "slide-left";
4271
+ "slide-right": "slide-right";
4272
+ "zoom-in": "zoom-in";
4273
+ }>>;
4274
+ staggerMs: z.ZodOptional<z.ZodNumber>;
4275
+ layout: z.ZodLiteral<"figure">;
4210
4276
  }, z.core.$strip>], "layout">;
4211
4277
  reference: z.ZodOptional<z.ZodString>;
4212
4278
  branding: z.ZodOptional<z.ZodNullable<z.ZodObject<{
@@ -8644,6 +8710,72 @@ export declare const mulmoBeatSchema: z.ZodObject<{
8644
8710
  }>>;
8645
8711
  staggerMs: z.ZodOptional<z.ZodNumber>;
8646
8712
  layout: z.ZodLiteral<"manifesto">;
8713
+ }, z.core.$strip>, z.ZodObject<{
8714
+ title: z.ZodString;
8715
+ stepLabel: z.ZodOptional<z.ZodString>;
8716
+ subtitle: z.ZodOptional<z.ZodString>;
8717
+ figure: z.ZodDiscriminatedUnion<[z.ZodObject<{
8718
+ type: z.ZodLiteral<"chart">;
8719
+ chartData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
8720
+ title: z.ZodOptional<z.ZodString>;
8721
+ }, z.core.$strip>, z.ZodObject<{
8722
+ type: z.ZodLiteral<"mermaid">;
8723
+ code: z.ZodString;
8724
+ title: z.ZodOptional<z.ZodString>;
8725
+ }, z.core.$strip>], "type">;
8726
+ accentColor: z.ZodOptional<z.ZodEnum<{
8727
+ primary: "primary";
8728
+ accent: "accent";
8729
+ success: "success";
8730
+ warning: "warning";
8731
+ danger: "danger";
8732
+ info: "info";
8733
+ highlight: "highlight";
8734
+ }>>;
8735
+ style: z.ZodOptional<z.ZodObject<{
8736
+ bgColor: z.ZodOptional<z.ZodString>;
8737
+ bgGradient: z.ZodOptional<z.ZodString>;
8738
+ decorations: z.ZodOptional<z.ZodBoolean>;
8739
+ bgOpacity: z.ZodOptional<z.ZodNumber>;
8740
+ footer: z.ZodOptional<z.ZodString>;
8741
+ }, z.core.$strip>>;
8742
+ eyebrow: z.ZodOptional<z.ZodObject<{
8743
+ label: z.ZodString;
8744
+ color: z.ZodOptional<z.ZodEnum<{
8745
+ primary: "primary";
8746
+ accent: "accent";
8747
+ success: "success";
8748
+ warning: "warning";
8749
+ danger: "danger";
8750
+ info: "info";
8751
+ highlight: "highlight";
8752
+ }>>;
8753
+ }, z.core.$strip>>;
8754
+ density: z.ZodOptional<z.ZodEnum<{
8755
+ default: "default";
8756
+ compact: "compact";
8757
+ }>>;
8758
+ titleSize: z.ZodOptional<z.ZodEnum<{
8759
+ default: "default";
8760
+ small: "small";
8761
+ large: "large";
8762
+ hero: "hero";
8763
+ }>>;
8764
+ subtitleSize: z.ZodOptional<z.ZodEnum<{
8765
+ lead: "lead";
8766
+ big: "big";
8767
+ default: "default";
8768
+ }>>;
8769
+ intro: z.ZodOptional<z.ZodEnum<{
8770
+ fade: "fade";
8771
+ "fade-up": "fade-up";
8772
+ "fade-down": "fade-down";
8773
+ "slide-left": "slide-left";
8774
+ "slide-right": "slide-right";
8775
+ "zoom-in": "zoom-in";
8776
+ }>>;
8777
+ staggerMs: z.ZodOptional<z.ZodNumber>;
8778
+ layout: z.ZodLiteral<"figure">;
8647
8779
  }, z.core.$strip>], "layout">;
8648
8780
  reference: z.ZodOptional<z.ZodString>;
8649
8781
  branding: z.ZodOptional<z.ZodNullable<z.ZodObject<{
@@ -13927,6 +14059,72 @@ export declare const mulmoScriptSchema: z.ZodObject<{
13927
14059
  }>>;
13928
14060
  staggerMs: z.ZodOptional<z.ZodNumber>;
13929
14061
  layout: z.ZodLiteral<"manifesto">;
14062
+ }, z.core.$strip>, z.ZodObject<{
14063
+ title: z.ZodString;
14064
+ stepLabel: z.ZodOptional<z.ZodString>;
14065
+ subtitle: z.ZodOptional<z.ZodString>;
14066
+ figure: z.ZodDiscriminatedUnion<[z.ZodObject<{
14067
+ type: z.ZodLiteral<"chart">;
14068
+ chartData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
14069
+ title: z.ZodOptional<z.ZodString>;
14070
+ }, z.core.$strip>, z.ZodObject<{
14071
+ type: z.ZodLiteral<"mermaid">;
14072
+ code: z.ZodString;
14073
+ title: z.ZodOptional<z.ZodString>;
14074
+ }, z.core.$strip>], "type">;
14075
+ accentColor: z.ZodOptional<z.ZodEnum<{
14076
+ primary: "primary";
14077
+ accent: "accent";
14078
+ success: "success";
14079
+ warning: "warning";
14080
+ danger: "danger";
14081
+ info: "info";
14082
+ highlight: "highlight";
14083
+ }>>;
14084
+ style: z.ZodOptional<z.ZodObject<{
14085
+ bgColor: z.ZodOptional<z.ZodString>;
14086
+ bgGradient: z.ZodOptional<z.ZodString>;
14087
+ decorations: z.ZodOptional<z.ZodBoolean>;
14088
+ bgOpacity: z.ZodOptional<z.ZodNumber>;
14089
+ footer: z.ZodOptional<z.ZodString>;
14090
+ }, z.core.$strip>>;
14091
+ eyebrow: z.ZodOptional<z.ZodObject<{
14092
+ label: z.ZodString;
14093
+ color: z.ZodOptional<z.ZodEnum<{
14094
+ primary: "primary";
14095
+ accent: "accent";
14096
+ success: "success";
14097
+ warning: "warning";
14098
+ danger: "danger";
14099
+ info: "info";
14100
+ highlight: "highlight";
14101
+ }>>;
14102
+ }, z.core.$strip>>;
14103
+ density: z.ZodOptional<z.ZodEnum<{
14104
+ default: "default";
14105
+ compact: "compact";
14106
+ }>>;
14107
+ titleSize: z.ZodOptional<z.ZodEnum<{
14108
+ default: "default";
14109
+ small: "small";
14110
+ large: "large";
14111
+ hero: "hero";
14112
+ }>>;
14113
+ subtitleSize: z.ZodOptional<z.ZodEnum<{
14114
+ lead: "lead";
14115
+ big: "big";
14116
+ default: "default";
14117
+ }>>;
14118
+ intro: z.ZodOptional<z.ZodEnum<{
14119
+ fade: "fade";
14120
+ "fade-up": "fade-up";
14121
+ "fade-down": "fade-down";
14122
+ "slide-left": "slide-left";
14123
+ "slide-right": "slide-right";
14124
+ "zoom-in": "zoom-in";
14125
+ }>>;
14126
+ staggerMs: z.ZodOptional<z.ZodNumber>;
14127
+ layout: z.ZodLiteral<"figure">;
13930
14128
  }, z.core.$strip>], "layout">;
13931
14129
  reference: z.ZodOptional<z.ZodString>;
13932
14130
  branding: z.ZodOptional<z.ZodNullable<z.ZodObject<{
@@ -18734,6 +18932,72 @@ export declare const mulmoStudioSchema: z.ZodObject<{
18734
18932
  }>>;
18735
18933
  staggerMs: z.ZodOptional<z.ZodNumber>;
18736
18934
  layout: z.ZodLiteral<"manifesto">;
18935
+ }, z.core.$strip>, z.ZodObject<{
18936
+ title: z.ZodString;
18937
+ stepLabel: z.ZodOptional<z.ZodString>;
18938
+ subtitle: z.ZodOptional<z.ZodString>;
18939
+ figure: z.ZodDiscriminatedUnion<[z.ZodObject<{
18940
+ type: z.ZodLiteral<"chart">;
18941
+ chartData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
18942
+ title: z.ZodOptional<z.ZodString>;
18943
+ }, z.core.$strip>, z.ZodObject<{
18944
+ type: z.ZodLiteral<"mermaid">;
18945
+ code: z.ZodString;
18946
+ title: z.ZodOptional<z.ZodString>;
18947
+ }, z.core.$strip>], "type">;
18948
+ accentColor: z.ZodOptional<z.ZodEnum<{
18949
+ primary: "primary";
18950
+ accent: "accent";
18951
+ success: "success";
18952
+ warning: "warning";
18953
+ danger: "danger";
18954
+ info: "info";
18955
+ highlight: "highlight";
18956
+ }>>;
18957
+ style: z.ZodOptional<z.ZodObject<{
18958
+ bgColor: z.ZodOptional<z.ZodString>;
18959
+ bgGradient: z.ZodOptional<z.ZodString>;
18960
+ decorations: z.ZodOptional<z.ZodBoolean>;
18961
+ bgOpacity: z.ZodOptional<z.ZodNumber>;
18962
+ footer: z.ZodOptional<z.ZodString>;
18963
+ }, z.core.$strip>>;
18964
+ eyebrow: z.ZodOptional<z.ZodObject<{
18965
+ label: z.ZodString;
18966
+ color: z.ZodOptional<z.ZodEnum<{
18967
+ primary: "primary";
18968
+ accent: "accent";
18969
+ success: "success";
18970
+ warning: "warning";
18971
+ danger: "danger";
18972
+ info: "info";
18973
+ highlight: "highlight";
18974
+ }>>;
18975
+ }, z.core.$strip>>;
18976
+ density: z.ZodOptional<z.ZodEnum<{
18977
+ default: "default";
18978
+ compact: "compact";
18979
+ }>>;
18980
+ titleSize: z.ZodOptional<z.ZodEnum<{
18981
+ default: "default";
18982
+ small: "small";
18983
+ large: "large";
18984
+ hero: "hero";
18985
+ }>>;
18986
+ subtitleSize: z.ZodOptional<z.ZodEnum<{
18987
+ lead: "lead";
18988
+ big: "big";
18989
+ default: "default";
18990
+ }>>;
18991
+ intro: z.ZodOptional<z.ZodEnum<{
18992
+ fade: "fade";
18993
+ "fade-up": "fade-up";
18994
+ "fade-down": "fade-down";
18995
+ "slide-left": "slide-left";
18996
+ "slide-right": "slide-right";
18997
+ "zoom-in": "zoom-in";
18998
+ }>>;
18999
+ staggerMs: z.ZodOptional<z.ZodNumber>;
19000
+ layout: z.ZodLiteral<"figure">;
18737
19001
  }, z.core.$strip>], "layout">;
18738
19002
  reference: z.ZodOptional<z.ZodString>;
18739
19003
  branding: z.ZodOptional<z.ZodNullable<z.ZodObject<{
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reading an html_tailwind `animation` value. Pure and dependency-free — the PNG render and the
3
+ * browser fragment path decide "is this animated, at what fps" the same way, and the browser
4
+ * bundle must not reach the schema module to do it.
5
+ */
6
+ export type AnimationConfig = {
7
+ fps?: number;
8
+ movie?: boolean;
9
+ };
10
+ /** Type guard: checks if animation value is an object config like { fps: 30 } */
11
+ export declare const isAnimationObject: (animation: unknown) => animation is AnimationConfig;
12
+ /** Check if a value is a valid animation config (true or non-array object) */
13
+ export declare const isAnimationEnabled: (animation: unknown) => animation is true | AnimationConfig;
14
+ /** Check if movie mode (CDP screencast) is enabled */
15
+ export declare const isMovieMode: (animation: unknown) => boolean;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Reading an html_tailwind `animation` value. Pure and dependency-free — the PNG render and the
3
+ * browser fragment path decide "is this animated, at what fps" the same way, and the browser
4
+ * bundle must not reach the schema module to do it.
5
+ */
6
+ /** Type guard: checks if animation value is an object config like { fps: 30 } */
7
+ export const isAnimationObject = (animation) => {
8
+ return typeof animation === "object" && animation !== null && !Array.isArray(animation);
9
+ };
10
+ /** Check if a value is a valid animation config (true or non-array object) */
11
+ export const isAnimationEnabled = (animation) => {
12
+ return animation === true || isAnimationObject(animation);
13
+ };
14
+ /** Check if movie mode (CDP screencast) is enabled */
15
+ export const isMovieMode = (animation) => {
16
+ return isAnimationObject(animation) && animation.movie === true;
17
+ };
@@ -1,15 +1,11 @@
1
1
  import type { MulmoChartMedia } from "../../types/index.js";
2
- import type { BeatHtmlFragment } from "./type.js";
2
+ import type { BeatHtmlFragment, BeatHtmlOptions } from "./type.js";
3
3
  /**
4
- * A chart beat as markup a host can inject.
4
+ * A chart beat as a deck `figure` slide, so it shares the `slide` beat's theme and its one
5
+ * host code path: `slideUtilityCss`, and the `[data-mulmo-chart]` canvas driven from the
6
+ * attribute rather than from an inline `<script>`, which neither runs through `innerHTML`
7
+ * nor survives sanitizing.
5
8
  *
6
- * The config rides on the canvas as `data-mulmo-chart` rather than in an inline `<script>`:
7
- * a script injected through `innerHTML` does not execute, and does not survive sanitizing
8
- * either. The attribute shape matches `@mulmocast/deck`'s, so a host that already drives
9
- * `[data-mulmo-chart]` for slides drives this too without a second code path.
10
- *
11
- * The markup itself comes from `image_plugins/chart_html.ts`, the same module the Node
12
- * render path uses — a second copy of it would drift, and the two paths have to draw the
13
- * same chart.
9
+ * The PNG path still draws from its own template, so the browser and the PNG differ in look.
14
10
  */
15
- export declare const chartToHtml: (image: MulmoChartMedia, idPrefix: string) => BeatHtmlFragment;
11
+ export declare const chartToHtml: (image: MulmoChartMedia, options: BeatHtmlOptions) => BeatHtmlFragment;
@@ -1,21 +1,11 @@
1
- import { chartFragmentHtml, chartPluginCdns, chartTypeOf } from "../image_plugins/chart_html.js";
1
+ import { deckSlideToHtml } from "./slide.js";
2
+ import { backgroundDecoration } from "./decoration.js";
2
3
  /**
3
- * A chart beat as markup a host can inject.
4
+ * A chart beat as a deck `figure` slide, so it shares the `slide` beat's theme and its one
5
+ * host code path: `slideUtilityCss`, and the `[data-mulmo-chart]` canvas driven from the
6
+ * attribute rather than from an inline `<script>`, which neither runs through `innerHTML`
7
+ * nor survives sanitizing.
4
8
  *
5
- * The config rides on the canvas as `data-mulmo-chart` rather than in an inline `<script>`:
6
- * a script injected through `innerHTML` does not execute, and does not survive sanitizing
7
- * either. The attribute shape matches `@mulmocast/deck`'s, so a host that already drives
8
- * `[data-mulmo-chart]` for slides drives this too without a second code path.
9
- *
10
- * The markup itself comes from `image_plugins/chart_html.ts`, the same module the Node
11
- * render path uses — a second copy of it would drift, and the two paths have to draw the
12
- * same chart.
9
+ * The PNG path still draws from its own template, so the browser and the PNG differ in look.
13
10
  */
14
- export const chartToHtml = (image, idPrefix) => {
15
- const plugins = chartPluginCdns(chartTypeOf(image.chartData));
16
- return {
17
- html: chartFragmentHtml(image.chartData, image.title, `${idPrefix}-chart`),
18
- requires: ["chart"],
19
- ...(plugins.length > 0 ? { chartPlugins: plugins } : {}),
20
- };
21
- };
11
+ export const chartToHtml = (image, options) => deckSlideToHtml({ layout: "figure", title: image.title, figure: { type: "chart", chartData: image.chartData } }, undefined, options, backgroundDecoration(image, options));
@@ -0,0 +1,16 @@
1
+ import type { ResolvedBranding, SlideBranding } from "@mulmocast/deck";
2
+ import type { BackgroundImage } from "../../types/index.js";
3
+ import type { BeatHtmlFragment, BeatHtmlOptions } from "./type.js";
4
+ export declare const safeMediaUrl: (url: string) => string;
5
+ /** A textSlide / markdown fragment with `documentCss` for the background the PNG paints on `body`. */
6
+ export declare const withDocumentBackground: (fragment: BeatHtmlFragment | undefined, image: {
7
+ backgroundImage?: BackgroundImage;
8
+ }, options: BeatHtmlOptions) => BeatHtmlFragment | undefined;
9
+ /** A chart / mermaid beat's background, as the branding layer of the figure slide deck draws it on. */
10
+ export declare const backgroundDecoration: (image: {
11
+ backgroundImage?: BackgroundImage;
12
+ }, options: BeatHtmlOptions) => {
13
+ branding?: ResolvedBranding;
14
+ };
15
+ /** A slide beat's branding, resolved as `convertBrandingToResolved` does on the Node side. */
16
+ export declare const slideBranding: (beatBranding: SlideBranding | null | undefined, options: BeatHtmlOptions) => ResolvedBranding | undefined;
@@ -0,0 +1,55 @@
1
+ import { backgroundImageCss, backgroundImageLayout, resolveBackgroundImage } from "../image_plugins/bg_image_css.js";
2
+ import { pickBranding } from "../image_plugins/slide_branding.js";
3
+ import { mediaSrc } from "../image_plugins/media_html.js";
4
+ import { neutralizeStyleTerminator } from "../html_escape.js";
5
+ /**
6
+ * The decorations the PNG render adds around a beat's content — a background image, a slide's
7
+ * branding — resolved for the browser. The PNG path turns each image into a `data:` URI on disk;
8
+ * here the host says how its page reaches the file (`resolveMediaUrl`).
9
+ */
10
+ // deck writes these urls into `style="…url('…')"` and `src="…"` unescaped, and a url from a script can carry a quote
11
+ // (`z.url()` accepts one). Percent-encoding leaves a url that still loads and cannot leave either context.
12
+ const UNSAFE_URL_CHARACTERS = /["'()\\<>\s]/g;
13
+ export const safeMediaUrl = (url) => url.replace(UNSAFE_URL_CHARACTERS, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
14
+ const mediaUrl = (source, options) => {
15
+ const url = (options.resolveMediaUrl ?? mediaSrc)(source);
16
+ return url === undefined ? undefined : safeMediaUrl(url);
17
+ };
18
+ const paintedBackground = (beatBackgroundImage, options) => {
19
+ const backgroundImage = resolveBackgroundImage(beatBackgroundImage, options.backgroundImage);
20
+ if (!backgroundImage)
21
+ return undefined;
22
+ const url = typeof backgroundImage === "string" ? safeMediaUrl(backgroundImage) : mediaUrl(backgroundImage.source, options);
23
+ return url === undefined ? undefined : { url, ...backgroundImageLayout(backgroundImage) };
24
+ };
25
+ /** A textSlide / markdown fragment with `documentCss` for the background the PNG paints on `body`. */
26
+ export const withDocumentBackground = (fragment, image, options) => {
27
+ const background = paintedBackground(image.backgroundImage, options);
28
+ if (!fragment || !background)
29
+ return fragment;
30
+ return { ...fragment, documentCss: neutralizeStyleTerminator(backgroundImageCss(background.url, background.size, background.opacity)) };
31
+ };
32
+ /** A chart / mermaid beat's background, as the branding layer of the figure slide deck draws it on. */
33
+ export const backgroundDecoration = (image, options) => {
34
+ const background = paintedBackground(image.backgroundImage, options);
35
+ return background ? { branding: { backgroundImage: { dataUrl: background.url, size: background.size, opacity: background.opacity } } } : {};
36
+ };
37
+ const resolvedLogo = (logo, options) => {
38
+ const dataUrl = mediaUrl(logo.source, options);
39
+ return dataUrl === undefined ? undefined : { dataUrl, position: logo.position, width: logo.width };
40
+ };
41
+ const resolvedBrandingBackground = (backgroundImage, options) => {
42
+ const dataUrl = mediaUrl(backgroundImage.source, options);
43
+ if (dataUrl === undefined)
44
+ return undefined;
45
+ return { dataUrl, size: backgroundImage.size ?? "cover", opacity: backgroundImage.opacity ?? 1, bgOpacity: backgroundImage.bgOpacity };
46
+ };
47
+ /** A slide beat's branding, resolved as `convertBrandingToResolved` does on the Node side. */
48
+ export const slideBranding = (beatBranding, options) => {
49
+ const branding = pickBranding(beatBranding, options.branding);
50
+ if (!branding)
51
+ return undefined;
52
+ const logo = branding.logo && resolvedLogo(branding.logo, options);
53
+ const backgroundImage = branding.backgroundImage && resolvedBrandingBackground(branding.backgroundImage, options);
54
+ return { ...(logo ? { logo } : {}), ...(backgroundImage ? { backgroundImage } : {}) };
55
+ };
@@ -4,12 +4,12 @@ import type { BeatHtmlFragment } from "./type.js";
4
4
  /**
5
5
  * An html_tailwind beat as markup a host can inject.
6
6
  *
7
- * The beat at rest — the same thing the Node dump path shows. This module generates no
8
- * `<script>`: the author's `script` FIELD, the swipe animation driver and the frame-based
9
- * `animation` all need script execution, which a fragment injected through `innerHTML`
10
- * cannot do and which the dump path has never emitted either. A `<script>` the author wrote
11
- * inside `html` is markup, and passes through unsanitized — see below. A beat whose only content is an animation therefore shows
12
- * its elements in their starting positions rather than nothing.
7
+ * `html` is the beat at rest — the same thing the Node dump path shows, with no generated
8
+ * `<script>`: a fragment injected through `innerHTML` cannot run one. The author's `script`
9
+ * FIELD, the swipe driver and the frame-based `animation` come back separately as
10
+ * `documentScript`, for a host that gives the beat a document of its own and can run them; it
11
+ * brings the beat to what the PNG shows (an animation's final frame). A `<script>` the author
12
+ * wrote inside `html` is markup, and passes through unsanitized — see below.
13
13
  *
14
14
  * The author's html is emitted as written. On this beat type markup IS the content — the
15
15
  * schema also accepts `script` — so escaping it would remove the feature rather than a
@@ -1,13 +1,14 @@
1
1
  import { htmlTailwindMarkup } from "../image_plugins/html_tailwind_markup.js";
2
+ import { htmlTailwindDocumentCss, htmlTailwindDocumentScript } from "../image_plugins/html_tailwind_script.js";
2
3
  /**
3
4
  * An html_tailwind beat as markup a host can inject.
4
5
  *
5
- * The beat at rest — the same thing the Node dump path shows. This module generates no
6
- * `<script>`: the author's `script` FIELD, the swipe animation driver and the frame-based
7
- * `animation` all need script execution, which a fragment injected through `innerHTML`
8
- * cannot do and which the dump path has never emitted either. A `<script>` the author wrote
9
- * inside `html` is markup, and passes through unsanitized — see below. A beat whose only content is an animation therefore shows
10
- * its elements in their starting positions rather than nothing.
6
+ * `html` is the beat at rest — the same thing the Node dump path shows, with no generated
7
+ * `<script>`: a fragment injected through `innerHTML` cannot run one. The author's `script`
8
+ * FIELD, the swipe driver and the frame-based `animation` come back separately as
9
+ * `documentScript`, for a host that gives the beat a document of its own and can run them; it
10
+ * brings the beat to what the PNG shows (an animation's final frame). A `<script>` the author
11
+ * wrote inside `html` is markup, and passes through unsanitized — see below.
11
12
  *
12
13
  * The author's html is emitted as written. On this beat type markup IS the content — the
13
14
  * schema also accepts `script` — so escaping it would remove the feature rather than a
@@ -16,5 +17,10 @@ import { htmlTailwindMarkup } from "../image_plugins/html_tailwind_markup.js";
16
17
  */
17
18
  export const htmlTailwindToHtml = (image) => {
18
19
  const html = htmlTailwindMarkup(image);
19
- return html === "" ? undefined : { html };
20
+ const documentScript = htmlTailwindDocumentScript(image);
21
+ const documentCss = htmlTailwindDocumentCss(image);
22
+ // A script-only beat draws itself, so it is a fragment with empty markup rather than nothing.
23
+ if (!documentScript && html === "")
24
+ return undefined;
25
+ return { html, ...(documentCss ? { documentCss } : {}), ...(documentScript ? { documentScript } : {}) };
20
26
  };
@@ -6,6 +6,7 @@ import { imageToHtml, movieToHtml } from "./media.js";
6
6
  import { mermaidToHtml } from "./mermaid.js";
7
7
  import { slideToHtml } from "./slide.js";
8
8
  import { assertSafeElementId } from "../element_id.js";
9
+ import { withDocumentBackground } from "./decoration.js";
9
10
  export { textSlideToMarkdown, textSlideToHtml } from "./text_slide.js";
10
11
  export { markdownToHtml } from "./markdown.js";
11
12
  /**
@@ -42,13 +43,13 @@ export const beatToHtml = (beat, options) => {
42
43
  assertSafeElementId(options.idPrefix);
43
44
  switch (image.type) {
44
45
  case "textSlide":
45
- return textSlideToHtml(image);
46
+ return withDocumentBackground(textSlideToHtml(image), image, options);
46
47
  case "markdown":
47
- return markdownToHtml(image, options.idPrefix);
48
+ return withDocumentBackground(markdownToHtml(image, options.idPrefix), image, options);
48
49
  case "chart":
49
- return chartToHtml(image, options.idPrefix);
50
+ return chartToHtml(image, options);
50
51
  case "mermaid":
51
- return mermaidToHtml(image, options.idPrefix);
52
+ return mermaidToHtml(image, options);
52
53
  case "image":
53
54
  // The only description of the picture this module can reach. A host with more should
54
55
  // set its own alt; an <img> with none makes a screen reader read the file name.
@@ -1,14 +1,11 @@
1
1
  import type { MulmoMermaidMedia } from "../../types/index.js";
2
- import type { BeatHtmlFragment } from "./type.js";
2
+ import type { BeatHtmlFragment, BeatHtmlOptions } from "./type.js";
3
3
  /**
4
- * A mermaid beat as markup a host can inject.
4
+ * A mermaid beat as a deck `figure` slide — see `chart.ts` for why deck draws it.
5
5
  *
6
6
  * Only `code.kind === "text"` renders. The other kinds — `url`, `path`, `base64` — are
7
7
  * resolved by `MulmoMediaSourceMethods.getText`, which fetches and reads files, so they
8
8
  * cannot be reached from here and `beatToHtml` returns undefined rather than inventing a
9
9
  * placeholder. `mermaid.ts`'s own markdown dump already draws the same line.
10
- *
11
- * The markup comes from `image_plugins/mermaid_html.ts`, the module the Node render path
12
- * uses, so the browser and the PNG draw the same diagram.
13
10
  */
14
- export declare const mermaidToHtml: (image: MulmoMermaidMedia, idPrefix: string) => BeatHtmlFragment | undefined;
11
+ export declare const mermaidToHtml: (image: MulmoMermaidMedia, options: BeatHtmlOptions) => BeatHtmlFragment | undefined;
@@ -1,22 +1,17 @@
1
- import { mermaidHtml } from "../image_plugins/mermaid_html.js";
1
+ import { deckSlideToHtml } from "./slide.js";
2
+ import { backgroundDecoration } from "./decoration.js";
2
3
  /**
3
- * A mermaid beat as markup a host can inject.
4
+ * A mermaid beat as a deck `figure` slide — see `chart.ts` for why deck draws it.
4
5
  *
5
6
  * Only `code.kind === "text"` renders. The other kinds — `url`, `path`, `base64` — are
6
7
  * resolved by `MulmoMediaSourceMethods.getText`, which fetches and reads files, so they
7
8
  * cannot be reached from here and `beatToHtml` returns undefined rather than inventing a
8
9
  * placeholder. `mermaid.ts`'s own markdown dump already draws the same line.
9
- *
10
- * The markup comes from `image_plugins/mermaid_html.ts`, the module the Node render path
11
- * uses, so the browser and the PNG draw the same diagram.
12
10
  */
13
- export const mermaidToHtml = (image, idPrefix) => {
11
+ export const mermaidToHtml = (image, options) => {
14
12
  if (image.code.kind !== "text")
15
13
  return undefined;
16
14
  const appendix = image.appendix?.join("\n") ?? "";
17
15
  const code = `${image.code.text}\n${appendix}`.trim();
18
- return {
19
- html: mermaidHtml(code, `${idPrefix}-mermaid`, image.title || "Diagram"),
20
- requires: ["mermaid"],
21
- };
16
+ return deckSlideToHtml({ layout: "figure", title: image.title || "Diagram", figure: { type: "mermaid", code } }, undefined, options, backgroundDecoration(image, options));
22
17
  };