fix: PPI + button replaces text immediately; add auto-sync folder
PPI + button fix: - After adding a mapping, the real PPI value is now immediately replaced with the fake value in the input/contenteditable element via the new replaceInInput() helper (handles both <textarea>/<input> and contenteditable divs by walking text nodes). - Triggers a re-scan 150ms later so the pre-send warning updates or dismisses itself if no more PPI remains. Auto-sync folder (File System Access API): - User picks a folder once per browser via "Choose Sync Folder". Any settings/mappings/identity change writes silent-send-sync.json to that folder automatically (via api.storage.onChanged listener). - On options page open and every time the page regains focus, the file is read back; if its lastModified is newer than local data, settings are imported immediately and the UI refreshes. - File handle is stored in IndexedDB (ss_sync_handles) so it persists across browser sessions without repeated permission prompts. - Pick the SAME folder in each browser (or a synced cloud folder for cross-computer sync) — fully automatic after that, no copy-paste. - sync.js gains saveSyncDirHandle / loadSyncDirHandle / clearSyncDirHandle helpers backed by IndexedDB. https://claude.ai/code/session_01TKpSR9M8JgHLXCp5CeDsQP
This commit is contained in:
@@ -171,6 +171,61 @@ const SilentSendSync = {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// File System Access API helpers — folder-based sync
|
||||
// The directory handle is stored in IndexedDB so the user only
|
||||
// needs to grant access once per browser session.
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
_dbPromise: null,
|
||||
|
||||
_openDB() {
|
||||
if (this._dbPromise) return this._dbPromise;
|
||||
this._dbPromise = new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open('ss_sync_handles', 1);
|
||||
req.onupgradeneeded = () => req.result.createObjectStore('handles');
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
return this._dbPromise;
|
||||
},
|
||||
|
||||
async saveSyncDirHandle(handle) {
|
||||
const db = await this._openDB();
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction('handles', 'readwrite');
|
||||
tx.objectStore('handles').put(handle, 'syncDir');
|
||||
tx.oncomplete = resolve;
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
},
|
||||
|
||||
async loadSyncDirHandle() {
|
||||
try {
|
||||
const db = await this._openDB();
|
||||
return new Promise((resolve) => {
|
||||
const tx = db.transaction('handles', 'readonly');
|
||||
const req = tx.objectStore('handles').get('syncDir');
|
||||
req.onsuccess = () => resolve(req.result || null);
|
||||
req.onerror = () => resolve(null);
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async clearSyncDirHandle() {
|
||||
try {
|
||||
const db = await this._openDB();
|
||||
return new Promise((resolve) => {
|
||||
const tx = db.transaction('handles', 'readwrite');
|
||||
tx.objectStore('handles').delete('syncDir');
|
||||
tx.oncomplete = resolve;
|
||||
tx.onerror = resolve;
|
||||
});
|
||||
} catch { /* ignore */ }
|
||||
},
|
||||
};
|
||||
|
||||
export default SilentSendSync;
|
||||
|
||||
Reference in New Issue
Block a user