mulmocast 2.9.2 → 2.10.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/lib/cli/bin.js +0 -0
- package/lib/index.browser.d.ts +7 -0
- package/lib/index.browser.js +6 -0
- package/lib/types/const.d.ts +7 -0
- package/lib/types/const.js +7 -0
- package/lib/types/schema.js +6 -2
- package/lib/utils/beat_html/chart.d.ts +15 -0
- package/lib/utils/beat_html/chart.js +21 -0
- package/lib/utils/beat_html/html_tailwind.d.ts +19 -0
- package/lib/utils/beat_html/html_tailwind.js +20 -0
- package/lib/utils/beat_html/index.d.ts +33 -0
- package/lib/utils/beat_html/index.js +65 -0
- package/lib/utils/beat_html/markdown.d.ts +20 -0
- package/lib/utils/beat_html/markdown.js +25 -0
- package/lib/utils/beat_html/media.d.ts +14 -0
- package/lib/utils/beat_html/media.js +19 -0
- package/lib/utils/beat_html/mermaid.d.ts +14 -0
- package/lib/utils/beat_html/mermaid.js +22 -0
- package/lib/utils/beat_html/slide.d.ts +21 -0
- package/lib/utils/beat_html/slide.js +33 -0
- package/lib/utils/beat_html/text_slide.d.ts +17 -0
- package/lib/utils/beat_html/text_slide.js +24 -0
- package/lib/utils/beat_html/type.d.ts +73 -0
- package/lib/utils/element_id.d.ts +26 -0
- package/lib/utils/element_id.js +31 -0
- package/lib/utils/html_escape.d.ts +14 -0
- package/lib/utils/html_escape.js +30 -0
- package/lib/utils/image_plugins/bg_image_util.js +2 -1
- package/lib/utils/image_plugins/chart.js +6 -30
- package/lib/utils/image_plugins/chart_html.d.ts +24 -0
- package/lib/utils/image_plugins/chart_html.js +81 -0
- package/lib/utils/image_plugins/html_tailwind.js +2 -5
- package/lib/utils/image_plugins/html_tailwind_markup.d.ts +21 -0
- package/lib/utils/image_plugins/html_tailwind_markup.js +8 -0
- package/lib/utils/image_plugins/markdown.js +41 -30
- package/lib/utils/image_plugins/markdown_layout.d.ts +46 -4
- package/lib/utils/image_plugins/markdown_layout.js +96 -83
- package/lib/utils/image_plugins/media_html.d.ts +16 -0
- package/lib/utils/image_plugins/media_html.js +39 -0
- package/lib/utils/image_plugins/mermaid.d.ts +1 -0
- package/lib/utils/image_plugins/mermaid.js +4 -16
- package/lib/utils/image_plugins/mermaid_html.d.ts +22 -0
- package/lib/utils/image_plugins/mermaid_html.js +36 -0
- package/lib/utils/image_plugins/movie.js +2 -15
- package/lib/utils/sdk_timeout.d.ts +3 -0
- package/lib/utils/sdk_timeout.js +7 -0
- package/lib/utils/swipe_to_html.d.ts +18 -0
- package/lib/utils/swipe_to_html.js +26 -2
- package/package.json +11 -10
- package/scripts/test/test_captions_2.json~ +278 -0
- package/scripts/test/test_en_portrait.json~ +31 -0
- package/scripts/test/test_slide_image_ref_gemini_en.json~ +289 -0
- package/scripts/test/test_vertexai.json~ +21 -0
- package/lib/agents/image_google_agent.d.ts +0 -5
- package/lib/agents/image_google_agent.js +0 -86
- package/lib/agents/movie_google_agent.d.ts +0 -9
- package/lib/agents/movie_google_agent.js +0 -122
- package/lib/agents/mulmo_prompts_agent.d.ts +0 -7
- package/lib/agents/mulmo_prompts_agent.js +0 -37
- package/lib/agents/prompts_data.d.ts +0 -15
- package/lib/agents/prompts_data.js +0 -16
- package/lib/agents/tts_nijivoice_agent.d.ts +0 -5
- package/lib/agents/tts_nijivoice_agent.js +0 -69
- package/lib/agents/validate_mulmo_script_agent.d.ts +0 -17
- package/lib/agents/validate_mulmo_script_agent.js +0 -34
- package/lib/cli/args.d.ts +0 -15
- package/lib/cli/args.js +0 -62
- package/lib/cli/cli.d.ts +0 -17
- package/lib/cli/cli.js +0 -117
- package/lib/cli/run.d.ts +0 -2
- package/lib/cli/run.js +0 -3
- package/lib/cli/tool-args.d.ts +0 -16
- package/lib/cli/tool-args.js +0 -64
- package/lib/cli/tool-cli.d.ts +0 -2
- package/lib/cli/tool-cli.js +0 -32
- package/lib/cli/tool_commands/prompt/builder.d.ts +0 -4
- package/lib/cli/tool_commands/prompt/builder.js +0 -11
- package/lib/cli/tool_commands/prompt/handler.d.ts +0 -4
- package/lib/cli/tool_commands/prompt/handler.js +0 -14
- package/lib/cli/tool_commands/prompt/index.d.ts +0 -4
- package/lib/cli/tool_commands/prompt/index.js +0 -4
- package/lib/cli/tool_commands/schema/builder.d.ts +0 -2
- package/lib/cli/tool_commands/schema/builder.js +0 -3
- package/lib/cli/tool_commands/schema/handler.d.ts +0 -2
- package/lib/cli/tool_commands/schema/handler.js +0 -12
- package/lib/cli/tool_commands/schema/index.d.ts +0 -4
- package/lib/cli/tool_commands/schema/index.js +0 -4
- package/lib/cli/tool_commands/scripting/builder.d.ts +0 -20
- package/lib/cli/tool_commands/scripting/builder.js +0 -63
- package/lib/cli/tool_commands/scripting/handler.d.ts +0 -12
- package/lib/cli/tool_commands/scripting/handler.js +0 -36
- package/lib/cli/tool_commands/scripting/index.d.ts +0 -4
- package/lib/cli/tool_commands/scripting/index.js +0 -4
- package/lib/index.d.ts +0 -8
- package/lib/index.js +0 -8
- package/lib/methods/mulmo_script_template.d.ts +0 -4
- package/lib/methods/mulmo_script_template.js +0 -6
- package/lib/methods/mulmo_studio.d.ts +0 -8
- package/lib/methods/mulmo_studio.js +0 -25
- package/lib/slide/blocks.d.ts +0 -13
- package/lib/slide/blocks.js +0 -251
- package/lib/slide/index.d.ts +0 -6
- package/lib/slide/index.js +0 -7
- package/lib/slide/layouts/big_quote.d.ts +0 -2
- package/lib/slide/layouts/big_quote.js +0 -19
- package/lib/slide/layouts/columns.d.ts +0 -2
- package/lib/slide/layouts/columns.js +0 -53
- package/lib/slide/layouts/comparison.d.ts +0 -2
- package/lib/slide/layouts/comparison.js +0 -25
- package/lib/slide/layouts/funnel.d.ts +0 -2
- package/lib/slide/layouts/funnel.js +0 -25
- package/lib/slide/layouts/grid.d.ts +0 -2
- package/lib/slide/layouts/grid.js +0 -38
- package/lib/slide/layouts/index.d.ts +0 -3
- package/lib/slide/layouts/index.js +0 -46
- package/lib/slide/layouts/matrix.d.ts +0 -2
- package/lib/slide/layouts/matrix.js +0 -53
- package/lib/slide/layouts/split.d.ts +0 -2
- package/lib/slide/layouts/split.js +0 -51
- package/lib/slide/layouts/stats.d.ts +0 -2
- package/lib/slide/layouts/stats.js +0 -23
- package/lib/slide/layouts/table.d.ts +0 -2
- package/lib/slide/layouts/table.js +0 -10
- package/lib/slide/layouts/timeline.d.ts +0 -2
- package/lib/slide/layouts/timeline.js +0 -27
- package/lib/slide/layouts/title.d.ts +0 -2
- package/lib/slide/layouts/title.js +0 -19
- package/lib/slide/layouts/waterfall.d.ts +0 -2
- package/lib/slide/layouts/waterfall.js +0 -63
- package/lib/slide/render.d.ts +0 -17
- package/lib/slide/render.js +0 -101
- package/lib/slide/schema.d.ts +0 -9309
- package/lib/slide/schema.js +0 -434
- package/lib/slide/utils.d.ts +0 -76
- package/lib/slide/utils.js +0 -243
- package/lib/tools/prompt.js +0 -18
- package/lib/tools/seed.d.ts +0 -3
- package/lib/tools/seed.js +0 -211
- package/lib/tools/seed_from_url.d.ts +0 -3
- package/lib/tools/seed_from_url.js +0 -188
- package/lib/types/slide.d.ts +0 -9309
- package/lib/types/slide.js +0 -434
- package/lib/utils/assets.d.ts +0 -18
- package/lib/utils/assets.js +0 -101
- package/lib/utils/cli.d.ts +0 -1
- package/lib/utils/cli.js +0 -15
- package/lib/utils/const.d.ts +0 -12
- package/lib/utils/const.js +0 -12
- package/lib/utils/image_preprocess.d.ts +0 -14
- package/lib/utils/image_preprocess.js +0 -52
- package/lib/utils/markdown.d.ts +0 -3
- package/lib/utils/markdown.js +0 -42
- package/lib/utils/pdf.d.ts +0 -9
- package/lib/utils/pdf.js +0 -77
- package/lib/utils/preprocess.d.ts +0 -343
- package/lib/utils/preprocess.js +0 -58
- package/lib/utils/provider2agent.d.ts +0 -141
- package/lib/utils/provider2agent.js +0 -267
- package/lib/utils/text_hash.d.ts +0 -1
- package/lib/utils/text_hash.js +0 -4
- /package/lib/{tools/prompt.d.ts → utils/beat_html/type.js} +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escaping for JSON embedded in an inline script. Pure — no Node, no filesystem — because the
|
|
3
|
+
* Node render path and the browser fragment path embed the same values and must neutralize
|
|
4
|
+
* them the same way.
|
|
5
|
+
*
|
|
6
|
+
* HTML text and attributes are handled by escapeHtml from @mulmocast/deck; this module exists
|
|
7
|
+
* only for the inline-script context, which that helper does not cover.
|
|
8
|
+
*
|
|
9
|
+
* The lookup goes through a Map rather than an object literal so a value naming an
|
|
10
|
+
* Object.prototype member cannot resolve through the prototype chain.
|
|
11
|
+
*/
|
|
12
|
+
// JSON.stringify emits these only from inside string literals, so replacing them with their
|
|
13
|
+
// \u escapes leaves the parsed value identical while stopping `</script>` from terminating the
|
|
14
|
+
// block the JSON is embedded in. U+2028 / U+2029 are valid JSON output but were line
|
|
15
|
+
// terminators in JavaScript source before ES2019.
|
|
16
|
+
const SCRIPT_JSON_ESCAPES = new Map([
|
|
17
|
+
["<", "\\u003c"],
|
|
18
|
+
[">", "\\u003e"],
|
|
19
|
+
["&", "\\u0026"],
|
|
20
|
+
["\u2028", "\\u2028"],
|
|
21
|
+
["\u2029", "\\u2029"],
|
|
22
|
+
]);
|
|
23
|
+
/** Neutralize the output of JSON.stringify for embedding inside an inline `<script>`. */
|
|
24
|
+
export const escapeJsonForScript = (json) => json.replace(/[<>&\u2028\u2029]/g, (character) => SCRIPT_JSON_ESCAPES.get(character) ?? character);
|
|
25
|
+
// A <style> element holds raw text: nothing inside it is parsed as markup except the closing
|
|
26
|
+
// tag, so `</style` is the only sequence that can end it. Neutralizing just that keeps
|
|
27
|
+
// author-supplied CSS working, which escaping the whole string would not. `\3c ` is the CSS
|
|
28
|
+
// escape for `<`, and the matched text is carried through unchanged so a `</STYLE>` written
|
|
29
|
+
// inside a CSS string keeps its casing as well as its value.
|
|
30
|
+
export const neutralizeStyleTerminator = (css) => css.replace(/<\/style/gi, (terminator) => `\\3c ${terminator.slice(1)}`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MulmoMediaSourceMethods } from "../../methods/mulmo_media_source.js";
|
|
2
2
|
import { resolveStyle } from "./utils.js";
|
|
3
3
|
import { safeFetch, DEFAULT_FETCH_TIMEOUT_MS } from "../fetch.js";
|
|
4
|
+
import { neutralizeStyleTerminator } from "../html_escape.js";
|
|
4
5
|
/**
|
|
5
6
|
* Resolve background image from beat level and global level settings.
|
|
6
7
|
* Beat level takes precedence over global level.
|
|
@@ -50,7 +51,7 @@ export const resolveCombinedStyle = async (params, beatBackgroundImage, beatStyl
|
|
|
50
51
|
const resolvedBackgroundImage = resolveBackgroundImage(beatBackgroundImage, globalBackgroundImage);
|
|
51
52
|
const backgroundCSS = await backgroundImageToCSS(resolvedBackgroundImage, context);
|
|
52
53
|
const style = resolveStyle(beatStyle, textSlideStyle);
|
|
53
|
-
return backgroundCSS + style;
|
|
54
|
+
return neutralizeStyleTerminator(backgroundCSS + style);
|
|
54
55
|
};
|
|
55
56
|
export const backgroundImageToCSS = async (backgroundImage, context) => {
|
|
56
57
|
if (!backgroundImage) {
|
|
@@ -2,19 +2,8 @@ import { getHTMLFile } from "../file.js";
|
|
|
2
2
|
import { renderHTMLToImage, interpolate } from "../html_render.js";
|
|
3
3
|
import { parrotingImagePath, generateUniqueId } from "./utils.js";
|
|
4
4
|
import { resolveCombinedStyle } from "./bg_image_util.js";
|
|
5
|
+
import { chartHtml, escapedChartTemplateValues, resolveChartPlugins, stringifyChartData } from "./chart_html.js";
|
|
5
6
|
export const imageType = "chart";
|
|
6
|
-
/** Chart.js plugin CDN URLs keyed by chart type */
|
|
7
|
-
const CHART_PLUGIN_CDNS = {
|
|
8
|
-
sankey: "https://cdn.jsdelivr.net/npm/chartjs-chart-sankey",
|
|
9
|
-
treemap: "https://cdn.jsdelivr.net/npm/chartjs-chart-treemap@3",
|
|
10
|
-
};
|
|
11
|
-
/** Resolve CDN script tags for Chart.js plugins based on chart type */
|
|
12
|
-
const resolveChartPlugins = (chartType) => {
|
|
13
|
-
const cdn = CHART_PLUGIN_CDNS[chartType];
|
|
14
|
-
if (!cdn)
|
|
15
|
-
return "";
|
|
16
|
-
return `<script src="${cdn}"></script>`;
|
|
17
|
-
};
|
|
18
7
|
const processChart = async (params) => {
|
|
19
8
|
const { beat, imagePath, canvasSize } = params;
|
|
20
9
|
if (!beat.image || beat.image.type !== imageType)
|
|
@@ -25,10 +14,9 @@ const processChart = async (params) => {
|
|
|
25
14
|
const combinedStyle = await resolveCombinedStyle(params, beat.image.backgroundImage, beat.image.style);
|
|
26
15
|
const template = getHTMLFile("chart");
|
|
27
16
|
const htmlData = interpolate(template, {
|
|
28
|
-
title
|
|
17
|
+
...escapedChartTemplateValues(beat.image.title, beat.image.chartData),
|
|
29
18
|
style: combinedStyle,
|
|
30
19
|
chart_width: chart_width.toString(),
|
|
31
|
-
chart_data: JSON.stringify(beat.image.chartData),
|
|
32
20
|
chart_plugins: resolveChartPlugins(chartType),
|
|
33
21
|
});
|
|
34
22
|
await renderHTMLToImage(htmlData, imagePath, canvasSize.width, canvasSize.height);
|
|
@@ -38,22 +26,10 @@ const dumpHtml = async (params) => {
|
|
|
38
26
|
const { beat } = params;
|
|
39
27
|
if (!beat.image || beat.image.type !== imageType)
|
|
40
28
|
return;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
return
|
|
45
|
-
<div class="chart-container mb-6">
|
|
46
|
-
<h3 class="text-xl font-semibold mb-4">${title}</h3>
|
|
47
|
-
<div class="w-full" style="position: relative; height: 400px;">
|
|
48
|
-
<canvas id="${chartId}"></canvas>
|
|
49
|
-
</div>
|
|
50
|
-
<script>
|
|
51
|
-
(function() {
|
|
52
|
-
const ctx = document.getElementById('${chartId}').getContext('2d');
|
|
53
|
-
new Chart(ctx, ${chartData});
|
|
54
|
-
})();
|
|
55
|
-
</script>
|
|
56
|
-
</div>`;
|
|
29
|
+
// main と同じ評価順(stringify → title 読み取り → id 生成)を保つ。引数式は本体より先に
|
|
30
|
+
// 評価されるので、stringify を wrapper 側でやらないと throw / getter の順序が入れ替わる。
|
|
31
|
+
const chartDataJson = stringifyChartData(beat.image.chartData);
|
|
32
|
+
return chartHtml(chartDataJson, beat.image.title, generateUniqueId("chart"));
|
|
57
33
|
};
|
|
58
34
|
export const process = processChart;
|
|
59
35
|
export const path = parrotingImagePath;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MulmoChartMedia } from "../../types/index.js";
|
|
2
|
+
/** Resolve CDN script tags for Chart.js plugins based on chart type */
|
|
3
|
+
export declare const resolveChartPlugins: (chartType: string) => string;
|
|
4
|
+
export declare const stringifyChartData: (chartData: MulmoChartMedia["chartData"]) => string;
|
|
5
|
+
/** For a standalone document, where the inline script runs. */
|
|
6
|
+
export declare const chartHtml: (chartDataJson: string, title: string, chartId: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* For a host that injects the markup into its own page, where an injected `<script>`
|
|
9
|
+
* neither survives sanitizing nor executes via innerHTML. The config rides on the canvas
|
|
10
|
+
* instead, in the shape `@mulmocast/deck` already uses so one host runtime drives both.
|
|
11
|
+
*/
|
|
12
|
+
export declare const chartFragmentHtml: (chartData: MulmoChartMedia["chartData"], title: string, chartId: string) => string;
|
|
13
|
+
/** `chartData` is a free record, so the type may be absent or not a string. */
|
|
14
|
+
export declare const chartTypeOf: (chartData: MulmoChartMedia["chartData"]) => string;
|
|
15
|
+
/** The Chart.js plugin CDNs a chart type needs, for a host that loads them once per page. */
|
|
16
|
+
export declare const chartPluginCdns: (chartType: string) => string[];
|
|
17
|
+
/**
|
|
18
|
+
* The two values the PNG/PDF/movie template interpolates from user data, escaped for the
|
|
19
|
+
* contexts it drops them into: `<h1>${title}</h1>` and `const chartData = ${chart_data};`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const escapedChartTemplateValues: (title: string, chartData: MulmoChartMedia["chartData"]) => {
|
|
22
|
+
title: string;
|
|
23
|
+
chart_data: string;
|
|
24
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Deep import: the package barrel pulls in every layout and all of zod, which measured
|
|
2
|
+
// 551kb against 1.5kb for this file, and the browser fragment path bundles this module.
|
|
3
|
+
import { escapeHtml } from "@mulmocast/deck/lib/utils.js";
|
|
4
|
+
import { escapeJsonForScript } from "../html_escape.js";
|
|
5
|
+
import { assertSafeElementId } from "../element_id.js";
|
|
6
|
+
/**
|
|
7
|
+
* Chart.js markup and plugin resolution. Pure — no Node, no filesystem — because both the
|
|
8
|
+
* Node render path and the browser fragment path draw the same chart and must draw it the
|
|
9
|
+
* same way.
|
|
10
|
+
*
|
|
11
|
+
* The canvas id is a parameter rather than generated here: the Node path renders once to a
|
|
12
|
+
* PNG so a random id is fine, while the browser path re-renders and needs the same beat to
|
|
13
|
+
* produce the same markup, or a host diffing fragments sees every chart change identity on
|
|
14
|
+
* every render.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Chart.js plugin CDN URLs keyed by chart type. A Map rather than an object literal: the
|
|
18
|
+
* chart type comes from the script, so an object lookup resolves `constructor` and
|
|
19
|
+
* `__proto__` through the prototype chain and hands back a function.
|
|
20
|
+
*/
|
|
21
|
+
const CHART_PLUGIN_CDNS = new Map([
|
|
22
|
+
["sankey", "https://cdn.jsdelivr.net/npm/chartjs-chart-sankey"],
|
|
23
|
+
["treemap", "https://cdn.jsdelivr.net/npm/chartjs-chart-treemap@3"],
|
|
24
|
+
]);
|
|
25
|
+
/** Resolve CDN script tags for Chart.js plugins based on chart type */
|
|
26
|
+
export const resolveChartPlugins = (chartType) => {
|
|
27
|
+
const cdn = CHART_PLUGIN_CDNS.get(chartType);
|
|
28
|
+
if (!cdn)
|
|
29
|
+
return "";
|
|
30
|
+
return `<script src="${cdn}"></script>`;
|
|
31
|
+
};
|
|
32
|
+
export const stringifyChartData = (chartData) => JSON.stringify(chartData, null, 2);
|
|
33
|
+
/** The card both paths draw: a heading and a fixed-height canvas. `trailing` is where the
|
|
34
|
+
* document path puts its driver script; the fragment path leaves it empty. */
|
|
35
|
+
const chartCard = (title, canvas, trailing) => `
|
|
36
|
+
<div class="chart-container mb-6">
|
|
37
|
+
<h3 class="text-xl font-semibold mb-4">${escapeHtml(title || "Chart")}</h3>
|
|
38
|
+
<div class="w-full" style="position: relative; height: 400px;">
|
|
39
|
+
${canvas}
|
|
40
|
+
</div>${trailing}
|
|
41
|
+
</div>`;
|
|
42
|
+
/** For a standalone document, where the inline script runs. */
|
|
43
|
+
export const chartHtml = (chartDataJson, title, chartId) => {
|
|
44
|
+
assertSafeElementId(chartId);
|
|
45
|
+
const script = `
|
|
46
|
+
<script>
|
|
47
|
+
(function() {
|
|
48
|
+
const ctx = document.getElementById('${chartId}').getContext('2d');
|
|
49
|
+
new Chart(ctx, ${escapeJsonForScript(chartDataJson)});
|
|
50
|
+
})();
|
|
51
|
+
</script>`;
|
|
52
|
+
return chartCard(title, `<canvas id="${chartId}"></canvas>`, script);
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* For a host that injects the markup into its own page, where an injected `<script>`
|
|
56
|
+
* neither survives sanitizing nor executes via innerHTML. The config rides on the canvas
|
|
57
|
+
* instead, in the shape `@mulmocast/deck` already uses so one host runtime drives both.
|
|
58
|
+
*/
|
|
59
|
+
export const chartFragmentHtml = (chartData, title, chartId) => {
|
|
60
|
+
assertSafeElementId(chartId);
|
|
61
|
+
const config = escapeHtml(JSON.stringify(chartData));
|
|
62
|
+
return chartCard(title, `<canvas id="${chartId}" data-chart-ready="false" data-mulmo-chart="${config}"></canvas>`, "");
|
|
63
|
+
};
|
|
64
|
+
/** `chartData` is a free record, so the type may be absent or not a string. */
|
|
65
|
+
export const chartTypeOf = (chartData) => {
|
|
66
|
+
const type = chartData?.type;
|
|
67
|
+
return typeof type === "string" ? type : "";
|
|
68
|
+
};
|
|
69
|
+
/** The Chart.js plugin CDNs a chart type needs, for a host that loads them once per page. */
|
|
70
|
+
export const chartPluginCdns = (chartType) => {
|
|
71
|
+
const cdn = CHART_PLUGIN_CDNS.get(chartType);
|
|
72
|
+
return cdn ? [cdn] : [];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The two values the PNG/PDF/movie template interpolates from user data, escaped for the
|
|
76
|
+
* contexts it drops them into: `<h1>${title}</h1>` and `const chartData = ${chart_data};`.
|
|
77
|
+
*/
|
|
78
|
+
export const escapedChartTemplateValues = (title, chartData) => ({
|
|
79
|
+
title: escapeHtml(title),
|
|
80
|
+
chart_data: escapeJsonForScript(JSON.stringify(chartData)),
|
|
81
|
+
});
|
|
@@ -6,6 +6,7 @@ import { renderHTMLToImage, interpolate, renderHTMLToFrames, renderHTMLToVideo,
|
|
|
6
6
|
import { framesToVideo } from "../ffmpeg_utils.js";
|
|
7
7
|
import { parrotingImagePath } from "./utils.js";
|
|
8
8
|
import { swipeElementsToHtml, swipeElementsToScript } from "../swipe_to_html.js";
|
|
9
|
+
import { htmlTailwindMarkup } from "./html_tailwind_markup.js";
|
|
9
10
|
export const imageType = "html_tailwind";
|
|
10
11
|
/**
|
|
11
12
|
* Resolve image:name references to file:// absolute paths using imageRefs.
|
|
@@ -229,11 +230,7 @@ const dumpHtml = async (params) => {
|
|
|
229
230
|
const { beat } = params;
|
|
230
231
|
if (!beat.image || beat.image.type !== imageType)
|
|
231
232
|
return;
|
|
232
|
-
|
|
233
|
-
if (imageData.elements && Array.isArray(imageData.elements) && imageData.elements.length > 0) {
|
|
234
|
-
return swipeElementsToHtml(imageData.elements);
|
|
235
|
-
}
|
|
236
|
-
return joinHtml(imageData.html ?? "");
|
|
233
|
+
return htmlTailwindMarkup(beat.image);
|
|
237
234
|
};
|
|
238
235
|
export const process = processHtmlTailwind;
|
|
239
236
|
export const path = parrotingImagePath;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static markup an html_tailwind beat shows. Pure — no Node, no filesystem — because the
|
|
3
|
+
* Node dump path and the browser fragment path show the same thing.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately generates no `<script>`: the author's `script` FIELD, the swipe animation
|
|
6
|
+
* driver and the frame-based `animation` all need script execution, which the dump path has
|
|
7
|
+
* never emitted and which a fragment injected through `innerHTML` could not run anyway.
|
|
8
|
+
* Both paths show the beat's markup at rest.
|
|
9
|
+
*
|
|
10
|
+
* A `<script>` the author wrote inside `html` is another thing entirely: it is markup, and
|
|
11
|
+
* it passes through, because this beat type is raw author markup by design.
|
|
12
|
+
*
|
|
13
|
+
* The author's `html` is emitted as written. It is markup by design on this beat type — the
|
|
14
|
+
* schema also accepts a `script` — so escaping it would break the feature rather than
|
|
15
|
+
* protect anything the author does not already have.
|
|
16
|
+
*/
|
|
17
|
+
export type HtmlTailwindMarkupSource = {
|
|
18
|
+
html?: string | string[];
|
|
19
|
+
elements?: unknown;
|
|
20
|
+
};
|
|
21
|
+
export declare const htmlTailwindMarkup: (image: HtmlTailwindMarkupSource) => string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { isSwipeElements, swipeElementsToHtml } from "../swipe_to_html.js";
|
|
2
|
+
export const htmlTailwindMarkup = (image) => {
|
|
3
|
+
if (isSwipeElements(image.elements) && image.elements.length > 0) {
|
|
4
|
+
return swipeElementsToHtml(image.elements);
|
|
5
|
+
}
|
|
6
|
+
const html = image.html ?? "";
|
|
7
|
+
return Array.isArray(html) ? html.join("\n") : html;
|
|
8
|
+
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { getHTMLFile } from "../file.js";
|
|
2
2
|
import { renderHTMLToImage, interpolate } from "../html_render.js";
|
|
3
|
-
import { parrotingImagePath } from "./utils.js";
|
|
3
|
+
import { parrotingImagePath, generateUniqueId } from "./utils.js";
|
|
4
4
|
import { resolveCombinedStyle } from "./bg_image_util.js";
|
|
5
|
-
import {
|
|
5
|
+
import { renderMarkdownContent, renderMarkdownLayout, layoutToMarkdown, toMarkdownString } from "./markdown_layout.js";
|
|
6
6
|
import { resolveImageRefs, resolveMovieRefs } from "./html_tailwind.js";
|
|
7
7
|
import { isObject } from "graphai";
|
|
8
|
+
/**
|
|
9
|
+
* This path renders once to a PNG, so a random element id is fine. The browser path needs
|
|
10
|
+
* a stable one and supplies its own — that generator is the only thing the two callers of
|
|
11
|
+
* the shared markdown renderer differ on.
|
|
12
|
+
*/
|
|
13
|
+
const nodeMermaidId = () => generateUniqueId("mermaid");
|
|
8
14
|
export const imageType = "markdown";
|
|
9
15
|
// Type guard for object (data) format
|
|
10
16
|
const isMarkdownLayout = (md) => {
|
|
@@ -23,48 +29,54 @@ const dumpMarkdown = (params) => {
|
|
|
23
29
|
// object (data) format
|
|
24
30
|
return layoutToMarkdown(md);
|
|
25
31
|
};
|
|
26
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Full HTML for rendering, and whether it contains a diagram.
|
|
34
|
+
*
|
|
35
|
+
* The two come back together on purpose. There used to be a second detector —
|
|
36
|
+
* `containsMermaid`, a `text.includes("```mermaid")` over the raw source — deciding
|
|
37
|
+
* whether to load the mermaid runtime, while the renderer decided what to draw. Once the
|
|
38
|
+
* renderer started asking marked's lexer, the two disagreed: a ```MERMAID fence drew a
|
|
39
|
+
* diagram that no runtime was loaded for, so the PNG came out with an empty box, and an
|
|
40
|
+
* example quoted inside an outer ````markdown fence loaded the runtime for nothing.
|
|
41
|
+
*
|
|
42
|
+
* One question, one answer. The renderer already knows, so it says.
|
|
43
|
+
*/
|
|
27
44
|
const generateHtml = async (params) => {
|
|
28
45
|
const { beat } = params;
|
|
29
46
|
if (!beat.image || beat.image.type !== imageType)
|
|
30
|
-
return "";
|
|
47
|
+
return { html: "", hasMermaid: false };
|
|
31
48
|
const md = beat.image.markdown;
|
|
32
49
|
const combinedStyle = await resolveCombinedStyle(params, beat.image.backgroundImage, beat.image.style);
|
|
33
50
|
if (isMarkdownLayout(md)) {
|
|
34
|
-
const htmlBody =
|
|
51
|
+
const { html: htmlBody, hasMermaid } = renderMarkdownLayout(md, nodeMermaidId);
|
|
35
52
|
const template = getHTMLFile("tailwind");
|
|
36
|
-
return
|
|
37
|
-
title: "Markdown Layout",
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
53
|
+
return {
|
|
54
|
+
html: interpolate(template, { title: "Markdown Layout", html_body: htmlBody, custom_style: combinedStyle }),
|
|
55
|
+
hasMermaid,
|
|
56
|
+
};
|
|
41
57
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (containsMermaid(md)) {
|
|
58
|
+
const { html: body, hasMermaid } = renderMarkdownContent(dumpMarkdown(params) ?? "", nodeMermaidId);
|
|
59
|
+
// The tailwind template is what loads the mermaid CDN, so a diagram needs it.
|
|
60
|
+
if (hasMermaid) {
|
|
46
61
|
const template = getHTMLFile("tailwind");
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
return {
|
|
63
|
+
html: interpolate(template, {
|
|
64
|
+
title: "Markdown",
|
|
65
|
+
html_body: `<div class="prose max-w-none p-6">${body}</div>`,
|
|
66
|
+
custom_style: combinedStyle,
|
|
67
|
+
}),
|
|
68
|
+
hasMermaid,
|
|
69
|
+
};
|
|
52
70
|
}
|
|
53
|
-
return `<html><head><style>${combinedStyle}</style></head><body>${body}</body></html
|
|
54
|
-
};
|
|
55
|
-
// Check if markdown content contains mermaid code blocks
|
|
56
|
-
const containsMermaid = (md) => {
|
|
57
|
-
const text = isMarkdownLayout(md) ? layoutToMarkdown(md) : toMarkdownString(md);
|
|
58
|
-
return text.includes("```mermaid");
|
|
71
|
+
return { html: `<html><head><style>${combinedStyle}</style></head><body>${body}</body></html>`, hasMermaid };
|
|
59
72
|
};
|
|
60
73
|
const processMarkdown = async (params) => {
|
|
61
74
|
const { beat, imagePath, canvasSize } = params;
|
|
62
75
|
if (!beat.image || beat.image.type !== imageType)
|
|
63
76
|
return;
|
|
64
|
-
const rawHtml = await generateHtml(params);
|
|
77
|
+
const { html: rawHtml, hasMermaid } = await generateHtml(params);
|
|
65
78
|
const resolvedImages = resolveImageRefs(rawHtml, params.imageRefs ?? {});
|
|
66
79
|
const html = resolveMovieRefs(resolvedImages, params.movieRefs ?? {});
|
|
67
|
-
const hasMermaid = containsMermaid(beat.image.markdown);
|
|
68
80
|
await renderHTMLToImage(html, imagePath, canvasSize.width, canvasSize.height, hasMermaid);
|
|
69
81
|
return imagePath;
|
|
70
82
|
};
|
|
@@ -74,11 +86,10 @@ const dumpHtml = async (params) => {
|
|
|
74
86
|
return "";
|
|
75
87
|
const md = beat.image.markdown;
|
|
76
88
|
if (isMarkdownLayout(md)) {
|
|
77
|
-
return
|
|
89
|
+
return renderMarkdownLayout(md, nodeMermaidId).html;
|
|
78
90
|
}
|
|
79
91
|
else {
|
|
80
|
-
|
|
81
|
-
return await parseMarkdown(markdown ?? "");
|
|
92
|
+
return renderMarkdownContent(dumpMarkdown(params) ?? "", nodeMermaidId).html;
|
|
82
93
|
}
|
|
83
94
|
};
|
|
84
95
|
export const process = processMarkdown;
|
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
import { type MulmoMarkdownLayout } from "../../types/type.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The one markdown implementation, shared by the Node render path
|
|
4
|
+
* (`image_plugins/markdown.ts`, which turns it into a PNG) and the browser fragment path
|
|
5
|
+
* (`beat_html/markdown.ts`, which hands it to a host's DOM).
|
|
6
|
+
*
|
|
7
|
+
* Deliberately pure: no `node:*`, no filesystem, no puppeteer. That is what lets the
|
|
8
|
+
* browser side import it rather than keep a second copy — and a second copy is not
|
|
9
|
+
* hypothetical, it existed for one commit and the two immediately disagreed about which
|
|
10
|
+
* fences are diagrams. `test/beat_html/test_browser_safety.ts` bundles the browser entry
|
|
11
|
+
* with esbuild and fails if anything here reaches Node.
|
|
12
|
+
*
|
|
13
|
+
* Element ids come from a caller-supplied generator, because that is the only thing the
|
|
14
|
+
* two callers genuinely need to differ on.
|
|
15
|
+
*/
|
|
16
|
+
/** A markdown field is written either as one string or as lines to be joined. */
|
|
17
|
+
export declare const toMarkdownString: (content: string | string[]) => string;
|
|
18
|
+
/**
|
|
19
|
+
* Renders markdown, turning fenced `mermaid` blocks into mermaid markup.
|
|
20
|
+
*
|
|
21
|
+
* Asked of marked's own lexer rather than matched against the raw source. A regex over
|
|
22
|
+
* text was what both copies used, and review found four inputs it got wrong: a CRLF fence
|
|
23
|
+
* and a fence with trailing whitespace after the language were missed, while a mermaid
|
|
24
|
+
* fence quoted INSIDE an outer ````markdown fence, and one inside an indented code block,
|
|
25
|
+
* were converted — turning somebody's example of mermaid syntax into an actual diagram.
|
|
26
|
+
* marked decides what is a code block and what language it is, so all four follow from
|
|
27
|
+
* asking it.
|
|
28
|
+
*
|
|
29
|
+
* A fresh `Marked` instance per call rather than the global `marked.use`, because the
|
|
30
|
+
* renderer closes over this call's mermaid counter — a shared one would leak ids between
|
|
31
|
+
* beats.
|
|
32
|
+
*/
|
|
33
|
+
export declare const renderMarkdownContent: (content: string | string[], nextId: () => string) => {
|
|
34
|
+
html: string;
|
|
35
|
+
hasMermaid: boolean;
|
|
36
|
+
};
|
|
37
|
+
/** The object form of `markdown`, as opposed to a plain string or array of lines. */
|
|
38
|
+
export declare const isMarkdownLayout: (md: unknown) => md is MulmoMarkdownLayout;
|
|
39
|
+
/** Lay a markdown layout out in Tailwind: header / sidebar-left / row-2 | 2x2 | content. */
|
|
40
|
+
export declare const renderMarkdownLayout: (md: MulmoMarkdownLayout, nextId: () => string) => {
|
|
41
|
+
html: string;
|
|
42
|
+
hasMermaid: boolean;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* A layout flattened back to plain markdown, in slot order. Used by `mulmo markdown`,
|
|
46
|
+
* which wants what the beat SAYS rather than how it is laid out.
|
|
47
|
+
*/
|
|
5
48
|
export declare const layoutToMarkdown: (md: MulmoMarkdownLayout) => string;
|
|
6
|
-
export { toMarkdownString, parseMarkdown };
|