@embedpdf/plugin-annotation 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,89 +5,143 @@ var manifest = {
5
5
  name: "Annotation Plugin",
6
6
  version: "1.0.0",
7
7
  provides: ["annotation"],
8
- requires: [],
9
- optional: [],
8
+ requires: ["interaction-manager", "selection"],
9
+ optional: ["history"],
10
10
  defaultConfig: {
11
- enabled: true
11
+ enabled: true,
12
+ autoCommit: true
12
13
  }
13
14
  };
14
15
 
15
16
  // src/lib/annotation-plugin.ts
16
- import { BasePlugin, createBehaviorEmitter, SET_DOCUMENT } from "@embedpdf/core";
17
-
18
- // ../models/dist/index.js
19
- var PdfAnnotationSubtype = /* @__PURE__ */ ((PdfAnnotationSubtype2) => {
20
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNKNOWN"] = 0] = "UNKNOWN";
21
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TEXT"] = 1] = "TEXT";
22
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINK"] = 2] = "LINK";
23
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FREETEXT"] = 3] = "FREETEXT";
24
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINE"] = 4] = "LINE";
25
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUARE"] = 5] = "SQUARE";
26
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CIRCLE"] = 6] = "CIRCLE";
27
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYGON"] = 7] = "POLYGON";
28
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYLINE"] = 8] = "POLYLINE";
29
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["HIGHLIGHT"] = 9] = "HIGHLIGHT";
30
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNDERLINE"] = 10] = "UNDERLINE";
31
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUIGGLY"] = 11] = "SQUIGGLY";
32
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STRIKEOUT"] = 12] = "STRIKEOUT";
33
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STAMP"] = 13] = "STAMP";
34
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CARET"] = 14] = "CARET";
35
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["INK"] = 15] = "INK";
36
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POPUP"] = 16] = "POPUP";
37
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FILEATTACHMENT"] = 17] = "FILEATTACHMENT";
38
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SOUND"] = 18] = "SOUND";
39
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["MOVIE"] = 19] = "MOVIE";
40
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WIDGET"] = 20] = "WIDGET";
41
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SCREEN"] = 21] = "SCREEN";
42
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["PRINTERMARK"] = 22] = "PRINTERMARK";
43
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TRAPNET"] = 23] = "TRAPNET";
44
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WATERMARK"] = 24] = "WATERMARK";
45
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["THREED"] = 25] = "THREED";
46
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["RICHMEDIA"] = 26] = "RICHMEDIA";
47
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["XFAWIDGET"] = 27] = "XFAWIDGET";
48
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["REDACT"] = 28] = "REDACT";
49
- return PdfAnnotationSubtype2;
50
- })(PdfAnnotationSubtype || {});
51
- function ignore() {
52
- }
17
+ import {
18
+ BasePlugin,
19
+ createBehaviorEmitter,
20
+ enumEntries,
21
+ SET_DOCUMENT
22
+ } from "@embedpdf/core";
23
+ import {
24
+ ignore,
25
+ Task,
26
+ PdfAnnotationSubtype,
27
+ PdfTaskHelper,
28
+ PdfErrorCode
29
+ } from "@embedpdf/models";
53
30
 
54
31
  // src/lib/actions.ts
