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