@embedpdf/plugin-attachment 2.15.1 → 3.0.0-next.16
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/LICENSE +192 -21
- package/dist/contract-C__XpO5g.cjs +42 -0
- package/dist/contract-C__XpO5g.cjs.map +1 -0
- package/dist/contract-CqFkPaxs.js +32 -0
- package/dist/contract-CqFkPaxs.js.map +1 -0
- package/dist/contract-D2t_jdnb.d.cts +123 -0
- package/dist/contract-D2t_jdnb.d.ts +123 -0
- package/dist/contract.cjs +4 -0
- package/dist/contract.d.cts +2 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +2 -0
- package/dist/index.cjs +424 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -1
- package/dist/index.js +420 -81
- package/dist/index.js.map +1 -1
- package/package.json +27 -56
- package/dist/lib/attachment-plugin.d.ts +0 -11
- package/dist/lib/index.d.ts +0 -7
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/types.d.ts +0 -13
- package/dist/preact/core.d.ts +0 -1
- package/dist/preact/index.cjs +0 -2
- package/dist/preact/index.cjs.map +0 -1
- package/dist/preact/index.d.ts +0 -1
- package/dist/preact/index.js +0 -10
- package/dist/preact/index.js.map +0 -1
- package/dist/react/core.d.ts +0 -1
- package/dist/react/index.cjs +0 -2
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -10
- package/dist/react/index.js.map +0 -1
- package/dist/shared/hooks/index.d.ts +0 -1
- package/dist/shared/hooks/use-attachment.d.ts +0 -11
- package/dist/shared/index.d.ts +0 -2
- package/dist/shared-preact/hooks/index.d.ts +0 -1
- package/dist/shared-preact/hooks/use-attachment.d.ts +0 -11
- package/dist/shared-preact/index.d.ts +0 -2
- package/dist/shared-react/hooks/index.d.ts +0 -1
- package/dist/shared-react/hooks/use-attachment.d.ts +0 -11
- package/dist/shared-react/index.d.ts +0 -2
- package/dist/svelte/hooks/index.d.ts +0 -1
- package/dist/svelte/hooks/use-attachment.d.ts +0 -11
- package/dist/svelte/index.cjs +0 -2
- package/dist/svelte/index.cjs.map +0 -1
- package/dist/svelte/index.d.ts +0 -1
- package/dist/svelte/index.js +0 -9
- package/dist/svelte/index.js.map +0 -1
- package/dist/vue/hooks/index.d.ts +0 -1
- package/dist/vue/hooks/use-attachment.d.ts +0 -3
- package/dist/vue/index.cjs +0 -2
- package/dist/vue/index.cjs.map +0 -1
- package/dist/vue/index.d.ts +0 -2
- package/dist/vue/index.js +0 -10
- package/dist/vue/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,81 +1,420 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
import { n as AttachmentToken, t as displayNameOf } from "./contract-CqFkPaxs.js";
|
|
2
|
+
import { PluginError, definePlugin, defineState, memo, toPluginError } from "@embedpdf/core";
|
|
3
|
+
import { AnnotationToken } from "@embedpdf/plugin-annotation/contract";
|
|
4
|
+
import { StageToken } from "@embedpdf/plugin-stage/contract";
|
|
5
|
+
import { annotationKey, toAttachmentRef } from "@embedpdf/engine-core/runtime";
|
|
6
|
+
//#region src/model.ts
|
|
7
|
+
/**
|
|
8
|
+
* The attachment model: the document's attachments as the mirror holds
|
|
9
|
+
* them, how an attachment and a page's file become one `AttachedFile`, and
|
|
10
|
+
* the name a new file takes. Pure.
|
|
11
|
+
*/
|
|
12
|
+
const initialAttachmentState = () => ({ selected: [] });
|
|
13
|
+
/** Make these files the selection; the same ids leave the state as it is. */
|
|
14
|
+
function selected(state, ids) {
|
|
15
|
+
const unique = ids.filter((id, i) => ids.indexOf(id) === i);
|
|
16
|
+
return unique.length === state.selected.length && unique.every((id, i) => id === state.selected[i]) ? state : {
|
|
17
|
+
...state,
|
|
18
|
+
selected: unique
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** An attachment's id: its key. */
|
|
22
|
+
const documentFileId = (ref) => `doc:${ref.key}`;
|
|
23
|
+
/** A page file's id: its annotation's key. */
|
|
24
|
+
const pageFileId = (ref) => `page:${annotationKey(ref)}`;
|
|
25
|
+
/** An attachment as the list shows it. */
|
|
26
|
+
function documentFileOf(attachment) {
|
|
27
|
+
const { ref, index: _index, ...info } = attachment;
|
|
28
|
+
return {
|
|
29
|
+
...info,
|
|
30
|
+
id: documentFileId(ref),
|
|
31
|
+
home: {
|
|
32
|
+
kind: "document",
|
|
33
|
+
ref
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** A file attachment annotation's file as the list shows it; `null` for another kind, or one without a file. */
|
|
38
|
+
function pageFileOf(annotation) {
|
|
39
|
+
if (annotation.subtype !== "file-attachment" || !annotation.file) return null;
|
|
40
|
+
return {
|
|
41
|
+
...annotation.file,
|
|
42
|
+
id: pageFileId(annotation.ref),
|
|
43
|
+
home: {
|
|
44
|
+
kind: "page",
|
|
45
|
+
ref: annotation.ref
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `name`, or, when a file has it already, the name with the first number
|
|
51
|
+
* that makes it free: `notes (2).txt`, then `notes (3).txt`. The number goes
|
|
52
|
+
* before the extension, as a file manager puts it.
|
|
53
|
+
*/
|
|
54
|
+
function freeName(name, taken) {
|
|
55
|
+
if (!taken.has(name)) return name;
|
|
56
|
+
const dot = name.lastIndexOf(".");
|
|
57
|
+
const stem = dot > 0 ? name.slice(0, dot) : name;
|
|
58
|
+
const extension = dot > 0 ? name.slice(dot) : "";
|
|
59
|
+
for (let number = 2;; number++) {
|
|
60
|
+
const candidate = `${stem} (${number})${extension}`;
|
|
61
|
+
if (!taken.has(candidate)) return candidate;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/sync/files.ts
|
|
66
|
+
/** The files with each `index` its position, keeping the files whose position held. */
|
|
67
|
+
function indexed(files) {
|
|
68
|
+
return files.map((file, index) => file.index === index ? file : {
|
|
69
|
+
...file,
|
|
70
|
+
index
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const has = (files, key) => files.some((file) => file.ref.key === key);
|
|
74
|
+
/**
|
|
75
|
+
* The files with `attachment` among them, before the first key that sorts
|
|
76
|
+
* after its own, as the name tree puts a new entry; one with its key gives
|
|
77
|
+
* way to it.
|
|
78
|
+
*/
|
|
79
|
+
function withFile(files, attachment) {
|
|
80
|
+
const key = attachment.ref.key;
|
|
81
|
+
const rest = files.filter((file) => file.ref.key !== key);
|
|
82
|
+
const at = rest.findIndex((file) => file.ref.key > key);
|
|
83
|
+
return indexed(at < 0 ? [...rest, attachment] : [
|
|
84
|
+
...rest.slice(0, at),
|
|
85
|
+
attachment,
|
|
86
|
+
...rest.slice(at)
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
function withoutFile(files, key) {
|
|
90
|
+
return has(files, key) ? indexed(files.filter((file) => file.ref.key !== key)) : files;
|
|
91
|
+
}
|
|
92
|
+
/** One confirmed event, applied with the record it carries. Before the first load there is nothing to apply it to. */
|
|
93
|
+
function foldFiles(files, event) {
|
|
94
|
+
if (!files) return files;
|
|
95
|
+
switch (event.type) {
|
|
96
|
+
case "attachments.created":
|
|
97
|
+
case "attachments.restored": return withFile(files, event.attachment);
|
|
98
|
+
case "attachments.deleted": return event.deleted ? withoutFile(files, event.deleted.key) : files;
|
|
99
|
+
default: return files;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** A file a create adds, as it shows until the engine answers: what the op says of it, and its size. */
|
|
103
|
+
function predictedFile(op) {
|
|
104
|
+
const bytes = op.resources.file;
|
|
105
|
+
return {
|
|
106
|
+
ref: toAttachmentRef(op.file.name),
|
|
107
|
+
index: 0,
|
|
108
|
+
name: op.file.name,
|
|
109
|
+
mimeType: op.file.mimeType ?? null,
|
|
110
|
+
description: op.file.description ?? null,
|
|
111
|
+
size: bytes === void 0 ? null : bytes instanceof Uint8Array ? bytes.byteLength : bytes.size,
|
|
112
|
+
checksum: null,
|
|
113
|
+
createdAt: null,
|
|
114
|
+
modifiedAt: null
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* One op of a change this session staged, shown before the engine answers,
|
|
119
|
+
* by the engine's rules: a create of a name a file has, a delete of a file
|
|
120
|
+
* that isn't there, and a restore of a name a file has change nothing.
|
|
121
|
+
*/
|
|
122
|
+
function predictFiles(files, op) {
|
|
123
|
+
if (!files) return files;
|
|
124
|
+
switch (op.type) {
|
|
125
|
+
case "attachments.create": return has(files, op.file.name) ? files : withFile(files, predictedFile(op));
|
|
126
|
+
case "attachments.delete": return withoutFile(files, op.ref.key);
|
|
127
|
+
case "attachments.restore": return has(files, op.attachment.ref.key) ? files : withFile(files, op.attachment);
|
|
128
|
+
default: return files;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** What undoing one op looks like, read off the files it applies to. */
|
|
132
|
+
function undoOfOp(files, op) {
|
|
133
|
+
switch (op.type) {
|
|
134
|
+
case "attachments.create": return [{
|
|
135
|
+
type: "attachments.delete",
|
|
136
|
+
ref: toAttachmentRef(op.file.name)
|
|
137
|
+
}];
|
|
138
|
+
case "attachments.delete": {
|
|
139
|
+
const attachment = files.find((file) => file.ref.key === op.ref.key);
|
|
140
|
+
return attachment ? [{
|
|
141
|
+
type: "attachments.restore",
|
|
142
|
+
attachment
|
|
143
|
+
}] : [];
|
|
144
|
+
}
|
|
145
|
+
default: return [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* What undoing `ops` looks like, read off the files they apply to, each op
|
|
150
|
+
* on the files the ops before it leave; the last op is undone first.
|
|
151
|
+
*/
|
|
152
|
+
function undoOf(files, ops) {
|
|
153
|
+
if (!files) return [];
|
|
154
|
+
const undo = [];
|
|
155
|
+
let current = files;
|
|
156
|
+
for (const op of ops) {
|
|
157
|
+
undo.push(undoOfOp(current, op));
|
|
158
|
+
current = predictFiles(current, op) ?? current;
|
|
159
|
+
}
|
|
160
|
+
return undo.reverse().flat();
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/controller.ts
|
|
164
|
+
const MODIFY = "doc.attachments.modify";
|
|
165
|
+
const DOWNLOAD = "doc.download";
|
|
166
|
+
const NO_FILES = Object.freeze([]);
|
|
167
|
+
/**
|
|
168
|
+
* The attachments controller. The document's attachments are a mirror,
|
|
169
|
+
* read when something asks for them (`ensureLoaded()`, what
|
|
170
|
+
* `attachmentState` does), and every read shows its `view()`: the engine's
|
|
171
|
+
* attachments with this session's pending adds and removes on top. A page's
|
|
172
|
+
* file is a file attachment annotation, read from the annotation plugin, so
|
|
173
|
+
* the list shows both homes with no read of its own. Each add or remove of
|
|
174
|
+
* an attachment is one change on the document's queue: it shows at once, is
|
|
175
|
+
* one step of the history, and undoing it shows the files as they were.
|
|
176
|
+
*/
|
|
177
|
+
function createAttachmentController(ctx) {
|
|
178
|
+
const changed = ctx.events.source();
|
|
179
|
+
const resynced = ctx.events.source();
|
|
180
|
+
const documentFiles = /* @__PURE__ */ new WeakMap();
|
|
181
|
+
const fileOfAttachment = (attachment) => {
|
|
182
|
+
let file = documentFiles.get(attachment);
|
|
183
|
+
if (!file) documentFiles.set(attachment, file = documentFileOf(attachment));
|
|
184
|
+
return file;
|
|
185
|
+
};
|
|
186
|
+
const pageFiles = /* @__PURE__ */ new WeakMap();
|
|
187
|
+
const fileOfAnnotation = (annotation) => {
|
|
188
|
+
let file = pageFiles.get(annotation);
|
|
189
|
+
if (file === void 0) pageFiles.set(annotation, file = pageFileOf(annotation));
|
|
190
|
+
return file;
|
|
191
|
+
};
|
|
192
|
+
/** The fact a confirmed attachment event reports, or `null` for any other event. */
|
|
193
|
+
const factOf = (event, previous) => {
|
|
194
|
+
switch (event.type) {
|
|
195
|
+
case "attachments.created": return {
|
|
196
|
+
type: "added",
|
|
197
|
+
files: [fileOfAttachment(event.attachment)]
|
|
198
|
+
};
|
|
199
|
+
case "attachments.restored": return {
|
|
200
|
+
type: "restored",
|
|
201
|
+
files: [fileOfAttachment(event.attachment)]
|
|
202
|
+
};
|
|
203
|
+
case "attachments.deleted": {
|
|
204
|
+
const gone = previous?.find((file) => file.ref.key === event.deleted?.key);
|
|
205
|
+
return {
|
|
206
|
+
type: "removed",
|
|
207
|
+
files: gone ? [fileOfAttachment(gone)] : []
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
default: return null;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const files = ctx.mirror({
|
|
214
|
+
name: "attachments",
|
|
215
|
+
onDemand: true,
|
|
216
|
+
initial: () => null,
|
|
217
|
+
load: async (doc) => ({ value: (await doc.attachments.list()).attachments }),
|
|
218
|
+
fold: foldFiles,
|
|
219
|
+
predict: predictFiles,
|
|
220
|
+
changed: ({ cause, event, previous, next }) => {
|
|
221
|
+
if (!next) return;
|
|
222
|
+
if (cause === "load") {
|
|
223
|
+
resynced.emit({ files: next.map(fileOfAttachment) });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const fact = event && "origin" in event ? factOf(event, previous) : null;
|
|
227
|
+
if (fact && event && "origin" in event) changed.emit({
|
|
228
|
+
...fact,
|
|
229
|
+
origin: event.origin
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
const annotations = () => ctx.tryGet(AnnotationToken);
|
|
234
|
+
const list = memo(() => [files.view(), annotations()?.list() ?? null], (attachments, annots) => {
|
|
235
|
+
const all = [...(attachments ?? []).map(fileOfAttachment), ...(annots ?? []).flatMap((annotation) => {
|
|
236
|
+
const file = fileOfAnnotation(annotation);
|
|
237
|
+
return file ? [file] : [];
|
|
238
|
+
})];
|
|
239
|
+
return all.length > 0 ? all : NO_FILES;
|
|
240
|
+
});
|
|
241
|
+
const get = (id) => list().find((file) => file.id === id) ?? null;
|
|
242
|
+
const getSelected = memo(() => [list(), ctx.state.get().selected], (files, ids) => {
|
|
243
|
+
if (ids.length === 0) return NO_FILES;
|
|
244
|
+
const byId = new Map(files.map((file) => [file.id, file]));
|
|
245
|
+
const chosen = ids.flatMap((id) => {
|
|
246
|
+
const file = byId.get(id);
|
|
247
|
+
return file ? [file] : [];
|
|
248
|
+
});
|
|
249
|
+
return chosen.length > 0 ? chosen : NO_FILES;
|
|
250
|
+
});
|
|
251
|
+
const isPending = (file) => {
|
|
252
|
+
if (file.home.kind === "page") return annotations()?.isPending(file.home.ref) ?? false;
|
|
253
|
+
const key = file.home.ref.key;
|
|
254
|
+
return !(files.get() ?? []).some((confirmed) => confirmed.ref.key === key);
|
|
255
|
+
};
|
|
256
|
+
/** Refuse before anything starts: without the permission, or once the caller cancelled. */
|
|
257
|
+
const refuse = (permission, operation, options) => {
|
|
258
|
+
ctx.assertAllowed(permission, operation);
|
|
259
|
+
if (options?.signal?.aborted) throw new PluginError("operation-cancelled", "attachment", `${operation} was cancelled`);
|
|
260
|
+
};
|
|
261
|
+
/** The engine's answer to a staged change. */
|
|
262
|
+
const answer = async (change, options) => {
|
|
263
|
+
try {
|
|
264
|
+
return await ctx.cancellable(options?.signal, change.result);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
throw toPluginError("attachment", error);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
/** The annotation plugin, for a page's file: the list shows none without it. */
|
|
270
|
+
const annotationsFor = (operation) => {
|
|
271
|
+
const annotation = annotations();
|
|
272
|
+
if (!annotation) throw new PluginError("not-found", "attachment", `${operation}: no annotation plugin`);
|
|
273
|
+
return annotation;
|
|
274
|
+
};
|
|
275
|
+
/** The op that attaches `source` under a name no file has (`taken`, which it joins). */
|
|
276
|
+
const createOp = (source, taken) => {
|
|
277
|
+
const { data, description } = source;
|
|
278
|
+
const blob = typeof Blob !== "undefined" && data instanceof Blob ? data : null;
|
|
279
|
+
const given = source.name ?? (blob && "name" in blob ? String(blob.name) : "");
|
|
280
|
+
if (!given) throw new PluginError("invalid-input", "attachment", "attachment.add: a file needs a name: pass { data, name } or a File");
|
|
281
|
+
const name = freeName(given, taken);
|
|
282
|
+
taken.add(name);
|
|
283
|
+
const mimeType = source.mimeType ?? (blob?.type || void 0);
|
|
284
|
+
return {
|
|
285
|
+
type: "attachments.create",
|
|
286
|
+
file: {
|
|
287
|
+
name,
|
|
288
|
+
...mimeType !== void 0 ? { mimeType } : {},
|
|
289
|
+
...description !== void 0 ? { description } : {}
|
|
290
|
+
},
|
|
291
|
+
resources: { file: data instanceof ArrayBuffer ? new Uint8Array(data) : data }
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
const add = async (sources, options) => {
|
|
295
|
+
refuse(MODIFY, "attachment.add", options);
|
|
296
|
+
if (sources.length === 0) return NO_FILES;
|
|
297
|
+
await ctx.cancellable(options?.signal, files.ensureLoaded());
|
|
298
|
+
const taken = new Set((files.view() ?? []).map((file) => file.ref.key));
|
|
299
|
+
const ops = sources.map((source) => createOp(source, taken));
|
|
300
|
+
const change = ctx.changes.stage({
|
|
301
|
+
label: {
|
|
302
|
+
key: "attachment.add",
|
|
303
|
+
count: ops.length
|
|
304
|
+
},
|
|
305
|
+
ops,
|
|
306
|
+
undo: undoOf(files.view(), ops)
|
|
307
|
+
});
|
|
308
|
+
return (await answer(change, options)).items.flatMap((item) => item.type === "attachments.create" ? [fileOfAttachment(item.attachment)] : []);
|
|
309
|
+
};
|
|
310
|
+
const remove = async (file, options) => {
|
|
311
|
+
if (file.home.kind === "page") return annotationsFor("attachment.remove").delete(file.home.ref, options);
|
|
312
|
+
refuse(MODIFY, "attachment.remove", options);
|
|
313
|
+
const { ref } = file.home;
|
|
314
|
+
const view = files.view();
|
|
315
|
+
if (view && !view.some((attachment) => attachment.ref.key === ref.key)) throw new PluginError("not-found", "attachment", `attachment.remove: the document has no attachment '${ref.key}'`);
|
|
316
|
+
const ops = [{
|
|
317
|
+
type: "attachments.delete",
|
|
318
|
+
ref
|
|
319
|
+
}];
|
|
320
|
+
const change = ctx.changes.stage({
|
|
321
|
+
label: { key: "attachment.remove" },
|
|
322
|
+
ops,
|
|
323
|
+
undo: undoOf(view, ops)
|
|
324
|
+
});
|
|
325
|
+
await answer(change, options);
|
|
326
|
+
};
|
|
327
|
+
const download = async (file, options) => {
|
|
328
|
+
const content = (bytes) => ({
|
|
329
|
+
bytes,
|
|
330
|
+
name: displayNameOf(file),
|
|
331
|
+
mimeType: file.mimeType
|
|
332
|
+
});
|
|
333
|
+
if (file.home.kind === "page") return content(await annotationsFor("attachment.download").downloadResource(file.home.ref, "file", options));
|
|
334
|
+
refuse(DOWNLOAD, "attachment.download", options);
|
|
335
|
+
if (isPending(file)) await ctx.cancellable(options?.signal, ctx.changes.whenSettled());
|
|
336
|
+
try {
|
|
337
|
+
return content(await ctx.cancellable(options?.signal, ctx.doc.attachments.download(file.home.ref)));
|
|
338
|
+
} catch (error) {
|
|
339
|
+
throw toPluginError("attachment", error);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const reveal = (file) => {
|
|
343
|
+
if (file.home.kind !== "page") return;
|
|
344
|
+
const annotation = annotations();
|
|
345
|
+
const record = annotation?.get(file.home.ref);
|
|
346
|
+
if (!annotation || !record) return;
|
|
347
|
+
annotation.selection.set([record.ref]);
|
|
348
|
+
ctx.tryGet(StageToken)?.reveal(record.ref.page, {
|
|
349
|
+
rect: record.rect,
|
|
350
|
+
anchor: {
|
|
351
|
+
x: "center",
|
|
352
|
+
y: .35
|
|
353
|
+
},
|
|
354
|
+
behavior: "smooth"
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
return { api: {
|
|
358
|
+
list,
|
|
359
|
+
get,
|
|
360
|
+
getSelected,
|
|
361
|
+
select: (files) => ctx.state.update(selected, files.map((file) => file.id)),
|
|
362
|
+
isPending,
|
|
363
|
+
getStatus: files.getStatus,
|
|
364
|
+
ensureLoaded: (options) => ctx.cancellable(options?.signal, files.ensureLoaded()),
|
|
365
|
+
refresh: (options) => ctx.cancellable(options?.signal, files.refresh()),
|
|
366
|
+
canDownload: () => ctx.allows(DOWNLOAD),
|
|
367
|
+
canAdd: () => ctx.allows(MODIFY),
|
|
368
|
+
canRemove: (file) => file.home.kind === "page" ? annotations()?.canDelete(file.home.ref) ?? false : ctx.allows(MODIFY),
|
|
369
|
+
add,
|
|
370
|
+
remove,
|
|
371
|
+
download,
|
|
372
|
+
reveal,
|
|
373
|
+
onChanged: changed.on,
|
|
374
|
+
onResynced: resynced.on
|
|
375
|
+
} };
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/attachment.plugin.ts
|
|
379
|
+
/**
|
|
380
|
+
* The files a document carries. Document-scoped; optionally uses the
|
|
381
|
+
* annotations (a page's files, and removing and saving them) and the stage
|
|
382
|
+
* (going to a page's file).
|
|
383
|
+
*/
|
|
384
|
+
const attachmentPlugin = () => definePlugin({
|
|
385
|
+
id: "attachment",
|
|
386
|
+
token: AttachmentToken,
|
|
387
|
+
scope: "document",
|
|
388
|
+
optional: [AnnotationToken, StageToken],
|
|
389
|
+
state: initialAttachmentState,
|
|
390
|
+
create: createAttachmentController
|
|
391
|
+
});
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/state.ts
|
|
394
|
+
/**
|
|
395
|
+
* The attachment page's State table as code: what `useAttachmentState()`
|
|
396
|
+
* returns, and the same fields in every other framework. Reading the state
|
|
397
|
+
* loads the attachments (`ensure`): the first reader of a document starts
|
|
398
|
+
* the read.
|
|
399
|
+
*/
|
|
400
|
+
const attachmentState = defineState(AttachmentToken, {
|
|
401
|
+
read: (attachment) => ({
|
|
402
|
+
files: attachment.list(),
|
|
403
|
+
selected: attachment.getSelected(),
|
|
404
|
+
status: attachment.getStatus(),
|
|
405
|
+
canAdd: attachment.canAdd(),
|
|
406
|
+
canDownload: attachment.canDownload()
|
|
407
|
+
}),
|
|
408
|
+
empty: {
|
|
409
|
+
files: [],
|
|
410
|
+
selected: [],
|
|
411
|
+
status: "idle",
|
|
412
|
+
canAdd: false,
|
|
413
|
+
canDownload: false
|
|
414
|
+
},
|
|
415
|
+
ensure: (attachment) => attachment.ensureLoaded()
|
|
416
|
+
});
|
|
417
|
+
//#endregion
|
|
418
|
+
export { AttachmentToken, attachmentPlugin, attachmentState, displayNameOf };
|
|
419
|
+
|
|
420
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/lib/attachment-plugin.ts","../src/lib/manifest.ts","../src/lib/index.ts"],"sourcesContent":["import { BasePlugin, PluginRegistry } from '@embedpdf/core';\n\nimport { AttachmentCapability, AttachmentPluginConfig, AttachmentScope } from './types';\nimport {\n PdfAttachmentObject,\n PdfErrorCode,\n PdfErrorReason,\n PdfTaskHelper,\n Task,\n} from '@embedpdf/models';\n\nexport class AttachmentPlugin extends BasePlugin<AttachmentPluginConfig, AttachmentCapability> {\n static readonly id = 'attachment' as const;\n\n constructor(id: string, registry: PluginRegistry) {\n super(id, registry);\n }\n\n async initialize(_: AttachmentPluginConfig): Promise<void> {}\n\n // ─────────────────────────────────────────────────────────\n // Capability\n // ─────────────────────────────────────────────────────────\n\n protected buildCapability(): AttachmentCapability {\n return {\n // Active document operations\n getAttachments: () => this.getAttachments(),\n downloadAttachment: (attachment) => this.downloadAttachment(attachment),\n\n // Document-scoped operations\n forDocument: (documentId: string) => this.createAttachmentScope(documentId),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Scoping\n // ─────────────────────────────────────────────────────────\n\n private createAttachmentScope(documentId: string): AttachmentScope {\n return {\n getAttachments: () => this.getAttachments(documentId),\n downloadAttachment: (attachment) => this.downloadAttachment(attachment, documentId),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Core Operations\n // ─────────────────────────────────────────────────────────\n\n private downloadAttachment(\n attachment: PdfAttachmentObject,\n documentId?: string,\n ): Task<ArrayBuffer, PdfErrorReason> {\n const id = documentId ?? this.getActiveDocumentId();\n const coreDoc = this.coreState.core.documents[id];\n\n if (!coreDoc?.document) {\n return PdfTaskHelper.reject({\n code: PdfErrorCode.NotFound,\n message: `Document ${id} not found`,\n });\n }\n\n return this.engine.readAttachmentContent(coreDoc.document, attachment);\n }\n\n private getAttachments(documentId?: string): Task<PdfAttachmentObject[], PdfErrorReason> {\n const id = documentId ?? this.getActiveDocumentId();\n const coreDoc = this.coreState.core.documents[id];\n\n if (!coreDoc?.document) {\n return PdfTaskHelper.reject({\n code: PdfErrorCode.NotFound,\n message: `Document ${id} not found`,\n });\n }\n\n return this.engine.getAttachments(coreDoc.document);\n }\n}\n","import { PluginManifest } from '@embedpdf/core';\nimport { AttachmentPluginConfig } from './types';\n\nexport const ATTACHMENT_PLUGIN_ID = 'attachment';\n\nexport const manifest: PluginManifest<AttachmentPluginConfig> = {\n id: ATTACHMENT_PLUGIN_ID,\n name: 'Attachment Plugin',\n version: '1.0.0',\n provides: ['attachment'],\n requires: [],\n optional: [],\n defaultConfig: {},\n};\n","import { PluginPackage } from '@embedpdf/core';\n\nimport { AttachmentPlugin } from './attachment-plugin';\nimport { manifest, ATTACHMENT_PLUGIN_ID } from './manifest';\nimport { AttachmentPluginConfig } from './types';\n\nexport const AttachmentPluginPackage: PluginPackage<AttachmentPlugin, AttachmentPluginConfig> = {\n manifest,\n create: (registry) => new AttachmentPlugin(ATTACHMENT_PLUGIN_ID, registry),\n reducer: () => {},\n initialState: {},\n};\n\nexport * from './attachment-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":[],"mappings":";;AAWO,MAAM,oBAAN,MAAM,0BAAyB,WAAyD;AAAA,EAG7F,YAAY,IAAY,UAA0B;AAChD,UAAM,IAAI,QAAQ;AAAA,EACpB;AAAA,EAEA,MAAM,WAAW,GAA0C;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA,EAMlD,kBAAwC;AAChD,WAAO;AAAA;AAAA,MAEL,gBAAgB,MAAM,KAAK,eAAA;AAAA,MAC3B,oBAAoB,CAAC,eAAe,KAAK,mBAAmB,UAAU;AAAA;AAAA,MAGtE,aAAa,CAAC,eAAuB,KAAK,sBAAsB,UAAU;AAAA,IAAA;AAAA,EAE9E;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAAsB,YAAqC;AACjE,WAAO;AAAA,MACL,gBAAgB,MAAM,KAAK,eAAe,UAAU;AAAA,MACpD,oBAAoB,CAAC,eAAe,KAAK,mBAAmB,YAAY,UAAU;AAAA,IAAA;AAAA,EAEtF;AAAA;AAAA;AAAA;AAAA,EAMQ,mBACN,YACA,YACmC;AACnC,UAAM,KAAK,cAAc,KAAK,oBAAA;AAC9B,UAAM,UAAU,KAAK,UAAU,KAAK,UAAU,EAAE;AAEhD,QAAI,EAAC,mCAAS,WAAU;AACtB,aAAO,cAAc,OAAO;AAAA,QAC1B,MAAM,aAAa;AAAA,QACnB,SAAS,YAAY,EAAE;AAAA,MAAA,CACxB;AAAA,IACH;AAEA,WAAO,KAAK,OAAO,sBAAsB,QAAQ,UAAU,UAAU;AAAA,EACvE;AAAA,EAEQ,eAAe,YAAkE;AACvF,UAAM,KAAK,cAAc,KAAK,oBAAA;AAC9B,UAAM,UAAU,KAAK,UAAU,KAAK,UAAU,EAAE;AAEhD,QAAI,EAAC,mCAAS,WAAU;AACtB,aAAO,cAAc,OAAO;AAAA,QAC1B,MAAM,aAAa;AAAA,QACnB,SAAS,YAAY,EAAE;AAAA,MAAA,CACxB;AAAA,IACH;AAEA,WAAO,KAAK,OAAO,eAAe,QAAQ,QAAQ;AAAA,EACpD;AACF;AApEE,kBAAgB,KAAK;AADhB,IAAM,mBAAN;ACRA,MAAM,uBAAuB;AAE7B,MAAM,WAAmD;AAAA,EAC9D,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU,CAAC,YAAY;AAAA,EACvB,UAAU,CAAA;AAAA,EACV,UAAU,CAAA;AAAA,EACV,eAAe,CAAA;AACjB;ACPO,MAAM,0BAAmF;AAAA,EAC9F;AAAA,EACA,QAAQ,CAAC,aAAa,IAAI,iBAAiB,sBAAsB,QAAQ;AAAA,EACzE,SAAS,MAAM;AAAA,EAAC;AAAA,EAChB,cAAc,CAAA;AAChB;"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/model.ts","../src/sync/files.ts","../src/controller.ts","../src/attachment.plugin.ts","../src/state.ts"],"sourcesContent":["/**\n * The attachment model: the document's attachments as the mirror holds\n * them, how an attachment and a page's file become one `AttachedFile`, and\n * the name a new file takes. Pure.\n */\nimport {\n annotationKey,\n type Annotation,\n type AnnotationRef,\n type Attachment,\n type AttachmentRef,\n} from '@embedpdf/engine-core/runtime';\n\nimport type { AttachedFile } from './contract';\n\n/** The session's part: the selected files, by id, in the order they were selected. */\nexport interface AttachmentState {\n readonly selected: readonly string[];\n}\n\nexport const initialAttachmentState = (): AttachmentState => ({ selected: [] });\n\n/** Make these files the selection; the same ids leave the state as it is. */\nexport function selected(state: AttachmentState, ids: readonly string[]): AttachmentState {\n const unique = ids.filter((id, i) => ids.indexOf(id) === i);\n const same =\n unique.length === state.selected.length && unique.every((id, i) => id === state.selected[i]);\n return same ? state : { ...state, selected: unique };\n}\n\n/** The document's attachments, in the order its name tree keeps them; `null` before the first read lands. */\nexport type DocumentFiles = readonly Attachment[] | null;\n\n/** An attachment's id: its key. */\nexport const documentFileId = (ref: AttachmentRef): string => `doc:${ref.key}`;\n\n/** A page file's id: its annotation's key. */\nexport const pageFileId = (ref: AnnotationRef): string => `page:${annotationKey(ref)}`;\n\n/** An attachment as the list shows it. */\nexport function documentFileOf(attachment: Attachment): AttachedFile {\n const { ref, index: _index, ...info } = attachment;\n return { ...info, id: documentFileId(ref), home: { kind: 'document', ref } };\n}\n\n/** A file attachment annotation's file as the list shows it; `null` for another kind, or one without a file. */\nexport function pageFileOf(annotation: Annotation): AttachedFile | null {\n if (annotation.subtype !== 'file-attachment' || !annotation.file) return null;\n return {\n ...annotation.file,\n id: pageFileId(annotation.ref),\n home: { kind: 'page', ref: annotation.ref },\n };\n}\n\n/**\n * `name`, or, when a file has it already, the name with the first number\n * that makes it free: `notes (2).txt`, then `notes (3).txt`. The number goes\n * before the extension, as a file manager puts it.\n */\nexport function freeName(name: string, taken: ReadonlySet<string>): string {\n if (!taken.has(name)) return name;\n const dot = name.lastIndexOf('.');\n const stem = dot > 0 ? name.slice(0, dot) : name;\n const extension = dot > 0 ? name.slice(dot) : '';\n for (let number = 2; ; number++) {\n const candidate = `${stem} (${number})${extension}`;\n if (!taken.has(candidate)) return candidate;\n }\n}\n","/**\n * How the document's attachments change: `foldFiles` applies one confirmed\n * event, the mirror's `fold`; `predictFiles` applies one op of a pending\n * change by the engine's rules, the mirror's `predict`; `undoOf` says what\n * undoing ops looks like. Pure.\n */\nimport type { PredictedOp } from '@embedpdf/core';\nimport {\n toAttachmentRef,\n type Attachment,\n type ChangeOp,\n type DocumentEvent,\n} from '@embedpdf/engine-core/runtime';\n\nimport type { DocumentFiles } from '../model';\n\ntype CreateOp = Extract<ChangeOp, { type: 'attachments.create' }>;\n\n/** The files with each `index` its position, keeping the files whose position held. */\nfunction indexed(files: readonly Attachment[]): readonly Attachment[] {\n return files.map((file, index) => (file.index === index ? file : { ...file, index }));\n}\n\nconst has = (files: readonly Attachment[], key: string): boolean =>\n files.some((file) => file.ref.key === key);\n\n/**\n * The files with `attachment` among them, before the first key that sorts\n * after its own, as the name tree puts a new entry; one with its key gives\n * way to it.\n */\nfunction withFile(files: readonly Attachment[], attachment: Attachment): readonly Attachment[] {\n const key = attachment.ref.key;\n const rest = files.filter((file) => file.ref.key !== key);\n const at = rest.findIndex((file) => file.ref.key > key);\n return indexed(\n at < 0 ? [...rest, attachment] : [...rest.slice(0, at), attachment, ...rest.slice(at)],\n );\n}\n\nfunction withoutFile(files: readonly Attachment[], key: string): readonly Attachment[] {\n return has(files, key) ? indexed(files.filter((file) => file.ref.key !== key)) : files;\n}\n\n/* ── confirmed events ────────────────────────────────────────────────────── */\n\n/** One confirmed event, applied with the record it carries. Before the first load there is nothing to apply it to. */\nexport function foldFiles(files: DocumentFiles, event: DocumentEvent): DocumentFiles {\n if (!files) return files;\n switch (event.type) {\n case 'attachments.created':\n case 'attachments.restored':\n return withFile(files, event.attachment);\n case 'attachments.deleted':\n return event.deleted ? withoutFile(files, event.deleted.key) : files;\n default:\n return files;\n }\n}\n\n/* ── pending changes ─────────────────────────────────────────────────────── */\n\n/** A file a create adds, as it shows until the engine answers: what the op says of it, and its size. */\nfunction predictedFile(op: CreateOp): Attachment {\n const bytes = op.resources.file;\n return {\n ref: toAttachmentRef(op.file.name),\n index: 0,\n name: op.file.name,\n mimeType: op.file.mimeType ?? null,\n description: op.file.description ?? null,\n size: bytes === undefined ? null : bytes instanceof Uint8Array ? bytes.byteLength : bytes.size,\n checksum: null,\n createdAt: null,\n modifiedAt: null,\n };\n}\n\n/**\n * One op of a change this session staged, shown before the engine answers,\n * by the engine's rules: a create of a name a file has, a delete of a file\n * that isn't there, and a restore of a name a file has change nothing.\n */\nexport function predictFiles(files: DocumentFiles, op: PredictedOp): DocumentFiles {\n if (!files) return files;\n switch (op.type) {\n case 'attachments.create':\n return has(files, op.file.name) ? files : withFile(files, predictedFile(op));\n case 'attachments.delete':\n return withoutFile(files, op.ref.key);\n case 'attachments.restore':\n return has(files, op.attachment.ref.key) ? files : withFile(files, op.attachment);\n default:\n return files;\n }\n}\n\n/** What undoing one op looks like, read off the files it applies to. */\nfunction undoOfOp(files: readonly Attachment[], op: ChangeOp): PredictedOp[] {\n switch (op.type) {\n case 'attachments.create':\n return [{ type: 'attachments.delete', ref: toAttachmentRef(op.file.name) }];\n case 'attachments.delete': {\n const attachment = files.find((file) => file.ref.key === op.ref.key);\n return attachment ? [{ type: 'attachments.restore', attachment }] : [];\n }\n default:\n return [];\n }\n}\n\n/**\n * What undoing `ops` looks like, read off the files they apply to, each op\n * on the files the ops before it leave; the last op is undone first.\n */\nexport function undoOf(files: DocumentFiles, ops: readonly ChangeOp[]): PredictedOp[] {\n if (!files) return [];\n const undo: PredictedOp[][] = [];\n let current = files;\n for (const op of ops) {\n undo.push(undoOfOp(current, op));\n current = predictFiles(current, op) ?? current;\n }\n return undo.reverse().flat();\n}\n","import {\n memo,\n PluginError,\n toPluginError,\n type DocCapability,\n type OperationOptions,\n type PendingChange,\n type PluginContext,\n} from '@embedpdf/core';\nimport type {\n Annotation,\n Attachment,\n AttachmentContent,\n AttachmentFileSource,\n ChangeItem,\n ChangeOp,\n ChangeResult,\n DocumentEvent,\n} from '@embedpdf/engine-core/runtime';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport type {\n AttachedFile,\n AttachmentCapability,\n AttachmentsChangedEvent,\n AttachmentsResyncedEvent,\n} from './contract';\nimport {\n documentFileOf,\n freeName,\n pageFileOf,\n selected,\n type AttachmentState,\n type DocumentFiles,\n} from './model';\nimport { displayNameOf } from './names';\nimport { foldFiles, predictFiles, undoOf } from './sync/files';\n\nconst MODIFY: DocCapability = 'doc.attachments.modify';\nconst DOWNLOAD: DocCapability = 'doc.download';\n\nconst NO_FILES: readonly AttachedFile[] = Object.freeze([]);\n\n/**\n * The attachments controller. The document's attachments are a mirror,\n * read when something asks for them (`ensureLoaded()`, what\n * `attachmentState` does), and every read shows its `view()`: the engine's\n * attachments with this session's pending adds and removes on top. A page's\n * file is a file attachment annotation, read from the annotation plugin, so\n * the list shows both homes with no read of its own. Each add or remove of\n * an attachment is one change on the document's queue: it shows at once, is\n * one step of the history, and undoing it shows the files as they were.\n */\nexport function createAttachmentController(ctx: PluginContext<AttachmentState>) {\n const changed = ctx.events.source<AttachmentsChangedEvent>();\n const resynced = ctx.events.source<AttachmentsResyncedEvent>();\n\n // One `AttachedFile` per record, so a list row keeps its object while its file is unchanged.\n const documentFiles = new WeakMap<Attachment, AttachedFile>();\n const fileOfAttachment = (attachment: Attachment): AttachedFile => {\n let file = documentFiles.get(attachment);\n if (!file) documentFiles.set(attachment, (file = documentFileOf(attachment)));\n return file;\n };\n const pageFiles = new WeakMap<Annotation, AttachedFile | null>();\n const fileOfAnnotation = (annotation: Annotation): AttachedFile | null => {\n let file = pageFiles.get(annotation);\n if (file === undefined) pageFiles.set(annotation, (file = pageFileOf(annotation)));\n return file;\n };\n\n /** The fact a confirmed attachment event reports, or `null` for any other event. */\n const factOf = (\n event: DocumentEvent,\n previous: DocumentFiles,\n ): Omit<AttachmentsChangedEvent, 'origin'> | null => {\n switch (event.type) {\n case 'attachments.created':\n return { type: 'added', files: [fileOfAttachment(event.attachment)] };\n case 'attachments.restored':\n return { type: 'restored', files: [fileOfAttachment(event.attachment)] };\n case 'attachments.deleted': {\n const gone = previous?.find((file) => file.ref.key === event.deleted?.key);\n return { type: 'removed', files: gone ? [fileOfAttachment(gone)] : [] };\n }\n default:\n return null;\n }\n };\n\n const files = ctx.mirror<DocumentFiles>({\n name: 'attachments',\n onDemand: true,\n initial: () => null,\n load: async (doc) => ({ value: (await doc.attachments.list()).attachments }),\n fold: foldFiles,\n predict: predictFiles,\n changed: ({ cause, event, previous, next }) => {\n if (!next) return;\n if (cause === 'load') {\n resynced.emit({ files: next.map(fileOfAttachment) });\n return;\n }\n const fact = event && 'origin' in event ? factOf(event, previous) : null;\n if (fact && event && 'origin' in event) changed.emit({ ...fact, origin: event.origin });\n },\n });\n\n const annotations = () => ctx.tryGet(AnnotationToken);\n\n /* ── reads ─────────────────────────────────────────────────────────────── */\n\n const list = memo(\n () => [files.view(), annotations()?.list() ?? null] as const,\n (attachments, annots) => {\n const all = [\n ...(attachments ?? []).map(fileOfAttachment),\n ...(annots ?? []).flatMap((annotation) => {\n const file = fileOfAnnotation(annotation);\n return file ? [file] : [];\n }),\n ];\n return all.length > 0 ? all : NO_FILES;\n },\n );\n const get = (id: string): AttachedFile | null => list().find((file) => file.id === id) ?? null;\n const getSelected = memo(\n () => [list(), ctx.state.get().selected] as const,\n (files, ids) => {\n if (ids.length === 0) return NO_FILES;\n const byId = new Map(files.map((file) => [file.id, file]));\n const chosen = ids.flatMap((id) => {\n const file = byId.get(id);\n return file ? [file] : [];\n });\n return chosen.length > 0 ? chosen : NO_FILES;\n },\n );\n\n const isPending = (file: AttachedFile): boolean => {\n if (file.home.kind === 'page') return annotations()?.isPending(file.home.ref) ?? false;\n const key = file.home.ref.key;\n return !(files.get() ?? []).some((confirmed) => confirmed.ref.key === key);\n };\n\n /* ── writes ────────────────────────────────────────────────────────────── */\n\n /** Refuse before anything starts: without the permission, or once the caller cancelled. */\n const refuse = (permission: DocCapability, operation: string, options?: OperationOptions) => {\n ctx.assertAllowed(permission, operation);\n if (options?.signal?.aborted) {\n throw new PluginError('operation-cancelled', 'attachment', `${operation} was cancelled`);\n }\n };\n\n /** The engine's answer to a staged change. */\n const answer = async (\n change: PendingChange,\n options?: OperationOptions,\n ): Promise<ChangeResult> => {\n try {\n return await ctx.cancellable(options?.signal, change.result);\n } catch (error) {\n throw toPluginError('attachment', error);\n }\n };\n\n /** The annotation plugin, for a page's file: the list shows none without it. */\n const annotationsFor = (operation: string) => {\n const annotation = annotations();\n if (!annotation) {\n throw new PluginError('not-found', 'attachment', `${operation}: no annotation plugin`);\n }\n return annotation;\n };\n\n /** The op that attaches `source` under a name no file has (`taken`, which it joins). */\n const createOp = (source: AttachmentFileSource, taken: Set<string>): ChangeOp => {\n const { data, description } = source;\n const blob = typeof Blob !== 'undefined' && data instanceof Blob ? data : null;\n const given = source.name ?? (blob && 'name' in blob ? String(blob.name) : '');\n if (!given) {\n throw new PluginError(\n 'invalid-input',\n 'attachment',\n 'attachment.add: a file needs a name: pass { data, name } or a File',\n );\n }\n const name = freeName(given, taken);\n taken.add(name);\n const mimeType = source.mimeType ?? (blob?.type || undefined);\n return {\n type: 'attachments.create',\n file: {\n name,\n ...(mimeType !== undefined ? { mimeType } : {}),\n ...(description !== undefined ? { description } : {}),\n },\n resources: { file: data instanceof ArrayBuffer ? new Uint8Array(data) : data },\n };\n };\n\n const add: AttachmentCapability['add'] = async (sources, options) => {\n refuse(MODIFY, 'attachment.add', options);\n if (sources.length === 0) return NO_FILES;\n // The names in the document decide the free ones, so they are read first.\n await ctx.cancellable(options?.signal, files.ensureLoaded());\n const taken = new Set((files.view() ?? []).map((file) => file.ref.key));\n const ops = sources.map((source) => createOp(source, taken));\n const change = ctx.changes.stage({\n label: { key: 'attachment.add', count: ops.length },\n ops,\n undo: undoOf(files.view(), ops),\n });\n const result = await answer(change, options);\n return result.items.flatMap((item: ChangeItem) =>\n item.type === 'attachments.create' ? [fileOfAttachment(item.attachment)] : [],\n );\n };\n\n const remove: AttachmentCapability['remove'] = async (file, options) => {\n if (file.home.kind === 'page') {\n return annotationsFor('attachment.remove').delete(file.home.ref, options);\n }\n refuse(MODIFY, 'attachment.remove', options);\n const { ref } = file.home;\n const view = files.view();\n if (view && !view.some((attachment) => attachment.ref.key === ref.key)) {\n throw new PluginError(\n 'not-found',\n 'attachment',\n `attachment.remove: the document has no attachment '${ref.key}'`,\n );\n }\n const ops: ChangeOp[] = [{ type: 'attachments.delete', ref }];\n const change = ctx.changes.stage({\n label: { key: 'attachment.remove' },\n ops,\n undo: undoOf(view, ops),\n });\n await answer(change, options);\n };\n\n const download: AttachmentCapability['download'] = async (file, options) => {\n const content = (bytes: Uint8Array): AttachmentContent => ({\n bytes,\n name: displayNameOf(file),\n mimeType: file.mimeType,\n });\n if (file.home.kind === 'page') {\n const annotation = annotationsFor('attachment.download');\n return content(await annotation.downloadResource(file.home.ref, 'file', options));\n }\n refuse(DOWNLOAD, 'attachment.download', options);\n // A file this session is still adding is read once the engine has it.\n if (isPending(file)) await ctx.cancellable(options?.signal, ctx.changes.whenSettled());\n try {\n return content(\n await ctx.cancellable(options?.signal, ctx.doc.attachments.download(file.home.ref)),\n );\n } catch (error) {\n throw toPluginError('attachment', error);\n }\n };\n\n const reveal = (file: AttachedFile): void => {\n if (file.home.kind !== 'page') return;\n const annotation = annotations();\n const record = annotation?.get(file.home.ref);\n if (!annotation || !record) return;\n annotation.selection.set([record.ref]);\n // The file lands a third down the view, as a comment's annotation does.\n ctx.tryGet(StageToken)?.reveal(record.ref.page, {\n rect: record.rect,\n anchor: { x: 'center', y: 0.35 },\n behavior: 'smooth',\n });\n };\n\n const api: AttachmentCapability = {\n list,\n get,\n getSelected,\n select: (files) => ctx.state.update(selected, files.map((file) => file.id)),\n isPending,\n getStatus: files.getStatus,\n ensureLoaded: (options) => ctx.cancellable(options?.signal, files.ensureLoaded()),\n refresh: (options) => ctx.cancellable(options?.signal, files.refresh()),\n\n canDownload: () => ctx.allows(DOWNLOAD),\n canAdd: () => ctx.allows(MODIFY),\n canRemove: (file) =>\n file.home.kind === 'page'\n ? (annotations()?.canDelete(file.home.ref) ?? false)\n : ctx.allows(MODIFY),\n\n add,\n remove,\n download,\n reveal,\n\n onChanged: changed.on,\n onResynced: resynced.on,\n };\n\n return { api };\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract';\nimport { StageToken } from '@embedpdf/plugin-stage/contract';\n\nimport { AttachmentToken } from './contract';\nimport { createAttachmentController } from './controller';\nimport { initialAttachmentState } from './model';\n\n/**\n * The files a document carries. Document-scoped; optionally uses the\n * annotations (a page's files, and removing and saving them) and the stage\n * (going to a page's file).\n */\nexport const attachmentPlugin = () =>\n definePlugin({\n id: 'attachment',\n token: AttachmentToken,\n scope: 'document',\n optional: [AnnotationToken, StageToken],\n state: initialAttachmentState,\n create: createAttachmentController,\n });\n","/**\n * The attachment page's State table as code: what `useAttachmentState()`\n * returns, and the same fields in every other framework. Reading the state\n * loads the attachments (`ensure`): the first reader of a document starts\n * the read.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { AttachmentToken } from './contract';\n\nexport const attachmentState = defineState(AttachmentToken, {\n read: (attachment) => ({\n files: attachment.list(),\n selected: attachment.getSelected(),\n status: attachment.getStatus(),\n canAdd: attachment.canAdd(),\n canDownload: attachment.canDownload(),\n }),\n empty: {\n files: [],\n selected: [],\n status: 'idle',\n canAdd: false,\n canDownload: false,\n },\n ensure: (attachment) => attachment.ensureLoaded(),\n});\n"],"mappings":"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"}
|