@embedpdf/plugin-annotation 1.0.6 → 1.0.8

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