eabab39210
Mark AutoTrader listings as 'not wanted' on both search results and detail pages. Includes the unlisted signing / auto-update pipeline (web-ext + dist/updates.json polled by Firefox). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
96 lines
3.2 KiB
JavaScript
96 lines
3.2 KiB
JavaScript
const api = typeof browser !== "undefined" ? browser : chrome;
|
|
|
|
const STORAGE_KEY = "dismissedListings";
|
|
const SETTINGS_KEY = "settings";
|
|
|
|
const els = {
|
|
count: document.getElementById("count"),
|
|
hideToggle: document.getElementById("hideToggle"),
|
|
exportBtn: document.getElementById("exportBtn"),
|
|
importBtn: document.getElementById("importBtn"),
|
|
importFile: document.getElementById("importFile"),
|
|
clearBtn: document.getElementById("clearBtn"),
|
|
status: document.getElementById("status"),
|
|
};
|
|
|
|
function setStatus(msg) {
|
|
els.status.textContent = msg;
|
|
if (msg) setTimeout(() => (els.status.textContent = ""), 2500);
|
|
}
|
|
|
|
async function refresh() {
|
|
const res = await api.storage.local.get([STORAGE_KEY, SETTINGS_KEY]);
|
|
const dismissed = res[STORAGE_KEY] || {};
|
|
const settings = res[SETTINGS_KEY] || {};
|
|
const n = Object.keys(dismissed).length;
|
|
els.count.textContent = n === 1 ? "1 listing dismissed" : `${n} listings dismissed`;
|
|
els.hideToggle.checked = !!settings.hideDismissed;
|
|
}
|
|
|
|
els.hideToggle.addEventListener("change", async () => {
|
|
const res = await api.storage.local.get(SETTINGS_KEY);
|
|
const settings = res[SETTINGS_KEY] || {};
|
|
settings.hideDismissed = els.hideToggle.checked;
|
|
await api.storage.local.set({ [SETTINGS_KEY]: settings });
|
|
});
|
|
|
|
els.clearBtn.addEventListener("click", async () => {
|
|
const res = await api.storage.local.get(STORAGE_KEY);
|
|
const n = Object.keys(res[STORAGE_KEY] || {}).length;
|
|
if (n === 0) {
|
|
setStatus("Nothing to clear.");
|
|
return;
|
|
}
|
|
const ok = confirm(`Clear all ${n} dismissals? This can't be undone.`);
|
|
if (!ok) return;
|
|
await api.storage.local.set({ [STORAGE_KEY]: {} });
|
|
await refresh();
|
|
setStatus("Cleared.");
|
|
});
|
|
|
|
els.exportBtn.addEventListener("click", async () => {
|
|
const res = await api.storage.local.get(STORAGE_KEY);
|
|
const data = res[STORAGE_KEY] || {};
|
|
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
const stamp = new Date().toISOString().slice(0, 10);
|
|
a.href = url;
|
|
a.download = `autotrader-marker-${stamp}.json`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
URL.revokeObjectURL(url);
|
|
setStatus("Exported.");
|
|
});
|
|
|
|
els.importBtn.addEventListener("click", () => els.importFile.click());
|
|
els.importFile.addEventListener("change", async (e) => {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
try {
|
|
const text = await file.text();
|
|
const parsed = JSON.parse(text);
|
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
throw new Error("Expected an object keyed by listing id");
|
|
}
|
|
const res = await api.storage.local.get(STORAGE_KEY);
|
|
const merged = { ...(res[STORAGE_KEY] || {}), ...parsed };
|
|
await api.storage.local.set({ [STORAGE_KEY]: merged });
|
|
await refresh();
|
|
setStatus(`Imported ${Object.keys(parsed).length} entries.`);
|
|
} catch (err) {
|
|
setStatus(`Import failed: ${err.message}`);
|
|
} finally {
|
|
els.importFile.value = "";
|
|
}
|
|
});
|
|
|
|
api.storage.onChanged.addListener((changes, area) => {
|
|
if (area === "local" && (changes[STORAGE_KEY] || changes[SETTINGS_KEY])) {
|
|
refresh();
|
|
}
|
|
});
|
|
|
|
refresh();
|