Skip to content

Commit 501a719

Browse files
authored
Add bulk comment and annotation clearing to editor (Stirling-Tools#6792)
# Description of Changes Closes Stirling-Tools#6695 This PR adds bulk cleanup actions for comments and annotations in the PDF editor, while tightening the save and navigation behavior around annotation edits. ### Comments sidebar Adds a “Clear all comments” action to the comments sidebar overflow menu. The action opens a confirmation modal before clearing sidebar comments and replies. The implementation distinguishes between standalone comment annotations and comments attached to existing visual annotations. Standalone comments and replies are removed from the document, while comments attached to markup, shapes, ink, or other visual annotations are cleared from the sidebar without deleting the underlying annotation itself. This preserves the visible document markup while removing the comment metadata and persisted comment contents. The comments sidebar state is also reset after clearing, including draft comments, reply drafts, edit state, and open confirmation/delete modal state. ### Annotate tool Adds a document-level “Clear all annotations” action to the Annotate tool. The action is exposed through the annotation panel’s overflow menu and uses a confirmation modal before removing annotations. The clear operation is routed through the existing annotation API bridge and delegates to EmbedPDF’s document-level annotation clearing API. The UI handles unavailable annotation state, successful clears, and failures. After annotations are cleared, the editor resets annotation interaction state, exits placement/selection-specific state, returns to select mode, and marks the document as having unsaved changes only when annotations were actually removed. The user can then persist the removal through the normal Save Changes flow. ### Save and navigation hardening Improves the viewer save/apply flow used by annotations and manual redactions. Save operations are now deduplicated while an apply operation is already in flight, preventing duplicate exports or duplicate file consumption when users trigger save/navigation repeatedly. The global unsaved-changes navigation modal now waits for “Apply & Leave” to complete successfully before navigating. If saving fails, the modal keeps the user in place instead of leaving with unsaved edits still present. The Annotate panel also prevents “Save Changes” and “Clear all annotations” from running concurrently. <!-- Please provide a summary of the changes, including: - What was changed: - Why the change was made - Any challenges encountered --> --- ## Checklist ### General - [X] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [X] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [X] I have performed a self-review of my own code - [X] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [X] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [X] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) Clear all comments : <img width="310" height="397" alt="image" src="https://github.com/user-attachments/assets/d1682611-13f8-4f40-aa77-44b37450e56e" /> Clear all annotations: <img width="284" height="549" alt="image" src="https://github.com/user-attachments/assets/e4049bc1-f07b-4b36-b08e-ad6d6b86fe62" /> ### Testing (if applicable) - [X] I have run `task check` to verify linters, typechecks, and tests pass - [X] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details.
1 parent bc6f1a1 commit 501a719

10 files changed

Lines changed: 566 additions & 61 deletions

File tree

frontend/editor/public/locales/en-GB/translation.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,17 @@ changeColor = "Change Colour"
16131613
chooseColor = "Choose colour"
16141614
circle = "Circle"
16151615
clearBackground = "Remove background"
1616+
clearDocumentAnnotations = "Clear all annotations"
1617+
clearDocumentAnnotationsConfirm = "Clear all"
1618+
clearDocumentAnnotationsDescription = "This removes all annotations currently loaded in the editor. Please save changes to persist this in the PDF."
1619+
clearDocumentAnnotationsEmptyBody = "There are no annotations currently loaded in the editor."
1620+
clearDocumentAnnotationsEmptyTitle = "No annotations to clear"
1621+
clearDocumentAnnotationsErrorTitle = "Could not clear annotations"
1622+
clearDocumentAnnotationsSuccessBody = "Please save changes to persist the annotation removal in the PDF."
1623+
clearDocumentAnnotationsSuccessTitle = "All annotations cleared"
1624+
clearDocumentAnnotationsTitle = "Clear all annotations?"
1625+
clearDocumentAnnotationsUnavailableBody = "The annotation layer is still loading. Try again in a moment."
1626+
clearDocumentAnnotationsUnavailableTitle = "Annotations are not ready"
16161627
color = "Colour"
16171628
comment = "Comment"
16181629
comments = "Comments"
@@ -1630,6 +1641,7 @@ imagePreview = "Preview"
16301641
insertText = "Insert Text"
16311642
line = "Line"
16321643
lineArrow = "Arrow"
1644+
moreActions = "More actions"
16331645
noBackground = "No background"
16341646
note = "Note"
16351647
notesStamps = "Notes & Stamps"
@@ -7981,6 +7993,8 @@ onlyPdfSupported = "This file format is not supported for preview."
79817993
pageNavigation = "Page navigation"
79827994
previousPage = "Previous Page"
79837995
resetZoom = "Reset zoom"
7996+
saveChangesErrorBody = "The document could not be saved. Try again."
7997+
saveChangesErrorTitle = "Could not save changes"
79847998
singlePageView = "Single Page View"
79857999
unknownFile = "Unknown file"
79868000
view = "View"

frontend/editor/public/locales/en-US/translation.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,17 @@ changeColor = "Change Color"
16371637
chooseColor = "Choose color"
16381638
circle = "Circle"
16391639
clearBackground = "Remove background"
1640+
clearDocumentAnnotations = "Clear all annotations"
1641+
clearDocumentAnnotationsConfirm = "Clear all"
1642+
clearDocumentAnnotationsDescription = "This removes all annotations currently loaded in the editor. Please save changes to persist this in the PDF."
1643+
clearDocumentAnnotationsEmptyBody = "There are no annotations currently loaded in the editor."
1644+
clearDocumentAnnotationsEmptyTitle = "No annotations to clear"
1645+
clearDocumentAnnotationsErrorTitle = "Could not clear annotations"
1646+
clearDocumentAnnotationsSuccessBody = "Please save changes to persist the annotation removal in the PDF."
1647+
clearDocumentAnnotationsSuccessTitle = "All annotations cleared"
1648+
clearDocumentAnnotationsTitle = "Clear all annotations?"
1649+
clearDocumentAnnotationsUnavailableBody = "The annotation layer is still loading. Try again in a moment."
1650+
clearDocumentAnnotationsUnavailableTitle = "Annotations are not ready"
16401651
color = "Color"
16411652
comment = "Comment"
16421653
comments = "Comments"
@@ -1654,6 +1665,7 @@ imagePreview = "Preview"
16541665
insertText = "Insert Text"
16551666
line = "Line"
16561667
lineArrow = "Arrow"
1668+
moreActions = "More actions"
16571669
noBackground = "No background"
16581670
note = "Note"
16591671
notesStamps = "Notes & Stamps"
@@ -8005,6 +8017,8 @@ onlyPdfSupported = "This file format is not supported for preview."
80058017
pageNavigation = "Page navigation"
80068018
previousPage = "Previous Page"
80078019
resetZoom = "Reset zoom"
8020+
saveChangesErrorBody = "The document could not be saved. Try again."
8021+
saveChangesErrorTitle = "Could not save changes"
80088022
singlePageView = "Single Page View"
80098023
unknownFile = "Unknown file"
80108024
view = "View"
@@ -8026,6 +8040,10 @@ addComment = "Add comment"
80268040
addCommentPlaceholder = "Add comment..."
80278041
addLink = "Add link"
80288042
addReplyPlaceholder = "Add reply..."
8043+
cancelClearAll = "Cancel"
8044+
clearAll = "Clear all comments"
8045+
clearAllDescription = "This removes comments and replies from the sidebar while keeping any attached annotations in the document."
8046+
clearAllTitle = "Clear all comments?"
80298047
deleteAnnotationAndComment = "Delete annotation & comment"
80308048
deleteDescription = "This annotation has a comment attached. You can remove just the comment from the sidebar while keeping the annotation, or delete everything."
80318049
deleteTitle = "Remove annotation from comments?"

frontend/editor/src/core/components/shared/NavigationWarningModal.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@ const NavigationWarningModal = () => {
4848

4949
const handleApplyAndContinue = async () => {
5050
const handlers = navigationWarningHandlersRef.current;
51-
if (handlers?.onApplyAndContinue) {
52-
await handlers.onApplyAndContinue();
51+
try {
52+
if (handlers?.onApplyAndContinue) {
53+
await handlers.onApplyAndContinue();
54+
}
55+
finishAndNavigate();
56+
} catch (error) {
57+
console.error("Failed to apply changes before navigating:", error);
5358
}
54-
finishAndNavigate();
5559
};
5660

5761
const handleExportAndContinue = async () => {

frontend/editor/src/core/components/tools/redact/ManualRedactionControls.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ export default function ManualRedactionControls({
112112
isSavingRef.current = true;
113113
try {
114114
await applyChanges();
115+
} catch {
116+
// The viewer-level save handler reports the failure to the user.
115117
} finally {
116118
isSavingRef.current = false;
117119
}

frontend/editor/src/core/components/viewer/AnnotationAPIBridge.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useImperativeHandle, forwardRef, useCallback } from "react";
2+
import type { TrackedAnnotation } from "@embedpdf/plugin-annotation";
23
import { useAnnotationCapability } from "@embedpdf/plugin-annotation/react";
34
import { PdfAnnotationSubtype, PdfAnnotationIcon } from "@embedpdf/models";
45
import type {
@@ -9,6 +10,7 @@ import type {
910
AnnotationPatch,
1011
AnnotationRect,
1112
AnnotationSelection,
13+
ClearDocumentAnnotationsResult,
1214
} from "@app/components/viewer/viewerTypes";
1315
import { useDocumentReady } from "@app/components/viewer/hooks/useDocumentReady";
1416

@@ -98,6 +100,8 @@ type AnnotationDefaults =
98100
}
99101
| null;
100102

103+
const EMBEDPDF_CLEAR_DOCUMENT_ANNOTATIONS = "deleteAllAnnotations";
104+
101105
type AnnotationApiSurface = {
102106
setActiveTool: (toolId: AnnotationToolId | null) => void;
103107
getActiveTool?: () => { id: AnnotationToolId } | null;
@@ -116,6 +120,8 @@ type AnnotationApiSurface = {
116120
deleteAnnotations?: (
117121
annotations: Array<{ pageIndex: number; id: string }>,
118122
) => void;
123+
getAnnotations?: () => unknown[];
124+
[EMBEDPDF_CLEAR_DOCUMENT_ANNOTATIONS]?: () => void | Promise<void>;
119125
createAnnotation?: (
120126
pageIndex: number,
121127
annotation: Record<string, unknown>,
@@ -494,6 +500,30 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(
494500
api?.deleteAnnotations?.(annotations);
495501
},
496502

503+
clearDocumentAnnotations:
504+
async (): Promise<ClearDocumentAnnotationsResult> => {
505+
const api = annotationApi;
506+
const clearAnnotations = api?.[EMBEDPDF_CLEAR_DOCUMENT_ANNOTATIONS];
507+
508+
if (!api || !clearAnnotations || !api.getAnnotations) {
509+
return { available: false, cleared: false };
510+
}
511+
512+
let annotations: TrackedAnnotation[];
513+
try {
514+
annotations = api.getAnnotations();
515+
} catch {
516+
return { available: false, cleared: false };
517+
}
518+
519+
if (annotations.length === 0) {
520+
return { available: true, cleared: false };
521+
}
522+
523+
await clearAnnotations.call(api);
524+
return { available: true, cleared: true };
525+
},
526+
497527
createAnnotation: (
498528
pageIndex: number,
499529
annotation: Record<string, unknown>,

0 commit comments

Comments
 (0)