@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.js CHANGED
@@ -5,104 +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 PdfSoftHyphenMarker = "\xAD";
20
- var PdfZeroWidthSpace = "\u200B";
21
- var PdfWordJoiner = "\u2060";
22
- var PdfBomOrZwnbsp = "\uFEFF";
23
- var PdfNonCharacterFFFE = "\uFFFE";
24
- var PdfNonCharacterFFFF = "\uFFFF";
25
- var PdfUnwantedTextMarkers = Object.freeze([
26
- PdfSoftHyphenMarker,
27
- PdfZeroWidthSpace,
28
- PdfWordJoiner,
29
- PdfBomOrZwnbsp,
30
- PdfNonCharacterFFFE,
31
- PdfNonCharacterFFFF
32
- ]);
33
- var PdfUnwantedTextRegex = new RegExp(`[${PdfUnwantedTextMarkers.join("")}]`, "g");
34
- var PdfAnnotationSubtype = /* @__PURE__ */ ((PdfAnnotationSubtype2) => {
35
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNKNOWN"] = 0] = "UNKNOWN";
36
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TEXT"] = 1] = "TEXT";
37
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINK"] = 2] = "LINK";
38
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FREETEXT"] = 3] = "FREETEXT";
39
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["LINE"] = 4] = "LINE";
40
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUARE"] = 5] = "SQUARE";
41
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CIRCLE"] = 6] = "CIRCLE";
42
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYGON"] = 7] = "POLYGON";
43
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POLYLINE"] = 8] = "POLYLINE";
44
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["HIGHLIGHT"] = 9] = "HIGHLIGHT";
45
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["UNDERLINE"] = 10] = "UNDERLINE";
46
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SQUIGGLY"] = 11] = "SQUIGGLY";
47
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STRIKEOUT"] = 12] = "STRIKEOUT";
48
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["STAMP"] = 13] = "STAMP";
49
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["CARET"] = 14] = "CARET";
50
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["INK"] = 15] = "INK";
51
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["POPUP"] = 16] = "POPUP";
52
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["FILEATTACHMENT"] = 17] = "FILEATTACHMENT";
53
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SOUND"] = 18] = "SOUND";
54
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["MOVIE"] = 19] = "MOVIE";
55
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WIDGET"] = 20] = "WIDGET";
56
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["SCREEN"] = 21] = "SCREEN";
57
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["PRINTERMARK"] = 22] = "PRINTERMARK";
58
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["TRAPNET"] = 23] = "TRAPNET";
59
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["WATERMARK"] = 24] = "WATERMARK";
60
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["THREED"] = 25] = "THREED";
61
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["RICHMEDIA"] = 26] = "RICHMEDIA";
62
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["XFAWIDGET"] = 27] = "XFAWIDGET";
63
- PdfAnnotationSubtype2[PdfAnnotationSubtype2["REDACT"] = 28] = "REDACT";
64
- return PdfAnnotationSubtype2;
65
- })(PdfAnnotationSubtype || {});
66
- function ignore() {
67
- }
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";
68
30
 
69
31
  // src/lib/actions.ts
70
- var SET_ANNOTATIONS = "SET_ANNOTATIONS";
71
- var SELECT_ANNOTATION = "SELECT_ANNOTATION";
72
- var DESELECT_ANNOTATION = "DESELECT_ANNOTATION";
73
- var SET_ANNOTATION_MODE = "SET_ANNOTATION_MODE";
74
- var UPDATE_ANNOTATION_COLOR = "UPDATE_ANNOTATION_COLOR";
75
- function setAnnotations(payload) {
76
- return { type: SET_ANNOTATIONS, payload };
77
- }
78
- function selectAnnotation(pageIndex, annotationId, annotation) {
79
- return {
80
- type: SELECT_ANNOTATION,
81
- payload: { pageIndex, annotationId, annotation }
82
- };
83
- }
84
- function deselectAnnotation() {
85
- return { type: DESELECT_ANNOTATION };
86
- }
87
- function setAnnotationMode(mode) {
88
- return {
89
- type: SET_ANNOTATION_MODE,
90
- payload: mode
91
- };
92
- }
93
- function updateAnnotationColor(pageIndex, annotationId, color) {
94
- return {
95
- type: UPDATE_ANNOTATION_COLOR,
96
- payload: { pageIndex, annotationId, color }
97
- };
98
- }
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);
99
121
 
