(function () { if (window.__clientBackup) return; window.__clientBackup = true; const TARGET_TEXT = "Spellchecker"; const CLONE_KEY = "data-lsbackup-cloned"; function exportLS() { const data = {}; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); data[key] = localStorage.getItem(key); } const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "localstorage-backup.json"; a.click(); URL.revokeObjectURL(url); } function importLS(file, onDone) { const reader = new FileReader(); reader.onload = e => { try { const data = JSON.parse(e.target.result); let count = 0; for (const [key, value] of Object.entries(data)) { localStorage.setItem(key, value); count++; } onDone(null, count); } catch (err) { onDone(err); } }; reader.readAsText(file); } function buildPanel() { const panel = document.createElement("div"); panel.style.cssText = ` display: none; flex-direction: column; gap: 8px; padding: 10px 12px; border-radius: 8px; background: var(--md-sys-color-surface-container-highest); border: 1px solid var(--md-sys-color-outline-variant); font-size: 12px; color: var(--md-sys-color-on-surface); `; const btnStyle = ` padding: 5px 12px; border-radius: 4px; border: none; font-size: 11px; font-weight: 600; cursor: pointer; `; const status = document.createElement("span"); status.style.cssText = "font-size: 11px; opacity: 0.7; min-height: 14px;"; const exportBtn = document.createElement("button"); exportBtn.textContent = "⬇ Export localStorage"; exportBtn.style.cssText = btnStyle + ` background: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); `; exportBtn.addEventListener("click", e => { e.preventDefault(); e.stopPropagation(); exportLS(); status.textContent = `✓ Exported ${localStorage.length} keys`; }); const fileInput = document.createElement("input"); fileInput.type = "file"; fileInput.accept = ".json"; fileInput.style.cssText = "display: none;"; fileInput.addEventListener("change", e => { const file = e.target.files[0]; if (!file) return; importLS(file, (err, count) => { if (err) { status.textContent = "✗ Invalid JSON file"; } else { status.textContent = `✓ Imported ${count} keys`; } fileInput.value = ""; }); }); const importBtn = document.createElement("button"); importBtn.textContent = "⬆ Import localStorage"; importBtn.style.cssText = btnStyle + ` background: var(--md-sys-color-surface-container); color: var(--md-sys-color-on-surface); border: 1px solid var(--md-sys-color-outline-variant); `; importBtn.addEventListener("click", e => { e.preventDefault(); e.stopPropagation(); fileInput.click(); }); panel.appendChild(exportBtn); panel.appendChild(importBtn); panel.appendChild(fileInput); panel.appendChild(status); return panel; } function tryInject() { document.querySelectorAll("a.pos_relative").forEach(btn => { if ( btn.hasAttribute(CLONE_KEY) || btn.hasAttribute("data-lsbackup-entry") || !btn.innerText.includes(TARGET_TEXT) ) return; btn.setAttribute(CLONE_KEY, "true"); const clone = btn.cloneNode(true); clone.removeAttribute(CLONE_KEY); clone.setAttribute("data-lsbackup-entry", "true"); const title = clone.querySelector("div.d_flex.flex-g_1.flex-d_column > div"); if (title) title.textContent = "Sanctum Backup"; const desc = clone.querySelector("div.d_flex.flex-g_1.flex-d_column > span"); if (desc) desc.textContent = "Backup or Restore all client data"; const iconBtn = document.createElement("div"); iconBtn.title = "LocalStorage Backup"; iconBtn.style.cssText = "cursor: pointer; z-index: 10; flex-shrink: 0;"; iconBtn.innerHTML = `
`; const existingIcon = clone.querySelector("div.fill_var\\(--md-sys-color-on-surface\\)"); if (existingIcon) { existingIcon.replaceWith(iconBtn); } else { clone.prepend(iconBtn); } clone.addEventListener("click", e => { e.preventDefault(); e.stopPropagation(); panel.style.display = panel.style.display === "flex" ? "none" : "flex"; }); const wrapper = document.createElement("div"); wrapper.style.cssText = "display: flex; flex-direction: column;"; const panel = buildPanel(); wrapper.appendChild(clone); wrapper.appendChild(panel); btn.parentNode.insertBefore(wrapper, btn.nextSibling); }); } tryInject(); const observer = new MutationObserver(() => tryInject()); observer.observe(document.body, { childList: true, subtree: true }); })();