Skip to content

Commit 5d7cd4d

Browse files
committed
pdf-server: fix form Reset button; split panel into Reset vs Clear all
Reset-button regression: buildFieldNameMap() now seeds formFieldValues from the PDF's stored values, but syncFormValuesToStorage() was pushing them back into annotationStorage in our normalised repr (checkbox→true, radio→export string). pdf.js's native repr may differ, and overwriting it breaks the form's Reset button. Fix: skip syncing values that match baseline — the PDF's own values are already in storage natively. Panel buttons: - Reset: revert to what's in the PDF file. Restores baseline annotations and form values, clears undo/redo. Empty diff → clean. Disabled when not dirty. - Clear all: removes EVERYTHING including PDF-native items. Non-empty diff (baseline items are 'removed') → dirty; saving writes stripped PDF. Disabled when nothing to clear.
1 parent e789132 commit 5d7cd4d

3 files changed

Lines changed: 83 additions & 11 deletions

File tree

examples/pdf-server/mcp-app.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@
162162
<div class="annotation-panel-header">
163163
<span class="annotation-panel-title">Annotations (<span id="annotation-panel-count">0</span>)</span>
164164
<div class="annotation-panel-header-actions">
165-
<button id="annotation-panel-clear-all" class="annotation-panel-clear-all" title="Clear all">Clear all</button>
165+
<button id="annotation-panel-reset" class="annotation-panel-reset" title="Revert to what's in the PDF file">Reset</button>
166+
<button id="annotation-panel-clear-all" class="annotation-panel-clear-all" title="Remove everything, including items from the PDF file">Clear all</button>
166167
<button id="annotation-panel-close" class="annotation-panel-close" title="Close panel">&#x2715;</button>
167168
</div>
168169
</div>

examples/pdf-server/src/mcp-app.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,7 @@ body {
897897
gap: 4px;
898898
}
899899