100
122
  // src/lib/annotation-plugin.ts
101
123
  var AnnotationPlugin = class extends BasePlugin {
102
- constructor(id, registry, engine) {
124
+ constructor(id, registry, engine, config) {
103
125
  super(id, registry);
126
+ this.ANNOTATION_HISTORY_TOPIC = "annotations";
104
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
+ });
105
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;
106
145
  this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {
107
146
  const doc = state.core.document;
108
147
  if (doc) {
@@ -111,29 +150,119 @@ var AnnotationPlugin = class extends BasePlugin {
111
150
  });
112
151
  }
113
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);
114
205
  }
115
206
  buildCapability() {
116
207
  return {
117
208
  getPageAnnotations: (options) => {
118
209
  return this.getPageAnnotations(options);
119
210
  },
211
+ getSelectedAnnotation: () => {
212
+ return getSelectedAnnotation(this.state);
213
+ },
120
214
  selectAnnotation: (pageIndex, annotationId) => {
121
215
  this.selectAnnotation(pageIndex, annotationId);
122
216
  },
123
217
  deselectAnnotation: () => {
124
218
  this.dispatch(deselectAnnotation());
125
219
  },
126
- updateAnnotationColor: async (color) => {
127
- 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;
128
239
  },
129
- setAnnotationMode: (mode) => {
130
- this.dispatch(setAnnotationMode(mode));
240
+ setToolDefaults: (subtype, patch) => {
241
+ this.dispatch(updateToolDefaults(subtype, patch));
131
242
  },
132
- 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()
133
252
  };
134
253
  }
135
- onStoreUpdated(_prevState, newState) {
136
- 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
+ }
137
266
  }
138
267
  getAllAnnotations(doc) {
139
268
  const task = this.engine.getAllAnnotations(doc);
@@ -143,114 +272,328 @@ var AnnotationPlugin = class extends BasePlugin {
143
272
  const { pageIndex } = options;
144
273
  const doc = this.coreState.core.document;
145
274
  if (!doc) {
146
- throw new Error("document does not open");
275
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
147
276
  }
148
277
  const page = doc.pages.find((p) => p.index === pageIndex);
149
278
  if (!page) {
150
- throw new Error("page does not open");
279
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Page not found" });
151
280
  }
152
281
  return this.engine.getPageAnnotations(doc, page);
153
282
  }
154
283
  selectAnnotation(pageIndex, annotationId) {
155
- const pageAnnotations = this.state.annotations[pageIndex];
156
- 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();
157
292
  return;
158
293
  }
159
- const annotation = pageAnnotations.find((a) => a.id === annotationId);
160
- if (annotation) {
161
- this.dispatch(selectAnnotation(pageIndex, annotationId, annotation));
162
- }
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);
163
302
  }
164
- async updateSelectedAnnotationColor(color) {
165
- const selected = this.state.selectedAnnotation;
166
- if (!selected) {
167
- 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;
168
310
  }
169
- if (selected.annotation.type !== PdfAnnotationSubtype.HIGHLIGHT) {
170
- 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;
171
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);
172
343
  const doc = this.coreState.core.document;
173
- if (!doc) {
174
- 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
+ }
175
374
  }
176
- this.dispatch(updateAnnotationColor(selected.pageIndex, selected.annotationId, color));
177
- try {
178
- return true;
179
- } catch (error) {
180
- console.error("Failed to update annotation color:", error);
181
- 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
+ }
182
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;
183
405
  }
184
406
  };
185
407
  AnnotationPlugin.id = "annotation";
186
408
 
187
409
  // src/lib/reducer.ts
