@openpresentation/opf 0.26.4 → 0.26.5
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/README.md +2 -0
- package/dist/browser.js +13 -12
- package/dist/{chunk-XFOOKVRI.js → chunk-CIWC7DRZ.js} +1977 -373
- package/dist/{chunk-O6BY3AU2.js → chunk-DXTPLVAK.js} +1 -1
- package/dist/{chunk-QH2W6HIM.js → chunk-EFMR32A5.js} +1 -1
- package/dist/{chunk-6NBTZYYD.js → chunk-HPEZCAYC.js} +1 -1
- package/dist/{chunk-ZSZDVIDV.js → chunk-K5Q3LLYK.js} +2 -2
- package/dist/chunk-KPALXWX3.js +712 -0
- package/dist/{chunk-6IXSHIAL.js → chunk-L7YEVQHP.js} +6 -6
- package/dist/chunk-MD3CJ5YJ.js +452 -0
- package/dist/{chunk-MN7DRK57.js → chunk-MLH5LIGF.js} +11 -21
- package/dist/{chunk-EDCRBTJF.js → chunk-STIYINEL.js} +3 -3
- package/dist/{chunk-H5QC3X3K.js → chunk-TAV5S2JG.js} +1 -1
- package/dist/{chunk-NZLW6FBQ.js → chunk-UFEWQDQI.js} +1 -1
- package/dist/{chunk-GWJBZ4LC.js → chunk-UY6SQVR2.js} +5 -5
- package/dist/{chunk-PIO73BQ6.js → chunk-WAZDP2WS.js} +11 -20
- package/dist/{conversion-DSafOGzh.d.ts → conversion-C7o2EpDb.d.ts} +2 -2
- package/dist/conversion-PMJWLHDV.js +21 -0
- package/dist/convert.js +5 -5
- package/dist/deck.js +8 -8
- package/dist/diff.js +2 -2
- package/dist/docs.js +3 -3
- package/dist/explain-JSuESV-Y.d.ts +71 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +17 -16
- package/dist/markdown.js +6 -6
- package/dist/node-engine.d.ts +2 -2
- package/dist/node-engine.js +17 -16
- package/dist/pagination.js +2 -2
- package/dist/patch.js +1 -1
- package/dist/schema.js +1 -1
- package/dist/source.d.ts +83 -2
- package/dist/source.js +10 -9
- package/dist/validator.js +1 -1
- package/dist/yaml.js +4 -4
- package/package.json +5 -5
- package/dist/chunk-QAEBYDKY.js +0 -192
- package/dist/conversion-QIAJM6DQ.js +0 -20
|
@@ -22,8 +22,8 @@ var catalogSchemaNames = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// src/annotation-validation.ts
|
|
25
|
-
var issue = (path, message,
|
|
26
|
-
var pointer = (
|
|
25
|
+
var issue = (path, message, params2) => ({ path, message, keyword: "opf", schemaPath: "#/x-opf-semantics", params: params2 });
|
|
26
|
+
var pointer = (dotted2) => `/${dotted2.split(".").map((part) => part.replaceAll("~", "~0").replaceAll("/", "~1")).join("/")}`;
|
|
27
27
|
function markedRuns(value) {
|
|
28
28
|
if (!Array.isArray(value)) return [];
|
|
29
29
|
return value.flatMap((run, index) => isRecord(run) && (run.cite !== void 0 || run.footnote !== void 0) ? [index] : []);
|
|
@@ -41,9 +41,9 @@ function annotationIssues(value) {
|
|
|
41
41
|
const unsupported = (runs, path, where) => {
|
|
42
42
|
for (const runIndex of markedRuns(runs)) issues.push(issue(`${path}/${runIndex}`, `cite and footnote are not supported in ${where}; markers are drawn only in the slide title, subtitle and tag, text, bullets, list items and quote text`, { code: "cite-unsupported-location" }));
|
|
43
43
|
};
|
|
44
|
-
const cell = (
|
|
45
|
-
if (Array.isArray(
|
|
46
|
-
else if (isRecord(
|
|
44
|
+
const cell = (entry2, path) => {
|
|
45
|
+
if (Array.isArray(entry2)) unsupported(entry2, path, "table cells");
|
|
46
|
+
else if (isRecord(entry2) && Array.isArray(entry2.value)) unsupported(entry2.value, pathFor(path, "value"), "table cells");
|
|
47
47
|
};
|
|
48
48
|
const payload = (node, path) => {
|
|
49
49
|
if (isRecord(node.table)) {
|
|
@@ -52,8 +52,8 @@ function annotationIssues(value) {
|
|
|
52
52
|
cell(column, `${pathFor(tablePath, "columns")}/${index}`);
|
|
53
53
|
});
|
|
54
54
|
if (Array.isArray(table.rows)) table.rows.forEach((row, rowIndex) => {
|
|
55
|
-
if (Array.isArray(row)) row.forEach((
|
|
56
|
-
cell(
|
|
55
|
+
if (Array.isArray(row)) row.forEach((entry2, cellIndex) => {
|
|
56
|
+
cell(entry2, `${pathFor(tablePath, "rows")}/${rowIndex}/${cellIndex}`);
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
}
|
|
@@ -68,15 +68,15 @@ function annotationIssues(value) {
|
|
|
68
68
|
const slidePath = `/slides/${index}`;
|
|
69
69
|
payload(slide, slidePath);
|
|
70
70
|
visitContentPayloads(slide, slidePath, payload);
|
|
71
|
-
walkCitationRuns(slide, `slides.${index}`, (
|
|
72
|
-
const runPath = pointer(
|
|
73
|
-
const cite =
|
|
71
|
+
walkCitationRuns(slide, `slides.${index}`, (entry2) => {
|
|
72
|
+
const runPath = pointer(entry2.path);
|
|
73
|
+
const cite = entry2.run.cite;
|
|
74
74
|
if (typeof cite === "string") {
|
|
75
75
|
if (!ids.has(cite)) issues.push(issue(`${runPath}/cite`, `unknown reference '${cite}'; add it to the top-level references list`, { code: "cite-unknown-reference", id: cite }));
|
|
76
76
|
} else if (Array.isArray(cite)) cite.forEach((id, position) => {
|
|
77
77
|
if (typeof id === "string" && !ids.has(id)) issues.push(issue(`${runPath}/cite/${position}`, `unknown reference '${id}'; add it to the top-level references list`, { code: "cite-unknown-reference", id }));
|
|
78
78
|
});
|
|
79
|
-
if (Array.isArray(
|
|
79
|
+
if (Array.isArray(entry2.footnote)) unsupported(entry2.footnote, `${runPath}/footnote`, "footnote text");
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
82
|
return issues;
|
|
@@ -144,7 +144,7 @@ function bestMatchErrors(ajv2, rootId, errors) {
|
|
|
144
144
|
if (analysis) analyses.push(analysis);
|
|
145
145
|
}
|
|
146
146
|
if (!analyses.length) return errors;
|
|
147
|
-
const within = (inner, other) => other !== inner && [...inner.span].every((
|
|
147
|
+
const within = (inner, other) => other !== inner && [...inner.span].every((entry2) => other.span.has(entry2)) && (other.span.size > inner.span.size || errors.indexOf(other.union) > errors.indexOf(inner.union));
|
|
148
148
|
const outer = analyses.filter((analysis) => !analyses.some((other) => within(analysis, other)));
|
|
149
149
|
const replaced = new Map(outer.map((analysis) => [analysis.union, analysis]));
|
|
150
150
|
const drop = new Set(outer.flatMap((analysis) => [...analysis.span]));
|
|
@@ -213,18 +213,18 @@ function rememberValidationDefinition(issue2, parentSchema, keyword) {
|
|
|
213
213
|
if (!parentSchema || typeof parentSchema !== "object") return;
|
|
214
214
|
if (!schemaLocations) {
|
|
215
215
|
schemaLocations = /* @__PURE__ */ new WeakMap();
|
|
216
|
-
for (const
|
|
216
|
+
for (const entry2 of schemaEntries) {
|
|
217
217
|
const stack = [
|
|
218
|
-
{ value:
|
|
218
|
+
{ value: entry2.schema, pointer: "" }
|
|
219
219
|
];
|
|
220
220
|
while (stack.length) {
|
|
221
221
|
const current = stack.pop();
|
|
222
222
|
if (!current?.value || typeof current.value !== "object")
|
|
223
223
|
continue;
|
|
224
224
|
schemaLocations.set(current.value, {
|
|
225
|
-
schemaName:
|
|
225
|
+
schemaName: entry2.name,
|
|
226
226
|
pointer: current.pointer,
|
|
227
|
-
uri: `${
|
|
227
|
+
uri: `${entry2.schema.$id}#${current.pointer}`
|
|
228
228
|
});
|
|
229
229
|
for (const [key2, value] of Object.entries(current.value)) {
|
|
230
230
|
const segment = key2.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
@@ -379,13 +379,13 @@ function toIssue(error) {
|
|
|
379
379
|
rememberValidationDefinition(issue2, error.parentSchema, error.keyword);
|
|
380
380
|
return issue2;
|
|
381
381
|
}
|
|
382
|
-
function semanticIssue(path, message,
|
|
382
|
+
function semanticIssue(path, message, params2 = {}) {
|
|
383
383
|
return {
|
|
384
384
|
path,
|
|
385
385
|
message,
|
|
386
386
|
keyword: "opf",
|
|
387
387
|
schemaPath: "#/x-opf-semantics",
|
|
388
|
-
params
|
|
388
|
+
params: params2
|
|
389
389
|
};
|
|
390
390
|
}
|
|
391
391
|
function hasOwn(value, key2) {
|
|
@@ -414,22 +414,22 @@ function inferredKinds(value) {
|
|
|
414
414
|
if (hasOwn(value, "timeline")) kinds.push("timeline");
|
|
415
415
|
return kinds;
|
|
416
416
|
}
|
|
417
|
-
function isImplicitBlocksComposition(value, inferred,
|
|
418
|
-
if (
|
|
417
|
+
function isImplicitBlocksComposition(value, inferred, options2) {
|
|
418
|
+
if (options2.slideRoot !== true || hasOwn(value, "blocks")) {
|
|
419
419
|
return false;
|
|
420
420
|
}
|
|
421
421
|
return inferred.length > 1;
|
|
422
422
|
}
|
|
423
|
-
function validateContentPayload(value, path,
|
|
423
|
+
function validateContentPayload(value, path, options2 = {}) {
|
|
424
424
|
const issues = [];
|
|
425
425
|
const explicitType = value.type;
|
|
426
426
|
const payloadFields = presentFields(value, rootPayloadFields);
|
|
427
427
|
const hasBlocks = hasOwn(value, "blocks");
|
|
428
|
-
const captionIssue = (reason,
|
|
429
|
-
const imageOptionIssues = (reason,
|
|
430
|
-
for (const field of imageOptionFields) if (hasOwn(value, field)) issues.push(semanticIssue(pathFor(path, field), `${field} is only valid on an image payload; ${reason}`, { code: "image-option-unsupported-payload", field, ...
|
|
428
|
+
const captionIssue = (reason, params2 = {}) => semanticIssue(pathFor(path, "caption"), `caption is only valid on an image, chart, table or video payload; ${reason}`, { code: "caption-unsupported-payload", ...params2 });
|
|
429
|
+
const imageOptionIssues = (reason, params2 = {}) => {
|
|
430
|
+
for (const field of imageOptionFields) if (hasOwn(value, field)) issues.push(semanticIssue(pathFor(path, field), `${field} is only valid on an image payload; ${reason}`, { code: "image-option-unsupported-payload", field, ...params2 }));
|
|
431
431
|
};
|
|
432
|
-
if (hasOwn(value, "placement") && !
|
|
432
|
+
if (hasOwn(value, "placement") && !options2.topLevel && !hasBlocks && explicitType !== "group" && inferredKinds(value).includes("image")) {
|
|
433
433
|
issues.push(semanticIssue(pathFor(path, "placement"), "placement is only valid on a top-level block (slides.N.blocks.I); a block inside a group or a promoted region cannot bleed to a slide edge", { code: "image-placement-invalid" }));
|
|
434
434
|
}
|
|
435
435
|
if (hasBlocks || explicitType === "group") {
|
|
@@ -438,11 +438,11 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
438
438
|
if (explicitType !== void 0 && explicitType !== "group") issues.push(semanticIssue(path, "a group must use type 'group' or omit type"));
|
|
439
439
|
const incompatible2 = payloadFields.filter((field) => field !== "blocks" && field !== "type");
|
|
440
440
|
if (incompatible2.length) issues.push(semanticIssue(path, "blocks cannot be mixed with leaf payload fields", { fields: incompatible2 }));
|
|
441
|
-
if (!Array.isArray(value.blocks) || value.blocks.length === 0 && !
|
|
441
|
+
if (!Array.isArray(value.blocks) || value.blocks.length === 0 && !options2.slideRoot) issues.push(semanticIssue(pathFor(path, "blocks"), "a group requires at least one block"));
|
|
442
442
|
if (Array.isArray(value.blocks)) value.blocks.forEach((block, index) => {
|
|
443
|
-
if (isRecord(block)) issues.push(...validateContentPayload(block, `${pathFor(path, "blocks")}/${index}`, { topLevel:
|
|
443
|
+
if (isRecord(block)) issues.push(...validateContentPayload(block, `${pathFor(path, "blocks")}/${index}`, { topLevel: options2.slideRoot === true }));
|
|
444
444
|
});
|
|
445
|
-
if (
|
|
445
|
+
if (options2.slideRoot && Array.isArray(value.blocks)) {
|
|
446
446
|
const edges = /* @__PURE__ */ new Map();
|
|
447
447
|
value.blocks.forEach((block, index) => {
|
|
448
448
|
const edge = isRecord(block) && isRecord(block.placement) ? block.placement.edge : void 0;
|
|
@@ -453,7 +453,7 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
453
453
|
}
|
|
454
454
|
return issues;
|
|
455
455
|
}
|
|
456
|
-
if (hasOwn(value, "composition") && !
|
|
456
|
+
if (hasOwn(value, "composition") && !options2.slideRoot) issues.push(semanticIssue(pathFor(path, "composition"), "composition is only valid on a group containing blocks"));
|
|
457
457
|
if (explicitType !== void 0 && !isContentKind(explicitType)) {
|
|
458
458
|
return issues;
|
|
459
459
|
}
|
|
@@ -468,7 +468,7 @@ function validateContentPayload(value, path, options = {}) {
|
|
|
468
468
|
return issues;
|
|
469
469
|
}
|
|
470
470
|
if (!kind && inferred.length > 1) {
|
|
471
|
-
if (isImplicitBlocksComposition(value, inferred,
|
|
471
|
+
if (isImplicitBlocksComposition(value, inferred, options2)) {
|
|
472
472
|
if (hasOwn(value, "caption")) issues.push(captionIssue("a slide root with several payloads cannot carry one; put the caption on a block", { inferredTypes: inferred }));
|
|
473
473
|
return issues;
|
|
474
474
|
}
|
|
@@ -577,22 +577,22 @@ function deckMetadataIssues(value) {
|
|
|
577
577
|
const issues = [];
|
|
578
578
|
const entries = (key2) => {
|
|
579
579
|
const field = value[key2];
|
|
580
|
-
if (Array.isArray(field)) return field.flatMap((
|
|
580
|
+
if (Array.isArray(field)) return field.flatMap((entry2, index) => isRecord(entry2) ? [{ entry: entry2, path: `/${key2}/${index}` }] : []);
|
|
581
581
|
return isRecord(field) ? [{ entry: field, path: `/${key2}` }] : [];
|
|
582
582
|
};
|
|
583
583
|
const organizationIds = /* @__PURE__ */ new Set();
|
|
584
584
|
for (const key2 of ["organization", "speaker"]) {
|
|
585
585
|
const seen = /* @__PURE__ */ new Set();
|
|
586
|
-
for (const { entry, path } of entries(key2)) {
|
|
587
|
-
if (typeof
|
|
588
|
-
if (seen.has(
|
|
589
|
-
seen.add(
|
|
590
|
-
if (key2 === "organization") organizationIds.add(
|
|
586
|
+
for (const { entry: entry2, path } of entries(key2)) {
|
|
587
|
+
if (typeof entry2.id !== "string") continue;
|
|
588
|
+
if (seen.has(entry2.id)) issues.push(semanticIssue(`${path}/id`, `${key2} ids must be unique within a presentation`, { id: entry2.id }));
|
|
589
|
+
seen.add(entry2.id);
|
|
590
|
+
if (key2 === "organization") organizationIds.add(entry2.id);
|
|
591
591
|
}
|
|
592
592
|
}
|
|
593
|
-
for (const { entry, path } of entries("speaker")) {
|
|
594
|
-
if (typeof
|
|
595
|
-
issues.push(semanticIssue(`${path}/organizationId`, `organizationId '${
|
|
593
|
+
for (const { entry: entry2, path } of entries("speaker")) {
|
|
594
|
+
if (typeof entry2.organizationId === "string" && !organizationIds.has(entry2.organizationId)) {
|
|
595
|
+
issues.push(semanticIssue(`${path}/organizationId`, `organizationId '${entry2.organizationId}' names no organization in the presentation; use the id of an entry in organization`, { id: entry2.organizationId }));
|
|
596
596
|
}
|
|
597
597
|
}
|
|
598
598
|
return issues;
|
|
@@ -659,11 +659,11 @@ function slideThemeDimensionsWarnings(document, slide, index, catalogs) {
|
|
|
659
659
|
const slidePath = `/slides/${index}`;
|
|
660
660
|
const slideDesign = isRecord(slide.design) ? slide.design : void 0;
|
|
661
661
|
if (!slideDesign || typeof slideDesign.theme !== "string") return [];
|
|
662
|
-
const
|
|
663
|
-
const slideTheme = resolveDesignRecords(document, index,
|
|
662
|
+
const options2 = { catalogs: catalogs ?? [] };
|
|
663
|
+
const slideTheme = resolveDesignRecords(document, index, options2);
|
|
664
664
|
if (slideTheme.diagnostics.some((diagnostic) => diagnostic.kind === "themes") || slideTheme.theme.dimensions === void 0) return [];
|
|
665
665
|
const deckDesign = isRecord(document.design) ? document.design : {};
|
|
666
|
-
const deckDimensions = deckDesign.dimensions ?? resolveDesignRecords(document, void 0,
|
|
666
|
+
const deckDimensions = deckDesign.dimensions ?? resolveDesignRecords(document, void 0, options2).theme.dimensions;
|
|
667
667
|
const slideSize = canvasSize(slideTheme.theme.dimensions);
|
|
668
668
|
const deckSize = canvasSize(deckDimensions);
|
|
669
669
|
if (slideSize === deckSize) return [];
|
|
@@ -790,28 +790,28 @@ function variableReferenceWarnings(value) {
|
|
|
790
790
|
}
|
|
791
791
|
const variables = isRecord(value.variables) ? value.variables : {};
|
|
792
792
|
const issues = [];
|
|
793
|
-
const colorRef = (
|
|
794
|
-
if (typeof
|
|
795
|
-
const id =
|
|
793
|
+
const colorRef = (entry2, path) => {
|
|
794
|
+
if (typeof entry2 !== "string" || !entry2.startsWith("var:")) return;
|
|
795
|
+
const id = entry2.slice("var:".length);
|
|
796
796
|
if (!variableIdPattern.test(id) || hasOwn(variables, id)) return;
|
|
797
797
|
issues.push(semanticIssue(path, `unknown variable '${id}'; declare it in the top-level variables map`, { id, code: "variable-reference-unknown" }));
|
|
798
798
|
};
|
|
799
|
-
const runColor = (
|
|
800
|
-
if (typeof
|
|
801
|
-
if (hexColorPattern.test(
|
|
802
|
-
if (
|
|
803
|
-
colorRef(
|
|
799
|
+
const runColor = (entry2, path) => {
|
|
800
|
+
if (typeof entry2 !== "string") return;
|
|
801
|
+
if (hexColorPattern.test(entry2) || schemeColorNames.has(entry2)) return;
|
|
802
|
+
if (entry2.startsWith("var:") && variableIdPattern.test(entry2.slice("var:".length))) {
|
|
803
|
+
colorRef(entry2, path);
|
|
804
804
|
return;
|
|
805
805
|
}
|
|
806
806
|
issues.push(semanticIssue(
|
|
807
807
|
path,
|
|
808
|
-
`run color '${
|
|
809
|
-
{ color:
|
|
808
|
+
`run color '${entry2}' is not a hex color, a color-scheme name, or a 'var:' reference; renderers fall back to the theme color`,
|
|
809
|
+
{ color: entry2, code: "run-color-unrecognized" }
|
|
810
810
|
));
|
|
811
811
|
};
|
|
812
|
-
const richText = (
|
|
813
|
-
if (!Array.isArray(
|
|
814
|
-
|
|
812
|
+
const richText = (entry2, path) => {
|
|
813
|
+
if (!Array.isArray(entry2)) return;
|
|
814
|
+
entry2.forEach((run, index) => {
|
|
815
815
|
if (isRecord(run)) runColor(run.color, `${path}/${index}/color`);
|
|
816
816
|
});
|
|
817
817
|
};
|
|
@@ -855,15 +855,15 @@ function variableReferenceWarnings(value) {
|
|
|
855
855
|
};
|
|
856
856
|
const textEntries = (entries, path) => {
|
|
857
857
|
if (!Array.isArray(entries)) return;
|
|
858
|
-
entries.forEach((
|
|
858
|
+
entries.forEach((entry2, index) => {
|
|
859
859
|
const entryPath = `${path}/${index}`;
|
|
860
|
-
if (Array.isArray(
|
|
861
|
-
richText(
|
|
860
|
+
if (Array.isArray(entry2)) {
|
|
861
|
+
richText(entry2, entryPath);
|
|
862
862
|
return;
|
|
863
863
|
}
|
|
864
|
-
if (!isRecord(
|
|
865
|
-
richText(
|
|
866
|
-
richText(
|
|
864
|
+
if (!isRecord(entry2)) return;
|
|
865
|
+
richText(entry2.text, pathFor(entryPath, "text"));
|
|
866
|
+
richText(entry2.description, pathFor(entryPath, "description"));
|
|
867
867
|
});
|
|
868
868
|
};
|
|
869
869
|
const backgroundColors = (background, path) => {
|
|
@@ -919,9 +919,9 @@ function presentationReferenceWarnings(value, catalogs) {
|
|
|
919
919
|
numberingWarnings(slide, slidePath);
|
|
920
920
|
visitContentPayloads(slide, slidePath, numberingWarnings);
|
|
921
921
|
const highlightWarnings = (payload, path) => {
|
|
922
|
-
const
|
|
923
|
-
if (!isRecord(
|
|
924
|
-
for (const finding2 of codeHighlightLines(
|
|
922
|
+
const code2 = payload.code;
|
|
923
|
+
if (!isRecord(code2) || !Array.isArray(code2.highlight) || typeof code2.source !== "string") return;
|
|
924
|
+
for (const finding2 of codeHighlightLines(code2.highlight, code2.source).issues) {
|
|
925
925
|
if (finding2.code === "code-highlight-invalid") continue;
|
|
926
926
|
issues.push(semanticIssue(pathFor(pathFor(pathFor(path, "code"), "highlight"), String(finding2.index)), finding2.message, { code: finding2.code }));
|
|
927
927
|
}
|
|
@@ -958,23 +958,23 @@ function contentDepthIssues(value) {
|
|
|
958
958
|
for (const key2 of promotedRegionKeys) if (hasOwn(slide, key2)) stack.push({ value: slide[key2], path: `/slides/${index}/${key2}`, depth: 0, ancestors: [] });
|
|
959
959
|
});
|
|
960
960
|
while (stack.length) {
|
|
961
|
-
const
|
|
962
|
-
if (!isRecord(
|
|
963
|
-
if (
|
|
964
|
-
const ancestors = [...
|
|
965
|
-
|
|
966
|
-
stack.push({ value: block, path: `${
|
|
961
|
+
const entry2 = stack.pop();
|
|
962
|
+
if (!isRecord(entry2.value) || !Array.isArray(entry2.value.blocks)) continue;
|
|
963
|
+
if (entry2.ancestors.includes(entry2.value) || entry2.depth >= MAX_COMPOSITION_DEPTH) return [semanticIssue(entry2.path, `content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels`)];
|
|
964
|
+
const ancestors = [...entry2.ancestors, entry2.value];
|
|
965
|
+
entry2.value.blocks.forEach((block, i) => {
|
|
966
|
+
stack.push({ value: block, path: `${entry2.path}/blocks/${i}`, depth: entry2.depth + 1, ancestors });
|
|
967
967
|
});
|
|
968
968
|
}
|
|
969
969
|
return [];
|
|
970
970
|
}
|
|
971
971
|
var variableTypeList = VARIABLE_KINDS.join(", ");
|
|
972
|
-
function variableIssue(id, suffix, keyword, message,
|
|
972
|
+
function variableIssue(id, suffix, keyword, message, params2) {
|
|
973
973
|
return {
|
|
974
974
|
instancePath: `/variables/${id.replaceAll("~", "~0").replaceAll("/", "~1")}${suffix}`,
|
|
975
975
|
schemaPath: "#/$defs/Variable/oneOf",
|
|
976
976
|
keyword,
|
|
977
|
-
params,
|
|
977
|
+
params: params2,
|
|
978
978
|
message
|
|
979
979
|
};
|
|
980
980
|
}
|
|
@@ -984,23 +984,23 @@ function typedVariableErrors(errors, value) {
|
|
|
984
984
|
const variables = value.variables;
|
|
985
985
|
const kept = errors.filter((error) => !error.instancePath.startsWith("/variables/"));
|
|
986
986
|
const schemaId = schemas.presentation.$id;
|
|
987
|
-
for (const [id,
|
|
988
|
-
if (typeof
|
|
989
|
-
if (!hexColorPattern.test(
|
|
987
|
+
for (const [id, entry2] of Object.entries(variables)) {
|
|
988
|
+
if (typeof entry2 === "string") {
|
|
989
|
+
if (!hexColorPattern.test(entry2)) kept.push(variableIssue(id, "", "type", "must be a hex color string or a variable object", {}));
|
|
990
990
|
continue;
|
|
991
991
|
}
|
|
992
|
-
if (!isRecord(
|
|
992
|
+
if (!isRecord(entry2)) {
|
|
993
993
|
kept.push(variableIssue(id, "", "type", "must be a hex color string or a variable object", {}));
|
|
994
994
|
continue;
|
|
995
995
|
}
|
|
996
|
-
const kind =
|
|
996
|
+
const kind = entry2.type;
|
|
997
997
|
if (typeof kind !== "string" || !VARIABLE_KINDS.includes(kind)) {
|
|
998
998
|
kept.push(variableIssue(id, "/type", "enum", `must be one of: ${variableTypeList} (a hex string is shorthand for a color variable)`, { allowedValues: [...VARIABLE_KINDS] }));
|
|
999
999
|
continue;
|
|
1000
1000
|
}
|
|
1001
1001
|
const definition = `${kind.charAt(0).toUpperCase()}${kind.slice(1)}Variable`;
|
|
1002
1002
|
const check = getAjv().getSchema(`${schemaId}#/$defs/${definition}`);
|
|
1003
|
-
if (!check || check(
|
|
1003
|
+
if (!check || check(entry2) === true) continue;
|
|
1004
1004
|
for (const error of check.errors ?? []) {
|
|
1005
1005
|
kept.push({ ...error, instancePath: `/variables/${id.replaceAll("~", "~0").replaceAll("/", "~1")}${error.instancePath}`, schemaPath: `#/$defs/${definition}${error.schemaPath.replace(/^#/, "")}` });
|
|
1006
1006
|
}
|
|
@@ -1044,7 +1044,7 @@ function dataUnionErrors(errors, value) {
|
|
|
1044
1044
|
return { ...error, message: field === "fields" ? "'fields' applies only to a dataset-backed table; add 'dataset' or remove 'fields'" : datasetTableMessage(field) };
|
|
1045
1045
|
});
|
|
1046
1046
|
}
|
|
1047
|
-
function validateAgainstSchema(value, schemaOrKind = "presentation",
|
|
1047
|
+
function validateAgainstSchema(value, schemaOrKind = "presentation", options2 = {}) {
|
|
1048
1048
|
const resolved = resolveValidator(schemaOrKind);
|
|
1049
1049
|
if (resolved.schemaName === "presentation") {
|
|
1050
1050
|
const errors2 = contentDepthIssues(value);
|
|
@@ -1055,15 +1055,15 @@ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {
|
|
|
1055
1055
|
let subject = value;
|
|
1056
1056
|
let template;
|
|
1057
1057
|
let unfilledVariables;
|
|
1058
|
-
if (resolved.schemaName === "presentation" && isRecord(value) && (hasContentVariables(value) ||
|
|
1059
|
-
template =
|
|
1060
|
-
const view = instantiateForValidation(value,
|
|
1058
|
+
if (resolved.schemaName === "presentation" && isRecord(value) && (hasContentVariables(value) || options2.values !== void 0 || options2.template !== void 0)) {
|
|
1059
|
+
template = options2.template ?? isTemplate(value);
|
|
1060
|
+
const view = instantiateForValidation(value, options2.values ?? {}, template);
|
|
1061
1061
|
subject = view.presentation;
|
|
1062
1062
|
unfilledVariables = view.unfilled;
|
|
1063
|
-
for (const
|
|
1064
|
-
if (
|
|
1065
|
-
const issue2 = semanticIssue(
|
|
1066
|
-
(
|
|
1063
|
+
for (const entry2 of view.diagnostics) {
|
|
1064
|
+
if (entry2.code === "variable-example-used" || entry2.code === "variable-rich-flattened" || entry2.code === "variable-unfilled") continue;
|
|
1065
|
+
const issue2 = semanticIssue(entry2.path, entry2.message, { id: entry2.id, code: entry2.code });
|
|
1066
|
+
(entry2.severity === "error" ? variableIssues.errors : variableIssues.warnings).push(issue2);
|
|
1067
1067
|
}
|
|
1068
1068
|
if (!template) {
|
|
1069
1069
|
const declared = isRecord(value.variables) ? value.variables : {};
|
|
@@ -1085,7 +1085,7 @@ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {
|
|
|
1085
1085
|
errors.push(...variableIssues.errors);
|
|
1086
1086
|
const data = dataIssues(subject);
|
|
1087
1087
|
errors.push(...data.errors);
|
|
1088
|
-
warnings.push(...presentationReferenceWarnings(subject,
|
|
1088
|
+
warnings.push(...presentationReferenceWarnings(subject, options2.catalogs));
|
|
1089
1089
|
warnings.push(...data.warnings);
|
|
1090
1090
|
warnings.push(...variableIssues.warnings);
|
|
1091
1091
|
} else if (resolved.schemaName === "language") {
|
|
@@ -1104,9 +1104,9 @@ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {
|
|
|
1104
1104
|
|
|
1105
1105
|
// src/narrative-plan.ts
|
|
1106
1106
|
var isRec = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1107
|
-
function resolveNarrative(document,
|
|
1107
|
+
function resolveNarrative(document, options2 = {}) {
|
|
1108
1108
|
if (!isRec(document) || typeof document.narrative !== "string") return void 0;
|
|
1109
|
-
const found = resolveReference(document, "narratives", document.narrative, { catalogs:
|
|
1109
|
+
const found = resolveReference(document, "narratives", document.narrative, { catalogs: options2.catalogs ?? [] });
|
|
1110
1110
|
if (!found) return void 0;
|
|
1111
1111
|
const record = found.record;
|
|
1112
1112
|
const beats = Array.isArray(record.beats) ? record.beats.flatMap((beat) => isRec(beat) && typeof beat.id === "string" ? [beat.id] : []) : [];
|
|
@@ -1230,7 +1230,7 @@ function simulateVision(hex, model) {
|
|
|
1230
1230
|
}
|
|
1231
1231
|
var PAGE = "#FFFFFF";
|
|
1232
1232
|
var colorFrom = (scheme, slot, fallback) => typeof slot === "string" ? slot.startsWith("#") ? normalizeHexColor(slot) ?? fallback : normalizeHexColor(scheme[slot]) ?? fallback : fallback;
|
|
1233
|
-
function backdropOf(definition, scheme,
|
|
1233
|
+
function backdropOf(definition, scheme, text2, variables, canvasColor) {
|
|
1234
1234
|
const resolve2 = (value, fallback) => backgroundColorRef(value, scheme, variables, fallback);
|
|
1235
1235
|
const canvas = canvasColor ?? defaultSlideBackground(scheme);
|
|
1236
1236
|
if (!definition) return { kind: "solid", color: canvas };
|
|
@@ -1259,7 +1259,7 @@ function backdropOf(definition, scheme, text, variables, canvasColor) {
|
|
|
1259
1259
|
kind: "pattern",
|
|
1260
1260
|
colors: [
|
|
1261
1261
|
blend(resolve2(pattern.backgroundColor, PAGE), PAGE, opacity),
|
|
1262
|
-
blend(resolve2(pattern.foregroundColor,
|
|
1262
|
+
blend(resolve2(pattern.foregroundColor, text2), PAGE, opacity)
|
|
1263
1263
|
]
|
|
1264
1264
|
};
|
|
1265
1265
|
}
|
|
@@ -1329,24 +1329,24 @@ function colorChoice(levels, scheme, variables = {}) {
|
|
|
1329
1329
|
}
|
|
1330
1330
|
function resolveDesign(document, index, context) {
|
|
1331
1331
|
const deck = rec(document.design), own4 = rec(rec(rec(document.slides)[index]).design);
|
|
1332
|
-
const { options, diagnostics, resolved } = context;
|
|
1332
|
+
const { options: options2, diagnostics, resolved } = context;
|
|
1333
1333
|
const { theme, colorScheme, fontScheme, fontSchemePath } = resolved;
|
|
1334
1334
|
const fontPath = `/${fontSchemePath.split(".").join("/")}`;
|
|
1335
1335
|
const fontDiagnostic = diagnostics.find((diagnostic) => diagnostic.kind === "fontSchemes");
|
|
1336
|
-
const dimensions = { width:
|
|
1336
|
+
const dimensions = { width: options2.width, height: options2.height };
|
|
1337
1337
|
const variables = rec(document.variables);
|
|
1338
1338
|
const choice = colorChoice([own4, deck, theme], colorScheme, variables);
|
|
1339
1339
|
const roles = resolveColorRoles(colorScheme, { background: choice.background, variant: choice.variant });
|
|
1340
1340
|
const dark = roles.dark;
|
|
1341
|
-
const
|
|
1342
|
-
const backdrop = backdropOf(choice.drawn ? choice.drawn.definition : own4.background ?? deck.background ?? theme.background, colorScheme,
|
|
1341
|
+
const text2 = roles.text;
|
|
1342
|
+
const backdrop = backdropOf(choice.drawn ? choice.drawn.definition : own4.background ?? deck.background ?? theme.background, colorScheme, text2, variables, roles.map.bg1 === "light1" ? void 0 : roles.background);
|
|
1343
1343
|
const picture = imageBackground(choice.drawn ? choice.drawn.definition : own4.background ?? deck.background ?? theme.background);
|
|
1344
1344
|
const backgroundImage = picture && own4.background !== void 0 ? { source: picture.src, fit: picture.fit, path: `/slides/${index}/design/background` } : picture && deck.background !== void 0 ? { source: picture.src, fit: picture.fit, path: "/design/background" } : void 0;
|
|
1345
1345
|
return {
|
|
1346
1346
|
theme,
|
|
1347
1347
|
colorScheme,
|
|
1348
1348
|
fontScheme,
|
|
1349
|
-
fonts:
|
|
1349
|
+
fonts: options2.fontFamilies,
|
|
1350
1350
|
dimensions,
|
|
1351
1351
|
backdrop,
|
|
1352
1352
|
dark,
|
|
@@ -1357,7 +1357,7 @@ function resolveDesign(document, index, context) {
|
|
|
1357
1357
|
colors: {
|
|
1358
1358
|
background: roles.background,
|
|
1359
1359
|
surface: roles.surface,
|
|
1360
|
-
text,
|
|
1360
|
+
text: text2,
|
|
1361
1361
|
mutedText: roles.textSecondary,
|
|
1362
1362
|
primary: roles.primary,
|
|
1363
1363
|
secondary: roles.secondary,
|
|
@@ -1424,10 +1424,10 @@ function backdropColors(backdrop, box, size, layer) {
|
|
|
1424
1424
|
}
|
|
1425
1425
|
}
|
|
1426
1426
|
}
|
|
1427
|
-
function worstContrast(
|
|
1427
|
+
function worstContrast(text2, sample) {
|
|
1428
1428
|
let worst = { ratio: Infinity, against: sample.colors[0] ?? PAGE };
|
|
1429
1429
|
for (const color of sample.colors) {
|
|
1430
|
-
const ratio = contrastRatio(
|
|
1430
|
+
const ratio = contrastRatio(text2, color);
|
|
1431
1431
|
if (ratio < worst.ratio) worst = { ratio, against: color };
|
|
1432
1432
|
}
|
|
1433
1433
|
return worst;
|
|
@@ -1459,11 +1459,11 @@ function textValues(slide, slidePath) {
|
|
|
1459
1459
|
push(at2("text"), node.text, "body", "text");
|
|
1460
1460
|
for (const field of ["items", "bullets"])
|
|
1461
1461
|
if (Array.isArray(node[field]))
|
|
1462
|
-
node[field].forEach((
|
|
1463
|
-
if (isText(
|
|
1464
|
-
else if (
|
|
1465
|
-
push(at2(field, index, "text"),
|
|
1466
|
-
push(at2(field, index, "description"),
|
|
1462
|
+
node[field].forEach((entry2, index) => {
|
|
1463
|
+
if (isText(entry2)) push(at2(field, index), entry2, "list", field);
|
|
1464
|
+
else if (entry2 && typeof entry2 === "object") {
|
|
1465
|
+
push(at2(field, index, "text"), entry2.text, "list", field);
|
|
1466
|
+
push(at2(field, index, "description"), entry2.description, "list-description", field);
|
|
1467
1467
|
}
|
|
1468
1468
|
});
|
|
1469
1469
|
const table = rec(node.table);
|
|
@@ -1529,9 +1529,9 @@ function altOf(value, assets) {
|
|
|
1529
1529
|
if (typeof own4 === "string") return own4;
|
|
1530
1530
|
let source = typeof value === "string" ? value : rec(value).src;
|
|
1531
1531
|
for (let hop = 0; typeof source === "string" && source.startsWith("asset:") && hop < 8; hop++) {
|
|
1532
|
-
const
|
|
1533
|
-
if (typeof rec(
|
|
1534
|
-
source = typeof
|
|
1532
|
+
const entry2 = rec(assets)[source.slice(6)];
|
|
1533
|
+
if (typeof rec(entry2).alt === "string") return rec(entry2).alt;
|
|
1534
|
+
source = typeof entry2 === "string" ? entry2 : rec(entry2).src;
|
|
1535
1535
|
}
|
|
1536
1536
|
return void 0;
|
|
1537
1537
|
}
|
|
@@ -1568,10 +1568,10 @@ function assetRefs(document, slide, slidePath, includeDeck) {
|
|
|
1568
1568
|
return;
|
|
1569
1569
|
}
|
|
1570
1570
|
for (const shape of LOGO_SHAPES) {
|
|
1571
|
-
const
|
|
1572
|
-
if (
|
|
1573
|
-
if (typeof
|
|
1574
|
-
else for (const tone of ["onLight", "onDark"]) if (rec(
|
|
1571
|
+
const entry2 = rec(logo)[shape];
|
|
1572
|
+
if (entry2 === void 0) continue;
|
|
1573
|
+
if (typeof entry2 === "string" || entry2 && typeof entry2 === "object" && "src" in entry2) out.push({ path: `${base}/${shape}`, value: entry2, kind: "organization" });
|
|
1574
|
+
else for (const tone of ["onLight", "onDark"]) if (rec(entry2)[tone] !== void 0) out.push({ path: `${base}/${shape}/${tone}`, value: rec(entry2)[tone], kind: "organization" });
|
|
1575
1575
|
}
|
|
1576
1576
|
});
|
|
1577
1577
|
const speakers = Array.isArray(document.speaker) ? document.speaker : document.speaker ? [document.speaker] : [];
|
|
@@ -1603,7 +1603,7 @@ function inkBox(item, measurement) {
|
|
|
1603
1603
|
if (!fit || !Array.isArray(fit.lines) || !fit.lines.length || !item.textStyle || item.quoteLayout || item.metricLayout || item.timelineLayout) return item.box;
|
|
1604
1604
|
try {
|
|
1605
1605
|
const measure = textWidthMeasurer(item.textStyle, measurement);
|
|
1606
|
-
const width = Math.min(item.box.width, Math.max(...fit.lines.map((
|
|
1606
|
+
const width = Math.min(item.box.width, Math.max(...fit.lines.map((line2) => measure(line2, fit.fontSize))));
|
|
1607
1607
|
const height = Math.min(item.box.height, fit.lines.length * fit.lineHeight);
|
|
1608
1608
|
const x = item.alignment === "center" ? item.box.x + (item.box.width - width) / 2 : item.alignment === "right" ? item.box.x + item.box.width - width : item.box.x;
|
|
1609
1609
|
return { x, y: item.box.y, width, height };
|
|
@@ -1971,12 +1971,12 @@ var readingOrderRules = [
|
|
|
1971
1971
|
function bindingOrder(items, regions) {
|
|
1972
1972
|
const rank = new Map(regions.map((region, index) => [region.name, index]));
|
|
1973
1973
|
const blockRank = (item) => {
|
|
1974
|
-
const region = regions.find((
|
|
1974
|
+
const region = regions.find((entry2) => entry2.name === item.region);
|
|
1975
1975
|
const blocks = region ? [...region.content, ...region.overflow ?? []] : [];
|
|
1976
1976
|
const found = blocks.findIndex((block) => item.path === block || item.path.startsWith(`${block}.`));
|
|
1977
1977
|
return found < 0 ? blocks.length : found;
|
|
1978
1978
|
};
|
|
1979
|
-
return items.map((item, index) => ({ item, index, region: rank.get(item.region ?? "") ?? regions.length, block: blockRank(item) })).sort((a, b) => a.region - b.region || a.block - b.block || a.index - b.index).map((
|
|
1979
|
+
return items.map((item, index) => ({ item, index, region: rank.get(item.region ?? "") ?? regions.length, block: blockRank(item) })).sort((a, b) => a.region - b.region || a.block - b.block || a.index - b.index).map((entry2) => entry2.item.path);
|
|
1980
1980
|
}
|
|
1981
1981
|
function visualOrder(items, rtl) {
|
|
1982
1982
|
if (rtl) return readingRows(items).flatMap((row) => row.map((item) => item.path));
|
|
@@ -2005,17 +2005,17 @@ var linkRules = [
|
|
|
2005
2005
|
const run = runs[i];
|
|
2006
2006
|
const link = run.style.link;
|
|
2007
2007
|
if (typeof link !== "string") continue;
|
|
2008
|
-
let
|
|
2008
|
+
let text2 = run.text;
|
|
2009
2009
|
let last = i;
|
|
2010
|
-
while (runs[last + 1] && runs[last + 1].style.link === link)
|
|
2011
|
-
const normalized =
|
|
2012
|
-
const problem = !
|
|
2010
|
+
while (runs[last + 1] && runs[last + 1].style.link === link) text2 += runs[++last].text;
|
|
2011
|
+
const normalized = text2.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, "").replace(/\s+/g, " ").trim();
|
|
2012
|
+
const problem = !text2.trim() ? "is blank" : GENERIC_LINKS.has(normalized) ? "is generic" : /^(https?:\/\/|www\.)\S{30,}$/i.test(text2.trim()) ? "is a long raw URL" : void 0;
|
|
2013
2013
|
i = last;
|
|
2014
2014
|
if (!problem) continue;
|
|
2015
2015
|
context.report(linkRule, {
|
|
2016
2016
|
path: run.path,
|
|
2017
2017
|
slide,
|
|
2018
|
-
message: `Link text ${JSON.stringify(
|
|
2018
|
+
message: `Link text ${JSON.stringify(text2.trim().slice(0, 50))} ${problem}; the destination is ${link.length > 60 ? `${link.slice(0, 57)}...` : link}.`,
|
|
2019
2019
|
help: 'Use text that names the destination or action, for example "Q3 financial results (PDF)", instead of "click here".',
|
|
2020
2020
|
fixes: [{ id: "focus-link", title: "Edit the text", kind: "focus", safe: true, focus: { path: run.valuePath, field: "link", value: link } }]
|
|
2021
2021
|
});
|
|
@@ -2116,12 +2116,12 @@ var chartRules = [
|
|
|
2116
2116
|
const resolved = resolveChartColors(chart.colors, slide.design.colorScheme, chartColorContext(slide.design));
|
|
2117
2117
|
resolved.forEach((color, index) => {
|
|
2118
2118
|
const path = `${payload.path === slide.path ? slide.path : payload.path}/chart/colors/${index}`;
|
|
2119
|
-
const
|
|
2119
|
+
const entry2 = chart.colors[index];
|
|
2120
2120
|
if (color === void 0) {
|
|
2121
2121
|
context.report(chartColorsRule, {
|
|
2122
2122
|
path,
|
|
2123
2123
|
slide,
|
|
2124
|
-
message: `Chart colour ${index + 1} (${JSON.stringify(
|
|
2124
|
+
message: `Chart colour ${index + 1} (${JSON.stringify(entry2)}) cannot be resolved, so the palette colour of that position is used.`,
|
|
2125
2125
|
help: 'Use a hex colour, a colour-scheme name such as "accent2", or the id of a colour variable the deck defines (var:<id>).'
|
|
2126
2126
|
});
|
|
2127
2127
|
return;
|
|
@@ -2602,13 +2602,13 @@ var contentRules = [
|
|
|
2602
2602
|
run(context) {
|
|
2603
2603
|
for (const slide of context.slides)
|
|
2604
2604
|
for (const tv of slide.texts) {
|
|
2605
|
-
const
|
|
2606
|
-
const hit = PLACEHOLDERS.find(({ pattern }) => pattern.test(
|
|
2605
|
+
const text2 = plainText(tv.value);
|
|
2606
|
+
const hit = PLACEHOLDERS.find(({ pattern }) => pattern.test(text2));
|
|
2607
2607
|
if (!hit) continue;
|
|
2608
2608
|
context.report(placeholderRule, {
|
|
2609
2609
|
path: tv.path,
|
|
2610
2610
|
slide,
|
|
2611
|
-
message: `Text looks like ${hit.label}: ${JSON.stringify(
|
|
2611
|
+
message: `Text looks like ${hit.label}: ${JSON.stringify(text2.length > 60 ? `${text2.slice(0, 57)}...` : text2)}.`,
|
|
2612
2612
|
help: "Replace it with the real content, or delete the element.",
|
|
2613
2613
|
fixes: [{ id: "focus-text", title: "Edit the text", kind: "focus", safe: true, focus: { path: tv.path, field: "text" } }]
|
|
2614
2614
|
});
|
|
@@ -2848,7 +2848,7 @@ var infos = [
|
|
|
2848
2848
|
...referenceRules,
|
|
2849
2849
|
...policyRules,
|
|
2850
2850
|
...dataRules,
|
|
2851
|
-
...implementations.flatMap((
|
|
2851
|
+
...implementations.flatMap((entry2) => [entry2.info, ...entry2.also ?? []])
|
|
2852
2852
|
];
|
|
2853
2853
|
var validationRules = Object.freeze(
|
|
2854
2854
|
infos.map((info, index) => ({ info, index })).sort((a, b) => validationCategories.indexOf(a.info.category) - validationCategories.indexOf(b.info.category) || a.index - b.index).map(({ info }) => Object.freeze({ ...info }))
|
|
@@ -2867,6 +2867,10 @@ function ruleInfo(id) {
|
|
|
2867
2867
|
if (!found) throw new Error(`Validation rule ${JSON.stringify(id)} is not registered.`);
|
|
2868
2868
|
return found;
|
|
2869
2869
|
}
|
|
2870
|
+
var VALIDATE_DOCS = "https://github.com/OpenPresentation/opf/blob/main/docs/validate.md";
|
|
2871
|
+
function ruleDocumentation(id) {
|
|
2872
|
+
return `${VALIDATE_DOCS}#${id.replace("/", "")}`;
|
|
2873
|
+
}
|
|
2870
2874
|
|
|
2871
2875
|
// src/check-document.ts
|
|
2872
2876
|
var own = (value, key2) => Object.hasOwn(value, key2);
|
|
@@ -2885,12 +2889,12 @@ function at(value, path) {
|
|
|
2885
2889
|
value
|
|
2886
2890
|
);
|
|
2887
2891
|
}
|
|
2888
|
-
function checkDocumentOptions(
|
|
2889
|
-
checkCatalogsOption(
|
|
2890
|
-
if (
|
|
2892
|
+
function checkDocumentOptions(options2) {
|
|
2893
|
+
checkCatalogsOption(options2.catalogs, "validate");
|
|
2894
|
+
if (options2.contracts !== void 0 && !Array.isArray(options2.contracts))
|
|
2891
2895
|
throw new TypeError("Validate contracts must be an array.");
|
|
2892
|
-
for (const
|
|
2893
|
-
if (!object(
|
|
2896
|
+
for (const contract2 of options2.contracts ?? []) {
|
|
2897
|
+
if (!object(contract2) || Object.keys(contract2).some(
|
|
2894
2898
|
(key2) => ![
|
|
2895
2899
|
"path",
|
|
2896
2900
|
"allowedValues",
|
|
@@ -2898,11 +2902,11 @@ function checkDocumentOptions(options) {
|
|
|
2898
2902
|
"message",
|
|
2899
2903
|
"documentation"
|
|
2900
2904
|
].includes(key2)
|
|
2901
|
-
) || typeof
|
|
2905
|
+
) || typeof contract2.path !== "string" || !Array.isArray(contract2.allowedValues) || !contract2.allowedValues.every(primitive) || contract2.severity !== void 0 && (typeof contract2.severity !== "string" || !["error", "warning", "info"].includes(contract2.severity)) || contract2.message !== void 0 && typeof contract2.message !== "string" || contract2.documentation !== void 0 && typeof contract2.documentation !== "string")
|
|
2902
2906
|
throw new TypeError(
|
|
2903
2907
|
"Invalid contract: provide a path and primitive allowedValues, with optional severity, message and documentation."
|
|
2904
2908
|
);
|
|
2905
|
-
parts(
|
|
2909
|
+
parts(contract2.path);
|
|
2906
2910
|
}
|
|
2907
2911
|
}
|
|
2908
2912
|
function finding(ruleId, data) {
|
|
@@ -2912,10 +2916,10 @@ function finding(ruleId, data) {
|
|
|
2912
2916
|
}
|
|
2913
2917
|
var issueRuleId = (issue2) => typeof issue2.params.code === "string" && issue2.keyword === "opf" ? `opf/${issue2.params.code}` : "opf/schema";
|
|
2914
2918
|
function issueFinding(issue2, ruleId = issueRuleId(issue2), schemaName = "presentation", prefix = "", scope = "document", severity) {
|
|
2915
|
-
const path = prefix + (issue2.path === "/" ? "" : issue2.path) + (issue2.keyword === "additionalProperties" && typeof issue2.params.additionalProperty === "string" ? pointer3([issue2.params.additionalProperty]) : ""),
|
|
2916
|
-
if (!
|
|
2919
|
+
const path = prefix + (issue2.path === "/" ? "" : issue2.path) + (issue2.keyword === "additionalProperties" && typeof issue2.params.additionalProperty === "string" ? pointer3([issue2.params.additionalProperty]) : ""), entry2 = schemaEntries.find((entry3) => entry3.name === schemaName);
|
|
2920
|
+
if (!entry2) throw new TypeError(`Unknown schema ${schemaName}`);
|
|
2917
2921
|
const precise = validationDefinition(issue2);
|
|
2918
|
-
const definition = precise?.uri ?? (issue2.keyword === "opf" ? "packages/javascript/src/schema-check.ts" : issue2.schemaPath.startsWith("#") ?
|
|
2922
|
+
const definition = precise?.uri ?? (issue2.keyword === "opf" ? "packages/javascript/src/schema-check.ts" : issue2.schemaPath.startsWith("#") ? entry2.schema.$id + issue2.schemaPath : issue2.schemaPath);
|
|
2919
2923
|
const lookup = [
|
|
2920
2924
|
"opf",
|
|
2921
2925
|
"schema",
|
|
@@ -2932,7 +2936,7 @@ function issueFinding(issue2, ruleId = issueRuleId(issue2), schemaName = "presen
|
|
|
2932
2936
|
else if (issue2.keyword === "opf")
|
|
2933
2937
|
help = "Resolve the reported structural conflict while preserving every content field. Preview the result after editing.";
|
|
2934
2938
|
const allowed = Array.isArray(issue2.params.allowedValues) ? issue2.params.allowedValues.filter(primitive) : [];
|
|
2935
|
-
const
|
|
2939
|
+
const nearest2 = issue2.keyword === "additionalProperties" ? nearestProperties(issue2, entry2.schema, precise?.pointer, precise?.schemaName) : [];
|
|
2936
2940
|
return finding(ruleId, {
|
|
2937
2941
|
...severity ? { severity } : {},
|
|
2938
2942
|
path,
|
|
@@ -2949,14 +2953,14 @@ function issueFinding(issue2, ruleId = issueRuleId(issue2), schemaName = "presen
|
|
|
2949
2953
|
origin: "schema",
|
|
2950
2954
|
definition
|
|
2951
2955
|
}))
|
|
2952
|
-
} :
|
|
2956
|
+
} : nearest2.length ? { suggestions: nearest2 } : {}
|
|
2953
2957
|
});
|
|
2954
2958
|
}
|
|
2955
2959
|
var NEAREST_PROPERTIES = 3;
|
|
2956
2960
|
function nearestProperties(issue2, fallbackRoot, pointerToKeyword, schemaName) {
|
|
2957
2961
|
const typed = issue2.params.additionalProperty;
|
|
2958
2962
|
if (typeof typed !== "string" || pointerToKeyword === void 0) return [];
|
|
2959
|
-
const rootEntry = schemaEntries.find((
|
|
2963
|
+
const rootEntry = schemaEntries.find((entry2) => entry2.name === schemaName);
|
|
2960
2964
|
let node = rootEntry?.schema ?? fallbackRoot;
|
|
2961
2965
|
const base = pointerToKeyword.split("/").slice(0, -1);
|
|
2962
2966
|
for (const token of base.slice(1)) {
|
|
@@ -2978,7 +2982,7 @@ function resolve(cursor, seen = /* @__PURE__ */ new Set()) {
|
|
|
2978
2982
|
const id = String(cursor.root.$id ?? "") + ref;
|
|
2979
2983
|
if (seen.has(id)) return { ...cursor, schema: {} };
|
|
2980
2984
|
seen.add(id);
|
|
2981
|
-
const [base, fragment = ""] = ref.split("#"), root = base ? schemaEntries.find((
|
|
2985
|
+
const [base, fragment = ""] = ref.split("#"), root = base ? schemaEntries.find((entry2) => entry2.schema.$id === base)?.schema : cursor.root;
|
|
2982
2986
|
if (!root) return { ...cursor, schema: {} };
|
|
2983
2987
|
const target = at(root, fragment);
|
|
2984
2988
|
if (!object(target)) return { ...cursor, schema: {} };
|
|
@@ -3061,29 +3065,29 @@ function fields(document) {
|
|
|
3061
3065
|
}
|
|
3062
3066
|
];
|
|
3063
3067
|
while (stack.length) {
|
|
3064
|
-
const
|
|
3065
|
-
if (!
|
|
3066
|
-
if (
|
|
3067
|
-
const resolved = resolve(
|
|
3068
|
-
const
|
|
3068
|
+
const entry2 = stack.pop();
|
|
3069
|
+
if (!entry2) break;
|
|
3070
|
+
if (entry2.ancestors.includes(entry2.value)) continue;
|
|
3071
|
+
const resolved = resolve(entry2.cursor), selected = active(entry2.cursor, entry2.value);
|
|
3072
|
+
const assetReference2 = [resolved, selected].some(
|
|
3069
3073
|
(cursor) => cursor.root.$id === schemas.presentation.$id && (cursor.path === "/$defs/Asset" || cursor.path === "/$defs/Asset/oneOf/0" || cursor.path === "/$defs/Watermark/properties/src") || String(cursor.schema.description ?? "").includes("asset:") || Array.isArray(cursor.schema.examples) && cursor.schema.examples.some(
|
|
3070
3074
|
(example) => typeof example === "string" && example.startsWith("asset:")
|
|
3071
3075
|
)
|
|
3072
3076
|
);
|
|
3073
3077
|
result.push({
|
|
3074
|
-
path: pointer3(
|
|
3075
|
-
value:
|
|
3078
|
+
path: pointer3(entry2.path),
|
|
3079
|
+
value: entry2.value,
|
|
3076
3080
|
cursor: resolved,
|
|
3077
|
-
assetReference
|
|
3081
|
+
assetReference: assetReference2
|
|
3078
3082
|
});
|
|
3079
|
-
const ancestors = [...
|
|
3083
|
+
const ancestors = [...entry2.ancestors, entry2.value];
|
|
3080
3084
|
const cursors = [selected, ...selected.baseSchemas ?? [], resolved];
|
|
3081
3085
|
const arrayCursor = cursors.find((cursor) => object(cursor.schema.items));
|
|
3082
|
-
if (Array.isArray(
|
|
3083
|
-
|
|
3086
|
+
if (Array.isArray(entry2.value) && arrayCursor)
|
|
3087
|
+
entry2.value.forEach((value, index) => {
|
|
3084
3088
|
stack.push({
|
|
3085
3089
|
value,
|
|
3086
|
-
path: [...
|
|
3090
|
+
path: [...entry2.path, index],
|
|
3087
3091
|
cursor: {
|
|
3088
3092
|
schema: arrayCursor.schema.items,
|
|
3089
3093
|
root: arrayCursor.root,
|
|
@@ -3092,14 +3096,14 @@ function fields(document) {
|
|
|
3092
3096
|
ancestors
|
|
3093
3097
|
});
|
|
3094
3098
|
});
|
|
3095
|
-
else if (object(
|
|
3096
|
-
for (const [key2, value] of Object.entries(
|
|
3097
|
-
if (
|
|
3099
|
+
else if (object(entry2.value))
|
|
3100
|
+
for (const [key2, value] of Object.entries(entry2.value)) {
|
|
3101
|
+
if (entry2.path.length === 0 && key2 === "catalogs") continue;
|
|
3098
3102
|
const cursor = childCursor(cursors, key2);
|
|
3099
3103
|
if (!cursor) continue;
|
|
3100
3104
|
stack.push({
|
|
3101
3105
|
value,
|
|
3102
|
-
path: [...
|
|
3106
|
+
path: [...entry2.path, key2],
|
|
3103
3107
|
cursor,
|
|
3104
3108
|
ancestors
|
|
3105
3109
|
});
|
|
@@ -3113,7 +3117,7 @@ function recordIssues(kind, id, value) {
|
|
|
3113
3117
|
const schemaName = catalogSchemaNames[kind];
|
|
3114
3118
|
return validateAgainstSchema(object(value) ? { $schema: schemas[schemaName].$id, id, ...value } : value, kind);
|
|
3115
3119
|
}
|
|
3116
|
-
function embeddedRecordFindings(document,
|
|
3120
|
+
function embeddedRecordFindings(document, options2, findings) {
|
|
3117
3121
|
const invalid = /* @__PURE__ */ new Set();
|
|
3118
3122
|
if (!object(document) || !object(document.catalogs)) return invalid;
|
|
3119
3123
|
for (const [group, value] of Object.entries(document.catalogs)) {
|
|
@@ -3126,8 +3130,8 @@ function embeddedRecordFindings(document, options, findings) {
|
|
|
3126
3130
|
for (const issue2 of validation.errors) findings.push(issueFinding(issue2, recordRuleId(issue2), catalogSchemaNames[kind], path, "document"));
|
|
3127
3131
|
if (!validation.valid) invalid.add(recordKey(group, kind, id));
|
|
3128
3132
|
if (group === "custom") continue;
|
|
3129
|
-
const source = catalogGroupSource(document, group,
|
|
3130
|
-
const registered = registeredCatalog(source,
|
|
3133
|
+
const source = catalogGroupSource(document, group, options2);
|
|
3134
|
+
const registered = registeredCatalog(source, options2);
|
|
3131
3135
|
if (!registered || Object.hasOwn(registered[kind] ?? {}, id)) continue;
|
|
3132
3136
|
findings.push(
|
|
3133
3137
|
finding("opf/catalog-record-not-in-source", {
|
|
@@ -3198,11 +3202,11 @@ function engineFindings(document, engine, enabled) {
|
|
|
3198
3202
|
}
|
|
3199
3203
|
let numberFixes;
|
|
3200
3204
|
for (const issue2 of engine.warnings) {
|
|
3201
|
-
const
|
|
3202
|
-
if (typeof
|
|
3203
|
-
const ruleId = `opf/${
|
|
3205
|
+
const code2 = issue2.params.code;
|
|
3206
|
+
if (typeof code2 !== "string") continue;
|
|
3207
|
+
const ruleId = `opf/${code2}`;
|
|
3204
3208
|
if (!enabled(ruleId)) continue;
|
|
3205
|
-
if (
|
|
3209
|
+
if (code2 === "chart-value-not-numeric") {
|
|
3206
3210
|
numberFixes ??= chartNumberFixPaths(document);
|
|
3207
3211
|
const fix = numberFixes.get(issue2.path);
|
|
3208
3212
|
if (fix) {
|
|
@@ -3215,7 +3219,7 @@ function engineFindings(document, engine, enabled) {
|
|
|
3215
3219
|
}
|
|
3216
3220
|
}
|
|
3217
3221
|
const found = issueFinding(issue2, ruleId, "presentation", "", "document", "warning");
|
|
3218
|
-
const help = HELP[
|
|
3222
|
+
const help = HELP[code2];
|
|
3219
3223
|
if (help) found.help = help;
|
|
3220
3224
|
findings.push(found);
|
|
3221
3225
|
}
|
|
@@ -3249,12 +3253,12 @@ function variableFindings(document, engine, schemaValid) {
|
|
|
3249
3253
|
if (depth2 > 64) return;
|
|
3250
3254
|
if (typeof value === "string") {
|
|
3251
3255
|
if (value.startsWith("data:") || !value.includes("{{")) return;
|
|
3252
|
-
for (const
|
|
3253
|
-
if (
|
|
3256
|
+
for (const match2 of value.matchAll(VARIABLE_TOKEN))
|
|
3257
|
+
if (match2[1])
|
|
3254
3258
|
findings.push(
|
|
3255
3259
|
finding("opf/variable-unfilled", {
|
|
3256
3260
|
path: pointer3(path),
|
|
3257
|
-
message: `Template variable ${JSON.stringify(
|
|
3261
|
+
message: `Template variable ${JSON.stringify(match2[0])} is still in the text.`,
|
|
3258
3262
|
help: "Declare the variable and fill it, or replace the token with the real value.",
|
|
3259
3263
|
severity: "warning"
|
|
3260
3264
|
})
|
|
@@ -3262,15 +3266,15 @@ function variableFindings(document, engine, schemaValid) {
|
|
|
3262
3266
|
return;
|
|
3263
3267
|
}
|
|
3264
3268
|
if (Array.isArray(value)) {
|
|
3265
|
-
value.forEach((
|
|
3266
|
-
walk2(
|
|
3269
|
+
value.forEach((entry2, index) => {
|
|
3270
|
+
walk2(entry2, [...path, String(index)], depth2 + 1);
|
|
3267
3271
|
});
|
|
3268
3272
|
return;
|
|
3269
3273
|
}
|
|
3270
3274
|
if (value && typeof value === "object")
|
|
3271
|
-
for (const [key2,
|
|
3275
|
+
for (const [key2, entry2] of Object.entries(value)) {
|
|
3272
3276
|
if (path.length === 0 && ["variables", "extensions", "catalogs", "assets"].includes(key2)) continue;
|
|
3273
|
-
walk2(
|
|
3277
|
+
walk2(entry2, [...path, key2], depth2 + 1);
|
|
3274
3278
|
}
|
|
3275
3279
|
};
|
|
3276
3280
|
walk2(document, [], 0);
|
|
@@ -3297,22 +3301,22 @@ function undeclaredCatalogFindings(document) {
|
|
|
3297
3301
|
}
|
|
3298
3302
|
return findings;
|
|
3299
3303
|
}
|
|
3300
|
-
function contentReferenceFindings(document,
|
|
3301
|
-
const invalid = embeddedRecordFindings(document,
|
|
3304
|
+
function contentReferenceFindings(document, options2, findings) {
|
|
3305
|
+
const invalid = embeddedRecordFindings(document, options2, findings);
|
|
3302
3306
|
const registeredChecked = /* @__PURE__ */ new Set();
|
|
3303
3307
|
for (const site of catalogReferenceSites(document)) {
|
|
3304
3308
|
const path = pointerPath(site.path);
|
|
3305
3309
|
const parsed = parseReference(site.reference);
|
|
3306
3310
|
if (parsed?.group !== void 0 && !catalogGroupDeclared(document, parsed.group)) continue;
|
|
3307
|
-
const found = resolveReference(document, site.kind, site.reference, { ...
|
|
3311
|
+
const found = resolveReference(document, site.kind, site.reference, { ...options2, ...site.group !== void 0 ? { group: site.group } : {} });
|
|
3308
3312
|
if (found?.origin === "host" && !registeredChecked.has(recordKey(found.group, site.kind, found.id))) {
|
|
3309
3313
|
registeredChecked.add(recordKey(found.group, site.kind, found.id));
|
|
3310
3314
|
for (const issue2 of recordIssues(site.kind, found.id, found.record).errors)
|
|
3311
3315
|
findings.push(issueFinding(issue2, recordRuleId(issue2), catalogSchemaNames[site.kind], pointer3(["catalogs", found.group, site.kind, found.id]), "context"));
|
|
3312
3316
|
}
|
|
3313
3317
|
if (found && !(found.origin === "document" && invalid.has(recordKey(found.group, site.kind, found.id)))) continue;
|
|
3314
|
-
const diagnostic = unresolvedReference(document, site.kind, site.reference, path,
|
|
3315
|
-
const suggestions = catalogRecords(document, site.kind,
|
|
3318
|
+
const diagnostic = unresolvedReference(document, site.kind, site.reference, path, options2);
|
|
3319
|
+
const suggestions = catalogRecords(document, site.kind, options2).map((entry2) => ({ value: entry2.reference, label: typeof entry2.record.name === "string" ? entry2.record.name : entry2.reference, origin: entry2.origin === "host" ? "registered" : "document", definition: entry2.origin === "document" ? `document#${pointer3(["catalogs", entry2.group, site.kind, entry2.id])}` : `${entry2.source ?? "context"}#/${site.kind}/${entry2.id}` })).sort((a, b) => distance(site.reference, a.value) - distance(site.reference, b.value) || (a.value < b.value ? -1 : a.value > b.value ? 1 : 0)).slice(0, 5);
|
|
3316
3320
|
findings.push(
|
|
3317
3321
|
finding("opf/unresolved-reference", {
|
|
3318
3322
|
path,
|
|
@@ -3325,9 +3329,9 @@ function contentReferenceFindings(document, options, findings) {
|
|
|
3325
3329
|
);
|
|
3326
3330
|
}
|
|
3327
3331
|
}
|
|
3328
|
-
function referenceFindings(document, engine,
|
|
3332
|
+
function referenceFindings(document, engine, options2) {
|
|
3329
3333
|
const findings = [];
|
|
3330
|
-
const catalogs = { catalogs:
|
|
3334
|
+
const catalogs = { catalogs: options2.catalogs ?? [] };
|
|
3331
3335
|
contentReferenceFindings(document, catalogs, findings);
|
|
3332
3336
|
const assetValues = object(document) && object(document.assets) ? document.assets : {};
|
|
3333
3337
|
for (const field of fields(document)) {
|
|
@@ -3402,10 +3406,10 @@ function referenceFindings(document, engine, options) {
|
|
|
3402
3406
|
);
|
|
3403
3407
|
return findings;
|
|
3404
3408
|
}
|
|
3405
|
-
function narrativeFindings(document,
|
|
3409
|
+
function narrativeFindings(document, options2) {
|
|
3406
3410
|
const findings = [];
|
|
3407
3411
|
if (!object(document)) return findings;
|
|
3408
|
-
const narrative = resolveNarrative(document,
|
|
3412
|
+
const narrative = resolveNarrative(document, options2);
|
|
3409
3413
|
if (narrative) {
|
|
3410
3414
|
const id = String(document.narrative);
|
|
3411
3415
|
const beatSuggestions = (beat) => narrative.beats.slice().sort((a, b) => distance(beat, a) - distance(beat, b) || (a < b ? -1 : a > b ? 1 : 0)).slice(0, 5).map((value) => ({ value, label: value, origin: narrative.origin, definition: `narratives/${id}#/beats` }));
|
|
@@ -3449,32 +3453,32 @@ function narrativeFindings(document, options) {
|
|
|
3449
3453
|
}
|
|
3450
3454
|
function contractFindings(document, contracts) {
|
|
3451
3455
|
const findings = [];
|
|
3452
|
-
for (const [index,
|
|
3453
|
-
const pattern = parts(
|
|
3456
|
+
for (const [index, contract2] of contracts.entries()) {
|
|
3457
|
+
const pattern = parts(contract2.path), stack = [{ value: document, path: [], depth: 0 }];
|
|
3454
3458
|
while (stack.length) {
|
|
3455
|
-
const
|
|
3456
|
-
if (!
|
|
3457
|
-
if (
|
|
3458
|
-
if (
|
|
3459
|
+
const entry2 = stack.pop();
|
|
3460
|
+
if (!entry2) break;
|
|
3461
|
+
if (entry2.depth === pattern.length) {
|
|
3462
|
+
if (contract2.allowedValues.some((value) => Object.is(value, entry2.value)))
|
|
3459
3463
|
continue;
|
|
3460
|
-
const path = pointer3(
|
|
3464
|
+
const path = pointer3(entry2.path), definition = contract2.documentation ?? `context#/contracts/${index}`, values = {
|
|
3461
3465
|
path,
|
|
3462
|
-
value: JSON.stringify(
|
|
3463
|
-
allowed:
|
|
3466
|
+
value: JSON.stringify(entry2.value),
|
|
3467
|
+
allowed: contract2.allowedValues.map((value) => JSON.stringify(value)).join(", "),
|
|
3464
3468
|
file: definition
|
|
3465
3469
|
};
|
|
3466
|
-
const message = (
|
|
3470
|
+
const message = (contract2.message ?? "This value is outside the allowed values for {{path}}.").replace(
|
|
3467
3471
|
/\{\{(path|value|allowed|file)\}\}/g,
|
|
3468
3472
|
(_, key2) => values[key2] ?? ""
|
|
3469
3473
|
);
|
|
3470
3474
|
findings.push(
|
|
3471
3475
|
finding("opf/contract", {
|
|
3472
|
-
severity:
|
|
3476
|
+
severity: contract2.severity ?? "error",
|
|
3473
3477
|
path,
|
|
3474
3478
|
message,
|
|
3475
3479
|
help: `Use one of the configured values: ${values.allowed}. This is an explicit host policy; no source was changed.`,
|
|
3476
3480
|
definition,
|
|
3477
|
-
suggestions:
|
|
3481
|
+
suggestions: contract2.allowedValues.map((value) => ({
|
|
3478
3482
|
value,
|
|
3479
3483
|
label: String(value),
|
|
3480
3484
|
origin: "contract",
|
|
@@ -3482,14 +3486,14 @@ function contractFindings(document, contracts) {
|
|
|
3482
3486
|
}))
|
|
3483
3487
|
})
|
|
3484
3488
|
);
|
|
3485
|
-
} else if (object(
|
|
3486
|
-
const key2 = pattern[
|
|
3487
|
-
for (const [name, value] of Object.entries(
|
|
3489
|
+
} else if (object(entry2.value) || Array.isArray(entry2.value)) {
|
|
3490
|
+
const key2 = pattern[entry2.depth];
|
|
3491
|
+
for (const [name, value] of Object.entries(entry2.value))
|
|
3488
3492
|
if (key2 === "*" || key2 === name)
|
|
3489
3493
|
stack.push({
|
|
3490
3494
|
value,
|
|
3491
|
-
path: [...
|
|
3492
|
-
depth:
|
|
3495
|
+
path: [...entry2.path, name],
|
|
3496
|
+
depth: entry2.depth + 1
|
|
3493
3497
|
});
|
|
3494
3498
|
}
|
|
3495
3499
|
}
|
|
@@ -3506,10 +3510,10 @@ var OPFPatchError = class extends Error {
|
|
|
3506
3510
|
index;
|
|
3507
3511
|
/** The failing operation, when known. */
|
|
3508
3512
|
operation;
|
|
3509
|
-
constructor(
|
|
3513
|
+
constructor(code2, message, details = {}) {
|
|
3510
3514
|
super(message);
|
|
3511
3515
|
this.name = "OPFPatchError";
|
|
3512
|
-
this.code =
|
|
3516
|
+
this.code = code2;
|
|
3513
3517
|
if (details.path !== void 0) this.path = details.path;
|
|
3514
3518
|
if (details.index !== void 0) this.index = details.index;
|
|
3515
3519
|
if (details.operation !== void 0) this.operation = details.operation;
|
|
@@ -3598,12 +3602,12 @@ function arrayIndex(token, length, allowEnd, path) {
|
|
|
3598
3602
|
if (!Number.isSafeInteger(at2) || at2 > length || !allowEnd && at2 >= length) throw new OPFPatchError("invalid-array-index", `Array index out of bounds: ${token}`, { path });
|
|
3599
3603
|
return at2;
|
|
3600
3604
|
}
|
|
3601
|
-
function normalizePatch(
|
|
3602
|
-
if (!Array.isArray(
|
|
3603
|
-
return
|
|
3605
|
+
function normalizePatch(patch2) {
|
|
3606
|
+
if (!Array.isArray(patch2)) throw new OPFPatchError("invalid-patch", "A JSON Patch must be an array of operations.");
|
|
3607
|
+
return patch2.map((operation, index) => normalizeOperation(operation, index));
|
|
3604
3608
|
}
|
|
3605
3609
|
function normalizeOperation(operation, index) {
|
|
3606
|
-
const fail = (
|
|
3610
|
+
const fail = (code2, message, path) => new OPFPatchError(code2, index === void 0 ? message : `Operation ${index}: ${message}`, { index, operation, path });
|
|
3607
3611
|
if (!isObject(operation)) throw fail("invalid-patch-operation", "Expected an operation object.");
|
|
3608
3612
|
const op = operation.op;
|
|
3609
3613
|
if (typeof op !== "string" || !["add", "remove", "replace", "move", "copy", "test"].includes(op)) {
|
|
@@ -3722,8 +3726,8 @@ function applyOne(document, operation) {
|
|
|
3722
3726
|
}
|
|
3723
3727
|
}
|
|
3724
3728
|
}
|
|
3725
|
-
function applyPatchWithInverse(document,
|
|
3726
|
-
const operations = normalizePatch(
|
|
3729
|
+
function applyPatchWithInverse(document, patch2, options2 = {}) {
|
|
3730
|
+
const operations = normalizePatch(patch2);
|
|
3727
3731
|
let working = clone(document);
|
|
3728
3732
|
const inverse = [];
|
|
3729
3733
|
for (const [index, operation] of operations.entries()) {
|
|
@@ -3740,19 +3744,19 @@ function applyPatchWithInverse(document, patch, options = {}) {
|
|
|
3740
3744
|
}
|
|
3741
3745
|
}
|
|
3742
3746
|
const result = { presentation: working, inverse, patch: operations };
|
|
3743
|
-
if (
|
|
3744
|
-
const validator = typeof
|
|
3747
|
+
if (options2.validate) {
|
|
3748
|
+
const validator = typeof options2.validate === "function" ? options2.validate : (value) => validate(value, { only: options2.strict ? ["format", "references"] : ["format"] });
|
|
3745
3749
|
const validation = validator(working);
|
|
3746
|
-
if (!validation.valid ||
|
|
3750
|
+
if (!validation.valid || options2.strict && validation.findings?.some((entry2) => entry2.severity === "warning")) throw new OPFPatchValidationError(validation);
|
|
3747
3751
|
result.validation = validation;
|
|
3748
3752
|
}
|
|
3749
3753
|
return result;
|
|
3750
3754
|
}
|
|
3751
|
-
function applyPatch(document,
|
|
3752
|
-
return applyPatchWithInverse(document,
|
|
3755
|
+
function applyPatch(document, patch2, options2 = {}) {
|
|
3756
|
+
return applyPatchWithInverse(document, patch2, options2).presentation;
|
|
3753
3757
|
}
|
|
3754
|
-
function invertPatch(document,
|
|
3755
|
-
return applyPatchWithInverse(document,
|
|
3758
|
+
function invertPatch(document, patch2) {
|
|
3759
|
+
return applyPatchWithInverse(document, patch2).inverse;
|
|
3756
3760
|
}
|
|
3757
3761
|
|
|
3758
3762
|
// src/schema-catalog-kinds.ts
|
|
@@ -4018,7 +4022,7 @@ function variantType(variant) {
|
|
|
4018
4022
|
if (Array.isArray(variant.enum) && variant.enum.length) return variant.enum[0] === null ? "null" : typeof variant.enum[0];
|
|
4019
4023
|
return "any";
|
|
4020
4024
|
}
|
|
4021
|
-
var firstParagraph = (
|
|
4025
|
+
var firstParagraph = (text2) => typeof text2 === "string" && text2.trim() ? (text2.trim().split(/\n[ \t]*\n/)[0] ?? "").trim() : void 0;
|
|
4022
4026
|
var definitionOf = (raw) => typeof raw.$ref === "string" ? /#\/\$defs\/([^/]+)$/.exec(raw.$ref)?.[1] : void 0;
|
|
4023
4027
|
function enumValues(variants, inherited) {
|
|
4024
4028
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -4204,14 +4208,14 @@ function catalogKindAt(path, document) {
|
|
|
4204
4208
|
}
|
|
4205
4209
|
|
|
4206
4210
|
// src/source-text.ts
|
|
4207
|
-
function createLocator(
|
|
4211
|
+
function createLocator(text2) {
|
|
4208
4212
|
const lineStarts = [0];
|
|
4209
|
-
for (let index = 0; index <
|
|
4210
|
-
const
|
|
4211
|
-
if (
|
|
4212
|
-
if (
|
|
4213
|
+
for (let index = 0; index < text2.length; index++) {
|
|
4214
|
+
const code2 = text2.charCodeAt(index);
|
|
4215
|
+
if (code2 === 13) {
|
|
4216
|
+
if (text2.charCodeAt(index + 1) === 10) index++;
|
|
4213
4217
|
lineStarts.push(index + 1);
|
|
4214
|
-
} else if (
|
|
4218
|
+
} else if (code2 === 10) lineStarts.push(index + 1);
|
|
4215
4219
|
}
|
|
4216
4220
|
return (offset, length) => {
|
|
4217
4221
|
let low = 0;
|
|
@@ -4224,28 +4228,28 @@ function createLocator(text) {
|
|
|
4224
4228
|
return { offset, length, line: low + 1, column: offset - (lineStarts[low] ?? 0) + 1 };
|
|
4225
4229
|
};
|
|
4226
4230
|
}
|
|
4227
|
-
function applyTextEdits(
|
|
4231
|
+
function applyTextEdits(text2, edits) {
|
|
4228
4232
|
const ordered = edits.map((edit, index) => ({ edit, index })).sort((a, b) => a.edit.offset - b.edit.offset || a.index - b.index);
|
|
4229
4233
|
let result = "";
|
|
4230
4234
|
let cursor = 0;
|
|
4231
4235
|
for (const { edit } of ordered) {
|
|
4232
4236
|
if (edit.offset < cursor) throw new RangeError("Text edits overlap.");
|
|
4233
|
-
result +=
|
|
4237
|
+
result += text2.slice(cursor, edit.offset) + edit.insert;
|
|
4234
4238
|
cursor = edit.offset + edit.length;
|
|
4235
4239
|
}
|
|
4236
|
-
return result +
|
|
4240
|
+
return result + text2.slice(cursor);
|
|
4237
4241
|
}
|
|
4238
4242
|
function rememberByText(read, size = 2) {
|
|
4239
4243
|
const kept = [];
|
|
4240
|
-
return (
|
|
4241
|
-
const at2 = kept.findIndex((
|
|
4244
|
+
return (text2) => {
|
|
4245
|
+
const at2 = kept.findIndex((entry2) => entry2.text === text2);
|
|
4242
4246
|
if (at2 >= 0) {
|
|
4243
|
-
const [
|
|
4244
|
-
kept.unshift(
|
|
4245
|
-
return
|
|
4247
|
+
const [entry2] = kept.splice(at2, 1);
|
|
4248
|
+
kept.unshift(entry2);
|
|
4249
|
+
return entry2.value;
|
|
4246
4250
|
}
|
|
4247
|
-
const value = read(
|
|
4248
|
-
kept.unshift({ text, value });
|
|
4251
|
+
const value = read(text2);
|
|
4252
|
+
kept.unshift({ text: text2, value });
|
|
4249
4253
|
if (kept.length > size) kept.pop();
|
|
4250
4254
|
return value;
|
|
4251
4255
|
};
|
|
@@ -4257,18 +4261,18 @@ var NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/;
|
|
|
4257
4261
|
var TYPOGRAPHIC = /[\u2018-\u201F\u00AB\u00BB]/;
|
|
4258
4262
|
var HEX = /^[0-9a-fA-F]{4}$/;
|
|
4259
4263
|
var isBreak = (char) => char === "\n" || char === "\r";
|
|
4260
|
-
function tokenize(
|
|
4264
|
+
function tokenize(text2) {
|
|
4261
4265
|
const tokens = [];
|
|
4262
4266
|
const lexical = { escapes: /* @__PURE__ */ new Map(), unicode: /* @__PURE__ */ new Map(), controls: /* @__PURE__ */ new Map() };
|
|
4263
|
-
const length =
|
|
4264
|
-
let index =
|
|
4267
|
+
const length = text2.length;
|
|
4268
|
+
let index = text2.charCodeAt(0) === 65279 ? 1 : 0;
|
|
4265
4269
|
const push = (token) => {
|
|
4266
|
-
const full = { ...token, text:
|
|
4270
|
+
const full = { ...token, text: text2.slice(token.start, token.end) };
|
|
4267
4271
|
tokens.push(full);
|
|
4268
4272
|
return full;
|
|
4269
4273
|
};
|
|
4270
4274
|
while (index < length) {
|
|
4271
|
-
const char =
|
|
4275
|
+
const char = text2[index];
|
|
4272
4276
|
if (char === " " || char === " " || char === "\n" || char === "\r") {
|
|
4273
4277
|
index++;
|
|
4274
4278
|
continue;
|
|
@@ -4286,7 +4290,7 @@ function tokenize(text) {
|
|
|
4286
4290
|
const controls = [];
|
|
4287
4291
|
let terminated = false;
|
|
4288
4292
|
while (index < length) {
|
|
4289
|
-
const current =
|
|
4293
|
+
const current = text2[index];
|
|
4290
4294
|
if (current === '"') {
|
|
4291
4295
|
terminated = true;
|
|
4292
4296
|
index++;
|
|
@@ -4294,12 +4298,12 @@ function tokenize(text) {
|
|
|
4294
4298
|
}
|
|
4295
4299
|
if (isBreak(current)) break;
|
|
4296
4300
|
if (current === "\\") {
|
|
4297
|
-
const following =
|
|
4301
|
+
const following = text2[index + 1];
|
|
4298
4302
|
if (following === void 0 || isBreak(following)) {
|
|
4299
4303
|
escapes.push(index);
|
|
4300
4304
|
index++;
|
|
4301
4305
|
} else if (following === "u") {
|
|
4302
|
-
if (HEX.test(
|
|
4306
|
+
if (HEX.test(text2.slice(index + 2, index + 6))) index += 6;
|
|
4303
4307
|
else {
|
|
4304
4308
|
unicode.push(index);
|
|
4305
4309
|
index += 2;
|
|
@@ -4324,9 +4328,9 @@ function tokenize(text) {
|
|
|
4324
4328
|
index++;
|
|
4325
4329
|
let terminated = false;
|
|
4326
4330
|
while (index < length) {
|
|
4327
|
-
const current =
|
|
4331
|
+
const current = text2[index];
|
|
4328
4332
|
if (isBreak(current)) break;
|
|
4329
|
-
if (current === "\\" && index + 1 < length && !isBreak(
|
|
4333
|
+
if (current === "\\" && index + 1 < length && !isBreak(text2[index + 1])) {
|
|
4330
4334
|
index += 2;
|
|
4331
4335
|
continue;
|
|
4332
4336
|
}
|
|
@@ -4343,7 +4347,7 @@ function tokenize(text) {
|
|
|
4343
4347
|
index++;
|
|
4344
4348
|
let terminated = false;
|
|
4345
4349
|
while (index < length) {
|
|
4346
|
-
const current =
|
|
4350
|
+
const current = text2[index];
|
|
4347
4351
|
if (isBreak(current)) break;
|
|
4348
4352
|
index++;
|
|
4349
4353
|
if (current === '"' || TYPOGRAPHIC.test(current)) {
|
|
@@ -4354,35 +4358,35 @@ function tokenize(text) {
|
|
|
4354
4358
|
push({ kind: "string", start, end: index, quote: "typographic", terminated });
|
|
4355
4359
|
continue;
|
|
4356
4360
|
}
|
|
4357
|
-
if (char === "/" &&
|
|
4358
|
-
while (index < length && !isBreak(
|
|
4361
|
+
if (char === "/" && text2[index + 1] === "/") {
|
|
4362
|
+
while (index < length && !isBreak(text2[index])) index++;
|
|
4359
4363
|
push({ kind: "comment", start, end: index });
|
|
4360
4364
|
continue;
|
|
4361
4365
|
}
|
|
4362
|
-
if (char === "/" &&
|
|
4363
|
-
const close =
|
|
4366
|
+
if (char === "/" && text2[index + 1] === "*") {
|
|
4367
|
+
const close = text2.indexOf("*/", index + 2);
|
|
4364
4368
|
index = close < 0 ? length : close + 2;
|
|
4365
4369
|
push({ kind: "comment", start, end: index, block: true, terminated: close >= 0 });
|
|
4366
4370
|
continue;
|
|
4367
4371
|
}
|
|
4368
|
-
if (WORD.test(String.fromCodePoint(
|
|
4372
|
+
if (WORD.test(String.fromCodePoint(text2.codePointAt(index) ?? 0))) {
|
|
4369
4373
|
while (index < length) {
|
|
4370
|
-
const
|
|
4371
|
-
if (!WORD.test(String.fromCodePoint(
|
|
4372
|
-
index +=
|
|
4374
|
+
const code2 = text2.codePointAt(index) ?? 0;
|
|
4375
|
+
if (!WORD.test(String.fromCodePoint(code2))) break;
|
|
4376
|
+
index += code2 > 65535 ? 2 : 1;
|
|
4373
4377
|
}
|
|
4374
|
-
const lexeme =
|
|
4378
|
+
const lexeme = text2.slice(start, index);
|
|
4375
4379
|
const kind = NUMBER.test(lexeme) ? "number" : lexeme === "true" || lexeme === "false" || lexeme === "null" ? "literal" : /^[+\-.]?[0-9]|^[+-]$/.test(lexeme) ? "badnumber" : "word";
|
|
4376
4380
|
push({ kind, start, end: index });
|
|
4377
4381
|
continue;
|
|
4378
4382
|
}
|
|
4379
4383
|
while (index < length) {
|
|
4380
|
-
const
|
|
4381
|
-
const next = String.fromCodePoint(
|
|
4384
|
+
const code2 = text2.codePointAt(index) ?? 0;
|
|
4385
|
+
const next = String.fromCodePoint(code2);
|
|
4382
4386
|
if (/[\s{}[\],:"'/]/.test(next) || TYPOGRAPHIC.test(next) || WORD.test(next)) break;
|
|
4383
|
-
index +=
|
|
4387
|
+
index += code2 > 65535 ? 2 : 1;
|
|
4384
4388
|
}
|
|
4385
|
-
if (index === start) index += (
|
|
4389
|
+
if (index === start) index += (text2.codePointAt(index) ?? 0) > 65535 ? 2 : 1;
|
|
4386
4390
|
push({ kind: "unknown", start, end: index });
|
|
4387
4391
|
}
|
|
4388
4392
|
push({ kind: "eof", start: length, end: length });
|
|
@@ -4393,14 +4397,14 @@ function clip(value, max = 40) {
|
|
|
4393
4397
|
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
4394
4398
|
return collapsed.length > max ? `${collapsed.slice(0, max - 1)}\u2026` : collapsed;
|
|
4395
4399
|
}
|
|
4396
|
-
function lineBounds(
|
|
4400
|
+
function lineBounds(text2, offset) {
|
|
4397
4401
|
let start = offset;
|
|
4398
|
-
while (start > 0 && !isBreak(
|
|
4402
|
+
while (start > 0 && !isBreak(text2[start - 1])) start--;
|
|
4399
4403
|
let end = offset;
|
|
4400
|
-
while (end <
|
|
4404
|
+
while (end < text2.length && !isBreak(text2[end])) end++;
|
|
4401
4405
|
return { start, end };
|
|
4402
4406
|
}
|
|
4403
|
-
var indentOf = (
|
|
4407
|
+
var indentOf = (text2, offset) => /^[ \t]*/.exec(text2.slice(lineBounds(text2, offset).start))?.[0] ?? "";
|
|
4404
4408
|
function unquoteSingle(raw) {
|
|
4405
4409
|
const inner = raw.endsWith("'") && raw.length > 1 ? raw.slice(1, -1) : raw.slice(1);
|
|
4406
4410
|
let result = "";
|
|
@@ -4421,13 +4425,13 @@ var YAML_START = /^[A-Za-z_][\w.-]*\s*:(?:\s|$)/;
|
|
|
4421
4425
|
var MINIMAL_DECK = '{\n "slides": [\n { "title": "" }\n ]\n}\n';
|
|
4422
4426
|
var HINT_FORMS_LIMIT = 2;
|
|
4423
4427
|
var Analyzer = class {
|
|
4424
|
-
constructor(
|
|
4425
|
-
this.text =
|
|
4426
|
-
const lexed = tokenize(
|
|
4428
|
+
constructor(text2) {
|
|
4429
|
+
this.text = text2;
|
|
4430
|
+
const lexed = tokenize(text2);
|
|
4427
4431
|
this.comments = lexed.tokens.filter((token) => token.kind === "comment");
|
|
4428
4432
|
this.tokens = lexed.tokens.filter((token) => token.kind !== "comment");
|
|
4429
4433
|
this.lexical = lexed.lexical;
|
|
4430
|
-
this.locate = createLocator(
|
|
4434
|
+
this.locate = createLocator(text2);
|
|
4431
4435
|
}
|
|
4432
4436
|
text;
|
|
4433
4437
|
problems = [];
|
|
@@ -4622,7 +4626,7 @@ var Analyzer = class {
|
|
|
4622
4626
|
return { offset: start, length: end - start, insert: "" };
|
|
4623
4627
|
}
|
|
4624
4628
|
stringProblems(token) {
|
|
4625
|
-
const { text } = this;
|
|
4629
|
+
const { text: text2 } = this;
|
|
4626
4630
|
if (token.quote === "single") {
|
|
4627
4631
|
this.add({
|
|
4628
4632
|
code: "single-quotes",
|
|
@@ -4649,7 +4653,7 @@ var Analyzer = class {
|
|
|
4649
4653
|
const unicode = this.lexical.unicode.get(token);
|
|
4650
4654
|
const controls = this.lexical.controls.get(token);
|
|
4651
4655
|
if (token.terminated === false) {
|
|
4652
|
-
const lineEnd = token.start + clipTrailing(
|
|
4656
|
+
const lineEnd = token.start + clipTrailing(text2.slice(token.start, token.end)).length;
|
|
4653
4657
|
this.add({
|
|
4654
4658
|
code: "unclosed-string",
|
|
4655
4659
|
range: this.range(token.start, lineEnd),
|
|
@@ -4660,7 +4664,7 @@ var Analyzer = class {
|
|
|
4660
4664
|
}
|
|
4661
4665
|
if (escapes?.length) {
|
|
4662
4666
|
const first = escapes[0];
|
|
4663
|
-
const shown =
|
|
4667
|
+
const shown = text2.slice(first, first + 2);
|
|
4664
4668
|
this.add({
|
|
4665
4669
|
code: "invalid-escape",
|
|
4666
4670
|
range: this.range(first, Math.min(token.end, first + 2)),
|
|
@@ -4692,9 +4696,9 @@ var Analyzer = class {
|
|
|
4692
4696
|
title: "Replace with escape",
|
|
4693
4697
|
safe: true,
|
|
4694
4698
|
edits: controls.map((offset) => {
|
|
4695
|
-
const
|
|
4699
|
+
const code2 = text2.charCodeAt(offset);
|
|
4696
4700
|
const named = { 8: "\\b", 9: "\\t", 12: "\\f" };
|
|
4697
|
-
return { offset, length: 1, insert: named[
|
|
4701
|
+
return { offset, length: 1, insert: named[code2] ?? `\\u${code2.toString(16).padStart(4, "0")}` };
|
|
4698
4702
|
})
|
|
4699
4703
|
}
|
|
4700
4704
|
]
|
|
@@ -4963,10 +4967,10 @@ var Analyzer = class {
|
|
|
4963
4967
|
}
|
|
4964
4968
|
// -- structural problems --
|
|
4965
4969
|
missingComma(valueEnd, previous) {
|
|
4966
|
-
const
|
|
4970
|
+
const line2 = this.line(Math.max(valueEnd - 1, 0));
|
|
4967
4971
|
return {
|
|
4968
|
-
message: `Missing comma after ${previous ? `\`${previous.text}\`` : "the previous value"} (line ${
|
|
4969
|
-
next: `add \`,\` right after it, on line ${
|
|
4972
|
+
message: `Missing comma after ${previous ? `\`${previous.text}\`` : "the previous value"} (line ${line2}).`,
|
|
4973
|
+
next: `add \`,\` right after it, on line ${line2}.`,
|
|
4970
4974
|
fixes: [{ id: "insert-comma", title: "Insert comma", safe: true, edits: [{ offset: valueEnd, length: 0, insert: "," }], caret: valueEnd + 1 }]
|
|
4971
4975
|
};
|
|
4972
4976
|
}
|
|
@@ -5049,7 +5053,7 @@ var Analyzer = class {
|
|
|
5049
5053
|
}
|
|
5050
5054
|
/** The edits that remove an entry whose value is missing (key through colon), and the comma that belonged to it. */
|
|
5051
5055
|
removeEntry(start, end) {
|
|
5052
|
-
const { text } = this;
|
|
5056
|
+
const { text: text2 } = this;
|
|
5053
5057
|
const firstFrom = (offset) => {
|
|
5054
5058
|
let low = 0;
|
|
5055
5059
|
let high = this.tokens.length;
|
|
@@ -5070,18 +5074,18 @@ var Analyzer = class {
|
|
|
5070
5074
|
let to = end;
|
|
5071
5075
|
if (first?.kind === "comma") to = first.end;
|
|
5072
5076
|
else if (!entryFollows(second) && before?.kind === "comma") from = before.start;
|
|
5073
|
-
const alone = from === start && /^[ \t]*$/.test(
|
|
5077
|
+
const alone = from === start && /^[ \t]*$/.test(text2.slice(lineBounds(text2, start).start, start)) && /^[ \t]*$/.test(text2.slice(to, lineBounds(text2, Math.max(to - 1, 0)).end));
|
|
5074
5078
|
if (alone) {
|
|
5075
|
-
const bounds = lineBounds(
|
|
5076
|
-
let lineEnd = lineBounds(
|
|
5077
|
-
if (
|
|
5078
|
-
else if (lineEnd <
|
|
5079
|
+
const bounds = lineBounds(text2, start);
|
|
5080
|
+
let lineEnd = lineBounds(text2, Math.max(to - 1, 0)).end;
|
|
5081
|
+
if (text2[lineEnd] === "\r" && text2[lineEnd + 1] === "\n") lineEnd += 2;
|
|
5082
|
+
else if (lineEnd < text2.length) lineEnd++;
|
|
5079
5083
|
return [{ offset: bounds.start, length: lineEnd - bounds.start, insert: "" }];
|
|
5080
5084
|
}
|
|
5081
5085
|
return [{ offset: from, length: to - from, insert: "" }];
|
|
5082
5086
|
}
|
|
5083
5087
|
unclosed(container) {
|
|
5084
|
-
const { text } = this;
|
|
5088
|
+
const { text: text2 } = this;
|
|
5085
5089
|
const closer = container.type === "{" ? "}" : "]";
|
|
5086
5090
|
const open = container.open;
|
|
5087
5091
|
const last = this.last && this.last.start >= open.start ? this.last : open;
|
|
@@ -5094,20 +5098,20 @@ var Analyzer = class {
|
|
|
5094
5098
|
const removed = dangling ? last.end - last.start : 0;
|
|
5095
5099
|
const sameLine = this.line(open.start) === this.line(Math.max(last.end - 1, 0));
|
|
5096
5100
|
const own4 = sameLine ? closer : `
|
|
5097
|
-
${indentOf(
|
|
5101
|
+
${indentOf(text2, open.start)}${closer}`;
|
|
5098
5102
|
const inner = this.cascade.last === this.last && removed === 0 ? this.cascade.inserts : [];
|
|
5099
5103
|
const insert = inner.join("") + own4;
|
|
5100
5104
|
this.cascade = { last: this.last, inserts: [...inner, own4] };
|
|
5101
5105
|
const closers = insert.replace(/\s/g, "");
|
|
5102
5106
|
const key2 = container.path.at(-1);
|
|
5103
|
-
const
|
|
5104
|
-
const context = container.type === "{" ? firstEntryOf(
|
|
5107
|
+
const entry2 = typeof key2 === "string" ? `"${key2}": ${open.text}` : open.text;
|
|
5108
|
+
const context = container.type === "{" ? firstEntryOf(text2, open) : void 0;
|
|
5105
5109
|
const what = container.type === "{" ? "property" : "item";
|
|
5106
5110
|
this.add({
|
|
5107
5111
|
code: container.type === "{" ? "unclosed-object" : "unclosed-array",
|
|
5108
5112
|
range: this.range(open.start, open.end),
|
|
5109
5113
|
path: [...container.path],
|
|
5110
|
-
message: container.type === "{" ? `The \`${
|
|
5114
|
+
message: container.type === "{" ? `The \`${entry2}\` on line ${this.line(open.start)}${context ? ` (starting \`${context}\`)` : ""} is never closed.` : `The list \`${entry2}\` on line ${this.line(open.start)} is never closed.`,
|
|
5111
5115
|
next: `add \`${closer}\` after its last ${what}${sameLine ? "" : ", on its own line"}.`,
|
|
5112
5116
|
fixes: [{ id: "insert-closer", title: `Insert ${closers}`, safe: false, edits: [{ offset: insertAt, length: removed, insert }], caret: insertAt + insert.length }]
|
|
5113
5117
|
});
|
|
@@ -5145,22 +5149,22 @@ function keyName(token) {
|
|
|
5145
5149
|
function clipTrailing(value) {
|
|
5146
5150
|
return value.replace(/[ \t]+$/, "");
|
|
5147
5151
|
}
|
|
5148
|
-
function entryText(
|
|
5149
|
-
const raw =
|
|
5152
|
+
function entryText(text2, start, end, kind) {
|
|
5153
|
+
const raw = text2.slice(start, end);
|
|
5150
5154
|
if (kind === "object" || kind === "array") {
|
|
5151
5155
|
const head = raw.slice(0, raw.indexOf(kind === "object" ? "{" : "["));
|
|
5152
5156
|
return clip(`${head}${kind === "object" ? "{\u2026}" : "[\u2026]"}`, 40);
|
|
5153
5157
|
}
|
|
5154
5158
|
return clip(raw, 40);
|
|
5155
5159
|
}
|
|
5156
|
-
function firstEntryOf(
|
|
5157
|
-
const rest =
|
|
5158
|
-
const
|
|
5159
|
-
return
|
|
5160
|
+
function firstEntryOf(text2, open) {
|
|
5161
|
+
const rest = text2.slice(open.end, open.end + 200);
|
|
5162
|
+
const match2 = /^\s*("(?:[^"\\\n]|\\.)*"\s*:\s*(?:"(?:[^"\\\n]|\\.)*"|[^\s,{}[\]]+))/.exec(rest);
|
|
5163
|
+
return match2?.[1] ? clip(match2[1], 36) : void 0;
|
|
5160
5164
|
}
|
|
5161
5165
|
function jsonShort(value) {
|
|
5162
|
-
const
|
|
5163
|
-
return
|
|
5166
|
+
const text2 = JSON.stringify(value);
|
|
5167
|
+
return text2 !== void 0 && text2.length <= 32 ? text2 : void 0;
|
|
5164
5168
|
}
|
|
5165
5169
|
function exampleOf(info) {
|
|
5166
5170
|
const example = info.examples.map(jsonShort).find((item) => item !== void 0) ?? (info.enum?.[0] ? jsonShort(info.enum[0].value) : void 0);
|
|
@@ -5169,7 +5173,7 @@ function exampleOf(info) {
|
|
|
5169
5173
|
return type === "boolean" ? "true" : type === "number" || type === "integer" ? "1" : type === "object" ? "{ \u2026 }" : type === "array" ? "[ \u2026 ]" : '"\u2026"';
|
|
5170
5174
|
}
|
|
5171
5175
|
function listValues(values) {
|
|
5172
|
-
const shown = values.slice(0, 5).map((
|
|
5176
|
+
const shown = values.slice(0, 5).map((entry2) => String(entry2.value));
|
|
5173
5177
|
return values.length > 5 ? `one of ${values.length} values (${shown.slice(0, 3).join(", ")}, \u2026)` : `one of ${shown.join(", ")}`;
|
|
5174
5178
|
}
|
|
5175
5179
|
function joinAlternatives(items) {
|
|
@@ -5253,16 +5257,16 @@ function valueHint(path) {
|
|
|
5253
5257
|
}
|
|
5254
5258
|
return { next, title, insert, caret };
|
|
5255
5259
|
}
|
|
5256
|
-
function analyzeJsonSyntax(
|
|
5257
|
-
const limit = Math.max(0,
|
|
5258
|
-
const analyzer = new Analyzer(String(
|
|
5260
|
+
function analyzeJsonSyntax(text2, options2 = {}) {
|
|
5261
|
+
const limit = Math.max(0, options2.limit ?? 5);
|
|
5262
|
+
const analyzer = new Analyzer(String(text2 ?? ""));
|
|
5259
5263
|
try {
|
|
5260
5264
|
analyzer.run();
|
|
5261
5265
|
} catch {
|
|
5262
5266
|
return [];
|
|
5263
5267
|
}
|
|
5264
5268
|
const seen = /* @__PURE__ */ new Set();
|
|
5265
|
-
return analyzer.problems.sort((a, b) => a.problem.range.offset - b.problem.range.offset || a.order - b.order).map((
|
|
5269
|
+
return analyzer.problems.sort((a, b) => a.problem.range.offset - b.problem.range.offset || a.order - b.order).map((entry2) => entry2.problem).filter((problem) => {
|
|
5266
5270
|
const id = `${problem.code}:${problem.range.offset}`;
|
|
5267
5271
|
if (seen.has(id)) return false;
|
|
5268
5272
|
seen.add(id);
|
|
@@ -5353,16 +5357,1613 @@ function parseSource(source) {
|
|
|
5353
5357
|
};
|
|
5354
5358
|
}
|
|
5355
5359
|
|
|
5360
|
+
// src/finding-text-util.ts
|
|
5361
|
+
var WORDS = { object: "an object", array: "a list", string: "text", number: "a number", integer: "a whole number", boolean: "true or false", null: "null" };
|
|
5362
|
+
var KIND_WORDS = { layouts: "layout", themes: "theme", colorSchemes: "color scheme", fontSchemes: "font scheme", narratives: "narrative", audiences: "audience", purposes: "purpose", tones: "tone", languages: "language" };
|
|
5363
|
+
var KIND_EXAMPLES = { layouts: "`pillars` or `acme:hero`", themes: "`minimal` or `acme:bold`", colorSchemes: "`boost` or `acme:night`", fontSchemes: "`aptos` or `acme:brand`", narratives: "`classic-story`", audiences: "`board`", purposes: "`inform`", tones: "`formal`" };
|
|
5364
|
+
var ALIASES = { middle: "center", centre: "center", centered: "center", centred: "center", justify: "left", col: "column", cols: "column", pic: "image", picture: "image", photo: "image", paragraph: "text", bullets: "list", bullet: "list" };
|
|
5365
|
+
function segmentsOf(path) {
|
|
5366
|
+
if (Array.isArray(path)) return path.map(String);
|
|
5367
|
+
if (typeof path !== "string" || path === "" || path === "/") return [];
|
|
5368
|
+
return path.replace(/^\//, "").split("/").map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
5369
|
+
}
|
|
5370
|
+
function pointerOf(segments) {
|
|
5371
|
+
return segments.map((part) => `/${String(part).replaceAll("~", "~0").replaceAll("/", "~1")}`).join("");
|
|
5372
|
+
}
|
|
5373
|
+
var code = (value) => `\`${String(value)}\``;
|
|
5374
|
+
var isIndex = (segment) => /^\d+$/.test(String(segment));
|
|
5375
|
+
function cap(text2) {
|
|
5376
|
+
return text2.startsWith("`") ? text2 : text2.charAt(0).toUpperCase() + text2.slice(1);
|
|
5377
|
+
}
|
|
5378
|
+
function fieldLabel(segments) {
|
|
5379
|
+
if (!segments.length) return "the deck";
|
|
5380
|
+
const last = segments[segments.length - 1];
|
|
5381
|
+
if (isIndex(last)) {
|
|
5382
|
+
const owner = [...segments].reverse().find((segment) => !isIndex(segment));
|
|
5383
|
+
return owner === "slides" ? `slide ${Number(last) + 1}` : owner ? `${owner} entry ${Number(last) + 1}` : `entry ${Number(last) + 1}`;
|
|
5384
|
+
}
|
|
5385
|
+
return code(last);
|
|
5386
|
+
}
|
|
5387
|
+
function containerName(segments) {
|
|
5388
|
+
if (!segments.length) return "the deck";
|
|
5389
|
+
if (segments.length === 2 && segments[0] === "slides" && isIndex(segments[1])) return "a slide";
|
|
5390
|
+
const last = segments[segments.length - 1];
|
|
5391
|
+
return isIndex(last) ? fieldLabel(segments) : code(last);
|
|
5392
|
+
}
|
|
5393
|
+
function describeValue(value) {
|
|
5394
|
+
if (value === void 0) return "missing";
|
|
5395
|
+
if (value === null) return "null";
|
|
5396
|
+
if (Array.isArray(value)) return value.length ? `a list of ${value.length}` : "an empty list";
|
|
5397
|
+
if (typeof value === "object") return "an object";
|
|
5398
|
+
if (typeof value === "string") return value === "" ? "empty" : code(value.length > 30 ? `${value.slice(0, 29)}\u2026` : value);
|
|
5399
|
+
return `${typeof value === "number" ? "a number" : "true or false"} (${code(String(value))})`;
|
|
5400
|
+
}
|
|
5401
|
+
function describeKind(value) {
|
|
5402
|
+
if (typeof value === "string") return value === "" ? "empty text" : `text (${describeValue(value)})`;
|
|
5403
|
+
return describeValue(value);
|
|
5404
|
+
}
|
|
5405
|
+
function typeWords(type) {
|
|
5406
|
+
const list = (Array.isArray(type) ? type : [type]).filter(Boolean).map((entry2) => WORDS[String(entry2)] ?? String(entry2));
|
|
5407
|
+
return [...new Set(list)].join(" or ") || "a different kind of value";
|
|
5408
|
+
}
|
|
5409
|
+
function distance2(a, b) {
|
|
5410
|
+
const x = a.toLowerCase();
|
|
5411
|
+
const y = b.toLowerCase();
|
|
5412
|
+
if (x === y) return 0;
|
|
5413
|
+
const row = Array.from({ length: y.length + 1 }, (_, index) => index);
|
|
5414
|
+
for (let i = 1; i <= x.length; i++) {
|
|
5415
|
+
let previous = row[0];
|
|
5416
|
+
row[0] = i;
|
|
5417
|
+
for (let j = 1; j <= y.length; j++) {
|
|
5418
|
+
const keep3 = row[j];
|
|
5419
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (x[i - 1] === y[j - 1] ? 0 : 1));
|
|
5420
|
+
previous = keep3;
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
return row[y.length];
|
|
5424
|
+
}
|
|
5425
|
+
function nearest(value, candidates, limit = 5) {
|
|
5426
|
+
const text2 = String(value ?? "");
|
|
5427
|
+
const alias = ALIASES[text2.toLowerCase()];
|
|
5428
|
+
const ranked = candidates.map((candidate) => ({ candidate, score: String(candidate).toLowerCase() === alias ? 0 : distance2(text2, String(candidate)) }));
|
|
5429
|
+
ranked.sort((a, b) => a.score - b.score);
|
|
5430
|
+
const close = ranked.filter((entry2) => entry2.score <= Math.max(2, Math.floor(text2.length / 4))).map((entry2) => entry2.candidate);
|
|
5431
|
+
return { close: close.slice(0, limit), all: ranked.map((entry2) => entry2.candidate) };
|
|
5432
|
+
}
|
|
5433
|
+
function valueList(values, max = 5) {
|
|
5434
|
+
const list = values.map((value) => typeof value === "string" ? value : JSON.stringify(value));
|
|
5435
|
+
if (list.length <= max) return list.length > 1 ? `${list.slice(0, -1).join(", ")} or ${list.at(-1)}` : list.join("");
|
|
5436
|
+
return `one of ${list.length} values (${list.slice(0, 3).join(", ")}, \u2026)`;
|
|
5437
|
+
}
|
|
5438
|
+
var firstSentence = (text2) => /^.*?[.!?](?=\s|$)/.exec(String(text2 ?? "").replace(/\s+/g, " ").trim())?.[0] ?? "";
|
|
5439
|
+
function asNext(text2) {
|
|
5440
|
+
const trimmed = text2.trim();
|
|
5441
|
+
return /^[A-Z][a-z]/.test(trimmed) ? trimmed.charAt(0).toLowerCase() + trimmed.slice(1) : trimmed;
|
|
5442
|
+
}
|
|
5443
|
+
var isRecord2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5444
|
+
function readAt(document, segments) {
|
|
5445
|
+
let value = document;
|
|
5446
|
+
for (const segment of segments) {
|
|
5447
|
+
if (Array.isArray(value)) value = value[Number(segment)];
|
|
5448
|
+
else if (isRecord2(value)) value = value[segment];
|
|
5449
|
+
else return void 0;
|
|
5450
|
+
}
|
|
5451
|
+
return value;
|
|
5452
|
+
}
|
|
5453
|
+
|
|
5454
|
+
// src/finding-text-helpers.ts
|
|
5455
|
+
var ENDS = /[.?!]$/;
|
|
5456
|
+
function sentence(text2) {
|
|
5457
|
+
const trimmed = text2.trim();
|
|
5458
|
+
return ENDS.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
5459
|
+
}
|
|
5460
|
+
function us(text2) {
|
|
5461
|
+
return text2.replace(/colour/g, "color").replace(/Colour/g, "Color");
|
|
5462
|
+
}
|
|
5463
|
+
var keep = (text2) => sentence(us(text2));
|
|
5464
|
+
function match(text2, regex) {
|
|
5465
|
+
return regex.exec(text2) ?? void 0;
|
|
5466
|
+
}
|
|
5467
|
+
function unquote(text2) {
|
|
5468
|
+
if (text2 === void 0) return "";
|
|
5469
|
+
try {
|
|
5470
|
+
const value = JSON.parse(text2);
|
|
5471
|
+
if (typeof value === "string") return value;
|
|
5472
|
+
} catch {
|
|
5473
|
+
}
|
|
5474
|
+
return text2.replace(/^['"]|['"]$/g, "");
|
|
5475
|
+
}
|
|
5476
|
+
function unquoteList(text2) {
|
|
5477
|
+
if (text2 === void 0) return [];
|
|
5478
|
+
try {
|
|
5479
|
+
const value = JSON.parse(`[${text2}]`);
|
|
5480
|
+
if (Array.isArray(value) && value.every((entry2) => typeof entry2 === "string")) return value;
|
|
5481
|
+
} catch {
|
|
5482
|
+
}
|
|
5483
|
+
return text2.split(/,\s*/).map((entry2) => entry2.replace(/^['"]|['"]$/g, "").trim()).filter((entry2) => entry2 && entry2 !== "...");
|
|
5484
|
+
}
|
|
5485
|
+
function slideNumber(finding2) {
|
|
5486
|
+
if (finding2.slide !== void 0) return finding2.slide + 1;
|
|
5487
|
+
const found = /^\/slides\/(\d+)(?:\/|$)/.exec(finding2.path);
|
|
5488
|
+
return found ? Number(found[1]) + 1 : void 0;
|
|
5489
|
+
}
|
|
5490
|
+
var slideWord = (finding2) => {
|
|
5491
|
+
const number = slideNumber(finding2);
|
|
5492
|
+
return number === void 0 ? void 0 : `slide ${number}`;
|
|
5493
|
+
};
|
|
5494
|
+
var onSlide = (finding2) => {
|
|
5495
|
+
const word = slideWord(finding2);
|
|
5496
|
+
return word ? ` on ${word}` : "";
|
|
5497
|
+
};
|
|
5498
|
+
function slideRef(finding2, helpers) {
|
|
5499
|
+
const number = slideNumber(finding2);
|
|
5500
|
+
if (number === void 0) return "The deck";
|
|
5501
|
+
const id = finding2.slideId ?? helpers.valueAt(["slides", String(number - 1), "id"]);
|
|
5502
|
+
if (typeof id === "string" && id) return `Slide ${number} (${code(id)})`;
|
|
5503
|
+
const layout = helpers.valueAt(["slides", String(number - 1), "layout"]);
|
|
5504
|
+
if (typeof layout === "string" && layout) return `Slide ${number} (layout ${code(layout)})`;
|
|
5505
|
+
return `Slide ${number}`;
|
|
5506
|
+
}
|
|
5507
|
+
var lastName = (segments) => [...segments].reverse().find((segment) => !isIndex(segment));
|
|
5508
|
+
function dotted(segments) {
|
|
5509
|
+
return segments.reduce((out, segment) => isIndex(segment) ? `${out}[${segment}]` : out ? `${out}.${segment}` : segment, "");
|
|
5510
|
+
}
|
|
5511
|
+
function keysAt(helpers, path) {
|
|
5512
|
+
const value = helpers.valueAt(path);
|
|
5513
|
+
return isRecord2(value) ? Object.keys(value) : [];
|
|
5514
|
+
}
|
|
5515
|
+
function idsAt(helpers, path) {
|
|
5516
|
+
const value = helpers.valueAt(path);
|
|
5517
|
+
return Array.isArray(value) ? value.flatMap((entry2) => isRecord2(entry2) && typeof entry2.id === "string" ? [entry2.id] : []) : [];
|
|
5518
|
+
}
|
|
5519
|
+
function codeList(names2, max = 5, joiner = "and") {
|
|
5520
|
+
const shown = names2.slice(0, max).map(code);
|
|
5521
|
+
const rest = names2.length - shown.length;
|
|
5522
|
+
if (rest > 0) return `${shown.join(", ")} and ${rest} more`;
|
|
5523
|
+
return shown.length > 1 ? `${shown.slice(0, -1).join(", ")} ${joiner} ${shown.at(-1)}` : shown.join("");
|
|
5524
|
+
}
|
|
5525
|
+
function plainList(names2, max = 6) {
|
|
5526
|
+
const shown = names2.slice(0, max);
|
|
5527
|
+
const rest = names2.length - shown.length;
|
|
5528
|
+
return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
|
|
5529
|
+
}
|
|
5530
|
+
function andList(names2, max = 6) {
|
|
5531
|
+
const shown = names2.slice(0, max);
|
|
5532
|
+
const rest = names2.length - shown.length;
|
|
5533
|
+
if (rest > 0) return `${shown.join(", ")} and ${rest} more`;
|
|
5534
|
+
return shown.length > 1 ? `${shown.slice(0, -1).join(", ")} and ${shown.at(-1)}` : shown.join("");
|
|
5535
|
+
}
|
|
5536
|
+
function chooseNext(value, candidates, alternative = "", max = 3) {
|
|
5537
|
+
const { close } = nearest(value, candidates);
|
|
5538
|
+
const or = alternative ? ` Or ${alternative}.` : "";
|
|
5539
|
+
if (close.length) return `did you mean ${code(close[0])}?${or}`;
|
|
5540
|
+
if (candidates.length === 1) return `use ${code(candidates[0])}${alternative ? `, or ${alternative}` : ""}.`;
|
|
5541
|
+
if (candidates.length) return `pick one of ${codeList(candidates, max, "or")}${alternative ? `, or ${alternative}` : ""}.`;
|
|
5542
|
+
return alternative ? sentence(alternative) : "choose a different value.";
|
|
5543
|
+
}
|
|
5544
|
+
function kindPhrase(kind) {
|
|
5545
|
+
const phrases = { text: "text", code: "code", list: "a list", chart: "a chart", table: "a table", image: "an image", video: "a video", metric: "a metric", quote: "a quote", timeline: "a timeline", group: "a group" };
|
|
5546
|
+
return phrases[kind] ?? `${/^[aeiou]/i.test(kind) ? "an" : "a"} ${kind}`;
|
|
5547
|
+
}
|
|
5548
|
+
function freshName(base, taken = []) {
|
|
5549
|
+
const space = /\s/.test(base);
|
|
5550
|
+
for (let n = 2; n < 100; n++) {
|
|
5551
|
+
const candidate = space ? `${base} ${n}` : `${base}-${n}`;
|
|
5552
|
+
if (!taken.includes(candidate)) return candidate;
|
|
5553
|
+
}
|
|
5554
|
+
return `${base}-2`;
|
|
5555
|
+
}
|
|
5556
|
+
var BLOCK_KINDS = ["chart", "image", "video", "table", "code", "metric", "quote", "timeline", "items", "bullets", "text"];
|
|
5557
|
+
function blockKind(node) {
|
|
5558
|
+
if (!isRecord2(node)) return void 0;
|
|
5559
|
+
if (typeof node.type === "string" && node.type !== "group") return node.type;
|
|
5560
|
+
const found = BLOCK_KINDS.find((key2) => node[key2] !== void 0);
|
|
5561
|
+
if (found === "items" || found === "bullets") return "list";
|
|
5562
|
+
return found;
|
|
5563
|
+
}
|
|
5564
|
+
function helpAsNext(finding2, fallback) {
|
|
5565
|
+
const help = finding2.help?.trim();
|
|
5566
|
+
return help ? sentence(asNext(us(help))) : fallback;
|
|
5567
|
+
}
|
|
5568
|
+
var isText2 = (value) => typeof value === "string" && value !== "";
|
|
5569
|
+
function brief(text2, max = 40) {
|
|
5570
|
+
const flat = text2.replace(/\s+/g, " ").trim();
|
|
5571
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
5572
|
+
}
|
|
5573
|
+
|
|
5574
|
+
// src/finding-text-audit.ts
|
|
5575
|
+
var measuredNumber = (finding2, key2) => {
|
|
5576
|
+
const value = finding2.measured?.[key2];
|
|
5577
|
+
return typeof value === "number" ? value : void 0;
|
|
5578
|
+
};
|
|
5579
|
+
var measuredText = (finding2, key2) => {
|
|
5580
|
+
const value = finding2.measured?.[key2];
|
|
5581
|
+
return typeof value === "string" ? value : void 0;
|
|
5582
|
+
};
|
|
5583
|
+
var num = (value, digits = 2) => String(Math.round(value * 10 ** digits) / 10 ** digits);
|
|
5584
|
+
var textContrast = (finding2) => {
|
|
5585
|
+
const ratio = measuredNumber(finding2, "ratio");
|
|
5586
|
+
const needed = measuredNumber(finding2, "needed");
|
|
5587
|
+
const foreground = measuredText(finding2, "textColor");
|
|
5588
|
+
const background = measuredText(finding2, "backgroundColor");
|
|
5589
|
+
if (ratio === void 0 || needed === void 0) return void 0;
|
|
5590
|
+
const label = match(finding2.message, /^(.+?) colou?r /)?.[1];
|
|
5591
|
+
const subject = label && !/^(Text|Colou?red text)$/.test(label) ? us(label) : "Text";
|
|
5592
|
+
const colors = foreground && background ? ` ${code(foreground)} on ${code(background)}` : "";
|
|
5593
|
+
const large = finding2.measured?.large === true ? " for large text" : finding2.measured?.large === false ? " for normal-size text" : "";
|
|
5594
|
+
const also = measuredNumber(finding2, "alsoAffected");
|
|
5595
|
+
const fix = finding2.fixes?.find((entry2) => entry2.id === "use-readable-color");
|
|
5596
|
+
const replacement = fix?.kind === "patch" && fix.patch?.[0] && "value" in fix.patch[0] ? fix.patch[0].value : void 0;
|
|
5597
|
+
const overrides = finding2.path.endsWith("/color");
|
|
5598
|
+
return {
|
|
5599
|
+
message: `${subject}${colors} has contrast ${num(ratio)}:1; WCAG needs ${num(needed)}:1${large}.${also ? ` ${also} more text element${also === 1 ? "" : "s"} on this slide ${also === 1 ? "has" : "have"} the same problem.` : ""}`,
|
|
5600
|
+
next: typeof replacement === "string" ? replacement === "text" ? `use ${code("text")}, the slide's text color, which is readable here.` : `use ${code(replacement)}, which is readable here.` : overrides ? "pick a color with more contrast against the background, or remove the override so the theme chooses a readable one." : "change the slide background or the color scheme, or give the text an explicit readable color."
|
|
5601
|
+
};
|
|
5602
|
+
};
|
|
5603
|
+
var textOnImage = (finding2) => {
|
|
5604
|
+
const ratio = measuredNumber(finding2, "ratio");
|
|
5605
|
+
const needed = measuredNumber(finding2, "needed");
|
|
5606
|
+
return {
|
|
5607
|
+
message: `Contrast over this picture can't be measured${ratio !== void 0 && needed !== void 0 ? `: against the lightest or darkest possible pixels it is as low as ${num(ratio, 1)}:1, and ${num(needed)}:1 is needed` : ""}.`,
|
|
5608
|
+
next: `add a full-frame ${code("overlay")} to the background image, so the text stays readable on any picture.`
|
|
5609
|
+
};
|
|
5610
|
+
};
|
|
5611
|
+
var ALT_SUBJECT = {
|
|
5612
|
+
Image: "This image",
|
|
5613
|
+
Video: "This video",
|
|
5614
|
+
"Header/footer image": "This header or footer image",
|
|
5615
|
+
"Speaker photo": "This speaker photo",
|
|
5616
|
+
"Quote photo": "This quote photo",
|
|
5617
|
+
"Organization logo": "This organization logo",
|
|
5618
|
+
Picture: "This picture"
|
|
5619
|
+
};
|
|
5620
|
+
var missingAltText = (finding2) => {
|
|
5621
|
+
const found = match(finding2.message, /^(.+?) has (no|blank) alt text\.$/);
|
|
5622
|
+
if (!found) return void 0;
|
|
5623
|
+
const subject = ALT_SUBJECT[found[1]] ?? `This ${found[1].toLowerCase()}`;
|
|
5624
|
+
return {
|
|
5625
|
+
message: `${subject} has ${found[2] === "blank" ? "blank" : "no"} alt text.`,
|
|
5626
|
+
next: `describe it in ${code("alt")}, or write ${code('alt: ""')} if it is only decoration.`
|
|
5627
|
+
};
|
|
5628
|
+
};
|
|
5629
|
+
var poorAltText = (finding2) => {
|
|
5630
|
+
const found = match(finding2.message, /^(?:(Chart) |(.+?) )?alt text ("(?:[^"\\]|\\.)*") (.+)\.$/);
|
|
5631
|
+
if (!found) return void 0;
|
|
5632
|
+
const alt = unquote(found[3]);
|
|
5633
|
+
const chart = found[1] === "Chart";
|
|
5634
|
+
const problem = found[4].replace(/^is a generic word, not what the data shows$/, "is a generic word");
|
|
5635
|
+
return {
|
|
5636
|
+
message: `Alt text ${code(brief(alt, 50))} ${us(problem)}, so it doesn't describe the ${chart ? "chart" : "picture"}.`,
|
|
5637
|
+
next: chart ? "state what the chart shows: its point and the key numbers, in a sentence or two." : `describe what the picture shows, in plain words and without a leading "image of".`
|
|
5638
|
+
};
|
|
5639
|
+
};
|
|
5640
|
+
var missingSlideTitle = (finding2, _context, helpers) => ({
|
|
5641
|
+
message: `${slideRef(finding2, helpers)} has no title, and screen reader users find slides by title.`,
|
|
5642
|
+
next: `write a ${code("title")} for it.`
|
|
5643
|
+
});
|
|
5644
|
+
var duplicateSlideTitle = (finding2) => {
|
|
5645
|
+
const found = match(finding2.message, /^Slide (\d+) has the same title as slide (\d+): ("(?:[^"\\]|\\.)*")\.$/);
|
|
5646
|
+
if (!found) return void 0;
|
|
5647
|
+
return {
|
|
5648
|
+
message: `Slides ${found[2]} and ${found[1]} share the title ${code(unquote(found[3]))}.`,
|
|
5649
|
+
next: "give one of them a title that tells it apart, so people can find each slide by title."
|
|
5650
|
+
};
|
|
5651
|
+
};
|
|
5652
|
+
var readingOrder = (finding2) => {
|
|
5653
|
+
const above = match(finding2.message, /^Content ("(?:[^"\\]|\\.)*") is drawn above the title but is read after it\.$/);
|
|
5654
|
+
if (above) {
|
|
5655
|
+
return {
|
|
5656
|
+
message: `${code(unquote(above[1]))} is drawn above the title but is read after it${onSlide(finding2)}.`,
|
|
5657
|
+
next: "move the title above the content, or choose a layout that puts the title first."
|
|
5658
|
+
};
|
|
5659
|
+
}
|
|
5660
|
+
const found = match(finding2.message, /^Reading order differs from visual order: ("(?:[^"\\]|\\.)*") is read at (position \d+) but appears at (position \d+)\./);
|
|
5661
|
+
if (!found) return void 0;
|
|
5662
|
+
return {
|
|
5663
|
+
message: `${code(unquote(found[1]))} is read at ${found[2]} but appears at ${found[3]}${onSlide(finding2)}, so a screen reader reads the slide out of order.`,
|
|
5664
|
+
next: "reorder the blocks so the reading order follows what people see, or pin them to layout regions."
|
|
5665
|
+
};
|
|
5666
|
+
};
|
|
5667
|
+
var linkText = (finding2) => {
|
|
5668
|
+
const found = match(finding2.message, /^Link text ("(?:[^"\\]|\\.)*") (is blank|is generic|is a long raw URL); the destination is (.+)\.$/);
|
|
5669
|
+
if (!found) return void 0;
|
|
5670
|
+
const text2 = unquote(found[1]);
|
|
5671
|
+
const destination = found[3];
|
|
5672
|
+
const message = found[2] === "is blank" ? "The link text is blank, so a screen reader announces only that there is a link." : found[2] === "is generic" ? `The link text ${code(brief(text2, 40))} doesn't say where it goes.` : "The link text is a long raw web address, which a screen reader reads out character by character.";
|
|
5673
|
+
return { message, next: `say what is at ${destination}, for example the page or file name.` };
|
|
5674
|
+
};
|
|
5675
|
+
var chartColorOnly = (finding2) => {
|
|
5676
|
+
const parts2 = match(finding2.message, /^(Series|Slices) may be hard to tell apart without colou?r/);
|
|
5677
|
+
const noun = parts2?.[1] === "Slices" ? "slices" : "series";
|
|
5678
|
+
const pairs = measuredNumber(finding2, "confusablePairs");
|
|
5679
|
+
const repeat = match(finding2.message, /(\d+) (?:series|slices) exceed the (\d+)-colou?r palette/);
|
|
5680
|
+
const count = measuredNumber(finding2, "series");
|
|
5681
|
+
const message = pairs !== void 0 ? `Some ${noun} differ by color only: ${pairs === 1 ? "one pair looks" : `${pairs} pairs look`} alike to color-blind viewers or in grayscale.${repeat ? ` ${repeat[1]} ${noun} exceed the ${repeat[2]}-color palette, so colors repeat.` : ""}` : `${cap(noun)} differ by color only${count !== void 0 ? ` (${count} ${noun})` : ""}.`;
|
|
5682
|
+
return { message, next: `add data labels or a direct legend, choose more distinct ${code("colors")}, or show fewer ${noun}.` };
|
|
5683
|
+
};
|
|
5684
|
+
var chartColorsAdjusted = (finding2, _context, helpers) => {
|
|
5685
|
+
const index = match(finding2.message, /^Chart colou?r (\d+) /)?.[1] ?? (isText2(helpers.segments.at(-1)) ? String(Number(helpers.segments.at(-1)) + 1) : void 0);
|
|
5686
|
+
const value = helpers.valueAt(helpers.segments);
|
|
5687
|
+
const shown = typeof value === "string" ? value : unquote(match(finding2.message, /^Chart colou?r \d+ \((.+?)\) /)?.[1]);
|
|
5688
|
+
const contrast = measuredNumber(finding2, "contrast");
|
|
5689
|
+
const panel = measuredText(finding2, "panel");
|
|
5690
|
+
const label = index ? `Chart color ${index}` : "A chart color";
|
|
5691
|
+
if (/cannot be resolved/.test(finding2.message)) {
|
|
5692
|
+
return {
|
|
5693
|
+
message: `${label} (${code(shown)}) can't be resolved, so the palette color of that position is used.`,
|
|
5694
|
+
next: `write a hex color like ${code("#1F4E79")}, a scheme color such as ${code("accent1")}, or a ${code("var:")} reference to a declared variable.`
|
|
5695
|
+
};
|
|
5696
|
+
}
|
|
5697
|
+
if (contrast === void 0) return void 0;
|
|
5698
|
+
return {
|
|
5699
|
+
message: `${label} (${code(shown)}) has ${num(contrast, 1)}:1 contrast against the chart panel${panel ? ` (${code(panel)})` : ""}, so it is drawn lighter or darker to reach 3:1.`,
|
|
5700
|
+
next: `pick a color with at least 3:1 contrast against ${panel ? code(panel) : "the panel"}, or remove ${code("colors")} to use the palette.`
|
|
5701
|
+
};
|
|
5702
|
+
};
|
|
5703
|
+
var chartTextAlternative = (finding2) => {
|
|
5704
|
+
const where = match(finding2.message, /^The (.+?) (?:has no alt text|is marked decorative)/)?.[1] ?? "chart";
|
|
5705
|
+
if (/is marked decorative/.test(finding2.message)) {
|
|
5706
|
+
return {
|
|
5707
|
+
message: `The ${where} is marked decorative (empty ${code("alt")}), so assistive technology skips it.`,
|
|
5708
|
+
next: `write a sentence in ${code("alt")} if the chart carries information that people need.`
|
|
5709
|
+
};
|
|
5710
|
+
}
|
|
5711
|
+
return {
|
|
5712
|
+
message: `The ${where} has no text alternative.`,
|
|
5713
|
+
next: `write the message it carries in ${code("alt")}, or add text beside it that states what it shows.`
|
|
5714
|
+
};
|
|
5715
|
+
};
|
|
5716
|
+
var missingLanguage = (_finding, context, helpers) => ({
|
|
5717
|
+
message: "The deck doesn't declare its language.",
|
|
5718
|
+
next: (context.format ?? helpers.format) === "json" ? `add ${code('"language": "en-US"')} (or the language the text is written in) to the deck.` : `add ${code("language: en-US")} (or the language the text is written in) to the deck.`
|
|
5719
|
+
});
|
|
5720
|
+
var chartOptionAdapted = (finding2, _context, helpers) => {
|
|
5721
|
+
const at2 = helpers.segments.indexOf("chart");
|
|
5722
|
+
const option = at2 >= 0 ? helpers.segments.slice(at2 + 1).join(".") : lastName(helpers.segments) ?? "";
|
|
5723
|
+
if (!option) return void 0;
|
|
5724
|
+
const type = at2 >= 0 ? helpers.valueAt([...helpers.segments.slice(0, at2 + 1), "type"]) : void 0;
|
|
5725
|
+
const typeName = typeof type === "string" ? type : match(finding2.message, /^A '(.+?)' chart/)?.[1];
|
|
5726
|
+
const adaptation = finding2.message.includes(";") ? cap(us(finding2.message.slice(finding2.message.indexOf(";") + 1).trim())) : "";
|
|
5727
|
+
return {
|
|
5728
|
+
message: `${code(option)} isn't supported by ${typeName ? `a ${typeName}` : "this"} chart and is adapted.${adaptation ? ` ${sentence(adaptation)}` : ""}`,
|
|
5729
|
+
next: `remove ${code(option)}, or choose a chart type that can show it.`
|
|
5730
|
+
};
|
|
5731
|
+
};
|
|
5732
|
+
var textOverflow = (finding2) => {
|
|
5733
|
+
const message = finding2.message;
|
|
5734
|
+
let subject = `Text${onSlide(finding2)} doesn't fit at the smallest allowed size.`;
|
|
5735
|
+
const footnote = match(message, /^Footnote (\d+) does not fit/);
|
|
5736
|
+
if (/^The caption does not fit/.test(message)) subject = `The caption${onSlide(finding2)} doesn't fit its band at the smallest allowed size.`;
|
|
5737
|
+
else if (footnote) subject = `Footnote ${footnote[1]}${onSlide(finding2)} doesn't fit the footnote area.`;
|
|
5738
|
+
return { message: subject, next: "shorten it, split the slide, or choose a roomier layout." };
|
|
5739
|
+
};
|
|
5740
|
+
var unresolvedContent = (finding2) => ({
|
|
5741
|
+
message: keep(finding2.message),
|
|
5742
|
+
next: "supply what the message asks for, or remove the setting that can't be honored."
|
|
5743
|
+
});
|
|
5744
|
+
var layoutFailed = (finding2, _context, helpers) => {
|
|
5745
|
+
const reason = match(finding2.message, /^Layout failed: (.+)$/)?.[1];
|
|
5746
|
+
return {
|
|
5747
|
+
message: `${slideRef(finding2, helpers)} couldn't be laid out${reason ? `: ${sentence(us(reason)).replace(/\.$/, "")}` : ""}, so the geometry checks were skipped.`,
|
|
5748
|
+
next: "fix the composition setting or content named above."
|
|
5749
|
+
};
|
|
5750
|
+
};
|
|
5751
|
+
var minFontSize = (finding2) => {
|
|
5752
|
+
const size = measuredNumber(finding2, "sizePt");
|
|
5753
|
+
const minimum = measuredNumber(finding2, "minimumPt");
|
|
5754
|
+
if (size === void 0 || minimum === void 0) return void 0;
|
|
5755
|
+
const run = /^Text run is set to/.test(finding2.message);
|
|
5756
|
+
const label = run ? "A text run" : match(finding2.message, /^(.+?) is drawn at/)?.[1] ?? "Text";
|
|
5757
|
+
return {
|
|
5758
|
+
message: `${cap(us(label))}${onSlide(finding2)} ${run ? "is set to" : "is drawn at"} ${num(size, 1)} pt, below the ${num(minimum, 1)} pt readable minimum.`,
|
|
5759
|
+
next: run ? `raise ${code("fontSize")} to at least ${num(minimum, 1)}.` : "shorten the text, give it more room or split the slide, so it can be drawn larger."
|
|
5760
|
+
};
|
|
5761
|
+
};
|
|
5762
|
+
function regionNamesOf(slideLayout, helpers) {
|
|
5763
|
+
if (typeof slideLayout !== "string" || !slideLayout) return void 0;
|
|
5764
|
+
const colon = slideLayout.indexOf(":");
|
|
5765
|
+
const group = colon > 0 ? slideLayout.slice(0, colon) : void 0;
|
|
5766
|
+
const id = colon > 0 ? slideLayout.slice(colon + 1) : slideLayout;
|
|
5767
|
+
const record = (group ? [group] : ["custom", "default"]).map((name) => helpers.valueAt(["catalogs", name, "layouts", id])).find(isRecord2);
|
|
5768
|
+
if (!record) return void 0;
|
|
5769
|
+
const names2 = [];
|
|
5770
|
+
const walk2 = (list) => {
|
|
5771
|
+
if (!Array.isArray(list)) return;
|
|
5772
|
+
for (const entry2 of list) {
|
|
5773
|
+
if (!isRecord2(entry2)) continue;
|
|
5774
|
+
if (typeof entry2.name === "string" && !["title", "subtitle", "tag"].includes(entry2.name)) names2.push(entry2.name);
|
|
5775
|
+
walk2(entry2.regions);
|
|
5776
|
+
}
|
|
5777
|
+
};
|
|
5778
|
+
walk2(record.regions);
|
|
5779
|
+
return { id: slideLayout, names: names2 };
|
|
5780
|
+
}
|
|
5781
|
+
var regionUnknown = (finding2, _context, helpers) => {
|
|
5782
|
+
const region = match(finding2.message, /^The layout has no region '(.+?)'/)?.[1];
|
|
5783
|
+
if (!region) return void 0;
|
|
5784
|
+
const slide = slideNumber(finding2);
|
|
5785
|
+
const layout = slide ? regionNamesOf(helpers.valueAt(["slides", String(slide - 1), "layout"]), helpers) : void 0;
|
|
5786
|
+
const known = layout?.names ?? [];
|
|
5787
|
+
return {
|
|
5788
|
+
message: layout ? `Block pinned to region ${code(region)}; layout ${code(layout.id)} has ${andList(known)}.` : `A block is pinned to region ${code(region)}, which the slide's layout doesn't have.`,
|
|
5789
|
+
next: known.length ? chooseNext(region, known, `remove ${code("region")}`) : `pin it to a region the layout has, or remove ${code("region")}.`
|
|
5790
|
+
};
|
|
5791
|
+
};
|
|
5792
|
+
var regionKind = (finding2) => {
|
|
5793
|
+
const found = match(finding2.message, /^Region '(.+?)' does not accept (\S+) content \(it accepts (.+?)\)/);
|
|
5794
|
+
if (!found) return void 0;
|
|
5795
|
+
return {
|
|
5796
|
+
message: `Region ${code(found[1])} doesn't take ${found[2]} content (it takes ${found[3]}).`,
|
|
5797
|
+
next: `remove ${code("region")} so OPF places the block by its kind, or pin it to a region that takes ${found[2]}.`
|
|
5798
|
+
};
|
|
5799
|
+
};
|
|
5800
|
+
var regionFull = (finding2) => {
|
|
5801
|
+
const found = match(finding2.message, /^Region '(.+?)' already holds its (\d+) block/);
|
|
5802
|
+
if (!found) return void 0;
|
|
5803
|
+
return {
|
|
5804
|
+
message: `Region ${code(found[1])} already holds ${found[2]} block${found[2] === "1" ? "" : "s"}, which is all it takes.`,
|
|
5805
|
+
next: `pin the block to another region with room, or remove ${code("region")}.`
|
|
5806
|
+
};
|
|
5807
|
+
};
|
|
5808
|
+
var layoutUnplaced = (finding2, _context, helpers) => {
|
|
5809
|
+
const kind = blockKind(helpers.valueAt(helpers.segments));
|
|
5810
|
+
const what = kind ? `this ${kind} block` : "this block";
|
|
5811
|
+
const overflow = match(finding2.message, /drawn in '(.+?)' beyond its (\d+) block/);
|
|
5812
|
+
return {
|
|
5813
|
+
message: overflow ? `No region has room for ${what}; it is drawn in ${code(overflow[1])}, beyond its ${overflow[2]} block${overflow[2] === "1" ? "" : "s"}.` : `No region has room for ${what}; it is drawn in the overflow area.`,
|
|
5814
|
+
next: "paginate the slide, choose a layout with more room, or remove a block."
|
|
5815
|
+
};
|
|
5816
|
+
};
|
|
5817
|
+
var furnitureWraps = (finding2) => {
|
|
5818
|
+
const found = match(finding2.message, /^The (.+?) text wraps onto (\d+) lines? \(at most (\d+)\)/);
|
|
5819
|
+
const lines = measuredNumber(finding2, "lines") ?? (found ? Number(found[2]) : void 0);
|
|
5820
|
+
const most = measuredNumber(finding2, "maximumLines") ?? (found ? Number(found[3]) : void 0);
|
|
5821
|
+
const zone = found?.[1] ?? "header or footer";
|
|
5822
|
+
if (lines === void 0) return void 0;
|
|
5823
|
+
return {
|
|
5824
|
+
message: `The ${zone} text wraps onto ${lines} lines${most !== void 0 ? ` (${most} at most)` : ""}; headers and footers should be short.`,
|
|
5825
|
+
next: "shorten it, or move part of it to another zone."
|
|
5826
|
+
};
|
|
5827
|
+
};
|
|
5828
|
+
var fontOutsideScheme = (finding2) => {
|
|
5829
|
+
const found = match(finding2.message, /^Text uses ("(?:[^"\\]|\\.)*"), which is not in the font scheme \((.+)\)\.$/);
|
|
5830
|
+
if (!found) return void 0;
|
|
5831
|
+
return {
|
|
5832
|
+
message: `${code(unquote(found[1]))} isn't in this deck's font scheme (${found[2]}), so it won't follow a scheme change.`,
|
|
5833
|
+
next: `remove ${code("fontFamily")} to use the scheme font, or add the font to the font scheme.`
|
|
5834
|
+
};
|
|
5835
|
+
};
|
|
5836
|
+
var imageResolution = (finding2) => {
|
|
5837
|
+
const width = measuredNumber(finding2, "widthPx");
|
|
5838
|
+
const height = measuredNumber(finding2, "heightPx");
|
|
5839
|
+
const ppi = measuredNumber(finding2, "ppi");
|
|
5840
|
+
const minimum = measuredNumber(finding2, "minimumPpi");
|
|
5841
|
+
if (width === void 0 || height === void 0 || ppi === void 0) return void 0;
|
|
5842
|
+
const shown = match(finding2.message, /shown ([\d.]+) x ([\d.]+) in/);
|
|
5843
|
+
const needed = shown && minimum !== void 0 ? ` about ${Math.ceil(Number(shown[1]) * minimum)}\xD7${Math.ceil(Number(shown[2]) * minimum)} px` : "";
|
|
5844
|
+
return {
|
|
5845
|
+
message: `This image is ${width}\xD7${height} px, shown at about ${ppi} ppi${minimum !== void 0 ? `; ${minimum} are needed to look sharp` : ""}.`,
|
|
5846
|
+
next: needed ? `use a file of${needed} or larger, or show it smaller.` : "use a larger file, or show the image smaller."
|
|
5847
|
+
};
|
|
5848
|
+
};
|
|
5849
|
+
var chartValueNotNumeric = (finding2) => {
|
|
5850
|
+
const value = unquote(match(finding2.message, /^chart value ("(?:[^"\\]|\\.)*") is not a number/)?.[1]);
|
|
5851
|
+
const fix = finding2.fixes?.find((entry2) => entry2.id === "store-chart-numbers");
|
|
5852
|
+
if (!value) return void 0;
|
|
5853
|
+
return {
|
|
5854
|
+
message: `${code(value)} is text, so it is plotted as a gap.`,
|
|
5855
|
+
next: fix ? `${sentence((fix.title.charAt(0).toLowerCase() + fix.title.slice(1)).replace(/\.$/, ""))}` : `write a number (12, -3.5, 1e6) and put units, currency or percent in the column ${code("format")}.`
|
|
5856
|
+
};
|
|
5857
|
+
};
|
|
5858
|
+
var chartMappingAdapted = (finding2) => ({
|
|
5859
|
+
message: keep(cap(finding2.message)),
|
|
5860
|
+
next: "remove that mapping entry, or name a different column."
|
|
5861
|
+
});
|
|
5862
|
+
var chartHighlightAdapted = (finding2) => {
|
|
5863
|
+
const name = unquote(match(finding2.message, /^highlight names \w+ ("(?:[^"\\]|\\.)*"), which is not plotted as a series/)?.[1]);
|
|
5864
|
+
if (!name) return void 0;
|
|
5865
|
+
return {
|
|
5866
|
+
message: `${code(name)} is not plotted as a series, so highlighting it changes nothing. It is the category or X column, or the mapping leaves it out.`,
|
|
5867
|
+
next: "name a series the chart plots, or remove the entry."
|
|
5868
|
+
};
|
|
5869
|
+
};
|
|
5870
|
+
var codeHighlightOutOfRange = (finding2) => {
|
|
5871
|
+
const found = match(finding2.message, /line (\d+) is past the last line \(the code has (\d+) lines?\)/);
|
|
5872
|
+
if (!found) return void 0;
|
|
5873
|
+
return {
|
|
5874
|
+
message: `Highlight line ${found[1]} is past the last line (${found[2]}), so it marks nothing.`,
|
|
5875
|
+
next: `change it to a line from 1 to ${found[2]}, or remove the entry.`
|
|
5876
|
+
};
|
|
5877
|
+
};
|
|
5878
|
+
var codeHighlightRangeReversed = (finding2) => {
|
|
5879
|
+
const found = match(finding2.message, /^The highlight range \[(\d+), (\d+)\] ends before it starts.*write \[(\d+), (\d+)\]/);
|
|
5880
|
+
if (!found) return void 0;
|
|
5881
|
+
return {
|
|
5882
|
+
message: `Highlight ${found[1]}\u2013${found[2]} ends before it starts, so it marks no line.`,
|
|
5883
|
+
next: `write [${found[3]}, ${found[4]}].`
|
|
5884
|
+
};
|
|
5885
|
+
};
|
|
5886
|
+
var variableBuiltinMissing = (finding2, _context, helpers) => {
|
|
5887
|
+
const idParam = isRecord2(finding2.validation?.params) ? finding2.validation.params.id : void 0;
|
|
5888
|
+
const id = (typeof idParam === "string" ? idParam : void 0) ?? match(finding2.message, /^Built-in '(.+?)' has no value/)?.[1] ?? match(finding2.message, /'\{\{(.+?)\}\}'/)?.[1];
|
|
5889
|
+
if (!id) return void 0;
|
|
5890
|
+
const root = id.split(".")[0];
|
|
5891
|
+
const field = id.split(".").at(-1) ?? id;
|
|
5892
|
+
const token = code(`{{${id}}}`);
|
|
5893
|
+
if (id === "slide.section") return { message: `${token} has no value: ${slideRef(finding2, helpers)} has no section.`, next: `give the slide a ${code("section")}, or remove the token.` };
|
|
5894
|
+
if (root === "deck") return { message: `${token} has no value in this document.`, next: `add ${code(field)} to the deck.` };
|
|
5895
|
+
if (id === "speakers") return { message: `${token} has no value in this document.`, next: `add a ${code("speaker")} with a ${code("name")}.` };
|
|
5896
|
+
if (root === "speaker" || root === "organization") return { message: `${token} has no value in this document.`, next: `add ${code(field)} to ${code(root)}.` };
|
|
5897
|
+
return { message: `${token} has no value in this document.`, next: "add the field it reads." };
|
|
5898
|
+
};
|
|
5899
|
+
var durationOutsideNarrative2 = (finding2) => {
|
|
5900
|
+
const found = match(finding2.message, /^The target duration of (\d+(?:\.\d+)?) minutes is outside the (.+?) narrative ("(?:[^"\\]|\\.)*") suits\.$/);
|
|
5901
|
+
if (!found) return void 0;
|
|
5902
|
+
return {
|
|
5903
|
+
message: `A ${found[1]}-minute deck is outside the ${found[2]} minutes that narrative ${code(unquote(found[3]))} suits.`.replace("minutes minutes", "minutes"),
|
|
5904
|
+
next: `change ${code("duration")}, or choose a narrative that suits ${found[1]} minutes.`
|
|
5905
|
+
};
|
|
5906
|
+
};
|
|
5907
|
+
var narrativeDurationRange = (_finding, _context, helpers) => {
|
|
5908
|
+
const range = helpers.valueAt(helpers.segments);
|
|
5909
|
+
if (isRecord2(range) && typeof range.min === "number" && typeof range.max === "number") {
|
|
5910
|
+
return {
|
|
5911
|
+
message: `The duration range has min ${range.min} greater than max ${range.max}.`,
|
|
5912
|
+
next: `swap them: ${code(`min: ${range.max}`)} and ${code(`max: ${range.min}`)}.`
|
|
5913
|
+
};
|
|
5914
|
+
}
|
|
5915
|
+
return { message: "The duration range has min greater than max.", next: `swap ${code("min")} and ${code("max")}.` };
|
|
5916
|
+
};
|
|
5917
|
+
var placeholderText = (finding2) => {
|
|
5918
|
+
const found = match(finding2.message, /^Text looks like (.+?): ("(?:[^"\\]|\\.)*")\.$/);
|
|
5919
|
+
if (!found) return void 0;
|
|
5920
|
+
return {
|
|
5921
|
+
message: `${code(brief(unquote(found[2]), 50))} looks like placeholder text (${found[1]}).`,
|
|
5922
|
+
next: "replace it with the real text."
|
|
5923
|
+
};
|
|
5924
|
+
};
|
|
5925
|
+
var emptyText = (finding2) => {
|
|
5926
|
+
const what = match(finding2.message, /^The (.+) is empty\.$/)?.[1];
|
|
5927
|
+
if (!what) return void 0;
|
|
5928
|
+
return {
|
|
5929
|
+
message: `The ${what}${onSlide(finding2)} is empty.`,
|
|
5930
|
+
next: `write the text, or remove the empty ${what}.`
|
|
5931
|
+
};
|
|
5932
|
+
};
|
|
5933
|
+
var unusedBeat = (finding2, _context, helpers) => {
|
|
5934
|
+
const beat = measuredText(finding2, "beat");
|
|
5935
|
+
const position = measuredNumber(finding2, "position");
|
|
5936
|
+
const beats = measuredNumber(finding2, "beats");
|
|
5937
|
+
if (!beat) return void 0;
|
|
5938
|
+
const narrative = isText2(helpers.valueAt(["narrative"])) ? String(helpers.valueAt(["narrative"])) : unquote(match(finding2.message, /of narrative ("(?:[^"\\]|\\.)*")/)?.[1]);
|
|
5939
|
+
return {
|
|
5940
|
+
message: `Beat ${code(beat)}${position !== void 0 && beats !== void 0 ? ` (${position} of ${beats})` : ""} of narrative ${code(narrative)} has no slide.`,
|
|
5941
|
+
next: `give a slide ${code(`beat: "${beat}"`)}, or ignore this if the beat is optional.`
|
|
5942
|
+
};
|
|
5943
|
+
};
|
|
5944
|
+
var emptySlide = (finding2, _context, helpers) => ({
|
|
5945
|
+
message: `${slideRef(finding2, helpers)} has no content.`,
|
|
5946
|
+
next: `add content or a ${code("title")}, remove the slide, or give it the ${code("blank")} layout if it is meant to be empty.`
|
|
5947
|
+
});
|
|
5948
|
+
var languageTag = (finding2, _context, helpers) => {
|
|
5949
|
+
const value = helpers.valueAt(helpers.segments);
|
|
5950
|
+
const tag = typeof value === "string" ? value : match(finding2.message, /^'(.+?)'/)?.[1];
|
|
5951
|
+
if (!tag) return void 0;
|
|
5952
|
+
const fix = finding2.fixes?.find((entry2) => entry2.id === "use-canonical-tag");
|
|
5953
|
+
const replacement = fix?.kind === "patch" && fix.patch?.[0] && "value" in fix.patch[0] && typeof fix.patch[0].value === "string" ? fix.patch[0].value : match(finding2.help ?? "", /^Write '(.+?)'\.$/)?.[1];
|
|
5954
|
+
const region = match(finding2.message, /names the region '(.+?)', which is not an assigned region/)?.[1];
|
|
5955
|
+
const reason = /not a well-formed/.test(finding2.message) ? "is not a well-formed language tag" : region ? `is not a valid tag: ${code(region)} is not an assigned region` : /not in canonical case/.test(finding2.message) ? "is not in canonical case" : /deprecated/.test(finding2.message) ? "uses a deprecated region" : "is not a valid language tag";
|
|
5956
|
+
return {
|
|
5957
|
+
message: `${code(tag)} ${reason}.`,
|
|
5958
|
+
next: replacement ? `did you mean ${code(replacement)}?` : `write a tag like ${code("en-US")}, ${code("pt-BR")} or ${code("es-419")}.`
|
|
5959
|
+
};
|
|
5960
|
+
};
|
|
5961
|
+
var AUDIT_TEXT = {
|
|
5962
|
+
"opf/text-contrast": textContrast,
|
|
5963
|
+
"opf/text-on-image": textOnImage,
|
|
5964
|
+
"opf/missing-alt-text": missingAltText,
|
|
5965
|
+
"opf/poor-alt-text": poorAltText,
|
|
5966
|
+
"opf/missing-slide-title": missingSlideTitle,
|
|
5967
|
+
"opf/duplicate-slide-title": duplicateSlideTitle,
|
|
5968
|
+
"opf/reading-order": readingOrder,
|
|
5969
|
+
"opf/link-text": linkText,
|
|
5970
|
+
"opf/chart-color-only": chartColorOnly,
|
|
5971
|
+
"opf/chart-colors-adjusted": chartColorsAdjusted,
|
|
5972
|
+
"opf/chart-text-alternative": chartTextAlternative,
|
|
5973
|
+
"opf/missing-language": missingLanguage,
|
|
5974
|
+
"opf/chart-option-adapted": chartOptionAdapted,
|
|
5975
|
+
"opf/text-overflow": textOverflow,
|
|
5976
|
+
"opf/unresolved-content": unresolvedContent,
|
|
5977
|
+
"opf/layout-failed": layoutFailed,
|
|
5978
|
+
"opf/min-font-size": minFontSize,
|
|
5979
|
+
"opf/region-unknown": regionUnknown,
|
|
5980
|
+
"opf/region-kind": regionKind,
|
|
5981
|
+
"opf/region-full": regionFull,
|
|
5982
|
+
"opf/layout-unplaced": layoutUnplaced,
|
|
5983
|
+
"opf/furniture-wraps": furnitureWraps,
|
|
5984
|
+
"opf/font-outside-scheme": fontOutsideScheme,
|
|
5985
|
+
"opf/image-resolution": imageResolution,
|
|
5986
|
+
"opf/chart-value-not-numeric": chartValueNotNumeric,
|
|
5987
|
+
"opf/chart-mapping-adapted": chartMappingAdapted,
|
|
5988
|
+
"opf/chart-highlight-adapted": chartHighlightAdapted,
|
|
5989
|
+
"opf/code-highlight-out-of-range": codeHighlightOutOfRange,
|
|
5990
|
+
"opf/code-highlight-range-reversed": codeHighlightRangeReversed,
|
|
5991
|
+
"opf/variable-builtin-missing": variableBuiltinMissing,
|
|
5992
|
+
"opf/duration-outside-narrative": durationOutsideNarrative2,
|
|
5993
|
+
"opf/narrative-duration-range": narrativeDurationRange,
|
|
5994
|
+
"opf/placeholder-text": placeholderText,
|
|
5995
|
+
"opf/empty-text": emptyText,
|
|
5996
|
+
"opf/unused-beat": unusedBeat,
|
|
5997
|
+
"opf/empty-slide": emptySlide,
|
|
5998
|
+
"opf/language-tag": languageTag
|
|
5999
|
+
};
|
|
6000
|
+
|
|
6001
|
+
// src/finding-text-format.ts
|
|
6002
|
+
var params = (finding2) => isRecord2(finding2.validation?.params) ? finding2.validation.params : {};
|
|
6003
|
+
var text = (value) => typeof value === "string" ? value : void 0;
|
|
6004
|
+
var jsonSyntax = (finding2) => ({
|
|
6005
|
+
message: keep(finding2.message),
|
|
6006
|
+
next: helpAsNext(finding2, "fix the syntax error at the marked position.")
|
|
6007
|
+
});
|
|
6008
|
+
var duplicateKey = (finding2, _context, helpers) => {
|
|
6009
|
+
const key2 = helpers.segments.at(-1) ?? unquote(match(finding2.message, /^Duplicate JSON property (".*")\.$/)?.[1]);
|
|
6010
|
+
const owner = helpers.segments.length > 1 ? `in ${code(helpers.segments.at(-2))}` : "in this object";
|
|
6011
|
+
return {
|
|
6012
|
+
message: `${code(key2)} appears twice ${owner}. JSON keeps only the last value, so the earlier one is ignored.`,
|
|
6013
|
+
next: "keep one of them, or merge their values into one."
|
|
6014
|
+
};
|
|
6015
|
+
};
|
|
6016
|
+
var schemaPlain = (finding2) => ({
|
|
6017
|
+
message: keep(finding2.message),
|
|
6018
|
+
next: helpAsNext(finding2, "check the schema reference for this field.")
|
|
6019
|
+
});
|
|
6020
|
+
var referenceIdDuplicate = (finding2, _context, helpers) => {
|
|
6021
|
+
const id = text(params(finding2).id) ?? unquote(match(finding2.message, /^reference id '(.+?)' is used/)?.[1]);
|
|
6022
|
+
const ids = idsAt(helpers, ["references"]);
|
|
6023
|
+
const index = Number(helpers.segments[1]);
|
|
6024
|
+
const first = ids.indexOf(id);
|
|
6025
|
+
const where = first >= 0 && Number.isInteger(index) && first !== index ? ` (references ${first + 1} and ${index + 1})` : "";
|
|
6026
|
+
return {
|
|
6027
|
+
message: `Two references use the id ${code(id)}${where}.`,
|
|
6028
|
+
next: `give the later one another id (for example ${code(freshName(id, ids))}); runs cite references by id.`
|
|
6029
|
+
};
|
|
6030
|
+
};
|
|
6031
|
+
var citeUnknownReference = (finding2, _context, helpers) => {
|
|
6032
|
+
const id = text(params(finding2).id) ?? unquote(match(finding2.message, /^unknown reference '(.+?)'/)?.[1]);
|
|
6033
|
+
const ids = idsAt(helpers, ["references"]);
|
|
6034
|
+
const add = `add a reference with the id ${code(id)} under ${code("references")}`;
|
|
6035
|
+
return {
|
|
6036
|
+
message: `${code(`cite: "${id}"`)} names a reference that does not exist.`,
|
|
6037
|
+
next: chooseNext(id, ids, add)
|
|
6038
|
+
};
|
|
6039
|
+
};
|
|
6040
|
+
var citeUnsupportedLocation = (finding2) => {
|
|
6041
|
+
const where = match(finding2.message, /^cite and footnote are not supported in ([^;]+);/)?.[1];
|
|
6042
|
+
const inReferences = /^reference text cannot cite/.test(finding2.message);
|
|
6043
|
+
if (inReferences)
|
|
6044
|
+
return { message: `${code("cite")} and ${code("footnote")} can't be used inside the text of a reference.`, next: `remove ${code("cite")} and ${code("footnote")} from that run.` };
|
|
6045
|
+
return {
|
|
6046
|
+
message: `${code("cite")} and ${code("footnote")} are drawn only in titles, text, bullets, list items and quotes${where ? `, not in ${where}` : ""}.`,
|
|
6047
|
+
next: "move the cited run into a text block, or remove the citation here."
|
|
6048
|
+
};
|
|
6049
|
+
};
|
|
6050
|
+
var captionUnsupportedPayload = (finding2) => {
|
|
6051
|
+
const type = text(params(finding2).type) ?? match(finding2.message, /; this payload is '(.+?)'/)?.[1];
|
|
6052
|
+
const kind = type ?? (/a group cannot carry one/.test(finding2.message) ? "group" : void 0);
|
|
6053
|
+
const is = kind ? kindPhrase(kind) : "not one of those";
|
|
6054
|
+
return {
|
|
6055
|
+
message: `A ${code("caption")} belongs to one image, chart, table or video; this block is ${is}.`,
|
|
6056
|
+
next: `put the ${code("caption")} on the image, chart, table or video it describes, or remove it.`
|
|
6057
|
+
};
|
|
6058
|
+
};
|
|
6059
|
+
var imageOptionUnsupportedPayload = (finding2) => {
|
|
6060
|
+
const field = text(params(finding2).field) ?? match(finding2.message, /^(\w+) is only valid on an image payload/)?.[1];
|
|
6061
|
+
const type = text(params(finding2).type) ?? match(finding2.message, /; this payload is '(.+?)'/)?.[1];
|
|
6062
|
+
const kind = type ?? (/a group cannot carry them/.test(finding2.message) ? "group" : void 0);
|
|
6063
|
+
if (!field) return void 0;
|
|
6064
|
+
return {
|
|
6065
|
+
message: `${code(field)} only applies to pictures; this block is ${kind ? kindPhrase(kind) : "not a picture"}.`,
|
|
6066
|
+
next: `remove ${code(field)}, or move it onto an image.`
|
|
6067
|
+
};
|
|
6068
|
+
};
|
|
6069
|
+
var imagePlacementInvalid = (finding2) => {
|
|
6070
|
+
const { edge, blocks } = params(finding2);
|
|
6071
|
+
if (typeof edge === "string" && Array.isArray(blocks) && blocks.length === 2) {
|
|
6072
|
+
return {
|
|
6073
|
+
message: `Blocks ${Number(blocks[0]) + 1} and ${Number(blocks[1]) + 1} are both placed on the ${edge} edge, and a slide edge takes one placed image.`,
|
|
6074
|
+
next: `move one of them to another edge, or remove its ${code("placement")}.`
|
|
6075
|
+
};
|
|
6076
|
+
}
|
|
6077
|
+
return {
|
|
6078
|
+
message: `${code("placement")} can't be used here: a block inside a group or a region can't bleed to a slide edge.`,
|
|
6079
|
+
next: `use ${code("placement")} on a top-level block of the slide, one per slide edge, or remove it.`
|
|
6080
|
+
};
|
|
6081
|
+
};
|
|
6082
|
+
var layoutPlaceholderGroup = (finding2) => {
|
|
6083
|
+
const detail = finding2.message.trim();
|
|
6084
|
+
return {
|
|
6085
|
+
message: detail ? `${cap(keep(detail))} A group holds body regions only, not headings.` : "A placeholder group nests at most three levels and holds body regions only, not headings.",
|
|
6086
|
+
next: "flatten the group, and keep the title, subtitle and tag at the layout's top level."
|
|
6087
|
+
};
|
|
6088
|
+
};
|
|
6089
|
+
function datasetOf(helpers) {
|
|
6090
|
+
const at2 = helpers.segments.findIndex((segment) => segment === "fields" || segment === "columns" || segment === "mapping" || segment === "highlight");
|
|
6091
|
+
const parent = at2 > 0 ? helpers.segments.slice(0, at2) : helpers.segments.slice(0, -1);
|
|
6092
|
+
for (const candidate of [parent, [...parent, "data"]]) {
|
|
6093
|
+
const value = helpers.valueAt([...candidate, "dataset"]);
|
|
6094
|
+
if (isText2(value)) return value;
|
|
6095
|
+
}
|
|
6096
|
+
return void 0;
|
|
6097
|
+
}
|
|
6098
|
+
var datasetUnknown = (finding2, _context, helpers) => {
|
|
6099
|
+
const id = unquote(match(finding2.message, /^unknown dataset '(.+?)'/)?.[1]) || String(helpers.valueAt(helpers.segments) ?? "");
|
|
6100
|
+
if (!id) return void 0;
|
|
6101
|
+
const ids = keysAt(helpers, ["datasets"]);
|
|
6102
|
+
const here = ids.length ? `Datasets in this deck: ${plainList(ids)}.` : "This deck has no datasets.";
|
|
6103
|
+
return {
|
|
6104
|
+
message: `${code(`dataset: "${id}"`)} does not exist. ${here}`,
|
|
6105
|
+
next: chooseNext(id, ids, `add ${code(`datasets.${id}`)}`)
|
|
6106
|
+
};
|
|
6107
|
+
};
|
|
6108
|
+
var datasetFieldUnknown = (finding2, _context, helpers) => {
|
|
6109
|
+
const found = match(finding2.message, /^field ("(?:[^"\\]|\\.)*") is not a column of the dataset; use one of (.+)$/);
|
|
6110
|
+
if (!found) return void 0;
|
|
6111
|
+
const field = unquote(found[1]);
|
|
6112
|
+
const columns = unquoteList(found[2]);
|
|
6113
|
+
const dataset = datasetOf(helpers);
|
|
6114
|
+
return {
|
|
6115
|
+
message: `${code(field)} is not a column of ${dataset ? code(dataset) : "the dataset"}. Columns: ${plainList(columns)}.`,
|
|
6116
|
+
next: chooseNext(field, columns)
|
|
6117
|
+
};
|
|
6118
|
+
};
|
|
6119
|
+
var dataColumnDuplicate = (finding2, _context, helpers) => {
|
|
6120
|
+
const name = unquote(match(finding2.message, /^column name ("(?:[^"\\]|\\.)*") is used by an earlier column/)?.[1]);
|
|
6121
|
+
if (!name) return void 0;
|
|
6122
|
+
const columns = helpers.valueAt(helpers.segments.slice(0, -1));
|
|
6123
|
+
const taken = Array.isArray(columns) ? columns.map((entry2) => isRecord2(entry2) ? String(entry2.name) : String(entry2)) : [];
|
|
6124
|
+
return {
|
|
6125
|
+
message: `Two columns are named ${code(name)}. Series and mappings address columns by name.`,
|
|
6126
|
+
next: `rename the second one (for example ${code(freshName(name, taken))}).`
|
|
6127
|
+
};
|
|
6128
|
+
};
|
|
6129
|
+
var chartMappingUnknownColumn = (finding2) => {
|
|
6130
|
+
const found = match(finding2.message, /^mapping names column ("(?:[^"\\]|\\.)*"), which the chart data does not have; use one of (.+)$/);
|
|
6131
|
+
if (!found) return void 0;
|
|
6132
|
+
const name = unquote(found[1]);
|
|
6133
|
+
const columns = unquoteList(found[2]);
|
|
6134
|
+
return {
|
|
6135
|
+
message: `The chart mapping names column ${code(name)}, which the data does not have. Columns: ${plainList(columns)}.`,
|
|
6136
|
+
next: chooseNext(name, columns)
|
|
6137
|
+
};
|
|
6138
|
+
};
|
|
6139
|
+
var numberFormatInvalid = (finding2, _context, helpers) => {
|
|
6140
|
+
const value = helpers.valueAt(helpers.segments);
|
|
6141
|
+
const reason = match(finding2.message, /^Number format '.*?' ((?:needs|scales|has|uses|asks|allows|is)\b.+)$/)?.[1];
|
|
6142
|
+
const subject = typeof value === "string" && value ? `${code(value)} is not a number format OPF can read` : "This is not a number format OPF can read";
|
|
6143
|
+
return {
|
|
6144
|
+
message: `${subject}.${reason ? ` It ${sentence(us(reason))}` : ""}`,
|
|
6145
|
+
next: `write a format such as ${code("0.0")}, ${code("#,##0")}, ${code("0%")} or ${code("$#,##0.00")}.`
|
|
6146
|
+
};
|
|
6147
|
+
};
|
|
6148
|
+
var chartHighlightUnknownName = (finding2) => {
|
|
6149
|
+
const series = match(finding2.message, /^highlight names series ("(?:[^"\\]|\\.)*"), which the chart data does not have as a column; use one of (.+)$/);
|
|
6150
|
+
if (series) {
|
|
6151
|
+
const name = unquote(series[1]);
|
|
6152
|
+
const columns = unquoteList(series[2]);
|
|
6153
|
+
return { message: `${code(`highlight: "${name}"`)} matches no series. Available: ${plainList(columns)}.`, next: chooseNext(name, columns) };
|
|
6154
|
+
}
|
|
6155
|
+
const category = match(finding2.message, /^highlight names category ("(?:[^"\\]|\\.)*"), which no row of the chart data has as its label(?:; the labels are (.+))?$/);
|
|
6156
|
+
if (category) {
|
|
6157
|
+
const name = unquote(category[1]);
|
|
6158
|
+
const labels = unquoteList(category[2]);
|
|
6159
|
+
return {
|
|
6160
|
+
message: `${code(`highlight: "${name}"`)} matches no category.${labels.length ? ` Available: ${plainList(labels)}${/\.\.\.$/.test(category[2] ?? "") ? ", \u2026" : ""}.` : ""}`,
|
|
6161
|
+
next: labels.length ? chooseNext(name, labels) : "name a category that appears in the chart data, or remove the entry."
|
|
6162
|
+
};
|
|
6163
|
+
}
|
|
6164
|
+
return void 0;
|
|
6165
|
+
};
|
|
6166
|
+
var tokenOf = (finding2, id) => finding2.message.includes(`var:${id}`) ? code(`var:${id}`) : code(`{{${id}}}`);
|
|
6167
|
+
var SAMPLE_BY_KIND = {
|
|
6168
|
+
number: "a plain number such as 42",
|
|
6169
|
+
date: "a date such as 2026-10-24",
|
|
6170
|
+
color: "a color such as #1F4E79",
|
|
6171
|
+
url: "a web address such as https://example.com",
|
|
6172
|
+
image: "an `asset:` reference or an https:// address",
|
|
6173
|
+
list: "a list of values",
|
|
6174
|
+
text: "some text"
|
|
6175
|
+
};
|
|
6176
|
+
var variableInvalidValue = (finding2) => {
|
|
6177
|
+
const found = match(finding2.message, /^Value for '(.+?)' \((\w+)\): (.+?)\.?$/);
|
|
6178
|
+
const id = text(params(finding2).id) ?? found?.[1];
|
|
6179
|
+
if (!id) return void 0;
|
|
6180
|
+
const kind = found?.[2] ?? "";
|
|
6181
|
+
return {
|
|
6182
|
+
message: `The value given for ${code(id)} does not fit its type${kind ? ` (${kind})` : ""}${found?.[3] ? `: ${us(found[3])}` : ""}.`,
|
|
6183
|
+
next: `give it ${SAMPLE_BY_KIND[kind] ?? "a value of the declared type"}.`
|
|
6184
|
+
};
|
|
6185
|
+
};
|
|
6186
|
+
var BUILTINS = ["deck.name", "deck.description", "deck.author", "deck.slideCount", "slide.number", "slide.section", "speakers", "speaker.name", "speaker.title", "speaker.email", "speaker.phone", "speaker.bio", "speaker.photo", "organization.name", "organization.legalName", "organization.tagline", "organization.domain", "organization.email", "organization.phone"];
|
|
6187
|
+
var variableUnknownBuiltin = (finding2) => {
|
|
6188
|
+
const id = text(params(finding2).id) ?? match(finding2.message, /^'(.+?)' is not a built-in variable/)?.[1];
|
|
6189
|
+
if (!id) return void 0;
|
|
6190
|
+
const mentioned = [...finding2.message.matchAll(/'((?:deck|slide|speaker|organization)[A-Za-z0-9_.-]*|speakers)'/g)].map((entry2) => entry2[1]).filter((name) => name !== id);
|
|
6191
|
+
const candidates = [.../* @__PURE__ */ new Set([...mentioned, ...BUILTINS])];
|
|
6192
|
+
const { close } = nearest(id, candidates);
|
|
6193
|
+
const whole = /^'var:/.test(finding2.message) || finding2.message.includes(`'var:${id}'`);
|
|
6194
|
+
const message = whole ? `${code(`var:${id}`)} is not supported: ${code(id)} is a slide-scoped built-in, written only as the inline token ${code(`{{${id}}}`)}.` : `${tokenOf(finding2, id)} is not a built-in variable.`;
|
|
6195
|
+
if (whole) return { message, next: `write ${code(`{{${id}}}`)} inside the text instead.` };
|
|
6196
|
+
return { message, next: close.length ? `did you mean ${code(close[0])}?` : `use one of ${codeList(mentioned.length ? mentioned : BUILTINS.slice(0, 4), 4, "or")}, or declare ${code(id)} under ${code("variables")}.` };
|
|
6197
|
+
};
|
|
6198
|
+
var variableFormat = (finding2, _context, helpers) => {
|
|
6199
|
+
const id = text(params(finding2).id);
|
|
6200
|
+
const declared = id ? helpers.valueAt(["variables", id, "format"]) : void 0;
|
|
6201
|
+
const reason = match(finding2.message, /^(?:Number|Date) format '.*?' (.+)$/)?.[1];
|
|
6202
|
+
const kind = id ? helpers.valueAt(["variables", id, "type"]) : void 0;
|
|
6203
|
+
return {
|
|
6204
|
+
message: `The format${typeof declared === "string" ? ` ${code(declared)}` : ""} does not apply to ${kind ? `a ${String(kind)}` : "this"} value${id ? ` (${code(id)})` : ""}${reason ? `: it ${sentence(us(reason)).replace(/\.$/, "")}` : ""}.`,
|
|
6205
|
+
next: `change the ${code("format")} of ${id ? code(id) : "the variable"} to one that fits its values, such as ${kind === "date" ? code("MMMM d, yyyy") : code("#,##0.0")}.`
|
|
6206
|
+
};
|
|
6207
|
+
};
|
|
6208
|
+
var variableUnfilled = (finding2, _context, helpers) => {
|
|
6209
|
+
const token = match(finding2.message, /^Template variable ("\{\{.+\}\}") is still in the text\.$/);
|
|
6210
|
+
if (token) {
|
|
6211
|
+
const literal = unquote(token[1]);
|
|
6212
|
+
const id2 = /\{\{\s*([a-z][a-z0-9-]*)/.exec(literal)?.[1] ?? literal;
|
|
6213
|
+
return {
|
|
6214
|
+
message: `${code(literal)} is still in the text, so it would show literally.`,
|
|
6215
|
+
next: `declare ${code(id2)} under ${code("variables")} and give it a value, or replace the token with the real text.`
|
|
6216
|
+
};
|
|
6217
|
+
}
|
|
6218
|
+
const id = text(params(finding2).id) ?? match(finding2.message, /^(?:required variable '(.+?)'|Template variable ("(?:[^"\\]|\\.)*")) has no value/)?.[1] ?? helpers.segments.at(-1);
|
|
6219
|
+
const inTemplate = /^Template variable/.test(finding2.message);
|
|
6220
|
+
if (!id) return void 0;
|
|
6221
|
+
const name = id.startsWith('"') ? unquote(id) : id;
|
|
6222
|
+
if (inTemplate) {
|
|
6223
|
+
return {
|
|
6224
|
+
message: `Template variable ${code(name)} has no value yet.`,
|
|
6225
|
+
next: `fill it (${code("opf fill")}) before using the deck, or leave it while you edit the template.`
|
|
6226
|
+
};
|
|
6227
|
+
}
|
|
6228
|
+
return {
|
|
6229
|
+
message: `Required variable ${code(name)} has no value, so ${code(`{{${name}}}`)} would show literally.`,
|
|
6230
|
+
next: `give it a ${code("value")}, or mark the deck ${code("template: true")}.`
|
|
6231
|
+
};
|
|
6232
|
+
};
|
|
6233
|
+
var variableUnknown = (finding2, _context, helpers) => {
|
|
6234
|
+
const id = text(params(finding2).id) ?? match(finding2.message, /^'\{\{(.+?)\}\}' names no declared variable/)?.[1];
|
|
6235
|
+
if (!id) return void 0;
|
|
6236
|
+
const declared = keysAt(helpers, ["variables"]);
|
|
6237
|
+
const { close } = nearest(id, declared);
|
|
6238
|
+
return {
|
|
6239
|
+
message: `${code(`{{${id}}}`)} is not declared under ${code("variables")}.`,
|
|
6240
|
+
next: close.length ? `did you mean ${code(close[0])}? Or declare ${code(id)} under ${code("variables")}.` : `declare ${code(id)} under ${code("variables")}, or write ${code("\\{{")} for literal braces.`
|
|
6241
|
+
};
|
|
6242
|
+
};
|
|
6243
|
+
var variableUnknownValue = (finding2, _context, helpers) => {
|
|
6244
|
+
const id = text(params(finding2).id) ?? match(finding2.message, /^A value was supplied for '(.+?)'/)?.[1];
|
|
6245
|
+
if (!id) return void 0;
|
|
6246
|
+
const declared = keysAt(helpers, ["variables"]);
|
|
6247
|
+
const { close } = nearest(id, declared);
|
|
6248
|
+
return {
|
|
6249
|
+
message: `A value was supplied for ${code(id)}, which is not declared.`,
|
|
6250
|
+
next: close.length ? `did you mean ${code(close[0])}? Or declare ${code(id)} under ${code("variables")}.` : `declare ${code(id)} under ${code("variables")}, or remove the value.`
|
|
6251
|
+
};
|
|
6252
|
+
};
|
|
6253
|
+
var variableUnused = (finding2, _context, helpers) => {
|
|
6254
|
+
const id = text(params(finding2).id) ?? helpers.segments.at(-1);
|
|
6255
|
+
if (!id) return void 0;
|
|
6256
|
+
return {
|
|
6257
|
+
message: `Variable ${code(id)} is declared but never used.`,
|
|
6258
|
+
next: `use it as ${code(`{{${id}}}`)} or ${code(`var:${id}`)} where it belongs, or remove its declaration.`
|
|
6259
|
+
};
|
|
6260
|
+
};
|
|
6261
|
+
var variableReferenceUnknown = (finding2, _context, helpers) => {
|
|
6262
|
+
const id = text(params(finding2).id) ?? match(finding2.message, /^unknown variable '(.+?)'/)?.[1];
|
|
6263
|
+
if (!id) return void 0;
|
|
6264
|
+
const declared = keysAt(helpers, ["variables"]);
|
|
6265
|
+
const { close } = nearest(id, declared);
|
|
6266
|
+
return {
|
|
6267
|
+
message: `${code(`var:${id}`)} is not declared, so a default color is used.`,
|
|
6268
|
+
next: close.length ? `did you mean ${code(`var:${close[0]}`)}? Or declare ${code(id)} under ${code("variables")}.` : `declare ${code(id)} under ${code("variables")}, or use another color.`
|
|
6269
|
+
};
|
|
6270
|
+
};
|
|
6271
|
+
var SCHEME_COLORS = ["accent1", "accent2", "accent3", "accent4", "accent5", "accent6", "dark1", "dark2", "light1", "light2", "hyperlink", "followedHyperlink", "primary", "secondary", "accent", "background", "surface", "surfaceAlt", "text", "textSecondary"];
|
|
6272
|
+
var runColorUnrecognized = (finding2, _context, helpers) => {
|
|
6273
|
+
const value = text(params(finding2).color) ?? match(finding2.message, /^run color '(.+?)' is not/)?.[1] ?? text(helpers.valueAt(helpers.segments));
|
|
6274
|
+
if (!value) return void 0;
|
|
6275
|
+
const { close } = nearest(value, SCHEME_COLORS);
|
|
6276
|
+
return {
|
|
6277
|
+
message: `${code(`color: "${value}"`)} is not a hex color, a scheme color or a ${code("var:")} reference, so the theme color is used.`,
|
|
6278
|
+
next: close.length ? `did you mean ${code(close[0])}?` : `write a hex color like ${code("#1F4E79")}, a scheme color such as ${code("accent1")}, or ${code("var:")} and a variable id.`
|
|
6279
|
+
};
|
|
6280
|
+
};
|
|
6281
|
+
var numberingStartIgnored = (_finding, _context, helpers) => {
|
|
6282
|
+
const owner = lastName(helpers.segments);
|
|
6283
|
+
return {
|
|
6284
|
+
message: `${code("start")} has no effect without ${code("numbering")} on ${owner ? `the ${code(owner)} list` : "this list"}.`,
|
|
6285
|
+
next: `add ${code("numbering")} to the list, or remove ${code("start")}.`
|
|
6286
|
+
};
|
|
6287
|
+
};
|
|
6288
|
+
var slideSizeOutOfRange = (finding2) => {
|
|
6289
|
+
const { widthInches, heightInches } = params(finding2);
|
|
6290
|
+
const size = typeof widthInches === "number" && typeof heightInches === "number" ? `A ${widthInches}\xD7${heightInches} in slide` : "This slide size";
|
|
6291
|
+
return {
|
|
6292
|
+
message: `${size} is outside PowerPoint's 1 to 56 inch range, so PowerPoint may reject the export.`,
|
|
6293
|
+
next: `set ${code("design.dimensions")} to sizes between 1 and 56 inches, or to a preset.`
|
|
6294
|
+
};
|
|
6295
|
+
};
|
|
6296
|
+
var undeclaredCatalog = (finding2) => {
|
|
6297
|
+
const found = match(finding2.message, /^Reference ("(?:[^"\\]|\\.)*") names catalog group ("(?:[^"\\]|\\.)*"), which/);
|
|
6298
|
+
if (!found) return void 0;
|
|
6299
|
+
const reference = unquote(found[1]);
|
|
6300
|
+
const group = unquote(found[2]);
|
|
6301
|
+
const bare = reference.includes(":") ? reference.slice(reference.indexOf(":") + 1) : reference;
|
|
6302
|
+
return {
|
|
6303
|
+
message: `${code(reference)} uses catalog group ${code(group)}, which ${code("catalogs")} does not declare, so it can never resolve.`,
|
|
6304
|
+
next: `declare ${code(`catalogs.${group}`)}, or write ${code(bare)}.`
|
|
6305
|
+
};
|
|
6306
|
+
};
|
|
6307
|
+
var assetReference = (finding2, _context, helpers) => {
|
|
6308
|
+
const id = unquote(match(finding2.message, /^Asset ("(?:[^"\\]|\\.)*") is missing/)?.[1]);
|
|
6309
|
+
if (!id) return void 0;
|
|
6310
|
+
const ids = keysAt(helpers, ["assets"]);
|
|
6311
|
+
const suggested = (finding2.suggestions ?? []).map((entry2) => String(entry2.value).replace(/^asset:/, ""));
|
|
6312
|
+
const known = ids.length ? ids : suggested;
|
|
6313
|
+
const { close } = nearest(id, known);
|
|
6314
|
+
const add = `add ${code(`assets.${id}`)}`;
|
|
6315
|
+
return {
|
|
6316
|
+
message: `${code(`asset:${id}`)} is not in ${code("assets")}.${known.length ? ` Assets: ${plainList(known)}.` : " This deck has no assets."}`,
|
|
6317
|
+
next: close.length ? `did you mean ${code(`asset:${close[0]}`)}? Or ${add}.` : known.length ? `pick one of ${plainList(known.slice(0, 4))}, or ${add}.` : `${add}.`
|
|
6318
|
+
};
|
|
6319
|
+
};
|
|
6320
|
+
var assetCycle = (finding2) => {
|
|
6321
|
+
const chainText = match(finding2.message, /^Asset references form a cycle: (.+)\.$/)?.[1];
|
|
6322
|
+
const chain = chainText ? chainText.split(" \u2192 ").map(unquote) : [];
|
|
6323
|
+
const first = chain[0];
|
|
6324
|
+
if (!first || chain.length < 2) return void 0;
|
|
6325
|
+
const through = chain.slice(1, -1);
|
|
6326
|
+
const how = through.length ? ` through ${codeList(through)}` : "";
|
|
6327
|
+
return {
|
|
6328
|
+
message: `Asset ${code(first)} points back to itself${how}.`,
|
|
6329
|
+
next: "give one link a real source (an https:// address or embedded data) instead of an `asset:` reference."
|
|
6330
|
+
};
|
|
6331
|
+
};
|
|
6332
|
+
var KIND_SINGULAR = (kind) => KIND_WORDS[kind] ?? kind.replace(/s$/, "");
|
|
6333
|
+
var catalogRecord = (finding2, context) => {
|
|
6334
|
+
const [root, group, kind, id] = finding2.path.split("/").slice(1).map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
6335
|
+
if (root !== "catalogs" || !kind || !id) return void 0;
|
|
6336
|
+
const inner = describeFinding({ ...finding2, ruleId: "opf/schema" }, context);
|
|
6337
|
+
const registered = finding2.scope === "context";
|
|
6338
|
+
const record = `${registered ? "The registered" : "The embedded"} ${KIND_SINGULAR(kind)} ${code(id)}`;
|
|
6339
|
+
return {
|
|
6340
|
+
message: `${record} is invalid: ${inner.message.charAt(0).toLowerCase()}${inner.message.slice(1)}`,
|
|
6341
|
+
next: inner.next ?? `fix the record; references to it are reported too.`
|
|
6342
|
+
};
|
|
6343
|
+
};
|
|
6344
|
+
function layoutSpot(finding2) {
|
|
6345
|
+
const parts2 = finding2.path.split("/").slice(1).map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
6346
|
+
if (parts2[0] === "catalogs" && parts2[2] === "layouts") return { id: parts2[3], spot: dotted(parts2.slice(4)) };
|
|
6347
|
+
return { spot: dotted(parts2) };
|
|
6348
|
+
}
|
|
6349
|
+
var layoutTemplate = (finding2) => {
|
|
6350
|
+
const { id, spot } = layoutSpot(finding2);
|
|
6351
|
+
const detail = finding2.message.trim().replace(/\.$/, "");
|
|
6352
|
+
return {
|
|
6353
|
+
message: `The layout's region list is malformed${spot ? ` at ${code(spot)}` : ""}${id ? ` (layout ${code(id)})` : ""}: ${us(detail)}.`,
|
|
6354
|
+
next: spot ? `correct ${code(spot)} as described; the layout is not used until it is valid.` : "correct the layout as described; it is not used until it is valid."
|
|
6355
|
+
};
|
|
6356
|
+
};
|
|
6357
|
+
var layoutRegion = (finding2) => {
|
|
6358
|
+
const { id, spot } = layoutSpot(finding2);
|
|
6359
|
+
const detail = finding2.message.trim().replace(/\.$/, "");
|
|
6360
|
+
const reserved = match(detail, /^'(.+?)' is reserved and cannot name a body region \(reserved: (.+)\)$/);
|
|
6361
|
+
if (reserved) {
|
|
6362
|
+
return {
|
|
6363
|
+
message: `Region name ${code(reserved[1])} is reserved, so it can't name a body region${id ? ` of layout ${code(id)}` : ""}. Reserved names: ${reserved[2]}.`,
|
|
6364
|
+
next: `rename it, for example ${code(`${reserved[1]}-area`)}.`
|
|
6365
|
+
};
|
|
6366
|
+
}
|
|
6367
|
+
const twice = match(detail, /^region name '(.+?)' is used twice/);
|
|
6368
|
+
if (twice) {
|
|
6369
|
+
return {
|
|
6370
|
+
message: `Region name ${code(twice[1])} is used twice; names are unique across the whole layout.`,
|
|
6371
|
+
next: `rename one of them (for example ${code(freshName(twice[1]))}).`
|
|
6372
|
+
};
|
|
6373
|
+
}
|
|
6374
|
+
const badName = match(detail, /^'(.+?)' is not a region name/);
|
|
6375
|
+
if (badName) {
|
|
6376
|
+
const bad = badName[1];
|
|
6377
|
+
return {
|
|
6378
|
+
message: `${code(bad)} is not a valid region name: use lowercase letters, digits and hyphens, starting with a letter.`,
|
|
6379
|
+
next: `rename it, for example ${code(bad.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+|-+$/g, "") || "body")}.`
|
|
6380
|
+
};
|
|
6381
|
+
}
|
|
6382
|
+
return {
|
|
6383
|
+
message: `A region of the layout is not valid${spot ? ` at ${code(spot)}` : ""}${id ? ` (layout ${code(id)})` : ""}: ${us(detail)}.`,
|
|
6384
|
+
next: spot ? `correct ${code(spot)} as described.` : "correct the layout as described."
|
|
6385
|
+
};
|
|
6386
|
+
};
|
|
6387
|
+
var FALLBACK = (kind) => kind === "layouts" ? "the slide is laid out automatically" : "the engine default is used";
|
|
6388
|
+
var unresolvedReference2 = (finding2, _context, helpers) => {
|
|
6389
|
+
const found = match(finding2.message, /^([A-Z][A-Za-z ]*?) '(.+?)' resolves nowhere/);
|
|
6390
|
+
const kind = finding2.lookup?.[2] ?? "";
|
|
6391
|
+
const reference = found?.[2] ?? (() => {
|
|
6392
|
+
const value = helpers.valueAt(helpers.segments);
|
|
6393
|
+
return typeof value === "string" ? value : void 0;
|
|
6394
|
+
})();
|
|
6395
|
+
if (!reference) return void 0;
|
|
6396
|
+
const noun = cap(KIND_SINGULAR(kind || (found?.[1] ?? "catalog record").toLowerCase()));
|
|
6397
|
+
const colon = reference.indexOf(":");
|
|
6398
|
+
const group = colon > 0 ? reference.slice(0, colon) : void 0;
|
|
6399
|
+
const bare = colon > 0 ? reference.slice(colon + 1) : reference;
|
|
6400
|
+
const embeddedIn = group ? [group] : ["custom", "default"];
|
|
6401
|
+
const embedded = kind && embeddedIn.some((name) => isRecord2(helpers.valueAt(["catalogs", name, kind, bare])));
|
|
6402
|
+
const suggestions = (finding2.suggestions ?? []).slice(0, 5).map((entry2) => String(entry2.value));
|
|
6403
|
+
if (embedded) {
|
|
6404
|
+
return {
|
|
6405
|
+
message: `${noun} ${code(reference)} is embedded but invalid, so it is not used and ${FALLBACK(kind)}.`,
|
|
6406
|
+
next: `fix the record named in the other problems, or remove the reference.`
|
|
6407
|
+
};
|
|
6408
|
+
}
|
|
6409
|
+
const message = group && /declares no catalogs\./.test(finding2.message) ? `${noun} ${code(reference)} uses catalog group ${code(group)}, which the deck does not declare, so ${FALLBACK(kind)}.` : `${noun} ${code(reference)} is not embedded or in a registered catalog, so ${FALLBACK(kind)}.`;
|
|
6410
|
+
const { close } = nearest(reference, suggestions);
|
|
6411
|
+
const remove = kind === "layouts" ? "remove the reference to let OPF pick" : "remove the reference";
|
|
6412
|
+
return {
|
|
6413
|
+
message,
|
|
6414
|
+
next: close.length ? `did you mean ${code(close[0])}?` : suggestions.length ? `choose one of ${codeList(suggestions, 5, "or")}, embed the record under ${code("catalogs.custom")}, or ${remove}.` : `embed the record under ${code("catalogs.custom")}, register the catalog that defines it, or ${remove}.`
|
|
6415
|
+
};
|
|
6416
|
+
};
|
|
6417
|
+
var catalogRecordNotInSource = (finding2) => {
|
|
6418
|
+
const found = match(finding2.message, /^Record ("(?:[^"\\]|\\.)*") is embedded under catalogs\.([^.]+)\.(\w+), but the catalog registered for (.+?) has no /);
|
|
6419
|
+
if (!found) return void 0;
|
|
6420
|
+
const id = unquote(found[1]);
|
|
6421
|
+
const group = found[2];
|
|
6422
|
+
return {
|
|
6423
|
+
message: `${code(id)} says it comes from ${found[4]}, but that catalog has no such record. It is treated as your own.`,
|
|
6424
|
+
next: `move it to ${code("catalogs.custom")}${group === "default" ? "" : ` and drop the ${code(`${group}:`)} prefix from its references`}, or use an id that the catalog publishes.`
|
|
6425
|
+
};
|
|
6426
|
+
};
|
|
6427
|
+
var unusedReference = (finding2, _context, helpers) => {
|
|
6428
|
+
const id = text(params(finding2).id) ?? match(finding2.message, /^reference '(.+?)' is never cited/)?.[1];
|
|
6429
|
+
if (!id) return void 0;
|
|
6430
|
+
return {
|
|
6431
|
+
message: `Reference ${code(id)} is never cited.`,
|
|
6432
|
+
next: `cite it from a text, bullet or list item (${code(`cite: "${id}"`)}), or remove it.`
|
|
6433
|
+
};
|
|
6434
|
+
};
|
|
6435
|
+
var unusedDataset = (finding2, _context, helpers) => {
|
|
6436
|
+
const id = helpers.segments.at(-1) ?? unquote(match(finding2.message, /^Dataset ("(?:[^"\\]|\\.)*") is never referenced/)?.[1]);
|
|
6437
|
+
if (!id) return void 0;
|
|
6438
|
+
return {
|
|
6439
|
+
message: `Dataset ${code(id)} is not used by any chart or table.`,
|
|
6440
|
+
next: `point a chart or table at it with ${code(`dataset: "${id}"`)}, or remove it.`
|
|
6441
|
+
};
|
|
6442
|
+
};
|
|
6443
|
+
var unknownBeat = (finding2, _context, helpers) => {
|
|
6444
|
+
const found = match(finding2.message, /^Slide (\d+) names beat ("(?:[^"\\]|\\.)*"), which narrative ("(?:[^"\\]|\\.)*") does not define/);
|
|
6445
|
+
if (!found) return void 0;
|
|
6446
|
+
const beat = unquote(found[2]);
|
|
6447
|
+
const narrative = unquote(found[3]);
|
|
6448
|
+
const beats = (finding2.suggestions ?? []).map((entry2) => String(entry2.value));
|
|
6449
|
+
const { close } = nearest(beat, beats);
|
|
6450
|
+
return {
|
|
6451
|
+
message: `Slide ${found[1]} names beat ${code(beat)}, which narrative ${code(narrative)} does not define.${beats.length ? ` Its beats include ${plainList(beats.slice(0, 5))}.` : ""}`,
|
|
6452
|
+
next: close.length ? `did you mean ${code(close[0])}?` : beats.length ? `use one of ${codeList(beats, 5, "or")}, or remove ${code("beat")}.` : `use a beat the narrative defines, or remove ${code("beat")}.`
|
|
6453
|
+
};
|
|
6454
|
+
};
|
|
6455
|
+
var slideThemeDimensions = (finding2) => {
|
|
6456
|
+
const { slideDimensions, deckDimensions } = params(finding2);
|
|
6457
|
+
const nice = (value) => {
|
|
6458
|
+
const found = /^([\d.]+)x([\d.]+) px$/.exec(String(value ?? ""));
|
|
6459
|
+
return found ? `${Math.round(Number(found[1]))}\xD7${Math.round(Number(found[2]))} px` : void 0;
|
|
6460
|
+
};
|
|
6461
|
+
const slide = nice(slideDimensions);
|
|
6462
|
+
const deck = nice(deckDimensions);
|
|
6463
|
+
return {
|
|
6464
|
+
message: `This slide's theme sets another slide size${slide && deck ? ` (${slide}; the deck is ${deck})` : ""}, which is ignored: a deck has one size.`,
|
|
6465
|
+
next: `set ${code("design.dimensions")} on the deck, or give every slide's theme the same dimensions.`
|
|
6466
|
+
};
|
|
6467
|
+
};
|
|
6468
|
+
var contract = (finding2, _context, helpers) => {
|
|
6469
|
+
const actual = helpers.valueAt(helpers.segments);
|
|
6470
|
+
const allowed = (finding2.suggestions ?? []).filter((entry2) => entry2.origin === "contract").map((entry2) => entry2.value);
|
|
6471
|
+
const standard = match(finding2.message, /^This value is outside the allowed values for (.+)\.$/);
|
|
6472
|
+
const label = helpers.segments.length ? fieldLabel(helpers.segments) : "the deck";
|
|
6473
|
+
const allowedWords = allowed.length ? plainList(allowed.map((value) => typeof value === "string" ? value : JSON.stringify(value)), 6) : void 0;
|
|
6474
|
+
const message = standard ? `${cap(label)} is ${actual === void 0 ? "missing or unset" : describeValue(actual)}, which this host's policy does not allow.${allowedWords ? ` Allowed: ${allowedWords}.` : ""}` : keep(finding2.message);
|
|
6475
|
+
return {
|
|
6476
|
+
message,
|
|
6477
|
+
next: allowed.length ? chooseNext(actual, allowed.map((value) => typeof value === "string" ? value : JSON.stringify(value))) : helpAsNext(finding2, "change it to an allowed value.")
|
|
6478
|
+
};
|
|
6479
|
+
};
|
|
6480
|
+
var FORMAT_TEXT = {
|
|
6481
|
+
"opf/json-syntax": jsonSyntax,
|
|
6482
|
+
"opf/duplicate-key": duplicateKey,
|
|
6483
|
+
"opf/schema": schemaPlain,
|
|
6484
|
+
"opf/reference-id-duplicate": referenceIdDuplicate,
|
|
6485
|
+
"opf/cite-unknown-reference": citeUnknownReference,
|
|
6486
|
+
"opf/cite-unsupported-location": citeUnsupportedLocation,
|
|
6487
|
+
"opf/caption-unsupported-payload": captionUnsupportedPayload,
|
|
6488
|
+
"opf/image-option-unsupported-payload": imageOptionUnsupportedPayload,
|
|
6489
|
+
"opf/image-placement-invalid": imagePlacementInvalid,
|
|
6490
|
+
"opf/layout-placeholder-group": layoutPlaceholderGroup,
|
|
6491
|
+
"opf/dataset-unknown": datasetUnknown,
|
|
6492
|
+
"opf/dataset-field-unknown": datasetFieldUnknown,
|
|
6493
|
+
"opf/data-column-duplicate": dataColumnDuplicate,
|
|
6494
|
+
"opf/chart-mapping-unknown-column": chartMappingUnknownColumn,
|
|
6495
|
+
"opf/number-format-invalid": numberFormatInvalid,
|
|
6496
|
+
"opf/variable-invalid-value": variableInvalidValue,
|
|
6497
|
+
"opf/variable-unknown-builtin": variableUnknownBuiltin,
|
|
6498
|
+
"opf/chart-highlight-unknown-name": chartHighlightUnknownName,
|
|
6499
|
+
"opf/variable-format": variableFormat,
|
|
6500
|
+
"opf/variable-unfilled": variableUnfilled,
|
|
6501
|
+
"opf/run-color-unrecognized": runColorUnrecognized,
|
|
6502
|
+
"opf/numbering-start-ignored": numberingStartIgnored,
|
|
6503
|
+
"opf/slide-size-out-of-range": slideSizeOutOfRange,
|
|
6504
|
+
"opf/undeclared-catalog": undeclaredCatalog,
|
|
6505
|
+
"opf/asset-reference": assetReference,
|
|
6506
|
+
"opf/asset-cycle": assetCycle,
|
|
6507
|
+
"opf/catalog-record": catalogRecord,
|
|
6508
|
+
"opf/layout-template": layoutTemplate,
|
|
6509
|
+
"opf/layout-region": layoutRegion,
|
|
6510
|
+
"opf/unresolved-reference": unresolvedReference2,
|
|
6511
|
+
"opf/catalog-record-not-in-source": catalogRecordNotInSource,
|
|
6512
|
+
"opf/unused-reference": unusedReference,
|
|
6513
|
+
"opf/unused-dataset": unusedDataset,
|
|
6514
|
+
"opf/unknown-beat": unknownBeat,
|
|
6515
|
+
"opf/slide-theme-dimensions": slideThemeDimensions,
|
|
6516
|
+
"opf/variable-unknown": variableUnknown,
|
|
6517
|
+
"opf/variable-unknown-value": variableUnknownValue,
|
|
6518
|
+
"opf/variable-unused": variableUnused,
|
|
6519
|
+
"opf/variable-reference-unknown": variableReferenceUnknown,
|
|
6520
|
+
"opf/contract": contract
|
|
6521
|
+
};
|
|
6522
|
+
|
|
6523
|
+
// src/finding-text-rules.ts
|
|
6524
|
+
var GENERIC_NEXT = "check the rule's documentation for how to fix it.";
|
|
6525
|
+
function safe(handler) {
|
|
6526
|
+
return (finding2, context, helpers) => {
|
|
6527
|
+
try {
|
|
6528
|
+
const text2 = handler(finding2, context, helpers);
|
|
6529
|
+
if (text2?.message && text2.next) return text2;
|
|
6530
|
+
} catch {
|
|
6531
|
+
}
|
|
6532
|
+
return { message: keep(finding2.message || "This finding has no message"), next: helpAsNext(finding2, GENERIC_NEXT) };
|
|
6533
|
+
};
|
|
6534
|
+
}
|
|
6535
|
+
var RULE_TEXT = Object.freeze(
|
|
6536
|
+
Object.fromEntries(Object.entries({ ...FORMAT_TEXT, ...AUDIT_TEXT }).map(([id, handler]) => [id, safe(handler)]))
|
|
6537
|
+
);
|
|
6538
|
+
|
|
6539
|
+
// src/markdown/dialect.ts
|
|
6540
|
+
var SLIDE_OPTION_KEYS = ["id", "type", "layout", "section", "tag", "hidden", "beat"];
|
|
6541
|
+
var BLOCK_OPTION_KEYS = ["id", "type", "as", "region"];
|
|
6542
|
+
var BLOCK_AS_VALUES = ["bullets", "video"];
|
|
6543
|
+
var FENCE_KINDS = ["chart", "metric", "timeline", "opf-block", "opf-slide"];
|
|
6544
|
+
var METRIC_KEYS = ["value", "label", "description", "unit", "delta", "trend", "sentiment"];
|
|
6545
|
+
var FENCE_ATTRIBUTES = { chart: ["alt"], timeline: ["name", "description"], code: ["title"] };
|
|
6546
|
+
var SPAN_FLAGS = ["bold", "italic", "underline", "strike", "sup", "sub"];
|
|
6547
|
+
var SPAN_VALUES = ["color", "size", "font", "lang"];
|
|
6548
|
+
var LINE_KEYWORDS = ["Note:", "Notes:"];
|
|
6549
|
+
|
|
6550
|
+
// src/finding-text-source.ts
|
|
6551
|
+
var keep2 = (finding2, next) => next ? { message: finding2.message, next } : { message: finding2.message };
|
|
6552
|
+
var wholeHelp = (finding2) => keep2(finding2, finding2.help ? asNext(finding2.help) : void 0);
|
|
6553
|
+
var line = (finding2) => finding2.location?.line ?? 1;
|
|
6554
|
+
var quoted = (text2, pattern) => pattern.exec(text2)?.[1];
|
|
6555
|
+
var optionKeys = (kind) => kind === "block" ? BLOCK_OPTION_KEYS : SLIDE_OPTION_KEYS;
|
|
6556
|
+
var options = {
|
|
6557
|
+
"markdown/options-unknown-key": (finding2) => {
|
|
6558
|
+
const kind = quoted(finding2.message, /^Unknown (slide|block) option/) ?? "slide";
|
|
6559
|
+
const key2 = quoted(finding2.message, /option "(.*)"\.$/) ?? "";
|
|
6560
|
+
const guess = finding2.suggestions?.[0]?.value;
|
|
6561
|
+
const keys = optionKeys(kind).join(", ");
|
|
6562
|
+
return {
|
|
6563
|
+
message: `${code(key2)} is not a ${kind} option.`,
|
|
6564
|
+
next: guess !== void 0 ? `did you mean ${code(guess)}? The keys are ${keys}. Anything else goes in an \`opf-${kind}\` fence.` : `use one of ${keys}. Anything else goes in an \`opf-${kind}\` fence.`
|
|
6565
|
+
};
|
|
6566
|
+
},
|
|
6567
|
+
"markdown/options-value": (finding2) => {
|
|
6568
|
+
if (/^hidden takes/.test(finding2.message)) return keep2(finding2, "write `hidden=true`, `hidden=false` or just `hidden`.");
|
|
6569
|
+
const key2 = quoted(finding2.message, /^Option (\w+) needs a value/);
|
|
6570
|
+
if (key2) return keep2(finding2, `write ${code(`${key2}=value`)}, for example ${code(key2 === "layout" ? "layout=title" : key2 === "id" ? "id=cover" : `${key2}=name`)}.`);
|
|
6571
|
+
if (/^as takes/.test(finding2.message)) return keep2(finding2, "write `as=bullets` before a list or `as=video` before an image line.");
|
|
6572
|
+
return keep2(finding2, "put `as=bullets` before a list and `as=video` before an image line, or remove `as`.");
|
|
6573
|
+
},
|
|
6574
|
+
"markdown/options-syntax": (finding2) => keep2(finding2, `write options as ${code("key=value")} pairs, with text in double quotes: ${code('<!-- slide: layout=title section="Part 1" -->')}.`),
|
|
6575
|
+
"markdown/options-duplicate": (finding2) => {
|
|
6576
|
+
const key2 = quoted(finding2.message, /^Option (\w+) is given twice/);
|
|
6577
|
+
if (key2) return keep2(finding2, `keep one ${code(`${key2}=`)} in the comment.`);
|
|
6578
|
+
return keep2(finding2, "merge them into one.");
|
|
6579
|
+
},
|
|
6580
|
+
"markdown/options-orphan": (finding2) => keep2(finding2, "put the `<!-- block: \u2026 -->` comment directly before the block it describes, or remove it."),
|
|
6581
|
+
"markdown/options-trailing": (finding2) => keep2(finding2, "put the content on its own line after the options comment."),
|
|
6582
|
+
"markdown/options-embedded": (finding2) => keep2(finding2, "remove the comment and put `id` and `type` inside the `opf-block` YAML.")
|
|
6583
|
+
};
|
|
6584
|
+
var markdown = {
|
|
6585
|
+
"markdown/front-matter-unterminated": (finding2) => keep2(finding2, "add a line with only `---` after the settings, or remove the opening `---` (a deck without front matter starts with its first slide)."),
|
|
6586
|
+
"markdown/front-matter-slides": () => ({
|
|
6587
|
+
message: "The front matter can't hold `slides`: the slides are the Markdown below it.",
|
|
6588
|
+
next: "remove the `slides` key and write the slides below the front matter, separated by `---`."
|
|
6589
|
+
}),
|
|
6590
|
+
"markdown/front-matter-comments": (finding2) => keep2(finding2, "add the deck properties as `key: value` lines, or remove the first `---` if the file starts with a slide."),
|
|
6591
|
+
"markdown/no-slides": (finding2) => keep2(finding2, "write a `# Title` line, or any content, then `---` between slides."),
|
|
6592
|
+
"markdown/comment-unterminated": (finding2) => ({ message: `The ${code("<!--")} on line ${line(finding2)} has no ${code("-->")}.`, next: "end the comment with `-->`." }),
|
|
6593
|
+
"markdown/fence-unterminated": (finding2) => {
|
|
6594
|
+
const fence = /Add a closing (`{3,}|~{3,}) line/.exec(finding2.help ?? "")?.[1] ?? "```";
|
|
6595
|
+
return { message: `The ${fence} fence opened on line ${line(finding2)} is never closed.`, next: `add a line with ${fence} to close it.` };
|
|
6596
|
+
},
|
|
6597
|
+
"markdown/code-fence": (finding2) => ({ message: `The code fence opened on line ${line(finding2)} could not be read as code.`, next: "close the fence with at least as many backticks as opened it." }),
|
|
6598
|
+
"markdown/chart-type": () => ({ message: "A chart fence needs its type after `chart`.", next: "write ```chart column (also `bar`, `line`, `pie`, \u2026), then the data." }),
|
|
6599
|
+
"markdown/chart-attributes": (finding2) => keep2(finding2, 'write ```chart column alt="What the data shows" (`alt` is optional).'),
|
|
6600
|
+
"markdown/chart-data": wholeHelp,
|
|
6601
|
+
"markdown/chart-ragged": (finding2) => keep2(finding2, "give every row one value per column."),
|
|
6602
|
+
"markdown/metric-block": (finding2) => keep2(finding2, `write ${code("key: value")} lines using ${METRIC_KEYS.join(", ")}; a value is plain text or a JSON string in double quotes.`),
|
|
6603
|
+
"markdown/timeline-attributes": (finding2) => keep2(finding2, 'write ```timeline name="Roadmap" description="\u2026" (both are optional).'),
|
|
6604
|
+
"markdown/timeline-description": (finding2) => keep2(finding2, "put the event first, then its description indented below it."),
|
|
6605
|
+
"markdown/timeline-events": (finding2) => keep2(finding2, "write one event per line, for example `2024 Q1 \u2014 Pilot`."),
|
|
6606
|
+
"markdown/slide-property-conflict": (finding2) => {
|
|
6607
|
+
const field = quoted(finding2.message, /property "(.*)" is set twice/) ?? finding2.path.split("/").at(-1) ?? "field";
|
|
6608
|
+
return { message: `${code(field)} is set both in the Markdown and in the \`opf-slide\` fence.`, next: "keep it in one of the two places." };
|
|
6609
|
+
},
|
|
6610
|
+
"markdown/duplicate-title": (finding2) => keep2(finding2, "use `---` to start a new slide, or `##` for the subtitle."),
|
|
6611
|
+
"markdown/duplicate-subtitle": (finding2) => keep2(finding2, "write further lines as paragraphs; a slide has one subtitle."),
|
|
6612
|
+
"markdown/empty-heading": (finding2) => keep2(finding2, "write the title after the `#`, or remove the line."),
|
|
6613
|
+
"markdown/empty-quote": (finding2) => keep2(finding2, "write the quote after `>`."),
|
|
6614
|
+
"markdown/image-source": (finding2) => keep2(finding2, "write the file or address inside the parentheses: ``."),
|
|
6615
|
+
"markdown/heading-demoted": (finding2) => keep2(finding2, "use `#` for the title and `##` for the subtitle; a bold paragraph on purpose silences this."),
|
|
6616
|
+
"markdown/empty-slide": (finding2) => keep2(finding2, "remove the extra `---`, or write `<!-- slide -->` to keep an empty slide."),
|
|
6617
|
+
"markdown/numbered-list": () => ({
|
|
6618
|
+
message: "Numbers in a list aren't kept: the list becomes bullets.",
|
|
6619
|
+
next: "add `numbering` to the slide in an `opf-slide` fence (see docs/numbered-lists.md), write the numbers into the text, or use a bulleted list."
|
|
6620
|
+
}),
|
|
6621
|
+
"markdown/table-ragged": (finding2) => keep2(finding2, "give every row the same number of cells as the header."),
|
|
6622
|
+
"markdown/formatting-dropped": (finding2) => keep2(finding2, "use a plain string here, or put the field in an `opf-slide` fence."),
|
|
6623
|
+
"markdown/front-matter": wholeHelp,
|
|
6624
|
+
"markdown/front-matter-not-mapping": wholeHelp,
|
|
6625
|
+
"markdown/opf-slide": wholeHelp,
|
|
6626
|
+
"markdown/opf-slide-not-mapping": wholeHelp,
|
|
6627
|
+
"markdown/opf-block": wholeHelp,
|
|
6628
|
+
"markdown/opf-block-not-mapping": wholeHelp
|
|
6629
|
+
};
|
|
6630
|
+
var yaml = Object.fromEntries(
|
|
6631
|
+
["yaml/syntax", "yaml/empty", "yaml/not-mapping", "yaml/multiple-documents", "yaml/duplicate-key", "yaml/key", "yaml/tag", "yaml/number", "yaml/alias", "yaml/alias-limit", "yaml/alias-cycle"].map((id) => [id, wholeHelp])
|
|
6632
|
+
);
|
|
6633
|
+
var SOURCE_TEXT = { ...yaml, ...markdown, ...options };
|
|
6634
|
+
var entry = (category, severity, summary, rationale, fix, problem, fixed, language = "markdown") => ({
|
|
6635
|
+
category,
|
|
6636
|
+
severity,
|
|
6637
|
+
summary,
|
|
6638
|
+
rationale,
|
|
6639
|
+
fix,
|
|
6640
|
+
...problem !== void 0 && fixed !== void 0 ? { example: { problem, fixed, language } } : {}
|
|
6641
|
+
});
|
|
6642
|
+
var YAML_WHY = "OPF reads JSON-compatible YAML 1.2, so a file that is not valid YAML cannot be opened at all; the finding says where the reader gave up.";
|
|
6643
|
+
var SOURCE_EXPLAIN = {
|
|
6644
|
+
"yaml/syntax": entry("format", "error", "The YAML text cannot be parsed.", YAML_WHY, "Fix the line the message names: indent with spaces, close quotes and brackets, give every key a colon and a value.", "slides:\n - title: a", "slides:\n - title: a", "yaml"),
|
|
6645
|
+
"yaml/empty": entry("format", "error", "The YAML document is empty.", "An empty file (or one that is all comments) holds no deck.", "Write `slides:` and at least one `- title:` line.", "# deck", "slides:\n - title: Title", "yaml"),
|
|
6646
|
+
"yaml/not-mapping": entry("format", "error", "The top level of the YAML is not a mapping.", "A deck is a mapping of properties (`name`, `slides`, ...); a bare list or value has nowhere to put them.", "Start with `key: value` lines, and put a list of slides under `slides:`.", "- title: a", "slides:\n - title: a", "yaml"),
|
|
6647
|
+
"yaml/multiple-documents": entry("format", "error", "The file holds more than one YAML document.", "An OPF file is exactly one document; a second `---` starts a document the reader would silently ignore.", "Remove the extra `---`, or put each deck in its own file.", "a: 1\n---\nb: 2", "a: 1\nb: 2", "yaml"),
|
|
6648
|
+
"yaml/duplicate-key": entry("format", "error", "A YAML mapping repeats a key.", "Parsers keep one value of a repeated key and drop the other, so an author loses content without a trace.", "Keep one of the two, or rename the one you meant as another key.", "name: A\nname: B", "name: B", "yaml"),
|
|
6649
|
+
"yaml/key": entry("format", "error", "A YAML mapping key is not a string.", "JSON keys are always strings; a number, a list or a null as a key cannot be carried.", 'Quote the key: `"1": value`.', "1: x", '"1": x', "yaml"),
|
|
6650
|
+
"yaml/tag": entry("format", "error", "A YAML tag is not allowed.", "OPF reads the YAML core schema; custom tags (`!!binary`, `!!set`) have no JSON form.", "Remove the tag; quote a value to keep it as text.", "a: !!set {x}", "a: x", "yaml"),
|
|
6651
|
+
"yaml/number": entry("format", "error", "A YAML number is not a finite number.", "JSON cannot carry `.inf` or `.nan`.", "Quote the value to keep it as text, or write a finite number.", "n: .inf", 'n: ".inf"', "yaml"),
|
|
6652
|
+
"yaml/alias": entry("format", "error", "A YAML anchor, alias or merge key is not allowed.", "Anchors and aliases make one value stand in for another, which OPF tools do not expand unless asked.", "Write each value out, or read the file with `aliases: true` (CLI: `--aliases`).", "a: &x 1\nb: *x", "a: 1\nb: 1", "yaml"),
|
|
6653
|
+
"yaml/alias-limit": entry("format", "error", "The anchors expand past the limit.", "Aliases that contain aliases grow exponentially; the reader stops at 100.", "Write the repeated values out, or reduce the aliasing.", void 0, void 0, "yaml"),
|
|
6654
|
+
"yaml/alias-cycle": entry("format", "error", "An alias refers to the value that contains it.", "A value that contains itself is infinitely deep and cannot be written as JSON.", "Write the nested value out.", void 0, void 0, "yaml"),
|
|
6655
|
+
"markdown/front-matter-unterminated": entry("format", "error", "The front matter is not closed.", "Without the closing `---` the reader cannot tell where the settings end and the first slide begins.", "Add a line with only `---` after the settings.", "---\nname: x\n\n# A", "---\nname: x\n---\n\n# A"),
|
|
6656
|
+
"markdown/front-matter": entry("format", "error", "The front matter is not valid YAML.", YAML_WHY, "Fix the line the message names.", "---\nname x\n---", "---\nname: x\n---"),
|
|
6657
|
+
"markdown/front-matter-not-mapping": entry("format", "error", "The front matter is not a mapping.", "Front matter holds deck properties as `key: value` lines.", "Write `key: value` lines.", "---\nname x\n---", "---\nname: x\n---"),
|
|
6658
|
+
"markdown/front-matter-slides": entry("format", "error", "The front matter holds `slides`.", "In Markdown the slides are the text below the front matter.", "Remove the `slides` key and write the slides below, separated by `---`.", "---\nslides: []\n---", "---\nname: x\n---"),
|
|
6659
|
+
"markdown/front-matter-comments": entry("format", "warning", "The front matter is only comments.", "A file that opens with `---` then only comments reads as empty front matter, not as slide content.", "Add the deck properties, or remove the first `---`.", void 0, void 0),
|
|
6660
|
+
"markdown/no-slides": entry("format", "error", "The Markdown has no slides.", "A deck needs at least one slide.", "Write a `# Title` line, or any content, then `---` between slides.", "", "# Title"),
|
|
6661
|
+
"markdown/options-syntax": entry("format", "error", "A slide or block options comment is malformed.", "Options are `key=value` pairs; a stray quote or missing value cannot be read.", "Write `<!-- slide: layout=title -->` with values bare or in double quotes.", "<!-- slide: layout= -->", "<!-- slide: layout=title -->"),
|
|
6662
|
+
"markdown/options-unknown-key": entry("format", "error", "An options comment names an option that does not exist.", "The dialect reads a fixed set of keys; an unknown one would be silently lost.", "Use one of the keys, or put other fields in an `opf-slide` fence.", "<!-- slide: layot=cover -->", "<!-- slide: layout=cover -->"),
|
|
6663
|
+
"markdown/options-value": entry("format", "error", "An option has a value it cannot take.", "Each option reads a specific kind of value, for example `hidden` is true or false.", "Write a value the option takes.", "<!-- slide: hidden=maybe -->", "<!-- slide: hidden=true -->"),
|
|
6664
|
+
"markdown/options-duplicate": entry("format", "error", "An option or an options comment is repeated.", "Two comments or two copies of one key leave it unclear which one is meant.", "Merge them into one.", "<!-- slide: layout=a layout=b -->", "<!-- slide: layout=b -->"),
|
|
6665
|
+
"markdown/options-orphan": entry("format", "error", "A block options comment is not followed by a block.", "The comment describes the block right after it.", "Put the comment directly before its block, or remove it."),
|
|
6666
|
+
"markdown/options-trailing": entry("format", "error", "Text follows an options comment on the same line.", "Everything after `-->` on that line would be ignored.", "Put the content on its own line."),
|
|
6667
|
+
"markdown/options-embedded": entry("format", "error", "A block options comment precedes an `opf-block` fence.", "The fence carries its own `id` and `type`.", "Put them inside the fence's YAML."),
|
|
6668
|
+
"markdown/comment-unterminated": entry("format", "error", "An HTML comment is never closed.", "Everything after an unclosed `<!--` would be swallowed into the comment.", "End the comment with `-->`.", "<!-- note", "<!-- note -->"),
|
|
6669
|
+
"markdown/fence-unterminated": entry("format", "error", "A fenced block is never closed.", "Everything after an unclosed fence would be read as its content.", "Add a closing fence line with at least as many backticks as opened it.", "```js\nx", "```js\nx\n```"),
|
|
6670
|
+
"markdown/code-fence": entry("format", "error", "A fenced block could not be read as code.", "A fence closes only with a line of at least as many backticks as opened it.", "Close the fence with at least as many backticks."),
|
|
6671
|
+
"markdown/chart-type": entry("format", "error", "A chart fence has no type.", "A chart needs its type (`column`, `bar`, `line`, `pie`, ...) to be drawn.", "Write the type after `chart`.", "```chart\n```", "```chart column\nCategory,Value\nA,1\n```"),
|
|
6672
|
+
"markdown/chart-attributes": entry("format", "error", "A chart fence has an attribute it cannot take.", "A chart fence takes `alt`, a text alternative in double quotes.", 'Write ```chart column alt="What the data shows".'),
|
|
6673
|
+
"markdown/chart-data": entry("format", "error", "A chart fence has no usable data.", "The chart is drawn from CSV with a header row, or a JSON object with `columns` and `rows`.", "Write CSV with a header row and at least one data row."),
|
|
6674
|
+
"markdown/chart-ragged": entry("format", "warning", "A chart row has a different number of values than the header.", "Missing cells plot as gaps.", "Give every row one value per column."),
|
|
6675
|
+
"markdown/metric-block": entry("format", "error", "A metric fence is malformed.", "A metric is `key: value` lines using a fixed set of keys.", "Write `value:` and, if you like, `label:`, `unit:`, `delta:`, `trend:` and `sentiment:`.", "```metric\nlabel: Revenue\n```", "```metric\nvalue: 42\nlabel: Revenue\n```"),
|
|
6676
|
+
"markdown/timeline-attributes": entry("format", "error", "A timeline fence has an attribute it cannot take.", "A timeline fence takes `name` and `description`.", "Remove the attribute, or use `name=` or `description=`."),
|
|
6677
|
+
"markdown/timeline-description": entry("format", "error", "An indented line has no event above it.", "A description belongs to the event before it.", "Put the event first, then its description indented below it."),
|
|
6678
|
+
"markdown/timeline-events": entry("format", "error", "A timeline fence has no events.", "A timeline is drawn from its events.", "Write one event per line, for example `2024 Q1 \u2014 Pilot`."),
|
|
6679
|
+
"markdown/opf-slide": entry("format", "error", "The YAML of an `opf-slide` fence is not valid.", YAML_WHY, "Fix the line the message names."),
|
|
6680
|
+
"markdown/opf-slide-not-mapping": entry("format", "error", "An `opf-slide` fence is not a mapping.", "The fence holds the slide's fields as `key: value` lines.", "Write `key: value` lines."),
|
|
6681
|
+
"markdown/opf-block": entry("format", "error", "The YAML of an `opf-block` fence is not valid.", YAML_WHY, "Fix the line the message names."),
|
|
6682
|
+
"markdown/opf-block-not-mapping": entry("format", "error", "An `opf-block` fence is not a mapping.", "The fence holds the block's fields as `key: value` lines.", "Write `key: value` lines."),
|
|
6683
|
+
"markdown/slide-property-conflict": entry("format", "error", "A slide field is set in the Markdown and in the `opf-slide` fence.", "Two values for one field leave it unclear which wins.", "Keep it in one place."),
|
|
6684
|
+
"markdown/duplicate-title": entry("format", "error", "A slide has more than one `#` title.", "A slide has one title; a second `#` is the start of the next slide.", "Use `---` to start a new slide, or `##` for the subtitle."),
|
|
6685
|
+
"markdown/duplicate-subtitle": entry("format", "error", "A slide has more than one `##` subtitle.", "A slide has one subtitle.", "Write further lines as paragraphs."),
|
|
6686
|
+
"markdown/empty-heading": entry("format", "error", "A heading has no text.", "An empty title is an empty field.", "Write the title after the `#`, or remove the line."),
|
|
6687
|
+
"markdown/empty-quote": entry("format", "error", "A block quote has no text.", "A quote without text draws nothing.", "Write the quote after `>`."),
|
|
6688
|
+
"markdown/image-source": entry("format", "error", "An image has no source.", "A picture needs a file or an address.", "Write it inside the parentheses: ``."),
|
|
6689
|
+
"markdown/heading-demoted": entry("format", "warning", "A heading below level 2 has no OPF form.", "Only `#` (title) and `##` (subtitle) are slide fields.", "Use a bold paragraph on purpose to silence this."),
|
|
6690
|
+
"markdown/empty-slide": entry("format", "warning", "Two `---` lines in a row gave an empty slide.", "The empty slide was skipped.", "Remove the extra `---`, or write `<!-- slide -->` to keep it."),
|
|
6691
|
+
"markdown/numbered-list": entry("format", "warning", "List numbers are not kept.", "The dialect does not read numbers as `numbering`, so each item became a bullet.", "Add `numbering` in an `opf-slide` fence, or use a bulleted list.", "1. one\n2. two", "```opf-slide\nitems:\n - one\n - two\nnumbering: decimal\n```"),
|
|
6692
|
+
"markdown/table-ragged": entry("format", "warning", "A table row has a different number of cells than the header.", "Missing cells are empty.", "Give every row the same number of cells."),
|
|
6693
|
+
"markdown/formatting-dropped": entry("format", "warning", "Inline formatting was dropped.", "The field takes a plain string.", "Use plain text here, or put the field in an `opf-slide` fence.")
|
|
6694
|
+
};
|
|
6695
|
+
|
|
6696
|
+
// src/finding-text.ts
|
|
6697
|
+
var ID = "an id of lowercase letters, digits and hyphens that starts with a letter, like `my-layout`";
|
|
6698
|
+
var ID_NEXT = "use lowercase letters, digits and hyphens, and start with a letter.";
|
|
6699
|
+
var LANGUAGE = "a language tag like `en-US` or `de`";
|
|
6700
|
+
var PATTERN_PHRASES = Object.freeze({
|
|
6701
|
+
"^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$": { what: "a hex color like `#1F4E79` (3, 6 or 8 hex digits)", next: "write the color as #RRGGBB." },
|
|
6702
|
+
"^[a-z][a-z0-9-]*$": { what: ID, next: ID_NEXT },
|
|
6703
|
+
"^[a-z0-9][a-z0-9-]*$": { what: "an id of lowercase letters, digits and hyphens, like `my-chart`", next: ID_NEXT },
|
|
6704
|
+
"^(?:[a-z][a-z0-9-]*:)?[a-z][a-z0-9-]*$": { what: "a catalog id like `pillars` or `acme:hero` (lowercase letters, digits and hyphens)", next: "use the id of a record, with the catalog group in front when it has one." },
|
|
6705
|
+
"^[a-zA-Z0-9][a-zA-Z0-9._-]*$": { what: "an id of letters, digits, dots, hyphens and underscores that starts with a letter or digit, like `logo-2`", next: "use letters, digits, dots, hyphens and underscores, without spaces." },
|
|
6706
|
+
"^[a-zA-Z0-9_-]+$": { what: "an id of letters, digits, hyphens and underscores, like `hero_1`", next: "remove spaces and other punctuation." },
|
|
6707
|
+
"^var:[a-z][a-z0-9-]*$": { what: "a variable reference like `var:brand`", next: "write `var:` and the id of a variable under `variables`." },
|
|
6708
|
+
"^var:organization(\\.[A-Za-z0-9_-]+)?\\.logo(\\.(full|stacked|icon|wordmark))?$": { what: "an organization logo reference like `var:organization.logo.full`", next: "write `var:organization.logo` and, if you want, `.full`, `.stacked`, `.icon` or `.wordmark`." },
|
|
6709
|
+
"^\\d{4}-\\d{2}-\\d{2}$": { what: "a date written year-month-day, like `2026-10-24`", next: "write the date as YYYY-MM-DD." },
|
|
6710
|
+
"^(?:https?://|mailto:|tel:)\\S+$": { what: "a web, email or phone address (`https://\u2026`, `mailto:\u2026` or `tel:\u2026`)", next: "start with https://, mailto: or tel:, and leave out spaces." },
|
|
6711
|
+
"^(https?://|mailto:|tel:)\\S+$": { what: "a web, email or phone address (`https://\u2026`, `mailto:\u2026` or `tel:\u2026`)", next: "start with https://, mailto: or tel:, and leave out spaces." },
|
|
6712
|
+
"^(asset:|https://|data:|\\./|\\.\\./)": { what: "a source that starts with `asset:`, `https://`, `data:`, `./` or `../`", next: "register the file under `assets` and write `asset:` and its id, or give an https:// address." },
|
|
6713
|
+
"^(?:https://[^\\s]+|pkg:[^\\s]+)$": { what: "an `https://` address or a `pkg:` reference", next: "write an address that starts with https:// or pkg:, without spaces." },
|
|
6714
|
+
"^(?:[A-Za-z]{2,3}|[iIxX])(?:-[A-Za-z0-9]{1,8})*$": { what: LANGUAGE, next: "write a language and, if needed, a region: `en-US`, `pt-BR`, `de`." },
|
|
6715
|
+
"^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$": { what: LANGUAGE, next: "write a language and, if needed, a region: `en-US`, `pt-BR`, `de`." },
|
|
6716
|
+
"^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{1,8})*$": { what: LANGUAGE, next: "write a language and, if needed, a region: `en-US`, `pt-BR`, `de`." },
|
|
6717
|
+
"^[0-9a-f]{64}$": { what: "a 64-character lowercase hex digest", next: "paste the SHA-256 digest in lowercase hex." },
|
|
6718
|
+
"^[^/]+\\.json$": { what: "a file name that ends in `.json`, without folders", next: "write a plain file name such as `pillars.json`." },
|
|
6719
|
+
"^[a-z][a-z0-9-]*\\.svg$": { what: "a file name like `pillars.svg`", next: "write the record id and `.svg`." },
|
|
6720
|
+
"^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$": { what: "a rule id of two parts like `opf/text-contrast`", next: "write the producer, a slash and the rule name." }
|
|
6721
|
+
});
|
|
6722
|
+
var hasHex = (pattern) => typeof pattern === "string" && /^\^#\(\?:\[0-9a-fA-F\]/.test(pattern);
|
|
6723
|
+
function helpersFor(finding2, context) {
|
|
6724
|
+
const segments = segmentsOf(finding2.path);
|
|
6725
|
+
return {
|
|
6726
|
+
segments,
|
|
6727
|
+
valueAt: (path) => context.valueAt ? context.valueAt([...path]) : readAt(context.document, path),
|
|
6728
|
+
get: (path) => context.valueAt ? context.valueAt([...path]) : readAt(context.document, path),
|
|
6729
|
+
chord: context.chord ?? "Ctrl+Space",
|
|
6730
|
+
format: context.format ?? "json"
|
|
6731
|
+
};
|
|
6732
|
+
}
|
|
6733
|
+
var attempt = (run, fallback) => {
|
|
6734
|
+
try {
|
|
6735
|
+
return run();
|
|
6736
|
+
} catch {
|
|
6737
|
+
return fallback;
|
|
6738
|
+
}
|
|
6739
|
+
};
|
|
6740
|
+
function schemaKeys(segments, context) {
|
|
6741
|
+
if (context.keysOf) return context.keysOf(segments);
|
|
6742
|
+
return attempt(() => [...new Set(schemaVariants(schemaAtPath(context.document, segments)).flatMap((variant) => Object.keys(variant.properties ?? {})))], []);
|
|
6743
|
+
}
|
|
6744
|
+
function starterText(schema, property, format) {
|
|
6745
|
+
const example = schema?.examples?.find((value) => typeof value === "string" && !value.includes("..."));
|
|
6746
|
+
const type = Array.isArray(schema?.type) ? schema?.type[0] : schema?.type;
|
|
6747
|
+
if (example !== void 0) return format === "json" ? JSON.stringify(example) : String(example);
|
|
6748
|
+
if (type === "object") return "{ \u2026 }";
|
|
6749
|
+
if (type === "array") return "[ \u2026 ]";
|
|
6750
|
+
if (type === "number" || type === "integer") return "0";
|
|
6751
|
+
if (type === "boolean") return "false";
|
|
6752
|
+
if (property === "id") return format === "json" ? '"acme"' : "acme";
|
|
6753
|
+
return format === "json" ? '"\u2026"' : "\u2026";
|
|
6754
|
+
}
|
|
6755
|
+
function describeSchema(finding2, context, helpers) {
|
|
6756
|
+
const { keyword, params: params2 = {}, alternatives } = finding2.validation ?? { keyword: "" };
|
|
6757
|
+
const segments = helpers.segments;
|
|
6758
|
+
const label = fieldLabel(segments);
|
|
6759
|
+
const actual = helpers.valueAt(segments);
|
|
6760
|
+
const chord = helpers.chord;
|
|
6761
|
+
const format = helpers.format;
|
|
6762
|
+
switch (keyword) {
|
|
6763
|
+
case "required": {
|
|
6764
|
+
const property = String(params2.missingProperty ?? "");
|
|
6765
|
+
const where = containerName(segments);
|
|
6766
|
+
const schema = context.schemaAt ? context.schemaAt([...segments, property]) : attempt(() => schemaAtPath(context.document, [...segments, property]), void 0);
|
|
6767
|
+
if (format === "json") {
|
|
6768
|
+
const sample2 = property === "slides" ? '[ { "title": "\u2026" } ]' : starterText(schema, property, "json");
|
|
6769
|
+
return { message: `${cap(where)} is missing ${code(property)}.`, next: `add "${property}": ${sample2}.` };
|
|
6770
|
+
}
|
|
6771
|
+
const sample = property === "slides" ? "slides:` with at least one `- title: \u2026" : `${property}: ${starterText(schema, property, "yaml")}`;
|
|
6772
|
+
return { message: `${cap(where)} is missing ${code(property)}.`, next: `add \`${sample}\`.` };
|
|
6773
|
+
}
|
|
6774
|
+
case "type": {
|
|
6775
|
+
const expected = typeWords(params2.type);
|
|
6776
|
+
const next = params2.type === "array" && typeof actual === "string" ? `write [${JSON.stringify(actual)}].` : params2.type === "string" && typeof actual === "number" ? `write ${JSON.stringify(String(actual))}.` : params2.type === "string" && typeof actual === "boolean" ? `write ${JSON.stringify(String(actual))}.` : `write ${expected}.`;
|
|
6777
|
+
return { message: `${cap(label)} should be ${expected}, but it is ${describeKind(actual)}.`, next };
|
|
6778
|
+
}
|
|
6779
|
+
case "oneOf":
|
|
6780
|
+
case "anyOf": {
|
|
6781
|
+
const forms = (alternatives ?? []).map((entry2) => String(entry2).split(":")[0]?.trim() ?? "");
|
|
6782
|
+
const words = forms.length ? [...new Set(forms.map((form) => WORDS[form] ?? form))].slice(0, 3).join(" or ") : "a different kind of value";
|
|
6783
|
+
return { message: `${cap(label)} should be ${words}, but it is ${describeKind(actual)}.`, next: forms.length ? `write ${words}.` : "check the schema reference for this field." };
|
|
6784
|
+
}
|
|
6785
|
+
case "enum": {
|
|
6786
|
+
const values = params2.allowedValues ?? finding2.suggestions?.map((entry2) => entry2.value) ?? [];
|
|
6787
|
+
const guess = nearest(actual, values).close[0];
|
|
6788
|
+
const is = actual === void 0 ? "" : `, but is ${describeValue(actual)}`;
|
|
6789
|
+
return {
|
|
6790
|
+
message: `${cap(label)} must be ${valueList(values)}${is}.`,
|
|
6791
|
+
next: guess !== void 0 ? `did you mean ${code(guess)}?` : values.length > 5 ? `press ${chord} for the ${values.length} values.` : "choose one of these values."
|
|
6792
|
+
};
|
|
6793
|
+
}
|
|
6794
|
+
case "const":
|
|
6795
|
+
return { message: `${cap(label)} must be exactly ${code(params2.allowedValue)}.`, next: `write ${JSON.stringify(params2.allowedValue)}.` };
|
|
6796
|
+
case "pattern": {
|
|
6797
|
+
const pattern = String(params2.pattern ?? "");
|
|
6798
|
+
const kind = context.catalogKind ? context.catalogKind(segments) : attempt(() => catalogKindAt(segments, context.document), void 0);
|
|
6799
|
+
actual === "" ? "it is empty" : `it is ${describeValue(actual)}`;
|
|
6800
|
+
if (kind) {
|
|
6801
|
+
const word = KIND_WORDS[kind] ?? "catalog";
|
|
6802
|
+
const count = context.catalogCount?.(kind);
|
|
6803
|
+
const ids = KIND_EXAMPLES[kind] ?? "`id` or `name:id`";
|
|
6804
|
+
const press = `press ${chord}${count ? ` for the ${count} ${word}s` : " for the available ids"}`;
|
|
6805
|
+
const orRemove = `, or remove it${kind === "layouts" ? " to let OPF pick" : ""}.`;
|
|
6806
|
+
if (actual === "") return { message: `${cap(label)} is empty.`, next: `${press} (an id such as ${ids})${orRemove}` };
|
|
6807
|
+
return {
|
|
6808
|
+
message: `${cap(label)} must be a ${word} id such as ${ids} (lowercase letters, digits and hyphens), but it is ${describeValue(actual)}.`,
|
|
6809
|
+
next: `${press}${orRemove}`
|
|
6810
|
+
};
|
|
6811
|
+
}
|
|
6812
|
+
const phrase = PATTERN_PHRASES[pattern];
|
|
6813
|
+
if (phrase) return { message: `${cap(label)} must be ${phrase.what}, but is ${describeValue(actual)}.`, next: phrase.next };
|
|
6814
|
+
if (hasHex(pattern)) return { message: `${cap(label)} must be a hex color like ${code("#1F4E79")} (3, 6 or 8 hex digits), but is ${describeValue(actual)}.`, next: "write the color as #RRGGBB." };
|
|
6815
|
+
return { message: `${cap(label)} does not match the expected format${typeof actual === "string" ? `: ${describeValue(actual)}` : ""}.`, next: "check the schema reference for this field." };
|
|
6816
|
+
}
|
|
6817
|
+
case "propertyNames": {
|
|
6818
|
+
const name = String(params2.propertyName ?? "");
|
|
6819
|
+
return { message: `${code(name)} is not a valid name here.`, next: "use letters, digits, hyphens and underscores, and start with a letter." };
|
|
6820
|
+
}
|
|
6821
|
+
case "additionalProperties": {
|
|
6822
|
+
const name = String(params2.additionalProperty ?? segments.at(-1) ?? "");
|
|
6823
|
+
const parent = segments.slice(0, -1);
|
|
6824
|
+
const allowed = schemaKeys(parent, context);
|
|
6825
|
+
const hint = nearest(name, allowed).close[0];
|
|
6826
|
+
return {
|
|
6827
|
+
message: `${code(name)} is not a property of ${containerName(parent)}.`,
|
|
6828
|
+
next: `${hint ? `did you mean ${code(hint)}? ` : ""}To keep custom data, put it under ${code("extensions")}.`.trim()
|
|
6829
|
+
};
|
|
6830
|
+
}
|
|
6831
|
+
case "minItems":
|
|
6832
|
+
return { message: `${cap(label)} needs at least ${params2.limit} item${params2.limit === 1 ? "" : "s"}.`, next: label === "`slides`" ? "add a slide." : "add an item." };
|
|
6833
|
+
case "maxItems":
|
|
6834
|
+
return { message: `${cap(label)} can hold at most ${params2.limit} items.`, next: "remove the extra ones." };
|
|
6835
|
+
case "minProperties":
|
|
6836
|
+
return { message: `${cap(label)} needs at least ${params2.limit} propert${params2.limit === 1 ? "y" : "ies"}.`, next: "add one, or remove the empty object." };
|
|
6837
|
+
case "uniqueItems":
|
|
6838
|
+
return { message: `${cap(label)} repeats an entry.`, next: "remove the repeated entry." };
|
|
6839
|
+
case "minLength":
|
|
6840
|
+
return { message: `${cap(label)} is empty.`, next: "write some text, or remove the key." };
|
|
6841
|
+
case "maxLength":
|
|
6842
|
+
return { message: `${cap(label)} is longer than ${params2.limit} characters.`, next: "shorten it." };
|
|
6843
|
+
case "minimum":
|
|
6844
|
+
case "exclusiveMinimum":
|
|
6845
|
+
case "maximum":
|
|
6846
|
+
case "exclusiveMaximum": {
|
|
6847
|
+
const bound = /minimum/i.test(keyword) ? "at least" : "at most";
|
|
6848
|
+
return {
|
|
6849
|
+
message: `${cap(label)} must be ${keyword.startsWith("exclusive") ? bound === "at least" ? "greater than" : "less than" : bound} ${params2.limit}, but is ${describeValue(actual)}.`,
|
|
6850
|
+
next: `write ${params2.limit}${keyword.startsWith("exclusive") ? " or a different number" : ""}.`
|
|
6851
|
+
};
|
|
6852
|
+
}
|
|
6853
|
+
case "format":
|
|
6854
|
+
return { message: `${cap(label)} must be a ${params2.format === "uri" ? "web address like https://example.com" : params2.format}.`, next: "check the value." };
|
|
6855
|
+
default:
|
|
6856
|
+
return void 0;
|
|
6857
|
+
}
|
|
6858
|
+
}
|
|
6859
|
+
function ruleText(finding2, context, helpers) {
|
|
6860
|
+
const handler = RULE_TEXT[finding2.ruleId] ?? SOURCE_TEXT[finding2.ruleId];
|
|
6861
|
+
return handler?.(finding2, context, helpers);
|
|
6862
|
+
}
|
|
6863
|
+
function describeFinding(finding2, context = {}) {
|
|
6864
|
+
const helpers = helpersFor(finding2, context);
|
|
6865
|
+
let described;
|
|
6866
|
+
try {
|
|
6867
|
+
if (finding2.ruleId === "opf/schema" && finding2.validation?.keyword) described = describeSchema(finding2, context, helpers);
|
|
6868
|
+
else described = ruleText(finding2, context, helpers);
|
|
6869
|
+
} catch {
|
|
6870
|
+
described = void 0;
|
|
6871
|
+
}
|
|
6872
|
+
if (described) return described.next === void 0 ? { message: described.message, ...fallbackNext(finding2) } : described;
|
|
6873
|
+
return { message: finding2.message, ...fallbackNext(finding2) };
|
|
6874
|
+
}
|
|
6875
|
+
function fallbackNext(finding2) {
|
|
6876
|
+
const help = firstSentence(finding2.help);
|
|
6877
|
+
return help ? { next: asNext(help) } : {};
|
|
6878
|
+
}
|
|
6879
|
+
function starterValue(document, segments, property) {
|
|
6880
|
+
if (property === "slides" && segments.length === 0) return [{ title: "Slide title" }];
|
|
6881
|
+
try {
|
|
6882
|
+
return createSchemaValue(schemaAtPath(document, [...segments, property]));
|
|
6883
|
+
} catch {
|
|
6884
|
+
return void 0;
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6887
|
+
|
|
6888
|
+
// src/schema-fixes.ts
|
|
6889
|
+
var MAX_CHANGES = 5;
|
|
6890
|
+
var patch = (id, title, operations, safe2 = false) => ({ id, title, kind: "patch", safe: safe2, patch: operations });
|
|
6891
|
+
var allowsKey = (document, segments, key2) => {
|
|
6892
|
+
try {
|
|
6893
|
+
return schemaVariants(schemaAtPath(document, segments)).some((variant) => Object.hasOwn(variant.properties ?? {}, key2));
|
|
6894
|
+
} catch {
|
|
6895
|
+
return false;
|
|
6896
|
+
}
|
|
6897
|
+
};
|
|
6898
|
+
function schemaFixes(finding2, document) {
|
|
6899
|
+
const validation = finding2.validation;
|
|
6900
|
+
if (finding2.ruleId !== "opf/schema" || !validation) return [];
|
|
6901
|
+
const segments = segmentsOf(finding2.path);
|
|
6902
|
+
const params2 = validation.params;
|
|
6903
|
+
switch (validation.keyword) {
|
|
6904
|
+
case "required": {
|
|
6905
|
+
const property = String(params2.missingProperty ?? "");
|
|
6906
|
+
if (!property || !isRecord2(readAt(document, segments)) || Object.hasOwn(readAt(document, segments), property)) return [];
|
|
6907
|
+
const value = starterValue(document, segments, property);
|
|
6908
|
+
if (value === void 0) return [];
|
|
6909
|
+
return [patch(`add:${property}`, `Add \`${property}\``, [{ op: "add", path: pointerOf([...segments, property]), value }])];
|
|
6910
|
+
}
|
|
6911
|
+
case "enum": {
|
|
6912
|
+
const allowed = Array.isArray(params2.allowedValues) ? params2.allowedValues : [];
|
|
6913
|
+
if (!allowed.length || readAt(document, segments) === void 0) return [];
|
|
6914
|
+
const current = readAt(document, segments);
|
|
6915
|
+
const values = [.../* @__PURE__ */ new Set([...nearest(current, allowed, MAX_CHANGES).close, ...allowed])].slice(0, MAX_CHANGES);
|
|
6916
|
+
return values.map((value) => patch(`change-to:${String(value)}`, `Change to ${String(value)}`, [{ op: "replace", path: finding2.path, value }]));
|
|
6917
|
+
}
|
|
6918
|
+
case "additionalProperties": {
|
|
6919
|
+
const name = String(params2.additionalProperty ?? segments.at(-1) ?? "");
|
|
6920
|
+
const parent = segments.slice(0, -1);
|
|
6921
|
+
const owner = readAt(document, parent);
|
|
6922
|
+
if (!name || !isRecord2(owner) || !Object.hasOwn(owner, name)) return [];
|
|
6923
|
+
const fixes = [];
|
|
6924
|
+
for (const suggestion of finding2.suggestions ?? []) {
|
|
6925
|
+
const target = String(suggestion.value);
|
|
6926
|
+
if (!Object.hasOwn(owner, target)) fixes.push(patch(`rename-to:${target}`, `Rename to ${target}`, [{ op: "move", from: finding2.path, path: pointerOf([...parent, target]) }]));
|
|
6927
|
+
}
|
|
6928
|
+
if (allowsKey(document, parent, "extensions") && name !== "extensions") {
|
|
6929
|
+
const existing = owner.extensions;
|
|
6930
|
+
if (existing === void 0) fixes.push(patch("move-to-extensions", "Move into extensions", [{ op: "add", path: pointerOf([...parent, "extensions"]), value: {} }, { op: "move", from: finding2.path, path: pointerOf([...parent, "extensions", name]) }]));
|
|
6931
|
+
else if (isRecord2(existing) && !Object.hasOwn(existing, name)) fixes.push(patch("move-to-extensions", "Move into extensions", [{ op: "move", from: finding2.path, path: pointerOf([...parent, "extensions", name]) }]));
|
|
6932
|
+
}
|
|
6933
|
+
return fixes;
|
|
6934
|
+
}
|
|
6935
|
+
case "type": {
|
|
6936
|
+
const current = readAt(document, segments);
|
|
6937
|
+
if (current === void 0) return [];
|
|
6938
|
+
if (params2.type === "array" && (typeof current === "string" || isRecord2(current))) return [patch("wrap-in-list", "Wrap in a list", [{ op: "replace", path: finding2.path, value: [current] }], typeof current === "string")];
|
|
6939
|
+
if (params2.type === "string" && (typeof current === "number" || typeof current === "boolean")) return [patch("convert-to-string", "Convert to string", [{ op: "replace", path: finding2.path, value: String(current) }], true)];
|
|
6940
|
+
return [];
|
|
6941
|
+
}
|
|
6942
|
+
case "oneOf":
|
|
6943
|
+
case "anyOf": {
|
|
6944
|
+
const current = readAt(document, segments);
|
|
6945
|
+
const forms = (validation.alternatives ?? []).map((entry2) => String(entry2).split(":")[0]?.trim());
|
|
6946
|
+
if (forms.includes("string") && (typeof current === "number" || typeof current === "boolean")) return [patch("convert-to-string", "Convert to string", [{ op: "replace", path: finding2.path, value: String(current) }], true)];
|
|
6947
|
+
return [];
|
|
6948
|
+
}
|
|
6949
|
+
case "minItems":
|
|
6950
|
+
if (finding2.path === "/slides" && Array.isArray(readAt(document, segments)) && params2.limit === 1) return [patch("add-slide", "Add a slide", [{ op: "add", path: "/slides/0", value: { title: "Slide title" } }])];
|
|
6951
|
+
return [];
|
|
6952
|
+
default:
|
|
6953
|
+
return [];
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
|
|
5356
6957
|
// src/script-measurement.ts
|
|
5357
|
-
function scriptProfile(presentation, slideIndex,
|
|
6958
|
+
function scriptProfile(presentation, slideIndex, options2, language) {
|
|
5358
6959
|
let resolved;
|
|
5359
6960
|
try {
|
|
5360
|
-
resolved = resolveScriptFonts(presentation, { slideIndex, ...
|
|
6961
|
+
resolved = resolveScriptFonts(presentation, { slideIndex, ...options2.catalogs !== void 0 ? { catalogs: options2.catalogs } : {}, ...language === void 0 ? {} : { language } });
|
|
5361
6962
|
} catch {
|
|
5362
6963
|
resolved = void 0;
|
|
5363
6964
|
}
|
|
5364
6965
|
const slots = (role) => {
|
|
5365
|
-
const latin =
|
|
6966
|
+
const latin = options2.fontFamilies[role];
|
|
5366
6967
|
const slot = (key2) => !resolved || resolved.sources[key2] === "latin" || resolved.sources[key2] === "schemeFamily" ? latin : resolved[role][key2] ?? latin;
|
|
5367
6968
|
return { latin, eastAsian: slot("eastAsian"), complexScript: slot("complexScript") };
|
|
5368
6969
|
};
|
|
@@ -5373,13 +6974,13 @@ function scriptProfile(presentation, slideIndex, options, language) {
|
|
|
5373
6974
|
script: resolved?.script ?? "Zzzz",
|
|
5374
6975
|
...resolved ? { scriptRole: resolved.scriptRole, bcp47: resolved.bcp47, lang: resolved.lang, languageSource: resolved.languageSource, direction: resolved.direction } : {},
|
|
5375
6976
|
rtl: resolved?.rtl === true,
|
|
5376
|
-
serif:
|
|
6977
|
+
serif: options2.serif
|
|
5377
6978
|
};
|
|
5378
6979
|
}
|
|
5379
|
-
function slideScriptMeasurement(presentation, slideIndex, measurement,
|
|
6980
|
+
function slideScriptMeasurement(presentation, slideIndex, measurement, options2) {
|
|
5380
6981
|
const forScripts = measurement.forScripts;
|
|
5381
6982
|
if (typeof forScripts !== "function") return measurement;
|
|
5382
|
-
const deckProfile = scriptProfile(presentation, slideIndex,
|
|
6983
|
+
const deckProfile = scriptProfile(presentation, slideIndex, options2);
|
|
5383
6984
|
const deck = forScripts.call(measurement, deckProfile);
|
|
5384
6985
|
const languages = /* @__PURE__ */ new Map();
|
|
5385
6986
|
const forLanguage = (lang) => {
|
|
@@ -5387,7 +6988,7 @@ function slideScriptMeasurement(presentation, slideIndex, measurement, options)
|
|
|
5387
6988
|
if (!languages.has(lang)) {
|
|
5388
6989
|
let own4;
|
|
5389
6990
|
try {
|
|
5390
|
-
const profile = scriptProfile(presentation, slideIndex,
|
|
6991
|
+
const profile = scriptProfile(presentation, slideIndex, options2, lang);
|
|
5391
6992
|
own4 = profile.languageSource === "option" && profile.bcp47?.toLowerCase() !== deckProfile.bcp47?.toLowerCase() ? forScripts.call(measurement, profile) : void 0;
|
|
5392
6993
|
} catch {
|
|
5393
6994
|
own4 = void 0;
|
|
@@ -5397,31 +6998,31 @@ function slideScriptMeasurement(presentation, slideIndex, measurement, options)
|
|
|
5397
6998
|
return languages.get(lang);
|
|
5398
6999
|
};
|
|
5399
7000
|
const pick = (style) => forLanguage(style?.lang) ?? deck;
|
|
5400
|
-
const scripted = { ...deck, measure: (
|
|
7001
|
+
const scripted = { ...deck, measure: (text2, size, style) => pick(style).measure(text2, size, style) };
|
|
5401
7002
|
if (typeof deck.resolveStyle === "function") scripted.resolveStyle = (style) => deck.resolveStyle.call(deck, style);
|
|
5402
7003
|
if (typeof deck.outlineBounds === "function")
|
|
5403
|
-
scripted.outlineBounds = (
|
|
7004
|
+
scripted.outlineBounds = (text2, size, style) => {
|
|
5404
7005
|
const own4 = pick(style);
|
|
5405
|
-
return typeof own4.outlineBounds === "function" ? own4.outlineBounds(
|
|
7006
|
+
return typeof own4.outlineBounds === "function" ? own4.outlineBounds(text2, size, style) : deck.outlineBounds.call(deck, text2, size, style);
|
|
5406
7007
|
};
|
|
5407
7008
|
return scripted;
|
|
5408
7009
|
}
|
|
5409
|
-
function resolveSlideScriptMeasurement(presentation, slideIndex, measurement,
|
|
7010
|
+
function resolveSlideScriptMeasurement(presentation, slideIndex, measurement, options2 = {}) {
|
|
5410
7011
|
if (typeof measurement.forScripts !== "function") return measurement;
|
|
5411
|
-
const { fontScheme } = resolveDesignRecords(presentation, slideIndex,
|
|
7012
|
+
const { fontScheme } = resolveDesignRecords(presentation, slideIndex, options2.catalogs !== void 0 ? { catalogs: options2.catalogs } : {});
|
|
5412
7013
|
const resolved = resolveFontFamilies(fontScheme);
|
|
5413
|
-
const fontFamilies = { heading:
|
|
5414
|
-
return slideScriptMeasurement(presentation, slideIndex, measurement, { ...
|
|
7014
|
+
const fontFamilies = { heading: options2.fontFamilies?.heading ?? resolved.heading, body: options2.fontFamilies?.body ?? resolved.body };
|
|
7015
|
+
return slideScriptMeasurement(presentation, slideIndex, measurement, { ...options2.catalogs !== void 0 ? { catalogs: options2.catalogs } : {}, fontFamilies, serif: fontScheme.type === "serif" });
|
|
5415
7016
|
}
|
|
5416
7017
|
|
|
5417
7018
|
// src/slide-context.ts
|
|
5418
7019
|
var rec2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5419
|
-
function resolveSlideContext(presentation, index,
|
|
7020
|
+
function resolveSlideContext(presentation, index, options2 = {}) {
|
|
5420
7021
|
const deck = rec2(presentation);
|
|
5421
7022
|
const slides = Array.isArray(deck.slides) ? deck.slides : [];
|
|
5422
7023
|
if (!Number.isInteger(index) || index < 0 || index >= slides.length) throw new RangeError(`Slide index ${String(index)} is outside the presentation's ${slides.length} slides.`);
|
|
5423
7024
|
const slide = rec2(slides[index]);
|
|
5424
|
-
const catalogs = { catalogs: checkCatalogsOption(
|
|
7025
|
+
const catalogs = { catalogs: checkCatalogsOption(options2.catalogs, "resolveSlideContext") };
|
|
5425
7026
|
const records = resolveDesignRecords(presentation, index, catalogs);
|
|
5426
7027
|
const diagnostics = [...records.diagnostics];
|
|
5427
7028
|
const { theme, colorScheme, fontScheme } = records;
|
|
@@ -5435,30 +7036,30 @@ function resolveSlideContext(presentation, index, options = {}) {
|
|
|
5435
7036
|
layoutProvenance = provenanceOf(found);
|
|
5436
7037
|
} else diagnostics.push(unresolvedReference(presentation, "layouts", slide.layout, `slides.${index}.layout`, catalogs));
|
|
5437
7038
|
}
|
|
5438
|
-
if (
|
|
7039
|
+
if (options2.strictReferences && diagnostics.length) throw new OPFUnresolvedReferenceError(diagnostics);
|
|
5439
7040
|
const roles = resolveColorRoles(colorScheme, colorChoice([rec2(slide.design), rec2(deck.design), theme], colorScheme, rec2(deck.variables)));
|
|
5440
7041
|
const composeOptions = {
|
|
5441
7042
|
...resolveCanvasDimensions(design.dimensions ?? theme.dimensions),
|
|
5442
7043
|
...layout ? { layout } : {},
|
|
5443
7044
|
presentation,
|
|
5444
7045
|
slideIndex: index,
|
|
5445
|
-
slideNumber:
|
|
5446
|
-
slideCount:
|
|
7046
|
+
slideNumber: options2.slideNumber ?? index + 1,
|
|
7047
|
+
slideCount: options2.slideCount ?? slides.length,
|
|
5447
7048
|
fontFamilies: resolveFontFamilies(fontScheme),
|
|
5448
7049
|
darkBackground: roles.dark,
|
|
5449
7050
|
...theme.background !== void 0 ? { themeBackground: theme.background } : {},
|
|
5450
7051
|
// OPF 0.19: a theme may carry the shared design keys (between the deck's design and the layout record's).
|
|
5451
7052
|
...theme.design !== void 0 && rec2(theme.design) === theme.design ? { themeDesign: theme.design } : {},
|
|
5452
|
-
...
|
|
5453
|
-
...
|
|
7053
|
+
...options2.fonts?.textMeasurement ? { textMeasurement: options2.fonts.textMeasurement } : {},
|
|
7054
|
+
...options2.date !== void 0 ? { date: options2.date } : {}
|
|
5454
7055
|
};
|
|
5455
7056
|
const resolvedSlide = resolveSlideVariables(slide, { slideNumber: composeOptions.slideNumber, slideCount: composeOptions.slideCount, presentation, darkBackground: composeOptions.darkBackground === true });
|
|
5456
7057
|
return { slide: resolvedSlide, options: composeOptions, diagnostics, resolved: { theme, colorScheme, roles, fontScheme, fontSchemePath: records.fontSchemePath, ...layout ? { layout } : {}, provenance: { ...layoutProvenance ? { layout: layoutProvenance } : {}, ...records.provenance } } };
|
|
5457
7058
|
}
|
|
5458
7059
|
|
|
5459
7060
|
// src/rule-slides.ts
|
|
5460
|
-
var measurementFor = (
|
|
5461
|
-
const measurement =
|
|
7061
|
+
var measurementFor = (options2, index) => {
|
|
7062
|
+
const measurement = options2.fonts?.textMeasurement;
|
|
5462
7063
|
return typeof measurement === "function" ? measurement(index) : measurement;
|
|
5463
7064
|
};
|
|
5464
7065
|
var lazy = (compute) => {
|
|
@@ -5485,16 +7086,16 @@ function relax(value) {
|
|
|
5485
7086
|
walk2(copy);
|
|
5486
7087
|
return copy;
|
|
5487
7088
|
}
|
|
5488
|
-
function buildSlides(document,
|
|
7089
|
+
function buildSlides(document, options2) {
|
|
5489
7090
|
const slides = Array.isArray(document.slides) ? document.slides : [];
|
|
5490
7091
|
return slides.map((slide, index) => {
|
|
5491
7092
|
const path = pointer2("slides", index);
|
|
5492
|
-
const baseContext = lazy(() => resolveSlideContext(document, index, { catalogs:
|
|
7093
|
+
const baseContext = lazy(() => resolveSlideContext(document, index, { catalogs: options2.catalogs, date: "2000-01-01" }));
|
|
5493
7094
|
const measurement = lazy(() => {
|
|
5494
|
-
const host = measurementFor(
|
|
7095
|
+
const host = measurementFor(options2, index);
|
|
5495
7096
|
if (!host) return void 0;
|
|
5496
7097
|
const { resolved: resolved2 } = baseContext();
|
|
5497
|
-
return slideScriptMeasurement(document, index, host, { catalogs:
|
|
7098
|
+
return slideScriptMeasurement(document, index, host, { catalogs: options2.catalogs, fontFamilies: resolveFontFamilies(resolved2.fontScheme), serif: resolved2.fontScheme.type === "serif" });
|
|
5498
7099
|
});
|
|
5499
7100
|
const resolved = lazy(() => {
|
|
5500
7101
|
const textMeasurement = measurement();
|
|
@@ -5549,7 +7150,6 @@ function buildSlides(document, options) {
|
|
|
5549
7150
|
// src/validator.ts
|
|
5550
7151
|
var SEVERITIES = ["error", "warning", "info"];
|
|
5551
7152
|
var OPTION_KEYS = ["only", "ignore", "severity", "ignorePaths", "catalogs", "contracts", "thresholds", "fonts", "chartPalette", "template", "values"];
|
|
5552
|
-
var DOCS = "https://github.com/OpenPresentation/opf/blob/main/docs/validate.md";
|
|
5553
7153
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5554
7154
|
var OPFValidationError = class extends Error {
|
|
5555
7155
|
/** The full report that failed. */
|
|
@@ -5557,7 +7157,7 @@ var OPFValidationError = class extends Error {
|
|
|
5557
7157
|
/** The error-severity findings of the report. */
|
|
5558
7158
|
findings;
|
|
5559
7159
|
constructor(report2) {
|
|
5560
|
-
const errors = report2.findings.filter((
|
|
7160
|
+
const errors = report2.findings.filter((entry2) => entry2.severity === "error");
|
|
5561
7161
|
const first = errors[0];
|
|
5562
7162
|
super(first ? `OPF validation failed at ${first.path || "/"}: ${first.message}` : "OPF validation failed");
|
|
5563
7163
|
this.name = "OPFValidationError";
|
|
@@ -5573,7 +7173,7 @@ function ruleFor(name, what) {
|
|
|
5573
7173
|
}
|
|
5574
7174
|
function names(list, what) {
|
|
5575
7175
|
if (list === void 0) return void 0;
|
|
5576
|
-
if (!Array.isArray(list) || !list.every((
|
|
7176
|
+
if (!Array.isArray(list) || !list.every((entry2) => typeof entry2 === "string")) throw new TypeError(`Validate ${what} must be an array of rule ids or category names.`);
|
|
5577
7177
|
const result = { rules: /* @__PURE__ */ new Set(), categories: /* @__PURE__ */ new Set() };
|
|
5578
7178
|
for (const name of list) {
|
|
5579
7179
|
const found = ruleFor(name, what);
|
|
@@ -5582,65 +7182,65 @@ function names(list, what) {
|
|
|
5582
7182
|
}
|
|
5583
7183
|
return result;
|
|
5584
7184
|
}
|
|
5585
|
-
function resolveOptions(
|
|
5586
|
-
if (!isObject2(
|
|
5587
|
-
for (const key2 of Object.keys(
|
|
5588
|
-
checkDocumentOptions(
|
|
5589
|
-
const only = names(
|
|
5590
|
-
const ignore = names(
|
|
7185
|
+
function resolveOptions(options2) {
|
|
7186
|
+
if (!isObject2(options2)) throw new TypeError("Validate options must be an object.");
|
|
7187
|
+
for (const key2 of Object.keys(options2)) if (!OPTION_KEYS.includes(key2)) throw new TypeError(`Unknown validate option ${JSON.stringify(key2)}. Options: ${OPTION_KEYS.join(", ")}.`);
|
|
7188
|
+
checkDocumentOptions(options2);
|
|
7189
|
+
const only = names(options2.only, "only");
|
|
7190
|
+
const ignore = names(options2.ignore, "ignore");
|
|
5591
7191
|
const severity = /* @__PURE__ */ new Map();
|
|
5592
|
-
if (
|
|
5593
|
-
if (!isObject2(
|
|
5594
|
-
for (const [name, value] of Object.entries(
|
|
7192
|
+
if (options2.severity !== void 0) {
|
|
7193
|
+
if (!isObject2(options2.severity)) throw new TypeError('Validate severity must map rule ids or category names to a severity or "off".');
|
|
7194
|
+
for (const [name, value] of Object.entries(options2.severity)) {
|
|
5595
7195
|
if (typeof value !== "string" || value !== "off" && !SEVERITIES.includes(value)) throw new TypeError(`Validate severity for ${JSON.stringify(name)} must be error, warning, info or off.`);
|
|
5596
7196
|
const found = ruleFor(name, "severity");
|
|
5597
7197
|
severity.set(typeof found === "string" ? found : found.id, value);
|
|
5598
7198
|
}
|
|
5599
7199
|
}
|
|
5600
7200
|
const ignorePaths = [];
|
|
5601
|
-
if (
|
|
5602
|
-
if (!Array.isArray(
|
|
5603
|
-
for (const
|
|
5604
|
-
if (!isObject2(
|
|
7201
|
+
if (options2.ignorePaths !== void 0) {
|
|
7202
|
+
if (!Array.isArray(options2.ignorePaths)) throw new TypeError("Validate ignorePaths must be an array of {rule, path}.");
|
|
7203
|
+
for (const entry2 of options2.ignorePaths) {
|
|
7204
|
+
if (!isObject2(entry2) || typeof entry2.rule !== "string" || typeof entry2.path !== "string" || entry2.path !== "" && !entry2.path.startsWith("/"))
|
|
5605
7205
|
throw new TypeError('Each validate ignorePaths entry needs a rule id, category name or "*", and a JSON Pointer path.');
|
|
5606
|
-
const found =
|
|
7206
|
+
const found = entry2.rule === "*" ? "*" : ruleFor(entry2.rule, "ignorePaths");
|
|
5607
7207
|
ignorePaths.push({
|
|
5608
7208
|
match: (info) => found === "*" || (typeof found === "string" ? found === info.category : found.id === info.id),
|
|
5609
|
-
path: splitPointer(
|
|
7209
|
+
path: splitPointer(entry2.path)
|
|
5610
7210
|
});
|
|
5611
7211
|
}
|
|
5612
7212
|
}
|
|
5613
7213
|
const thresholds = { ...DEFAULT_VALIDATION_THRESHOLDS };
|
|
5614
|
-
if (
|
|
5615
|
-
if (!isObject2(
|
|
5616
|
-
for (const [key2, value] of Object.entries(
|
|
7214
|
+
if (options2.thresholds !== void 0) {
|
|
7215
|
+
if (!isObject2(options2.thresholds)) throw new TypeError("Validate thresholds must be an object.");
|
|
7216
|
+
for (const [key2, value] of Object.entries(options2.thresholds)) {
|
|
5617
7217
|
if (!(key2 in DEFAULT_VALIDATION_THRESHOLDS)) throw new TypeError(`Unknown validate threshold ${JSON.stringify(key2)}. Thresholds: ${Object.keys(DEFAULT_VALIDATION_THRESHOLDS).join(", ")}.`);
|
|
5618
7218
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new TypeError(`Validate threshold ${key2} must be a positive number.`);
|
|
5619
7219
|
thresholds[key2] = value;
|
|
5620
7220
|
}
|
|
5621
7221
|
}
|
|
5622
|
-
if (
|
|
5623
|
-
if (!isObject2(
|
|
5624
|
-
const measurement =
|
|
7222
|
+
if (options2.fonts !== void 0) {
|
|
7223
|
+
if (!isObject2(options2.fonts)) throw new TypeError("Validate fonts must be a fonts object with a textMeasurement.");
|
|
7224
|
+
const measurement = options2.fonts.textMeasurement;
|
|
5625
7225
|
if (measurement !== void 0 && typeof measurement !== "function" && !isObject2(measurement)) throw new TypeError("Validate fonts.textMeasurement must be a text measurement, or a function of the slide index.");
|
|
5626
7226
|
}
|
|
5627
|
-
if (
|
|
7227
|
+
if (options2.chartPalette !== void 0 && (!Array.isArray(options2.chartPalette) || !options2.chartPalette.every((color) => typeof color === "string" && /^#[0-9a-fA-F]{6}$/.test(color))))
|
|
5628
7228
|
throw new TypeError("Validate chartPalette must be an array of #RRGGBB colours.");
|
|
5629
|
-
if (
|
|
5630
|
-
if (
|
|
7229
|
+
if (options2.template !== void 0 && typeof options2.template !== "boolean") throw new TypeError("Validate template must be true or false.");
|
|
7230
|
+
if (options2.values !== void 0 && !isObject2(options2.values)) throw new TypeError("Validate values must be an object keyed by variable id.");
|
|
5631
7231
|
const level = (info) => severity.get(info.id) ?? severity.get(info.category);
|
|
5632
7232
|
const off = (info) => level(info) === "off" || !!ignore?.rules.has(info.id) || !!ignore?.categories.has(info.category);
|
|
5633
7233
|
return {
|
|
5634
|
-
options,
|
|
7234
|
+
options: options2,
|
|
5635
7235
|
off,
|
|
5636
7236
|
enabled: (info) => !off(info) && (!only || only.rules.has(info.id) || only.categories.has(info.category)),
|
|
5637
7237
|
severity: (info) => {
|
|
5638
7238
|
const value = level(info);
|
|
5639
7239
|
return value === "off" ? void 0 : value;
|
|
5640
7240
|
},
|
|
5641
|
-
ignored: (info, path) => ignorePaths.some((
|
|
7241
|
+
ignored: (info, path) => ignorePaths.some((entry2) => entry2.match(info) && entry2.path.every((part, index) => path[index] === part)),
|
|
5642
7242
|
thresholds,
|
|
5643
|
-
chartPalette:
|
|
7243
|
+
chartPalette: options2.chartPalette
|
|
5644
7244
|
};
|
|
5645
7245
|
}
|
|
5646
7246
|
var order = new Map(validationRules.map((info, index) => [info.id, index]));
|
|
@@ -5648,25 +7248,25 @@ var categoryOrder = (category) => {
|
|
|
5648
7248
|
const index = validationCategories.indexOf(category);
|
|
5649
7249
|
return index < 0 ? validationCategories.length : index;
|
|
5650
7250
|
};
|
|
5651
|
-
var auditInfos = ruleImplementations.flatMap((
|
|
7251
|
+
var auditInfos = ruleImplementations.flatMap((entry2) => [entry2.info, ...entry2.also ?? []]);
|
|
5652
7252
|
var isLayoutRule = (info) => info.category === "layout" && info.cost === "composition";
|
|
5653
7253
|
function slideOf(path) {
|
|
5654
|
-
const
|
|
5655
|
-
return
|
|
7254
|
+
const match2 = /^\/slides\/(\d+)(?:\/|$)/.exec(path);
|
|
7255
|
+
return match2 ? Number(match2[1]) : void 0;
|
|
5656
7256
|
}
|
|
5657
7257
|
function counts(findings) {
|
|
5658
7258
|
const result = { error: 0, warning: 0, info: 0 };
|
|
5659
|
-
for (const
|
|
7259
|
+
for (const entry2 of findings) result[entry2.severity]++;
|
|
5660
7260
|
return result;
|
|
5661
7261
|
}
|
|
5662
7262
|
var FIXED = { backgroundPixels: "not-read", imageBytes: "embedded-only", nativeExport: "not-checked" };
|
|
5663
7263
|
function syntaxReport(parsed, run) {
|
|
5664
7264
|
const findings = [];
|
|
5665
|
-
for (const
|
|
5666
|
-
const info = findValidationRule(
|
|
7265
|
+
for (const entry2 of parsed.syntax) {
|
|
7266
|
+
const info = findValidationRule(entry2.ruleId);
|
|
5667
7267
|
if (info && run.off(info)) continue;
|
|
5668
7268
|
const severity = info ? run.severity(info) : void 0;
|
|
5669
|
-
findings.push(severity ? { ...
|
|
7269
|
+
findings.push(severity ? { ...entry2, severity } : entry2);
|
|
5670
7270
|
}
|
|
5671
7271
|
const checks = {
|
|
5672
7272
|
syntax: "checked",
|
|
@@ -5693,11 +7293,11 @@ function validateDocument(document, run, parsed) {
|
|
|
5693
7293
|
if (enabledRule(findValidationRule("opf/variable-unfilled"))) findings.push(...variableFindings(document, engine, schemaValid));
|
|
5694
7294
|
if (enabledRule(findValidationRule("opf/undeclared-catalog"))) findings.push(...undeclaredCatalogFindings(document));
|
|
5695
7295
|
if (parsed) {
|
|
5696
|
-
findings.push(...parsed.duplicates.filter((
|
|
7296
|
+
findings.push(...parsed.duplicates.filter((entry2) => enabledRule(findValidationRule(entry2.ruleId))));
|
|
5697
7297
|
}
|
|
5698
7298
|
const referencesWanted = validationRules.some((info) => info.category === "references" && run.enabled(info));
|
|
5699
7299
|
if (referencesWanted) {
|
|
5700
|
-
findings.push(...referenceFindings(document, engine, run.options).filter((
|
|
7300
|
+
findings.push(...referenceFindings(document, engine, run.options).filter((entry2) => enabledRule(findValidationRule(entry2.ruleId))));
|
|
5701
7301
|
}
|
|
5702
7302
|
const contracts = run.options.contracts ?? [];
|
|
5703
7303
|
const contractRule = findValidationRule("opf/contract");
|
|
@@ -5716,7 +7316,7 @@ function validateDocument(document, run, parsed) {
|
|
|
5716
7316
|
chartPalette: run.chartPalette,
|
|
5717
7317
|
report(info, input) {
|
|
5718
7318
|
const severity = run.severity(info) ?? input.severity ?? info.severity;
|
|
5719
|
-
const
|
|
7319
|
+
const entry2 = {
|
|
5720
7320
|
ruleId: info.id,
|
|
5721
7321
|
severity,
|
|
5722
7322
|
category: info.category,
|
|
@@ -5724,28 +7324,28 @@ function validateDocument(document, run, parsed) {
|
|
|
5724
7324
|
path: input.path,
|
|
5725
7325
|
message: input.message,
|
|
5726
7326
|
help: input.help,
|
|
5727
|
-
definition:
|
|
7327
|
+
definition: ruleDocumentation(info.id),
|
|
5728
7328
|
...input.slide ? { slide: input.slide.index, ...input.slide.id ? { slideId: input.slide.id } : {} } : {},
|
|
5729
7329
|
...input.measured ? { measured: input.measured } : {},
|
|
5730
7330
|
...input.fixes?.length ? { fixes: input.fixes } : {},
|
|
5731
7331
|
...input.suggestions ? { suggestions: input.suggestions } : {}
|
|
5732
7332
|
};
|
|
5733
|
-
if (run.enabled(info)) findings.push(
|
|
5734
|
-
return
|
|
7333
|
+
if (run.enabled(info)) findings.push(entry2);
|
|
7334
|
+
return entry2;
|
|
5735
7335
|
}
|
|
5736
7336
|
};
|
|
5737
|
-
for (const
|
|
5738
|
-
const all = [
|
|
7337
|
+
for (const entry2 of ruleImplementations) {
|
|
7338
|
+
const all = [entry2.info, ...entry2.also ?? []];
|
|
5739
7339
|
if (!all.some((info) => run.enabled(info))) continue;
|
|
5740
7340
|
for (const info of all) if (run.enabled(info)) auditRan.add(info.id);
|
|
5741
|
-
|
|
7341
|
+
entry2.run(context);
|
|
5742
7342
|
}
|
|
5743
7343
|
}
|
|
5744
7344
|
const finished = [];
|
|
5745
|
-
for (const
|
|
5746
|
-
const info = findValidationRule(
|
|
7345
|
+
for (const entry2 of findings) {
|
|
7346
|
+
const info = findValidationRule(entry2.ruleId);
|
|
5747
7347
|
const override = info ? run.severity(info) : void 0;
|
|
5748
|
-
const next = override && override !==
|
|
7348
|
+
const next = override && override !== entry2.severity ? { ...entry2, severity: override } : entry2;
|
|
5749
7349
|
if (info && run.ignored(info, splitPointer(next.path))) continue;
|
|
5750
7350
|
const index = slideOf(next.path);
|
|
5751
7351
|
if (next.slide === void 0 && index !== void 0 && next.scope !== "context") {
|
|
@@ -5757,6 +7357,10 @@ function validateDocument(document, run, parsed) {
|
|
|
5757
7357
|
const location = parsed.locate(next.path);
|
|
5758
7358
|
if (location) next.location = location;
|
|
5759
7359
|
}
|
|
7360
|
+
if (next.ruleId === "opf/schema" && next.fixes === void 0 && next.scope !== "context") {
|
|
7361
|
+
const fixes = schemaFixes(next, document);
|
|
7362
|
+
if (fixes.length) next.fixes = fixes;
|
|
7363
|
+
}
|
|
5760
7364
|
finished.push(next);
|
|
5761
7365
|
}
|
|
5762
7366
|
finished.sort(
|
|
@@ -5784,17 +7388,17 @@ function validateDocument(document, run, parsed) {
|
|
|
5784
7388
|
...engine.template !== void 0 ? { template: engine.template, unfilledVariables: engine.unfilledVariables } : {}
|
|
5785
7389
|
};
|
|
5786
7390
|
}
|
|
5787
|
-
function validate(input,
|
|
5788
|
-
const run = resolveOptions(
|
|
7391
|
+
function validate(input, options2 = {}) {
|
|
7392
|
+
const run = resolveOptions(options2);
|
|
5789
7393
|
if (typeof input === "string") {
|
|
5790
7394
|
const parsed = parseSource(input);
|
|
5791
7395
|
return parsed.syntax.length ? syntaxReport(parsed, run) : validateDocument(parsed.value, run, parsed);
|
|
5792
7396
|
}
|
|
5793
7397
|
return validateDocument(input, run);
|
|
5794
7398
|
}
|
|
5795
|
-
function assertValid(value,
|
|
7399
|
+
function assertValid(value, options2 = {}) {
|
|
5796
7400
|
if (typeof value === "string") throw new TypeError("assertValid takes a parsed presentation. Use validate(text) for JSON text.");
|
|
5797
|
-
const report2 = validate(value,
|
|
7401
|
+
const report2 = validate(value, options2);
|
|
5798
7402
|
if (!report2.valid) throw new OPFValidationError(report2);
|
|
5799
7403
|
}
|
|
5800
7404
|
function validateCatalogRecord(kind, value) {
|
|
@@ -5820,4 +7424,4 @@ function assertValidCatalogRecord(kind, value) {
|
|
|
5820
7424
|
if (!report2.valid) throw new OPFValidationError(report2);
|
|
5821
7425
|
}
|
|
5822
7426
|
|
|
5823
|
-
export { CATALOG_SCHEMA, COMMON_KEYS, DEFAULT_VALIDATION_THRESHOLDS, OPFPatchError, OPFPatchValidationError, OPFValidationError, UNRANKED_PRIORITY, activeSchema, analyzeJsonSyntax, applyPatch, applyPatchWithInverse, applyTextEdits, assertValid, assertValidCatalogRecord, catalogDisplayKinds, catalogKindAt, catalogSchemaNames, createLocator, createSchemaValue, describeField, escapePointerToken, findValidationRule, formatPointer, getAtPointer, hasPointer, invertPatch, isPromotedRegionKey, jsonEqual, listSchemaFields, normalizePatch, parsePointer, parseSource, pointerFromPath, readPointer, rememberByText, resolveSchema, resolveSlideContext, resolveSlideScriptMeasurement, schemaAtPath, schemaCatalogKinds, schemaLabel, schemaType, schemaVariants, splitOpfPath, splitPointer2 as splitPointer, unescapePointerToken, validate, validateCatalogRecord, validationCategories, validationRules };
|
|
7427
|
+
export { BLOCK_AS_VALUES, BLOCK_OPTION_KEYS, CATALOG_SCHEMA, COMMON_KEYS, DEFAULT_VALIDATION_THRESHOLDS, FENCE_ATTRIBUTES, FENCE_KINDS, LINE_KEYWORDS, METRIC_KEYS, OPFPatchError, OPFPatchValidationError, OPFValidationError, PATTERN_PHRASES, SLIDE_OPTION_KEYS, SOURCE_EXPLAIN, SPAN_FLAGS, SPAN_VALUES, UNRANKED_PRIORITY, activeSchema, analyzeJsonSyntax, applyPatch, applyPatchWithInverse, applyTextEdits, assertValid, assertValidCatalogRecord, catalogDisplayKinds, catalogKindAt, catalogSchemaNames, createLocator, createSchemaValue, describeField, describeFinding, distance2 as distance, escapePointerToken, findValidationRule, formatPointer, getAtPointer, hasPointer, invertPatch, isPromotedRegionKey, jsonEqual, listSchemaFields, nearest, normalizePatch, parsePointer, parseSource, pointerFromPath, readPointer, rememberByText, resolveSchema, resolveSlideContext, resolveSlideScriptMeasurement, ruleDocumentation, schemaAtPath, schemaCatalogKinds, schemaLabel, schemaType, schemaVariants, segmentsOf, splitOpfPath, splitPointer2 as splitPointer, starterValue, unescapePointerToken, us, validate, validateCatalogRecord, validationCategories, validationRules };
|