Step 6: extract Cache & Scans + Duplicate Review from options.js
Splits the 3133-line options.js into three vanilla scripts loaded in order at the bottom of options.html: options-cache.js 161 lines (Cache & Scans block) options-dupe-review.js 616 lines (Duplicate Review + Keep Ranking) options.js 2356 lines (everything else) No behavior change. Cross-file references work because classic <script> tags share the global declarative environment: top-level `let` bindings in options-cache.js (_configuredScanRoots, _cacheSkippedByRemote) are visible by bare reference in options.js, where Library Issues still reads them. Calls into options.js from the extracted files (escapeHtml, openModal/closeModal, keepActionViewport, clearNativeRepairCard, renderNativeMessagingFailure) all occur inside event handlers, resolved at call time after options.js parses. node --check passes on each file individually and on the concatenation of all three in load order. Brace counts balanced. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
// ---------- cache status ----------
|
||||
|
||||
function fmtCacheAge(hours) {
|
||||
if (!Number.isFinite(hours)) return "?";
|
||||
if (hours < 1) return `${Math.round(hours * 60)}m`;
|
||||
if (hours < 24) return `${hours.toFixed(1)}h`;
|
||||
return `${(hours / 24).toFixed(1)}d`;
|
||||
}
|
||||
|
||||
let _configuredScanRoots = [];
|
||||
let _cacheSkippedByRemote = new Map();
|
||||
let _skippedModalText = "";
|
||||
|
||||
function rememberConfiguredScanRoots(r) {
|
||||
_configuredScanRoots = [
|
||||
...(r?.configured?.default_source || []),
|
||||
...(r?.configured?.default_target || []),
|
||||
];
|
||||
}
|
||||
|
||||
function setupHealthRow(level, name, detail) {
|
||||
const icon = level === "ok" ? "✓" : level === "warn" ? "!" : level === "fail" ? "✗" : "i";
|
||||
return `<div class="diag-row ${level}"><span class="icon">${icon}</span><span class="name">${escapeHtml(name)}</span><span class="detail">${escapeHtml(detail)}</span></div>`;
|
||||
}
|
||||
|
||||
function openSkippedModal(remote) {
|
||||
const items = _cacheSkippedByRemote.get(remote) || [];
|
||||
const summary = document.getElementById("skipped-modal-summary");
|
||||
const list = document.getElementById("skipped-modal-list");
|
||||
document.getElementById("skipped-modal-subtitle").textContent = `${remote} · ${items.length} skipped`;
|
||||
const reasonCounts = new Map();
|
||||
for (const item of items) reasonCounts.set(item.reason || "unparsed ID", (reasonCounts.get(item.reason || "unparsed ID") || 0) + 1);
|
||||
summary.innerHTML = [...reasonCounts.entries()]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([reason, count]) => `<span>${escapeHtml(count)} ${escapeHtml(reason)}</span>`)
|
||||
.join("");
|
||||
list.innerHTML = items.map((item) => `<div class="skip-row">
|
||||
<div class="name">${escapeHtml(item.name || item.path || "?")}</div>
|
||||
<div class="reason">${escapeHtml(item.reason || "unparsed ID")}</div>
|
||||
<div class="path">${escapeHtml(item.path || "")}</div>
|
||||
</div>`).join("") || `<div style="color:#777;">No skipped IDs recorded for this remote.</div>`;
|
||||
_skippedModalText = [
|
||||
`Skipped IDs for ${remote}`,
|
||||
...items.map((item) => `${item.name || item.path || "?"}\t${item.reason || "unparsed ID"}\t${item.path || ""}`),
|
||||
].join("\n");
|
||||
openModal("skipped-modal");
|
||||
}
|
||||
|
||||
function closeSkippedModal() {
|
||||
closeModal("skipped-modal");
|
||||
}
|
||||
|
||||
document.getElementById("skipped-modal-close").addEventListener("click", closeSkippedModal);
|
||||
document.getElementById("skipped-modal-done").addEventListener("click", closeSkippedModal);
|
||||
document.getElementById("skipped-modal").addEventListener("click", (event) => {
|
||||
if (event.target.id === "skipped-modal") closeSkippedModal();
|
||||
});
|
||||
document.getElementById("skipped-modal-copy").addEventListener("click", async () => {
|
||||
if (!_skippedModalText) return;
|
||||
await navigator.clipboard.writeText(_skippedModalText);
|
||||
const btn = document.getElementById("skipped-modal-copy");
|
||||
btn.textContent = "Copied";
|
||||
setTimeout(() => { btn.textContent = "Copy List"; }, 1200);
|
||||
});
|
||||
|
||||
document.getElementById("setup-health-run").addEventListener("click", (event) =>
|
||||
keepActionViewport(event.currentTarget, async () => {
|
||||
const out = document.getElementById("setup-health-results");
|
||||
clearNativeRepairCard();
|
||||
out.textContent = "checking setup health...";
|
||||
const [settings, cache, host] = await Promise.all([
|
||||
chrome.runtime.sendMessage({ type: "get-settings" }),
|
||||
chrome.runtime.sendMessage({ type: "cache-status" }),
|
||||
chrome.runtime.sendMessage({ type: "host-status" }),
|
||||
]);
|
||||
const rows = [];
|
||||
const mode = settings?.quickMode !== false ? "LIVE" : "CACHE";
|
||||
rows.push(setupHealthRow(settings?.scanPaused ? "warn" : "ok", "Search state",
|
||||
settings?.scanPaused ? `${mode} mode · scanning paused` : `${mode} mode · scanning enabled`));
|
||||
rows.push(setupHealthRow("info", "Library profile",
|
||||
settings?.activeProfile || "config.json defaults"));
|
||||
const nativeBlocked = [cache, host].find((r) => r && !r.ok && r.error_kind);
|
||||
if (nativeBlocked) await renderNativeMessagingFailure(nativeBlocked);
|
||||
if (!cache?.ok && cache?.error_kind) {
|
||||
rows.push(setupHealthRow("warn", "Cache", "Blocked until native host registration is fixed."));
|
||||
} else if (!cache?.ok) {
|
||||
rows.push(setupHealthRow("fail", "Cache", cache?.error || "cache status unavailable"));
|
||||
} else if (!cache.cache_exists) {
|
||||
rows.push(setupHealthRow("warn", "Cache", "cache.json missing; cached searches need a rebuild"));
|
||||
} else {
|
||||
const remotes = cache.remotes || [];
|
||||
const stale = remotes.filter((r) => r.stale || r.status === "never_scanned");
|
||||
const files = remotes.reduce((sum, r) => sum + Number(r.file_count || 0), 0);
|
||||
rows.push(setupHealthRow(stale.length || (cache.warnings || []).length ? "warn" : "ok", "Cache",
|
||||
`${files.toLocaleString()} files · ${remotes.length} remote(s) · ${stale.length} stale/unscanned`));
|
||||
}
|
||||
if (!host?.ok && host?.error_kind) {
|
||||
rows.push(setupHealthRow("warn", "Native host", "Registration is required before host checks can run."));
|
||||
} else if (!host?.ok) {
|
||||
rows.push(setupHealthRow("fail", "Native host", host?.error || "host status unavailable"));
|
||||
} else {
|
||||
const failed = (host.checks || []).filter((c) => c.status === "fail");
|
||||
rows.push(setupHealthRow(failed.length ? "fail" : "ok", "Native host",
|
||||
failed.length ? `${failed.length} registration check(s) failed; use Diagnostics` : "registration checks passed"));
|
||||
}
|
||||
out.innerHTML = rows.join("");
|
||||
})
|
||||
);
|
||||
|
||||
document.getElementById("cache-status-run").addEventListener("click", async () => {
|
||||
const out = document.getElementById("cache-status-results");
|
||||
out.textContent = "checking cache...";
|
||||
try {
|
||||
const r = await chrome.runtime.sendMessage({ type: "cache-status" });
|
||||
if (!r || !r.ok) {
|
||||
out.innerHTML = `<span style="color:#faa;">error:</span> ${escapeHtml(r?.error || "no response")}`;
|
||||
return;
|
||||
}
|
||||
rememberConfiguredScanRoots(r);
|
||||
_cacheSkippedByRemote = new Map((r.remotes || []).map((m) => [m.remote, m.skipped_items || []]));
|
||||
if (!r.cache_exists) {
|
||||
const configured = (r.remotes || []).map((m) =>
|
||||
`<div style="margin-top:6px;color:#ffa;">! ${escapeHtml(m.remote)} · never scanned</div>`
|
||||
);
|
||||
out.innerHTML = [
|
||||
`<div><span style="color:#ffa;">cache not found</span></div>`,
|
||||
`<div>${escapeHtml(r.cache_path || "")}</div>`,
|
||||
...configured,
|
||||
].join("");
|
||||
return;
|
||||
}
|
||||
const rows = [
|
||||
`<div><span style="color:#777;">Path:</span> ${escapeHtml(r.cache_path || "")}</div>`,
|
||||
`<div><span style="color:#777;">Version:</span> ${escapeHtml(r.version ?? "?")}</div>`,
|
||||
`<div><span style="color:#777;">Stale after:</span> ${escapeHtml(r.stale_hours ?? 24)}h</div>`,
|
||||
`<div><span style="color:#777;">Configured target:</span> ${escapeHtml((r.configured?.default_target || []).join(", ") || "(none)")}</div>`,
|
||||
`<div><span style="color:#777;">Configured source:</span> ${escapeHtml((r.configured?.default_source || []).join(", ") || "(none)")}</div>`,
|
||||
];
|
||||
for (const m of r.remotes || []) {
|
||||
const color = m.status === "never_scanned" || m.stale ? "#ffa" : "#afa";
|
||||
const state = m.status === "never_scanned" ? "never scanned" : `${m.status || (m.stale ? "stale" : "fresh")} · age ${fmtCacheAge(m.age_hours)}`;
|
||||
const skippedCount = Number(m.skipped_count) || 0;
|
||||
const skippedNote = skippedCount
|
||||
? ` · <button class="chip-btn cache-show-skipped" type="button" data-remote="${escapeHtml(m.remote)}" style="color:#ffa;background:rgba(255,200,50,.08);border-color:rgba(255,200,50,.2);">${skippedCount} non-JAV ▾</button>`
|
||||
: "";
|
||||
rows.push(`<div style="margin-top:6px;"><span style="color:${color};">${escapeHtml(m.remote)}</span> · ${escapeHtml(state)} · ${escapeHtml(m.file_count)} files${skippedNote} <button class="chip-btn cache-refresh-remote" type="button" data-remote="${escapeHtml(m.remote)}">Refresh</button></div>`);
|
||||
for (const issue of m.issues || []) {
|
||||
rows.push(`<div style="color:#ffa;margin-left:12px;">! ${escapeHtml(issue.count)} ${escapeHtml(issue.message)}</div>`);
|
||||
}
|
||||
}
|
||||
if ((r.warnings || []).length) {
|
||||
rows.push(`<div style="margin-top:10px;color:#ffcc44;">Rebuild cache recommended:</div>`);
|
||||
for (const w of r.warnings || []) {
|
||||
rows.push(`<div style="color:#ffa;margin-left:12px;">! ${escapeHtml(w.message || w.code)}</div>`);
|
||||
}
|
||||
}
|
||||
out.innerHTML = rows.join("");
|
||||
} catch (err) {
|
||||
out.innerHTML = `<span style="color:#faa;">error:</span> ${escapeHtml(err.message || String(err))}`;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user