generated from coulomb/repo-seed
Fix state persistence when popup is closed
- Add 'dismissed' status to prevent dismissed PDFs from reappearing - Persist PDFs discovered from current tab and recent downloads via background - Add dismissPDF function that marks PDFs as dismissed instead of removing - Dismissed PDFs are kept for 7 days for duplicate detection, then cleaned up - Completed items (sent/canceled) can still be fully removed - Add addPDF message handler to service worker for popup-discovered PDFs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
getAllPDFs,
|
getAllPDFs,
|
||||||
getPendingPDFs,
|
getPendingPDFs,
|
||||||
updatePDFStatus,
|
updatePDFStatus,
|
||||||
|
dismissPDF,
|
||||||
removePDF,
|
removePDF,
|
||||||
cleanupOldEntries,
|
cleanupOldEntries,
|
||||||
PDFStatus,
|
PDFStatus,
|
||||||
@@ -129,6 +130,19 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a PDF to the queue (from popup discovery)
|
||||||
|
if (request.action === 'addPDF') {
|
||||||
|
addPDF(request.pdf).then(entry => {
|
||||||
|
if (entry) {
|
||||||
|
console.log('[Service Worker] PDF added via message:', entry.filename);
|
||||||
|
}
|
||||||
|
return updateBadge().then(() => entry);
|
||||||
|
}).then(entry => {
|
||||||
|
sendResponse({ entry });
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Legacy: Get only actionable PDFs
|
// Legacy: Get only actionable PDFs
|
||||||
if (request.action === 'getPDFQueue') {
|
if (request.action === 'getPDFQueue') {
|
||||||
getPendingPDFs().then(entries => {
|
getPendingPDFs().then(entries => {
|
||||||
@@ -148,6 +162,15 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.action === 'dismissPDF') {
|
||||||
|
dismissPDF(request.id).then(() => {
|
||||||
|
return updateBadge();
|
||||||
|
}).then(() => {
|
||||||
|
sendResponse({ success: true });
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.action === 'removePDF') {
|
if (request.action === 'removePDF') {
|
||||||
removePDF(request.id).then(() => {
|
removePDF(request.id).then(() => {
|
||||||
return updateBadge();
|
return updateBadge();
|
||||||
|
|||||||
@@ -153,22 +153,20 @@ async function loadPDFQueue() {
|
|||||||
console.log('[Popup] Loading PDF queue...');
|
console.log('[Popup] Loading PDF queue...');
|
||||||
|
|
||||||
// Get all PDFs from background script (including completed ones)
|
// Get all PDFs from background script (including completed ones)
|
||||||
const response = await chrome.runtime.sendMessage({ action: 'getAllPDFs' });
|
let response = await chrome.runtime.sendMessage({ action: 'getAllPDFs' });
|
||||||
pdfQueue = response?.entries || [];
|
pdfQueue = response?.entries || [];
|
||||||
console.log('[Popup] Got', pdfQueue.length, 'entries from background');
|
console.log('[Popup] Got', pdfQueue.length, 'entries from background');
|
||||||
|
|
||||||
// Also check current tab for PDF
|
// Check current tab for PDF and add to persistent queue via background
|
||||||
const currentTabPDF = await checkCurrentTabForPDF();
|
const currentTabPDF = await checkCurrentTabForPDF();
|
||||||
if (currentTabPDF) {
|
if (currentTabPDF) {
|
||||||
// Check if already in queue
|
// Add via background service (will check for duplicates/dismissed)
|
||||||
const exists = pdfQueue.some(p => p.url === currentTabPDF.url);
|
const addResult = await chrome.runtime.sendMessage({
|
||||||
if (!exists) {
|
action: 'addPDF',
|
||||||
console.log('[Popup] Adding current tab PDF to queue:', currentTabPDF.filename);
|
pdf: currentTabPDF
|
||||||
const entry: PDFQueueEntry = {
|
});
|
||||||
...currentTabPDF,
|
if (addResult?.entry) {
|
||||||
status: 'pending'
|
console.log('[Popup] Added current tab PDF to persistent queue:', currentTabPDF.filename);
|
||||||
};
|
|
||||||
pdfQueue.unshift(entry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,16 +175,19 @@ async function loadPDFQueue() {
|
|||||||
console.log('[Popup] Queue empty, checking recent downloads...');
|
console.log('[Popup] Queue empty, checking recent downloads...');
|
||||||
const recentPDFs = await checkRecentDownloads();
|
const recentPDFs = await checkRecentDownloads();
|
||||||
for (const pdf of recentPDFs) {
|
for (const pdf of recentPDFs) {
|
||||||
const exists = pdfQueue.some(p => p.url === pdf.url);
|
// Add each PDF via background service (will check for duplicates/dismissed)
|
||||||
if (!exists) {
|
await chrome.runtime.sendMessage({
|
||||||
pdfQueue.push({
|
action: 'addPDF',
|
||||||
...pdf,
|
pdf
|
||||||
status: 'pending'
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reload queue after potential additions
|
||||||
|
response = await chrome.runtime.sendMessage({ action: 'getAllPDFs' });
|
||||||
|
pdfQueue = response?.entries || [];
|
||||||
|
console.log('[Popup] Final queue count:', pdfQueue.length);
|
||||||
|
|
||||||
// Render the list
|
// Render the list
|
||||||
renderPDFList();
|
renderPDFList();
|
||||||
}
|
}
|
||||||
@@ -757,7 +758,17 @@ async function handleRefreshStatus(id: string) {
|
|||||||
* Handle dismiss PDF
|
* Handle dismiss PDF
|
||||||
*/
|
*/
|
||||||
async function handleDismissPDF(id: string) {
|
async function handleDismissPDF(id: string) {
|
||||||
await chrome.runtime.sendMessage({ action: 'removePDF', id });
|
const pdf = pdfQueue.find(p => p.id === id);
|
||||||
|
if (!pdf) return;
|
||||||
|
|
||||||
|
// For pending/failed/in_basket items, mark as dismissed to prevent re-showing
|
||||||
|
// For completed items (sent/canceled), actually remove from storage
|
||||||
|
if (pdf.status === 'sent' || pdf.status === 'canceled') {
|
||||||
|
await chrome.runtime.sendMessage({ action: 'removePDF', id });
|
||||||
|
} else {
|
||||||
|
await chrome.runtime.sendMessage({ action: 'dismissPDF', id });
|
||||||
|
}
|
||||||
|
|
||||||
pdfQueue = pdfQueue.filter(p => p.id !== id);
|
pdfQueue = pdfQueue.filter(p => p.id !== id);
|
||||||
renderPDFList();
|
renderPDFList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const STORAGE_KEY = 'pdfQueue';
|
|||||||
const MAX_ENTRIES = 50;
|
const MAX_ENTRIES = 50;
|
||||||
const SENT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
const SENT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
const FAILED_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
const FAILED_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||||
|
const DISMISSED_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
|
|
||||||
export type PDFStatus =
|
export type PDFStatus =
|
||||||
| 'pending' // Not yet uploaded
|
| 'pending' // Not yet uploaded
|
||||||
@@ -20,7 +21,8 @@ export type PDFStatus =
|
|||||||
| 'ordering' // Order in progress
|
| 'ordering' // Order in progress
|
||||||
| 'in_production' // PRODUCTION_QUEUE or PRINTING
|
| 'in_production' // PRODUCTION_QUEUE or PRINTING
|
||||||
| 'sent' // SENT - terminal
|
| 'sent' // SENT - terminal
|
||||||
| 'canceled'; // CANCELED - terminal
|
| 'canceled' // CANCELED - terminal
|
||||||
|
| 'dismissed'; // User dismissed, don't show again
|
||||||
|
|
||||||
export interface PDFQueueEntry extends DetectedPDF {
|
export interface PDFQueueEntry extends DetectedPDF {
|
||||||
status: PDFStatus;
|
status: PDFStatus;
|
||||||
@@ -70,10 +72,10 @@ export async function addPDF(pdf: DetectedPDF): Promise<PDFQueueEntry | null> {
|
|||||||
// Check for duplicate by URL
|
// Check for duplicate by URL
|
||||||
const existing = state.entries.find(e => e.url === pdf.url);
|
const existing = state.entries.find(e => e.url === pdf.url);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
// Skip if already uploaded (in basket, production, or completed)
|
// Skip if already processed (uploaded, dismissed, etc.)
|
||||||
const uploadedStatuses: PDFStatus[] = ['in_basket', 'ordering', 'in_production', 'sent', 'canceled'];
|
const processedStatuses: PDFStatus[] = ['in_basket', 'ordering', 'in_production', 'sent', 'canceled', 'dismissed'];
|
||||||
if (uploadedStatuses.includes(existing.status)) {
|
if (processedStatuses.includes(existing.status)) {
|
||||||
console.log('[PDF Queue] PDF already uploaded, skipping:', pdf.filename);
|
console.log('[PDF Queue] PDF already processed, skipping:', pdf.filename, existing.status);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
console.log('[PDF Queue] PDF already in queue:', pdf.filename);
|
console.log('[PDF Queue] PDF already in queue:', pdf.filename);
|
||||||
@@ -160,7 +162,24 @@ export async function updatePDFStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a PDF from the queue
|
* Dismiss a PDF (mark as dismissed so it won't reappear)
|
||||||
|
*/
|
||||||
|
export async function dismissPDF(id: string): Promise<void> {
|
||||||
|
const state = await loadQueue();
|
||||||
|
const entry = state.entries.find(e => e.id === id);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
console.warn('[PDF Queue] PDF not found for dismissal:', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.status = 'dismissed';
|
||||||
|
await saveQueue(state);
|
||||||
|
console.log('[PDF Queue] Dismissed PDF:', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a PDF from the queue (complete removal, used for cleanup)
|
||||||
*/
|
*/
|
||||||
export async function removePDF(id: string): Promise<void> {
|
export async function removePDF(id: string): Promise<void> {
|
||||||
const state = await loadQueue();
|
const state = await loadQueue();
|
||||||
@@ -177,11 +196,12 @@ export async function removePDF(id: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all PDFs for display in popup (all non-terminal statuses + recent terminal)
|
* Get all PDFs for display in popup (excludes dismissed)
|
||||||
*/
|
*/
|
||||||
export async function getAllPDFs(): Promise<PDFQueueEntry[]> {
|
export async function getAllPDFs(): Promise<PDFQueueEntry[]> {
|
||||||
const state = await loadQueue();
|
const state = await loadQueue();
|
||||||
return state.entries;
|
// Filter out dismissed entries - they're kept for duplicate detection but not displayed
|
||||||
|
return state.entries.filter(e => e.status !== 'dismissed');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,6 +274,14 @@ export async function cleanupOldEntries(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove dismissed entries older than 7 days
|
||||||
|
if (entry.status === 'dismissed') {
|
||||||
|
const age = now - entry.timestamp;
|
||||||
|
if (age > DISMISSED_MAX_AGE_MS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user