@embedpdf/plugin-redaction 2.15.0 → 3.0.0-next.0

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.
Files changed (119) hide show
  1. package/LICENSE +192 -21
  2. package/README.md +109 -0
  3. package/dist/index.cjs +227 -2
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +89 -0
  6. package/dist/index.d.ts +89 -1
  7. package/dist/index.js +220 -1532
  8. package/dist/index.js.map +1 -1
  9. package/package.json +22 -67
  10. package/dist/lib/actions.d.ts +0 -113
  11. package/dist/lib/handlers/index.d.ts +0 -0
  12. package/dist/lib/index.d.ts +0 -11
  13. package/dist/lib/manifest.d.ts +0 -4
  14. package/dist/lib/redaction-plugin.d.ts +0 -153
  15. package/dist/lib/reducer.d.ts +0 -6
  16. package/dist/lib/selectors.d.ts +0 -5
  17. package/dist/lib/tools.d.ts +0 -64
  18. package/dist/lib/types.d.ts +0 -142
  19. package/dist/preact/adapter.d.ts +0 -10
  20. package/dist/preact/annotation.d.ts +0 -1
  21. package/dist/preact/core.d.ts +0 -1
  22. package/dist/preact/index.cjs +0 -2
  23. package/dist/preact/index.cjs.map +0 -1
  24. package/dist/preact/index.d.ts +0 -1
  25. package/dist/preact/index.js +0 -520
  26. package/dist/preact/index.js.map +0 -1
  27. package/dist/preact/utils.d.ts +0 -1
  28. package/dist/react/adapter.d.ts +0 -2
  29. package/dist/react/annotation.d.ts +0 -1
  30. package/dist/react/core.d.ts +0 -1
  31. package/dist/react/index.cjs +0 -2
  32. package/dist/react/index.cjs.map +0 -1
  33. package/dist/react/index.d.ts +0 -1
  34. package/dist/react/index.js +0 -519
  35. package/dist/react/index.js.map +0 -1
  36. package/dist/react/utils.d.ts +0 -1
  37. package/dist/shared/components/annotations/index.d.ts +0 -2
  38. package/dist/shared/components/annotations/redact-area.d.ts +0 -18
  39. package/dist/shared/components/annotations/redact-highlight.d.ts +0 -18
  40. package/dist/shared/components/highlight.d.ts +0 -14
  41. package/dist/shared/components/index.d.ts +0 -5
  42. package/dist/shared/components/marquee-redact.d.ts +0 -15
  43. package/dist/shared/components/pending-redactions.d.ts +0 -12
  44. package/dist/shared/components/redact-renderer-registration.d.ts +0 -5
  45. package/dist/shared/components/redact-renderers.d.ts +0 -7
  46. package/dist/shared/components/redaction-layer.d.ts +0 -16
  47. package/dist/shared/components/selection-redact.d.ts +0 -7
  48. package/dist/shared/components/types.d.ts +0 -9
  49. package/dist/shared/hooks/index.d.ts +0 -1
  50. package/dist/shared/hooks/use-redaction.d.ts +0 -15
  51. package/dist/shared/index.d.ts +0 -4
  52. package/dist/shared-preact/components/annotations/index.d.ts +0 -2
  53. package/dist/shared-preact/components/annotations/redact-area.d.ts +0 -18
  54. package/dist/shared-preact/components/annotations/redact-highlight.d.ts +0 -18
  55. package/dist/shared-preact/components/highlight.d.ts +0 -14
  56. package/dist/shared-preact/components/index.d.ts +0 -5
  57. package/dist/shared-preact/components/marquee-redact.d.ts +0 -15
  58. package/dist/shared-preact/components/pending-redactions.d.ts +0 -12
  59. package/dist/shared-preact/components/redact-renderer-registration.d.ts +0 -5
  60. package/dist/shared-preact/components/redact-renderers.d.ts +0 -7
  61. package/dist/shared-preact/components/redaction-layer.d.ts +0 -16
  62. package/dist/shared-preact/components/selection-redact.d.ts +0 -7
  63. package/dist/shared-preact/components/types.d.ts +0 -9
  64. package/dist/shared-preact/hooks/index.d.ts +0 -1
  65. package/dist/shared-preact/hooks/use-redaction.d.ts +0 -15
  66. package/dist/shared-preact/index.d.ts +0 -4
  67. package/dist/shared-react/components/annotations/index.d.ts +0 -2
  68. package/dist/shared-react/components/annotations/redact-area.d.ts +0 -18
  69. package/dist/shared-react/components/annotations/redact-highlight.d.ts +0 -18
  70. package/dist/shared-react/components/highlight.d.ts +0 -14
  71. package/dist/shared-react/components/index.d.ts +0 -5
  72. package/dist/shared-react/components/marquee-redact.d.ts +0 -15
  73. package/dist/shared-react/components/pending-redactions.d.ts +0 -12
  74. package/dist/shared-react/components/redact-renderer-registration.d.ts +0 -5
  75. package/dist/shared-react/components/redact-renderers.d.ts +0 -7
  76. package/dist/shared-react/components/redaction-layer.d.ts +0 -16
  77. package/dist/shared-react/components/selection-redact.d.ts +0 -7
  78. package/dist/shared-react/components/types.d.ts +0 -9
  79. package/dist/shared-react/hooks/index.d.ts +0 -1
  80. package/dist/shared-react/hooks/use-redaction.d.ts +0 -15
  81. package/dist/shared-react/index.d.ts +0 -4
  82. package/dist/svelte/components/RedactRendererRegistration.svelte.d.ts +0 -7
  83. package/dist/svelte/components/annotations/RedactArea.svelte.d.ts +0 -5
  84. package/dist/svelte/components/annotations/RedactHighlight.svelte.d.ts +0 -5
  85. package/dist/svelte/components/annotations/index.d.ts +0 -2
  86. package/dist/svelte/components/highlight.svelte.d.ts +0 -14
  87. package/dist/svelte/components/index.d.ts +0 -8
  88. package/dist/svelte/components/marquee-redact.svelte.d.ts +0 -16
  89. package/dist/svelte/components/pending-redactions.svelte.d.ts +0 -15
  90. package/dist/svelte/components/redact-renderers.d.ts +0 -7
  91. package/dist/svelte/components/redaction-layer.svelte.d.ts +0 -20
  92. package/dist/svelte/components/selection-redact.svelte.d.ts +0 -8
  93. package/dist/svelte/hooks/index.d.ts +0 -1
  94. package/dist/svelte/hooks/use-redaction.svelte.d.ts +0 -21
  95. package/dist/svelte/index.cjs +0 -2
  96. package/dist/svelte/index.cjs.map +0 -1
  97. package/dist/svelte/index.d.ts +0 -5
  98. package/dist/svelte/index.js +0 -801
  99. package/dist/svelte/index.js.map +0 -1
  100. package/dist/svelte/types.d.ts +0 -10
  101. package/dist/vue/components/annotations/index.d.ts +0 -2
  102. package/dist/vue/components/annotations/redact-area.vue.d.ts +0 -6
  103. package/dist/vue/components/annotations/redact-highlight.vue.d.ts +0 -6
  104. package/dist/vue/components/highlight.vue.d.ts +0 -17
  105. package/dist/vue/components/index.d.ts +0 -8
  106. package/dist/vue/components/marquee-redact.vue.d.ts +0 -19
  107. package/dist/vue/components/pending-redactions.vue.d.ts +0 -68
  108. package/dist/vue/components/redact-renderer-registration.vue.d.ts +0 -13
  109. package/dist/vue/components/redact-renderers.d.ts +0 -7
  110. package/dist/vue/components/redaction-layer.vue.d.ts +0 -50
  111. package/dist/vue/components/selection-redact.vue.d.ts +0 -8
  112. package/dist/vue/components/types.d.ts +0 -9
  113. package/dist/vue/hooks/index.d.ts +0 -1
  114. package/dist/vue/hooks/use-redaction.d.ts +0 -12
  115. package/dist/vue/index.cjs +0 -2
  116. package/dist/vue/index.cjs.map +0 -1
  117. package/dist/vue/index.d.ts +0 -5
  118. package/dist/vue/index.js +0 -638
  119. package/dist/vue/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,1537 +1,225 @@
