diff --git a/src/tools/redact-pdf.ts b/src/tools/redact-pdf.ts index ca100f9..3dd909d 100644 --- a/src/tools/redact-pdf.ts +++ b/src/tools/redact-pdf.ts @@ -142,6 +142,7 @@ export async function openRedactor(file: File, viewer: HTMLElement): Promise a.x0 === b.x0 && a.y0 === b.y0 && a.x1 === b.x1 && a.y1 === b.y1; + // What "Find personal info" marked last, so the button shows as on while those marks are there + // and a second tap takes them off again. + let personalFound: Box[][] = []; + const isPersonal = (box: Box, index: number) => personalFound[index]?.some((found) => same(found, box)); + function refresh() { const total = marks.reduce((sum, page) => sum + page.length, 0); count.textContent = t('rd.count', { n: total }); + personal.setAttribute('aria-pressed', String(marks.some((page, i) => page.some((box) => isPersonal(box, i))))); undo.disabled = !history.length; clear.disabled = !total; views.forEach(drawMarks); @@ -293,7 +301,7 @@ export async function openRedactor(file: File, viewer: HTMLElement): Promise a.x0 === b.x0 && a.y0 === b.y0 && a.x1 === b.x1 && a.y1 === b.y1; const fresh = found.map((page, i) => page.filter((box) => !marks[i].some((mark) => same(mark, box)))); if (fresh.some((page) => page.length)) change(marks.map((page, i) => [...page, ...fresh[i]])); - return found.reduce((sum, page) => sum + page.length, 0); + return found; } + const sum = (pages: Box[][]) => pages.reduce((total, page) => total + page.length, 0); find.onsubmit = async (event) => { event.preventDefault(); const query = search.value.trim(); if (!query) return; - const total = await markEverywhere(occurrences(query), markAll); + const total = sum(await markEverywhere(occurrences(query), markAll)); count.textContent = total ? t('rd.found', { n: total }) : t('rd.none', { text: query }); }; personal.onclick = async () => { - const total = await markEverywhere(personalData, personal); - count.textContent = total ? t('rd.personalFound', { n: total }) : t('rd.personalNone'); + if (personal.getAttribute('aria-pressed') === 'true') { + change(marks.map((page, i) => page.filter((box) => !isPersonal(box, i)))); + return; + } + const found = await markEverywhere(personalData, personal); + personalFound = found; + refresh(); + count.textContent = sum(found) ? t('rd.personalFound', { n: sum(found) }) : t('rd.personalNone'); }; undo.onclick = () => { marks = history.pop() ?? marks; diff --git a/tests/app.e2e.ts b/tests/app.e2e.ts index 9b75ed7..8c79134 100644 --- a/tests/app.e2e.ts +++ b/tests/app.e2e.ts @@ -436,12 +436,21 @@ test('finds and marks personal details in one click, and leaves amounts and date expect(onLine, `mark at ${middle}`).toBe(true); } - // A second click marks nothing twice. - await page.getByRole('button', { name: 'Find personal info' }).click(); + // The button stays on while its marks are there; a second click takes them off again. + const personal = page.getByRole('button', { name: 'Find personal info' }); + await expect(personal).toHaveAttribute('aria-pressed', 'true'); + await personal.click(); + await expect(marks).toHaveCount(0); + await expect(personal).toHaveAttribute('aria-pressed', 'false'); + await page.getByRole('button', { name: 'Undo' }).click(); + await expect(marks).toHaveCount(3); + await expect(personal).toHaveAttribute('aria-pressed', 'true'); + await page.getByRole('button', { name: 'Clear all' }).click(); + await expect(personal).toHaveAttribute('aria-pressed', 'false'); + // Found again, nothing is marked twice. + await personal.click(); await expect(page.locator('.redact-count')).toHaveText('Personal details marked: 3. Check each one before saving.'); await expect(marks).toHaveCount(3); - await page.getByRole('button', { name: 'Undo' }).click(); - await expect(marks).toHaveCount(0); }); test('transcribes speech on the device, with a one-time setup, and writes text and subtitles', async ({ page, baseURL }) => {