@openpresentation/opf-editor 0.29.0 → 0.29.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/dist/workspace.d.ts +9 -0
- package/dist/workspace.js +122 -17
- package/package.json +7 -7
package/dist/workspace.d.ts
CHANGED
|
@@ -383,6 +383,15 @@ export interface OpfEditorSetDocumentOptions {
|
|
|
383
383
|
rebase?: boolean;
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
+
/**
|
|
387
|
+
* The handle of a mounted editor. It is returned at once, before the parts have mounted (they wait for the fonts and, in a layout that shows
|
|
388
|
+
* slides, for the slide chunk), and every method works from the first moment: a setting (`setDensity`, `setExportMode`, `setView`, `setSlide`,
|
|
389
|
+
* `setReadOnly`, `setTheme`, `setName`) is kept and read when the parts mount, and a call that acts on a part (`select`, `goTo`, `showPanel`,
|
|
390
|
+
* `focus`, `openFind`, `present`, and the Studio's `setLayout`, `setZoom`, `fit`, `openLayout` and `commands.register`) is queued and runs, in
|
|
391
|
+
* order, as soon as the editor is mounted, before `mounted` is emitted; a later call to the same method replaces an earlier one. A queued call
|
|
392
|
+
* returns `true` (accepted) or, where it needs a part the editor does not have, `false`. Only `setDocument` and `setSourceText`, whose result
|
|
393
|
+
* depends on the document, throw before `ready`.
|
|
394
|
+
*/
|
|
386
395
|
export interface OpfEditor {
|
|
387
396
|
/** The mount element and the node the editor built inside it. */
|
|
388
397
|
readonly element: HTMLElement;
|
package/dist/workspace.js
CHANGED
|
@@ -266,6 +266,31 @@ export function createOpfEditor(element, options = {}) {
|
|
|
266
266
|
let statusText = "";
|
|
267
267
|
let density = "comfortable";
|
|
268
268
|
let exportMode = "menu";
|
|
269
|
+
// The parts are bound once the slide chunk has arrived and the fonts are at hand; `partsReady` is true from then on. A call on the handle made
|
|
270
|
+
// before that (a React host's effects run right after `createOpfEditor` returns) must neither reach a factory that is not bound yet nor be lost:
|
|
271
|
+
// a setting is remembered in `requested` and read when the parts mount; a call that acts on a mounted part waits in `queued` and runs, in order,
|
|
272
|
+
// once the editor is mounted (a later call with the same key replaces the earlier one).
|
|
273
|
+
let partsReady = false;
|
|
274
|
+
const requested = {};
|
|
275
|
+
const queued = [];
|
|
276
|
+
const waiting = () => !mounted && !destroyed;
|
|
277
|
+
const queue = (key, task) => {
|
|
278
|
+
const at = queued.findIndex((entry) => entry.key === key);
|
|
279
|
+
if (at >= 0) queued.splice(at, 1);
|
|
280
|
+
const entry = { key, task };
|
|
281
|
+
queued.push(entry);
|
|
282
|
+
return entry;
|
|
283
|
+
};
|
|
284
|
+
function flushQueued() {
|
|
285
|
+
for (const { task } of queued.splice(0)) {
|
|
286
|
+
if (destroyed) return;
|
|
287
|
+
try {
|
|
288
|
+
task();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
report(error);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
269
294
|
let frameSize = { width: 0, height: 0 };
|
|
270
295
|
const draftMemory = createSourceMemory();
|
|
271
296
|
const variablesMode = options.variables === "resolved" ? "resolved" : "authored";
|
|
@@ -392,6 +417,11 @@ export function createOpfEditor(element, options = {}) {
|
|
|
392
417
|
return false;
|
|
393
418
|
}
|
|
394
419
|
function goTo(index, path) {
|
|
420
|
+
if (waiting()) {
|
|
421
|
+
go(index);
|
|
422
|
+
queue("goTo", () => goTo(index, path));
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
395
425
|
if (index !== slideIndex && !go(index)) return false;
|
|
396
426
|
if (studioCtl) studioCtl.revealPath(path ?? `slides.${index}`);
|
|
397
427
|
else if (path) canvas?.reveal(path);
|
|
@@ -454,6 +484,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
454
484
|
if (!views.includes(view)) return false;
|
|
455
485
|
if (!mounted) {
|
|
456
486
|
activeView = view;
|
|
487
|
+
requested.view = view;
|
|
457
488
|
return true;
|
|
458
489
|
}
|
|
459
490
|
if (view !== "slide" && canvas && !canvas.commit()) return false;
|
|
@@ -496,6 +527,15 @@ export function createOpfEditor(element, options = {}) {
|
|
|
496
527
|
return true;
|
|
497
528
|
}
|
|
498
529
|
function showPanel(name, focus = false) {
|
|
530
|
+
if (!mounted) {
|
|
531
|
+
// The tabs of the shell are known now, the Studio's when it mounts; the panel is shown after the default one.
|
|
532
|
+
if (destroyed || (!studioLayout && !controls.tabs?.[name])) return false;
|
|
533
|
+
queue("panel", () => showPanel(name, focus));
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
return showPanelNow(name, focus);
|
|
537
|
+
}
|
|
538
|
+
function showPanelNow(name, focus = false) {
|
|
499
539
|
if (studioCtl) return studioCtl.showTab(name, { focus });
|
|
500
540
|
const tabs = controls.tabs;
|
|
501
541
|
if (!tabs?.[name]) return false;
|
|
@@ -513,6 +553,9 @@ export function createOpfEditor(element, options = {}) {
|
|
|
513
553
|
|
|
514
554
|
// ---- the parts --------------------------------------------------------------------------------------------------------------
|
|
515
555
|
function mountSlideRow() {
|
|
556
|
+
// The row is built from the slide chunk's factories: before they are bound (and after destroy) there is nothing to rebuild; the first
|
|
557
|
+
// mount reads the density and the layout as they are by then.
|
|
558
|
+
if (!partsReady || destroyed) return;
|
|
516
559
|
unbindKeys?.();
|
|
517
560
|
slideNav?.destroy();
|
|
518
561
|
strip?.destroy();
|
|
@@ -590,7 +633,7 @@ export function createOpfEditor(element, options = {}) {
|
|
|
590
633
|
...(configured.cursorDelay !== undefined ? { cursorDelay: configured.cursorDelay } : {}),
|
|
591
634
|
...(configured.completions !== undefined ? { completions: configured.completions } : {}),
|
|
592
635
|
...(configured.catalogs !== undefined ? { catalogs: configured.catalogs } : {}),
|
|
593
|
-
...(configured.name !== undefined ? { name: configured.name } : {}),
|
|
636
|
+
...((requested.name ?? configured.name) !== undefined ? { name: requested.name ?? configured.name } : {}),
|
|
594
637
|
...(text !== undefined ? { source: text } : {}),
|
|
595
638
|
editor: ui,
|
|
596
639
|
format: configured.format ?? "json",
|
|
@@ -847,6 +890,10 @@ export function createOpfEditor(element, options = {}) {
|
|
|
847
890
|
}
|
|
848
891
|
|
|
849
892
|
function present(from) {
|
|
893
|
+
if (waiting() && parts.present) {
|
|
894
|
+
queue("present", () => present(from));
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
850
897
|
if (destroyed || !mounted || !presenter || presenter.isRunning() || !inSync || slides.length === 0) return false;
|
|
851
898
|
// An open canvas edit is committed, and text typed a moment ago is applied, before the document is shown; a source that is not a valid deck waits.
|
|
852
899
|
if (canvas?.commit() === false) return false;
|
|
@@ -1008,17 +1055,17 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1008
1055
|
controls.redo?.addEventListener("click", () => redo());
|
|
1009
1056
|
for (const [name, button] of Object.entries(controls.views ?? {})) button.addEventListener("click", () => setView(name, { focus: true }));
|
|
1010
1057
|
for (const [name, tab] of Object.entries(controls.tabs ?? {})) {
|
|
1011
|
-
tab.addEventListener("click", () =>
|
|
1058
|
+
tab.addEventListener("click", () => showPanelNow(name));
|
|
1012
1059
|
tab.addEventListener("keydown", (event) => {
|
|
1013
1060
|
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
1014
1061
|
event.preventDefault();
|
|
1015
1062
|
const names = Object.keys(controls.tabs);
|
|
1016
1063
|
const at = names.indexOf(name);
|
|
1017
1064
|
const next = event.key === "Home" ? 0 : event.key === "End" ? names.length - 1 : (at + (event.key === "ArrowRight" ? 1 : -1) + names.length) % names.length;
|
|
1018
|
-
|
|
1065
|
+
showPanelNow(names[next], true);
|
|
1019
1066
|
});
|
|
1020
1067
|
}
|
|
1021
|
-
if (controls.tabs)
|
|
1068
|
+
if (controls.tabs) showPanelNow(options.panel && controls.tabs[options.panel] ? options.panel : Object.keys(controls.tabs)[0]);
|
|
1022
1069
|
if (options.shortcuts !== false) {
|
|
1023
1070
|
// Ctrl/Cmd+Z and +Shift+Z undo and redo when nothing that edits text has focus (the code editor and the canvas's inputs keep their own).
|
|
1024
1071
|
const onKey = (event) => {
|
|
@@ -1055,8 +1102,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1055
1102
|
media.addEventListener("change", change);
|
|
1056
1103
|
cleanups.push(() => media.removeEventListener("change", change));
|
|
1057
1104
|
};
|
|
1058
|
-
follow(options.density, "(pointer: coarse)", setDensity);
|
|
1059
|
-
follow(options.exportMode, "(max-width: 900px)", setExportMode);
|
|
1105
|
+
follow("density" in requested ? requested.density : options.density, "(pointer: coarse)", setDensity);
|
|
1106
|
+
follow("exportMode" in requested ? requested.exportMode : options.exportMode, "(max-width: 900px)", setExportMode);
|
|
1060
1107
|
}
|
|
1061
1108
|
|
|
1062
1109
|
// ---- the handle -------------------------------------------------------------------------------------------------------------
|
|
@@ -1092,6 +1139,14 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1092
1139
|
studioCtl?.refresh();
|
|
1093
1140
|
}
|
|
1094
1141
|
function setDensity(setting) {
|
|
1142
|
+
if (destroyed) return;
|
|
1143
|
+
// Before the parts mount the setting is kept; the mount reads it instead of the `density` option.
|
|
1144
|
+
if (!partsReady) {
|
|
1145
|
+
requested.density = setting;
|
|
1146
|
+
density = densityFor(setting);
|
|
1147
|
+
root.setAttribute("data-density", density);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1095
1150
|
const next = densityFor(setting);
|
|
1096
1151
|
if (next === density) return;
|
|
1097
1152
|
density = next;
|
|
@@ -1103,6 +1158,8 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1103
1158
|
mountSlideRow();
|
|
1104
1159
|
}
|
|
1105
1160
|
function setExportMode(setting) {
|
|
1161
|
+
if (destroyed) return;
|
|
1162
|
+
if (!partsReady) requested.exportMode = setting;
|
|
1106
1163
|
exportMode = exportModeFor(setting);
|
|
1107
1164
|
exportMenu?.setMode(exportMode);
|
|
1108
1165
|
}
|
|
@@ -1151,6 +1208,26 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1151
1208
|
return applied;
|
|
1152
1209
|
}
|
|
1153
1210
|
|
|
1211
|
+
// The Studio's command registry is not there before mount: `register` keeps the command and adds it when the Studio mounts (the function it
|
|
1212
|
+
// returns removes it either way); `list` is empty and `run` finds nothing yet.
|
|
1213
|
+
const preMountCommands = {
|
|
1214
|
+
list: () => [],
|
|
1215
|
+
run: () => false,
|
|
1216
|
+
register(command) {
|
|
1217
|
+
if (!waiting() || !studioLayout) return () => {};
|
|
1218
|
+
let stop;
|
|
1219
|
+
let withdrawn = false;
|
|
1220
|
+
const entry = queue(Symbol("command"), () => {
|
|
1221
|
+
if (!withdrawn) stop = studioCtl?.commands.register(command);
|
|
1222
|
+
});
|
|
1223
|
+
return () => {
|
|
1224
|
+
withdrawn = true;
|
|
1225
|
+
const at = queued.indexOf(entry);
|
|
1226
|
+
if (at >= 0) queued.splice(at, 1);
|
|
1227
|
+
stop?.();
|
|
1228
|
+
};
|
|
1229
|
+
},
|
|
1230
|
+
};
|
|
1154
1231
|
let fontsRef = { value: undefined };
|
|
1155
1232
|
let releaseFonts;
|
|
1156
1233
|
let readyPromise;
|
|
@@ -1220,24 +1297,33 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1220
1297
|
},
|
|
1221
1298
|
/** CE-20: switch the Studio between "studio" and "playground" in place (same session, history, slide and selection). */
|
|
1222
1299
|
setLayout(next) {
|
|
1223
|
-
if (
|
|
1224
|
-
|
|
1300
|
+
if (studioCtl) return studioCtl.setLayout(next);
|
|
1301
|
+
// Before mount the switch is queued (true), as the Studio's own check of the name would refuse it later.
|
|
1302
|
+
if (!waiting() || !studioLayout) return false;
|
|
1303
|
+
if (next !== "studio" && next !== "playground") throw new RangeError('setLayout takes "studio" or "playground".');
|
|
1304
|
+
queue("layout", () => studioCtl?.setLayout(next));
|
|
1305
|
+
return true;
|
|
1225
1306
|
},
|
|
1226
1307
|
get zoom() {
|
|
1227
1308
|
return studioCtl?.zoom ?? 1;
|
|
1228
1309
|
},
|
|
1229
1310
|
setZoom(value) {
|
|
1230
|
-
studioCtl
|
|
1311
|
+
if (studioCtl) studioCtl.setZoom(value);
|
|
1312
|
+
else if (waiting() && studioLayout) queue("zoom", () => studioCtl?.setZoom(value));
|
|
1231
1313
|
},
|
|
1232
1314
|
fit(mode) {
|
|
1233
|
-
studioCtl
|
|
1315
|
+
if (studioCtl) studioCtl.fit(mode);
|
|
1316
|
+
else if (waiting() && studioLayout) queue("zoom", () => studioCtl?.fit(mode));
|
|
1234
1317
|
},
|
|
1235
1318
|
openLayout(layoutId, openOptions = {}) {
|
|
1236
|
-
|
|
1319
|
+
if (studioCtl) return studioCtl.openLayout(layoutId, openOptions);
|
|
1320
|
+
if (!waiting() || !studioLayout) return false;
|
|
1321
|
+
queue("openLayout", () => studioCtl?.openLayout(layoutId, openOptions));
|
|
1322
|
+
return true;
|
|
1237
1323
|
},
|
|
1238
|
-
/** CE-20: the Studio's command registry (`list`, `run`, `register`); empty outside the Studio. */
|
|
1324
|
+
/** CE-20: the Studio's command registry (`list`, `run`, `register`); empty outside the Studio. Commands registered before mount are added when it mounts. */
|
|
1239
1325
|
get commands() {
|
|
1240
|
-
return studioCtl?.commands ??
|
|
1326
|
+
return studioCtl?.commands ?? preMountCommands;
|
|
1241
1327
|
},
|
|
1242
1328
|
get view() {
|
|
1243
1329
|
return activeView;
|
|
@@ -1273,6 +1359,10 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1273
1359
|
setSlide: go,
|
|
1274
1360
|
goTo,
|
|
1275
1361
|
select(path, selectOptions = {}) {
|
|
1362
|
+
if (waiting()) {
|
|
1363
|
+
queue("select", () => handle.select(path, selectOptions));
|
|
1364
|
+
return studioLayout ? true : null;
|
|
1365
|
+
}
|
|
1276
1366
|
if (studioCtl) return studioCtl.selectTargets(path, selectOptions);
|
|
1277
1367
|
return canvas?.reveal(path, { focus: Boolean(selectOptions.focus) }) ?? null;
|
|
1278
1368
|
},
|
|
@@ -1290,6 +1380,10 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1290
1380
|
return canvas?.commit() !== false;
|
|
1291
1381
|
},
|
|
1292
1382
|
focus(target) {
|
|
1383
|
+
if (waiting()) {
|
|
1384
|
+
queue("focus", () => handle.focus(target));
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1293
1387
|
if ((target ?? (pane ? "source" : "canvas")) === "source") {
|
|
1294
1388
|
pane?.editor?.api?.focus?.();
|
|
1295
1389
|
return Boolean(pane);
|
|
@@ -1299,6 +1393,10 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1299
1393
|
return Boolean(node);
|
|
1300
1394
|
},
|
|
1301
1395
|
openFind(openOptions) {
|
|
1396
|
+
if (waiting() && parts.find) {
|
|
1397
|
+
queue("find", () => handle.openFind(openOptions));
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1302
1400
|
if (!find) return false;
|
|
1303
1401
|
find.openPanel(openOptions ?? {});
|
|
1304
1402
|
return true;
|
|
@@ -1311,10 +1409,12 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1311
1409
|
return { text, format: pane.getFormat(), draft: draftInvalid };
|
|
1312
1410
|
},
|
|
1313
1411
|
setName(name) {
|
|
1412
|
+
if (waiting()) requested.name = name;
|
|
1314
1413
|
pane?.setName(name);
|
|
1315
1414
|
},
|
|
1316
1415
|
getName() {
|
|
1317
|
-
return pane
|
|
1416
|
+
if (pane) return pane.getName();
|
|
1417
|
+
return waiting() ? (requested.name ?? options.source?.name) : undefined;
|
|
1318
1418
|
},
|
|
1319
1419
|
setReadOnly(value) {
|
|
1320
1420
|
readOnly = Boolean(value);
|
|
@@ -1371,16 +1471,21 @@ export function createOpfEditor(element, options = {}) {
|
|
|
1371
1471
|
function mount(resolved) {
|
|
1372
1472
|
if (studioLayout && !studioShell) buildStudio();
|
|
1373
1473
|
fonts = resolved;
|
|
1374
|
-
density
|
|
1375
|
-
|
|
1474
|
+
// A density, an export mode or a view the host chose before the parts mounted wins over the option.
|
|
1475
|
+
density = densityFor("density" in requested ? requested.density : options.density);
|
|
1476
|
+
exportMode = exportModeFor("exportMode" in requested ? requested.exportMode : options.exportMode);
|
|
1376
1477
|
root.setAttribute("data-density", density);
|
|
1478
|
+
partsReady = true;
|
|
1377
1479
|
mountParts();
|
|
1378
1480
|
mounted = true;
|
|
1379
1481
|
if (readOnly) applyReadOnly();
|
|
1380
1482
|
say(MESSAGES.ready);
|
|
1381
|
-
|
|
1483
|
+
const wantedView = "view" in requested ? requested.view : options.view;
|
|
1484
|
+
activeView = "slide";
|
|
1485
|
+
if (wantedView && wantedView !== "slide") setView(wantedView);
|
|
1382
1486
|
else markView("slide");
|
|
1383
1487
|
syncChrome();
|
|
1488
|
+
flushQueued();
|
|
1384
1489
|
}
|
|
1385
1490
|
readyPromise = new Promise((resolve, reject) => {
|
|
1386
1491
|
const start = (arrived) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpresentation/opf-editor",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "Embeddable local OPF editor primitives built on the deterministic renderer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -302,12 +302,12 @@
|
|
|
302
302
|
"@lezer/highlight": "^1.2.5",
|
|
303
303
|
"jsonc-parser": "3.3.1",
|
|
304
304
|
"yaml": "^2.9.1",
|
|
305
|
-
"@openpresentation/opf": "^0.29.
|
|
305
|
+
"@openpresentation/opf": "^0.29.1"
|
|
306
306
|
},
|
|
307
307
|
"peerDependencies": {
|
|
308
308
|
"react": ">=18",
|
|
309
309
|
"react-dom": ">=18",
|
|
310
|
-
"@openpresentation/opf-render": "^0.29.
|
|
310
|
+
"@openpresentation/opf-render": "^0.29.1"
|
|
311
311
|
},
|
|
312
312
|
"peerDependenciesMeta": {
|
|
313
313
|
"@openpresentation/opf-render": {
|
|
@@ -376,9 +376,9 @@
|
|
|
376
376
|
"react-dom": "19.3.1",
|
|
377
377
|
"react-18": "npm:react@18.3.1",
|
|
378
378
|
"react-dom-18": "npm:react-dom@18.3.1",
|
|
379
|
-
"@openpresentation/
|
|
380
|
-
"@openpresentation/
|
|
381
|
-
"@openpresentation/opf-render": "0.29.
|
|
379
|
+
"@openpresentation/opf-pptx": "0.29.1",
|
|
380
|
+
"@openpresentation/gallery": "0.29.1",
|
|
381
|
+
"@openpresentation/opf-render": "0.29.1"
|
|
382
382
|
},
|
|
383
383
|
"scripts": {
|
|
384
384
|
"build": "node scripts/build.mjs",
|
|
@@ -431,5 +431,5 @@
|
|
|
431
431
|
"test:workspace-browser": "node test/workspace-browser.mjs",
|
|
432
432
|
"test:workspace-react-browser": "node test/workspace-react-browser.mjs"
|
|
433
433
|
},
|
|
434
|
-
"gitHead": "
|
|
434
|
+
"gitHead": "e9f46c24606ce0f474347b984eacea5dce7fd839"
|
|
435
435
|
}
|