@embedpdf/plugin-annotation 1.0.6 → 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,104 +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 PdfSoftHyphenMarker = "\xAD";
49
- var PdfZeroWidthSpace = "\u200B";
50
- var PdfWordJoiner = "\u2060";
51
- var PdfBomOrZwnbsp = "\uFEFF";
52
- var PdfNonCharacterFFFE = "\uFFFE";
53
- var PdfNonCharacterFFFF = "\uFFFF";
54
- var PdfUnwantedTextMarkers = Object.freeze([
55
- PdfSoftHyphenMarker,
56
- PdfZeroWidthSpace,
57
- PdfWordJoiner,
58
- PdfBomOrZwnbsp,
59
- PdfNonCharacterFFFE,
60
- PdfNonCharacterFFFF
61
- ]);
62
- var PdfUnwantedTextRegex = new RegExp(`[${PdfUnwantedTextMarkers.join("")}]`, "g");
63
- var PdfAnnotationSubtype = /* @__PURE__ */ ((PdfAnnotationSubtype2) => {
64
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNKNOWN"] = 0] = "UNKNOWN";
65
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TEXT"] = 1] = "TEXT";
66
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINK"] = 2] = "LINK";
67
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FREETEXT"] = 3] = "FREETEXT";
68
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINE"] = 4] = "LINE";
69
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUARE"] = 5] = "SQUARE";
70
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CIRCLE"] = 6] = "CIRCLE";
71
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYGON"] = 7] = "POLYGON";
72
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYLINE"] = 8] = "POLYLINE";
73
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["HIGHLIGHT"] = 9] = "HIGHLIGHT";
74
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNDERLINE"] = 10] = "UNDERLINE";
75
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUIGGLY"] = 11] = "SQUIGGLY";
76
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STRIKEOUT"] = 12] = "STRIKEOUT";
77
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STAMP"] = 13] = "STAMP";
78
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CARET"] = 14] = "CARET";
79
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["INK"] = 15] = "INK";
80
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POPUP"] = 16] = "POPUP";
81
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FILEATTACHMENT"] = 17] = "FILEATTACHMENT";
82
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SOUND"] = 18] = "SOUND";
83
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["MOVIE"] = 19] = "MOVIE";
84
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WIDGET"] = 20] = "WIDGET";
85
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SCREEN"] = 21] = "SCREEN";
86
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["PRINTERMARK"] = 22] = "PRINTERMARK";
87
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TRAPNET"] = 23] = "TRAPNET";
88
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WATERMARK"] = 24] = "WATERMARK";
89
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["THREED"] = 25] = "THREED";
90
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["RICHMEDIA"] = 26] = "RICHMEDIA";
91
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["XFAWIDGET"] = 27] = "XFAWIDGET";
92
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["REDACT"] = 28] = "REDACT";
93
- return PdfAnnotationSubtype2;
94
- })(PdfAnnotationSubtype || {});
95
- function ignore() {
96
- }
55
+ var import_models = require("@embedpdf/models");
97
56
 
98
57
  // src/lib/actions.ts
99
- var SET_ANNOTATIONS = "SET_ANNOTATIONS";
100
- var SELECT_ANNOTATION = "SELECT_ANNOTATION";
101
- var DESELECT_ANNOTATION = "DESELECT_ANNOTATION";
102
- var SET_ANNOTATION_MODE = "SET_ANNOTATION_MODE";
103
- var UPDATE_ANNOTATION_COLOR = "UPDATE_ANNOTATION_COLOR";
104
- function setAnnotations(payload) {
105
- return { type: SET_ANNOTATIONS, payload };
106
- }
107
- function selectAnnotation(pageIndex, annotationId, annotation) {
108
- return {
109
- type: SELECT_ANNOTATION,
110
- payload: { pageIndex, annotationId, annotation }
111
- };
112
- }
113
- function deselectAnnotation() {
114
- return { type: DESELECT_ANNOTATION };
115
- }
116
- function setAnnotationMode(mode) {
117
- return {
118
- type: SET_ANNOTATION_MODE,
119
- payload: mode
120
- };
121
- }
122
- function updateAnnotationColor(pageIndex, annotationId, color) {
123
- return {
124
- type: UPDATE_ANNOTATION_COLOR,
125
- payload: { pageIndex, annotationId, color }
126
- };
127
- }
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);
128
147
 
