@openpresentation/opf-editor 0.28.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/README.md +20 -6
- package/dist/canvas.d.ts +13 -1
- package/dist/canvas.js +39 -9
- package/dist/chart-options-panel.js +16 -16
- package/dist/codemirror-guard.js +69 -0
- package/dist/content-controls.js +127 -74
- package/dist/data-grid.js +15 -7
- package/dist/design-controls.d.ts +13 -0
- package/dist/design-controls.js +134 -124
- package/dist/exact-source.d.ts +30 -0
- package/dist/font-gate.js +20 -1
- package/dist/json-editor.js +8 -2
- package/dist/json-options.d.ts +2 -1
- package/dist/json-options.js +7 -6
- package/dist/layout-templates.d.ts +8 -3
- package/dist/layout-templates.js +30 -16
- package/dist/numbering-panel.js +2 -3
- package/dist/react.d.ts +12 -4
- package/dist/react.js +39 -5
- package/dist/slide-adapter.d.ts +49 -0
- package/dist/slide-adapter.js +289 -0
- package/dist/slide-edits.d.ts +46 -0
- package/dist/slide-edits.js +629 -0
- package/dist/slide-manager.js +2 -1
- package/dist/slide-scene.d.ts +96 -0
- package/dist/slide-scene.js +413 -0
- package/dist/source-checker.js +45 -6
- package/dist/source-completions.js +6 -4
- package/dist/source-pane.d.ts +18 -6
- package/dist/source-pane.js +45 -0
- package/dist/source-worker.js +8 -4
- package/dist/studio-canvas.d.ts +34 -0
- package/dist/studio-canvas.js +586 -0
- package/dist/studio-commands.d.ts +15 -0
- package/dist/studio-commands.js +155 -0
- package/dist/studio-design.d.ts +16 -0
- package/dist/studio-design.js +354 -0
- package/dist/studio-kit/dom.d.ts +16 -0
- package/dist/studio-kit/dom.js +42 -6
- package/dist/studio-layers.d.ts +12 -0
- package/dist/studio-layers.js +218 -0
- package/dist/studio.d.ts +31 -0
- package/dist/studio.js +1608 -0
- package/dist/styles.css +1546 -117
- package/dist/thumbnail-cache.d.ts +2 -0
- package/dist/thumbnail-cache.js +10 -1
- package/dist/workspace-measure.js +6 -4
- package/dist/workspace-parts.js +20 -0
- package/dist/workspace-shell.js +25 -15
- package/dist/workspace-theme.js +3 -0
- package/dist/workspace.d.ts +97 -9
- package/dist/workspace.js +325 -59
- package/package.json +29 -18
|
@@ -6,7 +6,7 @@ import { isolateHistory } from "@codemirror/commands";
|
|
|
6
6
|
import { EditorSelection } from "@codemirror/state";
|
|
7
7
|
import { describeField } from "@openpresentation/opf/schema";
|
|
8
8
|
import { listCatalogRecords, mergeCatalogs } from "./catalogs.js";
|
|
9
|
-
import {
|
|
9
|
+
import { CUSTOM_LAYOUTS_GROUP, layoutGroupName, layoutGroupOrder, layoutRegionSummary } from "./layout-templates.js";
|
|
10
10
|
import { Document, parse as parseYamlDocument, parseDocument } from "yaml";
|
|
11
11
|
import { pathAt, scanMarkdown } from "@openpresentation/opf/source";
|
|
12
12
|
import { findNodeAtLocation, getNodeValue } from "jsonc-parser";
|
|
@@ -188,12 +188,14 @@ function literalOption(label, literal, extra) {
|
|
|
188
188
|
function catalogOptions(kind, context, catalogs) {
|
|
189
189
|
let records = [];
|
|
190
190
|
try { records = listCatalogRecords(context.document, kind, { catalogs }); } catch { records = []; }
|
|
191
|
+
// Deck records first, then the catalog's groups in its own order (and its records in its order within a group), then custom layouts.
|
|
192
|
+
const position = new Map(records.map((record, index) => [record, index]));
|
|
193
|
+
const groups = kind === "layouts" ? layoutGroupOrder(records) : [];
|
|
191
194
|
const order = (record) => {
|
|
192
195
|
if (record.origin === "document") return -1;
|
|
193
196
|
if (kind !== "layouts") return 0;
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
return group < 0 ? LAYOUT_INTENT_GROUPS.length * 100 : group * 100 + LAYOUT_INTENT_GROUPS[group].ids.indexOf(id);
|
|
197
|
+
const group = layoutGroupName(record);
|
|
198
|
+
return (group === CUSTOM_LAYOUTS_GROUP ? groups.length : groups.indexOf(group)) * 100000 + position.get(record);
|
|
197
199
|
};
|
|
198
200
|
records.sort((a, b) => order(a) - order(b) || a.label.localeCompare(b.label, "en", { numeric: true }) || a.id.localeCompare(b.id, "en"));
|
|
199
201
|
const intent = kind === "layouts";
|
package/dist/source-pane.d.ts
CHANGED
|
@@ -179,13 +179,17 @@ export interface SourcePaneOptions {
|
|
|
179
179
|
smartListEnter?: boolean;
|
|
180
180
|
/**
|
|
181
181
|
* Run the pane's text checks (reading YAML or Markdown, the syntax pass of JSON and the document validation) in a Web Worker, so typing in a deck
|
|
182
|
-
* of a thousand slides never blocks the main thread.
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
182
|
+
* of a thousand slides never blocks the main thread. `true` starts the package's own worker with `new Worker(new URL("./source-worker.js",
|
|
183
|
+
* import.meta.url), { type: "module" })`, which webpack, Turbopack and Vite (build) bundle; a function returns the Worker
|
|
184
|
+
* (`() => new Worker(new URL("@openpresentation/opf-editor/source-worker", import.meta.url), { type: "module" })`); a Worker is used as given; a URL
|
|
185
|
+
* (or a string) is the address of a worker script the host built itself (esbuild: build `@openpresentation/opf-editor/source-worker` as an entry
|
|
186
|
+
* point) and is started as a module worker. The worker file imports core, so it only runs bundled. See docs/embedding.md, "The source worker",
|
|
187
|
+
* for each bundler. Only the typing delay uses the worker: every explicit action (`getText`, switching the format, `replaceDocument`, Format, Undo)
|
|
188
|
+
* is applied on the main thread at once. When the worker cannot start, fails, stops answering, or loads but does not say it is ready within 5 s (a
|
|
189
|
+
* script the bundler emptied), the pane checks on the main thread from then on and `onError` is called once with a message that names the
|
|
190
|
+
* worker; no edit is lost. Default `false` (everything on the main thread).
|
|
187
191
|
*/
|
|
188
|
-
worker?: boolean | Worker | (() => Worker);
|
|
192
|
+
worker?: boolean | string | URL | Worker | (() => Worker);
|
|
189
193
|
/** A preview for the card F12 and Alt+F12 open on a catalog record only the app provides (see `JsonCodeEditorOptions.resolvePreview`). */
|
|
190
194
|
resolvePreview?: (kind: string, id: string) => HTMLElement | string | undefined | Promise<HTMLElement | string | undefined>;
|
|
191
195
|
onError?(error: unknown): void;
|
|
@@ -218,6 +222,14 @@ export interface SourcePane {
|
|
|
218
222
|
* (`invalid-opf-edit` with `issues`) and changes nothing. Works in a read-only pane.
|
|
219
223
|
*/
|
|
220
224
|
replaceDocument(document: unknown, options?: ReplaceDocumentOptions): EditorChange;
|
|
225
|
+
/**
|
|
226
|
+
* Load raw source text as the pane's whole text, as if pasted over everything: a file as it was read, a draft that does not parse. Nothing is
|
|
227
|
+
* converted; the bytes stay as given (line endings included; a leading byte order mark is dropped). `options.format` switches the pane to the
|
|
228
|
+
* text's format first (without writing the document in it), `options.name` renames the file. Text that reads as a document the session
|
|
229
|
+
* accepts is applied at once as one undoable edit; anything else stays as a draft with its problems and the session keeps its last valid
|
|
230
|
+
* document. Returns true when the session now holds the text's document. Throws in a read-only pane.
|
|
231
|
+
*/
|
|
232
|
+
setSourceText(text: string, options?: { format?: SourceFormat; name?: string }): boolean;
|
|
221
233
|
/** Re-read the document from the session. */
|
|
222
234
|
refresh(): void;
|
|
223
235
|
/** Replace the host findings; `null` returns to the session's own validation. */
|
package/dist/source-pane.js
CHANGED
|
@@ -708,6 +708,44 @@ export function createSourcePane(container, options = {}) {
|
|
|
708
708
|
options.onFormatChange?.(format, previous);
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Put `text` in the pane as its whole source, in `next` (default the pane's format), as if the person had pasted it over everything: no
|
|
713
|
+
* conversion, the bytes as given (line endings included; a byte order mark is dropped). Text that reads as a document the session accepts is
|
|
714
|
+
* applied at once (one undoable edit; Undo in the code brings the old text back); anything else stays in the pane as a draft with its problems,
|
|
715
|
+
* and the session keeps its last valid document. Returns true when the session now holds the text's document.
|
|
716
|
+
*/
|
|
717
|
+
function setSourceText(text, { format: next, name: nextName } = {}) {
|
|
718
|
+
if (destroyed) throw new Error("The source pane was destroyed.");
|
|
719
|
+
if (typeof text !== "string") throw new TypeError("setSourceText needs the source text as a string.");
|
|
720
|
+
if (next !== undefined && !FORMATS.some((entry) => entry.id === next)) throw new TypeError(`setSourceText: format must be "json", "yaml" or "md", not ${JSON.stringify(next)}.`);
|
|
721
|
+
if (readOnly) throw new Error("The source pane is read-only.");
|
|
722
|
+
flush();
|
|
723
|
+
closeConfirm(false);
|
|
724
|
+
const previous = format;
|
|
725
|
+
for (const key of Object.keys(drafts)) delete drafts[key];
|
|
726
|
+
transientNotes = [];
|
|
727
|
+
rejectionFindings = [];
|
|
728
|
+
syntaxFindings = [];
|
|
729
|
+
if (next !== undefined && next !== format) {
|
|
730
|
+
format = next;
|
|
731
|
+
noteFindings = [];
|
|
732
|
+
control.setLanguage(formatOf(next).language);
|
|
733
|
+
refreshDecorations();
|
|
734
|
+
}
|
|
735
|
+
if (nextName !== undefined) name = sanitizeName(nextName) || "deck";
|
|
736
|
+
updateBar();
|
|
737
|
+
control.api.replace(0, control.api.getValue().length, text.replace(/^\uFEFF/, ""));
|
|
738
|
+
control.api.setSelection(0);
|
|
739
|
+
// Read and apply it now, also when it is the text already shown (in another format, say).
|
|
740
|
+
baseline = null;
|
|
741
|
+
flush();
|
|
742
|
+
renderedKey = "";
|
|
743
|
+
renderFindings();
|
|
744
|
+
syncAttributes();
|
|
745
|
+
if (format !== previous) options.onFormatChange?.(format, previous);
|
|
746
|
+
return !dirty;
|
|
747
|
+
}
|
|
748
|
+
|
|
711
749
|
// ---- Menu ---------------------------------------------------------------------------------------------------------
|
|
712
750
|
const menuOpen = () => !menu.hidden;
|
|
713
751
|
function openMenu(focus = "checked") {
|
|
@@ -795,6 +833,8 @@ export function createSourcePane(container, options = {}) {
|
|
|
795
833
|
nameButton.textContent = name;
|
|
796
834
|
nameButton.setAttribute("aria-label", `Rename ${name}`);
|
|
797
835
|
formatLabel.textContent = `.opf.${current.extension}`;
|
|
836
|
+
// The visible extension is part of the button's name (WCAG 2.5.3, label in name).
|
|
837
|
+
formatButton.setAttribute("aria-label", `Source format .opf.${current.extension}`);
|
|
798
838
|
root.dataset.format = current.id;
|
|
799
839
|
canonicalAction.hidden = current.id === "json";
|
|
800
840
|
for (const item of menuItems) item.setAttribute("aria-checked", String(item.dataset.format === current.id));
|
|
@@ -1291,6 +1331,11 @@ export function createSourcePane(container, options = {}) {
|
|
|
1291
1331
|
renderedKey = "";
|
|
1292
1332
|
renderFindings();
|
|
1293
1333
|
},
|
|
1334
|
+
/**
|
|
1335
|
+
* Load raw source text (a file, a paste, a draft that does not parse) as the pane's whole text, in `options.format`, without converting it.
|
|
1336
|
+
* Returns true when it read as a document the session accepted, false when it stays a draft with its problems. See `setSourceText` above.
|
|
1337
|
+
*/
|
|
1338
|
+
setSourceText,
|
|
1294
1339
|
/** Switch the source format ("json", "yaml" or "md"). Returns false when the document cannot be written in it. */
|
|
1295
1340
|
setFormat: (next) => setFormatInternal(next, false),
|
|
1296
1341
|
getFormat: () => format,
|
package/dist/source-worker.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// The worker entry of the source pane's text checks (`createSourcePane(el, { worker })`).
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// The worker entry of the source pane's text checks (`createSourcePane(el, { worker })`). `worker: true` starts it with
|
|
2
|
+
// `new Worker(new URL("./source-worker.js", import.meta.url), { type: "module" })`, which webpack, Turbopack, Vite (build) and Parcel bundle; a host can
|
|
3
|
+
// also write `new Worker(new URL("@openpresentation/opf-editor/source-worker", import.meta.url), { type: "module" })` itself, or build this file as an
|
|
4
|
+
// extra entry point (esbuild) and pass its URL. The file imports core, so it runs only bundled. See docs/embedding.md, "The source worker". It does the
|
|
5
|
+
// same work as the pane's own inline path (`./source-check.js`), off the main thread. It says `{ ready: true }` once it runs, so the pane can tell a
|
|
6
|
+
// worker that loaded but never started (an emptied script) from a slow check. Requests run in order, but when several checks of one kind are waiting only the newest runs
|
|
5
7
|
// (the older ones are answered with `skipped`): the pane drops stale replies anyway.
|
|
6
8
|
import { createCheckService } from "./source-check.js";
|
|
7
9
|
|
|
@@ -23,6 +25,8 @@ function drain() {
|
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
self.postMessage({ ready: true });
|
|
29
|
+
|
|
26
30
|
self.onmessage = (event) => {
|
|
27
31
|
queue.push(event.data);
|
|
28
32
|
// Messages that arrive while a check runs queue behind it; draining on the next task lets everything already waiting be seen together.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** CE-20 (internal): the Studio's canvas: the kit viewport and overlay over the workspace's canvas, driven by the slide adapter. */
|
|
2
|
+
import type { KitDom } from "./studio-kit/dom.js";
|
|
3
|
+
import type { CanvasEditor } from "./canvas.js";
|
|
4
|
+
import type { SlideAdapter } from "./slide-adapter.js";
|
|
5
|
+
import type { SceneNode } from "./slide-scene.js";
|
|
6
|
+
|
|
7
|
+
export declare function dottedPath(pointer: string): string;
|
|
8
|
+
export declare function isTextNode(node: SceneNode | null | undefined): boolean;
|
|
9
|
+
export interface StudioCanvas {
|
|
10
|
+
paint(): void;
|
|
11
|
+
paintHover(): void;
|
|
12
|
+
readonly zoom: number;
|
|
13
|
+
readonly panMode: boolean;
|
|
14
|
+
setZoom(zoom: number, options?: { anchor?: { clientX: number; clientY: number } }): number;
|
|
15
|
+
step(direction: number): number;
|
|
16
|
+
fit(mode?: "slide" | "width"): number;
|
|
17
|
+
readonly tool: "select" | "hand" | "text";
|
|
18
|
+
setTool(tool: "select" | "hand" | "text"): void;
|
|
19
|
+
focusPrimary(): void;
|
|
20
|
+
boxOf(key: string): Element | undefined;
|
|
21
|
+
describe(node: SceneNode): string;
|
|
22
|
+
beginInsertDrag(kind: string, event: PointerEvent, options?: { onEnd?(): void }): boolean;
|
|
23
|
+
readonly dragging: boolean;
|
|
24
|
+
cancelDrag(): void;
|
|
25
|
+
setEnabled(on: boolean): void;
|
|
26
|
+
destroy(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare function createStudioCanvas(options: {
|
|
29
|
+
dom: KitDom;
|
|
30
|
+
elements: { root: HTMLElement; viewport: HTMLElement; stage: HTMLElement; frame: HTMLElement; overlay: HTMLElement };
|
|
31
|
+
canvas: CanvasEditor;
|
|
32
|
+
adapter: SlideAdapter;
|
|
33
|
+
studio: any;
|
|
34
|
+
}): StudioCanvas;
|