55
- var SET_ANNOTATIONS = "SET_ANNOTATIONS";
56
- var SELECT_ANNOTATION = "SELECT_ANNOTATION";
57
- var DESELECT_ANNOTATION = "DESELECT_ANNOTATION";
58
- var SET_ANNOTATION_MODE = "SET_ANNOTATION_MODE";
59
- var UPDATE_ANNOTATION_COLOR = "UPDATE_ANNOTATION_COLOR";
60
- function setAnnotations(payload) {
61
- return { type: SET_ANNOTATIONS, payload };
62
- }
63
- function selectAnnotation(pageIndex, annotationId, annotation) {
64
- return {
65
- type: SELECT_ANNOTATION,
66
- payload: { pageIndex, annotationId, annotation }
67
- };
68
- }
69
- function deselectAnnotation() {
70
- return { type: DESELECT_ANNOTATION };
71
- }
72
- function setAnnotationMode(mode) {
73
- return {
74
- type: SET_ANNOTATION_MODE,
75
- payload: mode
76
- };
77
- }
78
- function updateAnnotationColor(pageIndex, annotationId, color) {
79
- return {
80
- type: UPDATE_ANNOTATION_COLOR,
81
- payload: { pageIndex, annotationId, color }
82
- };
83
- }
32
+ var SET_ANNOTATIONS = "ANNOTATION/SET_ANNOTATIONS";
33
+ var REINDEX_PAGE_ANNOTATIONS = "ANNOTATION/REINDEX_PAGE";
34
+ var SELECT_ANNOTATION = "ANNOTATION/SELECT_ANNOTATION";
35
+ var DESELECT_ANNOTATION = "ANNOTATION/DESELECT_ANNOTATION";
36
+ var SET_ANNOTATION_MODE = "ANNOTATION/SET_ANNOTATION_MODE";
37
+ var UPDATE_TOOL_DEFAULTS = "ANNOTATION/UPDATE_TOOL_DEFAULTS";
38
+ var ADD_COLOR_PRESET = "ANNOTATION/ADD_COLOR_PRESET";
39
+ var CREATE_ANNOTATION = "ANNOTATION/CREATE_ANNOTATION";
40
+ var PATCH_ANNOTATION = "ANNOTATION/PATCH_ANNOTATION";
41
+ var DELETE_ANNOTATION = "ANNOTATION/DELETE_ANNOTATION";
42
+ var COMMIT_PENDING_CHANGES = "ANNOTATION/COMMIT";
43
+ var STORE_PDF_ID = "ANNOTATION/STORE_PDF_ID";
44
+ var PURGE_ANNOTATION = "ANNOTATION/PURGE_ANNOTATION";
45
+ var setAnnotations = (p) => ({
46
+ type: SET_ANNOTATIONS,
47
+ payload: p
48
+ });
49
+ var reindexPageAnnotations = (pageIndex) => ({
50
+ type: REINDEX_PAGE_ANNOTATIONS,
51
+ payload: { pageIndex }
52
+ });
53
+ var selectAnnotation = (pageIndex, localId) => ({
54
+ type: SELECT_ANNOTATION,
55
+ payload: { pageIndex, localId }
56
+ });
57
+ var deselectAnnotation = () => ({ type: DESELECT_ANNOTATION });
58
+ var setAnnotationMode = (m) => ({
59
+ type: SET_ANNOTATION_MODE,
60
+ payload: m
61
+ });
62
+ var updateToolDefaults = (subtype, patch) => ({ type: UPDATE_TOOL_DEFAULTS, payload: { subtype, patch } });
63
+ var addColorPreset = (c) => ({
64
+ type: ADD_COLOR_PRESET,
65
+ payload: c
66
+ });
67
+ var createAnnotation = (pageIndex, localId, annotation) => ({
68
+ type: CREATE_ANNOTATION,
69
+ payload: { pageIndex, localId, annotation }
70
+ });
71
+ var patchAnnotation = (pageIndex, localId, patch) => ({
72
+ type: PATCH_ANNOTATION,
73
+ payload: { pageIndex, localId, patch }
74
+ });
75
+ var deleteAnnotation = (pageIndex, localId) => ({
76
+ type: DELETE_ANNOTATION,
77
+ payload: { pageIndex, localId }
78
+ });
79
+ var commitPendingChanges = () => ({ type: COMMIT_PENDING_CHANGES });
80
+ var storePdfId = (uid, pdfId) => ({
81
+ type: STORE_PDF_ID,
82
+ payload: { uid, pdfId }
83
+ });
84
+ var purgeAnnotation = (uid) => ({
85
+ type: PURGE_ANNOTATION,
86
+ payload: { uid }
87
+ });
88
+
89
+ // src/lib/utils.ts
90
+ var makeUid = (pageIndex, localId) => `p${pageIndex}#${localId}`;
91
+ var parseUid = (uid) => {
92
+ const [pg, rest] = uid.slice(1).split("#");
93
+ return { pageIndex: Number(pg), localId: Number(rest) };
94
+ };
95
+
96
+ // src/lib/selectors.ts
97
+ var makeUid2 = (page, id) => `p${page}#${id}`;
98
+ var getAnnotationsByPageIndex = (s, page) => (s.pages[page] ?? []).map((uid) => s.byUid[uid]);
99
+ var getAnnotations = (s) => {
100
+ const out = {};
101
+ for (const p of Object.keys(s.pages).map(Number)) out[p] = getAnnotationsByPageIndex(s, p);
102
+ return out;
103
+ };
104
+ var getSelectedAnnotation = (s) => s.selectedUid ? s.byUid[s.selectedUid] : null;
105
+ var getSelectedAnnotationWithPageIndex = (s) => {
106
+ if (!s.selectedUid) return null;
107
+ const { pageIndex, localId } = parseUid(s.selectedUid);
108
+ return { pageIndex, localId, annotation: s.byUid[s.selectedUid].object };
109
+ };
110
+ var getSelectedAnnotationByPageIndex = (s, pageIndex) => {
111
+ if (!s.selectedUid) return null;
112
+ const pageUids = s.pages[pageIndex] ?? [];
113
+ if (pageUids.includes(s.selectedUid)) {
114
+ return s.byUid[s.selectedUid];
115
+ }
116
+ return null;
117
+ };
118
+ var isInAnnotationMode = (s) => s.annotationMode !== null;
119
+ var getSelectedAnnotationMode = (s) => s.annotationMode;
120
+ var isAnnotationSelected = (s, page, id) => s.selectedUid === makeUid2(page, id);
84
121
 
