@openpresentation/opf-editor 0.16.0 → 0.18.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 +53 -44
- package/dist/assets.js +1 -1
- package/dist/canvas.d.ts +4 -4
- package/dist/canvas.js +4 -4
- package/dist/data.d.ts +3 -3
- package/dist/data.js +3 -3
- package/dist/design-controls.js +217 -63
- package/dist/design-options.d.ts +98 -29
- package/dist/design-options.js +307 -118
- package/dist/export.d.ts +42 -41
- package/dist/export.js +151 -87
- package/dist/find-replace.js +9 -1
- package/dist/font-gate.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -5
- package/dist/review.d.ts +6 -1
- package/dist/review.js +37 -1
- package/dist/template-panel.d.ts +1 -1
- package/dist/template-panel.js +10 -5
- package/dist/templates.d.ts +3 -1
- package/dist/templates.js +1 -1
- package/package.json +6 -5
package/dist/review.d.ts
CHANGED
|
@@ -61,8 +61,13 @@ export declare function groupFindings(findings: readonly ReviewFinding[]): { cat
|
|
|
61
61
|
export declare function findingTarget(presentation: unknown, finding: Pick<ReviewFinding, "path">): ReviewTarget;
|
|
62
62
|
/** Apply a `patch` fix as one undoable, validated session change; refuses a stale or unsafe patch. */
|
|
63
63
|
export declare function applyReviewFix(editor: EditorSession, finding: Pick<ReviewFinding, "ruleId"> | undefined, fix: ReviewFix, meta?: Record<string, unknown>): EditorChange;
|
|
64
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* The patch that sets (or, for `""`, marks decorative) the alt text of the picture at `pointer`; null when nothing changes. A
|
|
66
|
+
* logo reference (`var:organization.logo.icon`) stays a whole-field string: its alt is written on the organization logo
|
|
67
|
+
* asset(s) it draws, and a reference that names no logo is refused (`unresolved-logo-alt`).
|
|
68
|
+
*/
|
|
65
69
|
export declare function altTextPatch(presentation: unknown, pointer: string, text: string): JsonPatchOperation[] | null;
|
|
66
70
|
export declare function setReviewAltText(editor: EditorSession, pointer: string, text: string, meta?: Record<string, unknown>): { changed: boolean } & Partial<EditorChange>;
|
|
67
71
|
export declare function markDecorative(editor: EditorSession, pointer: string, meta?: Record<string, unknown>): { changed: boolean } & Partial<EditorChange>;
|
|
72
|
+
/** The alt text a picture has now; for a logo reference, the alt of the organization asset it draws. */
|
|
68
73
|
export declare function currentAltText(presentation: unknown, pointer: string): string;
|
package/dist/review.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// module needs no DOM.
|
|
8
8
|
import { validationCategories } from "@openpresentation/opf";
|
|
9
9
|
import { OPFEditorError, getValueAtPath, hasValueAtPath, jsonPointerToOpfPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
|
|
10
|
+
import { resolveLogo } from "@openpresentation/opf/composition";
|
|
10
11
|
import { assetIdOf, prepareAssetAlt } from "./assets.js";
|
|
12
|
+
import { parseLogoReference } from "./design-options.js";
|
|
11
13
|
|
|
12
14
|
function fail(code, message, details) {
|
|
13
15
|
return new OPFEditorError(code, message, details);
|
|
@@ -158,10 +160,36 @@ export function applyReviewFix(editor, finding, fix, meta = {}) {
|
|
|
158
160
|
|
|
159
161
|
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
160
162
|
|
|
161
|
-
|
|
163
|
+
// RR-71: a logo reference (`var:organization.logo.icon`) is a whole-field string, so its alt text cannot be written beside
|
|
164
|
+
// it ({ src: "var:…", alt } is an ordinary image whose source is no longer the logo). The logo's alt lives on the
|
|
165
|
+
// organization's asset (Organization.logo, each shape and onLight/onDark value a path or Asset): these are the OPF paths of
|
|
166
|
+
// the assets the reference draws, on a light and on a dark background (one path when both resolve to the same asset).
|
|
167
|
+
function logoAltPaths(presentation, value) {
|
|
168
|
+
if (typeof value !== "string" || !parseLogoReference(value)) return null;
|
|
169
|
+
const paths = new Set();
|
|
170
|
+
for (const onDark of [false, true]) {
|
|
171
|
+
const logo = resolveLogo(presentation, undefined, { reference: value, onDark });
|
|
172
|
+
if (logo) paths.add(logo.path);
|
|
173
|
+
}
|
|
174
|
+
if (!paths.size) {
|
|
175
|
+
throw fail("unresolved-logo-alt", `${value} names no logo yet. Add the organization's logo first; its alt text is written on the logo in the organization.`, { reference: value });
|
|
176
|
+
}
|
|
177
|
+
return [...paths];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The patch operations that give the asset value at `pointer` alt text (`""` marks it decorative), or `null` when it already
|
|
182
|
+
* has exactly this alt. For a logo reference the alt is written on the organization logo asset(s) it draws.
|
|
183
|
+
*/
|
|
162
184
|
export function altTextPatch(presentation, pointer, text) {
|
|
163
185
|
const value = getValueAtPath(presentation, pointer);
|
|
164
186
|
if (value === undefined) throw fail("stale-finding", "The picture this finding is about no longer exists.", { pointer });
|
|
187
|
+
const logoPaths = logoAltPaths(presentation, value);
|
|
188
|
+
if (logoPaths) {
|
|
189
|
+
// Both backgrounds may name one registry asset (asset:<id>): write it once.
|
|
190
|
+
const operations = [...new Map(logoPaths.flatMap((path) => altTextPatch(presentation, opfPathToJsonPointer(splitOpfPath(path)), text) ?? []).map((operation) => [JSON.stringify(operation), operation])).values()];
|
|
191
|
+
return operations.length ? operations : null;
|
|
192
|
+
}
|
|
165
193
|
const alt = String(text ?? "");
|
|
166
194
|
const id = assetIdOf(value);
|
|
167
195
|
const entry = id === undefined ? undefined : getValueAtPath(presentation, ["assets", id]);
|
|
@@ -202,6 +230,14 @@ export function markDecorative(editor, pointer, meta = {}) {
|
|
|
202
230
|
/** The alt text a picture has now (own or through the assets registry), for pre-filling the field. */
|
|
203
231
|
export function currentAltText(presentation, pointer) {
|
|
204
232
|
const value = getValueAtPath(presentation, pointer);
|
|
233
|
+
// A logo reference shows the alt of the organization asset it draws (on a light background first).
|
|
234
|
+
if (typeof value === "string" && parseLogoReference(value)) {
|
|
235
|
+
for (const onDark of [false, true]) {
|
|
236
|
+
const logo = resolveLogo(presentation, undefined, { reference: value, onDark });
|
|
237
|
+
if (logo) return currentAltText(presentation, opfPathToJsonPointer(splitOpfPath(logo.path)));
|
|
238
|
+
}
|
|
239
|
+
return "";
|
|
240
|
+
}
|
|
205
241
|
if (isObject(value) && typeof value.alt === "string") return value.alt;
|
|
206
242
|
const id = assetIdOf(value);
|
|
207
243
|
const entry = id === undefined ? undefined : getValueAtPath(presentation, ["assets", id]);
|
package/dist/template-panel.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface TemplatePanelOptions {
|
|
|
5
5
|
editor: EditorSession;
|
|
6
6
|
/**
|
|
7
7
|
* Draw the live preview: return the SVG of one slide of `presentation` with `variables` (the values typed so far)
|
|
8
|
-
* as an SVG string, or a promise of one. Typically `
|
|
8
|
+
* as an SVG string, or a promise of one. Typically `toSvg(presentation, slideIndex + 1, { variables, ... })` from
|
|
9
9
|
* `@openpresentation/opf-render` with the host's fonts. Omit for no preview.
|
|
10
10
|
*/
|
|
11
11
|
renderPreview?: (input: { presentation: unknown; variables: Record<string, unknown>; slideIndex: number }) => string | Promise<string>;
|
package/dist/template-panel.js
CHANGED
|
@@ -67,7 +67,7 @@ export function createTemplatePanel(container, options) {
|
|
|
67
67
|
const builtinList = h(doc, "ul", { class: "opf-template-builtin-list" });
|
|
68
68
|
const builtinSection = h(doc, "details", { class: "opf-template-builtins", "data-opf-component": "template-builtins" },
|
|
69
69
|
h(doc, "summary", { text: "Built-in variables" }),
|
|
70
|
-
h(doc, "p", { class: "opf-template-help", text: "Read from the presentation's own speaker, organization and deck fields; edit those fields to change them. Use them like any variable: {{speaker.name}} inside text, or var:speaker.photo as a whole image field." }),
|
|
70
|
+
h(doc, "p", { class: "opf-template-help", text: "Read from the presentation's own speaker, organization and deck fields; edit those fields to change them. Use them like any variable: {{speaker.name}} inside text, or var:speaker.photo as a whole image field. Slide number, section and slide count vary per slide: write them as {{slide.number}}, {{slide.section}} and {{deck.slideCount}} inside any text, not as a whole var: field. The organization logos are images that follow each slide's background (light or dark artwork): place one as a whole image field, for example var:organization.logo.icon in a header or footer zone's image, or choose it with Insert logo." }),
|
|
71
71
|
builtinList);
|
|
72
72
|
const live = h(doc, "p", { class: "opf-template-live", role: "status", "aria-live": "polite" });
|
|
73
73
|
const applyButton = h(doc, "button", { type: "button", class: "opf-template-apply primary", text: "Fill the presentation" });
|
|
@@ -230,14 +230,19 @@ export function createTemplatePanel(container, options) {
|
|
|
230
230
|
const builtins = listBuiltins(editor.presentation);
|
|
231
231
|
insertSelect.replaceChildren(
|
|
232
232
|
...fields.map((field) => h(doc, "option", { value: field.id, text: `${field.label} (${KIND_LABELS[field.kind]})` })),
|
|
233
|
-
|
|
233
|
+
// A slide-scoped image (an organization logo) is a whole-field var: reference, not a {{ }} token for text.
|
|
234
|
+
...(builtins.length ? [h(doc, "optgroup", { label: "Built-in variables" }, ...builtins.filter((entry) => !(entry.scope === "slide" && entry.kind === "image")).map((entry) => h(doc, "option", { value: entry.name, text: `${entry.label} (${entry.name})` })))] : []),
|
|
234
235
|
);
|
|
235
236
|
builtinSection.hidden = !builtins.length;
|
|
236
237
|
builtinList.replaceChildren(...builtins.map((entry) => {
|
|
237
|
-
const shown = builtinValueText(entry);
|
|
238
238
|
const uses = entry.uses.length;
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
// A slide-scoped built-in has no single value: each slide gets its own when it is drawn or exported (FA-31).
|
|
240
|
+
const slideScoped = entry.scope === "slide";
|
|
241
|
+
// RR-71: an organization logo is an image placed by var:, and the artwork follows each slide's background.
|
|
242
|
+
const logoImage = slideScoped && entry.kind === "image";
|
|
243
|
+
const shown = logoImage ? h(doc, "em", { text: "image; light or dark artwork per slide" }) : slideScoped ? h(doc, "em", { text: "varies per slide" }) : entry.available ? builtinValueText(entry) : h(doc, "em", { text: "not set" });
|
|
244
|
+
return h(doc, "li", { "data-builtin": entry.name, "data-kind": entry.kind, "data-scope": entry.scope, "data-available": entry.available ? "true" : "false" },
|
|
245
|
+
h(doc, "code", { text: logoImage ? `var:${entry.name}` : `{{${entry.name}}}` }), ` ${entry.label}: `, shown, slideScoped && !logoImage && !entry.available ? " (no slide has one yet)" : "", logoImage && !entry.available ? " (no logo yet)" : "", uses ? ` (used ${uses} time${uses === 1 ? "" : "s"})` : "");
|
|
241
246
|
}));
|
|
242
247
|
slideSelect.replaceChildren(...(editor.presentation.slides ?? []).map((slide, index) => h(doc, "option", { value: String(index), text: `${index + 1}. ${slideTitle(slide) || "Untitled"}` })));
|
|
243
248
|
previewSlide = Math.min(previewSlide, Math.max(0, (editor.presentation.slides?.length ?? 1) - 1));
|
package/dist/templates.d.ts
CHANGED
|
@@ -109,7 +109,9 @@ export interface BuiltinInfo {
|
|
|
109
109
|
name: string;
|
|
110
110
|
kind: "text" | "image" | "list";
|
|
111
111
|
label: string;
|
|
112
|
-
/**
|
|
112
|
+
/** `deck` for a value read once from the document; `slide` for `slide.number`, `slide.section` and `deck.slideCount`, which vary per slide. */
|
|
113
|
+
scope: "deck" | "slide";
|
|
114
|
+
/** The source value, when the document has one. Never set for a slide-scoped built-in. */
|
|
113
115
|
value?: unknown;
|
|
114
116
|
available: boolean;
|
|
115
117
|
uses: { id: string; path: string; form: "token" | "reference" }[];
|
package/dist/templates.js
CHANGED
|
@@ -8,7 +8,7 @@ import { OPFEditorError, getValueAtPath, opfPathToJsonPointer } from "./index.js
|
|
|
8
8
|
|
|
9
9
|
const VARIABLE_ID = /^[a-z][a-z0-9-]*$/;
|
|
10
10
|
// A built-in variable name (core FA-04): `speakers`, or deck/speaker/organization plus one or two dotted segments.
|
|
11
|
-
const BUILTIN_NAME = /^(?:speakers|(?:deck|speaker|organization)(
|
|
11
|
+
const BUILTIN_NAME = /^(?:speakers|(?:deck|slide|speaker|organization)(?:\.[A-Za-z0-9_-]+){1,3})$/;
|
|
12
12
|
const KINDS = ["color", "text", "number", "date", "image", "url", "list"];
|
|
13
13
|
|
|
14
14
|
/** The form control each variable kind uses. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -258,11 +258,11 @@
|
|
|
258
258
|
"@codemirror/state": "6.7.6",
|
|
259
259
|
"@codemirror/view": "6.43.13",
|
|
260
260
|
"@lezer/highlight": "1.2.4",
|
|
261
|
-
"@openpresentation/opf": "^0.
|
|
261
|
+
"@openpresentation/opf": "^0.18.1",
|
|
262
262
|
"jsonc-parser": "3.3.1"
|
|
263
263
|
},
|
|
264
264
|
"peerDependencies": {
|
|
265
|
-
"@openpresentation/opf-render": "^0.
|
|
265
|
+
"@openpresentation/opf-render": "^0.18.0"
|
|
266
266
|
},
|
|
267
267
|
"peerDependenciesMeta": {
|
|
268
268
|
"@openpresentation/opf-render": {
|
|
@@ -314,8 +314,9 @@
|
|
|
314
314
|
"@expo-google-fonts/roboto-mono": "0.4.2",
|
|
315
315
|
"@expo-google-fonts/stix-two-math": "0.4.0",
|
|
316
316
|
"@expo-google-fonts/tinos": "0.4.2",
|
|
317
|
-
"@openpresentation/opf-pptx": "^0.
|
|
318
|
-
"@openpresentation/opf-render": "^0.
|
|
317
|
+
"@openpresentation/opf-pptx": "^0.18.0",
|
|
318
|
+
"@openpresentation/opf-render": "^0.18.0",
|
|
319
|
+
"@resvg/resvg-js": "2.6.2",
|
|
319
320
|
"axe-core": "4.13.0",
|
|
320
321
|
"esbuild": "0.28.2",
|
|
321
322
|
"jszip": "3.10.2",
|