900+
.annotation-panel-reset,
900901
.annotation-panel-clear-all {
901902
border: none;
902903
background: transparent;
@@ -907,10 +908,19 @@ body {
907908
padding: 2px 6px;
908909
white-space: nowrap;
909910
}
911+
.annotation-panel-reset:hover {
912+
color: var(--text000);
913+
background: var(--bg100);
914+
}
910915
.annotation-panel-clear-all:hover {
911916
color: #e74c3c;
912917
background: var(--bg100);
913918
}
919+
.annotation-panel-reset:disabled,
920+
.annotation-panel-clear-all:disabled {
921+
opacity: 0.4;
922+
cursor: not-allowed;
923+
}
914924

915925
.annotation-panel-close {
916926
display: flex;

examples/pdf-server/src/mcp-app.ts

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ const annotationsPanelCountEl = document.getElementById(
190190
const annotationsPanelCloseBtn = document.getElementById(
191191
"annotation-panel-close",
192192
) as HTMLButtonElement;
193+
const annotationsPanelResetBtn = document.getElementById(
194+
"annotation-panel-reset",
195+
) as HTMLButtonElement;
193196
const annotationsPanelClearAllBtn = document.getElementById(
194197
"annotation-panel-clear-all",
195198
) as HTMLButtonElement;
@@ -2311,6 +2314,8 @@ function renderAnnotationPanel(): void {
23112314
if (!annotationPanelOpen) return;
23122315

23132316
annotationsPanelCountEl.textContent = String(sidebarItemCount());
2317+
annotationsPanelResetBtn.disabled = !isDirty;
2318+
annotationsPanelClearAllBtn.disabled = sidebarItemCount() === 0;
23142319

23152320
// Group annotations by page, sorted by Y position within each page
23162321
const byPage = new Map<number, TrackedAnnotation[]>();
@@ -2814,31 +2819,82 @@ function initAnnotationPanel(): void {
28142819
// Toggle button
28152820
annotationsBtn.addEventListener("click", toggleAnnotationPanel);
28162821
annotationsPanelCloseBtn.addEventListener("click", toggleAnnotationPanel);
2822+
annotationsPanelResetBtn.addEventListener("click", resetToBaseline);
28172823
annotationsPanelClearAllBtn.addEventListener("click", clearAllItems);
28182824

28192825
updateAnnotationsBadge();
28202826
}
28212827

2822-
function clearAllItems(): void {
2823-
// Clear all annotations
2828+
/** Remove the DOM elements backing every annotation and clear the map. */
2829+
function clearAnnotationMap(): void {
28242830
for (const [, tracked] of annotationMap) {
28252831
for (const el of tracked.elements) el.remove();
28262832
}
28272833
annotationMap.clear();
2834+
}
2835+
2836+
/** Remove all user-sourced entries from annotationStorage, leaving the
2837+
* PDF's own stored values intact. */
2838+
function clearUserFormStorage(): void {
2839+
if (!pdfDocument) return;
2840+
for (const [name] of formFieldValues) {
2841+
if (pdfBaselineFormValues.has(name)) continue; // PDF-native — leave alone
2842+
const ids = fieldNameToIds.get(name);
2843+
if (ids) for (const id of ids) pdfDocument.annotationStorage.remove(id);
2844+
}
2845+
}
2846+
2847+
/**
2848+
* Revert to what's in the PDF file: restore baseline annotations, restore
2849+
* baseline form values, discard all user edits. Result: diff is empty, clean.
2850+
*/
2851+
function resetToBaseline(): void {
2852+
clearAnnotationMap();
2853+
for (const def of pdfBaselineAnnotations) {
2854+
annotationMap.set(def.id, { def: { ...def }, elements: [] });
2855+
}
2856+
2857+
clearUserFormStorage();
2858+
formFieldValues.clear();
2859+
for (const [name, value] of pdfBaselineFormValues) {
2860+
formFieldValues.set(name, value);
2861+
}
2862+
2863+
undoStack.length = 0;
2864+
redoStack.length = 0;
2865+
selectedAnnotationIds.clear();
28282866

2829-
// Clear all form field values
2867+
updateAnnotationsBadge();
2868+
persistAnnotations(); // diff is now empty → setDirty(false)
2869+
renderPage();
2870+
renderAnnotationPanel();
2871+
}
2872+
2873+
/**
2874+
* Remove everything, including annotations and form values that came from
2875+
* the PDF file. Result: diff is non-empty (baseline items are "removed"),
2876+
* dirty — saving writes a stripped PDF.
2877+
*/
2878+
function clearAllItems(): void {
2879+
clearAnnotationMap();
2880+
2881+
// Clear all form field values from storage — user AND baseline.
2882+
// Baseline values revert to the field's defaultValue.
28302883
if (pdfDocument) {
2831-
for (const [name] of formFieldValues) {
2884+
for (const name of new Set([
2885+
...formFieldValues.keys(),
2886+
...pdfBaselineFormValues.keys(),
2887+
])) {
28322888
const ids = fieldNameToIds.get(name);
2833-
if (ids) {
2834-
for (const id of ids) {
2835-
pdfDocument.annotationStorage.remove(id);
2836-
}
2837-
}
2889+
if (ids) for (const id of ids) pdfDocument.annotationStorage.remove(id);
28382890
}
28392891
}
28402892
formFieldValues.clear();
28412893

2894+
undoStack.length = 0;
2895+
redoStack.length = 0;
2896+
selectedAnnotationIds.clear();
2897+
28422898
updateAnnotationsBadge();
28432899
persistAnnotations();
28442900
renderPage();
@@ -3333,11 +3389,16 @@ async function buildFieldNameMap(
33333389
log.info(`Built field name map: ${fieldNameToIds.size} fields`);
33343390
}
33353391

3336-
/** Sync formFieldValues into pdfDocument.annotationStorage so AnnotationLayer renders pre-filled values. */
3392+
/** Sync formFieldValues into pdfDocument.annotationStorage so AnnotationLayer renders pre-filled values.
3393+
* Skips values that match the PDF's baseline — those are already in storage
3394+
* in pdf.js's native format (which may differ from our string/bool repr,
3395+
* e.g. checkbox stores "Yes" not `true`). Overwriting with our normalised
3396+
* form can break the Reset button's ability to restore defaults. */
33373397
function syncFormValuesToStorage(): void {
33383398
if (!pdfDocument || fieldNameToIds.size === 0) return;
33393399
const storage = pdfDocument.annotationStorage;
33403400
for (const [name, value] of formFieldValues) {
3401+
if (pdfBaselineFormValues.get(name) === value) continue;
33413402
const ids = fieldNameToIds.get(name);
33423403
if (ids) {
33433404
for (const id of ids) {

0 commit comments

Comments
 (0)