85
122
  // src/lib/annotation-plugin.ts
86
123
  var AnnotationPlugin = class extends BasePlugin {
87
- constructor(id, registry, engine) {
124
+ constructor(id, registry, engine, config) {
88
125
  super(id, registry);
126
+ this.ANNOTATION_HISTORY_TOPIC = "annotations";
89
127
  this.state$ = createBehaviorEmitter();
128
+ /** Map <subtype> → <modeId>. Filled once in `initialize()`. */
129
+ this.modeBySubtype = /* @__PURE__ */ new Map();
130
+ /** The inverse map for quick lookup in onModeChange(). */
131
+ this.subtypeByMode = /* @__PURE__ */ new Map();
132
+ this.modeChange$ = createBehaviorEmitter();
133
+ this.activeTool$ = createBehaviorEmitter({
134
+ mode: null,
135
+ defaults: null
136
+ });
90
137
  this.engine = engine;
138
+ this.config = config;
139
+ const selection = registry.getPlugin("selection");
140
+ this.selection = selection?.provides() ?? null;
141
+ const history = registry.getPlugin("history");
142
+ this.history = history?.provides() ?? null;
143
+ const interactionManager = registry.getPlugin("interaction-manager");
144
+ this.interactionManager = interactionManager?.provides() ?? null;
91
145
  this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {
92
146
  const doc = state.core.document;
93
147
  if (doc) {
@@ -96,29 +150,119 @@ var AnnotationPlugin = class extends BasePlugin {
96
150
  });
97
151
  }
98
152
  async initialize() {
153
+ for (const [subtype, defaults] of enumEntries(this.state.toolDefaults)) {
154
+ this.registerTool(subtype, defaults);
155
+ }
156
+ this.history?.onHistoryChange((topic) => {
157
+ if (topic === this.ANNOTATION_HISTORY_TOPIC && this.config.autoCommit !== false) {
158
+ this.commit();
159
+ }
160
+ });
161
+ this.interactionManager?.onModeChange((s) => {
162
+ const newSubtype = this.subtypeByMode.get(s.activeMode) ?? null;
163
+ if (newSubtype !== this.state.annotationMode) {
164
+ this.dispatch(setAnnotationMode(newSubtype));
165
+ this.modeChange$.emit(newSubtype);
166
+ }
167
+ });
168
+ this.selection?.onEndSelection(() => {
169
+ if (!this.state.annotationMode) return;
170
+ const formattedSelection = this.selection?.getFormattedSelection();
171
+ if (!formattedSelection) return;
172
+ for (const selection of formattedSelection) {
173
+ const rect = selection.rect;
174
+ const segmentRects = selection.segmentRects;
175
+ const type = this.state.annotationMode;
176
+ const color = this.state.toolDefaults[type].color;
177
+ const opacity = this.state.toolDefaults[type].opacity;
178
+ this.createAnnotation(selection.pageIndex, {
179
+ type,
180
+ rect,
181
+ segmentRects,
182
+ color,
183
+ opacity,
184
+ pageIndex: selection.pageIndex,
185
+ id: Date.now() + Math.random()
186
+ });
187
+ }
188
+ this.selection?.clear();
189
+ });
190
+ }
191
+ registerTool(subtype, defaults) {
192
+ const modeId = defaults.interaction.mode;
193
+ const interactionMode = {
194
+ id: modeId,
195
+ scope: "page",
196
+ exclusive: defaults.interaction.exclusive,
197
+ cursor: defaults.interaction.cursor
198
+ };
199
+ this.interactionManager?.registerMode(interactionMode);
200
+ if (defaults.textSelection) {
201
+ this.selection?.enableForMode(modeId);
202
+ }
203
+ this.modeBySubtype.set(subtype, modeId);
204
+ this.subtypeByMode.set(modeId, subtype);
99
205
  }
100
206
  buildCapability() {
101
207
  return {
102
208
  getPageAnnotations: (options) => {
103
209
  return this.getPageAnnotations(options);
104
210
  },
211
+ getSelectedAnnotation: () => {
212
+ return getSelectedAnnotation(this.state);
213
+ },
105
214
  selectAnnotation: (pageIndex, annotationId) => {
106
215
  this.selectAnnotation(pageIndex, annotationId);
107
216
  },
108
217
  deselectAnnotation: () => {
109
218
  this.dispatch(deselectAnnotation());
110
219
  },
111
- updateAnnotationColor: async (color) => {
112
- return this.updateSelectedAnnotationColor(color);
220
+ getAnnotationMode: () => {
221
+ return this.state.annotationMode;
222
+ },
223
+ setAnnotationMode: (subtype) => {
224
+ if (subtype === this.state.annotationMode) return;
225
+ if (subtype) {
226
+ const mode = this.modeBySubtype.get(subtype);
227
+ if (!mode) throw new Error(`Mode missing for subtype ${subtype}`);
228
+ this.interactionManager?.activate(mode);
229
+ } else {
230
+ this.interactionManager?.activate("default");
231
+ }
232
+ },
233
+ getToolDefaults: (subtype) => {
234
+ const defaults = this.state.toolDefaults[subtype];
235
+ if (!defaults) {
236
+ throw new Error(`No defaults found for subtype: ${subtype}`);
237
+ }
238
+ return defaults;
113
239
  },
114
- setAnnotationMode: (mode) => {
115
- this.dispatch(setAnnotationMode(mode));
240
+ setToolDefaults: (subtype, patch) => {
241
+ this.dispatch(updateToolDefaults(subtype, patch));
116
242
  },
117
- onStateChange: this.state$.on
243
+ getColorPresets: () => [...this.state.colorPresets],
244
+ addColorPreset: (color) => this.dispatch(addColorPreset(color)),
245
+ createAnnotation: (pageIndex, annotation) => this.createAnnotation(pageIndex, annotation),
246
+ updateAnnotation: (pageIndex, localId, patch) => this.updateAnnotation(pageIndex, localId, patch),
247
+ deleteAnnotation: (pageIndex, localId) => this.deleteAnnotation(pageIndex, localId),
248
+ onStateChange: this.state$.on,
249
+ onModeChange: this.modeChange$.on,
250
+ onActiveToolChange: this.activeTool$.on,
251
+ commit: () => this.commit()
118
252
  };
119
253
  }
120
- onStoreUpdated(_prevState, newState) {
121
- this.state$.emit(newState);
254
+ emitActiveTool(state) {
255
+ const mode = state.annotationMode;
256
+ this.activeTool$.emit({
257
+ mode,
258
+ defaults: mode ? state.toolDefaults[mode] : null
259
+ });
260
+ }
261
+ onStoreUpdated(prev, next) {
262
+ this.state$.emit(next);
263
+ if (prev.annotationMode !== next.annotationMode || prev.toolDefaults[prev.annotationMode ?? PdfAnnotationSubtype.HIGHLIGHT] !== next.toolDefaults[next.annotationMode ?? PdfAnnotationSubtype.HIGHLIGHT]) {
264
+ this.emitActiveTool(next);
265
+ }
122
266
  }
123
267
  getAllAnnotations(doc) {
124
268
  const task = this.engine.getAllAnnotations(doc);
@@ -128,114 +272,328 @@ var AnnotationPlugin = class extends BasePlugin {
128
272
  const { pageIndex } = options;
129
273
  const doc = this.coreState.core.document;
130
274
  if (!doc) {
131
- throw new Error("document does not open");
275
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
132
276
  }
133
277
  const page = doc.pages.find((p) => p.index === pageIndex);
134
278
  if (!page) {
135
- throw new Error("page does not open");
279
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Page not found" });
136
280
  }
137
281
  return this.engine.getPageAnnotations(doc, page);
138
282
  }
139
283
  selectAnnotation(pageIndex, annotationId) {
140
- const pageAnnotations = this.state.annotations[pageIndex];
141
- if (!pageAnnotations) {
284
+ this.dispatch(selectAnnotation(pageIndex, annotationId));
285
+ }
286
+ createAnnotation(pageIndex, annotation) {
287
+ const localId = annotation.id;
288
+ const execute = () => this.dispatch(createAnnotation(pageIndex, localId, annotation));
289
+ if (!this.history) {
290
+ execute();
291
+ if (this.config.autoCommit) this.commit();
142
292
  return;
143
293
  }
144
- const annotation = pageAnnotations.find((a) => a.id === annotationId);
145
- if (annotation) {
146
- this.dispatch(selectAnnotation(pageIndex, annotationId, annotation));
147
- }
294
+ const command = {
295
+ execute,
296
+ undo: () => {
297
+ this.dispatch(deselectAnnotation());
298
+ this.dispatch(deleteAnnotation(pageIndex, localId));
299
+ }
300
+ };
301
+ this.history.register(command, this.ANNOTATION_HISTORY_TOPIC);
148
302
  }
149
- async updateSelectedAnnotationColor(color) {
150
- const selected = this.state.selectedAnnotation;
151
- if (!selected) {
152
- return false;
303
+ updateAnnotation(pageIndex, localId, patch) {
304
+ if (!this.history) {
305
+ this.dispatch(patchAnnotation(pageIndex, localId, patch));
306
+ if (this.config.autoCommit !== false) {
307
+ this.commit();
308
+ }
309
+ return;
153
310
  }
154
- if (selected.annotation.type !== PdfAnnotationSubtype.HIGHLIGHT) {
155
- return false;
311
+ const originalObject = this.state.byUid[makeUid(pageIndex, localId)].object;
312
+ const originalPatch = Object.fromEntries(
313
+ Object.keys(patch).map((key) => [key, originalObject[key]])
314
+ );
315
+ const command = {
316
+ execute: () => this.dispatch(patchAnnotation(pageIndex, localId, patch)),
317
+ undo: () => this.dispatch(patchAnnotation(pageIndex, localId, originalPatch))
318
+ };
319
+ this.history.register(command, this.ANNOTATION_HISTORY_TOPIC);
320
+ }
321
+ deleteAnnotation(pageIndex, localId) {
322
+ if (!this.history) {
323
+ this.dispatch(deselectAnnotation());
324
+ this.dispatch(deleteAnnotation(pageIndex, localId));
325
+ if (this.config.autoCommit !== false) {
326
+ this.commit();
327
+ }
328
+ return;
156
329
  }
330
+ const originalAnnotation = this.state.byUid[makeUid(pageIndex, localId)].object;
331
+ const command = {
332
+ execute: () => {
333
+ this.dispatch(deselectAnnotation());
334
+ this.dispatch(deleteAnnotation(pageIndex, localId));
335
+ },
336
+ undo: () => this.dispatch(createAnnotation(pageIndex, localId, originalAnnotation))
337
+ };
338
+ this.history.register(command, this.ANNOTATION_HISTORY_TOPIC);
339
+ }
340
+ commit() {
341
+ const task = new Task();
342
+ if (!this.state.hasPendingChanges) return PdfTaskHelper.resolve(true);
157
343
  const doc = this.coreState.core.document;
158
- if (!doc) {
159
- return false;
344
+ if (!doc)
345
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
346
+ const creations = [];
347
+ const updates = [];
348
+ const deletionsByPage = /* @__PURE__ */ new Map();
349
+ const affectedPages = /* @__PURE__ */ new Set();
350
+ for (const [uid, ta] of Object.entries(this.state.byUid)) {
351
+ if (ta.commitState === "synced") continue;
352
+ const { pageIndex } = parseUid(uid);
353
+ const page = doc.pages.find((p) => p.index === pageIndex);
354
+ if (!page) continue;
355
+ affectedPages.add(pageIndex);
356
+ switch (ta.commitState) {
357
+ case "new":
358
+ const task2 = this.engine.createPageAnnotation(doc, page, ta.object);
359
+ task2.wait((annoId) => this.dispatch(storePdfId(uid, annoId)), ignore);
360
+ creations.push(task2);
361
+ break;
362
+ case "dirty":
363
+ updates.push(
364
+ this.engine.updatePageAnnotation(doc, page, { ...ta.object, id: ta.pdfId })
365
+ );
366
+ break;
367
+ case "deleted":
368
+ if (!deletionsByPage.has(pageIndex)) {
369
+ deletionsByPage.set(pageIndex, []);
370
+ }
371
+ deletionsByPage.get(pageIndex).push({ ta, uid });
372
+ break;
373
+ }
160
374
  }
161
- this.dispatch(updateAnnotationColor(selected.pageIndex, selected.annotationId, color));
162
- try {
163
- return true;
164
- } catch (error) {
165
- console.error("Failed to update annotation color:", error);
166
- return false;
375
+ const deletionTasks = [];
376
+ for (const [pageIndex, deletions] of deletionsByPage.entries()) {
377
+ const page = doc.pages.find((p) => p.index === pageIndex);
378
+ deletions.sort((a, b) => (b.ta.pdfId ?? -1) - (a.ta.pdfId ?? -1));
379
+ for (const { ta, uid } of deletions) {
380
+ if (ta.pdfId !== void 0) {
381
+ const task2 = new Task();
382
+ const removeTask = this.engine.removePageAnnotation(doc, page, {
383
+ ...ta.object,
384
+ id: ta.pdfId
385
+ });
386
+ removeTask.wait(() => {
387
+ this.dispatch(purgeAnnotation(uid));
388
+ task2.resolve(true);
389
+ }, task2.fail);
390
+ deletionTasks.push(task2);
391
+ } else {
392
+ this.dispatch(purgeAnnotation(uid));
393
+ }
394
+ }
167
395
  }
396
+ const allWriteTasks = [...creations, ...updates, ...deletionTasks];
397
+ Task.allSettled(allWriteTasks).wait(() => {
398
+ for (const pageIndex of affectedPages) {
399
+ this.dispatch(reindexPageAnnotations(pageIndex));
400
+ }
401
+ this.dispatch(commitPendingChanges());
402
+ task.resolve(true);
403
+ }, task.fail);
404
+ return task;
168
405
  }
169
406
  };
170
407
  AnnotationPlugin.id = "annotation";
171
408
 
172
409
  // src/lib/reducer.ts
173
- var initialState = {
174
- annotations: {},
175
- selectedAnnotation: null,
176
- annotationMode: null
410
+ import { PdfAnnotationSubtype as PdfAnnotationSubtype2 } from "@embedpdf/models";
411
+ var DEFAULT_COLORS = [
412
+ "#E44234",
413
+ "#FF8D00",
414
+ "#FFCD45",
415
+ "#5CC96E",
416
+ "#25D2D1",
417
+ "#597CE2",
418
+ "#C544CE",
419
+ "#7D2E25"
420
+ ];
421
+ var patchAnno = (state, uid, patch) => {
422
+ const prev = state.byUid[uid];
423
+ if (!prev) return state;
424
+ return {
425
+ ...state,
426
+ byUid: {
427
+ ...state.byUid,
428
+ [uid]: {
429
+ ...prev,
430
+ commitState: prev.commitState === "synced" ? "dirty" : prev.commitState,
431
+ object: { ...prev.object, ...patch }
432
+ }
433
+ },
434
+ hasPendingChanges: true
435
+ };
177
436
  };
437
+ var initialState = (cfg) => ({
438
+ pages: {},
439
+ byUid: {},
440
+ selectedUid: null,
441
+ annotationMode: null,
442
+ toolDefaults: {
443
+ [PdfAnnotationSubtype2.HIGHLIGHT]: {
444
+ name: "Highlight",
445
+ color: "#FFCD45",
446
+ opacity: 1,
447
+ interaction: { mode: "highlight", exclusive: false },
448
+ textSelection: true
449
+ },
450
+ [PdfAnnotationSubtype2.UNDERLINE]: {
451
+ name: "Underline",
452
+ color: "#E44234",
453
+ opacity: 1,
454
+ interaction: { mode: "underline", exclusive: false },
455
+ textSelection: true
456
+ },
457
+ [PdfAnnotationSubtype2.STRIKEOUT]: {
458
+ name: "Strikeout",
459
+ color: "#E44234",
460
+ opacity: 1,
461
+ interaction: { mode: "strikeout", exclusive: false },
462
+ textSelection: true
463
+ },
464
+ [PdfAnnotationSubtype2.SQUIGGLY]: {
465
+ name: "Squiggly",
466
+ color: "#E44234",
467
+ opacity: 1,
468
+ interaction: { mode: "squiggly", exclusive: false },
469
+ textSelection: true
470
+ },
471
+ ...cfg.toolDefaults
472
+ },
473
+ colorPresets: cfg.colorPresets ?? DEFAULT_COLORS,
474
+ hasPendingChanges: false
475
+ });
178
476
  var reducer = (state, action) => {
179
477
  switch (action.type) {
180
- case SET_ANNOTATIONS:
478
+ /* ───── bulk load from engine ───── */
479
+ case SET_ANNOTATIONS: {
480
+ const newPages = { ...state.pages };
481
+ const newByUid = { ...state.byUid };
482
+ for (const [pgStr, list] of Object.entries(action.payload)) {
483
+ const pageIndex = Number(pgStr);
484
+ const oldUidsOnPage = state.pages[pageIndex] || [];
485
+ for (const uid of oldUidsOnPage) {
486
+ delete newByUid[uid];
487
+ }
488
+ const newUidsOnPage = list.map((a, index) => {
489
+ const localId = Date.now() + Math.random() + index;
490
+ const uid = makeUid(pageIndex, localId);
491
+ newByUid[uid] = { localId, pdfId: a.id, commitState: "synced", object: a };
492
+ return uid;
493
+ });
494
+ newPages[pageIndex] = newUidsOnPage;
495
+ }
496
+ return { ...state, pages: newPages, byUid: newByUid };
497
+ }
498
+ /* ───── GUI bits ───── */
499
+ case SET_ANNOTATION_MODE:
500
+ return { ...state, annotationMode: action.payload };
501
+ case SELECT_ANNOTATION:
181
502
  return {
182
503
  ...state,
183
- annotations: action.payload,
184
- // Clear selection if the annotations have changed
185
- selectedAnnotation: null
504
+ selectedUid: makeUid(action.payload.pageIndex, action.payload.localId)
186
505
  };
187
- case SELECT_ANNOTATION:
506
+ case DESELECT_ANNOTATION:
507
+ return { ...state, selectedUid: null };
508
+ case ADD_COLOR_PRESET:
509
+ return state.colorPresets.includes(action.payload) ? state : { ...state, colorPresets: [...state.colorPresets, action.payload] };
510
+ case UPDATE_TOOL_DEFAULTS: {
511
+ const { subtype, patch } = action.payload;
188
512
  return {
189
513
  ...state,
190
- selectedAnnotation: action.payload
514
+ toolDefaults: {
515
+ ...state.toolDefaults,
516
+ [subtype]: { ...state.toolDefaults[subtype], ...patch }
517
+ }
191
518
  };
192
- case DESELECT_ANNOTATION:
519
+ }
520
+ /* ───── create ───── */
521
+ case CREATE_ANNOTATION: {
522
+ const { pageIndex, localId, annotation } = action.payload;
523
+ const uid = makeUid(pageIndex, localId);
193
524
  return {
194
525
  ...state,
195
- selectedAnnotation: null
526
+ pages: { ...state.pages, [pageIndex]: [...state.pages[pageIndex] ?? [], uid] },
527
+ byUid: {
528
+ ...state.byUid,
529
+ [uid]: { localId, pdfId: void 0, commitState: "new", object: annotation }
530
+ },
531
+ hasPendingChanges: true
196
532
  };
197
- case SET_ANNOTATION_MODE:
533
+ }
534
+ /* ───── delete ───── */
535
+ case DELETE_ANNOTATION: {
536
+ const { pageIndex, localId } = action.payload;
537
+ const uid = makeUid(pageIndex, localId);
538
+ if (!state.byUid[uid]) return state;
198
539
  return {
199
540
  ...state,
200
- annotationMode: action.payload
541
+ pages: {
542
+ ...state.pages,
543
+ [pageIndex]: (state.pages[pageIndex] ?? []).filter((u) => u !== uid)
544
+ },
545
+ byUid: {
546
+ ...state.byUid,
547
+ [uid]: { ...state.byUid[uid], commitState: "deleted" }
548
+ },
549
+ hasPendingChanges: true
201
550
  };
202
- case UPDATE_ANNOTATION_COLOR: {
203
- const { pageIndex, annotationId, color } = action.payload;
204
- const pageAnnotations = state.annotations[pageIndex];
205
- if (!pageAnnotations) {
206
- return state;
551
+ }
552
+ /* ───── field edits ───── */
553
+ case PATCH_ANNOTATION: {
554
+ const uid = makeUid(action.payload.pageIndex, action.payload.localId);
555
+ return patchAnno(state, uid, action.payload.patch);
556
+ }
557
+ /* ───── commit bookkeeping ───── */
558
+ case COMMIT_PENDING_CHANGES: {
559
+ const cleaned = {};
560
+ for (const [uid, ta] of Object.entries(state.byUid)) {
561
+ cleaned[uid] = {
562
+ ...ta,
563
+ commitState: ta.commitState === "dirty" || ta.commitState === "new" ? "synced" : ta.commitState
564
+ };
207
565
  }
208
- const updatedAnnotations = pageAnnotations.map((annotation) => {
209
- if (annotation.id === annotationId) {
210
- if (annotation.type === PdfAnnotationSubtype.HIGHLIGHT) {
211
- return {
212
- ...annotation,
213
- color
214
- };
215
- }
216
- }
217
- return annotation;
566
+ return { ...state, byUid: cleaned, hasPendingChanges: false };
567
+ }
568
+ case REINDEX_PAGE_ANNOTATIONS: {
569
+ const { pageIndex } = action.payload;
570
+ const newByUid = { ...state.byUid };
571
+ const uidsOnPage = state.pages[pageIndex] || [];
572
+ const annosOnPage = uidsOnPage.map((uid) => state.byUid[uid]).filter((ta) => ta && ta.commitState !== "deleted");
573
+ annosOnPage.sort((a, b) => (a.pdfId ?? Infinity) - (b.pdfId ?? Infinity));
574
+ annosOnPage.forEach((ta, newPdfId) => {
575
+ const uid = makeUid(pageIndex, ta.localId);
576
+ newByUid[uid] = { ...newByUid[uid], pdfId: newPdfId };
218
577
  });
219
- const newAnnotations = {
220
- ...state.annotations,
221
- [pageIndex]: updatedAnnotations
222
- };
223
- let newSelectedAnnotation = state.selectedAnnotation;
224
- if (newSelectedAnnotation && newSelectedAnnotation.pageIndex === pageIndex && newSelectedAnnotation.annotationId === annotationId) {
225
- const updatedAnnotation = updatedAnnotations.find((a) => a.id === annotationId);
226
- if (updatedAnnotation) {
227
- newSelectedAnnotation = {
228
- ...newSelectedAnnotation,
229
- annotation: updatedAnnotation
230
- };
231
- }
232
- }
578
+ return { ...state, byUid: newByUid };
579
+ }
580
+ case STORE_PDF_ID: {
581
+ const { uid, pdfId } = action.payload;
582
+ const ta = state.byUid[uid];
583
+ if (!ta) return state;
233
584
  return {
234
585
  ...state,
235
- annotations: newAnnotations,
236
- selectedAnnotation: newSelectedAnnotation
586
+ byUid: {
587
+ ...state.byUid,
588
+ [uid]: { ...ta, pdfId, commitState: "synced" }
589
+ }
237
590
  };
238
591
  }
592
+ case PURGE_ANNOTATION: {
593
+ const { uid } = action.payload;
594
+ const { [uid]: _gone, ...rest } = state.byUid;
595
+ return { ...state, byUid: rest };
596
+ }
239
597
  default:
240
598
  return state;
241
599
  }
@@ -244,14 +602,22 @@ var reducer = (state, action) => {
244
602
  // src/lib/index.ts
245
603
  var AnnotationPluginPackage = {
246
604
  manifest,
247
- create: (registry, engine) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine),
605
+ create: (registry, engine, config) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine, config),
248
606
  reducer,
249
- initialState
607
+ initialState: (_, config) => initialState(config)
250
608
  };
251
609
  export {
252
610
  ANNOTATION_PLUGIN_ID,
253
611
  AnnotationPlugin,
254
612
  AnnotationPluginPackage,
613
+ getAnnotations,
614
+ getAnnotationsByPageIndex,
615
+ getSelectedAnnotation,
616
+ getSelectedAnnotationByPageIndex,
617
+ getSelectedAnnotationMode,
618
+ getSelectedAnnotationWithPageIndex,
619
+ isAnnotationSelected,
620
+ isInAnnotationMode,
255
621
  manifest
256
622
  };
257
623
  //# sourceMappingURL=index.js.map