@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/table-options.d.ts
CHANGED
|
@@ -39,15 +39,15 @@ export interface TableCellState extends TableCellAddress {
|
|
|
39
39
|
export interface PreparedTableChange {
|
|
40
40
|
action: "merge" | "split" | "style" | "table-style";
|
|
41
41
|
tablePath: string;
|
|
42
|
-
|
|
42
|
+
presentation: unknown;
|
|
43
43
|
patches: JsonPatchOperation[];
|
|
44
44
|
changed: boolean;
|
|
45
45
|
[key: string]: unknown;
|
|
46
46
|
}
|
|
47
|
-
export interface TableChange extends Omit<EditorChange, "
|
|
47
|
+
export interface TableChange extends Omit<EditorChange, "presentation" | "patches"> {
|
|
48
48
|
action: PreparedTableChange["action"];
|
|
49
49
|
tablePath: string;
|
|
50
|
-
|
|
50
|
+
presentation: unknown;
|
|
51
51
|
patches: JsonPatchOperation[];
|
|
52
52
|
changed: boolean;
|
|
53
53
|
[key: string]: unknown;
|
|
@@ -60,14 +60,14 @@ export declare function tableMerges(table: unknown): TableMerge[];
|
|
|
60
60
|
/** The state of one cell, including merge anchor/covered status. */
|
|
61
61
|
export declare function describeTableCell(table: unknown, cell: TableCellAddress): TableCellState;
|
|
62
62
|
/** Merge `span.colSpan` x `span.rowSpan` cells from `cell`. `join` keeps the text of covered cells by joining it into the anchor; without it, covered text refuses with `merge-would-lose-content`. */
|
|
63
|
-
export declare function prepareTableMerge(
|
|
64
|
-
export declare function prepareTableSplit(
|
|
63
|
+
export declare function prepareTableMerge(presentation: unknown, tablePath: string, cell: TableCellAddress, span: { colSpan?: number; rowSpan?: number }, options?: { join?: boolean }): PreparedTableChange;
|
|
64
|
+
export declare function prepareTableSplit(presentation: unknown, tablePath: string, cell: TableCellAddress): PreparedTableChange;
|
|
65
65
|
/** Merge style fields into the cells' styles (null removes a field, `style: null` clears). */
|
|
66
|
-
export declare function prepareTableCellStyle(
|
|
66
|
+
export declare function prepareTableCellStyle(presentation: unknown, tablePath: string, cells: TableCellAddress | TableCellAddress[], style: Record<string, unknown> | null): PreparedTableChange;
|
|
67
67
|
/** Apply `{ header, banding, borders }` (missing fields mean theme/false) or a named preset: sets the header fill, banded-row fill and borders of every cell and clears them elsewhere; `theme` removes a previous style. */
|
|
68
|
-
export declare function prepareTableStyle(
|
|
68
|
+
export declare function prepareTableStyle(presentation: unknown, tablePath: string, preset: keyof typeof TABLE_STYLE_PRESETS | Partial<TableStyle>): PreparedTableChange;
|
|
69
69
|
/** The current style per axis, or "custom" in every field when the fills and borders are not one this module writes. */
|
|
70
|
-
export declare function readTableStyle(
|
|
70
|
+
export declare function readTableStyle(presentation: unknown, tablePath: string): (TableStyle | { header: "custom"; banding: "custom"; borders: "custom" }) & { preset: keyof typeof TABLE_STYLE_PRESETS | "custom" };
|
|
71
71
|
export declare function mergeTableCells(editor: EditorSession, tablePath: string, cell: TableCellAddress, span: { colSpan?: number; rowSpan?: number }, options?: { join?: boolean; meta?: Record<string, unknown> }): TableChange;
|
|
72
72
|
export declare function splitTableCell(editor: EditorSession, tablePath: string, cell: TableCellAddress, meta?: Record<string, unknown>): TableChange;
|
|
73
73
|
export declare function setTableCellStyle(editor: EditorSession, tablePath: string, cells: TableCellAddress | TableCellAddress[], style: Record<string, unknown> | null, meta?: Record<string, unknown>): TableChange;
|
package/dist/table-options.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// Cells are addressed by `{ section: "header" | "body", row, column }`; `row` is ignored for the
|
|
6
6
|
// header section. Nothing here invents text: merging cells that hold text refuses unless you pass
|
|
7
7
|
// `join: true`, which keeps every word in the merged cell.
|
|
8
|
-
import { getValueAtPath, opfPathToJsonPointer, splitOpfPath
|
|
8
|
+
import { getValueAtPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
|
|
9
9
|
import { checkedDocument, fail } from "./edit-helpers.js";
|
|
10
|
+
import { checkFormat } from "./checks.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Table styles. A style is `{ header, banding, borders }`. The renderers' table defaults are a header
|
|
@@ -27,7 +28,7 @@ export const TABLE_BORDER_STYLES = Object.freeze(["theme", "none", "horizontal",
|
|
|
27
28
|
// The style fields a table style owns; text color, alignment and padding are never touched.
|
|
28
29
|
const STYLE_KEYS = ["fill", "borders"];
|
|
29
30
|
const HEADER_FILL = Object.freeze({ plain: "surface", accent: "accent" });
|
|
30
|
-
const BAND_FILL = "
|
|
31
|
+
const BAND_FILL = "surfaceAlt";
|
|
31
32
|
const RULE_COLOR = "textSecondary";
|
|
32
33
|
|
|
33
34
|
const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -59,9 +60,9 @@ export function parseTableCellPath(path) {
|
|
|
59
60
|
return undefined;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
function tableAt(
|
|
63
|
+
function tableAt(presentation, tablePath) {
|
|
63
64
|
const parts = splitOpfPath(tablePath);
|
|
64
|
-
const table = getValueAtPath(
|
|
65
|
+
const table = getValueAtPath(presentation, parts);
|
|
65
66
|
if (isObject(table) && !Array.isArray(table.rows) && typeof table.dataset === "string")
|
|
66
67
|
throw fail("table-dataset-backed", `This table shows the shared dataset '${table.dataset}', which holds no cell styles or merged cells. Use a copy of the data (in the data grid) to style it.`, { tablePath, dataset: table.dataset });
|
|
67
68
|
if (!isObject(table) || !Array.isArray(table.rows)) throw fail("table-not-found", "Choose a table (a path ending in .table).", { tablePath });
|
|
@@ -163,16 +164,16 @@ function withCell(cell, patch) {
|
|
|
163
164
|
return keys.length === 1 && keys[0] === "value" ? merged.value : merged;
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
function transaction(
|
|
167
|
-
const { parts, table } = tableAt(
|
|
167
|
+
function transaction(presentation, tablePath, change, extra = {}) {
|
|
168
|
+
const { parts, table } = tableAt(presentation, tablePath);
|
|
168
169
|
const next = clone(table);
|
|
169
170
|
const info = change(next);
|
|
170
171
|
const pointer = opfPathToJsonPointer(parts);
|
|
171
172
|
const changed = JSON.stringify(table) !== JSON.stringify(next);
|
|
172
173
|
const patches = changed ? [{ op: "test", path: pointer, value: clone(table) }, { op: "replace", path: pointer, value: next }] : [];
|
|
173
|
-
const before =
|
|
174
|
-
const result = changed ? checkedDocument(
|
|
175
|
-
return { ...extra, ...info, tablePath: parts.join("."),
|
|
174
|
+
const before = checkFormat(presentation);
|
|
175
|
+
const result = changed ? checkedDocument(presentation, patches, before) : presentation;
|
|
176
|
+
return { ...extra, ...info, tablePath: parts.join("."), presentation: clone(result), patches, changed };
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
// --- merge and split ---------------------------------------------------------------------------
|
|
@@ -200,11 +201,11 @@ function unmerge(table, cell) {
|
|
|
200
201
|
* `merge-overlap` for a rectangle that crosses another merged cell. A header cell merges across the
|
|
201
202
|
* header only (`rowSpan` 1). Re-merging an anchor first undoes its old merge.
|
|
202
203
|
*/
|
|
203
|
-
export function prepareTableMerge(
|
|
204
|
+
export function prepareTableMerge(presentation, tablePath, cell, span, options = {}) {
|
|
204
205
|
const colSpan = span.colSpan ?? 1;
|
|
205
206
|
const rowSpan = span.rowSpan ?? 1;
|
|
206
207
|
if (!Number.isInteger(colSpan) || colSpan < 1 || !Number.isInteger(rowSpan) || rowSpan < 1) throw fail("invalid-table-span", "Spans are whole numbers of 1 or more.", { span });
|
|
207
|
-
return transaction(
|
|
208
|
+
return transaction(presentation, tablePath, (table) => {
|
|
208
209
|
const target = { section: cell.section, row: cell.section === "header" ? 0 : cell.row, column: cell.column };
|
|
209
210
|
const existing = describeTableCell(table, target);
|
|
210
211
|
if (existing.covered) throw fail("merge-overlap", "This cell is covered by a merged cell. Select the merged cell itself.", { cell });
|
|
@@ -242,8 +243,8 @@ export function prepareTableMerge(document, tablePath, cell, span, options = {})
|
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
/** Compute the patch that splits a merged cell back into single cells (covered positions become empty text). */
|
|
245
|
-
export function prepareTableSplit(
|
|
246
|
-
return transaction(
|
|
246
|
+
export function prepareTableSplit(presentation, tablePath, cell) {
|
|
247
|
+
return transaction(presentation, tablePath, (table) => {
|
|
247
248
|
const target = { section: cell.section, row: cell.section === "header" ? 0 : cell.row, column: cell.column };
|
|
248
249
|
const entry = describeTableCell(table, target);
|
|
249
250
|
if (entry.covered) throw fail("table-cell-covered", "This position is covered by a merged cell. Select the merged cell to split it.", { cell });
|
|
@@ -259,11 +260,11 @@ export function prepareTableSplit(document, tablePath, cell) {
|
|
|
259
260
|
* clears the whole style. Covered (merged) positions are skipped. A cell left with no style or spans
|
|
260
261
|
* becomes a plain value again. Style is appearance only: values and spans are untouched.
|
|
261
262
|
*/
|
|
262
|
-
export function prepareTableCellStyle(
|
|
263
|
+
export function prepareTableCellStyle(presentation, tablePath, cells, style) {
|
|
263
264
|
const list = Array.isArray(cells) ? cells : [cells];
|
|
264
265
|
if (!list.length) throw fail("table-cell-not-found", "Choose at least one cell.", {});
|
|
265
266
|
if (style !== null && !isObject(style)) throw fail("invalid-table-style", "A cell style is an object, or null to clear it.", { style });
|
|
266
|
-
return transaction(
|
|
267
|
+
return transaction(presentation, tablePath, (table) => {
|
|
267
268
|
let skipped = 0;
|
|
268
269
|
for (const cell of list) {
|
|
269
270
|
const target = { section: cell.section, row: cell.section === "header" ? 0 : cell.row, column: cell.column };
|
|
@@ -329,12 +330,12 @@ function resolveStyle(preset) {
|
|
|
329
330
|
* `theme`/false) or a named preset from `TABLE_STYLE_PRESETS`. It sets the header fill, the
|
|
330
331
|
* alternating body-row fill and the cell borders, and clears those fields on every other cell, so
|
|
331
332
|
* `theme` removes a previous style. Text color, alignment, padding, values and merges are kept.
|
|
332
|
-
* Fills use scheme roles (surface, accent, background), so they follow the color scheme and the
|
|
333
|
+
* Fills use scheme roles (surface, surfaceAlt, accent, background), so they follow the color scheme and the
|
|
333
334
|
* renderers' automatic text contrast keeps text readable.
|
|
334
335
|
*/
|
|
335
|
-
export function prepareTableStyle(
|
|
336
|
+
export function prepareTableStyle(presentation, tablePath, preset) {
|
|
336
337
|
const style = resolveStyle(preset);
|
|
337
|
-
return transaction(
|
|
338
|
+
return transaction(presentation, tablePath, (table) => {
|
|
338
339
|
styleTable(table, style);
|
|
339
340
|
return {};
|
|
340
341
|
}, { action: "table-style", style });
|
|
@@ -367,8 +368,8 @@ export function applyTableStyleToTable(table, preset) {
|
|
|
367
368
|
* its fills and borders are not one of the styles this module writes (for example hand-set fills).
|
|
368
369
|
* `preset` is the named preset the style equals, or "custom".
|
|
369
370
|
*/
|
|
370
|
-
export function readTableStyle(
|
|
371
|
-
return readTableStyleOfTable(tableAt(
|
|
371
|
+
export function readTableStyle(presentation, tablePath) {
|
|
372
|
+
return readTableStyleOfTable(tableAt(presentation, tablePath).table);
|
|
372
373
|
}
|
|
373
374
|
|
|
374
375
|
/** {@link readTableStyle} for a table object. */
|
|
@@ -396,10 +397,10 @@ export * from "./table-structure.js";
|
|
|
396
397
|
// --- session forms ----------------------------------------------------------------------------
|
|
397
398
|
|
|
398
399
|
function commit(editor, prepared, meta = {}) {
|
|
399
|
-
const {
|
|
400
|
-
void
|
|
400
|
+
const { presentation, patches, ...summary } = prepared;
|
|
401
|
+
void presentation;
|
|
401
402
|
if (!editor || typeof editor.applyPatch !== "function") throw fail("invalid-editor", "Expected an editor session created by createEditorSession.");
|
|
402
|
-
if (!prepared.changed) return { ...summary,
|
|
403
|
+
if (!prepared.changed) return { ...summary, presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
|
|
403
404
|
const change = editor.applyPatch(patches, { ...meta, source: meta.source ?? "table-option", action: prepared.action, path: prepared.tablePath });
|
|
404
405
|
return { ...change, ...summary };
|
|
405
406
|
}
|
|
@@ -410,20 +411,20 @@ function checkEditor(editor) {
|
|
|
410
411
|
export function mergeTableCells(editor, tablePath, cell, span, options = {}) {
|
|
411
412
|
checkEditor(editor);
|
|
412
413
|
const { meta, ...rest } = options;
|
|
413
|
-
return commit(editor, prepareTableMerge(editor.
|
|
414
|
+
return commit(editor, prepareTableMerge(editor.presentation, tablePath, cell, span, rest), meta);
|
|
414
415
|
}
|
|
415
416
|
/** Split a merged cell as one undoable transaction. */
|
|
416
417
|
export function splitTableCell(editor, tablePath, cell, meta = {}) {
|
|
417
418
|
checkEditor(editor);
|
|
418
|
-
return commit(editor, prepareTableSplit(editor.
|
|
419
|
+
return commit(editor, prepareTableSplit(editor.presentation, tablePath, cell), meta);
|
|
419
420
|
}
|
|
420
421
|
/** Style cells as one undoable transaction. See {@link prepareTableCellStyle}. */
|
|
421
422
|
export function setTableCellStyle(editor, tablePath, cells, style, meta = {}) {
|
|
422
423
|
checkEditor(editor);
|
|
423
|
-
return commit(editor, prepareTableCellStyle(editor.
|
|
424
|
+
return commit(editor, prepareTableCellStyle(editor.presentation, tablePath, cells, style), meta);
|
|
424
425
|
}
|
|
425
426
|
/** Apply a table style as one undoable transaction. See {@link prepareTableStyle}. */
|
|
426
427
|
export function setTableStyle(editor, tablePath, preset, meta = {}) {
|
|
427
428
|
checkEditor(editor);
|
|
428
|
-
return commit(editor, prepareTableStyle(editor.
|
|
429
|
+
return commit(editor, prepareTableStyle(editor.presentation, tablePath, preset), meta);
|
|
429
430
|
}
|
|
@@ -2,29 +2,29 @@ import type { EditorSession } from "./index.js";
|
|
|
2
2
|
import type { GridAddress, GridCellEdit, GridChange, GridOptions, GridSortOptions, PreparedGridChange } from "./grid-model.js";
|
|
3
3
|
|
|
4
4
|
/** Set cell text (or typed values) in a table. Rich cells keep their runs' formatting and styled cells their style and spans. One patch; every problem is in `error.issues`. */
|
|
5
|
-
export declare function prepareTableCells(
|
|
5
|
+
export declare function prepareTableCells(presentation: unknown, tablePath: string, edits: GridCellEdit[], options?: GridOptions): PreparedGridChange;
|
|
6
6
|
export declare function setTableCells(editor: EditorSession, tablePath: string, edits: GridCellEdit[], options?: GridOptions): GridChange;
|
|
7
7
|
/** Paste TSV/CSV text or rows at a cell, growing the table as needed. Covered (merged) positions refuse the paste. One patch. */
|
|
8
|
-
export declare function prepareTablePaste(
|
|
8
|
+
export declare function prepareTablePaste(presentation: unknown, tablePath: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): PreparedGridChange;
|
|
9
9
|
export declare function pasteTableText(editor: EditorSession, tablePath: string, anchor: GridAddress, source: string | string[][], options?: GridOptions): GridChange;
|
|
10
10
|
/** Insert `count` rows so the first is body row `at`. A merged cell spanning that position grows. */
|
|
11
|
-
export declare function prepareTableInsertRows(
|
|
11
|
+
export declare function prepareTableInsertRows(presentation: unknown, tablePath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
12
12
|
export declare function insertTableRows(editor: EditorSession, tablePath: string, at: number, count?: number, options?: GridOptions): GridChange;
|
|
13
13
|
/** Delete body rows. A merged cell spanning a deleted row shrinks and keeps its text. The last row cannot be deleted. */
|
|
14
|
-
export declare function prepareTableDeleteRows(
|
|
14
|
+
export declare function prepareTableDeleteRows(presentation: unknown, tablePath: string, indices: number[], options?: GridOptions): PreparedGridChange;
|
|
15
15
|
export declare function deleteTableRows(editor: EditorSession, tablePath: string, indices: number[], options?: GridOptions): GridChange;
|
|
16
16
|
/** Move `count` rows from `from` so the first is at `to`. Refused when it would split a merged cell. */
|
|
17
|
-
export declare function prepareTableMoveRows(
|
|
17
|
+
export declare function prepareTableMoveRows(presentation: unknown, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
18
18
|
export declare function moveTableRows(editor: EditorSession, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): GridChange;
|
|
19
|
-
export declare function prepareTableInsertColumns(
|
|
19
|
+
export declare function prepareTableInsertColumns(presentation: unknown, tablePath: string, at: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
20
20
|
export declare function insertTableColumns(editor: EditorSession, tablePath: string, at: number, count?: number, options?: GridOptions): GridChange;
|
|
21
|
-
export declare function prepareTableDeleteColumns(
|
|
21
|
+
export declare function prepareTableDeleteColumns(presentation: unknown, tablePath: string, indices: number[], options?: GridOptions): PreparedGridChange;
|
|
22
22
|
export declare function deleteTableColumns(editor: EditorSession, tablePath: string, indices: number[], options?: GridOptions): GridChange;
|
|
23
|
-
export declare function prepareTableMoveColumns(
|
|
23
|
+
export declare function prepareTableMoveColumns(presentation: unknown, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): PreparedGridChange;
|
|
24
24
|
export declare function moveTableColumns(editor: EditorSession, tablePath: string, from: number, to: number, count?: number, options?: GridOptions): GridChange;
|
|
25
25
|
/** Sort the body rows by a column, stably and by type (numbers, ISO dates, text; empty cells last). Rows joined by a row-spanning merge move as a block. */
|
|
26
|
-
export declare function prepareTableSort(
|
|
26
|
+
export declare function prepareTableSort(presentation: unknown, tablePath: string, column: number, options?: GridSortOptions): PreparedGridChange;
|
|
27
27
|
export declare function sortTableRows(editor: EditorSession, tablePath: string, column: number, options?: GridSortOptions): GridChange;
|
|
28
28
|
/** Turn the header row on (the first row becomes the header, or `{ use: "new" }` adds an empty one) or off. */
|
|
29
|
-
export declare function prepareTableHeader(
|
|
29
|
+
export declare function prepareTableHeader(presentation: unknown, tablePath: string, enabled: boolean, options?: { use?: "first-row" | "new" }): PreparedGridChange;
|
|
30
30
|
export declare function setTableHeader(editor: EditorSession, tablePath: string, enabled: boolean, options?: { use?: "first-row" | "new"; meta?: Record<string, unknown> }): GridChange;
|
package/dist/table-structure.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// correct. A table cell is a plain value, rich runs or a styled cell; editing keeps runs' formatting and a cell's style and spans.
|
|
6
6
|
// Rows are addressed from 0 in the body; the header row is addressed as `{ section: "header", column }`.
|
|
7
7
|
//
|
|
8
|
-
// `prepare*` computes `{
|
|
8
|
+
// `prepare*` computes `{ presentation, patches, changed }` without touching a session; the session forms apply it as one undo step.
|
|
9
9
|
import {
|
|
10
10
|
prepareGridCells,
|
|
11
11
|
preparePaste,
|
|
@@ -28,64 +28,64 @@ import {
|
|
|
28
28
|
prepareSetHeader,
|
|
29
29
|
setGridHeader,
|
|
30
30
|
} from "./grid-model.js";
|
|
31
|
-
export function prepareTableCells(
|
|
32
|
-
return prepareGridCells(
|
|
31
|
+
export function prepareTableCells(presentation, tablePath, edits, options = {}) {
|
|
32
|
+
return prepareGridCells(presentation, tablePath, edits, { ...options, kind: "table" });
|
|
33
33
|
}
|
|
34
34
|
export function setTableCells(editor, tablePath, edits, options = {}) {
|
|
35
35
|
return setGridCells(editor, tablePath, edits, { ...options, kind: "table" });
|
|
36
36
|
}
|
|
37
|
-
export function prepareTablePaste(
|
|
38
|
-
return preparePaste(
|
|
37
|
+
export function prepareTablePaste(presentation, tablePath, anchor, source, options = {}) {
|
|
38
|
+
return preparePaste(presentation, tablePath, anchor, source, { ...options, kind: "table" });
|
|
39
39
|
}
|
|
40
40
|
export function pasteTableText(editor, tablePath, anchor, source, options = {}) {
|
|
41
41
|
return pasteGridText(editor, tablePath, anchor, source, { ...options, kind: "table" });
|
|
42
42
|
}
|
|
43
|
-
export function prepareTableInsertRows(
|
|
44
|
-
return prepareInsertRows(
|
|
43
|
+
export function prepareTableInsertRows(presentation, tablePath, at, count = 1, options = {}) {
|
|
44
|
+
return prepareInsertRows(presentation, tablePath, at, count, { ...options, kind: "table" });
|
|
45
45
|
}
|
|
46
46
|
export function insertTableRows(editor, tablePath, at, count = 1, options = {}) {
|
|
47
47
|
return insertGridRows(editor, tablePath, at, count, { ...options, kind: "table" });
|
|
48
48
|
}
|
|
49
|
-
export function prepareTableDeleteRows(
|
|
50
|
-
return prepareDeleteRows(
|
|
49
|
+
export function prepareTableDeleteRows(presentation, tablePath, indices, options = {}) {
|
|
50
|
+
return prepareDeleteRows(presentation, tablePath, indices, { ...options, kind: "table" });
|
|
51
51
|
}
|
|
52
52
|
export function deleteTableRows(editor, tablePath, indices, options = {}) {
|
|
53
53
|
return deleteGridRows(editor, tablePath, indices, { ...options, kind: "table" });
|
|
54
54
|
}
|
|
55
|
-
export function prepareTableMoveRows(
|
|
56
|
-
return prepareMoveRows(
|
|
55
|
+
export function prepareTableMoveRows(presentation, tablePath, from, to, count = 1, options = {}) {
|
|
56
|
+
return prepareMoveRows(presentation, tablePath, from, to, count, { ...options, kind: "table" });
|
|
57
57
|
}
|
|
58
58
|
export function moveTableRows(editor, tablePath, from, to, count = 1, options = {}) {
|
|
59
59
|
return moveGridRows(editor, tablePath, from, to, count, { ...options, kind: "table" });
|
|
60
60
|
}
|
|
61
|
-
export function prepareTableInsertColumns(
|
|
62
|
-
return prepareInsertColumns(
|
|
61
|
+
export function prepareTableInsertColumns(presentation, tablePath, at, count = 1, options = {}) {
|
|
62
|
+
return prepareInsertColumns(presentation, tablePath, at, count, { ...options, kind: "table" });
|
|
63
63
|
}
|
|
64
64
|
export function insertTableColumns(editor, tablePath, at, count = 1, options = {}) {
|
|
65
65
|
return insertGridColumns(editor, tablePath, at, count, { ...options, kind: "table" });
|
|
66
66
|
}
|
|
67
|
-
export function prepareTableDeleteColumns(
|
|
68
|
-
return prepareDeleteColumns(
|
|
67
|
+
export function prepareTableDeleteColumns(presentation, tablePath, indices, options = {}) {
|
|
68
|
+
return prepareDeleteColumns(presentation, tablePath, indices, { ...options, kind: "table" });
|
|
69
69
|
}
|
|
70
70
|
export function deleteTableColumns(editor, tablePath, indices, options = {}) {
|
|
71
71
|
return deleteGridColumns(editor, tablePath, indices, { ...options, kind: "table" });
|
|
72
72
|
}
|
|
73
|
-
export function prepareTableMoveColumns(
|
|
74
|
-
return prepareMoveColumns(
|
|
73
|
+
export function prepareTableMoveColumns(presentation, tablePath, from, to, count = 1, options = {}) {
|
|
74
|
+
return prepareMoveColumns(presentation, tablePath, from, to, count, { ...options, kind: "table" });
|
|
75
75
|
}
|
|
76
76
|
export function moveTableColumns(editor, tablePath, from, to, count = 1, options = {}) {
|
|
77
77
|
return moveGridColumns(editor, tablePath, from, to, count, { ...options, kind: "table" });
|
|
78
78
|
}
|
|
79
|
-
export function prepareTableSort(
|
|
80
|
-
return prepareSortRows(
|
|
79
|
+
export function prepareTableSort(presentation, tablePath, column, options = {}) {
|
|
80
|
+
return prepareSortRows(presentation, tablePath, column, { ...options, kind: "table" });
|
|
81
81
|
}
|
|
82
82
|
export function sortTableRows(editor, tablePath, column, options = {}) {
|
|
83
83
|
return sortGridRows(editor, tablePath, column, { ...options, kind: "table" });
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/** Turn the header row on or off. On uses the first row as the header, or adds an empty one with `{ use: "new" }`. See {@link prepareSetHeader}. */
|
|
87
|
-
export function prepareTableHeader(
|
|
88
|
-
return prepareSetHeader(
|
|
87
|
+
export function prepareTableHeader(presentation, tablePath, enabled, options = {}) {
|
|
88
|
+
return prepareSetHeader(presentation, tablePath, enabled, options);
|
|
89
89
|
}
|
|
90
90
|
export function setTableHeader(editor, tablePath, enabled, options = {}) {
|
|
91
91
|
return setGridHeader(editor, tablePath, enabled, options);
|
package/dist/template-panel.d.ts
CHANGED
|
@@ -4,11 +4,11 @@ import type { TemplateFill, TemplateFillApplyResult } from "./templates.js";
|
|
|
4
4
|
export interface TemplatePanelOptions {
|
|
5
5
|
editor: EditorSession;
|
|
6
6
|
/**
|
|
7
|
-
* Draw the live preview: return the SVG of one slide of `
|
|
8
|
-
* as an SVG string, or a promise of one. Typically `
|
|
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 `renderSlideSvg(presentation, slideIndex, { variables, ... })` from
|
|
9
9
|
* `@openpresentation/opf-render` with the host's fonts. Omit for no preview.
|
|
10
10
|
*/
|
|
11
|
-
renderPreview?: (input: {
|
|
11
|
+
renderPreview?: (input: { presentation: unknown; variables: Record<string, unknown>; slideIndex: number }) => string | Promise<string>;
|
|
12
12
|
/** The slide the preview starts on. Call `refresh()` when it changes. */
|
|
13
13
|
getSlideIndex?: () => number;
|
|
14
14
|
/** The text field a variable token is inserted into: its OPF path and, optionally, the selection as UTF-16 offsets. Omit to hide the insert section. */
|
package/dist/template-panel.js
CHANGED
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
createTemplateFill,
|
|
9
9
|
insertVariableToken,
|
|
10
10
|
isTemplateDocument,
|
|
11
|
+
listBuiltins,
|
|
11
12
|
setTemplate,
|
|
12
13
|
suggestVariableId,
|
|
13
|
-
templatesAvailable,
|
|
14
14
|
} from "./templates.js";
|
|
15
|
+
import { slideTitle } from "./slides.js";
|
|
15
16
|
|
|
16
17
|
const KIND_LABELS = { text: "Text", number: "Number", date: "Date", image: "Image", url: "Link", list: "List", color: "Color" };
|
|
17
18
|
const STATUS_TEXT = { filled: "Filled", default: "Default value", unfilled: "Needs a value", optional: "Optional" };
|
|
@@ -20,6 +21,13 @@ const NEW_VARIABLE_KINDS = ["text", "number", "date", "image", "url", "list"];
|
|
|
20
21
|
|
|
21
22
|
let panelCounter = 0;
|
|
22
23
|
|
|
24
|
+
/** A short display of a built-in's source value. */
|
|
25
|
+
function builtinValueText(entry) {
|
|
26
|
+
const value = entry.value;
|
|
27
|
+
const text = Array.isArray(value) ? value.join(", ") : value && typeof value === "object" ? String(value.src ?? "") : String(value ?? "");
|
|
28
|
+
return text.length > 60 ? `${text.slice(0, 57)}...` : text;
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
function h(doc, tag, attributes = {}, ...children) {
|
|
24
32
|
const element = doc.createElement(tag);
|
|
25
33
|
for (const [key, value] of Object.entries(attributes)) {
|
|
@@ -34,13 +42,12 @@ function h(doc, tag, attributes = {}, ...children) {
|
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
/**
|
|
37
|
-
* Mount the Fill template panel. Options: `editor` (a session), `renderPreview({
|
|
45
|
+
* Mount the Fill template panel. Options: `editor` (a session), `renderPreview({presentation, variables, slideIndex})`
|
|
38
46
|
* returning an SVG string (or a promise of one) for the live preview (omit for no preview), `getSlideIndex`,
|
|
39
47
|
* `getTarget()` returning `{path, start?, end?}` for the text field a token is inserted into, `onStatus(message,
|
|
40
48
|
* {error})`, `onApply(result)`, `readFile(file)` (default: FileReader as a data URL) for image uploads.
|
|
41
49
|
*/
|
|
42
50
|
export function createTemplatePanel(container, options) {
|
|
43
|
-
if (!templatesAvailable()) throw new Error("The Fill template panel needs a core release that ships resolveVariables.");
|
|
44
51
|
const { editor, renderPreview, getTarget, onStatus, onApply } = options;
|
|
45
52
|
const doc = container.ownerDocument;
|
|
46
53
|
const fill = createTemplateFill(editor);
|
|
@@ -56,6 +63,12 @@ export function createTemplatePanel(container, options) {
|
|
|
56
63
|
const modeBox = h(doc, "input", { type: "checkbox", id: `${id}-mode` });
|
|
57
64
|
modeLabel.append(modeBox, " This presentation is a template (it may have unfilled variables)");
|
|
58
65
|
const fieldList = h(doc, "div", { class: "opf-template-fields" });
|
|
66
|
+
// Built-in variables: read-only values from the document's own speaker, organization and deck fields.
|
|
67
|
+
const builtinList = h(doc, "ul", { class: "opf-template-builtin-list" });
|
|
68
|
+
const builtinSection = h(doc, "details", { class: "opf-template-builtins", "data-opf-component": "template-builtins" },
|
|
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." }),
|
|
71
|
+
builtinList);
|
|
59
72
|
const live = h(doc, "p", { class: "opf-template-live", role: "status", "aria-live": "polite" });
|
|
60
73
|
const applyButton = h(doc, "button", { type: "button", class: "opf-template-apply primary", text: "Fill the presentation" });
|
|
61
74
|
const partialButton = h(doc, "button", { type: "button", class: "opf-template-partial secondary", text: "Fill what is ready" });
|
|
@@ -85,7 +98,7 @@ export function createTemplatePanel(container, options) {
|
|
|
85
98
|
h(doc, "label", { for: `${id}-new-sample`, text: "Sample value" }), newSample, newButton))
|
|
86
99
|
: null;
|
|
87
100
|
|
|
88
|
-
root.append(summary, modeLabel, fieldList, actions, live);
|
|
101
|
+
root.append(summary, modeLabel, fieldList, builtinSection, actions, live);
|
|
89
102
|
if (previewSection) root.append(previewSection);
|
|
90
103
|
if (insertSection) root.append(insertSection);
|
|
91
104
|
container.append(root);
|
|
@@ -152,7 +165,7 @@ export function createTemplatePanel(container, options) {
|
|
|
152
165
|
extras.push(colorInput);
|
|
153
166
|
}
|
|
154
167
|
if (field.kind === "image") {
|
|
155
|
-
const assets = editor.
|
|
168
|
+
const assets = editor.presentation.assets && typeof editor.presentation.assets === "object" ? Object.keys(editor.presentation.assets) : [];
|
|
156
169
|
if (assets.length) {
|
|
157
170
|
assetSelect = h(doc, "select", { "aria-label": `${field.label}: pick a registered asset` }, h(doc, "option", { value: "", text: "Pick an asset…" }), ...assets.map((key) => h(doc, "option", { value: `asset:${key}`, text: key })));
|
|
158
171
|
assetSelect.addEventListener("change", () => {
|
|
@@ -214,11 +227,22 @@ export function createTemplatePanel(container, options) {
|
|
|
214
227
|
rows.set(field.id, row);
|
|
215
228
|
fieldList.append(row.element);
|
|
216
229
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
230
|
+
const builtins = listBuiltins(editor.presentation);
|
|
231
|
+
insertSelect.replaceChildren(
|
|
232
|
+
...fields.map((field) => h(doc, "option", { value: field.id, text: `${field.label} (${KIND_LABELS[field.kind]})` })),
|
|
233
|
+
...(builtins.length ? [h(doc, "optgroup", { label: "Built-in variables" }, ...builtins.map((entry) => h(doc, "option", { value: entry.name, text: `${entry.label} (${entry.name})` })))] : []),
|
|
234
|
+
);
|
|
235
|
+
builtinSection.hidden = !builtins.length;
|
|
236
|
+
builtinList.replaceChildren(...builtins.map((entry) => {
|
|
237
|
+
const shown = builtinValueText(entry);
|
|
238
|
+
const uses = entry.uses.length;
|
|
239
|
+
return h(doc, "li", { "data-builtin": entry.name, "data-kind": entry.kind, "data-available": entry.available ? "true" : "false" },
|
|
240
|
+
h(doc, "code", { text: `{{${entry.name}}}` }), ` ${entry.label}: `, entry.available ? shown : h(doc, "em", { text: "not set" }), uses ? ` (used ${uses} time${uses === 1 ? "" : "s"})` : "");
|
|
241
|
+
}));
|
|
242
|
+
slideSelect.replaceChildren(...(editor.presentation.slides ?? []).map((slide, index) => h(doc, "option", { value: String(index), text: `${index + 1}. ${slideTitle(slide) || "Untitled"}` })));
|
|
243
|
+
previewSlide = Math.min(previewSlide, Math.max(0, (editor.presentation.slides?.length ?? 1) - 1));
|
|
220
244
|
slideSelect.value = String(previewSlide);
|
|
221
|
-
modeBox.checked = isTemplateDocument(editor.
|
|
245
|
+
modeBox.checked = isTemplateDocument(editor.presentation);
|
|
222
246
|
update();
|
|
223
247
|
}
|
|
224
248
|
|
|
@@ -263,7 +287,7 @@ export function createTemplatePanel(container, options) {
|
|
|
263
287
|
if (!renderPreview) return;
|
|
264
288
|
const token = ++previewToken;
|
|
265
289
|
try {
|
|
266
|
-
const svg = await renderPreview({
|
|
290
|
+
const svg = await renderPreview({ presentation: editor.presentation, variables: fill.values, slideIndex: previewSlide });
|
|
267
291
|
if (token !== previewToken || destroyed) return;
|
|
268
292
|
previewBox.innerHTML = svg;
|
|
269
293
|
const preview = fill.preview();
|
|
@@ -304,7 +328,7 @@ export function createTemplatePanel(container, options) {
|
|
|
304
328
|
setTemplate(editor, modeBox.checked);
|
|
305
329
|
say(modeBox.checked ? "Marked as a template." : "Marked as a normal presentation.");
|
|
306
330
|
} catch (error) {
|
|
307
|
-
modeBox.checked = isTemplateDocument(editor.
|
|
331
|
+
modeBox.checked = isTemplateDocument(editor.presentation);
|
|
308
332
|
say(messageOf(error), true);
|
|
309
333
|
}
|
|
310
334
|
});
|
|
@@ -335,13 +359,13 @@ export function createTemplatePanel(container, options) {
|
|
|
335
359
|
say("Give the new variable a name.", true);
|
|
336
360
|
return;
|
|
337
361
|
}
|
|
338
|
-
const variableId = suggestVariableId(editor.
|
|
362
|
+
const variableId = suggestVariableId(editor.presentation, name);
|
|
339
363
|
const kind = newKind.value;
|
|
340
364
|
const declaration = { type: kind, label: name };
|
|
341
365
|
const sample = newSample.value.trim();
|
|
342
366
|
if (sample) {
|
|
343
367
|
// A template keeps the sample as an example (the slot stays unfilled); a normal deck needs a real value.
|
|
344
|
-
const field = isTemplateDocument(editor.
|
|
368
|
+
const field = isTemplateDocument(editor.presentation) ? "example" : "value";
|
|
345
369
|
declaration[field] = kind === "number" && Number.isFinite(Number(sample)) ? Number(sample) : kind === "list" ? sample.split(/\r?\n|,/).map((entry) => entry.trim()).filter(Boolean) : sample;
|
|
346
370
|
}
|
|
347
371
|
try {
|
package/dist/templates.d.ts
CHANGED
|
@@ -55,25 +55,23 @@ export interface TemplatePreview {
|
|
|
55
55
|
complete: boolean;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
export declare function templatesAvailable(): boolean;
|
|
60
|
-
export declare function isTemplateDocument(document: unknown): boolean;
|
|
58
|
+
export declare function isTemplateDocument(presentation: unknown): boolean;
|
|
61
59
|
/** True when the document is a template or declares a variable that is not a color. */
|
|
62
|
-
export declare function hasTemplateVariables(
|
|
60
|
+
export declare function hasTemplateVariables(presentation: unknown): boolean;
|
|
63
61
|
export declare function valueToFieldText(kind: TemplateVariableKind, value: unknown): string;
|
|
64
62
|
/** Parse a form control's text. A blank control clears the value. */
|
|
65
63
|
export declare function fieldTextToValue(kind: TemplateVariableKind, text: string): { ok: true; value: unknown } | { ok: false; message: string };
|
|
66
|
-
export declare function listTemplateFields(
|
|
67
|
-
export declare function templateStatus(
|
|
64
|
+
export declare function listTemplateFields(presentation: unknown, values?: Record<string, unknown>): TemplateField[];
|
|
65
|
+
export declare function templateStatus(presentation: unknown, values?: Record<string, unknown>): TemplateStatus;
|
|
68
66
|
/** The concrete deck for the values with each unfilled variable's example. Never throws for missing values. */
|
|
69
|
-
export declare function previewTemplate(
|
|
67
|
+
export declare function previewTemplate(presentation: unknown, values?: Record<string, unknown>): TemplatePreview;
|
|
70
68
|
|
|
71
69
|
export interface TemplateFillOptions {
|
|
72
70
|
/** Extra session meta for the fill edit. */
|
|
73
71
|
meta?: Record<string, unknown>;
|
|
74
72
|
}
|
|
75
73
|
export interface TemplateFillApplyResult {
|
|
76
|
-
|
|
74
|
+
presentation: unknown;
|
|
77
75
|
patches: unknown[];
|
|
78
76
|
inversePatches: unknown[];
|
|
79
77
|
validation: unknown;
|
|
@@ -103,9 +101,22 @@ export interface TemplateFill {
|
|
|
103
101
|
}
|
|
104
102
|
export declare function createTemplateFill(editor: EditorSession, options?: TemplateFillOptions): TemplateFill;
|
|
105
103
|
|
|
106
|
-
/**
|
|
104
|
+
/** The token for a declared variable id or a built-in name such as `speaker.name`. */
|
|
107
105
|
export declare function variableToken(id: string, format?: string): string;
|
|
108
|
-
|
|
106
|
+
/** One built-in variable of a document (core `listBuiltinVariables`). */
|
|
107
|
+
export interface BuiltinInfo {
|
|
108
|
+
/** Dotted name, as written inside `{{...}}`. */
|
|
109
|
+
name: string;
|
|
110
|
+
kind: "text" | "image" | "list";
|
|
111
|
+
label: string;
|
|
112
|
+
/** The source value, when the document has one. */
|
|
113
|
+
value?: unknown;
|
|
114
|
+
available: boolean;
|
|
115
|
+
uses: { id: string; path: string; form: "token" | "reference" }[];
|
|
116
|
+
}
|
|
117
|
+
/** The presentation's built-in variables, read-only. */
|
|
118
|
+
export declare function listBuiltins(presentation: unknown): BuiltinInfo[];
|
|
119
|
+
export declare function suggestVariableId(presentation: unknown, label?: string): string;
|
|
109
120
|
/** Declare a variable as one undoable edit. */
|
|
110
121
|
export declare function declareVariable(editor: EditorSession, id: string, declaration: string | Record<string, unknown>, meta?: Record<string, unknown>): unknown;
|
|
111
122
|
/** Mark the document as a template, or as a normal deck with `false`, as one undoable edit. */
|
|
@@ -123,4 +134,4 @@ export interface InsertVariableTokenOptions {
|
|
|
123
134
|
meta?: Record<string, unknown>;
|
|
124
135
|
}
|
|
125
136
|
/** Insert a variable token into a text field as one undoable edit. */
|
|
126
|
-
export declare function insertVariableToken(editor: EditorSession, path: string, id: string, options?: InsertVariableTokenOptions): { token: string;
|
|
137
|
+
export declare function insertVariableToken(editor: EditorSession, path: string, id: string, options?: InsertVariableTokenOptions): { token: string; presentation: unknown; patches: unknown[]; inversePatches: unknown[]; validation: unknown };
|