1
- import { BasePlugin, createBehaviorEmitter, refreshPages } from "@embedpdf/core";
2
- import { PdfAnnotationSubtype, PdfPermissionFlag, uuidV4, PdfTaskHelper, PdfErrorCode, Task } from "@embedpdf/models";
3
- import { defineAnnotationTool } from "@embedpdf/plugin-annotation";
4
- var RedactionMode = /* @__PURE__ */ ((RedactionMode2) => {
5
- RedactionMode2["Redact"] = "redact";
6
- RedactionMode2["MarqueeRedact"] = "marqueeRedact";
7
- RedactionMode2["RedactSelection"] = "redactSelection";
8
- return RedactionMode2;
9
- })(RedactionMode || {});
10
- const INIT_REDACTION_STATE = "REDACTION/INIT_STATE";
11
- const CLEANUP_REDACTION_STATE = "REDACTION/CLEANUP_STATE";
12
- const SET_ACTIVE_DOCUMENT = "REDACTION/SET_ACTIVE_DOCUMENT";
13
- const START_REDACTION = "START_REDACTION";
14
- const END_REDACTION = "END_REDACTION";
15
- const SET_ACTIVE_TYPE = "SET_ACTIVE_TYPE";
16
- const ADD_PENDING = "ADD_PENDING";
17
- const REMOVE_PENDING = "REMOVE_PENDING";
18
- const UPDATE_PENDING = "UPDATE_PENDING";
19
- const CLEAR_PENDING = "CLEAR_PENDING";
20
- const SELECT_PENDING = "SELECT_PENDING";
21
- const DESELECT_PENDING = "DESELECT_PENDING";
22
- function initRedactionState(documentId, state) {
23
- return { type: INIT_REDACTION_STATE, payload: { documentId, state } };
1
+ import { createCapabilityToken, definePlugin } from "@embedpdf/core";
2
+ import { AnnotationToken } from "@embedpdf/plugin-annotation/internal";
3
+ import { InteractionToken } from "@embedpdf/plugin-interaction";
4
+ import { SelectionToken } from "@embedpdf/plugin-selection";
5
+ //#region src/capability.ts
6
+ /** Applying destroys content — its own granted power, narrower than annotate. */
7
+ const REDACT_CAPABILITY = "doc.redact";
8
+ /** The annotation plugin's model-id convention for a durable ref. */
9
+ const idOf = (ref) => ref.kind === "objectNumber" ? `obj:${ref.annotObjectNumber}` : ref.kind === "nm" ? `nm:${ref.nm}` : `idx:${ref.pageObjectNumber}:${ref.index}`;
10
+ /** The PDF-space regions a redact mark targets: its quads' boxes, else `/Rect`. */
11
+ function regionsOf(dto) {
12
+ if (dto.quadPoints.length === 0) return [dto.rect];
13
+ return dto.quadPoints.map((q) => {
14
+ const xs = [
15
+ q.p1.x,
16
+ q.p2.x,
17
+ q.p3.x,
18
+ q.p4.x
19
+ ];
20
+ const ys = [
21
+ q.p1.y,
22
+ q.p2.y,
23
+ q.p3.y,
24
+ q.p4.y
25
+ ];
26
+ return {
27
+ left: Math.min(...xs),
28
+ right: Math.max(...xs),
29
+ bottom: Math.min(...ys),
30
+ top: Math.max(...ys)
31
+ };
32
+ });
24
33
  }
25
- function cleanupRedactionState(documentId) {
26
- return { type: CLEANUP_REDACTION_STATE, payload: documentId };
34
+ /** Positive-area intersection — the engine's collateral rule. */
35
+ const intersects = (a, b) => Math.min(a.right, b.right) > Math.max(a.left, b.left) && Math.min(a.top, b.top) > Math.max(a.bottom, b.bottom);
36
+ function createRedactionCapability(ctx) {
37
+ const anno = ctx.get(AnnotationToken);
38
+ const appliedCallbacks = /* @__PURE__ */ new Set();
39
+ const requireDoc = () => {
40
+ const doc = ctx.doc;
41
+ if (!doc) throw new Error("[redaction] no document bound");
42
+ return doc;
43
+ };
44
+ const requireService = () => {
45
+ const doc = requireDoc();
46
+ if (!doc.redaction) throw new Error("[redaction] engine has no redaction service");
47
+ return doc.redaction;
48
+ };
49
+ const pons = () => (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber);
50
+ const pageIndexOf = (pon) => ctx.document()?.pages.find((p) => p.pageObjectNumber === pon)?.index ?? -1;
51
+ const pendingOn = (pon) => anno.list(pon).filter((a) => a.subtype === "redact");
52
+ const collectPending = () => {
53
+ const items = [];
54
+ for (const pon of pons()) for (const dto of pendingOn(pon)) items.push({
55
+ id: idOf(dto.ref),
56
+ ref: dto.ref,
57
+ pageObjectNumber: pon,
58
+ pageIndex: pageIndexOf(pon),
59
+ kind: dto.quadPoints.length > 0 ? "text" : "area",
60
+ overlayText: dto.overlayText
61
+ });
62
+ return items;
63
+ };
64
+ const fireApplied = (result) => {
65
+ for (const cb of appliedCallbacks) try {
66
+ cb(result);
67
+ } catch (err) {
68
+ console.error("[redaction] onApplied callback failed", err);
69
+ }
70
+ };
71
+ const runApply = async (scope) => {
72
+ const service = requireService();
73
+ ctx.dispatch({ type: "APPLY_STARTED" });
74
+ try {
75
+ const result = await service.apply(scope);
76
+ ctx.dispatch({
77
+ type: "APPLY_FINISHED",
78
+ result
79
+ });
80
+ fireApplied(result);
81
+ return result;
82
+ } catch (err) {
83
+ ctx.dispatch({
84
+ type: "APPLY_FINISHED",
85
+ result: null
86
+ });
87
+ throw err;
88
+ }
89
+ };
90
+ const doc = ctx.doc;
91
+ if (doc) ctx.cleanup(doc.events.subscribe((event) => {
92
+ if (event.type !== "redaction.applied") return;
93
+ if (event.origin.kind !== "remote") return;
94
+ ctx.dispatch({
95
+ type: "APPLY_FINISHED",
96
+ result: event
97
+ });
98
+ fireApplied(event);
99
+ }));
100
+ return {
101
+ canApply: () => (ctx.doc?.redaction !== void 0 && ctx.doc?.security.allows(REDACT_CAPABILITY)) ?? false,
102
+ isApplying: () => ctx.getState().applying,
103
+ lastResult: () => ctx.getState().lastResult,
104
+ enableRedact: () => ctx.tryGet(InteractionToken)?.activateTool("redact"),
105
+ toggleRedact: () => {
106
+ const interaction = ctx.tryGet(InteractionToken);
107
+ if (!interaction) return;
108
+ interaction.activateTool(interaction.activeToolId() === "redact" ? "pointer" : "redact");
109
+ },
110
+ isRedactActive: () => ctx.tryGet(InteractionToken)?.activeToolId() === "redact",
111
+ queueCurrentSelection: async () => {
112
+ const selection = ctx.tryGet(SelectionToken);
113
+ if (!selection || !selection.hasSelection()) return false;
114
+ const snapshot = selection.snapshot();
115
+ for (const page of snapshot.pages) {
116
+ if (page.rects.length === 0) continue;
117
+ anno.createMarkup("redact", page.pon, page.rects, "redact");
118
+ }
119
+ selection.clear();
120
+ return true;
121
+ },
122
+ preparePending: async () => {
123
+ await Promise.all(pons().map((pon) => anno.ensurePage(pon)));
124
+ },
125
+ getPending: collectPending,
126
+ pendingCount: () => collectPending().length,
127
+ estimateCollateral: (ids) => {
128
+ const wanted = ids ? new Set(ids) : null;
129
+ let count = 0;
130
+ for (const pon of pons()) {
131
+ const all = anno.list(pon);
132
+ const marks = all.filter((a) => a.subtype === "redact" && (!wanted || wanted.has(idOf(a.ref))));
133
+ if (marks.length === 0) continue;
134
+ const regions = marks.flatMap(regionsOf);
135
+ for (const other of all) {
136
+ if (other.subtype === "redact") continue;
137
+ if (regions.some((r) => intersects(r, other.rect))) count++;
138
+ }
139
+ }
140
+ return count;
141
+ },
142
+ setLabel: async (ref, patch) => {
143
+ const current = anno.get(ref);
144
+ if (!current || current.subtype !== "redact") throw new Error("[redaction] setLabel target is not a redact annotation");
145
+ await anno.update(ref, {
146
+ subtype: "redact",
147
+ ...patch.overlayText !== void 0 ? { overlayText: patch.overlayText } : {},
148
+ ...patch.repeat !== void 0 ? { repeat: patch.repeat } : {},
149
+ fontFamily: current.fontFamily,
150
+ fontSize: current.fontSize,
151
+ fontColor: current.fontColor,
152
+ textAlign: current.textAlign
153
+ });
154
+ },
155
+ apply: async (ids) => {
156
+ const wanted = new Set(ids);
157
+ const refs = collectPending().filter((p) => wanted.has(p.id)).map((p) => p.ref);
158
+ if (refs.length === 0) throw new Error("[redaction] no matching pending redactions");
159
+ return runApply({
160
+ kind: "annotations",
161
+ refs
162
+ });
163
+ },
164
+ applyAll: async () => {
165
+ const pageObjectNumbers = pons();
166
+ if (pageObjectNumbers.length === 0) throw new Error("[redaction] document has no pages");
167
+ return runApply({
168
+ kind: "pages",
169
+ pageObjectNumbers
170
+ });
171
+ },
172
+ onApplied: (cb) => {
173
+ appliedCallbacks.add(cb);
174
+ return () => appliedCallbacks.delete(cb);
175
+ }
176
+ };
27
177
  }
28
- const addPending = (documentId, items) => ({
29
- type: ADD_PENDING,
30
- payload: { documentId, items }
178
+ //#endregion
179
+ //#region src/reducer.ts
180
+ const initialRedactionState = () => ({
181
+ applying: false,
182
+ lastResult: null
31
183
  });
32
- const removePending = (documentId, page, id) => ({
33
- type: REMOVE_PENDING,
34
- payload: { documentId, page, id }
35
- });
36
- const clearPending = (documentId) => ({
37
- type: CLEAR_PENDING,
38
- payload: documentId
39
- });
40
- const updatePending = (documentId, page, id, patch) => ({
41
- type: UPDATE_PENDING,
42
- payload: { documentId, page, id, patch }
43
- });
44
- const startRedaction = (documentId, mode) => ({
45
- type: START_REDACTION,
46
- payload: { documentId, mode }
47
- });
48
- const endRedaction = (documentId) => ({
49
- type: END_REDACTION,
50
- payload: documentId
51
- });
52
- const selectPending = (documentId, page, id) => ({
53
- type: SELECT_PENDING,
54
- payload: { documentId, page, id }
55
- });
56
- const deselectPending = (documentId) => ({
57
- type: DESELECT_PENDING,
58
- payload: documentId
59
- });
60
- const calculatePendingCount = (pending) => {
61
- return Object.values(pending).reduce((total, items) => total + items.length, 0);
62
- };
63
- const initialDocumentState = {
64
- isRedacting: false,
65
- activeType: null,
66
- pending: {},
67
- pendingCount: 0,
68
- selected: null
184
+ const redactionReducer = (state, action) => {
185
+ switch (action.type) {
186
+ case "APPLY_STARTED": return {
187
+ ...state,
188
+ applying: true
189
+ };
190
+ case "APPLY_FINISHED": return {
191
+ applying: false,
192
+ lastResult: action.result ?? state.lastResult
193
+ };
194
+ default: return state;
195
+ }
69
196
  };
70
- const initialState = {
71
- documents: {},
72
- activeDocumentId: null
73
- };
74
- const redactionReducer = (state = initialState, action) => {
75
- switch (action.type) {
76
- case INIT_REDACTION_STATE: {
77
- const { documentId, state: docState } = action.payload;
78
- return {
79
- ...state,
80
- documents: {
81
- ...state.documents,
82
- [documentId]: docState
83
- },
84
- // Set as active if no active document
85
- activeDocumentId: state.activeDocumentId ?? documentId
86
- };
87
- }
88
- case CLEANUP_REDACTION_STATE: {
89
- const documentId = action.payload;
90
- const { [documentId]: removed, ...remainingDocs } = state.documents;
91
- return {
92
- ...state,
93
- documents: remainingDocs,
94
- activeDocumentId: state.activeDocumentId === documentId ? null : state.activeDocumentId
95
- };
96
- }
97
- case SET_ACTIVE_DOCUMENT: {
98
- return {
99
- ...state,
100
- activeDocumentId: action.payload
101
- };
102
- }
103
- case ADD_PENDING: {
104
- const { documentId, items } = action.payload;
105
- const docState = state.documents[documentId];
106
- if (!docState) return state;
107
- const next = { ...docState.pending };
108
- for (const item of items) {
109
- const existing = next[item.page] ?? [];
110
- if (existing.some((it) => it.id === item.id)) continue;
111
- next[item.page] = existing.concat(item);
112
- }
113
- return {
114
- ...state,
115
- documents: {
116
- ...state.documents,
117
- [documentId]: {
118
- ...docState,
119
- pending: next,
120
- pendingCount: calculatePendingCount(next)
121
- }
122
- }
123
- };
124
- }
125
- case REMOVE_PENDING: {
126
- const { documentId, page, id } = action.payload;
127
- const docState = state.documents[documentId];
128
- if (!docState) return state;
129
- const list = docState.pending[page] ?? [];
130
- const filtered = list.filter((it) => it.id !== id);
131
- const next = { ...docState.pending, [page]: filtered };
132
- const stillSelected = docState.selected && !(docState.selected.page === page && docState.selected.id === id);
133
- return {
134
- ...state,
135
- documents: {
136
- ...state.documents,
137
- [documentId]: {
138
- ...docState,
139
- pending: next,
140
- pendingCount: calculatePendingCount(next),
141
- selected: stillSelected ? docState.selected : null
142
- }
143
- }
144
- };
145
- }
146
- case UPDATE_PENDING: {
147
- const { documentId, page, id, patch } = action.payload;
148
- const docState = state.documents[documentId];
149
- if (!docState) return state;
150
- const list = docState.pending[page] ?? [];
151
- const updated = list.map((item) => item.id === id ? { ...item, ...patch } : item);
152
- return {
153
- ...state,
154
- documents: {
155
- ...state.documents,
156
- [documentId]: {
157
- ...docState,
158
- pending: { ...docState.pending, [page]: updated }
159
- }
160
- }
161
- };
162
- }
163
- case CLEAR_PENDING: {
164
- const documentId = action.payload;
165
- const docState = state.documents[documentId];
166
- if (!docState) return state;
167
- return {
168
- ...state,
169
- documents: {
170
- ...state.documents,
171
- [documentId]: {
172
- ...docState,
173
- pending: {},
174
- pendingCount: 0,
175
- selected: null
176
- }
177
- }
178
- };
179
- }
180
- case SELECT_PENDING: {
181
- const { documentId, page, id } = action.payload;
182
- const docState = state.documents[documentId];
183
- if (!docState) return state;
184
- return {
185
- ...state,
186
- documents: {
187
- ...state.documents,
188
- [documentId]: {
189
- ...docState,
190
- selected: { page, id }
191
- }
192
- }
193
- };
194
- }
195
- case DESELECT_PENDING: {
196
- const documentId = action.payload;
197
- const docState = state.documents[documentId];
198
- if (!docState) return state;
199
- return {
200
- ...state,
201
- documents: {
202
- ...state.documents,
203
- [documentId]: {
204
- ...docState,
205
- selected: null
206
- }
207
- }
208
- };
209
- }
210
- case START_REDACTION: {
211
- const { documentId, mode } = action.payload;
212
- const docState = state.documents[documentId];
213
- if (!docState) return state;
214
- return {
215
- ...state,
216
- documents: {
217
- ...state.documents,
218
- [documentId]: {
219
- ...docState,
220
- isRedacting: true,
221
- activeType: mode
222
- }
223
- }
224
- };
225
- }
226
- case END_REDACTION: {
227
- const documentId = action.payload;
228
- const docState = state.documents[documentId];
229
- if (!docState) return state;
230
- return {
231
- ...state,
232
- documents: {
233
- ...state.documents,
234
- [documentId]: {
235
- ...docState,
236
- isRedacting: false,
237
- activeType: null
238
- }
239
- }
240
- };
241
- }
242
- case SET_ACTIVE_TYPE: {
243
- const { documentId, mode } = action.payload;
244
- const docState = state.documents[documentId];
245
- if (!docState) return state;
246
- return {
247
- ...state,
248
- documents: {
249
- ...state.documents,
250
- [documentId]: {
251
- ...docState,
252
- activeType: mode
253
- }
254
- }
255
- };
256
- }
257
- default:
258
- return state;
259
- }
260
- };
261
- const redactTool = defineAnnotationTool({
262
- id: "redact",
263
- name: "Redact",
264
- categories: ["annotation", "redaction"],
265
- matchScore: (a) => a.type === PdfAnnotationSubtype.REDACT ? 10 : 0,
266
- interaction: {
267
- mode: RedactionMode.Redact,
268
- exclusive: false,
269
- cursor: "crosshair",
270
- textSelection: true,
271
- // Dynamic based on whether it's a text or area redaction
272
- isDraggable: (anno) => {
273
- var _a;
274
- if (anno.type !== PdfAnnotationSubtype.REDACT) return true;
275
- return !((_a = anno.segmentRects) == null ? void 0 : _a.length);
276
- },
277
- isResizable: (anno) => {
278
- var _a;
279
- if (anno.type !== PdfAnnotationSubtype.REDACT) return true;
280
- return !((_a = anno.segmentRects) == null ? void 0 : _a.length);
281
- },
282
- isRotatable: false,
283
- lockAspectRatio: false,
284
- isGroupDraggable: false,
285
- isGroupResizable: false,
286
- isGroupRotatable: false
287
- },
288
- defaults: {
289
- type: PdfAnnotationSubtype.REDACT,
290
- color: "#000000",
291
- overlayColor: "#FFFFFF",
292
- strokeColor: "#E44234",
293
- opacity: 1
294
- },
295
- behavior: {
296
- useAppearanceStream: false
297
- }
197
+ //#endregion
198
+ //#region src/types.ts
199
+ const RedactionToken = createCapabilityToken("redaction");
200
+ //#endregion
201
+ //#region src/redaction.plugin.ts
202
+ /**
203
+ * Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage
204
+ * model. Marking is annotation-plane work (the composed `redact` tool ships
205
+ * with plugin-annotation); this plugin wraps `doc.redaction.apply`, projects
206
+ * the pending queue out of annotation state, and estimates collateral for
207
+ * confirm dialogs. It owns no mark state of its own.
208
+ *
209
+ * Trust boundary: on a layered document, applying rewrites the LAYER's bytes;
210
+ * the immutable base keeps the original. See the package README.
211
+ */
212
+ const redactionPlugin = () => definePlugin({
213
+ id: "redaction",
214
+ token: RedactionToken,
215
+ scope: "document",
216
+ requires: [AnnotationToken],
217
+ optional: [InteractionToken, SelectionToken],
218
+ initialState: initialRedactionState,
219
+ reduce: redactionReducer,
220
+ capability: createRedactionCapability
298
221
  });
299
- const redactTools = [redactTool];
300
- const _RedactionPlugin = class _RedactionPlugin extends BasePlugin {
301
- constructor(id, registry, config) {
302
- var _a, _b, _c, _d;
303
- super(id, registry);
304
- this.redactionSelection$ = /* @__PURE__ */ new Map();
305
- this.redactionMarquee$ = /* @__PURE__ */ new Map();
306
- this.pending$ = createBehaviorEmitter();
307
- this.selected$ = createBehaviorEmitter();
308
- this.state$ = createBehaviorEmitter();
309
- this.events$ = createBehaviorEmitter();
310
- this.documentUnsubscribers = /* @__PURE__ */ new Map();
311
- this.config = config;
312
- this.selectionCapability = (_a = this.registry.getPlugin("selection")) == null ? void 0 : _a.provides();
313
- this.interactionManagerCapability = (_b = this.registry.getPlugin("interaction-manager")) == null ? void 0 : _b.provides();
314
- this.annotationCapability = (_c = this.registry.getPlugin("annotation")) == null ? void 0 : _c.provides();
315
- this.historyCapability = (_d = this.registry.getPlugin("history")) == null ? void 0 : _d.provides();
316
- if (this.config.useAnnotationMode) {
317
- if (this.annotationCapability) {
318
- this.useAnnotationMode = true;
319
- } else {
320
- this.logger.warn(
321
- "RedactionPlugin",
322
- "ConfigError",
323
- "useAnnotationMode is enabled but annotation plugin is not available. Falling back to legacy mode."
324
- );
325
- this.useAnnotationMode = false;
326
- }
327
- } else {
328
- this.useAnnotationMode = false;
329
- }
330
- if (this.useAnnotationMode && this.annotationCapability) {
331
- for (const tool of redactTools) {
332
- this.annotationCapability.addTool(tool);
333
- }
334
- }
335
- this.setupRedactionModes();
336
- if (!this.useAnnotationMode && this.annotationCapability) {
337
- this.logger.info(
338
- "RedactionPlugin",
339
- "LegacyMode",
340
- "Using legacy redaction mode. Set useAnnotationMode: true in config to use annotation-based redactions."
341
- );
342
- }
343
- this.setupModeChangeListener();
344
- }
345
- /**
346
- * Setup redaction modes - registers all interaction modes for redaction.
347
- * Works for both annotation mode and legacy mode.
348
- */
349
- setupRedactionModes() {
350
- if (!this.interactionManagerCapability) return;
351
- this.interactionManagerCapability.registerMode({
352
- id: RedactionMode.Redact,
353
- scope: "page",
354
- exclusive: false,
355
- cursor: "crosshair"
356
- });
357
- this.interactionManagerCapability.registerMode({
358
- id: RedactionMode.MarqueeRedact,
359
- scope: "page",
360
- exclusive: false,
361
- cursor: "crosshair"
362
- });
363
- this.interactionManagerCapability.registerMode({
364
- id: RedactionMode.RedactSelection,
365
- scope: "page",
366
- exclusive: false
367
- });
368
- }
369
- /**
370
- * Setup mode change listener - handles all redaction modes
371
- */
372
- setupModeChangeListener() {
373
- var _a;
374
- (_a = this.interactionManagerCapability) == null ? void 0 : _a.onModeChange((modeState) => {
375
- const documentId = modeState.documentId;
376
- const isRedactionMode = modeState.activeMode === RedactionMode.Redact || modeState.activeMode === RedactionMode.MarqueeRedact || modeState.activeMode === RedactionMode.RedactSelection;
377
- if (isRedactionMode) {
378
- this.dispatch(startRedaction(documentId, modeState.activeMode));
379
- } else {
380
- const docState = this.getDocumentState(documentId);
381
- if (docState == null ? void 0 : docState.isRedacting) {
382
- this.dispatch(endRedaction(documentId));
383
- }
384
- }
385
- });
386
- }
387
- // ─────────────────────────────────────────────────────────
388
- // Document Lifecycle Hooks (from BasePlugin)
389
- // ─────────────────────────────────────────────────────────
390
- onDocumentLoadingStarted(documentId) {
391
- this.dispatch(
392
- initRedactionState(documentId, {
393
- ...initialDocumentState
394
- })
395
- );
396
- this.redactionSelection$.set(documentId, createBehaviorEmitter());
397
- this.redactionMarquee$.set(
398
- documentId,
399
- createBehaviorEmitter()
400
- );
401
- const unsubscribers = [];
402
- if (this.selectionCapability) {
403
- const selectionScope = this.selectionCapability.forDocument(documentId);
404
- const unsubSelection = selectionScope.onSelectionChange(() => {
405
- const docState = this.getDocumentState(documentId);
406
- if (!(docState == null ? void 0 : docState.isRedacting)) return;
407
- const formattedSelection = selectionScope.getFormattedSelection();
408
- const emitter = this.redactionSelection$.get(documentId);
409
- emitter == null ? void 0 : emitter.emit(formattedSelection);
410
- });
411
- const unsubEndSelection = selectionScope.onEndSelection(() => {
412
- const docState = this.getDocumentState(documentId);
413
- if (!(docState == null ? void 0 : docState.isRedacting)) return;
414
- if (!this.checkPermission(documentId, PdfPermissionFlag.ModifyContents)) return;
415
- const formattedSelection = selectionScope.getFormattedSelection();
416
- if (!formattedSelection.length) return;
417
- const textTask = selectionScope.getSelectedText();
418
- const emitter = this.redactionSelection$.get(documentId);
419
- emitter == null ? void 0 : emitter.emit([]);
420
- selectionScope.clear();
421
- textTask.wait(
422
- (textArr) => {
423
- const text = textArr.join(" ");
424
- this.createRedactionsFromSelection(documentId, formattedSelection, text);
425
- },
426
- () => {
427
- this.createRedactionsFromSelection(documentId, formattedSelection);
428
- }
429
- );
430
- });
431
- const unsubMarqueeChange = selectionScope.onMarqueeChange((event) => {
432
- var _a;
433
- const docState = this.getDocumentState(documentId);
434
- if (!(docState == null ? void 0 : docState.isRedacting)) return;
435
- (_a = this.redactionMarquee$.get(documentId)) == null ? void 0 : _a.emit({ pageIndex: event.pageIndex, rect: event.rect, modeId: event.modeId });
436
- });
437
- const unsubMarqueeEnd = selectionScope.onMarqueeEnd((event) => {
438
- const docState = this.getDocumentState(documentId);
439
- if (!(docState == null ? void 0 : docState.isRedacting)) return;
440
- if (!this.checkPermission(documentId, PdfPermissionFlag.ModifyContents)) return;
441
- if (this.useAnnotationMode) {
442
- this.createRedactAnnotationFromArea(documentId, event.pageIndex, event.rect);
443
- } else {
444
- const redactionColor = this.config.drawBlackBoxes ? "#000000" : "transparent";
445
- const item = {
446
- id: uuidV4(),
447
- kind: "area",
448
- page: event.pageIndex,
449
- rect: event.rect,
450
- source: "legacy",
451
- markColor: "#FF0000",
452
- redactionColor
453
- };
454
- this.dispatch(addPending(documentId, [item]));
455
- this.selectPending(event.pageIndex, item.id, documentId);
456
- }
457
- });
458
- const unsubEmptySpaceClick = selectionScope.onEmptySpaceClick(() => {
459
- this.deselectPending(documentId);
460
- });
461
- unsubscribers.push(
462
- unsubSelection,
463
- unsubEndSelection,
464
- unsubMarqueeChange,
465
- unsubMarqueeEnd,
466
- unsubEmptySpaceClick
467
- );
468
- }
469
- if (this.useAnnotationMode && this.annotationCapability) {
470
- const annoScope = this.annotationCapability.forDocument(documentId);
471
- const unsubEvents = annoScope.onAnnotationEvent((event) => {
472
- var _a;
473
- if (event.type === "loaded") {
474
- this.syncFromAnnotationLoad(documentId);
475
- return;
476
- }
477
- if (((_a = event.annotation) == null ? void 0 : _a.type) !== PdfAnnotationSubtype.REDACT) return;
478
- const redactAnno = event.annotation;
479
- if (event.type === "create") {
480
- this.syncFromAnnotationCreate(documentId, redactAnno);
481
- this.events$.emit({
482
- type: "add",
483
- documentId,
484
- items: [this.annotationToRedactionItem(redactAnno)]
485
- });
486
- } else if (event.type === "update") {
487
- this.logger.debug("RedactionPlugin", "AnnotationUpdated", {
488
- documentId,
489
- redactAnno,
490
- patch: event.patch
491
- });
492
- this.syncFromAnnotationUpdate(
493
- documentId,
494
- redactAnno,
495
- event.patch
496
- );
497
- } else if (event.type === "delete") {
498
- this.syncFromAnnotationDelete(documentId, redactAnno);
499
- this.events$.emit({
500
- type: "remove",
501
- documentId,
502
- page: redactAnno.pageIndex,
503
- id: redactAnno.id
504
- });
505
- }
506
- });
507
- const unsubState = annoScope.onStateChange(() => {
508
- this.syncSelectionFromAnnotation(documentId);
509
- });
510
- unsubscribers.push(unsubEvents, unsubState);
511
- }
512
- this.documentUnsubscribers.set(documentId, unsubscribers);
513
- this.logger.debug(
514
- "RedactionPlugin",
515
- "DocumentOpened",
516
- `Initialized redaction state for document: ${documentId}`
517
- );
518
- }
519
- onDocumentLoaded(documentId) {
520
- var _a, _b, _c;
521
- (_a = this.selectionCapability) == null ? void 0 : _a.enableForMode(
522
- RedactionMode.Redact,
523
- {
524
- enableSelection: true,
525
- showSelectionRects: false,
526
- enableMarquee: true,
527
- showMarqueeRects: false
528
- },
529
- documentId
530
- );
531
- (_b = this.selectionCapability) == null ? void 0 : _b.enableForMode(
532
- RedactionMode.MarqueeRedact,
533
- {
534
- enableSelection: false,
535
- enableMarquee: true,
536
- showMarqueeRects: false
537
- },
538
- documentId
539
- );
540
- (_c = this.selectionCapability) == null ? void 0 : _c.enableForMode(
541
- RedactionMode.RedactSelection,
542
- {
543
- enableSelection: true,
544
- showSelectionRects: false
545
- },
546
- documentId
547
- );
548
- }
549
- onDocumentClosed(documentId) {
550
- this.dispatch(cleanupRedactionState(documentId));
551
- const emitter = this.redactionSelection$.get(documentId);
552
- emitter == null ? void 0 : emitter.clear();
553
- this.redactionSelection$.delete(documentId);
554
- const marqueeEmitter = this.redactionMarquee$.get(documentId);
555
- marqueeEmitter == null ? void 0 : marqueeEmitter.clear();
556
- this.redactionMarquee$.delete(documentId);
557
- const unsubscribers = this.documentUnsubscribers.get(documentId);
558
- if (unsubscribers) {
559
- unsubscribers.forEach((unsub) => unsub());
560
- this.documentUnsubscribers.delete(documentId);
561
- }
562
- this.logger.debug(
563
- "RedactionPlugin",
564
- "DocumentClosed",
565
- `Cleaned up redaction state for document: ${documentId}`
566
- );
567
- }
568
- // ─────────────────────────────────────────────────────────
569
- // Capability
570
- // ─────────────────────────────────────────────────────────
571
- async initialize(_config) {
572
- this.logger.info("RedactionPlugin", "Initialize", "Redaction plugin initialized");
573
- }
574
- buildCapability() {
575
- return {
576
- // Active document operations
577
- queueCurrentSelectionAsPending: () => this.queueCurrentSelectionAsPending(),
578
- // Unified redact mode
579
- enableRedact: () => this.enableRedact(),
580
- toggleRedact: () => this.toggleRedact(),
581
- isRedactActive: () => this.isRedactActive(),
582
- endRedact: () => this.endRedact(),
583
- // Legacy marquee mode
584
- enableMarqueeRedact: () => this.enableMarqueeRedact(),
585
- toggleMarqueeRedact: () => this.toggleMarqueeRedact(),
586
- isMarqueeRedactActive: () => this.isMarqueeRedactActive(),
587
- // Legacy selection mode
588
- enableRedactSelection: () => this.enableRedactSelection(),
589
- toggleRedactSelection: () => this.toggleRedactSelection(),
590
- isRedactSelectionActive: () => this.isRedactSelectionActive(),
591
- addPending: (items) => this.addPendingItems(items),
592
- removePending: (page, id) => this.removePendingItem(page, id),
593
- clearPending: () => this.clearPendingItems(),
594
- commitAllPending: () => this.commitAllPending(),
595
- commitPending: (page, id) => this.commitPendingOne(page, id),
596
- selectPending: (page, id) => this.selectPending(page, id),
597
- getSelectedPending: () => this.getSelectedPending(),
598
- deselectPending: () => this.deselectPending(),
599
- getState: () => this.getDocumentStateOrThrow(),
600
- // Document-scoped operations
601
- forDocument: (documentId) => this.createRedactionScope(documentId),
602
- // Events
603
- onPendingChange: this.pending$.on,
604
- onSelectedChange: this.selected$.on,
605
- onRedactionEvent: this.events$.on,
606
- onStateChange: this.state$.on
607
- };
608
- }
609
- // ─────────────────────────────────────────────────────────
610
- // Document Scoping
611
- // ─────────────────────────────────────────────────────────
612
- createRedactionScope(documentId) {
613
- return {
614
- queueCurrentSelectionAsPending: () => this.queueCurrentSelectionAsPending(documentId),
615
- // Unified redact mode
616
- enableRedact: () => this.enableRedact(documentId),
617
- toggleRedact: () => this.toggleRedact(documentId),
618
- isRedactActive: () => this.isRedactActive(documentId),
619
- endRedact: () => this.endRedact(documentId),
620
- // Legacy marquee mode
621
- enableMarqueeRedact: () => this.enableMarqueeRedact(documentId),
622
- toggleMarqueeRedact: () => this.toggleMarqueeRedact(documentId),
623
- isMarqueeRedactActive: () => this.isMarqueeRedactActive(documentId),
624
- // Legacy selection mode
625
- enableRedactSelection: () => this.enableRedactSelection(documentId),
626
- toggleRedactSelection: () => this.toggleRedactSelection(documentId),
627
- isRedactSelectionActive: () => this.isRedactSelectionActive(documentId),
628
- addPending: (items) => this.addPendingItems(items, documentId),
629
- removePending: (page, id) => this.removePendingItem(page, id, documentId),
630
- clearPending: () => this.clearPendingItems(documentId),
631
- commitAllPending: () => this.commitAllPending(documentId),
632
- commitPending: (page, id) => this.commitPendingOne(page, id, documentId),
633
- selectPending: (page, id) => this.selectPending(page, id, documentId),
634
- getSelectedPending: () => this.getSelectedPending(documentId),
635
- deselectPending: () => this.deselectPending(documentId),
636
- getState: () => this.getDocumentStateOrThrow(documentId),
637
- onPendingChange: (listener) => this.pending$.on((event) => {
638
- if (event.documentId === documentId) listener(event.pending);
639
- }),
640
- onSelectedChange: (listener) => this.selected$.on((event) => {
641
- if (event.documentId === documentId) listener(event.selected);
642
- }),
643
- onRedactionEvent: (listener) => this.events$.on((event) => {
644
- if (event.documentId === documentId) listener(event);
645
- }),
646
- onStateChange: (listener) => this.state$.on((event) => {
647
- if (event.documentId === documentId) listener(event.state);
648
- })
649
- };
650
- }
651
- // ─────────────────────────────────────────────────────────
652
- // State Helpers
653
- // ─────────────────────────────────────────────────────────
654
- /**
655
- * Get pending redactions derived from annotation plugin (annotation mode only)
656
- */
657
- getPendingFromAnnotations(documentId) {
658
- if (!this.annotationCapability) return {};
659
- try {
660
- const annoState = this.annotationCapability.forDocument(documentId).getState();
661
- const result = {};
662
- for (const ta of Object.values(annoState.byUid)) {
663
- if (ta.object.type === PdfAnnotationSubtype.REDACT) {
664
- const item = this.annotationToRedactionItem(ta.object);
665
- const page = ta.object.pageIndex;
666
- (result[page] ?? (result[page] = [])).push(item);
667
- }
668
- }
669
- return result;
670
- } catch {
671
- return {};
672
- }
673
- }
674
- getDocumentState(documentId) {
675
- const id = documentId ?? this.getActiveDocumentId();
676
- return this.state.documents[id] ?? null;
677
- }
678
- getDocumentStateOrThrow(documentId) {
679
- const state = this.getDocumentState(documentId);
680
- if (!state) {
681
- throw new Error(`Redaction state not found for document: ${documentId ?? "active"}`);
682
- }
683
- return state;
684
- }
685
- isRedactTool(tool) {
686
- return (tool == null ? void 0 : tool.id) === redactTool.id && tool.defaults.type === PdfAnnotationSubtype.REDACT;
687
- }
688
- getRedactTool() {
689
- var _a;
690
- const tool = (_a = this.annotationCapability) == null ? void 0 : _a.getTool("redact");
691
- return this.isRedactTool(tool) ? tool : void 0;
692
- }
693
- // ─────────────────────────────────────────────────────────
694
- // Annotation Mode State Sync
695
- // ─────────────────────────────────────────────────────────
696
- /**
697
- * Sync internal state when REDACT annotation is created.
698
- * Called from annotation event listener in annotation mode.
699
- */
700
- syncFromAnnotationCreate(documentId, annotation) {
701
- const item = this.annotationToRedactionItem(annotation);
702
- this.dispatch(addPending(documentId, [item]));
703
- }
704
- /**
705
- * Sync internal state when REDACT annotation is updated (moved/resized/color changed).
706
- * Called from annotation event listener in annotation mode.
707
- */
708
- syncFromAnnotationUpdate(documentId, annotation, patch) {
709
- if (!("rect" in patch) && !("segmentRects" in patch) && !("strokeColor" in patch) && !("color" in patch))
710
- return;
711
- const updatePatch = {};
712
- if (patch.rect) updatePatch.rect = patch.rect;
713
- if (patch.segmentRects) updatePatch.rects = patch.segmentRects;
714
- if (patch.strokeColor) updatePatch.markColor = patch.strokeColor;
715
- if (patch.color) updatePatch.redactionColor = patch.color;
716
- this.logger.debug("RedactionPlugin", "AnnotationUpdated", {
717
- documentId,
718
- annotation,
719
- patch: updatePatch
720
- });
721
- this.dispatch(updatePending(documentId, annotation.pageIndex, annotation.id, updatePatch));
722
- }
723
- /**
724
- * Sync internal state when REDACT annotation is deleted.
725
- * Called from annotation event listener in annotation mode.
726
- */
727
- syncFromAnnotationDelete(documentId, annotation) {
728
- this.dispatch(removePending(documentId, annotation.pageIndex, annotation.id));
729
- }
730
- /**
731
- * Sync internal state from existing REDACT annotations after initial load.
732
- * Called when annotation plugin emits 'loaded' event.
733
- */
734
- syncFromAnnotationLoad(documentId) {
735
- const pending = this.getPendingFromAnnotations(documentId);
736
- this.dispatch(clearPending(documentId));
737
- for (const [, items] of Object.entries(pending)) {
738
- if (items.length > 0) {
739
- this.dispatch(addPending(documentId, items));
740
- }
741
- }
742
- }
743
- /**
744
- * Sync selection state from annotation plugin's selected REDACT annotation.
745
- * Called when annotation plugin state changes.
746
- */
747
- syncSelectionFromAnnotation(documentId) {
748
- var _a;
749
- const annoState = (_a = this.annotationCapability) == null ? void 0 : _a.forDocument(documentId).getState();
750
- if (!annoState) return;
751
- const selectedRedact = annoState.selectedUids.map((uid) => annoState.byUid[uid]).find((ta) => (ta == null ? void 0 : ta.object.type) === PdfAnnotationSubtype.REDACT);
752
- if (selectedRedact) {
753
- const obj = selectedRedact.object;
754
- this.dispatch(selectPending(documentId, obj.pageIndex, obj.id));
755
- } else {
756
- const docState = this.getDocumentState(documentId);
757
- if (docState == null ? void 0 : docState.selected) {
758
- this.dispatch(deselectPending(documentId));
759
- }
760
- }
761
- }
762
- // ─────────────────────────────────────────────────────────
763
- // Core Operations
764
- // ─────────────────────────────────────────────────────────
765
- addPendingItems(items, documentId) {
766
- const id = documentId ?? this.getActiveDocumentId();
767
- if (!this.checkPermission(id, PdfPermissionFlag.ModifyContents)) {
768
- this.logger.debug(
769
- "RedactionPlugin",
770
- "AddPendingItems",
771
- `Cannot add redactions: document ${id} lacks ModifyContents permission`
772
- );
773
- return;
774
- }
775
- if (this.useAnnotationMode) {
776
- if (!this.annotationCapability) return;
777
- const annoScope = this.annotationCapability.forDocument(id);
778
- for (const item of items) {
779
- const annotation = this.redactionItemToAnnotation(item);
780
- annoScope.createAnnotation(item.page, annotation);
781
- }
782
- if (items.length > 0) {
783
- const lastItem = items[items.length - 1];
784
- annoScope.selectAnnotation(lastItem.page, lastItem.id);
785
- }
786
- } else {
787
- this.dispatch(addPending(id, items));
788
- }
789
- this.events$.emit({ type: "add", documentId: id, items });
790
- }
791
- removePendingItem(page, itemId, documentId) {
792
- var _a;
793
- const id = documentId ?? this.getActiveDocumentId();
794
- if (this.useAnnotationMode) {
795
- (_a = this.annotationCapability) == null ? void 0 : _a.forDocument(id).deleteAnnotation(page, itemId);
796
- } else {
797
- this.dispatch(removePending(id, page, itemId));
798
- }
799
- this.events$.emit({ type: "remove", documentId: id, page, id: itemId });
800
- }
801
- clearPendingItems(documentId) {
802
- var _a;
803
- const id = documentId ?? this.getActiveDocumentId();
804
- if (this.useAnnotationMode) {
805
- const pending = this.getPendingFromAnnotations(id);
806
- const annoScope = (_a = this.annotationCapability) == null ? void 0 : _a.forDocument(id);
807
- for (const [pageStr, items] of Object.entries(pending)) {
808
- const page = Number(pageStr);
809
- for (const item of items) {
810
- annoScope == null ? void 0 : annoScope.deleteAnnotation(page, item.id);
811
- }
812
- }
813
- } else {
814
- this.dispatch(clearPending(id));
815
- }
816
- this.events$.emit({ type: "clear", documentId: id });
817
- }
818
- selectPending(page, itemId, documentId) {
819
- var _a, _b, _c;
820
- const id = documentId ?? this.getActiveDocumentId();
821
- if (this.useAnnotationMode) {
822
- (_a = this.annotationCapability) == null ? void 0 : _a.forDocument(id).selectAnnotation(page, itemId);
823
- } else {
824
- this.dispatch(selectPending(id, page, itemId));
825
- (_b = this.interactionManagerCapability) == null ? void 0 : _b.claimPageActivity(id, "redaction-selection", page);
826
- }
827
- (_c = this.selectionCapability) == null ? void 0 : _c.forDocument(id).clear();
828
- }
829
- getSelectedPending(documentId) {
830
- var _a;
831
- const id = documentId ?? this.getActiveDocumentId();
832
- return ((_a = this.getDocumentState(id)) == null ? void 0 : _a.selected) ?? null;
833
- }
834
- deselectPending(documentId) {
835
- var _a, _b;
836
- const id = documentId ?? this.getActiveDocumentId();
837
- if (this.useAnnotationMode) {
838
- (_a = this.annotationCapability) == null ? void 0 : _a.forDocument(id).deselectAnnotation();
839
- } else {
840
- this.dispatch(deselectPending(id));
841
- (_b = this.interactionManagerCapability) == null ? void 0 : _b.releasePageActivity(id, "redaction-selection");
842
- }
843
- }
844
- // ─────────────────────────────────────────────────────────
845
- // Legacy Selection Mode (text-based redactions)
846
- // ─────────────────────────────────────────────────────────
847
- enableRedactSelection(documentId) {
848
- var _a;
849
- const id = documentId ?? this.getActiveDocumentId();
850
- if (!this.checkPermission(id, PdfPermissionFlag.ModifyContents)) {
851
- this.logger.debug(
852
- "RedactionPlugin",
853
- "EnableRedactSelection",
854
- `Cannot enable redact selection: document ${id} lacks ModifyContents permission`
855
- );
856
- return;
857
- }
858
- (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id).activate(RedactionMode.RedactSelection);
859
- }
860
- toggleRedactSelection(documentId) {
861
- var _a;
862
- const id = documentId ?? this.getActiveDocumentId();
863
- const scope = (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id);
864
- const activeMode = scope == null ? void 0 : scope.getActiveMode();
865
- if (activeMode === RedactionMode.RedactSelection) {
866
- scope == null ? void 0 : scope.activateDefaultMode();
867
- } else {
868
- if (!this.checkPermission(id, PdfPermissionFlag.ModifyContents)) {
869
- return;
870
- }
871
- scope == null ? void 0 : scope.activate(RedactionMode.RedactSelection);
872
- }
873
- }
874
- isRedactSelectionActive(documentId) {
875
- var _a;
876
- const id = documentId ?? this.getActiveDocumentId();
877
- const activeMode = (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id).getActiveMode();
878
- return activeMode === RedactionMode.Redact || activeMode === RedactionMode.RedactSelection;
879
- }
880
- // ─────────────────────────────────────────────────────────
881
- // Legacy Marquee Mode (area-based redactions)
882
- // ─────────────────────────────────────────────────────────
883
- enableMarqueeRedact(documentId) {
884
- var _a;
885
- const id = documentId ?? this.getActiveDocumentId();
886
- if (!this.checkPermission(id, PdfPermissionFlag.ModifyContents)) {
887
- this.logger.debug(
888
- "RedactionPlugin",
889
- "EnableMarqueeRedact",
890
- `Cannot enable marquee redact: document ${id} lacks ModifyContents permission`
891
- );
892
- return;
893
- }
894
- (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id).activate(RedactionMode.MarqueeRedact);
895
- }
896
- toggleMarqueeRedact(documentId) {
897
- var _a;
898
- const id = documentId ?? this.getActiveDocumentId();
899
- const scope = (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id);
900
- const activeMode = scope == null ? void 0 : scope.getActiveMode();
901
- if (activeMode === RedactionMode.MarqueeRedact) {
902
- scope == null ? void 0 : scope.activateDefaultMode();
903
- } else {
904
- if (!this.checkPermission(id, PdfPermissionFlag.ModifyContents)) {
905
- return;
906
- }
907
- scope == null ? void 0 : scope.activate(RedactionMode.MarqueeRedact);
908
- }
909
- }
910
- isMarqueeRedactActive(documentId) {
911
- var _a;
912
- const id = documentId ?? this.getActiveDocumentId();
913
- const activeMode = (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id).getActiveMode();
914
- return activeMode === RedactionMode.Redact || activeMode === RedactionMode.MarqueeRedact;
915
- }
916
- // ─────────────────────────────────────────────────────────
917
- // Unified Redact Mode (recommended)
918
- // ─────────────────────────────────────────────────────────
919
- enableRedact(documentId) {
920
- var _a;
921
- const id = documentId ?? this.getActiveDocumentId();
922
- if (!this.checkPermission(id, PdfPermissionFlag.ModifyContents)) {
923
- this.logger.debug(
924
- "RedactionPlugin",
925
- "EnableRedact",
926
- `Cannot enable redact mode: document ${id} lacks ModifyContents permission`
927
- );
928
- return;
929
- }
930
- (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id).activate(RedactionMode.Redact);
931
- }
932
- toggleRedact(documentId) {
933
- var _a;
934
- const id = documentId ?? this.getActiveDocumentId();
935
- const scope = (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id);
936
- const activeMode = scope == null ? void 0 : scope.getActiveMode();
937
- if (activeMode === RedactionMode.Redact) {
938
- scope == null ? void 0 : scope.activateDefaultMode();
939
- } else {
940
- if (!this.checkPermission(id, PdfPermissionFlag.ModifyContents)) {
941
- return;
942
- }
943
- scope == null ? void 0 : scope.activate(RedactionMode.Redact);
944
- }
945
- }
946
- isRedactActive(documentId) {
947
- var _a;
948
- const id = documentId ?? this.getActiveDocumentId();
949
- const activeMode = (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id).getActiveMode();
950
- return activeMode === RedactionMode.Redact;
951
- }
952
- endRedact(documentId) {
953
- var _a;
954
- const id = documentId ?? this.getActiveDocumentId();
955
- (_a = this.interactionManagerCapability) == null ? void 0 : _a.forDocument(id).activateDefaultMode();
956
- }
957
- // ─────────────────────────────────────────────────────────
958
- // Public Methods
959
- // ─────────────────────────────────────────────────────────
960
- onRedactionSelectionChange(documentId, callback) {
961
- const emitter = this.redactionSelection$.get(documentId);
962
- return (emitter == null ? void 0 : emitter.on(callback)) ?? (() => {
963
- });
964
- }
965
- onRedactionMarqueeChange(documentId, callback) {
966
- const emitter = this.redactionMarquee$.get(documentId);
967
- return (emitter == null ? void 0 : emitter.on(callback)) ?? (() => {
968
- });
969
- }
970
- /**
971
- * Get the stroke color for redaction previews.
972
- * In annotation mode: returns tool's defaults.strokeColor
973
- * In legacy mode: returns hardcoded red
974
- */
975
- getPreviewStrokeColor() {
976
- var _a;
977
- if (this.useAnnotationMode) {
978
- return ((_a = this.getRedactTool()) == null ? void 0 : _a.defaults.strokeColor) ?? "#FF0000";
979
- }
980
- return "#FF0000";
981
- }
982
- queueCurrentSelectionAsPending(documentId) {
983
- const id = documentId ?? this.getActiveDocumentId();
984
- if (!this.selectionCapability) {
985
- return PdfTaskHelper.reject({
986
- code: PdfErrorCode.NotFound,
987
- message: "[RedactionPlugin] selection plugin required"
988
- });
989
- }
990
- const coreDoc = this.coreState.core.documents[id];
991
- if (!(coreDoc == null ? void 0 : coreDoc.document)) {
992
- return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
993
- }
994
- const selectionScope = this.selectionCapability.forDocument(id);
995
- const formatted = selectionScope.getFormattedSelection();
996
- if (!formatted.length) return PdfTaskHelper.resolve(true);
997
- const textTask = selectionScope.getSelectedText();
998
- const emitter = this.redactionSelection$.get(id);
999
- emitter == null ? void 0 : emitter.emit([]);
1000
- selectionScope.clear();
1001
- if (!this.useAnnotationMode) {
1002
- this.enableRedactSelection(id);
1003
- }
1004
- const task = new Task();
1005
- textTask.wait(
1006
- (textArr) => {
1007
- const text = textArr.join(" ");
1008
- this.createRedactionsFromSelection(id, formatted, text);
1009
- task.resolve(true);
1010
- },
1011
- () => {
1012
- this.createRedactionsFromSelection(id, formatted);
1013
- task.resolve(true);
1014
- }
1015
- );
1016
- return task;
1017
- }
1018
- commitPendingOne(page, id, documentId) {
1019
- const docId = documentId ?? this.getActiveDocumentId();
1020
- if (!this.checkPermission(docId, PdfPermissionFlag.ModifyContents)) {
1021
- this.logger.debug(
1022
- "RedactionPlugin",
1023
- "CommitPendingOne",
1024
- `Cannot commit redaction: document ${docId} lacks ModifyContents permission`
1025
- );
1026
- return PdfTaskHelper.reject({
1027
- code: PdfErrorCode.Security,
1028
- message: "Document lacks ModifyContents permission"
1029
- });
1030
- }
1031
- const coreDoc = this.coreState.core.documents[docId];
1032
- if (!(coreDoc == null ? void 0 : coreDoc.document))
1033
- return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
1034
- const pdfPage = coreDoc.document.pages[page];
1035
- if (!pdfPage)
1036
- return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Page not found" });
1037
- if (this.useAnnotationMode) {
1038
- this.logger.debug(
1039
- "RedactionPlugin",
1040
- "CommitPendingOne",
1041
- `Applying redaction in annotation mode: page ${page}, id ${id}`
1042
- );
1043
- return this.applyRedactionAnnotationMode(docId, coreDoc.document, pdfPage, id);
1044
- }
1045
- const docState = this.getDocumentState(docId);
1046
- if (!docState) {
1047
- return PdfTaskHelper.reject({
1048
- code: PdfErrorCode.NotFound,
1049
- message: "Document state not found"
1050
- });
1051
- }
1052
- const item = (docState.pending[page] ?? []).find((it) => it.id === id);
1053
- if (!item) {
1054
- this.logger.debug(
1055
- "RedactionPlugin",
1056
- "CommitPendingOne",
1057
- `No pending item found for page ${page}, id ${id}`
1058
- );
1059
- return PdfTaskHelper.resolve(true);
1060
- }
1061
- return this.commitPendingOneLegacy(docId, coreDoc.document, pdfPage, page, item);
1062
- }
1063
- /**
1064
- * Legacy commit single redaction using redactTextInRects
1065
- */
1066
- commitPendingOneLegacy(docId, doc, pdfPage, page, item) {
1067
- const rects = item.kind === "text" ? item.rects : [item.rect];
1068
- const task = new Task();
1069
- this.engine.redactTextInRects(doc, pdfPage, rects, {
1070
- drawBlackBoxes: this.config.drawBlackBoxes
1071
- }).wait(
1072
- () => {
1073
- this.dispatch(removePending(docId, page, item.id));
1074
- this.dispatchCoreAction(refreshPages(docId, [page]));
1075
- this.events$.emit({ type: "commit", documentId: docId, success: true });
1076
- task.resolve(true);
1077
- },
1078
- (error) => {
1079
- this.events$.emit({
1080
- type: "commit",
1081
- documentId: docId,
1082
- success: false,
1083
- error: error.reason
1084
- });
1085
- task.reject({ code: PdfErrorCode.Unknown, message: "Failed to commit redactions" });
1086
- }
1087
- );
1088
- return task;
1089
- }
1090
- /**
1091
- * Annotation mode: Apply single redaction using engine.applyRedaction
1092
- */
1093
- applyRedactionAnnotationMode(docId, doc, pdfPage, annotationId) {
1094
- var _a;
1095
- const task = new Task();
1096
- const anno = (_a = this.annotationCapability) == null ? void 0 : _a.forDocument(docId).getAnnotationById(annotationId);
1097
- this.logger.debug(
1098
- "RedactionPlugin",
1099
- "ApplyRedactionAnnotationMode",
1100
- `Looking for annotation ${annotationId}, found: ${!!anno}, type: ${anno == null ? void 0 : anno.object.type}`
1101
- );
1102
- if (!anno || anno.object.type !== PdfAnnotationSubtype.REDACT) {
1103
- this.logger.warn(
1104
- "RedactionPlugin",
1105
- "ApplyRedactionAnnotationMode",
1106
- `Redaction annotation not found or wrong type: ${annotationId}`
1107
- );
1108
- return PdfTaskHelper.reject({
1109
- code: PdfErrorCode.NotFound,
1110
- message: "Redaction annotation not found"
1111
- });
1112
- }
1113
- this.logger.debug(
1114
- "RedactionPlugin",
1115
- "ApplyRedactionAnnotationMode",
1116
- `Calling engine.applyRedaction for annotation ${annotationId} on page ${pdfPage.index}`
1117
- );
1118
- this.engine.applyRedaction(doc, pdfPage, anno.object).wait(
1119
- () => {
1120
- var _a2, _b;
1121
- this.logger.debug(
1122
- "RedactionPlugin",
1123
- "ApplyRedactionAnnotationMode",
1124
- `Successfully applied redaction ${annotationId} on page ${pdfPage.index}`
1125
- );
1126
- (_a2 = this.annotationCapability) == null ? void 0 : _a2.forDocument(docId).purgeAnnotation(pdfPage.index, annotationId);
1127
- this.dispatch(removePending(docId, pdfPage.index, annotationId));
1128
- (_b = this.historyCapability) == null ? void 0 : _b.forDocument(docId).purgeByMetadata(
1129
- (meta) => {
1130
- var _a3;
1131
- return ((_a3 = meta == null ? void 0 : meta.annotationIds) == null ? void 0 : _a3.includes(annotationId)) ?? false;
1132
- },
1133
- "annotations"
1134
- );
1135
- this.dispatchCoreAction(refreshPages(docId, [pdfPage.index]));
1136
- this.events$.emit({ type: "commit", documentId: docId, success: true });
1137
- task.resolve(true);
1138
- },
1139
- (error) => {
1140
- var _a2;
1141
- this.logger.error(
1142
- "RedactionPlugin",
1143
- "ApplyRedactionAnnotationMode",
1144
- `Failed to apply redaction ${annotationId}: ${((_a2 = error.reason) == null ? void 0 : _a2.message) ?? "Unknown error"}`
1145
- );
1146
- this.events$.emit({
1147
- type: "commit",
1148
- documentId: docId,
1149
- success: false,
1150
- error: error.reason
1151
- });
1152
- task.reject({ code: PdfErrorCode.Unknown, message: "Failed to apply redaction" });
1153
- }
1154
- );
1155
- return task;
1156
- }
1157
- commitAllPending(documentId) {
1158
- const docId = documentId ?? this.getActiveDocumentId();
1159
- if (!this.checkPermission(docId, PdfPermissionFlag.ModifyContents)) {
1160
- this.logger.debug(
1161
- "RedactionPlugin",
1162
- "CommitAllPending",
1163
- `Cannot commit redactions: document ${docId} lacks ModifyContents permission`
1164
- );
1165
- return PdfTaskHelper.reject({
1166
- code: PdfErrorCode.Security,
1167
- message: "Document lacks ModifyContents permission"
1168
- });
1169
- }
1170
- const coreDoc = this.coreState.core.documents[docId];
1171
- if (!(coreDoc == null ? void 0 : coreDoc.document))
1172
- return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Document not found" });
1173
- const docState = this.getDocumentState(docId);
1174
- if (!docState) {
1175
- return PdfTaskHelper.reject({
1176
- code: PdfErrorCode.NotFound,
1177
- message: "Document state not found"
1178
- });
1179
- }
1180
- if (this.useAnnotationMode) {
1181
- return this.applyAllRedactionsAnnotationMode(docId, coreDoc.document);
1182
- } else {
1183
- return this.commitAllPendingLegacy(docId, coreDoc.document, docState);
1184
- }
1185
- }
1186
- /**
1187
- * Legacy commit all redactions using redactTextInRects
1188
- */
1189
- commitAllPendingLegacy(docId, doc, docState) {
1190
- const perPage = /* @__PURE__ */ new Map();
1191
- for (const [page, items] of Object.entries(docState.pending)) {
1192
- const p = Number(page);
1193
- const list = perPage.get(p) ?? [];
1194
- for (const it of items) {
1195
- if (it.kind === "text") list.push(...it.rects);
1196
- else list.push(it.rect);
1197
- }
1198
- perPage.set(p, list);
1199
- }
1200
- const pagesToRefresh = Array.from(perPage.entries()).filter(([_, rects]) => rects.length > 0).map(([pageIndex]) => pageIndex);
1201
- const tasks = [];
1202
- for (const [pageIndex, rects] of perPage) {
1203
- const page = doc.pages[pageIndex];
1204
- if (!page) continue;
1205
- if (!rects.length) continue;
1206
- tasks.push(
1207
- this.engine.redactTextInRects(doc, page, rects, {
1208
- drawBlackBoxes: this.config.drawBlackBoxes
1209
- })
1210
- );
1211
- }
1212
- const task = new Task();
1213
- Task.all(tasks).wait(
1214
- () => {
1215
- this.dispatch(clearPending(docId));
1216
- this.dispatchCoreAction(refreshPages(docId, pagesToRefresh));
1217
- this.events$.emit({ type: "commit", documentId: docId, success: true });
1218
- task.resolve(true);
1219
- },
1220
- (error) => {
1221
- this.events$.emit({
1222
- type: "commit",
1223
- documentId: docId,
1224
- success: false,
1225
- error: error.reason
1226
- });
1227
- task.reject({ code: PdfErrorCode.Unknown, message: "Failed to commit redactions" });
1228
- }
1229
- );
1230
- return task;
1231
- }
1232
- /**
1233
- * Annotation mode: Apply all redactions using engine.applyAllRedactions per page
1234
- */
1235
- applyAllRedactionsAnnotationMode(docId, doc) {
1236
- const annotationCapability = this.annotationCapability;
1237
- if (!annotationCapability) {
1238
- return PdfTaskHelper.reject({
1239
- code: PdfErrorCode.NotFound,
1240
- message: "Annotation capability not found"
1241
- });
1242
- }
1243
- const annoState = annotationCapability.forDocument(docId).getState();
1244
- const redactAnnotationsByPage = /* @__PURE__ */ new Map();
1245
- for (const ta of Object.values(annoState.byUid)) {
1246
- if (ta.object.type === PdfAnnotationSubtype.REDACT) {
1247
- const pageIds = redactAnnotationsByPage.get(ta.object.pageIndex) ?? [];
1248
- pageIds.push(ta.object.id);
1249
- redactAnnotationsByPage.set(ta.object.pageIndex, pageIds);
1250
- }
1251
- }
1252
- const pagesToProcess = Array.from(redactAnnotationsByPage.keys());
1253
- if (pagesToProcess.length === 0) {
1254
- return PdfTaskHelper.resolve(true);
1255
- }
1256
- const tasks = [];
1257
- for (const pageIndex of pagesToProcess) {
1258
- const page = doc.pages[pageIndex];
1259
- if (!page) continue;
1260
- tasks.push(this.engine.applyAllRedactions(doc, page));
1261
- }
1262
- const task = new Task();
1263
- Task.all(tasks).wait(
1264
- () => {
1265
- var _a;
1266
- const annoScope = annotationCapability.forDocument(docId);
1267
- const allPurgedIds = [];
1268
- for (const [pageIndex, ids] of redactAnnotationsByPage) {
1269
- for (const id of ids) {
1270
- annoScope.purgeAnnotation(pageIndex, id);
1271
- this.dispatch(removePending(docId, pageIndex, id));
1272
- allPurgedIds.push(id);
1273
- }
1274
- }
1275
- if (allPurgedIds.length > 0) {
1276
- (_a = this.historyCapability) == null ? void 0 : _a.forDocument(docId).purgeByMetadata(
1277
- (meta) => {
1278
- var _a2;
1279
- return ((_a2 = meta == null ? void 0 : meta.annotationIds) == null ? void 0 : _a2.some((id) => allPurgedIds.includes(id))) ?? false;
1280
- },
1281
- "annotations"
1282
- );
1283
- }
1284
- this.dispatchCoreAction(refreshPages(docId, pagesToProcess));
1285
- this.events$.emit({ type: "commit", documentId: docId, success: true });
1286
- task.resolve(true);
1287
- },
1288
- (error) => {
1289
- this.events$.emit({
1290
- type: "commit",
1291
- documentId: docId,
1292
- success: false,
1293
- error: error.reason
1294
- });
1295
- task.reject({ code: PdfErrorCode.Unknown, message: "Failed to apply redactions" });
1296
- }
1297
- );
1298
- return task;
1299
- }
1300
- // ─────────────────────────────────────────────────────────
1301
- // Annotation Mode Helpers
1302
- // ─────────────────────────────────────────────────────────
1303
- /**
1304
- * Create REDACT annotations from text selection (annotation mode only)
1305
- * @returns Array of annotation IDs that were created
1306
- */
1307
- createRedactAnnotationsFromSelection(documentId, formattedSelection, text) {
1308
- var _a;
1309
- if (!this.annotationCapability) return [];
1310
- const annoScope = this.annotationCapability.forDocument(documentId);
1311
- const defaults = (_a = this.getRedactTool()) == null ? void 0 : _a.defaults;
1312
- const annotationIds = [];
1313
- for (const selection of formattedSelection) {
1314
- const annotationId = uuidV4();
1315
- annotationIds.push(annotationId);
1316
- const annotation = {
1317
- ...defaults,
1318
- id: annotationId,
1319
- type: PdfAnnotationSubtype.REDACT,
1320
- pageIndex: selection.pageIndex,
1321
- rect: selection.rect,
1322
- segmentRects: selection.segmentRects,
1323
- ...text ? { custom: { text } } : {},
1324
- created: /* @__PURE__ */ new Date()
1325
- };
1326
- annoScope.createAnnotation(selection.pageIndex, annotation);
1327
- if (selection === formattedSelection[formattedSelection.length - 1]) {
1328
- annoScope.selectAnnotation(selection.pageIndex, annotationId);
1329
- }
1330
- }
1331
- if (text) {
1332
- for (let i = 0; i < annotationIds.length; i++) {
1333
- const pageIndex = formattedSelection[i].pageIndex;
1334
- this.dispatch(updatePending(documentId, pageIndex, annotationIds[i], { text }));
1335
- }
1336
- }
1337
- return annotationIds;
1338
- }
1339
- /**
1340
- * Create legacy RedactionItems from text selection (legacy mode only)
1341
- */
1342
- createLegacyRedactionsFromSelection(documentId, formattedSelection, text) {
1343
- const redactionColor = this.config.drawBlackBoxes ? "#000000" : "transparent";
1344
- const items = formattedSelection.map((s) => ({
1345
- id: uuidV4(),
1346
- kind: "text",
1347
- page: s.pageIndex,
1348
- rect: s.rect,
1349
- rects: s.segmentRects,
1350
- source: "legacy",
1351
- markColor: "#FF0000",
1352
- redactionColor,
1353
- text
1354
- }));
1355
- this.dispatch(addPending(documentId, items));
1356
- if (items.length) {
1357
- this.selectPending(items[items.length - 1].page, items[items.length - 1].id, documentId);
1358
- }
1359
- }
1360
- /**
1361
- * Unified method to create redactions from text selection.
1362
- * Delegates to annotation mode or legacy mode helper based on configuration.
1363
- */
1364
- createRedactionsFromSelection(documentId, formattedSelection, text) {
1365
- if (this.useAnnotationMode) {
1366
- this.createRedactAnnotationsFromSelection(documentId, formattedSelection, text);
1367
- } else {
1368
- this.createLegacyRedactionsFromSelection(documentId, formattedSelection, text);
1369
- }
1370
- }
1371
- /**
1372
- * Create a REDACT annotation from an area/marquee selection (annotation mode only)
1373
- */
1374
- createRedactAnnotationFromArea(documentId, pageIndex, rect) {
1375
- var _a;
1376
- if (!this.annotationCapability) return;
1377
- const annoScope = this.annotationCapability.forDocument(documentId);
1378
- const defaults = (_a = this.getRedactTool()) == null ? void 0 : _a.defaults;
1379
- const annotationId = uuidV4();
1380
- const annotation = {
1381
- ...defaults,
1382
- id: annotationId,
1383
- type: PdfAnnotationSubtype.REDACT,
1384
- pageIndex,
1385
- rect,
1386
- segmentRects: [],
1387
- // No segment rects for area redaction
1388
- created: /* @__PURE__ */ new Date()
1389
- };
1390
- annoScope.createAnnotation(pageIndex, annotation);
1391
- annoScope.selectAnnotation(pageIndex, annotationId);
1392
- }
1393
- /**
1394
- * Convert a RedactionItem to a PdfRedactAnnoObject
1395
- */
1396
- redactionItemToAnnotation(item) {
1397
- var _a;
1398
- const defaults = ((_a = this.getRedactTool()) == null ? void 0 : _a.defaults) ?? {};
1399
- return {
1400
- ...defaults,
1401
- id: item.id,
1402
- type: PdfAnnotationSubtype.REDACT,
1403
- pageIndex: item.page,
1404
- rect: item.rect,
1405
- segmentRects: item.kind === "text" ? item.rects : [],
1406
- created: /* @__PURE__ */ new Date()
1407
- };
1408
- }
1409
- /**
1410
- * Convert a PdfRedactAnnoObject to a RedactionItem
1411
- */
1412
- annotationToRedactionItem(anno) {
1413
- var _a;
1414
- const markColor = anno.strokeColor ?? "#FF0000";
1415
- const redactionColor = anno.color ?? "transparent";
1416
- if (anno.segmentRects && anno.segmentRects.length > 0) {
1417
- return {
1418
- id: anno.id,
1419
- kind: "text",
1420
- page: anno.pageIndex,
1421
- rect: anno.rect,
1422
- rects: anno.segmentRects,
1423
- source: "annotation",
1424
- markColor,
1425
- redactionColor,
1426
- ...((_a = anno.custom) == null ? void 0 : _a.text) ? { text: anno.custom.text } : {}
1427
- };
1428
- } else {
1429
- return {
1430
- id: anno.id,
1431
- kind: "area",
1432
- page: anno.pageIndex,
1433
- rect: anno.rect,
1434
- source: "annotation",
1435
- markColor,
1436
- redactionColor
1437
- };
1438
- }
1439
- }
1440
- // ─────────────────────────────────────────────────────────
1441
- // Event Emission Helpers
1442
- // ─────────────────────────────────────────────────────────
1443
- emitPendingChange(documentId) {
1444
- const docState = this.getDocumentState(documentId);
1445
- if (docState) {
1446
- this.pending$.emit({ documentId, pending: docState.pending });
1447
- }
1448
- }
1449
- emitSelectedChange(documentId) {
1450
- const docState = this.getDocumentState(documentId);
1451
- if (docState) {
1452
- this.selected$.emit({ documentId, selected: docState.selected });
1453
- }
1454
- }
1455
- emitStateChange(documentId) {
1456
- const docState = this.getDocumentState(documentId);
1457
- if (docState) {
1458
- this.state$.emit({ documentId, state: docState });
1459
- }
1460
- }
1461
- // ─────────────────────────────────────────────────────────
1462
- // Store Update Handlers
1463
- // ─────────────────────────────────────────────────────────
1464
- onStoreUpdated(_, newState) {
1465
- for (const documentId in newState.documents) {
1466
- const docState = newState.documents[documentId];
1467
- if (docState) {
1468
- this.emitPendingChange(documentId);
1469
- this.emitSelectedChange(documentId);
1470
- this.emitStateChange(documentId);
1471
- }
1472
- }
1473
- }
1474
- // ─────────────────────────────────────────────────────────
1475
- // Lifecycle
1476
- // ─────────────────────────────────────────────────────────
1477
- async destroy() {
1478
- this.pending$.clear();
1479
- this.selected$.clear();
1480
- this.state$.clear();
1481
- this.events$.clear();
1482
- this.redactionSelection$.forEach((emitter) => emitter.clear());
1483
- this.redactionSelection$.clear();
1484
- this.redactionMarquee$.forEach((emitter) => emitter.clear());
1485
- this.redactionMarquee$.clear();
1486
- this.documentUnsubscribers.forEach((unsubscribers) => {
1487
- unsubscribers.forEach((unsub) => unsub());
1488
- });
1489
- this.documentUnsubscribers.clear();
1490
- await super.destroy();
1491
- }
1492
- };
1493
- _RedactionPlugin.id = "redaction";
1494
- let RedactionPlugin = _RedactionPlugin;
1495
- const REDACTION_PLUGIN_ID = "redaction";
1496
- const manifest = {
1497
- id: REDACTION_PLUGIN_ID,
1498
- name: "Redaction Plugin",
1499
- version: "1.0.0",
1500
- provides: ["redaction"],
1501
- requires: [],
1502
- optional: ["interaction-manager", "selection", "annotation"],
1503
- defaultConfig: {
1504
- drawBlackBoxes: true
1505
- }
1506
- };
1507
- const getPendingRedactionsCount = (s) => s.pendingCount;
1508
- const hasPendingRedactions = (s) => s.pendingCount > 0;
1509
- const getDocumentPendingCount = (state, documentId) => {
1510
- var _a;
1511
- return ((_a = state.documents[documentId]) == null ? void 0 : _a.pendingCount) ?? 0;
1512
- };
1513
- const getTotalPendingCount = (state) => {
1514
- return Object.values(state.documents).reduce((sum, doc) => sum + doc.pendingCount, 0);
1515
- };
1516
- const RedactionPluginPackage = {
1517
- manifest,
1518
- create: (registry, config) => new RedactionPlugin(REDACTION_PLUGIN_ID, registry, config),
1519
- reducer: redactionReducer,
1520
- initialState
1521
- };
1522
- export {
1523
- REDACTION_PLUGIN_ID,
1524
- RedactionMode,
1525
- RedactionPlugin,
1526
- RedactionPluginPackage,
1527
- getDocumentPendingCount,
1528
- getPendingRedactionsCount,
1529
- getTotalPendingCount,
1530
- hasPendingRedactions,
1531
- initialDocumentState,
1532
- initialState,
1533
- manifest,
1534
- redactTool,
1535
- redactTools
1536
- };
1537
- //# sourceMappingURL=index.js.map
222
+ //#endregion
223
+ export { RedactionToken, redactionPlugin };
224
+
225
+ //# sourceMappingURL=index.js.map