@openpresentation/opf-editor 0.7.0 → 0.7.1
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 +6 -2
- package/dist/canvas.js +7 -4
- package/dist/index.js +1 -1
- package/dist/json-menu.js +2 -2
- package/dist/json-options.d.ts +5 -1
- package/dist/json-options.js +42 -5
- package/dist/layout-placeholders.js +56 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# OPF Editor
|
|
2
2
|
|
|
3
|
+
Unfinished prepared shaping work is preserved in the [September 15 roadmap](docs/roadmap-shaping-20260915.md); it is not part of the published runtime.
|
|
4
|
+
|
|
3
5
|
Version 0.7.0 and this checkout require Node 24 (`24.x`). Use `.nvmrc` for local development. Earlier published versions retain their original engine declarations. Browser entrypoints remain browser-safe; native application compatibility is verified separately.
|
|
4
6
|
|
|
5
7
|
Version 0.7.0 forwards effective title alignment and shared outline placement options through composition and explicit pagination. Use the same `textMeasurement` and `textRasterPadding` in canvas rendering and export. Accepted rich-text trace origins support selection, caret placement and undo. See the [source contract and limits](https://github.com/OpenPresentation/opf/blob/f94125a1ff95bf0974a055fe1c081d348dad54f2/docs/plans/text-placement.md); native PowerPoint fidelity remains separate.
|
|
@@ -30,7 +32,9 @@ The host owns the source draft, OPF validation, slide preview, persistence and s
|
|
|
30
32
|
|
|
31
33
|
The control provides indentation, paired quotes/brackets, folding shortcuts, search, undo/redo, JSON syntax diagnostics and explicit formatting (`Cmd/Ctrl+Shift+F`). Enter at the end of a nonempty string array item supplies indentation, quotes and commas. Other Enter positions use ordinary JSON indentation. Source offsets are UTF-16 in the original authored string. Existing CRLF, CR and LF spellings survive ordinary edits and undo; new typed lines use the first existing separator. Paste retains the text delivered by the browser clipboard event. Explicit formatting deliberately standardizes whitespace, preserving JSON values and numeric lexemes.
|
|
32
34
|
|
|
33
|
-
Click a categorical property/value or press `Ctrl+Space`, `Cmd+Space` or `Alt+Down` to open its choices. Document catalog records override already-loaded records, which override built-ins; the actual schema supplies enums and booleans. Prose and extension fields do not gain menus just because their keys resemble schema fields. External catalog URLs are not fetched. `setCatalogs` replaces the host's loaded context and closes stale choices. Supply `onOptions(left?, top?)` to retain an existing menu UI
|
|
35
|
+
Click a categorical property/value or press `Ctrl+Space`, `Cmd+Space` or `Alt+Down` to open its choices. Document catalog records override already-loaded records, which override built-ins; the actual schema supplies enums and booleans. Prose and extension fields do not gain menus just because their keys resemble schema fields. External catalog URLs are not fetched. `setCatalogs` replaces the host's loaded context and closes stale choices. Supply `onOptions(left?, top?)` to retain an existing menu UI.
|
|
36
|
+
|
|
37
|
+
In version 0.7.1, layout choices add blank payloads for missing declared placeholders, preserving existing content. Repeated slots use separate blocks; subtitle slots use `subtitle`, and metric slots use `{ "metric": { "value": "", "label": "" } }`. Charts receive the smallest valid data scaffold rather than invented numeric values. Existing positioned content stays in its regions; additional body slots append inside the last region. Non-layout options still change only their value. Custom menus must apply the full source returned by `replaceFieldOption`, or use `fieldOptionEdit(context, value)` and pass its `from`, `to`, and `insert` to `control.api.replace` as one undoable edit. Always check that `context.source` still matches the editor first. Version 0.7.0 only changed the selected token. Version 0.7.1 requires core 0.10.1 for metric, quote and timeline placeholder contracts.
|
|
34
38
|
|
|
35
39
|
Run `npm run test:json` and `npm run test:json-browser` for the focused model and offline browser checks. The browser runner also accepts a report path followed by a clean installed consumer directory. These checks cover the JSON control; renderer, native PowerPoint and production adoption remain separate acceptance steps.
|
|
36
40
|
|
|
@@ -206,7 +210,7 @@ This repo does not require an npm automation token when Trusted Publishing is co
|
|
|
206
210
|
|
|
207
211
|
The current checkout uses `@openpresentation/opf/composition` for portable geometry. Slides can select `auto`, `row`, `column`, or `grid`, set weighted tracks, and request path-specific overflow diagnostics. See the sibling OPF repo's `docs/dynamic-composition.md` for the complete contract.
|
|
208
212
|
|
|
209
|
-
Version 0.7.
|
|
213
|
+
Version 0.7.1 requires `@openpresentation/opf@^0.10.1`. The optional renderer peer requires `@openpresentation/opf-render@^0.8.1`. Clean registry installs support the composition APIs without sibling checkouts. For coordinated source development, build OPF and run `node scripts/link-ecosystem.mjs` there; `pnpm test:ecosystem` verifies shared geometry and import/export behavior.
|
|
210
214
|
|
|
211
215
|
## Local interactive demo
|
|
212
216
|
|
package/dist/canvas.js
CHANGED
|
@@ -136,12 +136,13 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
136
136
|
if (
|
|
137
137
|
seen.has(path) ||
|
|
138
138
|
node.getAttribute('data-opf-generated') === 'true' ||
|
|
139
|
+
node.closest('[data-opf-furniture-generated="true"]') ||
|
|
139
140
|
(node.hasAttribute('data-opf-code-container') && typeof value === 'string') ||
|
|
140
141
|
(node.hasAttribute('data-opf-metric-container') && ['string','number'].includes(typeof value)) ||
|
|
141
142
|
(!item && !node.matches("g") && !node.matches("image")) ||
|
|
142
143
|
value === undefined ||
|
|
143
144
|
path === `slides.${slideIndex}` ||
|
|
144
|
-
path.includes(".design.")
|
|
145
|
+
(path.includes(".design.")&&!node.closest('[data-opf-furniture-editable="true"]'))
|
|
145
146
|
)
|
|
146
147
|
continue;
|
|
147
148
|
// Prefer the containing group over its duplicate text/shape trace attributes.
|
|
@@ -265,7 +266,8 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
265
266
|
const metricLayout=geometry.items.find(item=>item.metricLayout?.parts.some(part=>part.path===active.path))?.metricLayout;
|
|
266
267
|
const metricPart=metricLayout?.parts.find(part=>part.path===active.path);
|
|
267
268
|
const timelinePart=geometry.items.find(item=>item.timelineLayout?.parts.some(part=>part.path===active.path))?.timelineLayout?.parts.find(part=>part.path===active.path);
|
|
268
|
-
const
|
|
269
|
+
const furniturePart=geometry.furniture?.parts.find(part=>part.type==='text'&&part.path===active.path);
|
|
270
|
+
const internalPart=metricPart??timelinePart??furniturePart;
|
|
269
271
|
const allText = target.querySelectorAll("text");
|
|
270
272
|
const lineHeight = internalPart?.fit?.lineHeight ?? (
|
|
271
273
|
allText.length > 1
|
|
@@ -294,7 +296,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
294
296
|
lineHeight: `${lineHeight * scale}px`,
|
|
295
297
|
color: visibleText ? font.fill : "transparent",
|
|
296
298
|
caretColor: font.fill,
|
|
297
|
-
textAlign: timelinePart?.alignment ?? metricLayout?.alignment ?? (anchor === "middle" ? "center" : anchor === "end" ? "right" : "left"),
|
|
299
|
+
textAlign: furniturePart?.alignment ?? timelinePart?.alignment ?? metricLayout?.alignment ?? (anchor === "middle" ? "center" : anchor === "end" ? "right" : "left"),
|
|
298
300
|
width: `${width * scale}px`,
|
|
299
301
|
height: `${Math.max(lineHeight, allText.length * lineHeight) * scale + 2}px`,
|
|
300
302
|
minHeight: "0",
|
|
@@ -335,6 +337,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
335
337
|
const isCode=text.hasAttribute('data-opf-code-role');
|
|
336
338
|
const isMetric=text.hasAttribute('data-opf-metric-role');
|
|
337
339
|
const isTimeline=!!text.closest('[data-opf-timeline-role]');
|
|
340
|
+
const isFurniture=!!text.closest('[data-opf-furniture-editable="true"]');
|
|
338
341
|
input.spellcheck = !isCode;
|
|
339
342
|
input.style.cssText =
|
|
340
343
|
"position:absolute;pointer-events:auto;resize:none;border:0;outline:1px solid #8975d9;outline-offset:4px;margin:0;padding:0;background:transparent;overflow:hidden;min-height:0;box-shadow:none;border-radius:0;white-space:pre-wrap;overflow-wrap:break-word;box-sizing:border-box;z-index:2";
|
|
@@ -352,7 +355,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
352
355
|
overlay.append(input);
|
|
353
356
|
if(isCode){input.style.background='#111827';input.style.tabSize='4';input.style.overflow='auto';}
|
|
354
357
|
if(isMetric){input.style.tabSize='4';input.style.overflow='auto';}
|
|
355
|
-
if(isTimeline)input.style.tabSize='4';
|
|
358
|
+
if(isTimeline||isFurniture)input.style.tabSize='4';
|
|
356
359
|
// Only offer rich text where the canonical schema accepts TextRun[].
|
|
357
360
|
if (typeof value === "string") {
|
|
358
361
|
try {
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ function resolveCompositionOptions(document, slideIndex, options = {}) {
|
|
|
19
19
|
const reference = slide.design?.fontScheme ?? document.design?.fontScheme ?? theme?.fontScheme ?? "roboto";
|
|
20
20
|
const id = typeof reference === "string" ? reference : reference.id;
|
|
21
21
|
const fontScheme = {...(document.catalogs?.fontSchemes?.records?.find(record=>record.id===id) ?? bundledCatalogs.fontSchemes.find(record=>record.id===id)),...(typeof reference === "object" ? reference : {})};
|
|
22
|
-
return { ...resolveCanvasDimensions(slide.design?.dimensions ?? document.design?.dimensions ?? theme?.dimensions), fonts:resolveFontFamilies(fontScheme), contentAlignment:slide.design?.contentAlignment??document.design?.contentAlignment, titleAlignment:slide.design?.titleAlignment??document.design?.titleAlignment, contentBox:slide.design?.contentBox??document.design?.contentBox, ...options, layout, slideIndex };
|
|
22
|
+
return { ...resolveCanvasDimensions(slide.design?.dimensions ?? document.design?.dimensions ?? theme?.dimensions), fonts:resolveFontFamilies(fontScheme), contentAlignment:slide.design?.contentAlignment??document.design?.contentAlignment, titleAlignment:slide.design?.titleAlignment??document.design?.titleAlignment, contentBox:slide.design?.contentBox??document.design?.contentBox, presentation:document, ...options, layout, slideIndex };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export const packageName = "@openpresentation/opf-editor";
|
package/dist/json-menu.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {getJsonFieldContext,
|
|
1
|
+
import {getJsonFieldContext,fieldOptionEdit} from './json-options.js';
|
|
2
2
|
let nextMenuId=0;
|
|
3
3
|
|
|
4
4
|
/** Optional DOM menu; hosts may provide their own onOptions renderer instead. */
|
|
@@ -35,7 +35,7 @@ export function mountJsonFieldMenu(parent,api,catalogs,left,top,onClose){
|
|
|
35
35
|
function resize(){close();}
|
|
36
36
|
function choose(option){
|
|
37
37
|
if(api.getValue()!==source){close();return;}
|
|
38
|
-
try{
|
|
38
|
+
try{const edit=fieldOptionEdit(context,option.value);api.replace(edit.from,edit.to,edit.insert);close();api.focus();}
|
|
39
39
|
catch(cause){error.hidden=false;error.textContent=cause.message;}
|
|
40
40
|
}
|
|
41
41
|
function highlight(){
|
package/dist/json-options.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface JsonFieldOption {
|
|
|
13
13
|
layoutGroup?: 'Current layout' | 'Same placeholders' | 'Compatible placeholders' | 'Different counts' | 'Other layouts' | 'Unspecified placeholders';
|
|
14
14
|
/** Declared placeholder types and counts, not a rendering guarantee. */
|
|
15
15
|
placeholders?: string;
|
|
16
|
+
/** Declared kinds in layout order, used to create missing blank content. */
|
|
17
|
+
placeholderTypes?: string[];
|
|
16
18
|
related?: boolean;
|
|
17
19
|
}
|
|
18
20
|
export interface JsonFieldContext {
|
|
@@ -29,5 +31,7 @@ export interface JsonFieldContext {
|
|
|
29
31
|
}
|
|
30
32
|
/** Null for invalid JSON or a field with no categorical choices. */
|
|
31
33
|
export declare function getJsonFieldContext(source: string, position: number, loadedCatalogs?: JsonCatalogContext): JsonFieldContext | null;
|
|
32
|
-
/** Replace
|
|
34
|
+
/** Replace the token and add missing layout payloads. Hosts must reject stale sources. */
|
|
33
35
|
export declare function replaceFieldOption(context: JsonFieldContext, value: JsonOptionValue): string;
|
|
36
|
+
/** Minimal single replacement for atomic source-editor undo. */
|
|
37
|
+
export declare function fieldOptionEdit(context: JsonFieldContext, value: JsonOptionValue): {from:number;to:number;insert:string};
|
package/dist/json-options.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { catalogKinds, catalogSchemaNames, schemas, validateCatalogRecord, validatePresentation } from "@openpresentation/opf";
|
|
2
2
|
import { getCatalogOptions } from "./index.js";
|
|
3
3
|
import { schemaAtPath, schemaVariants } from "./schema.js";
|
|
4
|
-
import { findNodeAtOffset, getNodePath, parseTree } from "jsonc-parser";
|
|
4
|
+
import { applyEdits, findNodeAtLocation, findNodeAtOffset, getNodePath, modify, parseTree } from "jsonc-parser";
|
|
5
|
+
import { populateLayoutPlaceholders } from "./layout-placeholders.js";
|
|
5
6
|
function catalogFor(description) {
|
|
6
7
|
return catalogKinds.find(kind => description.includes(`catalogs.${kind}`)
|
|
7
8
|
|| new RegExp(`\\b${kind}['’]? catalog`).test(description));
|
|
@@ -18,8 +19,8 @@ function placeholderSummary(types) {
|
|
|
18
19
|
return "Placeholders not specified";
|
|
19
20
|
if (!types.length)
|
|
20
21
|
return "No placeholders";
|
|
21
|
-
const labels = { title: "Title", subtitle: "Subtitle", text: "Text", list: "List", picture: "Image", chart: "Chart", table: "Table", code: "Code", media: "
|
|
22
|
-
const order = ["title", "subtitle", "text", "list", "picture", "chart", "table", "code", "media"];
|
|
22
|
+
const labels = { title: "Title", subtitle: "Subtitle", text: "Text", metric: "Metric", list: "List", picture: "Image", chart: "Chart", table: "Table", code: "Code", media: "Video", quote: "Quote", timeline: "Timeline", tag: "Tag", diagram: "Image" };
|
|
23
|
+
const order = ["title", "subtitle", "text", "metric", "list", "picture", "chart", "table", "code", "media", "quote", "timeline", "tag", "diagram"];
|
|
23
24
|
return [...new Set(types)].sort((a, b) => order.indexOf(a) - order.indexOf(b)).map(type => {
|
|
24
25
|
const count = types.filter(value => value === type).length;
|
|
25
26
|
return `${labels[type] ?? type}${count > 1 ? ` × ${count}` : ""}`;
|
|
@@ -45,6 +46,7 @@ function describeOptions(choices, records, current, catalog) {
|
|
|
45
46
|
if (catalog !== "layouts")
|
|
46
47
|
continue;
|
|
47
48
|
const types = layoutTypes(option.value === current ? currentRecord : records.get(String(option.value)));
|
|
49
|
+
option.placeholderTypes = records.get(String(option.value))?.placeholders?.map(placeholder => placeholder.type);
|
|
48
50
|
option.placeholders = placeholderSummary(types);
|
|
49
51
|
const same = types && currentTypes && signature(types) === signature(currentTypes);
|
|
50
52
|
const equivalent = types && currentTypes && signature(compatible(types)) === signature(compatible(currentTypes));
|
|
@@ -145,12 +147,47 @@ export function getJsonFieldContext(source, position, loadedCatalogs = {}) {
|
|
|
145
147
|
export function replaceFieldOption(context, value) {
|
|
146
148
|
if (!context.options.some(option => option.value === value))
|
|
147
149
|
throw new Error("This choice is no longer available.");
|
|
148
|
-
|
|
150
|
+
let next = context.source.slice(0, context.offset) + JSON.stringify(value) + context.source.slice(context.offset + context.length);
|
|
149
151
|
const document = JSON.parse(next);
|
|
150
152
|
if (context.path.reduce((current, key) => current?.[key], document) !== value)
|
|
151
153
|
throw new Error("This field has a duplicate key. Resolve it in the JSON before choosing an option.");
|
|
152
|
-
const
|
|
154
|
+
const types = context.options.find(option => option.value === value).placeholderTypes;
|
|
155
|
+
if (value !== context.value && types && context.catalog === 'layouts' && context.path.length === 3
|
|
156
|
+
&& context.path[0] === 'slides' && context.path[2] === 'layout' && validatePresentation(document).valid) {
|
|
157
|
+
const populated = populateLayoutPlaceholders(document, context.path[1], types);
|
|
158
|
+
next = updateSource(next, document, populated);
|
|
159
|
+
}
|
|
160
|
+
const result = validatePresentation(JSON.parse(next));
|
|
153
161
|
if (!result.valid && validatePresentation(JSON.parse(context.source)).valid)
|
|
154
162
|
throw new Error(result.errors[0]?.message ?? "This option is not valid here.");
|
|
155
163
|
return next;
|
|
156
164
|
}
|
|
165
|
+
|
|
166
|
+
// Keep existing scalar spellings and unrelated source bytes when adding slots.
|
|
167
|
+
function updateSource(source, before, after) {
|
|
168
|
+
const indent = source.match(/^[\t ]+(?=\S)/m)?.[0] ?? ' ';
|
|
169
|
+
const formattingOptions = {insertSpaces: !indent.includes('\t'), tabSize: indent.length, eol: source.includes('\r\n') ? '\r\n' : '\n'};
|
|
170
|
+
function visit(a, b, path) {
|
|
171
|
+
if (JSON.stringify(a) === JSON.stringify(b)) return;
|
|
172
|
+
if (a && b && typeof a === 'object' && typeof b === 'object' && !Array.isArray(a) && !Array.isArray(b)) {
|
|
173
|
+
for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) visit(a[key], b[key], [...path, key]);
|
|
174
|
+
} else if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
175
|
+
b.forEach((value, index) => visit(a[index], value, [...path, index]));
|
|
176
|
+
} else {
|
|
177
|
+
const node = findNodeAtLocation(parseTree(source), path);
|
|
178
|
+
if (node && b !== undefined) source = source.slice(0, node.offset) + JSON.stringify(b) + source.slice(node.offset + node.length);
|
|
179
|
+
else source = applyEdits(source, modify(source, path, b, {formattingOptions}));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
visit(before, after, []);
|
|
183
|
+
return source;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** One source edit so layout changes and new slots share one undo step. */
|
|
187
|
+
export function fieldOptionEdit(context, value) {
|
|
188
|
+
const next = replaceFieldOption(context, value), source = context.source;
|
|
189
|
+
let from = 0, to = source.length, end = next.length;
|
|
190
|
+
while (from < to && from < end && source[from] === next[from]) from++;
|
|
191
|
+
while (to > from && end > from && source[to - 1] === next[end - 1]) { to--; end--; }
|
|
192
|
+
return {from, to, insert: next.slice(from, end)};
|
|
193
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {listBlockContainers,prepareBlockInsert} from './blocks.js';
|
|
2
|
+
|
|
3
|
+
const payloadFields=['text','items','bullets','image','video','chart','table','code','metric','quote','timeline'];
|
|
4
|
+
const headingFields=new Set(['title','subtitle','tag']);
|
|
5
|
+
const fields={text:'text',list:'items',picture:'image',media:'video',diagram:'image',chart:'chart',table:'table',code:'code',metric:'metric',quote:'quote',timeline:'timeline'};
|
|
6
|
+
const region=key=>/^(?:(?:top|middle|bottom)(?:\+(?:top|middle|bottom))*(?::(?:left|center|right)(?:\+(?:left|center|right))*)?|(?:left|center|right)(?:\+(?:left|center|right))*)$/.test(key);
|
|
7
|
+
|
|
8
|
+
function emptyPayload(field){
|
|
9
|
+
if(field==='items')return [];
|
|
10
|
+
if(field==='chart')return {type:'bar',data:{columns:['',''],rows:[['',null]]}};
|
|
11
|
+
if(field==='table')return {rows:[]};
|
|
12
|
+
if(field==='metric')return {value:'',label:''};
|
|
13
|
+
if(field==='quote')return {text:'',attribution:''};
|
|
14
|
+
if(field==='timeline')return {events:[{when:'',what:''}]};
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Supply authored slots without replacing content, metadata or region placement. */
|
|
19
|
+
export function populateLayoutPlaceholders(document,slideIndex,types){
|
|
20
|
+
let next=structuredClone(document),slide=next.slides[slideIndex];
|
|
21
|
+
const counts=new Map();
|
|
22
|
+
function count(host){
|
|
23
|
+
if(!host||typeof host!=='object')return;
|
|
24
|
+
if(Array.isArray(host.blocks)){host.blocks.forEach(count);return;}
|
|
25
|
+
for(const field of payloadFields)if(Object.hasOwn(host,field)){
|
|
26
|
+
// Bullet text occupies a text slot; rich-text runs are one payload.
|
|
27
|
+
const key=field==='bullets'?'text':field;
|
|
28
|
+
counts.set(key,(counts.get(key)??0)+1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const regions=Object.keys(slide).filter(region);
|
|
32
|
+
if(regions.length)regions.forEach(key=>count(slide[key]));else count(slide);
|
|
33
|
+
for(const type of types){
|
|
34
|
+
if(headingFields.has(type)){
|
|
35
|
+
if(!Object.hasOwn(slide,type))slide[type]='';
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const field=fields[type];
|
|
39
|
+
if(!field)continue;
|
|
40
|
+
const remaining=counts.get(field)??0;
|
|
41
|
+
if(remaining){counts.set(field,remaining-1);continue;}
|
|
42
|
+
const value=emptyPayload(field);
|
|
43
|
+
// Distinct implicit root payloads can coexist. Repeated kinds need blocks.
|
|
44
|
+
if(!regions.length&&!Array.isArray(slide.blocks)&&!slide.type&&!Object.hasOwn(slide,field))slide[field]=value;
|
|
45
|
+
else {
|
|
46
|
+
// Keep positioned content in its region. Append to the last outer region,
|
|
47
|
+
// rather than creating root blocks that would hide the existing regions.
|
|
48
|
+
const path=regions.length?`/slides/${slideIndex}/${regions.at(-1)}`:`/slides/${slideIndex}`;
|
|
49
|
+
const containers=listBlockContainers(next,{slideIndex,includeImplicit:true});
|
|
50
|
+
if(!containers.some(container=>container.path===path))throw new Error('This slide has no editable content container for the new placeholder.');
|
|
51
|
+
next=prepareBlockInsert(next,path,{[field]:value}).document;
|
|
52
|
+
slide=next.slides[slideIndex];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return next;
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-editor",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -106,11 +106,11 @@
|
|
|
106
106
|
"@codemirror/state": "6.7.4",
|
|
107
107
|
"@codemirror/view": "6.43.11",
|
|
108
108
|
"@lezer/highlight": "1.2.3",
|
|
109
|
-
"@openpresentation/opf": "^0.10.
|
|
109
|
+
"@openpresentation/opf": "^0.10.1",
|
|
110
110
|
"jsonc-parser": "3.3.1"
|
|
111
111
|
},
|
|
112
112
|
"peerDependencies": {
|
|
113
|
-
"@openpresentation/opf-render": "^0.8.
|
|
113
|
+
"@openpresentation/opf-render": "^0.8.1"
|
|
114
114
|
},
|
|
115
115
|
"peerDependenciesMeta": {
|
|
116
116
|
"@openpresentation/opf-render": {
|
|
@@ -118,8 +118,8 @@
|
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
|
-
"@openpresentation/opf-pptx": "0.8.
|
|
122
|
-
"@openpresentation/opf-render": "0.8.
|
|
121
|
+
"@openpresentation/opf-pptx": "0.8.1",
|
|
122
|
+
"@openpresentation/opf-render": "0.8.1",
|
|
123
123
|
"esbuild": "0.28.2",
|
|
124
124
|
"playwright": "1.63.0"
|
|
125
125
|
}
|