@p10node/office-engine 0.1.0 → 0.2.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.
Files changed (43) hide show
  1. package/README.md +1 -0
  2. package/dist/{DocViewer-Cv4LaMB_.js → DocViewer-PJieqbIR.js} +3254 -2027
  3. package/dist/DocViewer-PJieqbIR.js.map +1 -0
  4. package/dist/assets/worker-B1gMpGFN.js.map +1 -0
  5. package/dist/{collab-BzN1ZI-z.js → collab-BvjXvfJ9.js} +529 -232
  6. package/dist/collab-BvjXvfJ9.js.map +1 -0
  7. package/dist/collab.js +1 -1
  8. package/dist/core.js +1 -1
  9. package/dist/{default-worker-B5CGBnic.js → default-worker-DfpaJUjf.js} +1053 -1030
  10. package/dist/default-worker-DfpaJUjf.js.map +1 -0
  11. package/dist/embed.js +13 -3
  12. package/dist/embed.js.map +1 -1
  13. package/dist/index.js +4 -4
  14. package/dist/office-engine.embed.js +76 -24
  15. package/dist/office-engine.worker.js +26 -6
  16. package/dist/types/collab/compact.d.ts +23 -0
  17. package/dist/types/collab/hub.d.ts +35 -4
  18. package/dist/types/collab/index.d.ts +9 -2
  19. package/dist/types/collab/protocol.d.ts +29 -2
  20. package/dist/types/collab/session.d.ts +5 -1
  21. package/dist/types/collab/store.d.ts +94 -0
  22. package/dist/types/core/convert.d.ts +29 -0
  23. package/dist/types/core/zipwrite.d.ts +3 -1
  24. package/dist/types/embed.d.ts +2 -2
  25. package/dist/types/index.d.ts +1 -0
  26. package/dist/types/react/DocViewer.d.ts +18 -0
  27. package/dist/types/react/common/Dialog.d.ts +8 -0
  28. package/dist/types/react/common/Slots.d.ts +20 -0
  29. package/dist/types/react/docx/DocxToolbar.d.ts +2 -1
  30. package/dist/types/react/edit/Toolbar.d.ts +4 -0
  31. package/dist/types/react/file/DetailsDialog.d.ts +7 -0
  32. package/dist/types/react/file/FileMenu.d.ts +48 -0
  33. package/dist/types/react/file/importFile.d.ts +10 -0
  34. package/dist/types/react/file/print.d.ts +16 -0
  35. package/dist/types/react/file/text.d.ts +10 -0
  36. package/dist/types/react/xlsx/XlsxView.d.ts +3 -1
  37. package/dist/types/react/xlsx/fit.d.ts +4 -0
  38. package/dist/types/react/xlsx/print.d.ts +17 -0
  39. package/package.json +2 -1
  40. package/dist/DocViewer-Cv4LaMB_.js.map +0 -1
  41. package/dist/assets/worker-CJfZ05I7.js.map +0 -1
  42. package/dist/collab-BzN1ZI-z.js.map +0 -1
  43. package/dist/default-worker-B5CGBnic.js.map +0 -1
@@ -344,7 +344,7 @@ async function ce(e, t, n = "application/zip") {
344
344
  let n = await ae(oe(t)), { time: r, date: i } = se();
345
345
  l(e, 8, n.crc, n.csize, n.size, r, i, n.chunks);
346
346
  };
