diff --git a/manifest.firefox.json b/manifest.firefox.json index 31e9417..b20038a 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -12,7 +12,8 @@ "permissions": [ "storage", "activeTab", - "scripting" + "scripting", + "notifications" ], "host_permissions": [ "https://claude.ai/*", diff --git a/manifest.json b/manifest.json index f16e63c..d73335f 100644 --- a/manifest.json +++ b/manifest.json @@ -6,7 +6,8 @@ "permissions": [ "storage", "activeTab", - "scripting" + "scripting", + "notifications" ], "host_permissions": [ "https://claude.ai/*", diff --git a/src/background/service-worker.js b/src/background/service-worker.js index 7f04898..a93cb58 100644 --- a/src/background/service-worker.js +++ b/src/background/service-worker.js @@ -146,6 +146,13 @@ const messageHandlers = { sendResponse({ count: tabCounts.get(tabId) || 0 }); }, + async 'sync:notification-seen'() { + // Options page opened — clear the sync badge and pending notification flag + await api.storage.local.remove('ss_sync_notification'); + api.action.setBadgeText({ text: '' }); + api.action.setBadgeBackgroundColor({ color: '#6b7280' }); + }, + async 'update:settings'(message) { await Storage.saveSettings(message.settings); @@ -309,6 +316,33 @@ api.storage.onChanged.addListener(async (changes, areaName) => { } } + // When a sync operation applied new data, show badge + notification + if (areaName === 'local' && changes.ss_sync_notification?.newValue) { + const notif = changes.ss_sync_notification.newValue; + const sourceLabel = { + 'file': 'sync folder', + 'browser-sync': 'browser account sync', + 'code': 'sync code import', + }[notif.source] || 'sync'; + + // Purple badge — persists until Options is opened + api.action.setBadgeText({ text: 'SYN' }); + api.action.setBadgeBackgroundColor({ color: '#7c3aed' }); + + // Desktop notification + try { + api.notifications.create('ss-sync-applied', { + type: 'basic', + iconUrl: 'icons/icon48.svg', + title: 'Silent Send — Settings Synced', + message: `Settings updated via ${sourceLabel}. Open Options to review.`, + priority: 1, + }); + } catch (e) { + // Notifications permission not granted — badge is still visible + } + } + // When sync storage changes (another device pushed new data), pull it into local if (areaName === 'sync' && (changes.ss_sync_meta || Object.keys(changes).some(k => k.startsWith('ss_sync_chunk_')))) { const settings = await Storage.getSettings(); @@ -318,6 +352,14 @@ api.storage.onChanged.addListener(async (changes, areaName) => { } }); +// Clicking a sync notification opens the Options page +api.notifications.onClicked.addListener((notificationId) => { + if (notificationId === 'ss-sync-applied') { + api.runtime.openOptionsPage(); + api.notifications.clear(notificationId); + } +}); + // --- Set initial state --- api.runtime.onInstalled.addListener(async () => { api.action.setBadgeBackgroundColor({ color: '#6b7280' }); @@ -329,6 +371,14 @@ api.runtime.onInstalled.addListener(async () => { (async () => { const settings = await Storage.getSettings(); await updateIcon(settings); + + // Restore the SYN badge if the user hasn't opened Options since the last sync + const stored = await api.storage.local.get('ss_sync_notification'); + if (stored.ss_sync_notification) { + api.action.setBadgeText({ text: 'SYN' }); + api.action.setBadgeBackgroundColor({ color: '#7c3aed' }); + } + if (settings.browserSync) { await SilentSendSync.pullFromSyncStorage(); } diff --git a/src/content/content.js b/src/content/content.js index ec9b49f..0f25436 100644 --- a/src/content/content.js +++ b/src/content/content.js @@ -1134,9 +1134,17 @@ }); await setStorageData('ss_mappings', currentMappings); - // Update local mappings + // Update local mappings so the fetch interceptor uses them immediately mappings = currentMappings; + // Replace the PPI value in the current input right now + if (inputEl) { + replaceInInput(inputEl, real, fake); + // Re-scan — will dismiss warning if no more PPI remains + if (inputScanTimer) clearTimeout(inputScanTimer); + inputScanTimer = setTimeout(() => scanInputForPPI(inputEl), 150); + } + // Visual feedback btn.textContent = '\u2714'; btn.style.color = '#4ade80'; @@ -1145,6 +1153,27 @@ }); } + // Replace all occurrences of `real` with `fake` in an input or contenteditable element + function replaceInInput(el, real, fake) { + if (!el) return; + const re = new RegExp(real.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); + if (el.value !== undefined) { + //