188
- var initialState = {
189
- annotations: {},
190
- selectedAnnotation: null,
191
- 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
+ };
192
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
+ });
193
476
  var reducer = (state, action) => {
194
477
  switch (action.type) {
195
- 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:
196
502
  return {
197
503
  ...state,
198
- annotations: action.payload,
199
- // Clear selection if the annotations have changed
200
- selectedAnnotation: null
504
+ selectedUid: makeUid(action.payload.pageIndex, action.payload.localId)
201
505
  };
202
- 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;
203
512
  return {
204
513
  ...state,
205
- selectedAnnotation: action.payload
514
+ toolDefaults: {
515
+ ...state.toolDefaults,
516
+ [subtype]: { ...state.toolDefaults[subtype], ...patch }
517
+ }
206
518
  };
207
- case DESELECT_ANNOTATION:
519
+ }
520
+ /* ───── create ───── */
521
+ case CREATE_ANNOTATION: {
522
+ const { pageIndex, localId, annotation } = action.payload;
523
+ const uid = makeUid(pageIndex, localId);
208
524
  return {
209
525
  ...state,
210
- 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
211
532
  };
212
- 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;
213
539
  return {
214
540
  ...state,
215
- 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
216
550
  };
217
- case UPDATE_ANNOTATION_COLOR: {
218
- const { pageIndex, annotationId, color } = action.payload;
219
- const pageAnnotations = state.annotations[pageIndex];
220
- if (!pageAnnotations) {
221
- 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
+ };
222
565
  }
223
- const updatedAnnotations = pageAnnotations.map((annotation) => {
224
- if (annotation.id === annotationId) {
225
- if (annotation.type === PdfAnnotationSubtype.HIGHLIGHT) {
226
- return {
227
- ...annotation,
228
- color
229
- };
230
- }
231
- }
232
- 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 };
233
577
  });
234
- const newAnnotations = {
235
- ...state.annotations,
236
- [pageIndex]: updatedAnnotations
237
- };
238
- let newSelectedAnnotation = state.selectedAnnotation;
239
- if (newSelectedAnnotation && newSelectedAnnotation.pageIndex === pageIndex && newSelectedAnnotation.annotationId === annotationId) {
240
- const updatedAnnotation = updatedAnnotations.find((a) => a.id === annotationId);
241
- if (updatedAnnotation) {
242
- newSelectedAnnotation = {
243
- ...newSelectedAnnotation,
244
- annotation: updatedAnnotation
245
- };
246
- }
247
- }
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;
248
584
  return {
249
585
  ...state,
250
- annotations: newAnnotations,
251
- selectedAnnotation: newSelectedAnnotation
586
+ byUid: {
587
+ ...state.byUid,
588
+ [uid]: { ...ta, pdfId, commitState: "synced" }
589
+ }
252
590
  };
253
591
  }
592
+ case PURGE_ANNOTATION: {
593
+ const { uid } = action.payload;
594
+ const { [uid]: _gone, ...rest } = state.byUid;
595
+ return { ...state, byUid: rest };
596
+ }
254
597
  default:
255
598
  return state;
256
599
  }
@@ -259,14 +602,22 @@ var reducer = (state, action) => {
259
602
  // src/lib/index.ts
260
603
  var AnnotationPluginPackage = {
261
604
  manifest,
262
- create: (registry, engine) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine),
605
+ create: (registry, engine, config) => new AnnotationPlugin(ANNOTATION_PLUGIN_ID, registry, engine, config),
263
606
  reducer,
264
- initialState
607
+ initialState: (_, config) => initialState(config)
265
608
  };
266
609
  export {
267
610
  ANNOTATION_PLUGIN_ID,
268
611
  AnnotationPlugin,
269
612
  AnnotationPluginPackage,
613
+ getAnnotations,
614
+ getAnnotationsByPageIndex,
615
+ getSelectedAnnotation,
616
+ getSelectedAnnotationByPageIndex,
617
+ getSelectedAnnotationMode,
618
+ getSelectedAnnotationWithPageIndex,
619
+ isAnnotationSelected,
620
+ isInAnnotationMode,
270
621
  manifest
271
622
  };
272
623
  //# sourceMappingURL=index.js.map