@embedpdf/plugin-redaction 3.0.0-next.1 → 3.0.0-next.11
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/contract--fJOCVRN.d.cts +84 -0
- package/dist/contract--fJOCVRN.d.ts +84 -0
- package/dist/contract-BZLrD3p2.js +7 -0
- package/dist/contract-BZLrD3p2.js.map +1 -0
- package/dist/contract-CyLm5eeQ.cjs +11 -0
- package/dist/contract-CyLm5eeQ.cjs.map +1 -0
- package/dist/contract.cjs +3 -0
- package/dist/contract.d.cts +2 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +2 -0
- package/dist/index.cjs +31 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -74
- package/dist/index.d.ts +1 -74
- package/dist/index.js +23 -12
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { AnnotationRef, RedactionApplyResult, RedactionApplyResult as RedactionApplyResult$1 } from "@embedpdf/engine-core";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* One pending redaction mark — a live projection of a `redact` annotation on
|
|
5
|
+
* the annotation plane (the plugin owns NO mark state of its own; delete the
|
|
6
|
+
* annotation and the pending item is gone).
|
|
7
|
+
*/
|
|
8
|
+
interface RedactionPendingItem {
|
|
9
|
+
/** Stable list key derived from the ref. */
|
|
10
|
+
id: string;
|
|
11
|
+
ref: AnnotationRef;
|
|
12
|
+
pageObjectNumber: number;
|
|
13
|
+
/** Display index (0-based) from the page registry — for "page N" labels. */
|
|
14
|
+
pageIndex: number;
|
|
15
|
+
/** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */
|
|
16
|
+
kind: 'area' | 'text';
|
|
17
|
+
/** `/OverlayText` label, when set. */
|
|
18
|
+
overlayText: string | null;
|
|
19
|
+
}
|
|
20
|
+
interface RedactionState {
|
|
21
|
+
/** A destructive apply is in flight. */
|
|
22
|
+
applying: boolean;
|
|
23
|
+
/** The last apply result seen (own or remote), for status UI. */
|
|
24
|
+
lastResult: RedactionApplyResult | null;
|
|
25
|
+
}
|
|
26
|
+
type RedactionAction = {
|
|
27
|
+
type: 'APPLY_STARTED';
|
|
28
|
+
} | {
|
|
29
|
+
type: 'APPLY_FINISHED';
|
|
30
|
+
result: RedactionApplyResult | null;
|
|
31
|
+
};
|
|
32
|
+
/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */
|
|
33
|
+
interface RedactionLabelPatch {
|
|
34
|
+
overlayText?: string | null;
|
|
35
|
+
repeat?: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface RedactionCapability {
|
|
38
|
+
/**
|
|
39
|
+
* The twins (permissions.md). Marking and applying are DIFFERENT powers:
|
|
40
|
+
* a mark is an ordinary `redact` annotation, so `canMark()` is annotation
|
|
41
|
+
* create authority (any reviewer who can annotate can propose redactions —
|
|
42
|
+
* Acrobat parity); `canApply()` is the destructive rewrite — engine support
|
|
43
|
+
* present AND every capability the engine's apply asserts (`doc.redact`,
|
|
44
|
+
* `doc.pages.modify`, `doc.annotate.modify`). The engine enforces both
|
|
45
|
+
* independently — these are the UI mirrors.
|
|
46
|
+
*/
|
|
47
|
+
canMark(): boolean;
|
|
48
|
+
canApply(): boolean;
|
|
49
|
+
isApplying(): boolean;
|
|
50
|
+
lastResult(): RedactionApplyResult | null;
|
|
51
|
+
/** The composed marking tool (text-selection over text + area drag elsewhere). */
|
|
52
|
+
enableRedact(): void;
|
|
53
|
+
toggleRedact(): void;
|
|
54
|
+
isRedactActive(): boolean;
|
|
55
|
+
/** Mark the current text selection (per page) and clear it. False when
|
|
56
|
+
* there is no selection plugin or no selection. */
|
|
57
|
+
queueCurrentSelection(): Promise<boolean>;
|
|
58
|
+
/** Load every page's annotations so the pending view is complete (the
|
|
59
|
+
* annotation plane loads lazily per page). Call when opening a panel. */
|
|
60
|
+
preparePending(): Promise<void>;
|
|
61
|
+
/** Pending marks over the LOADED annotation state (see preparePending). */
|
|
62
|
+
getPending(): RedactionPendingItem[];
|
|
63
|
+
pendingCount(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Client-side estimate of how many OTHER annotations the given pending
|
|
66
|
+
* marks (default: all) would destroy — for confirm dialogs BEFORE the
|
|
67
|
+
* apply. The authoritative count comes back on the result.
|
|
68
|
+
*/
|
|
69
|
+
estimateCollateral(ids?: string[]): number;
|
|
70
|
+
/** Edit a mark's label. Reads the mark's current `/DA` styling and writes
|
|
71
|
+
* the full label set, so a text-only edit never resets the styling. */
|
|
72
|
+
setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;
|
|
73
|
+
/** Apply specific pending marks (refs scope). Irreversible. */
|
|
74
|
+
apply(ids: string[]): Promise<RedactionApplyResult>;
|
|
75
|
+
/** Apply every redaction in the document (pages scope — including marks on
|
|
76
|
+
* pages this client never loaded). Irreversible. */
|
|
77
|
+
applyAll(): Promise<RedactionApplyResult>;
|
|
78
|
+
/** Fires after ANY confirmed apply — own or a remote collaborator's. */
|
|
79
|
+
onApplied(cb: (result: RedactionApplyResult) => void): () => void;
|
|
80
|
+
}
|
|
81
|
+
declare const RedactionToken: import("@embedpdf/core").CapabilityToken<RedactionCapability>;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { RedactionPendingItem as a, RedactionLabelPatch as i, RedactionAction as n, RedactionState as o, RedactionCapability as r, RedactionToken as s, RedactionApplyResult$1 as t };
|
|
84
|
+
//# sourceMappingURL=contract--fJOCVRN.d.cts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { AnnotationRef, RedactionApplyResult, RedactionApplyResult as RedactionApplyResult$1 } from "@embedpdf/engine-core";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* One pending redaction mark — a live projection of a `redact` annotation on
|
|
5
|
+
* the annotation plane (the plugin owns NO mark state of its own; delete the
|
|
6
|
+
* annotation and the pending item is gone).
|
|
7
|
+
*/
|
|
8
|
+
interface RedactionPendingItem {
|
|
9
|
+
/** Stable list key derived from the ref. */
|
|
10
|
+
id: string;
|
|
11
|
+
ref: AnnotationRef;
|
|
12
|
+
pageObjectNumber: number;
|
|
13
|
+
/** Display index (0-based) from the page registry — for "page N" labels. */
|
|
14
|
+
pageIndex: number;
|
|
15
|
+
/** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */
|
|
16
|
+
kind: 'area' | 'text';
|
|
17
|
+
/** `/OverlayText` label, when set. */
|
|
18
|
+
overlayText: string | null;
|
|
19
|
+
}
|
|
20
|
+
interface RedactionState {
|
|
21
|
+
/** A destructive apply is in flight. */
|
|
22
|
+
applying: boolean;
|
|
23
|
+
/** The last apply result seen (own or remote), for status UI. */
|
|
24
|
+
lastResult: RedactionApplyResult | null;
|
|
25
|
+
}
|
|
26
|
+
type RedactionAction = {
|
|
27
|
+
type: 'APPLY_STARTED';
|
|
28
|
+
} | {
|
|
29
|
+
type: 'APPLY_FINISHED';
|
|
30
|
+
result: RedactionApplyResult | null;
|
|
31
|
+
};
|
|
32
|
+
/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */
|
|
33
|
+
interface RedactionLabelPatch {
|
|
34
|
+
overlayText?: string | null;
|
|
35
|
+
repeat?: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface RedactionCapability {
|
|
38
|
+
/**
|
|
39
|
+
* The twins (permissions.md). Marking and applying are DIFFERENT powers:
|
|
40
|
+
* a mark is an ordinary `redact` annotation, so `canMark()` is annotation
|
|
41
|
+
* create authority (any reviewer who can annotate can propose redactions —
|
|
42
|
+
* Acrobat parity); `canApply()` is the destructive rewrite — engine support
|
|
43
|
+
* present AND every capability the engine's apply asserts (`doc.redact`,
|
|
44
|
+
* `doc.pages.modify`, `doc.annotate.modify`). The engine enforces both
|
|
45
|
+
* independently — these are the UI mirrors.
|
|
46
|
+
*/
|
|
47
|
+
canMark(): boolean;
|
|
48
|
+
canApply(): boolean;
|
|
49
|
+
isApplying(): boolean;
|
|
50
|
+
lastResult(): RedactionApplyResult | null;
|
|
51
|
+
/** The composed marking tool (text-selection over text + area drag elsewhere). */
|
|
52
|
+
enableRedact(): void;
|
|
53
|
+
toggleRedact(): void;
|
|
54
|
+
isRedactActive(): boolean;
|
|
55
|
+
/** Mark the current text selection (per page) and clear it. False when
|
|
56
|
+
* there is no selection plugin or no selection. */
|
|
57
|
+
queueCurrentSelection(): Promise<boolean>;
|
|
58
|
+
/** Load every page's annotations so the pending view is complete (the
|
|
59
|
+
* annotation plane loads lazily per page). Call when opening a panel. */
|
|
60
|
+
preparePending(): Promise<void>;
|
|
61
|
+
/** Pending marks over the LOADED annotation state (see preparePending). */
|
|
62
|
+
getPending(): RedactionPendingItem[];
|
|
63
|
+
pendingCount(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Client-side estimate of how many OTHER annotations the given pending
|
|
66
|
+
* marks (default: all) would destroy — for confirm dialogs BEFORE the
|
|
67
|
+
* apply. The authoritative count comes back on the result.
|
|
68
|
+
*/
|
|
69
|
+
estimateCollateral(ids?: string[]): number;
|
|
70
|
+
/** Edit a mark's label. Reads the mark's current `/DA` styling and writes
|
|
71
|
+
* the full label set, so a text-only edit never resets the styling. */
|
|
72
|
+
setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;
|
|
73
|
+
/** Apply specific pending marks (refs scope). Irreversible. */
|
|
74
|
+
apply(ids: string[]): Promise<RedactionApplyResult>;
|
|
75
|
+
/** Apply every redaction in the document (pages scope — including marks on
|
|
76
|
+
* pages this client never loaded). Irreversible. */
|
|
77
|
+
applyAll(): Promise<RedactionApplyResult>;
|
|
78
|
+
/** Fires after ANY confirmed apply — own or a remote collaborator's. */
|
|
79
|
+
onApplied(cb: (result: RedactionApplyResult) => void): () => void;
|
|
80
|
+
}
|
|
81
|
+
declare const RedactionToken: import("@embedpdf/core").CapabilityToken<RedactionCapability>;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { RedactionPendingItem as a, RedactionLabelPatch as i, RedactionAction as n, RedactionState as o, RedactionCapability as r, RedactionToken as s, RedactionApplyResult$1 as t };
|
|
84
|
+
//# sourceMappingURL=contract--fJOCVRN.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-BZLrD3p2.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\nimport type { AnnotationRef, RedactionApplyResult } from '@embedpdf/engine-core';\n\n/**\n * One pending redaction mark — a live projection of a `redact` annotation on\n * the annotation plane (the plugin owns NO mark state of its own; delete the\n * annotation and the pending item is gone).\n */\nexport interface RedactionPendingItem {\n /** Stable list key derived from the ref. */\n id: string;\n ref: AnnotationRef;\n pageObjectNumber: number;\n /** Display index (0-based) from the page registry — for \"page N\" labels. */\n pageIndex: number;\n /** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */\n kind: 'area' | 'text';\n /** `/OverlayText` label, when set. */\n overlayText: string | null;\n}\n\nexport interface RedactionState {\n /** A destructive apply is in flight. */\n applying: boolean;\n /** The last apply result seen (own or remote), for status UI. */\n lastResult: RedactionApplyResult | null;\n}\n\nexport type RedactionAction =\n | { type: 'APPLY_STARTED' }\n | { type: 'APPLY_FINISHED'; result: RedactionApplyResult | null };\n\n/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */\nexport interface RedactionLabelPatch {\n overlayText?: string | null;\n repeat?: boolean;\n}\n\nexport interface RedactionCapability {\n /**\n * The twins (permissions.md). Marking and applying are DIFFERENT powers:\n * a mark is an ordinary `redact` annotation, so `canMark()` is annotation\n * create authority (any reviewer who can annotate can propose redactions —\n * Acrobat parity); `canApply()` is the destructive rewrite — engine support\n * present AND every capability the engine's apply asserts (`doc.redact`,\n * `doc.pages.modify`, `doc.annotate.modify`). The engine enforces both\n * independently — these are the UI mirrors.\n */\n canMark(): boolean;\n canApply(): boolean;\n isApplying(): boolean;\n lastResult(): RedactionApplyResult | null;\n\n /** The composed marking tool (text-selection over text + area drag elsewhere). */\n enableRedact(): void;\n toggleRedact(): void;\n isRedactActive(): boolean;\n\n /** Mark the current text selection (per page) and clear it. False when\n * there is no selection plugin or no selection. */\n queueCurrentSelection(): Promise<boolean>;\n\n /** Load every page's annotations so the pending view is complete (the\n * annotation plane loads lazily per page). Call when opening a panel. */\n preparePending(): Promise<void>;\n /** Pending marks over the LOADED annotation state (see preparePending). */\n getPending(): RedactionPendingItem[];\n pendingCount(): number;\n /**\n * Client-side estimate of how many OTHER annotations the given pending\n * marks (default: all) would destroy — for confirm dialogs BEFORE the\n * apply. The authoritative count comes back on the result.\n */\n estimateCollateral(ids?: string[]): number;\n\n /** Edit a mark's label. Reads the mark's current `/DA` styling and writes\n * the full label set, so a text-only edit never resets the styling. */\n setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;\n\n /** Apply specific pending marks (refs scope). Irreversible. */\n apply(ids: string[]): Promise<RedactionApplyResult>;\n /** Apply every redaction in the document (pages scope — including marks on\n * pages this client never loaded). Irreversible. */\n applyAll(): Promise<RedactionApplyResult>;\n\n /** Fires after ANY confirmed apply — own or a remote collaborator's. */\n onApplied(cb: (result: RedactionApplyResult) => void): () => void;\n}\n\nexport const RedactionToken = createCapabilityToken<RedactionCapability>('redaction');\n"],"mappings":";;AAyFA,MAAa,iBAAiB,sBAA2C,WAAW"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
const RedactionToken = (0, require("@embedpdf/core").createCapabilityToken)("redaction");
|
|
3
|
+
//#endregion
|
|
4
|
+
Object.defineProperty(exports, "RedactionToken", {
|
|
5
|
+
enumerable: true,
|
|
6
|
+
get: function() {
|
|
7
|
+
return RedactionToken;
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=contract-CyLm5eeQ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-CyLm5eeQ.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\nimport type { AnnotationRef, RedactionApplyResult } from '@embedpdf/engine-core';\n\n/**\n * One pending redaction mark — a live projection of a `redact` annotation on\n * the annotation plane (the plugin owns NO mark state of its own; delete the\n * annotation and the pending item is gone).\n */\nexport interface RedactionPendingItem {\n /** Stable list key derived from the ref. */\n id: string;\n ref: AnnotationRef;\n pageObjectNumber: number;\n /** Display index (0-based) from the page registry — for \"page N\" labels. */\n pageIndex: number;\n /** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */\n kind: 'area' | 'text';\n /** `/OverlayText` label, when set. */\n overlayText: string | null;\n}\n\nexport interface RedactionState {\n /** A destructive apply is in flight. */\n applying: boolean;\n /** The last apply result seen (own or remote), for status UI. */\n lastResult: RedactionApplyResult | null;\n}\n\nexport type RedactionAction =\n | { type: 'APPLY_STARTED' }\n | { type: 'APPLY_FINISHED'; result: RedactionApplyResult | null };\n\n/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */\nexport interface RedactionLabelPatch {\n overlayText?: string | null;\n repeat?: boolean;\n}\n\nexport interface RedactionCapability {\n /**\n * The twins (permissions.md). Marking and applying are DIFFERENT powers:\n * a mark is an ordinary `redact` annotation, so `canMark()` is annotation\n * create authority (any reviewer who can annotate can propose redactions —\n * Acrobat parity); `canApply()` is the destructive rewrite — engine support\n * present AND every capability the engine's apply asserts (`doc.redact`,\n * `doc.pages.modify`, `doc.annotate.modify`). The engine enforces both\n * independently — these are the UI mirrors.\n */\n canMark(): boolean;\n canApply(): boolean;\n isApplying(): boolean;\n lastResult(): RedactionApplyResult | null;\n\n /** The composed marking tool (text-selection over text + area drag elsewhere). */\n enableRedact(): void;\n toggleRedact(): void;\n isRedactActive(): boolean;\n\n /** Mark the current text selection (per page) and clear it. False when\n * there is no selection plugin or no selection. */\n queueCurrentSelection(): Promise<boolean>;\n\n /** Load every page's annotations so the pending view is complete (the\n * annotation plane loads lazily per page). Call when opening a panel. */\n preparePending(): Promise<void>;\n /** Pending marks over the LOADED annotation state (see preparePending). */\n getPending(): RedactionPendingItem[];\n pendingCount(): number;\n /**\n * Client-side estimate of how many OTHER annotations the given pending\n * marks (default: all) would destroy — for confirm dialogs BEFORE the\n * apply. The authoritative count comes back on the result.\n */\n estimateCollateral(ids?: string[]): number;\n\n /** Edit a mark's label. Reads the mark's current `/DA` styling and writes\n * the full label set, so a text-only edit never resets the styling. */\n setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;\n\n /** Apply specific pending marks (refs scope). Irreversible. */\n apply(ids: string[]): Promise<RedactionApplyResult>;\n /** Apply every redaction in the document (pages scope — including marks on\n * pages this client never loaded). Irreversible. */\n applyAll(): Promise<RedactionApplyResult>;\n\n /** Fires after ANY confirmed apply — own or a remote collaborator's. */\n onApplied(cb: (result: RedactionApplyResult) => void): () => void;\n}\n\nexport const RedactionToken = createCapabilityToken<RedactionCapability>('redaction');\n"],"mappings":";AAyFA,MAAa,kBAAA,2BAAA,CAAA,CAAA,sBAAA,CAA4D,WAAW"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as RedactionPendingItem, i as RedactionLabelPatch, o as RedactionState, r as RedactionCapability, s as RedactionToken, t as RedactionApplyResult } from "./contract--fJOCVRN.cjs";
|
|
2
|
+
export { type RedactionApplyResult, type RedactionCapability, type RedactionLabelPatch, type RedactionPendingItem, type RedactionState, RedactionToken };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as RedactionPendingItem, i as RedactionLabelPatch, o as RedactionState, r as RedactionCapability, s as RedactionToken, t as RedactionApplyResult } from "./contract--fJOCVRN.js";
|
|
2
|
+
export { type RedactionApplyResult, type RedactionCapability, type RedactionLabelPatch, type RedactionPendingItem, type RedactionState, RedactionToken };
|
package/dist/contract.js
ADDED
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_contract = require("./contract-CyLm5eeQ.cjs");
|
|
2
3
|
let _embedpdf_core = require("@embedpdf/core");
|
|
3
|
-
let
|
|
4
|
-
let
|
|
5
|
-
let
|
|
4
|
+
let _embedpdf_plugin_annotation_contract_host = require("@embedpdf/plugin-annotation/contract/host");
|
|
5
|
+
let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
|
|
6
|
+
let _embedpdf_plugin_selection_contract = require("@embedpdf/plugin-selection/contract");
|
|
6
7
|
//#region src/capability.ts
|
|
7
|
-
/** Applying destroys content — its own granted power, narrower than annotate.
|
|
8
|
-
|
|
8
|
+
/** Applying destroys content — its own granted power, narrower than annotate.
|
|
9
|
+
* The engine's apply asserts all three (LocalDocumentRedactionService; the
|
|
10
|
+
* cloud route guards match): the redact grant itself, the page-content
|
|
11
|
+
* rewrite, and the annotation consumption. `canApply` mirrors the full set. */
|
|
12
|
+
const APPLY_CAPABILITIES = [
|
|
13
|
+
"doc.redact",
|
|
14
|
+
"doc.pages.modify",
|
|
15
|
+
"doc.annotate.modify"
|
|
16
|
+
];
|
|
9
17
|
/** The annotation plugin's model-id convention for a durable ref. */
|
|
10
18
|
const idOf = (ref) => ref.kind === "objectNumber" ? `obj:${ref.annotObjectNumber}` : ref.kind === "nm" ? `nm:${ref.nm}` : `idx:${ref.pageObjectNumber}:${ref.index}`;
|
|
11
19
|
/** The PDF-space regions a redact mark targets: its quads' boxes, else `/Rect`. */
|
|
@@ -35,7 +43,7 @@ function regionsOf(dto) {
|
|
|
35
43
|
/** Positive-area intersection — the engine's collateral rule. */
|
|
36
44
|
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);
|
|
37
45
|
function createRedactionCapability(ctx) {
|
|
38
|
-
const anno = ctx.get(
|
|
46
|
+
const anno = ctx.get(_embedpdf_plugin_annotation_contract_host.AnnotationToken);
|
|
39
47
|
const appliedCallbacks = /* @__PURE__ */ new Set();
|
|
40
48
|
const requireDoc = () => {
|
|
41
49
|
const doc = ctx.doc;
|
|
@@ -99,23 +107,29 @@ function createRedactionCapability(ctx) {
|
|
|
99
107
|
fireApplied(event);
|
|
100
108
|
}));
|
|
101
109
|
return {
|
|
102
|
-
|
|
110
|
+
canMark: () => anno.canCreate(),
|
|
111
|
+
canApply: () => {
|
|
112
|
+
const doc = ctx.doc;
|
|
113
|
+
if (!doc || doc.redaction === void 0) return false;
|
|
114
|
+
return APPLY_CAPABILITIES.every((cap) => doc.security.allows(cap));
|
|
115
|
+
},
|
|
103
116
|
isApplying: () => ctx.getState().applying,
|
|
104
117
|
lastResult: () => ctx.getState().lastResult,
|
|
105
|
-
enableRedact: () => ctx.tryGet(
|
|
118
|
+
enableRedact: () => ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken)?.activateTool("redact"),
|
|
106
119
|
toggleRedact: () => {
|
|
107
|
-
const interaction = ctx.tryGet(
|
|
120
|
+
const interaction = ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken);
|
|
108
121
|
if (!interaction) return;
|
|
109
122
|
interaction.activateTool(interaction.activeToolId() === "redact" ? "pointer" : "redact");
|
|
110
123
|
},
|
|
111
|
-
isRedactActive: () => ctx.tryGet(
|
|
124
|
+
isRedactActive: () => ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken)?.activeToolId() === "redact",
|
|
112
125
|
queueCurrentSelection: async () => {
|
|
113
|
-
|
|
126
|
+
if (!anno.canCreate()) return false;
|
|
127
|
+
const selection = ctx.tryGet(_embedpdf_plugin_selection_contract.SelectionToken);
|
|
114
128
|
if (!selection || !selection.hasSelection()) return false;
|
|
115
129
|
const snapshot = selection.snapshot();
|
|
116
130
|
for (const page of snapshot.pages) {
|
|
117
|
-
if (page.
|
|
118
|
-
anno.createMarkup("redact", page.pon, page.
|
|
131
|
+
if (page.segments.length === 0) continue;
|
|
132
|
+
anno.createMarkup("redact", page.pon, page.segments.map((segment) => segment.quad), "redact");
|
|
119
133
|
}
|
|
120
134
|
selection.clear();
|
|
121
135
|
return true;
|
|
@@ -196,9 +210,6 @@ const redactionReducer = (state, action) => {
|
|
|
196
210
|
}
|
|
197
211
|
};
|
|
198
212
|
//#endregion
|
|
199
|
-
//#region src/types.ts
|
|
200
|
-
const RedactionToken = (0, _embedpdf_core.createCapabilityToken)("redaction");
|
|
201
|
-
//#endregion
|
|
202
213
|
//#region src/redaction.plugin.ts
|
|
203
214
|
/**
|
|
204
215
|
* Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage
|
|
@@ -212,16 +223,16 @@ const RedactionToken = (0, _embedpdf_core.createCapabilityToken)("redaction");
|
|
|
212
223
|
*/
|
|
213
224
|
const redactionPlugin = () => (0, _embedpdf_core.definePlugin)({
|
|
214
225
|
id: "redaction",
|
|
215
|
-
token: RedactionToken,
|
|
226
|
+
token: require_contract.RedactionToken,
|
|
216
227
|
scope: "document",
|
|
217
|
-
requires: [
|
|
218
|
-
optional: [
|
|
228
|
+
requires: [_embedpdf_plugin_annotation_contract_host.AnnotationToken],
|
|
229
|
+
optional: [_embedpdf_plugin_interaction_contract.InteractionToken, _embedpdf_plugin_selection_contract.SelectionToken],
|
|
219
230
|
initialState: initialRedactionState,
|
|
220
231
|
reduce: redactionReducer,
|
|
221
232
|
capability: createRedactionCapability
|
|
222
233
|
});
|
|
223
234
|
//#endregion
|
|
224
|
-
exports.RedactionToken = RedactionToken;
|
|
235
|
+
exports.RedactionToken = require_contract.RedactionToken;
|
|
225
236
|
exports.redactionPlugin = redactionPlugin;
|
|
226
237
|
|
|
227
238
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["AnnotationToken","InteractionToken","SelectionToken","AnnotationToken","InteractionToken","SelectionToken"],"sources":["../src/capability.ts","../src/reducer.ts","../src/types.ts","../src/redaction.plugin.ts"],"sourcesContent":["import type { DocCapability, PluginContext } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/internal';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { SelectionToken } from '@embedpdf/plugin-selection';\nimport type {\n AnnotationDTO,\n AnnotationRef,\n PdfRect,\n RedactionApplyResult,\n} from '@embedpdf/engine-core';\nimport type {\n RedactionAction,\n RedactionCapability,\n RedactionLabelPatch,\n RedactionPendingItem,\n RedactionState,\n} from './types';\n\n/** Applying destroys content — its own granted power, narrower than annotate. */\nconst REDACT_CAPABILITY: DocCapability = 'doc.redact';\n\n/** The annotation plugin's model-id convention for a durable ref. */\nconst idOf = (ref: AnnotationRef): string =>\n ref.kind === 'objectNumber'\n ? `obj:${ref.annotObjectNumber}`\n : ref.kind === 'nm'\n ? `nm:${ref.nm}`\n : `idx:${ref.pageObjectNumber}:${ref.index}`;\n\ntype RedactDTO = Extract<AnnotationDTO, { subtype: 'redact' }>;\n\n/** The PDF-space regions a redact mark targets: its quads' boxes, else `/Rect`. */\nfunction regionsOf(dto: RedactDTO): PdfRect[] {\n if (dto.quadPoints.length === 0) return [dto.rect];\n return dto.quadPoints.map((q) => {\n const xs = [q.p1.x, q.p2.x, q.p3.x, q.p4.x];\n const ys = [q.p1.y, q.p2.y, q.p3.y, q.p4.y];\n return {\n left: Math.min(...xs),\n right: Math.max(...xs),\n bottom: Math.min(...ys),\n top: Math.max(...ys),\n };\n });\n}\n\n/** Positive-area intersection — the engine's collateral rule. */\nconst intersects = (a: PdfRect, b: PdfRect): boolean =>\n Math.min(a.right, b.right) > Math.max(a.left, b.left) &&\n Math.min(a.top, b.top) > Math.max(a.bottom, b.bottom);\n\nexport function createRedactionCapability(\n ctx: PluginContext<RedactionState, RedactionAction>,\n): RedactionCapability {\n const anno = ctx.get(AnnotationToken);\n const appliedCallbacks = new Set<(result: RedactionApplyResult) => void>();\n\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[redaction] no document bound');\n return doc;\n };\n const requireService = () => {\n const doc = requireDoc();\n if (!doc.redaction) throw new Error('[redaction] engine has no redaction service');\n return doc.redaction;\n };\n\n const pons = (): number[] => (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber);\n\n const pageIndexOf = (pon: number): number =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon)?.index ?? -1;\n\n const pendingOn = (pon: number): RedactDTO[] =>\n anno.list(pon).filter((a): a is RedactDTO => a.subtype === 'redact');\n\n const collectPending = (): RedactionPendingItem[] => {\n const items: RedactionPendingItem[] = [];\n for (const pon of pons()) {\n for (const dto of pendingOn(pon)) {\n items.push({\n id: idOf(dto.ref),\n ref: dto.ref,\n pageObjectNumber: pon,\n pageIndex: pageIndexOf(pon),\n kind: dto.quadPoints.length > 0 ? 'text' : 'area',\n overlayText: dto.overlayText,\n });\n }\n }\n return items;\n };\n\n const fireApplied = (result: RedactionApplyResult) => {\n for (const cb of appliedCallbacks) {\n try {\n cb(result);\n } catch (err) {\n console.error('[redaction] onApplied callback failed', err);\n }\n }\n };\n\n const runApply = async (\n scope: Parameters<ReturnType<typeof requireService>['apply']>[0],\n ): Promise<RedactionApplyResult> => {\n const service = requireService();\n ctx.dispatch({ type: 'APPLY_STARTED' });\n try {\n const result = await service.apply(scope);\n ctx.dispatch({ type: 'APPLY_FINISHED', result });\n fireApplied(result);\n return result;\n } catch (err) {\n ctx.dispatch({ type: 'APPLY_FINISHED', result: null });\n throw err;\n }\n };\n\n // A REMOTE collaborator's apply arrives only as a document event (own\n // applies flow through runApply above). Annotation state + raster\n // invalidation are handled by their own planes; here we only surface it.\n const doc = ctx.doc;\n if (doc) {\n ctx.cleanup(\n doc.events.subscribe((event) => {\n if (event.type !== 'redaction.applied') return;\n if (event.origin.kind !== 'remote') return;\n ctx.dispatch({ type: 'APPLY_FINISHED', result: event });\n fireApplied(event);\n }),\n );\n }\n\n return {\n canApply: () =>\n (ctx.doc?.redaction !== undefined && ctx.doc?.security.allows(REDACT_CAPABILITY)) ?? false,\n isApplying: () => ctx.getState().applying,\n lastResult: () => ctx.getState().lastResult,\n\n enableRedact: () => ctx.tryGet(InteractionToken)?.activateTool('redact'),\n toggleRedact: () => {\n const interaction = ctx.tryGet(InteractionToken);\n if (!interaction) return;\n interaction.activateTool(interaction.activeToolId() === 'redact' ? 'pointer' : 'redact');\n },\n isRedactActive: () => ctx.tryGet(InteractionToken)?.activeToolId() === 'redact',\n\n queueCurrentSelection: async () => {\n const selection = ctx.tryGet(SelectionToken);\n if (!selection || !selection.hasSelection()) return false;\n const snapshot = selection.snapshot();\n for (const page of snapshot.pages) {\n if (page.rects.length === 0) continue;\n anno.createMarkup('redact', page.pon, page.rects, 'redact');\n }\n selection.clear();\n return true;\n },\n\n preparePending: async () => {\n await Promise.all(pons().map((pon) => anno.ensurePage(pon)));\n },\n getPending: collectPending,\n pendingCount: () => collectPending().length,\n\n estimateCollateral: (ids) => {\n const wanted = ids ? new Set(ids) : null;\n let count = 0;\n for (const pon of pons()) {\n const all = anno.list(pon);\n const marks = all.filter(\n (a): a is RedactDTO => a.subtype === 'redact' && (!wanted || wanted.has(idOf(a.ref))),\n );\n if (marks.length === 0) continue;\n const regions = marks.flatMap(regionsOf);\n for (const other of all) {\n if (other.subtype === 'redact') continue;\n if (regions.some((r) => intersects(r, other.rect))) count++;\n }\n }\n return count;\n },\n\n setLabel: async (ref, patch: RedactionLabelPatch) => {\n const current = anno.get(ref);\n if (!current || current.subtype !== 'redact') {\n throw new Error('[redaction] setLabel target is not a redact annotation');\n }\n // Always carry the current /DA styling: the engine rewrites /DA whenever\n // a label field rides a patch, so a text-only edit must not let the\n // styling fall back to defaults.\n await anno.update(ref, {\n subtype: 'redact',\n ...(patch.overlayText !== undefined ? { overlayText: patch.overlayText } : {}),\n ...(patch.repeat !== undefined ? { repeat: patch.repeat } : {}),\n fontFamily: current.fontFamily,\n fontSize: current.fontSize,\n fontColor: current.fontColor,\n textAlign: current.textAlign,\n });\n },\n\n apply: async (ids) => {\n const wanted = new Set(ids);\n const refs = collectPending()\n .filter((p) => wanted.has(p.id))\n .map((p) => p.ref);\n if (refs.length === 0) throw new Error('[redaction] no matching pending redactions');\n return runApply({ kind: 'annotations', refs });\n },\n\n applyAll: async () => {\n // Pages scope: authoritative for the WHOLE document, including marks on\n // pages this client never loaded. Pages without marks report 'unchanged'.\n const pageObjectNumbers = pons();\n if (pageObjectNumbers.length === 0) throw new Error('[redaction] document has no pages');\n return runApply({ kind: 'pages', pageObjectNumbers });\n },\n\n onApplied: (cb) => {\n appliedCallbacks.add(cb);\n return () => appliedCallbacks.delete(cb);\n },\n };\n}\n","import type { RedactionAction, RedactionState } from './types';\n\nexport const initialRedactionState = (): RedactionState => ({\n applying: false,\n lastResult: null,\n});\n\nexport const redactionReducer = (\n state: RedactionState,\n action: RedactionAction,\n): RedactionState => {\n switch (action.type) {\n case 'APPLY_STARTED':\n return { ...state, applying: true };\n case 'APPLY_FINISHED':\n return { applying: false, lastResult: action.result ?? state.lastResult };\n default:\n return state;\n }\n};\n","import { createCapabilityToken } from '@embedpdf/core';\nimport type { AnnotationRef, RedactionApplyResult } from '@embedpdf/engine-core';\n\n/**\n * One pending redaction mark — a live projection of a `redact` annotation on\n * the annotation plane (the plugin owns NO mark state of its own; delete the\n * annotation and the pending item is gone).\n */\nexport interface RedactionPendingItem {\n /** Stable list key derived from the ref. */\n id: string;\n ref: AnnotationRef;\n pageObjectNumber: number;\n /** Display index (0-based) from the page registry — for \"page N\" labels. */\n pageIndex: number;\n /** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */\n kind: 'area' | 'text';\n /** `/OverlayText` label, when set. */\n overlayText: string | null;\n}\n\nexport interface RedactionState {\n /** A destructive apply is in flight. */\n applying: boolean;\n /** The last apply result seen (own or remote), for status UI. */\n lastResult: RedactionApplyResult | null;\n}\n\nexport type RedactionAction =\n | { type: 'APPLY_STARTED' }\n | { type: 'APPLY_FINISHED'; result: RedactionApplyResult | null };\n\n/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */\nexport interface RedactionLabelPatch {\n overlayText?: string | null;\n repeat?: boolean;\n}\n\nexport interface RedactionCapability {\n /** Engine support present AND this session holds `doc.redact`. The engine\n * enforces the same capability independently — this is the UI mirror. */\n canApply(): boolean;\n isApplying(): boolean;\n lastResult(): RedactionApplyResult | null;\n\n /** The composed marking tool (text-selection over text + area drag elsewhere). */\n enableRedact(): void;\n toggleRedact(): void;\n isRedactActive(): boolean;\n\n /** Mark the current text selection (per page) and clear it. False when\n * there is no selection plugin or no selection. */\n queueCurrentSelection(): Promise<boolean>;\n\n /** Load every page's annotations so the pending view is complete (the\n * annotation plane loads lazily per page). Call when opening a panel. */\n preparePending(): Promise<void>;\n /** Pending marks over the LOADED annotation state (see preparePending). */\n getPending(): RedactionPendingItem[];\n pendingCount(): number;\n /**\n * Client-side estimate of how many OTHER annotations the given pending\n * marks (default: all) would destroy — for confirm dialogs BEFORE the\n * apply. The authoritative count comes back on the result.\n */\n estimateCollateral(ids?: string[]): number;\n\n /** Edit a mark's label. Reads the mark's current `/DA` styling and writes\n * the full label set, so a text-only edit never resets the styling. */\n setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;\n\n /** Apply specific pending marks (refs scope). Irreversible. */\n apply(ids: string[]): Promise<RedactionApplyResult>;\n /** Apply every redaction in the document (pages scope — including marks on\n * pages this client never loaded). Irreversible. */\n applyAll(): Promise<RedactionApplyResult>;\n\n /** Fires after ANY confirmed apply — own or a remote collaborator's. */\n onApplied(cb: (result: RedactionApplyResult) => void): () => void;\n}\n\nexport const RedactionToken = createCapabilityToken<RedactionCapability>('redaction');\n","import { definePlugin } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/internal';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { SelectionToken } from '@embedpdf/plugin-selection';\nimport { createRedactionCapability } from './capability';\nimport { initialRedactionState, redactionReducer } from './reducer';\nimport { RedactionToken } from './types';\nimport type { RedactionAction, RedactionCapability, RedactionState } from './types';\n\n/**\n * Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage\n * model. Marking is annotation-plane work (the composed `redact` tool ships\n * with plugin-annotation); this plugin wraps `doc.redaction.apply`, projects\n * the pending queue out of annotation state, and estimates collateral for\n * confirm dialogs. It owns no mark state of its own.\n *\n * Trust boundary: on a layered document, applying rewrites the LAYER's bytes;\n * the immutable base keeps the original. See the package README.\n */\nexport const redactionPlugin = () =>\n definePlugin<RedactionState, RedactionAction, RedactionCapability>({\n id: 'redaction',\n token: RedactionToken,\n scope: 'document',\n requires: [AnnotationToken],\n optional: [InteractionToken, SelectionToken],\n initialState: initialRedactionState,\n reduce: redactionReducer,\n capability: createRedactionCapability,\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["AnnotationToken","InteractionToken","SelectionToken","RedactionToken","AnnotationToken","InteractionToken","SelectionToken"],"sources":["../src/capability.ts","../src/reducer.ts","../src/redaction.plugin.ts"],"sourcesContent":["import type { DocCapability, PluginContext } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { SelectionToken } from '@embedpdf/plugin-selection/contract';\nimport type {\n AnnotationDTO,\n AnnotationRef,\n PdfRect,\n RedactionApplyResult,\n} from '@embedpdf/engine-core';\nimport type {\n RedactionAction,\n RedactionCapability,\n RedactionLabelPatch,\n RedactionPendingItem,\n RedactionState,\n} from './types';\n\n/** Applying destroys content — its own granted power, narrower than annotate.\n * The engine's apply asserts all three (LocalDocumentRedactionService; the\n * cloud route guards match): the redact grant itself, the page-content\n * rewrite, and the annotation consumption. `canApply` mirrors the full set. */\nconst APPLY_CAPABILITIES: readonly DocCapability[] = [\n 'doc.redact',\n 'doc.pages.modify',\n 'doc.annotate.modify',\n];\n\n/** The annotation plugin's model-id convention for a durable ref. */\nconst idOf = (ref: AnnotationRef): string =>\n ref.kind === 'objectNumber'\n ? `obj:${ref.annotObjectNumber}`\n : ref.kind === 'nm'\n ? `nm:${ref.nm}`\n : `idx:${ref.pageObjectNumber}:${ref.index}`;\n\ntype RedactDTO = Extract<AnnotationDTO, { subtype: 'redact' }>;\n\n/** The PDF-space regions a redact mark targets: its quads' boxes, else `/Rect`. */\nfunction regionsOf(dto: RedactDTO): PdfRect[] {\n if (dto.quadPoints.length === 0) return [dto.rect];\n return dto.quadPoints.map((q) => {\n const xs = [q.p1.x, q.p2.x, q.p3.x, q.p4.x];\n const ys = [q.p1.y, q.p2.y, q.p3.y, q.p4.y];\n return {\n left: Math.min(...xs),\n right: Math.max(...xs),\n bottom: Math.min(...ys),\n top: Math.max(...ys),\n };\n });\n}\n\n/** Positive-area intersection — the engine's collateral rule. */\nconst intersects = (a: PdfRect, b: PdfRect): boolean =>\n Math.min(a.right, b.right) > Math.max(a.left, b.left) &&\n Math.min(a.top, b.top) > Math.max(a.bottom, b.bottom);\n\nexport function createRedactionCapability(\n ctx: PluginContext<RedactionState, RedactionAction>,\n): RedactionCapability {\n const anno = ctx.get(AnnotationToken);\n const appliedCallbacks = new Set<(result: RedactionApplyResult) => void>();\n\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[redaction] no document bound');\n return doc;\n };\n const requireService = () => {\n const doc = requireDoc();\n if (!doc.redaction) throw new Error('[redaction] engine has no redaction service');\n return doc.redaction;\n };\n\n const pons = (): number[] => (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber);\n\n const pageIndexOf = (pon: number): number =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon)?.index ?? -1;\n\n const pendingOn = (pon: number): RedactDTO[] =>\n anno.list(pon).filter((a): a is RedactDTO => a.subtype === 'redact');\n\n const collectPending = (): RedactionPendingItem[] => {\n const items: RedactionPendingItem[] = [];\n for (const pon of pons()) {\n for (const dto of pendingOn(pon)) {\n items.push({\n id: idOf(dto.ref),\n ref: dto.ref,\n pageObjectNumber: pon,\n pageIndex: pageIndexOf(pon),\n kind: dto.quadPoints.length > 0 ? 'text' : 'area',\n overlayText: dto.overlayText,\n });\n }\n }\n return items;\n };\n\n const fireApplied = (result: RedactionApplyResult) => {\n for (const cb of appliedCallbacks) {\n try {\n cb(result);\n } catch (err) {\n console.error('[redaction] onApplied callback failed', err);\n }\n }\n };\n\n const runApply = async (\n scope: Parameters<ReturnType<typeof requireService>['apply']>[0],\n ): Promise<RedactionApplyResult> => {\n const service = requireService();\n ctx.dispatch({ type: 'APPLY_STARTED' });\n try {\n const result = await service.apply(scope);\n ctx.dispatch({ type: 'APPLY_FINISHED', result });\n fireApplied(result);\n return result;\n } catch (err) {\n ctx.dispatch({ type: 'APPLY_FINISHED', result: null });\n throw err;\n }\n };\n\n // A REMOTE collaborator's apply arrives only as a document event (own\n // applies flow through runApply above). Annotation state + raster\n // invalidation are handled by their own planes; here we only surface it.\n const doc = ctx.doc;\n if (doc) {\n ctx.cleanup(\n doc.events.subscribe((event) => {\n if (event.type !== 'redaction.applied') return;\n if (event.origin.kind !== 'remote') return;\n ctx.dispatch({ type: 'APPLY_FINISHED', result: event });\n fireApplied(event);\n }),\n );\n }\n\n return {\n // Marks are annotations — marking authority IS annotation create\n // authority (the annotation plugin's own twin, not a re-derivation).\n canMark: () => anno.canCreate(),\n // Mirror EVERYTHING the engine's apply asserts, not just the headline\n // capability — a token granted `doc.redact` à la carte without its bit-4\n // siblings must not see an armed Apply button.\n canApply: () => {\n const doc = ctx.doc;\n if (!doc || doc.redaction === undefined) return false;\n return APPLY_CAPABILITIES.every((cap) => doc.security.allows(cap));\n },\n isApplying: () => ctx.getState().applying,\n lastResult: () => ctx.getState().lastResult,\n\n enableRedact: () => ctx.tryGet(InteractionToken)?.activateTool('redact'),\n toggleRedact: () => {\n const interaction = ctx.tryGet(InteractionToken);\n if (!interaction) return;\n interaction.activateTool(interaction.activeToolId() === 'redact' ? 'pointer' : 'redact');\n },\n isRedactActive: () => ctx.tryGet(InteractionToken)?.activeToolId() === 'redact',\n\n queueCurrentSelection: async () => {\n // Marks are optimistic annotation creates — the same gate the pointer\n // path has (the annotation plugin refuses ungated creates too).\n if (!anno.canCreate()) return false;\n const selection = ctx.tryGet(SelectionToken);\n if (!selection || !selection.hasSelection()) return false;\n const snapshot = selection.snapshot();\n for (const page of snapshot.pages) {\n if (page.segments.length === 0) continue;\n // Preserve the selected text frame all the way into `/QuadPoints`.\n // Native apply now uses these exact cells for text removal and the\n // final overlay, so marked == previewed == applied for oriented text.\n anno.createMarkup(\n 'redact',\n page.pon,\n page.segments.map((segment) => segment.quad),\n 'redact',\n );\n }\n selection.clear();\n return true;\n },\n\n preparePending: async () => {\n await Promise.all(pons().map((pon) => anno.ensurePage(pon)));\n },\n getPending: collectPending,\n pendingCount: () => collectPending().length,\n\n estimateCollateral: (ids) => {\n const wanted = ids ? new Set(ids) : null;\n let count = 0;\n for (const pon of pons()) {\n const all = anno.list(pon);\n const marks = all.filter(\n (a): a is RedactDTO => a.subtype === 'redact' && (!wanted || wanted.has(idOf(a.ref))),\n );\n if (marks.length === 0) continue;\n const regions = marks.flatMap(regionsOf);\n for (const other of all) {\n if (other.subtype === 'redact') continue;\n if (regions.some((r) => intersects(r, other.rect))) count++;\n }\n }\n return count;\n },\n\n setLabel: async (ref, patch: RedactionLabelPatch) => {\n const current = anno.get(ref);\n if (!current || current.subtype !== 'redact') {\n throw new Error('[redaction] setLabel target is not a redact annotation');\n }\n // Always carry the current /DA styling: the engine rewrites /DA whenever\n // a label field rides a patch, so a text-only edit must not let the\n // styling fall back to defaults.\n await anno.update(ref, {\n subtype: 'redact',\n ...(patch.overlayText !== undefined ? { overlayText: patch.overlayText } : {}),\n ...(patch.repeat !== undefined ? { repeat: patch.repeat } : {}),\n fontFamily: current.fontFamily,\n fontSize: current.fontSize,\n fontColor: current.fontColor,\n textAlign: current.textAlign,\n });\n },\n\n apply: async (ids) => {\n const wanted = new Set(ids);\n const refs = collectPending()\n .filter((p) => wanted.has(p.id))\n .map((p) => p.ref);\n if (refs.length === 0) throw new Error('[redaction] no matching pending redactions');\n return runApply({ kind: 'annotations', refs });\n },\n\n applyAll: async () => {\n // Pages scope: authoritative for the WHOLE document, including marks on\n // pages this client never loaded. Pages without marks report 'unchanged'.\n const pageObjectNumbers = pons();\n if (pageObjectNumbers.length === 0) throw new Error('[redaction] document has no pages');\n return runApply({ kind: 'pages', pageObjectNumbers });\n },\n\n onApplied: (cb) => {\n appliedCallbacks.add(cb);\n return () => appliedCallbacks.delete(cb);\n },\n };\n}\n","import type { RedactionAction, RedactionState } from './types';\n\nexport const initialRedactionState = (): RedactionState => ({\n applying: false,\n lastResult: null,\n});\n\nexport const redactionReducer = (\n state: RedactionState,\n action: RedactionAction,\n): RedactionState => {\n switch (action.type) {\n case 'APPLY_STARTED':\n return { ...state, applying: true };\n case 'APPLY_FINISHED':\n return { applying: false, lastResult: action.result ?? state.lastResult };\n default:\n return state;\n }\n};\n","import { definePlugin } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { SelectionToken } from '@embedpdf/plugin-selection/contract';\nimport { createRedactionCapability } from './capability';\nimport { initialRedactionState, redactionReducer } from './reducer';\nimport { RedactionToken } from './types';\nimport type { RedactionAction, RedactionCapability, RedactionState } from './types';\n\n/**\n * Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage\n * model. Marking is annotation-plane work (the composed `redact` tool ships\n * with plugin-annotation); this plugin wraps `doc.redaction.apply`, projects\n * the pending queue out of annotation state, and estimates collateral for\n * confirm dialogs. It owns no mark state of its own.\n *\n * Trust boundary: on a layered document, applying rewrites the LAYER's bytes;\n * the immutable base keeps the original. See the package README.\n */\nexport const redactionPlugin = () =>\n definePlugin<RedactionState, RedactionAction, RedactionCapability>({\n id: 'redaction',\n token: RedactionToken,\n scope: 'document',\n requires: [AnnotationToken],\n optional: [InteractionToken, SelectionToken],\n initialState: initialRedactionState,\n reduce: redactionReducer,\n capability: createRedactionCapability,\n });\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,77 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/types.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* One pending redaction mark — a live projection of a `redact` annotation on
|
|
5
|
-
* the annotation plane (the plugin owns NO mark state of its own; delete the
|
|
6
|
-
* annotation and the pending item is gone).
|
|
7
|
-
*/
|
|
8
|
-
interface RedactionPendingItem {
|
|
9
|
-
/** Stable list key derived from the ref. */
|
|
10
|
-
id: string;
|
|
11
|
-
ref: AnnotationRef;
|
|
12
|
-
pageObjectNumber: number;
|
|
13
|
-
/** Display index (0-based) from the page registry — for "page N" labels. */
|
|
14
|
-
pageIndex: number;
|
|
15
|
-
/** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */
|
|
16
|
-
kind: 'area' | 'text';
|
|
17
|
-
/** `/OverlayText` label, when set. */
|
|
18
|
-
overlayText: string | null;
|
|
19
|
-
}
|
|
20
|
-
interface RedactionState {
|
|
21
|
-
/** A destructive apply is in flight. */
|
|
22
|
-
applying: boolean;
|
|
23
|
-
/** The last apply result seen (own or remote), for status UI. */
|
|
24
|
-
lastResult: RedactionApplyResult$1 | null;
|
|
25
|
-
}
|
|
26
|
-
type RedactionAction = {
|
|
27
|
-
type: 'APPLY_STARTED';
|
|
28
|
-
} | {
|
|
29
|
-
type: 'APPLY_FINISHED';
|
|
30
|
-
result: RedactionApplyResult$1 | null;
|
|
31
|
-
};
|
|
32
|
-
/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */
|
|
33
|
-
interface RedactionLabelPatch {
|
|
34
|
-
overlayText?: string | null;
|
|
35
|
-
repeat?: boolean;
|
|
36
|
-
}
|
|
37
|
-
interface RedactionCapability {
|
|
38
|
-
/** Engine support present AND this session holds `doc.redact`. The engine
|
|
39
|
-
* enforces the same capability independently — this is the UI mirror. */
|
|
40
|
-
canApply(): boolean;
|
|
41
|
-
isApplying(): boolean;
|
|
42
|
-
lastResult(): RedactionApplyResult$1 | null;
|
|
43
|
-
/** The composed marking tool (text-selection over text + area drag elsewhere). */
|
|
44
|
-
enableRedact(): void;
|
|
45
|
-
toggleRedact(): void;
|
|
46
|
-
isRedactActive(): boolean;
|
|
47
|
-
/** Mark the current text selection (per page) and clear it. False when
|
|
48
|
-
* there is no selection plugin or no selection. */
|
|
49
|
-
queueCurrentSelection(): Promise<boolean>;
|
|
50
|
-
/** Load every page's annotations so the pending view is complete (the
|
|
51
|
-
* annotation plane loads lazily per page). Call when opening a panel. */
|
|
52
|
-
preparePending(): Promise<void>;
|
|
53
|
-
/** Pending marks over the LOADED annotation state (see preparePending). */
|
|
54
|
-
getPending(): RedactionPendingItem[];
|
|
55
|
-
pendingCount(): number;
|
|
56
|
-
/**
|
|
57
|
-
* Client-side estimate of how many OTHER annotations the given pending
|
|
58
|
-
* marks (default: all) would destroy — for confirm dialogs BEFORE the
|
|
59
|
-
* apply. The authoritative count comes back on the result.
|
|
60
|
-
*/
|
|
61
|
-
estimateCollateral(ids?: string[]): number;
|
|
62
|
-
/** Edit a mark's label. Reads the mark's current `/DA` styling and writes
|
|
63
|
-
* the full label set, so a text-only edit never resets the styling. */
|
|
64
|
-
setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;
|
|
65
|
-
/** Apply specific pending marks (refs scope). Irreversible. */
|
|
66
|
-
apply(ids: string[]): Promise<RedactionApplyResult$1>;
|
|
67
|
-
/** Apply every redaction in the document (pages scope — including marks on
|
|
68
|
-
* pages this client never loaded). Irreversible. */
|
|
69
|
-
applyAll(): Promise<RedactionApplyResult$1>;
|
|
70
|
-
/** Fires after ANY confirmed apply — own or a remote collaborator's. */
|
|
71
|
-
onApplied(cb: (result: RedactionApplyResult$1) => void): () => void;
|
|
72
|
-
}
|
|
73
|
-
declare const RedactionToken: import("@embedpdf/core").CapabilityToken<RedactionCapability>;
|
|
74
|
-
//#endregion
|
|
1
|
+
import { a as RedactionPendingItem, i as RedactionLabelPatch, n as RedactionAction, o as RedactionState, r as RedactionCapability, s as RedactionToken, t as RedactionApplyResult } from "./contract--fJOCVRN.cjs";
|
|
75
2
|
//#region src/redaction.plugin.d.ts
|
|
76
3
|
/**
|
|
77
4
|
* Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage
|
package/dist/index.d.ts
CHANGED
|
@@ -1,77 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/types.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* One pending redaction mark — a live projection of a `redact` annotation on
|
|
5
|
-
* the annotation plane (the plugin owns NO mark state of its own; delete the
|
|
6
|
-
* annotation and the pending item is gone).
|
|
7
|
-
*/
|
|
8
|
-
interface RedactionPendingItem {
|
|
9
|
-
/** Stable list key derived from the ref. */
|
|
10
|
-
id: string;
|
|
11
|
-
ref: AnnotationRef;
|
|
12
|
-
pageObjectNumber: number;
|
|
13
|
-
/** Display index (0-based) from the page registry — for "page N" labels. */
|
|
14
|
-
pageIndex: number;
|
|
15
|
-
/** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */
|
|
16
|
-
kind: 'area' | 'text';
|
|
17
|
-
/** `/OverlayText` label, when set. */
|
|
18
|
-
overlayText: string | null;
|
|
19
|
-
}
|
|
20
|
-
interface RedactionState {
|
|
21
|
-
/** A destructive apply is in flight. */
|
|
22
|
-
applying: boolean;
|
|
23
|
-
/** The last apply result seen (own or remote), for status UI. */
|
|
24
|
-
lastResult: RedactionApplyResult$1 | null;
|
|
25
|
-
}
|
|
26
|
-
type RedactionAction = {
|
|
27
|
-
type: 'APPLY_STARTED';
|
|
28
|
-
} | {
|
|
29
|
-
type: 'APPLY_FINISHED';
|
|
30
|
-
result: RedactionApplyResult$1 | null;
|
|
31
|
-
};
|
|
32
|
-
/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */
|
|
33
|
-
interface RedactionLabelPatch {
|
|
34
|
-
overlayText?: string | null;
|
|
35
|
-
repeat?: boolean;
|
|
36
|
-
}
|
|
37
|
-
interface RedactionCapability {
|
|
38
|
-
/** Engine support present AND this session holds `doc.redact`. The engine
|
|
39
|
-
* enforces the same capability independently — this is the UI mirror. */
|
|
40
|
-
canApply(): boolean;
|
|
41
|
-
isApplying(): boolean;
|
|
42
|
-
lastResult(): RedactionApplyResult$1 | null;
|
|
43
|
-
/** The composed marking tool (text-selection over text + area drag elsewhere). */
|
|
44
|
-
enableRedact(): void;
|
|
45
|
-
toggleRedact(): void;
|
|
46
|
-
isRedactActive(): boolean;
|
|
47
|
-
/** Mark the current text selection (per page) and clear it. False when
|
|
48
|
-
* there is no selection plugin or no selection. */
|
|
49
|
-
queueCurrentSelection(): Promise<boolean>;
|
|
50
|
-
/** Load every page's annotations so the pending view is complete (the
|
|
51
|
-
* annotation plane loads lazily per page). Call when opening a panel. */
|
|
52
|
-
preparePending(): Promise<void>;
|
|
53
|
-
/** Pending marks over the LOADED annotation state (see preparePending). */
|
|
54
|
-
getPending(): RedactionPendingItem[];
|
|
55
|
-
pendingCount(): number;
|
|
56
|
-
/**
|
|
57
|
-
* Client-side estimate of how many OTHER annotations the given pending
|
|
58
|
-
* marks (default: all) would destroy — for confirm dialogs BEFORE the
|
|
59
|
-
* apply. The authoritative count comes back on the result.
|
|
60
|
-
*/
|
|
61
|
-
estimateCollateral(ids?: string[]): number;
|
|
62
|
-
/** Edit a mark's label. Reads the mark's current `/DA` styling and writes
|
|
63
|
-
* the full label set, so a text-only edit never resets the styling. */
|
|
64
|
-
setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;
|
|
65
|
-
/** Apply specific pending marks (refs scope). Irreversible. */
|
|
66
|
-
apply(ids: string[]): Promise<RedactionApplyResult$1>;
|
|
67
|
-
/** Apply every redaction in the document (pages scope — including marks on
|
|
68
|
-
* pages this client never loaded). Irreversible. */
|
|
69
|
-
applyAll(): Promise<RedactionApplyResult$1>;
|
|
70
|
-
/** Fires after ANY confirmed apply — own or a remote collaborator's. */
|
|
71
|
-
onApplied(cb: (result: RedactionApplyResult$1) => void): () => void;
|
|
72
|
-
}
|
|
73
|
-
declare const RedactionToken: import("@embedpdf/core").CapabilityToken<RedactionCapability>;
|
|
74
|
-
//#endregion
|
|
1
|
+
import { a as RedactionPendingItem, i as RedactionLabelPatch, n as RedactionAction, o as RedactionState, r as RedactionCapability, s as RedactionToken, t as RedactionApplyResult } from "./contract--fJOCVRN.js";
|
|
75
2
|
//#region src/redaction.plugin.d.ts
|
|
76
3
|
/**
|
|
77
4
|
* Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { t as RedactionToken } from "./contract-BZLrD3p2.js";
|
|
2
|
+
import { definePlugin } from "@embedpdf/core";
|
|
3
|
+
import { AnnotationToken } from "@embedpdf/plugin-annotation/contract/host";
|
|
4
|
+
import { InteractionToken } from "@embedpdf/plugin-interaction/contract";
|
|
5
|
+
import { SelectionToken } from "@embedpdf/plugin-selection/contract";
|
|
5
6
|
//#region src/capability.ts
|
|
6
|
-
/** Applying destroys content — its own granted power, narrower than annotate.
|
|
7
|
-
|
|
7
|
+
/** Applying destroys content — its own granted power, narrower than annotate.
|
|
8
|
+
* The engine's apply asserts all three (LocalDocumentRedactionService; the
|
|
9
|
+
* cloud route guards match): the redact grant itself, the page-content
|
|
10
|
+
* rewrite, and the annotation consumption. `canApply` mirrors the full set. */
|
|
11
|
+
const APPLY_CAPABILITIES = [
|
|
12
|
+
"doc.redact",
|
|
13
|
+
"doc.pages.modify",
|
|
14
|
+
"doc.annotate.modify"
|
|
15
|
+
];
|
|
8
16
|
/** The annotation plugin's model-id convention for a durable ref. */
|
|
9
17
|
const idOf = (ref) => ref.kind === "objectNumber" ? `obj:${ref.annotObjectNumber}` : ref.kind === "nm" ? `nm:${ref.nm}` : `idx:${ref.pageObjectNumber}:${ref.index}`;
|
|
10
18
|
/** The PDF-space regions a redact mark targets: its quads' boxes, else `/Rect`. */
|
|
@@ -98,7 +106,12 @@ function createRedactionCapability(ctx) {
|
|
|
98
106
|
fireApplied(event);
|
|
99
107
|
}));
|
|
100
108
|
return {
|
|
101
|
-
|
|
109
|
+
canMark: () => anno.canCreate(),
|
|
110
|
+
canApply: () => {
|
|
111
|
+
const doc = ctx.doc;
|
|
112
|
+
if (!doc || doc.redaction === void 0) return false;
|
|
113
|
+
return APPLY_CAPABILITIES.every((cap) => doc.security.allows(cap));
|
|
114
|
+
},
|
|
102
115
|
isApplying: () => ctx.getState().applying,
|
|
103
116
|
lastResult: () => ctx.getState().lastResult,
|
|
104
117
|
enableRedact: () => ctx.tryGet(InteractionToken)?.activateTool("redact"),
|
|
@@ -109,12 +122,13 @@ function createRedactionCapability(ctx) {
|
|
|
109
122
|
},
|
|
110
123
|
isRedactActive: () => ctx.tryGet(InteractionToken)?.activeToolId() === "redact",
|
|
111
124
|
queueCurrentSelection: async () => {
|
|
125
|
+
if (!anno.canCreate()) return false;
|
|
112
126
|
const selection = ctx.tryGet(SelectionToken);
|
|
113
127
|
if (!selection || !selection.hasSelection()) return false;
|
|
114
128
|
const snapshot = selection.snapshot();
|
|
115
129
|
for (const page of snapshot.pages) {
|
|
116
|
-
if (page.
|
|
117
|
-
anno.createMarkup("redact", page.pon, page.
|
|
130
|
+
if (page.segments.length === 0) continue;
|
|
131
|
+
anno.createMarkup("redact", page.pon, page.segments.map((segment) => segment.quad), "redact");
|
|
118
132
|
}
|
|
119
133
|
selection.clear();
|
|
120
134
|
return true;
|
|
@@ -195,9 +209,6 @@ const redactionReducer = (state, action) => {
|
|
|
195
209
|
}
|
|
196
210
|
};
|
|
197
211
|
//#endregion
|
|
198
|
-
//#region src/types.ts
|
|
199
|
-
const RedactionToken = createCapabilityToken("redaction");
|
|
200
|
-
//#endregion
|
|
201
212
|
//#region src/redaction.plugin.ts
|
|
202
213
|
/**
|
|
203
214
|
* Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/capability.ts","../src/reducer.ts","../src/types.ts","../src/redaction.plugin.ts"],"sourcesContent":["import type { DocCapability, PluginContext } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/internal';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { SelectionToken } from '@embedpdf/plugin-selection';\nimport type {\n AnnotationDTO,\n AnnotationRef,\n PdfRect,\n RedactionApplyResult,\n} from '@embedpdf/engine-core';\nimport type {\n RedactionAction,\n RedactionCapability,\n RedactionLabelPatch,\n RedactionPendingItem,\n RedactionState,\n} from './types';\n\n/** Applying destroys content — its own granted power, narrower than annotate. */\nconst REDACT_CAPABILITY: DocCapability = 'doc.redact';\n\n/** The annotation plugin's model-id convention for a durable ref. */\nconst idOf = (ref: AnnotationRef): string =>\n ref.kind === 'objectNumber'\n ? `obj:${ref.annotObjectNumber}`\n : ref.kind === 'nm'\n ? `nm:${ref.nm}`\n : `idx:${ref.pageObjectNumber}:${ref.index}`;\n\ntype RedactDTO = Extract<AnnotationDTO, { subtype: 'redact' }>;\n\n/** The PDF-space regions a redact mark targets: its quads' boxes, else `/Rect`. */\nfunction regionsOf(dto: RedactDTO): PdfRect[] {\n if (dto.quadPoints.length === 0) return [dto.rect];\n return dto.quadPoints.map((q) => {\n const xs = [q.p1.x, q.p2.x, q.p3.x, q.p4.x];\n const ys = [q.p1.y, q.p2.y, q.p3.y, q.p4.y];\n return {\n left: Math.min(...xs),\n right: Math.max(...xs),\n bottom: Math.min(...ys),\n top: Math.max(...ys),\n };\n });\n}\n\n/** Positive-area intersection — the engine's collateral rule. */\nconst intersects = (a: PdfRect, b: PdfRect): boolean =>\n Math.min(a.right, b.right) > Math.max(a.left, b.left) &&\n Math.min(a.top, b.top) > Math.max(a.bottom, b.bottom);\n\nexport function createRedactionCapability(\n ctx: PluginContext<RedactionState, RedactionAction>,\n): RedactionCapability {\n const anno = ctx.get(AnnotationToken);\n const appliedCallbacks = new Set<(result: RedactionApplyResult) => void>();\n\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[redaction] no document bound');\n return doc;\n };\n const requireService = () => {\n const doc = requireDoc();\n if (!doc.redaction) throw new Error('[redaction] engine has no redaction service');\n return doc.redaction;\n };\n\n const pons = (): number[] => (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber);\n\n const pageIndexOf = (pon: number): number =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon)?.index ?? -1;\n\n const pendingOn = (pon: number): RedactDTO[] =>\n anno.list(pon).filter((a): a is RedactDTO => a.subtype === 'redact');\n\n const collectPending = (): RedactionPendingItem[] => {\n const items: RedactionPendingItem[] = [];\n for (const pon of pons()) {\n for (const dto of pendingOn(pon)) {\n items.push({\n id: idOf(dto.ref),\n ref: dto.ref,\n pageObjectNumber: pon,\n pageIndex: pageIndexOf(pon),\n kind: dto.quadPoints.length > 0 ? 'text' : 'area',\n overlayText: dto.overlayText,\n });\n }\n }\n return items;\n };\n\n const fireApplied = (result: RedactionApplyResult) => {\n for (const cb of appliedCallbacks) {\n try {\n cb(result);\n } catch (err) {\n console.error('[redaction] onApplied callback failed', err);\n }\n }\n };\n\n const runApply = async (\n scope: Parameters<ReturnType<typeof requireService>['apply']>[0],\n ): Promise<RedactionApplyResult> => {\n const service = requireService();\n ctx.dispatch({ type: 'APPLY_STARTED' });\n try {\n const result = await service.apply(scope);\n ctx.dispatch({ type: 'APPLY_FINISHED', result });\n fireApplied(result);\n return result;\n } catch (err) {\n ctx.dispatch({ type: 'APPLY_FINISHED', result: null });\n throw err;\n }\n };\n\n // A REMOTE collaborator's apply arrives only as a document event (own\n // applies flow through runApply above). Annotation state + raster\n // invalidation are handled by their own planes; here we only surface it.\n const doc = ctx.doc;\n if (doc) {\n ctx.cleanup(\n doc.events.subscribe((event) => {\n if (event.type !== 'redaction.applied') return;\n if (event.origin.kind !== 'remote') return;\n ctx.dispatch({ type: 'APPLY_FINISHED', result: event });\n fireApplied(event);\n }),\n );\n }\n\n return {\n canApply: () =>\n (ctx.doc?.redaction !== undefined && ctx.doc?.security.allows(REDACT_CAPABILITY)) ?? false,\n isApplying: () => ctx.getState().applying,\n lastResult: () => ctx.getState().lastResult,\n\n enableRedact: () => ctx.tryGet(InteractionToken)?.activateTool('redact'),\n toggleRedact: () => {\n const interaction = ctx.tryGet(InteractionToken);\n if (!interaction) return;\n interaction.activateTool(interaction.activeToolId() === 'redact' ? 'pointer' : 'redact');\n },\n isRedactActive: () => ctx.tryGet(InteractionToken)?.activeToolId() === 'redact',\n\n queueCurrentSelection: async () => {\n const selection = ctx.tryGet(SelectionToken);\n if (!selection || !selection.hasSelection()) return false;\n const snapshot = selection.snapshot();\n for (const page of snapshot.pages) {\n if (page.rects.length === 0) continue;\n anno.createMarkup('redact', page.pon, page.rects, 'redact');\n }\n selection.clear();\n return true;\n },\n\n preparePending: async () => {\n await Promise.all(pons().map((pon) => anno.ensurePage(pon)));\n },\n getPending: collectPending,\n pendingCount: () => collectPending().length,\n\n estimateCollateral: (ids) => {\n const wanted = ids ? new Set(ids) : null;\n let count = 0;\n for (const pon of pons()) {\n const all = anno.list(pon);\n const marks = all.filter(\n (a): a is RedactDTO => a.subtype === 'redact' && (!wanted || wanted.has(idOf(a.ref))),\n );\n if (marks.length === 0) continue;\n const regions = marks.flatMap(regionsOf);\n for (const other of all) {\n if (other.subtype === 'redact') continue;\n if (regions.some((r) => intersects(r, other.rect))) count++;\n }\n }\n return count;\n },\n\n setLabel: async (ref, patch: RedactionLabelPatch) => {\n const current = anno.get(ref);\n if (!current || current.subtype !== 'redact') {\n throw new Error('[redaction] setLabel target is not a redact annotation');\n }\n // Always carry the current /DA styling: the engine rewrites /DA whenever\n // a label field rides a patch, so a text-only edit must not let the\n // styling fall back to defaults.\n await anno.update(ref, {\n subtype: 'redact',\n ...(patch.overlayText !== undefined ? { overlayText: patch.overlayText } : {}),\n ...(patch.repeat !== undefined ? { repeat: patch.repeat } : {}),\n fontFamily: current.fontFamily,\n fontSize: current.fontSize,\n fontColor: current.fontColor,\n textAlign: current.textAlign,\n });\n },\n\n apply: async (ids) => {\n const wanted = new Set(ids);\n const refs = collectPending()\n .filter((p) => wanted.has(p.id))\n .map((p) => p.ref);\n if (refs.length === 0) throw new Error('[redaction] no matching pending redactions');\n return runApply({ kind: 'annotations', refs });\n },\n\n applyAll: async () => {\n // Pages scope: authoritative for the WHOLE document, including marks on\n // pages this client never loaded. Pages without marks report 'unchanged'.\n const pageObjectNumbers = pons();\n if (pageObjectNumbers.length === 0) throw new Error('[redaction] document has no pages');\n return runApply({ kind: 'pages', pageObjectNumbers });\n },\n\n onApplied: (cb) => {\n appliedCallbacks.add(cb);\n return () => appliedCallbacks.delete(cb);\n },\n };\n}\n","import type { RedactionAction, RedactionState } from './types';\n\nexport const initialRedactionState = (): RedactionState => ({\n applying: false,\n lastResult: null,\n});\n\nexport const redactionReducer = (\n state: RedactionState,\n action: RedactionAction,\n): RedactionState => {\n switch (action.type) {\n case 'APPLY_STARTED':\n return { ...state, applying: true };\n case 'APPLY_FINISHED':\n return { applying: false, lastResult: action.result ?? state.lastResult };\n default:\n return state;\n }\n};\n","import { createCapabilityToken } from '@embedpdf/core';\nimport type { AnnotationRef, RedactionApplyResult } from '@embedpdf/engine-core';\n\n/**\n * One pending redaction mark — a live projection of a `redact` annotation on\n * the annotation plane (the plugin owns NO mark state of its own; delete the\n * annotation and the pending item is gone).\n */\nexport interface RedactionPendingItem {\n /** Stable list key derived from the ref. */\n id: string;\n ref: AnnotationRef;\n pageObjectNumber: number;\n /** Display index (0-based) from the page registry — for \"page N\" labels. */\n pageIndex: number;\n /** `area` = rect-only mark (marquee); `text` = per-line quads (selection). */\n kind: 'area' | 'text';\n /** `/OverlayText` label, when set. */\n overlayText: string | null;\n}\n\nexport interface RedactionState {\n /** A destructive apply is in flight. */\n applying: boolean;\n /** The last apply result seen (own or remote), for status UI. */\n lastResult: RedactionApplyResult | null;\n}\n\nexport type RedactionAction =\n | { type: 'APPLY_STARTED' }\n | { type: 'APPLY_FINISHED'; result: RedactionApplyResult | null };\n\n/** A label edit: `overlayText: null` clears it; `repeat` tiles it. */\nexport interface RedactionLabelPatch {\n overlayText?: string | null;\n repeat?: boolean;\n}\n\nexport interface RedactionCapability {\n /** Engine support present AND this session holds `doc.redact`. The engine\n * enforces the same capability independently — this is the UI mirror. */\n canApply(): boolean;\n isApplying(): boolean;\n lastResult(): RedactionApplyResult | null;\n\n /** The composed marking tool (text-selection over text + area drag elsewhere). */\n enableRedact(): void;\n toggleRedact(): void;\n isRedactActive(): boolean;\n\n /** Mark the current text selection (per page) and clear it. False when\n * there is no selection plugin or no selection. */\n queueCurrentSelection(): Promise<boolean>;\n\n /** Load every page's annotations so the pending view is complete (the\n * annotation plane loads lazily per page). Call when opening a panel. */\n preparePending(): Promise<void>;\n /** Pending marks over the LOADED annotation state (see preparePending). */\n getPending(): RedactionPendingItem[];\n pendingCount(): number;\n /**\n * Client-side estimate of how many OTHER annotations the given pending\n * marks (default: all) would destroy — for confirm dialogs BEFORE the\n * apply. The authoritative count comes back on the result.\n */\n estimateCollateral(ids?: string[]): number;\n\n /** Edit a mark's label. Reads the mark's current `/DA` styling and writes\n * the full label set, so a text-only edit never resets the styling. */\n setLabel(ref: AnnotationRef, patch: RedactionLabelPatch): Promise<void>;\n\n /** Apply specific pending marks (refs scope). Irreversible. */\n apply(ids: string[]): Promise<RedactionApplyResult>;\n /** Apply every redaction in the document (pages scope — including marks on\n * pages this client never loaded). Irreversible. */\n applyAll(): Promise<RedactionApplyResult>;\n\n /** Fires after ANY confirmed apply — own or a remote collaborator's. */\n onApplied(cb: (result: RedactionApplyResult) => void): () => void;\n}\n\nexport const RedactionToken = createCapabilityToken<RedactionCapability>('redaction');\n","import { definePlugin } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/internal';\nimport { InteractionToken } from '@embedpdf/plugin-interaction';\nimport { SelectionToken } from '@embedpdf/plugin-selection';\nimport { createRedactionCapability } from './capability';\nimport { initialRedactionState, redactionReducer } from './reducer';\nimport { RedactionToken } from './types';\nimport type { RedactionAction, RedactionCapability, RedactionState } from './types';\n\n/**\n * Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage\n * model. Marking is annotation-plane work (the composed `redact` tool ships\n * with plugin-annotation); this plugin wraps `doc.redaction.apply`, projects\n * the pending queue out of annotation state, and estimates collateral for\n * confirm dialogs. It owns no mark state of its own.\n *\n * Trust boundary: on a layered document, applying rewrites the LAYER's bytes;\n * the immutable base keeps the original. See the package README.\n */\nexport const redactionPlugin = () =>\n definePlugin<RedactionState, RedactionAction, RedactionCapability>({\n id: 'redaction',\n token: RedactionToken,\n scope: 'document',\n requires: [AnnotationToken],\n optional: [InteractionToken, SelectionToken],\n initialState: initialRedactionState,\n reduce: redactionReducer,\n capability: createRedactionCapability,\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/capability.ts","../src/reducer.ts","../src/redaction.plugin.ts"],"sourcesContent":["import type { DocCapability, PluginContext } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { SelectionToken } from '@embedpdf/plugin-selection/contract';\nimport type {\n AnnotationDTO,\n AnnotationRef,\n PdfRect,\n RedactionApplyResult,\n} from '@embedpdf/engine-core';\nimport type {\n RedactionAction,\n RedactionCapability,\n RedactionLabelPatch,\n RedactionPendingItem,\n RedactionState,\n} from './types';\n\n/** Applying destroys content — its own granted power, narrower than annotate.\n * The engine's apply asserts all three (LocalDocumentRedactionService; the\n * cloud route guards match): the redact grant itself, the page-content\n * rewrite, and the annotation consumption. `canApply` mirrors the full set. */\nconst APPLY_CAPABILITIES: readonly DocCapability[] = [\n 'doc.redact',\n 'doc.pages.modify',\n 'doc.annotate.modify',\n];\n\n/** The annotation plugin's model-id convention for a durable ref. */\nconst idOf = (ref: AnnotationRef): string =>\n ref.kind === 'objectNumber'\n ? `obj:${ref.annotObjectNumber}`\n : ref.kind === 'nm'\n ? `nm:${ref.nm}`\n : `idx:${ref.pageObjectNumber}:${ref.index}`;\n\ntype RedactDTO = Extract<AnnotationDTO, { subtype: 'redact' }>;\n\n/** The PDF-space regions a redact mark targets: its quads' boxes, else `/Rect`. */\nfunction regionsOf(dto: RedactDTO): PdfRect[] {\n if (dto.quadPoints.length === 0) return [dto.rect];\n return dto.quadPoints.map((q) => {\n const xs = [q.p1.x, q.p2.x, q.p3.x, q.p4.x];\n const ys = [q.p1.y, q.p2.y, q.p3.y, q.p4.y];\n return {\n left: Math.min(...xs),\n right: Math.max(...xs),\n bottom: Math.min(...ys),\n top: Math.max(...ys),\n };\n });\n}\n\n/** Positive-area intersection — the engine's collateral rule. */\nconst intersects = (a: PdfRect, b: PdfRect): boolean =>\n Math.min(a.right, b.right) > Math.max(a.left, b.left) &&\n Math.min(a.top, b.top) > Math.max(a.bottom, b.bottom);\n\nexport function createRedactionCapability(\n ctx: PluginContext<RedactionState, RedactionAction>,\n): RedactionCapability {\n const anno = ctx.get(AnnotationToken);\n const appliedCallbacks = new Set<(result: RedactionApplyResult) => void>();\n\n const requireDoc = () => {\n const doc = ctx.doc;\n if (!doc) throw new Error('[redaction] no document bound');\n return doc;\n };\n const requireService = () => {\n const doc = requireDoc();\n if (!doc.redaction) throw new Error('[redaction] engine has no redaction service');\n return doc.redaction;\n };\n\n const pons = (): number[] => (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber);\n\n const pageIndexOf = (pon: number): number =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon)?.index ?? -1;\n\n const pendingOn = (pon: number): RedactDTO[] =>\n anno.list(pon).filter((a): a is RedactDTO => a.subtype === 'redact');\n\n const collectPending = (): RedactionPendingItem[] => {\n const items: RedactionPendingItem[] = [];\n for (const pon of pons()) {\n for (const dto of pendingOn(pon)) {\n items.push({\n id: idOf(dto.ref),\n ref: dto.ref,\n pageObjectNumber: pon,\n pageIndex: pageIndexOf(pon),\n kind: dto.quadPoints.length > 0 ? 'text' : 'area',\n overlayText: dto.overlayText,\n });\n }\n }\n return items;\n };\n\n const fireApplied = (result: RedactionApplyResult) => {\n for (const cb of appliedCallbacks) {\n try {\n cb(result);\n } catch (err) {\n console.error('[redaction] onApplied callback failed', err);\n }\n }\n };\n\n const runApply = async (\n scope: Parameters<ReturnType<typeof requireService>['apply']>[0],\n ): Promise<RedactionApplyResult> => {\n const service = requireService();\n ctx.dispatch({ type: 'APPLY_STARTED' });\n try {\n const result = await service.apply(scope);\n ctx.dispatch({ type: 'APPLY_FINISHED', result });\n fireApplied(result);\n return result;\n } catch (err) {\n ctx.dispatch({ type: 'APPLY_FINISHED', result: null });\n throw err;\n }\n };\n\n // A REMOTE collaborator's apply arrives only as a document event (own\n // applies flow through runApply above). Annotation state + raster\n // invalidation are handled by their own planes; here we only surface it.\n const doc = ctx.doc;\n if (doc) {\n ctx.cleanup(\n doc.events.subscribe((event) => {\n if (event.type !== 'redaction.applied') return;\n if (event.origin.kind !== 'remote') return;\n ctx.dispatch({ type: 'APPLY_FINISHED', result: event });\n fireApplied(event);\n }),\n );\n }\n\n return {\n // Marks are annotations — marking authority IS annotation create\n // authority (the annotation plugin's own twin, not a re-derivation).\n canMark: () => anno.canCreate(),\n // Mirror EVERYTHING the engine's apply asserts, not just the headline\n // capability — a token granted `doc.redact` à la carte without its bit-4\n // siblings must not see an armed Apply button.\n canApply: () => {\n const doc = ctx.doc;\n if (!doc || doc.redaction === undefined) return false;\n return APPLY_CAPABILITIES.every((cap) => doc.security.allows(cap));\n },\n isApplying: () => ctx.getState().applying,\n lastResult: () => ctx.getState().lastResult,\n\n enableRedact: () => ctx.tryGet(InteractionToken)?.activateTool('redact'),\n toggleRedact: () => {\n const interaction = ctx.tryGet(InteractionToken);\n if (!interaction) return;\n interaction.activateTool(interaction.activeToolId() === 'redact' ? 'pointer' : 'redact');\n },\n isRedactActive: () => ctx.tryGet(InteractionToken)?.activeToolId() === 'redact',\n\n queueCurrentSelection: async () => {\n // Marks are optimistic annotation creates — the same gate the pointer\n // path has (the annotation plugin refuses ungated creates too).\n if (!anno.canCreate()) return false;\n const selection = ctx.tryGet(SelectionToken);\n if (!selection || !selection.hasSelection()) return false;\n const snapshot = selection.snapshot();\n for (const page of snapshot.pages) {\n if (page.segments.length === 0) continue;\n // Preserve the selected text frame all the way into `/QuadPoints`.\n // Native apply now uses these exact cells for text removal and the\n // final overlay, so marked == previewed == applied for oriented text.\n anno.createMarkup(\n 'redact',\n page.pon,\n page.segments.map((segment) => segment.quad),\n 'redact',\n );\n }\n selection.clear();\n return true;\n },\n\n preparePending: async () => {\n await Promise.all(pons().map((pon) => anno.ensurePage(pon)));\n },\n getPending: collectPending,\n pendingCount: () => collectPending().length,\n\n estimateCollateral: (ids) => {\n const wanted = ids ? new Set(ids) : null;\n let count = 0;\n for (const pon of pons()) {\n const all = anno.list(pon);\n const marks = all.filter(\n (a): a is RedactDTO => a.subtype === 'redact' && (!wanted || wanted.has(idOf(a.ref))),\n );\n if (marks.length === 0) continue;\n const regions = marks.flatMap(regionsOf);\n for (const other of all) {\n if (other.subtype === 'redact') continue;\n if (regions.some((r) => intersects(r, other.rect))) count++;\n }\n }\n return count;\n },\n\n setLabel: async (ref, patch: RedactionLabelPatch) => {\n const current = anno.get(ref);\n if (!current || current.subtype !== 'redact') {\n throw new Error('[redaction] setLabel target is not a redact annotation');\n }\n // Always carry the current /DA styling: the engine rewrites /DA whenever\n // a label field rides a patch, so a text-only edit must not let the\n // styling fall back to defaults.\n await anno.update(ref, {\n subtype: 'redact',\n ...(patch.overlayText !== undefined ? { overlayText: patch.overlayText } : {}),\n ...(patch.repeat !== undefined ? { repeat: patch.repeat } : {}),\n fontFamily: current.fontFamily,\n fontSize: current.fontSize,\n fontColor: current.fontColor,\n textAlign: current.textAlign,\n });\n },\n\n apply: async (ids) => {\n const wanted = new Set(ids);\n const refs = collectPending()\n .filter((p) => wanted.has(p.id))\n .map((p) => p.ref);\n if (refs.length === 0) throw new Error('[redaction] no matching pending redactions');\n return runApply({ kind: 'annotations', refs });\n },\n\n applyAll: async () => {\n // Pages scope: authoritative for the WHOLE document, including marks on\n // pages this client never loaded. Pages without marks report 'unchanged'.\n const pageObjectNumbers = pons();\n if (pageObjectNumbers.length === 0) throw new Error('[redaction] document has no pages');\n return runApply({ kind: 'pages', pageObjectNumbers });\n },\n\n onApplied: (cb) => {\n appliedCallbacks.add(cb);\n return () => appliedCallbacks.delete(cb);\n },\n };\n}\n","import type { RedactionAction, RedactionState } from './types';\n\nexport const initialRedactionState = (): RedactionState => ({\n applying: false,\n lastResult: null,\n});\n\nexport const redactionReducer = (\n state: RedactionState,\n action: RedactionAction,\n): RedactionState => {\n switch (action.type) {\n case 'APPLY_STARTED':\n return { ...state, applying: true };\n case 'APPLY_FINISHED':\n return { applying: false, lastResult: action.result ?? state.lastResult };\n default:\n return state;\n }\n};\n","import { definePlugin } from '@embedpdf/core';\nimport { AnnotationToken } from '@embedpdf/plugin-annotation/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { SelectionToken } from '@embedpdf/plugin-selection/contract';\nimport { createRedactionCapability } from './capability';\nimport { initialRedactionState, redactionReducer } from './reducer';\nimport { RedactionToken } from './types';\nimport type { RedactionAction, RedactionCapability, RedactionState } from './types';\n\n/**\n * Document-scoped redaction plugin — the DESTRUCTIVE half of the two-stage\n * model. Marking is annotation-plane work (the composed `redact` tool ships\n * with plugin-annotation); this plugin wraps `doc.redaction.apply`, projects\n * the pending queue out of annotation state, and estimates collateral for\n * confirm dialogs. It owns no mark state of its own.\n *\n * Trust boundary: on a layered document, applying rewrites the LAYER's bytes;\n * the immutable base keeps the original. See the package README.\n */\nexport const redactionPlugin = () =>\n definePlugin<RedactionState, RedactionAction, RedactionCapability>({\n id: 'redaction',\n token: RedactionToken,\n scope: 'document',\n requires: [AnnotationToken],\n optional: [InteractionToken, SelectionToken],\n initialState: initialRedactionState,\n reduce: redactionReducer,\n capability: createRedactionCapability,\n });\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@embedpdf/plugin-redaction",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Document-scoped redaction: the destructive apply stage over redact annotation marks — pending queue view, collateral estimation, and the doc.redaction.apply wrapper. Marking itself rides the annotation plane.",
|
|
6
6
|
"type": "module",
|
|
@@ -12,17 +12,21 @@
|
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
14
|
},
|
|
15
|
+
"./contract": {
|
|
16
|
+
"import": "./dist/contract.js",
|
|
17
|
+
"require": "./dist/contract.cjs"
|
|
18
|
+
},
|
|
15
19
|
"./package.json": "./package.json"
|
|
16
20
|
},
|
|
17
21
|
"files": [
|
|
18
22
|
"dist"
|
|
19
23
|
],
|
|
20
24
|
"dependencies": {
|
|
21
|
-
"@embedpdf/core": "3.0.0-next.
|
|
22
|
-
"@embedpdf/plugin-
|
|
23
|
-
"@embedpdf/plugin-
|
|
24
|
-
"@embedpdf/plugin-
|
|
25
|
-
"@embedpdf/engine-core": "3.0.0-next.
|
|
25
|
+
"@embedpdf/core": "3.0.0-next.11",
|
|
26
|
+
"@embedpdf/plugin-annotation": "3.0.0-next.11",
|
|
27
|
+
"@embedpdf/plugin-interaction": "3.0.0-next.11",
|
|
28
|
+
"@embedpdf/plugin-selection": "3.0.0-next.11",
|
|
29
|
+
"@embedpdf/engine-core": "3.0.0-next.11"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"typescript": "^5.0.0",
|