347
- for (let t of e.list()) {
347
+ for (let t of e?.list() ?? []) {
348
348
  let n = E(t.name), r = c.get(n);
349
349
  if (r) {
350
350
  c.delete(n), r.data !== null && await u(t.name, r.data);
@@ -6222,7 +6222,7 @@ var Wa = (e, t) => `${e}:${t}`, Ga = class {
6222
6222
  let a = this.sheetIndex(n, this.cur);
6223
6223
  if (a < 0) return new I("#REF!");
6224
6224
  let o = Wa(a, r * Q + i);
6225
- return e.has(o) && this.compute(o, e, t), this.value(a, r, i);
6225
+ return t.has(o) ? new I("#REF!") : (e.has(o) && this.compute(o, e, t), this.value(a, r, i));
6226
6226
  },
6227
6227
  bounds: (e) => {
6228
6228
  let t = this.wb.sheetsData.get(this.sheetIndex(e, this.cur));
@@ -7479,6 +7479,7 @@ var zo = class {
7479
7479
  root;
7480
7480
  P;
7481
7481
  cache = /* @__PURE__ */ new Map();
7482
+ found = /* @__PURE__ */ new Map();
7482
7483
  changed = !1;
7483
7484
  constructor(e) {
7484
7485
  this.nodes = t(e), this.root = a(this.nodes), this.P = i(this.root.q);
@@ -7493,6 +7494,23 @@ var zo = class {
7493
7494
  let r = o(n).length;
7494
7495
  return u(n, "count", String(r)), this.changed = !0, r - 1;
7495
7496
  }
7497
+ lookup(e) {
7498
+ let t = this.found.get(e);
7499
+ if (!t) {
7500
+ let r = /* @__PURE__ */ new Map();
7501
+ o(s(this.root, e)).forEach((e, t) => {
7502
+ let i = n([e]);
7503
+ r.has(i) || r.set(i, t);
7504
+ }), this.found.set(e, t = r);
7505
+ }
7506
+ return t;
7507
+ }
7508
+ intern(e, t) {
7509
+ let r = n([t]), i = this.lookup(e), a = i.get(r);
7510
+ if (a !== void 0) return a;
7511
+ let o = this.append(e, t);
7512
+ return i.set(r, o), o;
7513
+ }
7496
7514
  numFmtId(e) {
7497
7515
  for (let [t, n] of Object.entries(qa)) if (n === e) return Number(t);
7498
7516
  let t = s(this.root, "numFmts"), n = 163;
@@ -7525,13 +7543,13 @@ var zo = class {
7525
7543
  "font"
7526
7544
  ].some((e) => e in n)) {
7527
7545
  let e = o(this.section("fonts"), "font"), t = f(e[Number(l(p, "fontId") ?? 0)] ?? e[0] ?? d(a + "font")), r = (e, n) => m(t, n, e, Mo);
7528
- n.b !== void 0 && r("b", n.b ? d(a + "b") : void 0), n.i !== void 0 && r("i", n.i ? d(a + "i") : void 0), n.strike !== void 0 && r("strike", n.strike ? d(a + "strike") : void 0), n.u !== void 0 && r("u", n.u ? d(a + "u") : void 0), n.sz !== void 0 && r("sz", d(a + "sz", { val: String(n.sz) })), "color" in n && r("color", n.color ? d(a + "color", { rgb: "FF" + n.color.replace("#", "").toUpperCase() }) : void 0), n.font && (r("name", d(a + "name", { val: n.font })), r("scheme", void 0)), u(p, "fontId", String(this.append("fonts", t))), u(p, "applyFont", "1");
7546
+ n.b !== void 0 && r("b", n.b ? d(a + "b") : void 0), n.i !== void 0 && r("i", n.i ? d(a + "i") : void 0), n.strike !== void 0 && r("strike", n.strike ? d(a + "strike") : void 0), n.u !== void 0 && r("u", n.u ? d(a + "u") : void 0), n.sz !== void 0 && r("sz", d(a + "sz", { val: String(n.sz) })), "color" in n && r("color", n.color ? d(a + "color", { rgb: "FF" + n.color.replace("#", "").toUpperCase() }) : void 0), n.font && (r("name", d(a + "name", { val: n.font })), r("scheme", void 0)), u(p, "fontId", String(this.intern("fonts", t))), u(p, "applyFont", "1");
7529
7547
  }
7530
7548
  if ("fill" in n) {
7531
7549
  if (!n.fill) u(p, "fillId", "0");
7532
7550
  else {
7533
7551
  let e = t(`<${a}fill><${a}patternFill patternType="solid"><${a}fgColor rgb="FF${n.fill.replace("#", "").toUpperCase()}"/><${a}bgColor indexed="64"/></${a}patternFill></${a}fill>`)[0];
7534
- u(p, "fillId", String(this.append("fills", e)));
7552
+ u(p, "fillId", String(this.intern("fills", e)));
7535
7553
  }
7536
7554
  u(p, "applyFill", "1");
7537
7555
  }
@@ -7553,7 +7571,7 @@ var zo = class {
7553
7571
  "top",
7554
7572
  "bottom"
7555
7573
  ]) t in e && m(r, e[t] ? d(a + t, { style: n.borderStyle ?? "thin" }, [d(a + "color", { rgb: "FF" + i })]) : d(a + t), t, c);
7556
- s(r, "diagonal") || m(r, d(a + "diagonal"), "diagonal", c), u(p, "borderId", String(this.append("borders", r))), u(p, "applyBorder", "1");
7574
+ s(r, "diagonal") || m(r, d(a + "diagonal"), "diagonal", c), u(p, "borderId", String(this.intern("borders", r))), u(p, "applyBorder", "1");
7557
7575
  }
7558
7576
  if ("align" in n || "wrap" in n || "valign" in n || "rotate" in n) {
7559
7577
  let e = f(s(p, "alignment") ?? d(a + "alignment"));
@@ -7568,7 +7586,7 @@ var zo = class {
7568
7586
  ]), u(p, "applyAlignment", "1");
7569
7587
  }
7570
7588
  n.numFmt !== void 0 && (u(p, "numFmtId", String(this.numFmtId(n.numFmt))), u(p, "applyNumberFormat", "1"));
7571
- let h = this.append("cellXfs", p);
7589
+ let h = this.intern("cellXfs", p);
7572
7590
  return this.cache.set(r, h), h;
7573
7591
  }
7574
7592
  xml() {
@@ -7591,6 +7609,8 @@ var zo = class {
7591
7609
  if (!e || r.length <= n) continue;
7592
7610
  let i = new Set(r.slice(n));
7593
7611
  e.c = e.c.filter((e) => !i.has(e)), u(e, "count", String(n));
7612
+ let a = this.found.get(t);
7613
+ if (a) for (let [e, t] of a) t >= n && a.delete(e);
7594
7614
  }
7595
7615
  for (let [t, n] of this.cache) n >= e.cellXfs && this.cache.delete(t);
7596
7616
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Server-side snapshots: replays shared history onto a document headlessly, exactly the way a
3
+ * collaborator applies it, so the result can be saved as the room's new base file.
4
+ *
5
+ * const client = await DocumentClient.open(snapshotFile, { worker: false })
6
+ * const snap = await compact(client, { from: snapshotMeta, batches })
7
+ * const file = await client.call('save') // store it, then hub.snapshot(room, snap)
8
+ */
9
+ import type { DocumentClient } from '../core/worker/client';
10
+ import type { Batch, Snapshot } from './protocol';
11
+ export interface CompactOptions {
12
+ /** the snapshot `client` was opened from (default: the original upload, seq 0) */
13
+ from?: Snapshot;
14
+ /** batches to apply: every batch after `from.seq`, in order (older ones are ignored) */
15
+ batches: Batch[];
16
+ /** canonical batches kept as the new snapshot's context (default 1000) */
17
+ window?: number;
18
+ }
19
+ /**
20
+ * Applies `batches` to `client` (opened on the file of snapshot `from`) and returns the snapshot it
21
+ * now holds. Save the document afterwards with `client.call('save')`.
22
+ */
23
+ export declare function compact(client: DocumentClient, opts: CompactOptions): Promise<Snapshot>;
@@ -5,26 +5,46 @@
5
5
  * const conn = hub.connect((msg) => socket.send(encode(msg)))
6
6
  * socket.onmessage = (e) => conn.receive(decode(e.data))
7
7
  * socket.onclose = () => conn.close()
8
+ *
9
+ * Snapshots (see `compact`) bound the history: register one with `hub.snapshot()` and new clients
10
+ * open its file and only replay the batches after it, while the hub drops older batches.
8
11
  */
9
- import { type Batch, type ClientMsg, type HubMsg, type PeerInfo } from './protocol';
12
+ import { type Batch, type ClientMsg, type Entry, type HubMsg, type PeerInfo, type Snapshot } from './protocol';
10
13
  export interface RoomState {
11
14
  id: string;
12
15
  seq: number;
16
+ /** every batch with seq > `start`, in order */
13
17
  log: Batch[];
18
+ /** batches older than this were dropped (after a snapshot) */
19
+ start: number;
14
20
  ids: Set<string>;
15
21
  peers: Map<number, {
16
22
  info: PeerInfo;
17
23
  send: (m: HubMsg) => void;
18
24
  }>;
19
25
  nextSite: number;
26
+ /** snapshots clients can join from: seq → context */
27
+ snaps: Map<number, Entry[]>;
28
+ }
29
+ /** A persisted room: its latest snapshot and the batches after it (and possibly some before). */
30
+ export interface RoomLog {
31
+ log: Batch[];
32
+ snapshot?: Snapshot & {
33
+ sites?: number;
34
+ };
20
35
  }
36
+ type Loaded = Batch[] | RoomLog | undefined;
21
37
  export interface HubOptions {
22
38
  /** called for every sequenced batch (persist it to rebuild rooms after a restart) */
23
39
  onBatch?: (room: string, batch: Batch) => void;
24
- /** rebuilds a room log (e.g. from storage) the first time the room is used */
25
- loadLog?: (room: string) => Batch[] | undefined;
40
+ /** rebuilds a room (e.g. from storage) the first time it is used */
41
+ loadLog?: (room: string) => Loaded | Promise<Loaded>;
26
42
  /** called when the last peer left a room */
27
43
  onIdle?: (room: string) => void;
44
+ /** batches kept below the latest snapshot for clients reconnecting from older states (default 1000) */
45
+ retain?: number;
46
+ /** most recent snapshots clients can still join from (default 3) */
47
+ keep?: number;
28
48
  }
29
49
  export interface HubConnection {
30
50
  receive(msg: ClientMsg): void;
@@ -33,9 +53,20 @@ export interface HubConnection {
33
53
  export declare class CollabHub {
34
54
  private opts;
35
55
  readonly rooms: Map<string, RoomState>;
56
+ private loading;
36
57
  constructor(opts?: HubOptions);
37
- room(id: string): RoomState;
58
+ /** the room, loaded through `loadLog` the first time */
59
+ room(id: string): Promise<RoomState>;
38
60
  /** forgets a room (its log and peers) — e.g. when a new base file is uploaded */
39
61
  reset(id: string): void;
62
+ /** drops an idle room from memory (it is loaded again on the next join); false while peers are connected */
63
+ unload(id: string): boolean;
64
+ /**
65
+ * Registers a snapshot of a loaded room (`compact` output, its file stored where clients can open
66
+ * it): clients may join from it, and batches older than `retain` below it are dropped.
67
+ * Returns false when the snapshot doesn't fit the room (e.g. it was reset meanwhile).
68
+ */
69
+ snapshot(id: string, snap: Snapshot): boolean;
40
70
  connect(send: (m: HubMsg) => void): HubConnection;
41
71
  }
72
+ export {};
@@ -19,6 +19,11 @@ export interface CollabOptions {
19
19
  /** shared document id */
20
20
  room: string;
21
21
  user: UserInfo;
22
+ /**
23
+ * Sequence number of the snapshot `src` was opened from (see `compact`); default 0, the original
24
+ * upload. The hub then only sends the batches after it.
25
+ */
26
+ base?: number;
22
27
  }
23
28
  export interface Collab {
24
29
  readonly session: CollabSession<unknown>;
@@ -36,8 +41,10 @@ export interface Collab {
36
41
  close(): void;
37
42
  }
38
43
  export declare function startCollab(client: DocumentClient, opts: CollabOptions): Collab;
39
- export { CollabHub, type HubOptions, type HubConnection } from './hub';
44
+ export { CollabHub, type HubOptions, type HubConnection, type RoomLog, type RoomState } from './hub';
45
+ export { compact, type CompactOptions } from './compact';
46
+ export { RoomStore, MemoryBlobStore, type BlobStore, type RoomHead, type RoomStoreOptions, type Version } from './store';
40
47
  export { CollabSession, type Adapter, type CollabStatus } from './session';
41
48
  export { MemoryTransport, WebSocketTransport, type Transport, type TransportStatus } from './transport';
42
- export type { Batch, ClientMsg, HubMsg, PeerInfo, UserInfo } from './protocol';
49
+ export type { Batch, ClientMsg, Entry, HubMsg, PeerInfo, Snapshot, UserInfo } from './protocol';
43
50
  export { encode, decode } from './codec';
@@ -28,15 +28,38 @@ export interface Batch<Op = unknown> {
28
28
  /** client generated id (dedupes resends after reconnects) */
29
29
  id: string;
30
30
  ops: Op[];
31
+ /** when the hub sequenced it (ms since epoch) */
32
+ time?: number;
33
+ /** author: user id, else name */
34
+ user?: string;
35
+ }
36
+ /** A batch the way every client applies it: its ops transformed against the concurrent batches sequenced before it. */
37
+ export interface Entry<Op = unknown> {
38
+ seq: number;
39
+ site: number;
40
+ ops: Op[];
41
+ }
42
+ /**
43
+ * The document materialized at `seq` (see `compact`): a file containing every batch up to `seq`,
44
+ * plus the canonical batches right before it (`ctx`) that clients joining from that file need to
45
+ * merge batches still based on older states.
46
+ */
47
+ export interface Snapshot<Op = unknown> {
48
+ seq: number;
49
+ ctx: Entry<Op>[];
31
50
  }
32
51
  export type ClientMsg =
33
- /** join a room; `have`/`site` on reconnect */
52
+ /**
53
+ * join a room; `have`/`site` on reconnect. `base`: on the first join, the snapshot the client
54
+ * opened (default 0, the original upload)
55
+ */
34
56
  {
35
57
  t: 'hello';
36
58
  room: string;
37
59
  user: UserInfo;
38
60
  have?: number;
39
61
  site?: number;
62
+ base?: number;
40
63
  } | {
41
64
  t: 'ops';
42
65
  id: string;
@@ -47,7 +70,10 @@ export type ClientMsg =
47
70
  data: unknown;
48
71
  };
49
72
  export type HubMsg =
50
- /** `log` holds every batch with seq > `from`; `reset` when the hub lost history newer than `have` */
73
+ /**
74
+ * `log` holds every batch with seq > `from`; `ctx` the joined snapshot's context (first join from a
75
+ * snapshot); `reset` when the hub can't continue from the client's state (history lost or dropped)
76
+ */
51
77
  {
52
78
  t: 'welcome';
53
79
  site: number;
@@ -56,6 +82,7 @@ export type HubMsg =
56
82
  log: Batch[];
57
83
  peers: PeerInfo[];
58
84
  reset?: boolean;
85
+ ctx?: Entry[];
59
86
  } | {
60
87
  t: 'batch';
61
88
  b: Batch;
@@ -14,7 +14,7 @@
14
14
  * to rebase, like deleting a sheet) are not applied locally until the hub has
15
15
  * sequenced them.
16
16
  */
17
- import type { PeerInfo, UserInfo } from './protocol';
17
+ import type { Entry, PeerInfo, UserInfo } from './protocol';
18
18
  import type { Transport } from './transport';
19
19
  export interface ApplyResult {
20
20
  /** whatever `revert` needs to undo the op exactly (local ops only) */
@@ -60,6 +60,8 @@ export interface SessionOptions {
60
60
  user: UserInfo;
61
61
  /** canonical batches kept to transform late batches (default 5000) */
62
62
  logWindow?: number;
63
+ /** sequence number the opened file already contains: a snapshot (default 0, the original upload) */
64
+ base?: number;
63
65
  }
64
66
  export declare class CollabSession<Op = unknown> {
65
67
  private transport;
@@ -94,6 +96,8 @@ export declare class CollabSession<Op = unknown> {
94
96
  push(op: Op, undo: unknown): void;
95
97
  setPresence(data: unknown): void;
96
98
  on<K extends keyof SessionEvents>(event: K, fn: (d: SessionEvents[K]) => void): () => void;
99
+ /** the last `limit` batches as applied here (the context of a snapshot taken at `rev`) */
100
+ context(limit?: number): Entry<Op>[];
97
101
  /** resolves once everything queued so far was processed */
98
102
  idle(): Promise<void>;
99
103
  close(): void;
@@ -0,0 +1,94 @@
1
+ import type { RoomLog } from './hub';
2
+ import type { Batch, Snapshot } from './protocol';
3
+ /** Minimal object storage. Keys use `/` separators. */
4
+ export interface BlobStore {
5
+ /** null when missing */
6
+ get(key: string): Promise<Uint8Array | null>;
7
+ put(key: string, data: Uint8Array): Promise<void>;
8
+ /** every key starting with `prefix` (any order) */
9
+ list(prefix: string): Promise<string[]>;
10
+ delete(key: string): Promise<void>;
11
+ }
12
+ export declare class MemoryBlobStore implements BlobStore {
13
+ readonly data: Map<string, Uint8Array<ArrayBufferLike>>;
14
+ get(key: string): Promise<Uint8Array<ArrayBufferLike> | null>;
15
+ put(key: string, data: Uint8Array): Promise<void>;
16
+ list(prefix: string): Promise<string[]>;
17
+ delete(key: string): Promise<void>;
18
+ }
19
+ export interface RoomHead {
20
+ /** changes with every upload: stale writes of an older upload are ignored */
21
+ epoch: string;
22
+ /** latest snapshot */
23
+ seq: number;
24
+ size: number;
25
+ name: string;
26
+ type: string;
27
+ time: number;
28
+ }
29
+ /** a stored snapshot */
30
+ export interface Version {
31
+ seq: number;
32
+ size: number;
33
+ time: number;
34
+ }
35
+ export interface RoomStoreOptions {
36
+ /** key prefix, e.g. `collab/` */
37
+ prefix?: string;
38
+ /** batches are buffered and written as one segment at most this often (ms, default 250) */
39
+ flushMs?: number;
40
+ /** snapshots kept per room (default: all — the version history); older ones and their batches are deleted */
41
+ keep?: number;
42
+ /** batches below the latest snapshot loaded for clients reconnecting from older states (default 1000) */
43
+ retain?: number;
44
+ /** a write failed (it is retried) */
45
+ onError?: (error: Error, room: string) => void;
46
+ }
47
+ type SnapMeta = Version & {
48
+ sites?: number;
49
+ };
50
+ export declare class RoomStore {
51
+ readonly blobs: BlobStore;
52
+ private opts;
53
+ private heads;
54
+ private buffers;
55
+ private queues;
56
+ constructor(blobs: BlobStore, opts?: RoomStoreOptions);
57
+ private dir;
58
+ private epochDir;
59
+ /** runs `fn` after every earlier operation on `room` */
60
+ private run;
61
+ private readHead;
62
+ /** sets a room's document (snapshot 0 of a new epoch) and forgets its history */
63
+ create(room: string, file: Uint8Array, info: {
64
+ name: string;
65
+ type: string;
66
+ }): Promise<RoomHead>;
67
+ /** latest snapshot of the room (null: no such room) */
68
+ head(room: string): Promise<RoomHead | null>;
69
+ /** the document of snapshot `seq` */
70
+ file(room: string, seq: number, epoch?: string): Promise<Uint8Array | null>;
71
+ /** snapshot `seq` with its merge context */
72
+ snapshot(room: string, seq: number, epoch?: string): Promise<(Snapshot & SnapMeta) | null>;
73
+ /** stored snapshots, oldest first */
74
+ versions(room: string, epoch?: string): Promise<Version[]>;
75
+ /** stored batches with `after` < seq ≤ `upTo` (flushed ones only), in order */
76
+ batches(room: string, after: number, upTo?: number, epoch?: string): Promise<Batch[]>;
77
+ /** hub `onBatch`: buffers the batch, written with the next segment */
78
+ append(room: string, batch: Batch): void;
79
+ /** writes buffered batches now (all rooms when `room` is omitted) */
80
+ flush(room?: string): Promise<void>;
81
+ private flushRoom;
82
+ /** hub `loadLog`: the latest snapshot and the batches after it (plus `retain` before it) */
83
+ load(room: string): Promise<RoomLog | undefined>;
84
+ /**
85
+ * Stores a snapshot taken from `base` (the head it was compacted from) and makes it the latest.
86
+ * Returns the new head, or null when the room changed meanwhile (new upload, newer snapshot).
87
+ */
88
+ save(room: string, base: RoomHead, snap: Snapshot & {
89
+ sites?: number;
90
+ }, file: Uint8Array): Promise<RoomHead | null>;
91
+ /** drops snapshots beyond `keep` and the segments no remaining snapshot needs */
92
+ private prune;
93
+ }
94
+ export {};
@@ -0,0 +1,29 @@
1
+ import type { RunSet } from './docx/types';
2
+ /** Separator of a delimited text file: tab, semicolon (European CSV) or comma. */
3
+ export declare function detectSeparator(text: string): string;
4
+ /** RFC 4180 parser (quoted fields, escaped quotes, CRLF / LF, newlines inside quotes). */
5
+ export declare function parseCsv(text: string, sep?: string): string[][];
6
+ /**
7
+ * Builds a one-sheet workbook from rows of text.
8
+ * `decimalComma`: "12,5" is a number (semicolon-separated files from comma-decimal locales).
9
+ */
10
+ export declare function rowsToXlsx(rows: string[][], sheetName?: string, decimalComma?: boolean): Promise<Blob>;
11
+ export interface ImportRun {
12
+ text: string;
13
+ set?: RunSet;
14
+ href?: string;
15
+ }
16
+ export interface ImportPara {
17
+ runs: ImportRun[];
18
+ /** heading level 1-6 */
19
+ heading?: number;
20
+ list?: 'bullet' | 'number';
21
+ }
22
+ /** One paragraph per line. */
23
+ export declare function textToParas(text: string): ImportPara[];
24
+ /** Small CommonMark subset → HTML: headings, paragraphs, lists, quotes, code blocks, rules, tables. */
25
+ export declare function markdownToHtml(md: string): string;
26
+ /** Builds a document (Letter, 1" margins, Calibri 11) from paragraphs of formatted runs. */
27
+ export declare function parasToDocx(paras: ImportPara[]): Promise<Blob>;
28
+ /** One empty title slide, 16:9, Office theme; layouts: title, title and content, blank. */
29
+ export declare function blankPptx(): Promise<Blob>;
@@ -6,4 +6,6 @@ export type PartData = Uint8Array | string | (() => ReadableStream<Uint8Array>);
6
6
  * zero-copy when the source is a Blob); changed/added entries are deflated.
7
7
  * `changes`: path → new data, or null to delete.
8
8
  */
9
- export declare function rebuildZip(zip: ZipArchive, changes: Map<string, PartData | null>, type?: string): Promise<Blob>;
9
+ export declare function rebuildZip(zip: ZipArchive | null, changes: Map<string, PartData | null>, type?: string): Promise<Blob>;
10
+ /** Writes a new archive (all entries deflated). */
11
+ export declare function writeZip(entries: Map<string, PartData>, type?: string): Promise<Blob>;
@@ -10,8 +10,8 @@ export declare function mount(el: HTMLElement, options: MountOptions): MountedVi
10
10
  /**
11
11
  * `<office-viewer src="/file.xlsx" style="height:600px"></office-viewer>`
12
12
  * Rendered inside a shadow root so host page CSS cannot leak in.
13
- * Attributes: src, name, editable, toolbar ("false" to hide), thumbnails ("false" to hide);
14
- * real-time collaboration: collab (hub WebSocket URL), room, user (display name).
13
+ * Attributes: src, name, editable, toolbar / thumbnails / save / download / file-menu / open ("false" to hide);
14
+ * real-time collaboration: collab (hub WebSocket URL), room, user (display name), base (snapshot seq `src` was opened at).
15
15
  * Property: `file` (File/Blob/ArrayBuffer) for local data.
16
16
  */
17
17
  export declare class OfficeViewerElement extends HTMLElement {
@@ -1,5 +1,6 @@
1
1
  import './core/default-worker';
2
2
  export { DocViewer, type DocViewerProps } from './react/DocViewer';
3
+ export type { FileMenuItem } from './react/file/FileMenu';
3
4
  export { DocxView, type DocxViewProps } from './react/docx/DocxView';
4
5
  export { XlsxView, type XlsxViewProps } from './react/xlsx/XlsxView';
5
6
  export { PptxView, type PptxViewProps } from './react/pptx/PptxView';
@@ -2,6 +2,7 @@ import { type CSSProperties, type ReactNode } from 'react';
2
2
  import type { DocSource } from '../core/source';
3
3
  import type { DocumentClient, OpenOptions } from '../core/worker/client';
4
4
  import type { Zoom } from './common/ZoomControl';
5
+ import { type FileMenuItem } from './file/FileMenu';
5
6
  import type { CollabOptions } from '../collab';
6
7
  export interface DocViewerProps {
7
8
  /** File, Blob, ArrayBuffer, Uint8Array or URL (HTTP Range requests are used when the server supports them). */
@@ -30,6 +31,23 @@ export interface DocViewerProps {
30
31
  editable?: boolean;
31
32
  /** Show the Edit/View toggle. Default true. */
32
33
  allowEdit?: boolean;
34
+ /** Show the Save/Download button and enable Ctrl+S. Default true. */
35
+ allowSave?: boolean;
36
+ /**
37
+ * Allow downloading files to the user's device (the save fallback without `onSave`, "Download image").
38
+ * With `onSave`, saving still works when this is false. Default true.
39
+ */
40
+ allowDownload?: boolean;
41
+ /** Show the File menu (new, open, save, rename, download as PDF / HTML / text…, details, print) and its shortcuts. Default true. */
42
+ fileMenu?: boolean;
43
+ /** File → New / Open: replace the document with a blank one or a local file (Office, CSV, text, Markdown, HTML). Default true. */
44
+ allowOpen?: boolean;
45
+ /** Extra File menu entries (Share, Version history…), shown before Close. */
46
+ fileMenuItems?: FileMenuItem[];
47
+ /** File → Rename: called with the new name (extension included). Rename is shown when `fileMenu` is on. */
48
+ onRename?: (name: string) => void;
49
+ /** Adds File → Close. */
50
+ onClose?: () => void;
33
51
  /** Receives the saved file instead of downloading it. */
34
52
  onSave?: (file: Blob, name: string) => void | Promise<void>;
35
53
  /** Called when the document gets / loses unsaved changes. */
@@ -20,6 +20,14 @@ export declare function PromptDialog({ spec, onClose }: {
20
20
  spec: PromptSpec;
21
21
  onClose: () => void;
22
22
  }): import("react").JSX.Element;
23
+ /** Yes / no question (no window.confirm: works in sandboxed / embedded pages). */
24
+ export declare function ConfirmDialog(props: {
25
+ title: string;
26
+ message: ReactNode;
27
+ ok: string;
28
+ onOk: () => void;
29
+ onClose: () => void;
30
+ }): import("react").JSX.Element;
23
31
  /** Short-lived message at the bottom of the viewer. */
24
32
  export declare function Toast({ text, onDone }: {
25
33
  text: string;
@@ -1,5 +1,17 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { DocumentClient } from '../../core/worker/client';
3
+ import type { Printable } from '../file/print';
4
+ /** What the active view offers to the File menu. */
5
+ export interface FileOps {
6
+ /** all pages, for PDF / print and HTML */
7
+ printable(): Promise<Printable>;
8
+ /** text formats: 'txt' / 'md' (documents, presentations), 'csv' / 'tsv' (current sheet) */
9
+ text(format: string): Promise<string>;
10
+ /** File → Document details: [label, value] rows */
11
+ stats(): [string, string][];
12
+ /** File → Import (spreadsheets): rows into a new sheet */
13
+ importTable?(rows: string[][], name: string): Promise<void>;
14
+ }
3
15
  export interface ViewerApi {
4
16
  /** editor bar mount point (null when the viewer toolbar is hidden) */
5
17
  bar: HTMLElement | null;
@@ -8,10 +20,18 @@ export interface ViewerApi {
8
20
  setDirty: (dirty: boolean) => void;
9
21
  /** build + hand off the edited file */
10
22
  save: () => Promise<void>;
23
+ /** whether files may be downloaded to the user's device */
24
+ canDownload: boolean;
11
25
  client: DocumentClient | null;
26
+ /** set by the mounted view (see useFileOps) */
27
+ fileOps: {
28
+ current: FileOps | null;
29
+ };
12
30
  }
13
31
  export declare const ViewerContext: import("react").Context<ViewerApi>;
14
32
  export declare const useViewer: () => ViewerApi;
33
+ /** Publishes the view's file operations (latest closure each render) to the File menu. */
34
+ export declare function useFileOps(ops: FileOps): void;
15
35
  /** Renders the editor bar into the viewer's toolbar row. */
16
36
  export declare function EditorBar({ children }: {
17
37
  children: ReactNode;
@@ -4,7 +4,8 @@ export interface ImageActions {
4
4
  original: () => void;
5
5
  replace: () => void;
6
6
  alt: () => void;
7
- download: () => void;
7
+ /** undefined when downloads are disabled */
8
+ download?: () => void;
8
9
  }
9
10
  export declare function DocxToolbar(props: {
10
11
  editor: DocxEditor;
@@ -26,6 +26,8 @@ export interface DropOption<T> {
26
26
  disabled?: boolean;
27
27
  /** separator above this option */
28
28
  sep?: boolean;
29
+ /** group heading above this option */
30
+ head?: string;
29
31
  }
30
32
  /**
31
33
  * Styled dropdown (listbox). Never takes focus from the editor: mouse down is prevented and,
@@ -46,6 +48,8 @@ export declare function Dropdown<T extends string | number>(props: {
46
48
  disabled?: boolean;
47
49
  /** popup aligned to the trigger's right edge */
48
50
  alignRight?: boolean;
51
+ /** extra class of the trigger button */
52
+ className?: string;
49
53
  }): import("react").JSX.Element;
50
54
  /** Value picker (fonts, styles, formats…). */
51
55
  export declare function Select<T extends string | number>(props: {
@@ -0,0 +1,7 @@
1
+ import type { DocumentClient } from '../../core/worker/client';
2
+ export declare function DetailsDialog(props: {
3
+ client: DocumentClient;
4
+ file: [string, string][];
5
+ stats: [string, string][];
6
+ onClose: () => void;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,48 @@
1
+ import type { DocKind } from '../../core/opc';
2
+ import type { FileOps } from '../common/Slots';
3
+ import { type DropOption } from '../edit/Toolbar';
4
+ export declare const KIND_LABEL: Record<DocKind, string>;
5
+ /** Host-provided File menu entry (Share, Version history…). */
6
+ export interface FileMenuItem {
7
+ label: string;
8
+ /** built-in icon name */
9
+ icon?: string;
10
+ /** shortcut / note on the right */
11
+ hint?: string;
12
+ disabled?: boolean;
13
+ onClick: () => void;
14
+ }
15
+ export interface FileActionsOptions {
16
+ kind: DocKind | undefined;
17
+ /** file name without extension */
18
+ base: string;
19
+ /** New / Open (replace the document) */
20
+ canOpen: boolean;
21
+ /** Import into the document (spreadsheets) */
22
+ canImport: boolean;
23
+ canSave: boolean;
24
+ canDownload: boolean;
25
+ ops: {
26
+ current: FileOps | null;
27
+ };
28
+ create: (kind: DocKind) => void;
29
+ open: (file: File) => void;
30
+ save: () => Promise<void>;
31
+ /** the document in its own format */
32
+ downloadOriginal: () => Promise<void>;
33
+ rename?: () => void;
34
+ details?: () => void;
35
+ close?: () => void;
36
+ extra: FileMenuItem[];
37
+ notify: (message: string) => void;
38
+ }
39
+ export interface FileActions {
40
+ options: DropOption<string>[];
41
+ busy: boolean;
42
+ run: (action: string) => void;
43
+ }
44
+ /** File menu entries and their actions (also used by the Ctrl+O / Ctrl+P shortcuts). */
45
+ export declare function useFileActions(o: FileActionsOptions): FileActions;
46
+ export declare function FileMenu({ actions }: {
47
+ actions: FileActions;
48
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ /** `accept` of the File → Open picker */
2
+ export declare const OPEN_ACCEPT: string;
3
+ export interface Imported {
4
+ src: Blob;
5
+ name: string;
6
+ /** built from another format: not saved as an Office file yet */
7
+ converted: boolean;
8
+ }
9
+ /** Office files open as they are; CSV / TSV become a workbook, text / Markdown / HTML a document. */
10
+ export declare function importFile(file: File): Promise<Imported>;
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ /** Content laid out for paper: PDF / print and standalone HTML. */
3
+ export interface Printable {
4
+ /** the pages (`.de-print-page` elements) or flowing content */
5
+ node: ReactNode;
6
+ /** CSS `@page` size: '612pt 792pt', 'landscape', 'auto' */
7
+ size: string;
8
+ /** CSS `@page` margin */
9
+ margin: string;
10
+ /** shown to the user (e.g. output was truncated) */
11
+ notice?: string;
12
+ }
13
+ /** Opens the browser print dialog for the content (choose "Save as PDF" there). */
14
+ export declare function printDocument(p: Printable, title: string): Promise<void>;
15
+ /** Standalone web page of the content (pictures embedded). */
16
+ export declare function exportHtml(p: Printable, title: string): Promise<Blob>;