@openpresentation/opf-editor 0.12.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -80
- package/dist/annotations.d.ts +16 -16
- package/dist/annotations.js +71 -79
- package/dist/assets.d.ts +7 -7
- package/dist/assets.js +16 -16
- package/dist/background-options.d.ts +12 -5
- package/dist/background-options.js +44 -19
- package/dist/block-controls.js +7 -7
- package/dist/block-convert.d.ts +7 -7
- package/dist/block-convert.js +22 -21
- package/dist/blocks.d.ts +8 -8
- package/dist/blocks.js +41 -40
- package/dist/canvas-fields.js +35 -12
- package/dist/canvas.d.ts +30 -31
- package/dist/canvas.js +64 -68
- package/dist/catalogs.d.ts +48 -0
- package/dist/catalogs.js +217 -0
- package/dist/chart-data.d.ts +10 -10
- package/dist/chart-data.js +21 -21
- package/dist/chart-options-panel.d.ts +2 -2
- package/dist/chart-options-panel.js +82 -8
- package/dist/chart-options.d.ts +35 -9
- package/dist/chart-options.js +133 -40
- package/dist/checks.js +23 -0
- package/dist/color-authoring.js +1 -1
- package/dist/content-actions.d.ts +17 -17
- package/dist/content-actions.js +74 -73
- package/dist/content-controls.js +21 -21
- package/dist/data-grid.js +7 -8
- package/dist/data.d.ts +4 -4
- package/dist/data.js +5 -5
- package/dist/dataset-refs.d.ts +2 -2
- package/dist/dataset-refs.js +5 -5
- package/dist/design-controls.d.ts +12 -5
- package/dist/design-controls.js +312 -111
- package/dist/design-options.d.ts +20 -18
- package/dist/design-options.js +91 -99
- package/dist/edit-helpers.js +8 -7
- package/dist/exact-source.js +5 -5
- package/dist/export.d.ts +7 -7
- package/dist/export.js +48 -17
- package/dist/find-panel.js +5 -5
- package/dist/find-replace.d.ts +3 -3
- package/dist/find-replace.js +23 -20
- package/dist/font-gate.js +49 -58
- package/dist/galleries.js +50 -101
- package/dist/grid-model.d.ts +25 -27
- package/dist/grid-model.js +92 -102
- package/dist/image-crop.d.ts +4 -4
- package/dist/image-crop.js +28 -29
- package/dist/image-cropper.js +3 -3
- package/dist/image-options.d.ts +45 -0
- package/dist/image-options.js +195 -0
- package/dist/index.d.ts +48 -31
- package/dist/index.js +154 -126
- package/dist/json-editor.js +2 -2
- package/dist/json-menu.js +2 -2
- package/dist/json-options.d.ts +3 -3
- package/dist/json-options.js +44 -54
- package/dist/layout-handles.js +5 -5
- package/dist/layout-placeholders.js +4 -4
- package/dist/layout.d.ts +2 -2
- package/dist/layout.js +8 -7
- package/dist/numbering-panel.js +3 -5
- package/dist/numbering.d.ts +3 -5
- package/dist/numbering.js +14 -26
- package/dist/outline-view.js +24 -24
- package/dist/outline.d.ts +12 -12
- package/dist/outline.js +52 -50
- package/dist/persistence.d.ts +1 -1
- package/dist/persistence.js +19 -18
- package/dist/presentation-ids.js +2 -2
- package/dist/react.js +3 -3
- package/dist/review-panel.d.ts +30 -13
- package/dist/review-panel.js +160 -53
- package/dist/review.d.ts +36 -43
- package/dist/review.js +92 -53
- package/dist/rich-text-input.js +4 -3
- package/dist/rich-text-toolbar.js +5 -3
- package/dist/rich-text.js +5 -2
- package/dist/schema-inspector.d.ts +2 -2
- package/dist/schema-inspector.js +16 -11
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +26 -6
- package/dist/slide-manager.d.ts +2 -2
- package/dist/slide-manager.js +19 -19
- package/dist/slides.d.ts +21 -21
- package/dist/slides.js +104 -101
- package/dist/svelte.js +2 -3
- package/dist/switches.d.ts +37 -22
- package/dist/switches.js +282 -171
- package/dist/table-options.d.ts +8 -8
- package/dist/table-options.js +28 -27
- package/dist/table-structure.d.ts +10 -10
- package/dist/table-structure.js +21 -21
- package/dist/template-panel.d.ts +3 -3
- package/dist/template-panel.js +37 -13
- package/dist/templates.d.ts +22 -11
- package/dist/templates.js +48 -55
- package/dist/transfer.d.ts +15 -4
- package/dist/transfer.js +107 -161
- package/package.json +12 -6
- package/dist/font-defaults.js +0 -25
package/dist/chart-options.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
// Chart options (RR-35): axis titles, legend position and data labels
|
|
2
|
-
// `{ type, data, axisTitles?, legend?, dataLabels? }`. These helpers read the
|
|
1
|
+
// Chart options (RR-35): axis titles, legend position and data labels, plus (FA-09) the chart's text alternative `alt` and (FA-14) the
|
|
2
|
+
// highlight. A chart is `{ type, data, alt?, axisTitles?, legend?, dataLabels?, highlight? }`. These helpers read the fields as form
|
|
3
3
|
// state and edit them as one validated, undoable patch (a few add / replace / remove operations
|
|
4
4
|
// on the chart, one undo step), so the canvas redraws the legend, titles and labels and Undo
|
|
5
5
|
// restores the chart. Which fields a chart offers follows core's support table
|
|
6
6
|
// (`chartOptionSupport`): a pie has no axis titles, area and radar labels have no position
|
|
7
|
-
// choice, and only a pie or doughnut can show percent.
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
// choice, and only a pie or doughnut can show percent. A highlight names plotted series and category labels (the
|
|
8
|
+
// choices come from the chart's resolved data, so a dataset chart offers its dataset's columns and rows); a pie
|
|
9
|
+
// highlights slices (categories), an area only series. Nothing here invents text. A combo chart (FA-15) also offers which series are lines (`line`), which lines use the secondary value
|
|
10
|
+
// axis (`secondaryAxis`) and that axis's title.
|
|
11
|
+
import { resolveChartData } from "@openpresentation/opf";
|
|
12
|
+
import { chartOptionSupport, chartOptionTarget } from "@openpresentation/opf/composition";
|
|
13
|
+
import { getValueAtPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
|
|
10
14
|
import { checkedDocument, fail, same } from "./edit-helpers.js";
|
|
15
|
+
import { checkFormat } from "./checks.js";
|
|
11
16
|
|
|
12
17
|
export const CHART_LEGEND_POSITIONS = Object.freeze(["default", "none", "top", "bottom", "left", "right"]);
|
|
13
18
|
export const CHART_LABEL_CONTENT = Object.freeze(["category", "value", "percent"]);
|
|
@@ -16,11 +21,6 @@ const isObject = (value) => Boolean(value) && typeof value === "object" && !Arra
|
|
|
16
21
|
const CONTENT_ORDER = CHART_LABEL_CONTENT;
|
|
17
22
|
const POSITIONS = new Set(["center", "inside-end", "inside-base", "outside-end", "above", "below", "left", "right"]);
|
|
18
23
|
|
|
19
|
-
/** True when the installed core knows the chart option fields; an older core leaves the panel out. */
|
|
20
|
-
export function chartOptionsAvailable() {
|
|
21
|
-
return typeof core.chartOptionSupport === "function" && typeof core.chartOptionTarget === "function" && typeof core.resolveChartOptions === "function";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
24
|
/** The chart path a selection path points at (`…chart`, `…chart.data.rows.0.1`), or undefined. */
|
|
25
25
|
export function parseChartPath(path) {
|
|
26
26
|
let parts;
|
|
@@ -33,38 +33,64 @@ export function parseChartPath(path) {
|
|
|
33
33
|
return index >= 1 ? parts.slice(0, index + 1).join(".") : undefined;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function chartAt(
|
|
36
|
+
function chartAt(presentation, chartPath) {
|
|
37
37
|
const parts = splitOpfPath(chartPath);
|
|
38
|
-
const chart = getValueAtPath(
|
|
38
|
+
const chart = getValueAtPath(presentation, parts);
|
|
39
39
|
if (!isObject(chart) || typeof chart.type !== "string") throw fail("chart-not-found", "Choose a chart (a path ending in .chart).", { chartPath });
|
|
40
40
|
return { parts, chart };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// The names a highlight can take, from the chart's resolved data: the plotted series (the columns after the category and, on a
|
|
44
|
+
// scatter chart, the X column) and the distinct non-empty row labels. Empty when the data does not resolve.
|
|
45
|
+
function highlightChoices(chart, document) {
|
|
46
|
+
const none = { series: [], categories: [] };
|
|
47
|
+
let resolved;
|
|
48
|
+
try { resolved = resolveChartData(chart, document); } catch { return none; }
|
|
49
|
+
if (!resolved?.ok || resolved.columns.length < 2) return none;
|
|
50
|
+
const first = resolved.hasX ? 2 : 1;
|
|
51
|
+
const label = (cell) => (cell === null || cell === undefined ? "" : String(cell));
|
|
52
|
+
return { series: resolved.columns.slice(first), categories: [...new Set(resolved.rows.map((row) => label(row[0])).filter(Boolean))] };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const names = (value) => (Array.isArray(value) ? [...new Set(value.filter((entry) => typeof entry === "string"))] : []);
|
|
56
|
+
|
|
43
57
|
/**
|
|
44
|
-
* What the chart options offer for `chart`, and the form state of the
|
|
58
|
+
* What the chart options offer for `chart`, and the form state of the fields. `presentation` (optional) resolves a dataset-backed
|
|
59
|
+
* chart's columns and rows for the highlight choices.
|
|
45
60
|
* `fields.axisTitles.category` and `.value` say whether the type has that axis; `fields.legend` whether it has a legend;
|
|
46
61
|
* `fields.dataLabels` the contents and positions it accepts (`positions` is empty when labels have no position choice).
|
|
62
|
+
* `fields.axisTitles.secondary` is true on a combo chart, whose `combo.series` lists the plotted series ({ name, role, axis });
|
|
63
|
+
* pass the `presentation` for a dataset-backed chart.
|
|
47
64
|
*/
|
|
48
|
-
export function readChartOptions(chart) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
axisTitles: { category: true, value: true }, legend: true,
|
|
65
|
+
export function readChartOptions(chart, presentation) {
|
|
66
|
+
const target = chartOptionTarget(chart?.type);
|
|
67
|
+
// A chart type outside the catalog is never restricted: every field is offered (a secondary axis exists only on combo charts).
|
|
68
|
+
const support = target ? chartOptionSupport(target) : {
|
|
69
|
+
axisTitles: { category: true, value: true, secondary: false }, legend: true,
|
|
54
70
|
dataLabels: { supported: true, content: CONTENT_ORDER, positions: [...POSITIONS], defaultPosition: null, defaultOn: false },
|
|
71
|
+
highlight: { series: true, categories: true },
|
|
55
72
|
};
|
|
56
73
|
const titles = isObject(chart?.axisTitles) ? chart.axisTitles : {};
|
|
57
74
|
const labels = chart?.dataLabels;
|
|
58
75
|
const labelObject = isObject(labels) ? labels : {};
|
|
76
|
+
const combo = target?.kind === "combo" ? comboSeries(chart, presentation) : undefined;
|
|
59
77
|
return {
|
|
60
78
|
target,
|
|
61
79
|
fields: {
|
|
62
|
-
axisTitles: { ...support.axisTitles },
|
|
80
|
+
axisTitles: { ...support.axisTitles, secondary: support.axisTitles.secondary === true },
|
|
63
81
|
legend: support.legend,
|
|
64
82
|
dataLabels: { ...support.dataLabels, content: [...support.dataLabels.content], positions: [...support.dataLabels.positions] },
|
|
83
|
+
highlight: { ...support.highlight },
|
|
65
84
|
},
|
|
85
|
+
// FA-15: a combo chart's plotted series, each drawn as columns ("bar") or as a line, on the primary or secondary value axis.
|
|
86
|
+
...(combo ? { combo: { series: combo } } : {}),
|
|
87
|
+
choices: highlightChoices(chart, presentation),
|
|
66
88
|
state: {
|
|
67
|
-
|
|
89
|
+
// FA-09: the text alternative. `decorative` is the empty alt, a reviewed choice, not a missing one.
|
|
90
|
+
alt: typeof chart?.alt === "string" ? chart.alt : "",
|
|
91
|
+
decorative: chart?.alt === "",
|
|
92
|
+
highlight: { series: names(chart?.highlight?.series), categories: names(chart?.highlight?.categories) },
|
|
93
|
+
axisTitles: { category: typeof titles.category === "string" ? titles.category : "", value: typeof titles.value === "string" ? titles.value : "", secondary: typeof titles.secondary === "string" ? titles.secondary : "" },
|
|
68
94
|
legend: typeof chart?.legend === "string" ? chart.legend : "default",
|
|
69
95
|
dataLabels: {
|
|
70
96
|
// Funnel and treemap label their marks by default, so `dataLabels: false` is what switches them off.
|
|
@@ -78,18 +104,64 @@ export function readChartOptions(chart) {
|
|
|
78
104
|
};
|
|
79
105
|
}
|
|
80
106
|
|
|
107
|
+
/**
|
|
108
|
+
* FA-15: a combo chart's plotted series ({ name, role, axis }) in the data's plotted order (the mapping, else the columns), with
|
|
109
|
+
* the role and axis core resolves for each; [] when the data does not resolve.
|
|
110
|
+
*/
|
|
111
|
+
function comboSeries(chart, document) {
|
|
112
|
+
const resolved = resolveChartData(chart, document);
|
|
113
|
+
if (!resolved.ok || !Array.isArray(resolved.combo)) return [];
|
|
114
|
+
const plan = new Map(resolved.columns.slice(1).map((name, index) => [name, resolved.combo[index]]));
|
|
115
|
+
const { line: _line, secondaryAxis: _secondary, ...plain } = chart;
|
|
116
|
+
const plotted = resolveChartData({ ...plain, type: "column" }, document);
|
|
117
|
+
const names = plotted.ok ? plotted.columns.slice(1) : [...plan.keys()];
|
|
118
|
+
return names.filter((name) => plan.has(name)).map((name) => ({ name, role: plan.get(name)?.role ?? "bar", axis: plan.get(name)?.axis ?? "primary" }));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// FA-15: `line` and `secondaryAxis` for a combo chart from the wanted line and secondary-axis names. `line` is written only when
|
|
122
|
+
// it differs from the default (the last plotted series), names keep the data's order, and only a line can use the secondary axis.
|
|
123
|
+
function comboFields(chart, change, document) {
|
|
124
|
+
const series = comboSeries(chart, document);
|
|
125
|
+
const plotted = series.map((entry) => entry.name);
|
|
126
|
+
const currentLines = series.filter((entry) => entry.role === "line").map((entry) => entry.name);
|
|
127
|
+
const currentSecondary = series.filter((entry) => entry.axis === "secondary").map((entry) => entry.name);
|
|
128
|
+
const wanted = (value, current, field) => {
|
|
129
|
+
if (value === undefined) return current;
|
|
130
|
+
if (!Array.isArray(value) || value.some((name) => typeof name !== "string")) throw fail("invalid-chart-option", `${field} is a list of series names.`, { [field]: value });
|
|
131
|
+
const unknown = value.filter((name) => !plotted.includes(name));
|
|
132
|
+
if (unknown.length) throw fail("invalid-chart-option", `${unknown.map((name) => `'${name}'`).join(", ")} ${unknown.length === 1 ? "is not a plotted series" : "are not plotted series"} of this chart.`, { [field]: value });
|
|
133
|
+
return value;
|
|
134
|
+
};
|
|
135
|
+
const lines = wanted(change.line, currentLines, "line");
|
|
136
|
+
if (!lines.length) throw fail("invalid-chart-option", "A combo chart draws at least one series as a line; choose a column chart for columns only.", { line: change.line });
|
|
137
|
+
if (lines.length >= plotted.length) throw fail("invalid-chart-option", "A combo chart keeps at least one series as columns.", { line: change.line });
|
|
138
|
+
const ordered = plotted.filter((name) => lines.includes(name));
|
|
139
|
+
const secondary = plotted.filter((name) => ordered.includes(name) && wanted(change.secondaryAxis, currentSecondary, "secondaryAxis").includes(name));
|
|
140
|
+
const defaultLine = ordered.length === 1 && ordered[0] === plotted[plotted.length - 1];
|
|
141
|
+
return { line: defaultLine ? undefined : ordered, secondaryAxis: secondary.length ? secondary : undefined };
|
|
142
|
+
}
|
|
143
|
+
|
|
81
144
|
// The desired value of each field (undefined removes it), from the chart and a patch.
|
|
82
|
-
function desired(chart, change, support) {
|
|
145
|
+
function desired(chart, change, support, choices, current, document) {
|
|
83
146
|
const out = {};
|
|
84
|
-
const
|
|
85
|
-
if (change.
|
|
147
|
+
const combo = support.axisTitles.secondary === true;
|
|
148
|
+
if (change.line !== undefined || change.secondaryAxis !== undefined) {
|
|
149
|
+
// Only a combo chart has line series; elsewhere the fields are removed.
|
|
150
|
+
Object.assign(out, combo ? comboFields(chart, change, document) : { line: undefined, secondaryAxis: undefined });
|
|
151
|
+
}
|
|
152
|
+
// The secondary axis title exists only while a series uses the secondary axis.
|
|
153
|
+
const hasSecondary = combo && (Object.hasOwn(out, "secondaryAxis") ? out.secondaryAxis !== undefined : Array.isArray(chart.secondaryAxis) && chart.secondaryAxis.length > 0);
|
|
154
|
+
if (change.axisTitles !== undefined || (current.axisTitles.secondary && !hasSecondary)) {
|
|
86
155
|
const titles = {};
|
|
87
|
-
for (const axis of ["category", "value"]) {
|
|
156
|
+
for (const axis of ["category", "value", "secondary"]) {
|
|
88
157
|
const value = change.axisTitles?.[axis] !== undefined ? change.axisTitles[axis] : current.axisTitles[axis];
|
|
89
|
-
if (typeof value === "string" && value.trim() && support.axisTitles[axis]) titles[axis] = value.trim();
|
|
158
|
+
if (typeof value === "string" && value.trim() && support.axisTitles[axis] && (axis !== "secondary" || hasSecondary)) titles[axis] = value.trim();
|
|
90
159
|
}
|
|
91
160
|
out.axisTitles = Object.keys(titles).length ? titles : undefined;
|
|
92
161
|
}
|
|
162
|
+
if (change.decorative === true) out.alt = "";
|
|
163
|
+
else if (change.alt !== undefined) out.alt = typeof change.alt === "string" && change.alt.trim() ? change.alt.trim() : undefined;
|
|
164
|
+
else if (change.decorative === false && chart.alt === "") out.alt = undefined;
|
|
93
165
|
if (change.legend !== undefined) {
|
|
94
166
|
if (change.legend !== "default" && !CHART_LEGEND_POSITIONS.includes(change.legend)) throw fail("invalid-chart-option", `'${change.legend}' is not a legend position.`, { legend: change.legend });
|
|
95
167
|
out.legend = change.legend === "default" || !support.legend ? undefined : change.legend;
|
|
@@ -112,22 +184,43 @@ function desired(chart, change, support) {
|
|
|
112
184
|
out.dataLabels = Object.keys(next).length ? next : true;
|
|
113
185
|
}
|
|
114
186
|
}
|
|
187
|
+
if (change.highlight !== undefined) {
|
|
188
|
+
if (change.highlight === null) out.highlight = undefined;
|
|
189
|
+
else {
|
|
190
|
+
const next = {};
|
|
191
|
+
for (const part of ["series", "categories"]) {
|
|
192
|
+
const requested = change.highlight[part] !== undefined ? change.highlight[part] : current.highlight[part];
|
|
193
|
+
if (!Array.isArray(requested) || requested.some((entry) => typeof entry !== "string")) throw fail("invalid-chart-option", `Highlight ${part} must be a list of names.`, { part });
|
|
194
|
+
const listed = names(requested);
|
|
195
|
+
// A part the chart type cannot highlight is never written (a pie highlights slices, not series).
|
|
196
|
+
if (!support.highlight[part] || !listed.length) continue;
|
|
197
|
+
const unknown = choices[part].length ? listed.find((entry) => !choices[part].includes(entry)) : undefined;
|
|
198
|
+
if (unknown !== undefined) throw fail("invalid-chart-option", `'${unknown}' is not a ${part === "series" ? "plotted series" : "category label"} of this chart.`, { part, name: unknown });
|
|
199
|
+
next[part] = listed;
|
|
200
|
+
}
|
|
201
|
+
out.highlight = Object.keys(next).length ? next : undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
115
204
|
return out;
|
|
116
205
|
}
|
|
117
206
|
|
|
118
207
|
/**
|
|
119
208
|
* Prepare one validated patch for a change to the chart's options. `change` is
|
|
120
|
-
* `{ axisTitles?: { category?, value? }, legend?, dataLabels? }`: `
|
|
209
|
+
* `{ alt?, decorative?, axisTitles?: { category?, value?, secondary? }, legend?, dataLabels?, highlight?, line?, secondaryAxis? }`: `alt` is the text alternative (trimmed; empty or `null`
|
|
210
|
+
* removes it), `decorative: true` writes the empty alt (and wins over `alt`), `decorative: false` removes an empty alt; `axisTitles` entries are strings (empty removes a title),
|
|
121
211
|
* `legend` is `"default"` (remove the field), `"none"`, `"top"`, `"bottom"`, `"left"` or `"right"`, and `dataLabels` is
|
|
122
|
-
* `true`, `false` (or `null`, which removes the field), or `{ content?, position?, separator? }` merged over the current labels
|
|
212
|
+
* `true`, `false` (or `null`, which removes the field), or `{ content?, position?, separator? }` merged over the current labels, and
|
|
213
|
+
* `highlight` is `null` (remove the field) or `{ series?, categories? }`: each list replaces that part (an empty list removes it, a name the
|
|
214
|
+
* chart does not have is refused) and the other part is kept. On a combo chart (FA-15), `line` lists the series drawn as lines and
|
|
215
|
+
* `secondaryAxis` the lines on the secondary value axis, by name; at least one series stays columns and at least one is a line, `line`
|
|
216
|
+
* is written only when it differs from the default (the last series), and the secondary axis title goes when no series uses that axis.
|
|
123
217
|
* Fields the chart type cannot show are never written. The document is not modified.
|
|
124
218
|
*/
|
|
125
|
-
export function prepareChartOptions(
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const wanted = desired(chart, change, support);
|
|
219
|
+
export function prepareChartOptions(presentation, chartPath, change) {
|
|
220
|
+
const before = checkFormat(presentation);
|
|
221
|
+
const { parts, chart } = chartAt(presentation, chartPath);
|
|
222
|
+
const read = readChartOptions(chart, presentation);
|
|
223
|
+
const wanted = desired(chart, change, read.fields, read.choices, read.state, presentation);
|
|
131
224
|
const patches = [];
|
|
132
225
|
for (const [key, value] of Object.entries(wanted)) {
|
|
133
226
|
const path = opfPathToJsonPointer([...parts, key]);
|
|
@@ -137,8 +230,8 @@ export function prepareChartOptions(document, chartPath, change) {
|
|
|
137
230
|
} else if (!present) patches.push({ op: "add", path, value: structuredClone(value) });
|
|
138
231
|
else if (!same(chart[key], value)) patches.push({ op: "replace", path, value: structuredClone(value) });
|
|
139
232
|
}
|
|
140
|
-
const next = checkedDocument(
|
|
141
|
-
return { action: "chart-options", chartPath, changed: patches.length > 0, patches,
|
|
233
|
+
const next = checkedDocument(presentation, patches, before);
|
|
234
|
+
return { action: "chart-options", chartPath, changed: patches.length > 0, patches, presentation: next };
|
|
142
235
|
}
|
|
143
236
|
|
|
144
237
|
function checkEditor(editor) {
|
|
@@ -148,10 +241,10 @@ function checkEditor(editor) {
|
|
|
148
241
|
/** Apply a chart option change to an editor session as one undoable edit. Returns the editor change. */
|
|
149
242
|
export function setChartOptions(editor, chartPath, change, meta = {}) {
|
|
150
243
|
checkEditor(editor);
|
|
151
|
-
const prepared = prepareChartOptions(editor.
|
|
152
|
-
const {
|
|
153
|
-
void
|
|
154
|
-
if (!prepared.changed) return { ...summary,
|
|
244
|
+
const prepared = prepareChartOptions(editor.presentation, chartPath, change);
|
|
245
|
+
const { presentation, patches, ...summary } = prepared;
|
|
246
|
+
void presentation;
|
|
247
|
+
if (!prepared.changed) return { ...summary, presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
|
|
155
248
|
const applied = editor.applyPatch(patches, { ...meta, source: meta.source ?? "chart-option", action: prepared.action, path: chartPath });
|
|
156
249
|
return { ...applied, ...summary };
|
|
157
250
|
}
|
package/dist/checks.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The editor's one use of core's checker. Every edit is validated with core's `validate` for the `format` category and the two
|
|
2
|
+
// catalog rules (`opf/unresolved-reference`, `opf/undeclared-catalog`, `opf/catalog-record-not-in-source`): the schema and semantic OPF rules, with no
|
|
3
|
+
// layout built, so it is cheap enough to run on every keystroke and undo. `catalogs` are the host's registered catalogs, so a
|
|
4
|
+
// reference that resolves in one of them is not reported. The full report (accessibility, layout, content) belongs to the Review
|
|
5
|
+
// panel, which runs it on idle (`./review.js`).
|
|
6
|
+
import { validate } from "@openpresentation/opf";
|
|
7
|
+
|
|
8
|
+
const EDIT_CATEGORIES = Object.freeze(["format", "opf/unresolved-reference", "opf/undeclared-catalog", "opf/catalog-record-not-in-source"]);
|
|
9
|
+
|
|
10
|
+
/** Core's `ValidationReport` for the `format` category and the catalog reference rules: `valid`, `findings`, `counts`, `schemaValid`. */
|
|
11
|
+
export function checkFormat(presentation, options = {}) {
|
|
12
|
+
return validate(presentation, { only: EDIT_CATEGORIES, ...(options.catalogs?.length ? { catalogs: options.catalogs } : {}) });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The error-severity findings of a report: what makes it invalid. */
|
|
16
|
+
export function errorFindings(report) {
|
|
17
|
+
return (report?.findings ?? []).filter((finding) => finding.severity === "error");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The message of the first error finding, or `fallback` when the report has none. */
|
|
21
|
+
export function firstErrorMessage(report, fallback) {
|
|
22
|
+
return errorFindings(report)[0]?.message ?? fallback;
|
|
23
|
+
}
|
package/dist/color-authoring.js
CHANGED
|
@@ -6,7 +6,7 @@ const SCHEME_SLOTS = new Set([
|
|
|
6
6
|
]);
|
|
7
7
|
|
|
8
8
|
const ROLE_KEYS = new Set([
|
|
9
|
-
"primary", "secondary", "accent", "background", "surface", "text", "textSecondary",
|
|
9
|
+
"primary", "secondary", "accent", "background", "surface", "surfaceAlt", "text", "textSecondary",
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
12
|
/** Whether a toolbar-authored text color matches documented ColorRef / TextRun forms. */
|
|
@@ -7,7 +7,7 @@ export type SlidePages = readonly (PaginatedPage & { sourceSlideIndex: number })
|
|
|
7
7
|
|
|
8
8
|
/** What every prepare function returns without touching a session. A refusal throws `content-action-refused`. */
|
|
9
9
|
export interface PreparedContentAction {
|
|
10
|
-
|
|
10
|
+
presentation: unknown;
|
|
11
11
|
/** A `test` of what was read, then `replace`, or per-slide `remove` and `add`. Empty when nothing changed. */
|
|
12
12
|
patches: JsonPatchOperation[];
|
|
13
13
|
/** The path of the block or slide the change touched: select it again after applying. */
|
|
@@ -27,8 +27,8 @@ export interface PreparedContentAction {
|
|
|
27
27
|
pages?: (PaginatedPage & { sourceSlideIndex: number })[];
|
|
28
28
|
}
|
|
29
29
|
/** The session change of an applied action, with the same report fields. One undo step. */
|
|
30
|
-
export interface ContentActionChange extends Omit<EditorChange, "
|
|
31
|
-
|
|
30
|
+
export interface ContentActionChange extends Omit<EditorChange, "presentation" | "patches"> {
|
|
31
|
+
presentation: unknown;
|
|
32
32
|
patches: JsonPatchOperation[];
|
|
33
33
|
changed: boolean;
|
|
34
34
|
lossless: boolean;
|
|
@@ -53,17 +53,17 @@ export interface ListShiftOptions extends DryRunOptions {
|
|
|
53
53
|
|
|
54
54
|
/** The index of the list item a selection points at (`slides.0.blocks.1.items.2.text` gives 2), or undefined. */
|
|
55
55
|
export declare function listItemIndexForSelection(selectedPath: string, blockPath: string): number | undefined;
|
|
56
|
-
export declare function prepareListShift(
|
|
56
|
+
export declare function prepareListShift(presentation: unknown, blockPath: string, indices: number[], delta: number, options?: ListShiftOptions): PreparedContentAction;
|
|
57
57
|
/** Nest (`delta` 1) or un-nest (`delta` -1) list items as one undoable step. */
|
|
58
58
|
export declare function shiftListItems(editor: EditorSession, blockPath: string, indices: number[], delta: number, options?: ListShiftOptions, meta?: Record<string, unknown>): ContentActionChange;
|
|
59
59
|
|
|
60
|
-
export declare function prepareGroupBlocks(
|
|
61
|
-
export declare function prepareUngroupBlock(
|
|
62
|
-
export declare function prepareBlocksToRegions(
|
|
63
|
-
export declare function prepareRegionsToBlocks(
|
|
64
|
-
export declare function prepareMoveRegion(
|
|
65
|
-
export declare function prepareImageToDesign(
|
|
66
|
-
export declare function prepareImageToContent(
|
|
60
|
+
export declare function prepareGroupBlocks(presentation: unknown, containerPath: string, indices: number[], options?: DryRunOptions & { composition?: Record<string, unknown> }): PreparedContentAction;
|
|
61
|
+
export declare function prepareUngroupBlock(presentation: unknown, groupPath: string, options?: DryRunOptions): PreparedContentAction;
|
|
62
|
+
export declare function prepareBlocksToRegions(presentation: unknown, slideIndex: number, regions: string[], options?: DryRunOptions): PreparedContentAction;
|
|
63
|
+
export declare function prepareRegionsToBlocks(presentation: unknown, slideIndex: number, options?: DryRunOptions): PreparedContentAction;
|
|
64
|
+
export declare function prepareMoveRegion(presentation: unknown, slideIndex: number, from: string, to: string, options?: DryRunOptions & { swap?: boolean }): PreparedContentAction;
|
|
65
|
+
export declare function prepareImageToDesign(presentation: unknown, blockPath: string, target: ImageTarget, options?: DryRunOptions & PromoteImageOptions): PreparedContentAction;
|
|
66
|
+
export declare function prepareImageToContent(presentation: unknown, slideIndex: number, source: ImageTarget, options?: DryRunOptions & { index?: number; region?: string }): PreparedContentAction;
|
|
67
67
|
|
|
68
68
|
/** Wrap the blocks at `indices` of a slide, group or region group in a new group. */
|
|
69
69
|
export declare function groupBlocks(editor: EditorSession, containerPath: string, indices: number[], options?: { composition?: Record<string, unknown> }, meta?: Record<string, unknown>): ContentActionChange;
|
|
@@ -72,15 +72,15 @@ export declare function ungroupBlock(editor: EditorSession, groupPath: string, m
|
|
|
72
72
|
export declare function placeBlocksInRegions(editor: EditorSession, slideIndex: number, regions: string[], meta?: Record<string, unknown>): ContentActionChange;
|
|
73
73
|
export declare function regionsAsBlocks(editor: EditorSession, slideIndex: number, meta?: Record<string, unknown>): ContentActionChange;
|
|
74
74
|
export declare function moveSlideRegion(editor: EditorSession, slideIndex: number, from: string, to: string, options?: { swap?: boolean }, meta?: Record<string, unknown>): ContentActionChange;
|
|
75
|
-
/** Move an image block into the slide's design as its
|
|
75
|
+
/** Move an image block into the slide's design as its background or watermark. */
|
|
76
76
|
export declare function moveImageToDesign(editor: EditorSession, blockPath: string, target: ImageTarget, options?: PromoteImageOptions, meta?: Record<string, unknown>): ContentActionChange;
|
|
77
|
-
/** Move a slide's own
|
|
77
|
+
/** Move a slide's own background image or watermark back into its content as an image block. */
|
|
78
78
|
export declare function moveImageToContent(editor: EditorSession, slideIndex: number, source: ImageTarget, options?: { index?: number; region?: string }, meta?: Record<string, unknown>): ContentActionChange;
|
|
79
79
|
|
|
80
|
-
export declare function prepareSplitSlide(
|
|
81
|
-
export declare function prepareSplitSlideOnOverflow(
|
|
82
|
-
export declare function prepareMergeSlides(
|
|
83
|
-
export declare function prepareUnpaginate(
|
|
80
|
+
export declare function prepareSplitSlide(presentation: unknown, slideIndex: number, options?: { at?: number[]; each?: boolean; repeatHeadings?: boolean }): PreparedContentAction;
|
|
81
|
+
export declare function prepareSplitSlideOnOverflow(presentation: unknown, slideIndex: number, options?: PresentationPaginationOptions): PreparedContentAction;
|
|
82
|
+
export declare function prepareMergeSlides(presentation: unknown, start: number, count?: number): PreparedContentAction;
|
|
83
|
+
export declare function prepareUnpaginate(presentation: unknown, pages: SlidePages, options?: { sourceSlideIndex?: number }): PreparedContentAction;
|
|
84
84
|
/** Split a slide by its blocks into several slides, as one undoable step. */
|
|
85
85
|
export declare function splitSlideByBlocks(editor: EditorSession, slideIndex: number, options?: { at?: number[]; each?: boolean; repeatHeadings?: boolean }, meta?: Record<string, unknown>): ContentActionChange;
|
|
86
86
|
/** Split a slide that overflows with the existing pagination; the change carries `pages` for `unpaginateSlides`. */
|