129
148
  // src/lib/annotation-plugin.ts
130
149
  var AnnotationPlugin = class extends import_core.BasePlugin {
131
- constructor(id, registry, engine) {
150
+ constructor(id, registry, engine, config) {
132
151
  super(id, registry);
152
+ this.ANNOTATION_HISTORY_TOPIC = "annotations";
133
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
+ });
134
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;
135
171
  this.coreStore.onAction(import_core.SET_DOCUMENT, (_action, state) => {
136
172
  const doc = state.core.document;
137
173
  if (doc) {
@@ -140,146 +176,450 @@ var AnnotationPlugin = class extends import_core.BasePlugin {
140
176
  });
141
177
  }
142
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);
143
231
  }
144
232
  buildCapability() {
145
233
  return {
146
234
  getPageAnnotations: (options) => {
147
235
  return this.getPageAnnotations(options);
148
236
  },
237
+ getSelectedAnnotation: () => {
238
+ return getSelectedAnnotation(this.state);
239
+ },
149
240
  selectAnnotation: (pageIndex, annotationId) => {
150
241
  this.selectAnnotation(pageIndex, annotationId);
151
242
  },
152
243
  deselectAnnotation: () => {
153
244
  this.dispatch(deselectAnnotation());
154
245
  },
155
- updateAnnotationColor: async (color) => {
156
- 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;
157
265
  },
158
- setAnnotationMode: (mode) => {
159
- this.dispatch(setAnnotationMode(mode));
266
+ setToolDefaults: (subtype, patch) => {
267
+ this.dispatch(updateToolDefaults(subtype, patch));
160
268
  },
161
- 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()
162
278
  };
163
279
  }
164
- onStoreUpdated(_prevState, newState) {
165
- 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
+ }
166
292
  }
167
293
  getAllAnnotations(doc) {
168
294
  const task = this.engine.getAllAnnotations(doc);
169
- task.wait((annotations) => this.dispatch(setAnnotations(annotations)), ignore);
295
+ task.wait((annotations) => this.dispatch(setAnnotations(annotations)), import_models.ignore);
170
296
  }
171
297
  getPageAnnotations(options) {
172
298
  const { pageIndex } = options;
173
299
  const doc = this.coreState.core.document;
174
300
  if (!doc) {
175
- throw new Error("document does not open");
301
+ return import_models.PdfTaskHelper.reject({ code: import_models.PdfErrorCode.NotFound, message: "Document not found" });
176
302
  }
177
303
  const page = doc.pages.find((p) => p.index === pageIndex);
178
304
  if (!page) {
179
- throw new Error("page does not open");
305
+ return import_models.PdfTaskHelper.reject({ code: import_models.PdfErrorCode.NotFound, message: "Page not found" });
180
306
  }
181
307
  return this.engine.getPageAnnotations(doc, page);
182
308
  }
183
309
  selectAnnotation(pageIndex, annotationId) {
184
- const pageAnnotations = this.state.annotations[pageIndex];
185
- 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();
186
318
  return;
187
319
  }
188
- const annotation = pageAnnotations.find((a) => a.id === annotationId);
189
- if (annotation) {
190
- this.dispatch(selectAnnotation(pageIndex, annotationId, annotation));
191
- }
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);
192
328
  }
193
- async updateSelectedAnnotationColor(color) {
194
- const selected = this.state.selectedAnnotation;
195
- if (!selected) {
196
- 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;
197
336
  }
198
- if (selected.annotation.type !== PdfAnnotationSubtype.HIGHLIGHT) {
199
- 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;
200
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);
201
369
  const doc = this.coreState.core.document;
202
- if (!doc) {
203
- 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
+ }
204
400
  }
205
- this.dispatch(updateAnnotationColor(selected.pageIndex, selected.annotationId, color));
206
- try {
207
- return true;
208
- } catch (error) {
209
- console.error("Failed to update annotation color:", error);
210
- 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
+ }
211
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;
212
431
  }
213
432
  };
214
433
  AnnotationPlugin.id = "annotation";
215
434
 
216
435
  // src/lib/reducer.ts
