@json-to-office/core-docx 0.15.0 → 0.17.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/dist/components/highcharts.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/visual.d.ts +42 -0
- package/dist/components/visual.d.ts.map +1 -0
- package/dist/core/cached-render.d.ts.map +1 -1
- package/dist/core/flattenVisuals.d.ts +33 -0
- package/dist/core/flattenVisuals.d.ts.map +1 -0
- package/dist/core/generator.d.ts +12 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/render.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +359 -57
- package/dist/index.js.map +1 -1
- package/dist/plugin/createDocumentGenerator.d.ts +7 -1
- package/dist/plugin/createDocumentGenerator.d.ts.map +1 -1
- package/dist/plugin/example/index.js +220 -56
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/plugin/types.d.ts +15 -0
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugin/validation.d.ts +14 -3
- package/dist/plugin/validation.d.ts.map +1 -1
- package/dist/styles/utils/componentDefaults.d.ts +6 -0
- package/dist/styles/utils/componentDefaults.d.ts.map +1 -1
- package/dist/styles/utils/resolveComponentTree.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +5 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/serviceClient.d.ts +39 -0
- package/dist/utils/serviceClient.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1954,7 +1954,8 @@ import {
|
|
|
1954
1954
|
isTableComponent,
|
|
1955
1955
|
isListComponent,
|
|
1956
1956
|
isTocComponent,
|
|
1957
|
-
isHighchartsComponent
|
|
1957
|
+
isHighchartsComponent,
|
|
1958
|
+
isVisualComponent
|
|
1958
1959
|
} from "@json-to-office/shared-docx";
|
|
1959
1960
|
|
|
1960
1961
|
// src/core/generator.ts
|
|
@@ -2050,6 +2051,9 @@ function resolveListProps(props, theme) {
|
|
|
2050
2051
|
function resolveHighchartsProps(props, _theme) {
|
|
2051
2052
|
return props;
|
|
2052
2053
|
}
|
|
2054
|
+
function resolveVisualProps(props, _theme) {
|
|
2055
|
+
return props;
|
|
2056
|
+
}
|
|
2053
2057
|
function getCustomComponentDefaults(theme, componentName) {
|
|
2054
2058
|
const defaults = getComponentDefaults(theme);
|
|
2055
2059
|
return defaults?.[componentName] || {};
|
|
@@ -2079,7 +2083,8 @@ var RESOLVER_MAP = {
|
|
|
2079
2083
|
section: resolveSectionProps,
|
|
2080
2084
|
columns: resolveColumnsProps,
|
|
2081
2085
|
list: resolveListProps,
|
|
2082
|
-
highcharts: resolveHighchartsProps
|
|
2086
|
+
highcharts: resolveHighchartsProps,
|
|
2087
|
+
visual: resolveVisualProps
|
|
2083
2088
|
};
|
|
2084
2089
|
function resolveComponentDefaults(component, theme) {
|
|
2085
2090
|
if (!component.props) return component;
|
|
@@ -4068,7 +4073,7 @@ async function clearComponentCache() {
|
|
|
4068
4073
|
}
|
|
4069
4074
|
}
|
|
4070
4075
|
async function renderComponentWithCache(component, theme, themeName, context, bypassCache = false) {
|
|
4071
|
-
const forceBypassForType = component.name === "toc" || component.name === "section" || componentHasRevision(component);
|
|
4076
|
+
const forceBypassForType = component.name === "toc" || component.name === "section" || component.name === "visual" || componentHasRevision(component);
|
|
4072
4077
|
if (!componentCache) {
|
|
4073
4078
|
initializeComponentCache();
|
|
4074
4079
|
}
|
|
@@ -6350,21 +6355,61 @@ function hasNodeBuiltins() {
|
|
|
6350
6355
|
}
|
|
6351
6356
|
}
|
|
6352
6357
|
|
|
6358
|
+
// src/utils/serviceClient.ts
|
|
6359
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
6360
|
+
function resolveServiceUrl(propsUrl, servicesUrl, defaultUrl) {
|
|
6361
|
+
const raw = (propsUrl || servicesUrl || defaultUrl).trim();
|
|
6362
|
+
const withScheme = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
6363
|
+
return withScheme.replace(/\/+$/, "");
|
|
6364
|
+
}
|
|
6365
|
+
async function postJsonToService(opts) {
|
|
6366
|
+
const resolvedHeaders = typeof opts.headers === "function" ? await opts.headers(opts.body) : opts.headers;
|
|
6367
|
+
const headers = {
|
|
6368
|
+
"Content-Type": "application/json",
|
|
6369
|
+
...resolvedHeaders
|
|
6370
|
+
};
|
|
6371
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
6372
|
+
const controller = new AbortController();
|
|
6373
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6374
|
+
let response;
|
|
6375
|
+
try {
|
|
6376
|
+
response = await fetch(`${opts.url}${opts.path}`, {
|
|
6377
|
+
method: "POST",
|
|
6378
|
+
headers,
|
|
6379
|
+
body: JSON.stringify(opts.body),
|
|
6380
|
+
signal: controller.signal
|
|
6381
|
+
});
|
|
6382
|
+
} catch (error) {
|
|
6383
|
+
if (error?.name === "AbortError") {
|
|
6384
|
+
throw new Error(
|
|
6385
|
+
`${opts.serviceLabel} timed out after ${timeoutMs}ms at ${opts.url}.`
|
|
6386
|
+
);
|
|
6387
|
+
}
|
|
6388
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
6389
|
+
throw new Error(opts.onUnreachable(opts.url, cause));
|
|
6390
|
+
} finally {
|
|
6391
|
+
clearTimeout(timer);
|
|
6392
|
+
}
|
|
6393
|
+
if (!response.ok) {
|
|
6394
|
+
throw new Error(
|
|
6395
|
+
`${opts.serviceLabel} returned ${response.status}: ${response.statusText}`
|
|
6396
|
+
);
|
|
6397
|
+
}
|
|
6398
|
+
return response;
|
|
6399
|
+
}
|
|
6400
|
+
|
|
6353
6401
|
// src/components/highcharts.ts
|
|
6354
6402
|
var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
|
|
6355
|
-
function getExportServerUrl(propsUrl, servicesUrl) {
|
|
6356
|
-
const raw = propsUrl || servicesUrl || DEFAULT_EXPORT_SERVER_URL;
|
|
6357
|
-
return raw.startsWith("http") ? raw : `http://${raw}`;
|
|
6358
|
-
}
|
|
6359
6403
|
async function generateChart(config, servicesConfig) {
|
|
6360
6404
|
if (!isNodeEnvironment()) {
|
|
6361
6405
|
throw new Error(
|
|
6362
6406
|
"Highcharts export server requires a Node.js environment. Chart generation is not available in browser environments."
|
|
6363
6407
|
);
|
|
6364
6408
|
}
|
|
6365
|
-
const serverUrl =
|
|
6409
|
+
const serverUrl = resolveServiceUrl(
|
|
6366
6410
|
config.serverUrl,
|
|
6367
|
-
servicesConfig?.serverUrl
|
|
6411
|
+
servicesConfig?.serverUrl,
|
|
6412
|
+
DEFAULT_EXPORT_SERVER_URL
|
|
6368
6413
|
);
|
|
6369
6414
|
const requestBody = {
|
|
6370
6415
|
infile: config.options,
|
|
@@ -6372,26 +6417,15 @@ async function generateChart(config, servicesConfig) {
|
|
|
6372
6417
|
b64: true,
|
|
6373
6418
|
scale: config.scale
|
|
6374
6419
|
};
|
|
6375
|
-
const
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
body: JSON.stringify(requestBody)
|
|
6384
|
-
}).catch((error) => {
|
|
6385
|
-
throw new Error(
|
|
6386
|
-
`Highcharts Export Server is not running at ${serverUrl}. Start it with: npx highcharts-export-server --enableServer true
|
|
6387
|
-
Cause: ${error instanceof Error ? error.message : String(error)}`
|
|
6388
|
-
);
|
|
6420
|
+
const response = await postJsonToService({
|
|
6421
|
+
url: serverUrl,
|
|
6422
|
+
path: "/export",
|
|
6423
|
+
body: requestBody,
|
|
6424
|
+
headers: servicesConfig?.headers,
|
|
6425
|
+
serviceLabel: "Highcharts export server",
|
|
6426
|
+
onUnreachable: (url, cause) => `Highcharts Export Server is not running at ${url}. Start it with: npx highcharts-export-server --enableServer true
|
|
6427
|
+
Cause: ${cause}`
|
|
6389
6428
|
});
|
|
6390
|
-
if (!response.ok) {
|
|
6391
|
-
throw new Error(
|
|
6392
|
-
`Highcharts export server returned ${response.status}: ${response.statusText}`
|
|
6393
|
-
);
|
|
6394
|
-
}
|
|
6395
6429
|
const base64Data = await response.text();
|
|
6396
6430
|
const base64DataUri = `data:image/png;base64,${base64Data}`;
|
|
6397
6431
|
const width = config.options.chart.width;
|
|
@@ -6425,6 +6459,116 @@ async function renderHighchartsComponent(component, theme, themeName, context) {
|
|
|
6425
6459
|
return imageParagraphs;
|
|
6426
6460
|
}
|
|
6427
6461
|
|
|
6462
|
+
// src/components/visual.ts
|
|
6463
|
+
import {
|
|
6464
|
+
clampVisualDpi,
|
|
6465
|
+
DEFAULT_VISUAL_DPI
|
|
6466
|
+
} from "@json-to-office/shared";
|
|
6467
|
+
var DEFAULT_RASTERIZE_SERVER_URL = "http://localhost:7802";
|
|
6468
|
+
var PIXELS_PER_INCH = 96;
|
|
6469
|
+
function buildVisualPresentation(props) {
|
|
6470
|
+
const { canvas, elements } = props;
|
|
6471
|
+
const presentationProps = {
|
|
6472
|
+
slideWidth: canvas.width,
|
|
6473
|
+
slideHeight: canvas.height
|
|
6474
|
+
};
|
|
6475
|
+
if (canvas.theme) presentationProps.theme = canvas.theme;
|
|
6476
|
+
const slideProps = {};
|
|
6477
|
+
if (canvas.background) slideProps.background = canvas.background;
|
|
6478
|
+
return {
|
|
6479
|
+
name: "pptx",
|
|
6480
|
+
props: presentationProps,
|
|
6481
|
+
children: [
|
|
6482
|
+
{
|
|
6483
|
+
name: "slide",
|
|
6484
|
+
props: slideProps,
|
|
6485
|
+
children: elements ?? []
|
|
6486
|
+
}
|
|
6487
|
+
]
|
|
6488
|
+
};
|
|
6489
|
+
}
|
|
6490
|
+
function defaultVisualWidthPx(props) {
|
|
6491
|
+
return Math.round(props.canvas.width * PIXELS_PER_INCH);
|
|
6492
|
+
}
|
|
6493
|
+
function visualToImageOptions(props) {
|
|
6494
|
+
return {
|
|
6495
|
+
width: props.width ?? defaultVisualWidthPx(props),
|
|
6496
|
+
...props.height !== void 0 && { height: props.height },
|
|
6497
|
+
alignment: props.alignment ?? "center",
|
|
6498
|
+
...props.caption !== void 0 && { caption: props.caption },
|
|
6499
|
+
...props.spacing !== void 0 && { spacing: props.spacing },
|
|
6500
|
+
...props.floating !== void 0 && { floating: props.floating },
|
|
6501
|
+
...props.keepNext !== void 0 && { keepNext: props.keepNext },
|
|
6502
|
+
...props.keepLines !== void 0 && { keepLines: props.keepLines }
|
|
6503
|
+
};
|
|
6504
|
+
}
|
|
6505
|
+
function visualToImageProps(props, base64DataUri) {
|
|
6506
|
+
return {
|
|
6507
|
+
base64: base64DataUri,
|
|
6508
|
+
...visualToImageOptions(props),
|
|
6509
|
+
...props.alt !== void 0 && { alt: props.alt }
|
|
6510
|
+
};
|
|
6511
|
+
}
|
|
6512
|
+
async function rasterize(presentation, dpi, propsServerUrl, serviceConfig) {
|
|
6513
|
+
if (!isNodeEnvironment()) {
|
|
6514
|
+
throw new Error(
|
|
6515
|
+
"Visual rasterization requires a Node.js environment. It is not available in browser environments."
|
|
6516
|
+
);
|
|
6517
|
+
}
|
|
6518
|
+
if (serviceConfig?.render) {
|
|
6519
|
+
return serviceConfig.render({ presentation, dpi });
|
|
6520
|
+
}
|
|
6521
|
+
const serverUrl = resolveServiceUrl(
|
|
6522
|
+
propsServerUrl,
|
|
6523
|
+
serviceConfig?.serverUrl,
|
|
6524
|
+
DEFAULT_RASTERIZE_SERVER_URL
|
|
6525
|
+
);
|
|
6526
|
+
const response = await postJsonToService({
|
|
6527
|
+
url: serverUrl,
|
|
6528
|
+
path: "/rasterize",
|
|
6529
|
+
body: { presentation, dpi },
|
|
6530
|
+
headers: serviceConfig?.headers,
|
|
6531
|
+
serviceLabel: "PPTX rasterization service",
|
|
6532
|
+
onUnreachable: (url, cause) => `PPTX rasterization service is not reachable at ${url}. Configure services.pptx with a \`render\` callback or a running \`serverUrl\`.
|
|
6533
|
+
Cause: ${cause}`
|
|
6534
|
+
});
|
|
6535
|
+
let result;
|
|
6536
|
+
try {
|
|
6537
|
+
result = await response.json();
|
|
6538
|
+
} catch {
|
|
6539
|
+
throw new Error(
|
|
6540
|
+
"PPTX rasterization service returned a non-JSON response (expected { base64DataUri, width, height })."
|
|
6541
|
+
);
|
|
6542
|
+
}
|
|
6543
|
+
if (!result?.base64DataUri) {
|
|
6544
|
+
throw new Error(
|
|
6545
|
+
"PPTX rasterization service returned a malformed response (missing base64DataUri)."
|
|
6546
|
+
);
|
|
6547
|
+
}
|
|
6548
|
+
return result;
|
|
6549
|
+
}
|
|
6550
|
+
async function renderVisualComponent(component, theme, themeName, context) {
|
|
6551
|
+
if (!isVisualComponent(component)) return [];
|
|
6552
|
+
const props = component.props;
|
|
6553
|
+
const serviceConfig = context?.services?.pptx;
|
|
6554
|
+
const presentation = buildVisualPresentation(props);
|
|
6555
|
+
const dpi = clampVisualDpi(
|
|
6556
|
+
props.dpi ?? serviceConfig?.dpi ?? DEFAULT_VISUAL_DPI
|
|
6557
|
+
);
|
|
6558
|
+
const result = await rasterize(
|
|
6559
|
+
presentation,
|
|
6560
|
+
dpi,
|
|
6561
|
+
props.serverUrl,
|
|
6562
|
+
serviceConfig
|
|
6563
|
+
);
|
|
6564
|
+
return await createImage(
|
|
6565
|
+
result.base64DataUri,
|
|
6566
|
+
theme,
|
|
6567
|
+
themeName,
|
|
6568
|
+
visualToImageOptions(props)
|
|
6569
|
+
);
|
|
6570
|
+
}
|
|
6571
|
+
|
|
6428
6572
|
// src/components/text-space-after.ts
|
|
6429
6573
|
import { Type } from "@sinclair/typebox";
|
|
6430
6574
|
|
|
@@ -6581,7 +6725,7 @@ async function renderDocument(structure, layout, options) {
|
|
|
6581
6725
|
}
|
|
6582
6726
|
});
|
|
6583
6727
|
}
|
|
6584
|
-
async function renderHeaderFooterComponents(components, theme, themeName,
|
|
6728
|
+
async function renderHeaderFooterComponents(components, theme, themeName, context) {
|
|
6585
6729
|
if (!components || components.length === 0) {
|
|
6586
6730
|
return [];
|
|
6587
6731
|
}
|
|
@@ -6713,6 +6857,14 @@ async function renderHeaderFooterComponents(components, theme, themeName, _conte
|
|
|
6713
6857
|
} else if (isTableComponent(component)) {
|
|
6714
6858
|
const tables = await renderTableComponent(component, theme, themeName);
|
|
6715
6859
|
elements.push(...tables);
|
|
6860
|
+
} else if (isVisualComponent(component)) {
|
|
6861
|
+
const visualEls = await renderVisualComponent(
|
|
6862
|
+
component,
|
|
6863
|
+
theme,
|
|
6864
|
+
themeName,
|
|
6865
|
+
context
|
|
6866
|
+
);
|
|
6867
|
+
elements.push(...visualEls);
|
|
6716
6868
|
}
|
|
6717
6869
|
}
|
|
6718
6870
|
return elements;
|
|
@@ -6833,6 +6985,8 @@ async function renderComponent(component, theme, themeName, context) {
|
|
|
6833
6985
|
themeName,
|
|
6834
6986
|
context
|
|
6835
6987
|
);
|
|
6988
|
+
} else if (isVisualComponent(component)) {
|
|
6989
|
+
return await renderVisualComponent(component, theme, themeName, context);
|
|
6836
6990
|
} else if (isSectionComponent(component)) {
|
|
6837
6991
|
return await renderSectionComponent(component, theme, themeName, context);
|
|
6838
6992
|
}
|
|
@@ -7133,9 +7287,32 @@ async function generateDocumentWithCustomThemes(documentIn, customThemes, servic
|
|
|
7133
7287
|
return renderedDocument;
|
|
7134
7288
|
}
|
|
7135
7289
|
async function generateDocumentFromJson(jsonConfig, options) {
|
|
7290
|
+
const validation = options?.validation;
|
|
7291
|
+
if (validation?.enabled !== false) {
|
|
7292
|
+
const result = validateJsonComponent(jsonConfig, {
|
|
7293
|
+
allowUnknownFields: validation?.allowUnknownFields
|
|
7294
|
+
});
|
|
7295
|
+
if (!result.valid) {
|
|
7296
|
+
throw new JsonValidationError(
|
|
7297
|
+
"Document validation failed",
|
|
7298
|
+
result.errors
|
|
7299
|
+
);
|
|
7300
|
+
}
|
|
7301
|
+
}
|
|
7136
7302
|
let componentToConvert;
|
|
7137
7303
|
if (typeof jsonConfig === "string") {
|
|
7138
|
-
|
|
7304
|
+
let parsed;
|
|
7305
|
+
try {
|
|
7306
|
+
parsed = JSON.parse(jsonConfig);
|
|
7307
|
+
} catch (error) {
|
|
7308
|
+
throw new JsonParsingError("Invalid JSON syntax", [
|
|
7309
|
+
{
|
|
7310
|
+
path: "",
|
|
7311
|
+
message: error instanceof Error ? error.message : "Invalid JSON",
|
|
7312
|
+
code: "JSON_PARSE_ERROR"
|
|
7313
|
+
}
|
|
7314
|
+
]);
|
|
7315
|
+
}
|
|
7139
7316
|
if (!isReportComponent(parsed)) {
|
|
7140
7317
|
throw new Error("Parsed JSON must be a docx component");
|
|
7141
7318
|
}
|
|
@@ -7204,6 +7381,112 @@ var DocumentGenerator = {
|
|
|
7204
7381
|
isReportComponentDefinition
|
|
7205
7382
|
};
|
|
7206
7383
|
|
|
7384
|
+
// src/core/flattenVisuals.ts
|
|
7385
|
+
import {
|
|
7386
|
+
clampVisualDpi as clampVisualDpi2,
|
|
7387
|
+
DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI2
|
|
7388
|
+
} from "@json-to-office/shared";
|
|
7389
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
7390
|
+
function createLimiter(max) {
|
|
7391
|
+
let active = 0;
|
|
7392
|
+
const queue = [];
|
|
7393
|
+
const release = () => {
|
|
7394
|
+
active--;
|
|
7395
|
+
queue.shift()?.();
|
|
7396
|
+
};
|
|
7397
|
+
return async function limit(fn) {
|
|
7398
|
+
if (active >= max)
|
|
7399
|
+
await new Promise((resolve2) => queue.push(resolve2));
|
|
7400
|
+
active++;
|
|
7401
|
+
try {
|
|
7402
|
+
return await fn();
|
|
7403
|
+
} finally {
|
|
7404
|
+
release();
|
|
7405
|
+
}
|
|
7406
|
+
};
|
|
7407
|
+
}
|
|
7408
|
+
async function flattenVisuals(doc, options) {
|
|
7409
|
+
const ctx = {
|
|
7410
|
+
rasterize: options.rasterize,
|
|
7411
|
+
dpi: options.dpi,
|
|
7412
|
+
limit: createLimiter(
|
|
7413
|
+
Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY)
|
|
7414
|
+
)
|
|
7415
|
+
};
|
|
7416
|
+
return await flattenNode(doc, ctx);
|
|
7417
|
+
}
|
|
7418
|
+
async function rasterizeVisual(obj, ctx) {
|
|
7419
|
+
const props = obj.props;
|
|
7420
|
+
const dpi = clampVisualDpi2(props.dpi ?? ctx.dpi ?? DEFAULT_VISUAL_DPI2);
|
|
7421
|
+
const result = await ctx.limit(
|
|
7422
|
+
() => ctx.rasterize({ presentation: buildVisualPresentation(props), dpi })
|
|
7423
|
+
);
|
|
7424
|
+
const image = {
|
|
7425
|
+
name: "image",
|
|
7426
|
+
props: visualToImageProps(props, result.base64DataUri)
|
|
7427
|
+
};
|
|
7428
|
+
if (obj.id !== void 0) image.id = obj.id;
|
|
7429
|
+
if (obj.enabled !== void 0) image.enabled = obj.enabled;
|
|
7430
|
+
return image;
|
|
7431
|
+
}
|
|
7432
|
+
async function flattenNode(node, ctx) {
|
|
7433
|
+
if (Array.isArray(node)) {
|
|
7434
|
+
return Promise.all(node.map((n) => flattenNode(n, ctx)));
|
|
7435
|
+
}
|
|
7436
|
+
if (!node || typeof node !== "object") return node;
|
|
7437
|
+
const obj = node;
|
|
7438
|
+
if (obj.name === "visual" && obj.props && obj.enabled !== false) {
|
|
7439
|
+
return rasterizeVisual(obj, ctx);
|
|
7440
|
+
}
|
|
7441
|
+
const next = { ...obj };
|
|
7442
|
+
if (Array.isArray(obj.children)) {
|
|
7443
|
+
next.children = await flattenNode(obj.children, ctx);
|
|
7444
|
+
}
|
|
7445
|
+
if (obj.props && typeof obj.props === "object") {
|
|
7446
|
+
const props = obj.props;
|
|
7447
|
+
let propsChanged = false;
|
|
7448
|
+
const nextProps = { ...props };
|
|
7449
|
+
for (const key of ["header", "footer"]) {
|
|
7450
|
+
if (Array.isArray(props[key])) {
|
|
7451
|
+
nextProps[key] = await flattenNode(props[key], ctx);
|
|
7452
|
+
propsChanged = true;
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
if (Array.isArray(props.columns)) {
|
|
7456
|
+
nextProps.columns = await Promise.all(
|
|
7457
|
+
props.columns.map((col) => flattenColumn(col, ctx))
|
|
7458
|
+
);
|
|
7459
|
+
propsChanged = true;
|
|
7460
|
+
}
|
|
7461
|
+
if (propsChanged) next.props = nextProps;
|
|
7462
|
+
}
|
|
7463
|
+
return next;
|
|
7464
|
+
}
|
|
7465
|
+
async function flattenColumn(col, ctx) {
|
|
7466
|
+
if (!col || typeof col !== "object") return col;
|
|
7467
|
+
const column = col;
|
|
7468
|
+
const nextCol = { ...column };
|
|
7469
|
+
const header = column.header;
|
|
7470
|
+
if (header && typeof header === "object" && "content" in header) {
|
|
7471
|
+
nextCol.header = {
|
|
7472
|
+
...header,
|
|
7473
|
+
content: await flattenNode(header.content, ctx)
|
|
7474
|
+
};
|
|
7475
|
+
}
|
|
7476
|
+
if (Array.isArray(column.cells)) {
|
|
7477
|
+
nextCol.cells = await Promise.all(
|
|
7478
|
+
column.cells.map(async (cell) => {
|
|
7479
|
+
if (cell && typeof cell === "object" && "content" in cell) {
|
|
7480
|
+
const c = cell;
|
|
7481
|
+
return { ...c, content: await flattenNode(c.content, ctx) };
|
|
7482
|
+
}
|
|
7483
|
+
return cell;
|
|
7484
|
+
})
|
|
7485
|
+
);
|
|
7486
|
+
}
|
|
7487
|
+
return nextCol;
|
|
7488
|
+
}
|
|
7489
|
+
|
|
7207
7490
|
// src/utils/warningsDocument.ts
|
|
7208
7491
|
function generateWarningsDocument(warnings) {
|
|
7209
7492
|
if (!warnings || warnings.length === 0) {
|
|
@@ -7430,22 +7713,20 @@ import {
|
|
|
7430
7713
|
ComponentValidationError as ComponentValidationError2,
|
|
7431
7714
|
UnknownPreservedComponentError
|
|
7432
7715
|
} from "@json-to-office/shared/plugin";
|
|
7433
|
-
function validateComponentProps(schema, props, componentName) {
|
|
7716
|
+
function validateComponentProps(schema, props, componentName, opts) {
|
|
7434
7717
|
return validateCustomComponentProps(schema.propsSchema, props, {
|
|
7435
|
-
clean: true,
|
|
7436
|
-
applyDefaults: true,
|
|
7718
|
+
clean: opts?.clean ?? true,
|
|
7719
|
+
applyDefaults: opts?.applyDefaults ?? true,
|
|
7437
7720
|
componentName
|
|
7438
7721
|
});
|
|
7439
7722
|
}
|
|
7440
|
-
function validateDocument(document, customComponents) {
|
|
7723
|
+
function validateDocument(document, customComponents, options) {
|
|
7724
|
+
const knownCustomNames = new Set(customComponents.map((c) => c.name));
|
|
7441
7725
|
const documentResult = validateDocumentUnified(document, {
|
|
7442
|
-
|
|
7443
|
-
|
|
7726
|
+
knownCustomNames,
|
|
7727
|
+
allowUnknownFields: options?.allowUnknownFields
|
|
7444
7728
|
});
|
|
7445
|
-
|
|
7446
|
-
return { ...documentResult, success: false };
|
|
7447
|
-
}
|
|
7448
|
-
const errors = [];
|
|
7729
|
+
const errors = [...documentResult.errors || []];
|
|
7449
7730
|
function validateComponents(components, pathPrefix = "children") {
|
|
7450
7731
|
components.forEach((componentData, index) => {
|
|
7451
7732
|
const customComponent = customComponents.find(
|
|
@@ -7462,7 +7743,9 @@ function validateDocument(document, customComponents) {
|
|
|
7462
7743
|
const validation = validateComponentProps(
|
|
7463
7744
|
versionEntry,
|
|
7464
7745
|
componentData.props,
|
|
7465
|
-
customComponent.name
|
|
7746
|
+
customComponent.name,
|
|
7747
|
+
// Reject unknown custom props unless the caller allows them.
|
|
7748
|
+
{ clean: options?.allowUnknownFields === true }
|
|
7466
7749
|
);
|
|
7467
7750
|
if (!validation.valid && validation.errors) {
|
|
7468
7751
|
const indexedErrors = validation.errors.map(
|
|
@@ -7827,10 +8110,26 @@ function createBuilderImpl(state) {
|
|
|
7827
8110
|
try {
|
|
7828
8111
|
const preserveSet = resolvePreserveSet(options);
|
|
7829
8112
|
const internalDocument = document;
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
8113
|
+
const vOpts = {
|
|
8114
|
+
...state.validation,
|
|
8115
|
+
...options?.validation
|
|
8116
|
+
};
|
|
8117
|
+
if (vOpts.enabled !== false) {
|
|
8118
|
+
const result = validateDocument(
|
|
8119
|
+
internalDocument,
|
|
8120
|
+
state.components,
|
|
8121
|
+
{ allowUnknownFields: vOpts.allowUnknownFields }
|
|
8122
|
+
);
|
|
8123
|
+
if (!result.valid) {
|
|
8124
|
+
throw new ComponentValidationError2(
|
|
8125
|
+
(result.errors ?? []).map((e) => ({
|
|
8126
|
+
path: e.path ?? "",
|
|
8127
|
+
message: e.message
|
|
8128
|
+
})),
|
|
8129
|
+
internalDocument
|
|
8130
|
+
);
|
|
8131
|
+
}
|
|
8132
|
+
}
|
|
7834
8133
|
const baseThemeName = internalDocument.props.theme || "minimal";
|
|
7835
8134
|
const docTheme = resolveDocumentTheme(baseThemeName);
|
|
7836
8135
|
const warnings = [];
|
|
@@ -7924,21 +8223,21 @@ function createBuilderImpl(state) {
|
|
|
7924
8223
|
function validate(document) {
|
|
7925
8224
|
try {
|
|
7926
8225
|
const internalDocument = document;
|
|
7927
|
-
validateDocument(
|
|
8226
|
+
const result = validateDocument(
|
|
7928
8227
|
internalDocument,
|
|
7929
8228
|
state.components
|
|
7930
8229
|
);
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
if (error instanceof ComponentValidationError2) {
|
|
7934
|
-
return {
|
|
7935
|
-
valid: false,
|
|
7936
|
-
errors: error.errors.map((e) => ({
|
|
7937
|
-
path: e.path,
|
|
7938
|
-
message: e.message
|
|
7939
|
-
}))
|
|
7940
|
-
};
|
|
8230
|
+
if (result.valid) {
|
|
8231
|
+
return { valid: true };
|
|
7941
8232
|
}
|
|
8233
|
+
return {
|
|
8234
|
+
valid: false,
|
|
8235
|
+
errors: (result.errors ?? []).map((e) => ({
|
|
8236
|
+
path: e.path ?? "",
|
|
8237
|
+
message: e.message
|
|
8238
|
+
}))
|
|
8239
|
+
};
|
|
8240
|
+
} catch (error) {
|
|
7942
8241
|
return {
|
|
7943
8242
|
valid: false,
|
|
7944
8243
|
errors: [
|
|
@@ -7988,7 +8287,8 @@ function createDocumentGenerator(options) {
|
|
|
7988
8287
|
debug: options.debug ?? false,
|
|
7989
8288
|
enableCache: options.enableCache ?? false,
|
|
7990
8289
|
services: options.services,
|
|
7991
|
-
fonts: options.fonts
|
|
8290
|
+
fonts: options.fonts,
|
|
8291
|
+
validation: options.validation
|
|
7992
8292
|
};
|
|
7993
8293
|
return createBuilderImpl(initialState);
|
|
7994
8294
|
}
|
|
@@ -8023,6 +8323,7 @@ export {
|
|
|
8023
8323
|
examples,
|
|
8024
8324
|
exportPluginSchema,
|
|
8025
8325
|
exportThemeToJson,
|
|
8326
|
+
flattenVisuals,
|
|
8026
8327
|
formatDate,
|
|
8027
8328
|
formatWarningsText,
|
|
8028
8329
|
generateAndSave,
|
|
@@ -8057,6 +8358,7 @@ export {
|
|
|
8057
8358
|
isSectionComponent,
|
|
8058
8359
|
isStatisticComponent,
|
|
8059
8360
|
isTableComponent,
|
|
8361
|
+
isVisualComponent,
|
|
8060
8362
|
loadJsonExample,
|
|
8061
8363
|
loadThemeFromFile,
|
|
8062
8364
|
loadThemeFromJson,
|