217
- var initialState = {
218
- annotations: {},
219
- selectedAnnotation: null,
220
- 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
+ };
221
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
+ });
222
502
  var reducer = (state, action) => {
223
503
  switch (action.type) {
224
- 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:
225
528
  return {
226
529
  ...state,
227
- annotations: action.payload,
228
- // Clear selection if the annotations have changed
229
- selectedAnnotation: null
530
+ selectedUid: makeUid(action.payload.pageIndex, action.payload.localId)
230
531
  };
231
- 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;
232
538
  return {
233
539
  ...state,
234
- selectedAnnotation: action.payload
540
+ toolDefaults: {
541
+ ...state.toolDefaults,
542
+ [subtype]: { ...state.toolDefaults[subtype], ...patch }
543
+ }
235
544
  };
236
- case DESELECT_ANNOTATION:
545
+ }
546
+ /* ───── create ───── */
547
+ case CREATE_ANNOTATION: {
548
+ const { pageIndex, localId, annotation } = action.payload;
549
+ const uid = makeUid(pageIndex, localId);
237
550
  return {
238
551
  ...state,
239
- 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
240
558
  };
241
- 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;
242
565
  return {
243
566
  ...state,
244
- 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
245
576
  };
246
- case UPDATE_ANNOTATION_COLOR: {
247
- const { pageIndex, annotationId, color } = action.payload;
248
- const pageAnnotations = state.annotations[pageIndex];
249
- if (!pageAnnotations) {
250
- 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
+ };
251
591
  }
252
- const updatedAnnotations = pageAnnotations.map((annotation) => {
253
- if (annotation.id === annotationId) {
254
- if (annotation.type === PdfAnnotationSubtype.HIGHLIGHT) {
255
- return {
256
- ...annotation,
257
- color
258
- };
259
- }
260
- }
261
- 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 };
262
603
  });
263
- const newAnnotations = {
264
- ...state.annotations,
265
- [pageIndex]: updatedAnnotations
266
- };
267
- let newSelectedAnnotation = state.selectedAnnotation;
268
- if (newSelectedAnnotation && newSelectedAnnotation.pageIndex === pageIndex && newSelectedAnnotation.annotationId === annotationId) {
269
- const updatedAnnotation = updatedAnnotations.find((a) => a.id === annotationId);
270
- if (updatedAnnotation) {
271
- newSelectedAnnotation = {
272
- ...newSelectedAnnotation,
273
- annotation: updatedAnnotation
274
- };
275
- }
276
- }
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;
277
610
  return {
278
611
  ...state,
279
- annotations: newAnnotations,
280
- selectedAnnotation: newSelectedAnnotation
612
+ byUid: {
613
+ ...state.byUid,
614
+ [uid]: { ...ta, pdfId, commitState: "synced" }
615
+ }
281
616
  };
282
617
  }
618
+ case PURGE_ANNOTATION: {
619
+ const { uid } = action.payload;
620
+ const { [uid]: _gone, ...rest } = state.byUid;
621
+ return { ...state, byUid: rest };
622
+ }
283
623
  default:
284
624
  return state;
285
625
  }
@@ -288,15 +628,23 @@ var reducer = (state, action) => {
288
628
  // src/lib/index.ts
289
629
  var AnnotationPluginPackage = {
290
630
  manifest,
291
- create: (registry, engine) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine),
631
+ create: (registry, engine, config) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine, config),
292
632
  reducer,
293
- initialState
633
+ initialState: (_, config) => initialState(config)
294
634
  };
295
635
  // Annotate the CommonJS export names for ESM import in node:
296
636
  0 && (module.exports = {
297
637
  ANNOTATION_PLUGIN_ID,
298
638
  AnnotationPlugin,
299
639
  AnnotationPluginPackage,
640
+ getAnnotations,
641
+ getAnnotationsByPageIndex,
642
+ getSelectedAnnotation,
643
+ getSelectedAnnotationByPageIndex,
644
+ getSelectedAnnotationMode,
645
+ getSelectedAnnotationWithPageIndex,
646
+ isAnnotationSelected,
647
+ isInAnnotationMode,
300
648
  manifest
301
649
  });
302
650
  //# sourceMappingURL=index.cjs.map