WebDAV-/Nextcloud-Sync mit Einstellungsseite (v1.1.0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
root
2026-10-11 18:15:37 +00:00
co-authored by Claude Sonnet 5.5
parent bec85f7e50
commit bb7ee831a8
9 changed files with 327 additions and 8 deletions
+49 -7
View File
@@ -1,9 +1,11 @@
const $ = (id) => document.getElementById(id);
const store = chrome.storage.sync;
let links = [];
let deleted = [];
let editing = false;
let editId = null;
let syncOn = false;
let syncState = ""; // "", "busy", "ok", "err"
const uid = () => Math.random().toString(36).slice(2, 10);
@@ -14,11 +16,43 @@ function normalizeUrl(raw) {
}
async function load() {
const data = await store.get("links");
links = data.links || [];
const s = await LabSync.readLocal();
links = s.links;
deleted = s.deleted;
// Einträge aus Version 1.0 haben noch keinen Zeitstempel
if (links.some((l) => !l.updated)) {
links = links.map((l) => ({ ...l, updated: l.updated || Date.now() }));
await save(false);
}
render();
syncOn = !!(await LabSync.getConfig())?.url;
render();
if (syncOn) runSync();
}
async function save(sync = true) {
await LabSync.writeLocal({ links, deleted, modified: Date.now() });
if (sync && syncOn) runSync();
}
async function runSync() {
setSync("busy");
try {
await LabSync.sync();
const s = await LabSync.readLocal();
links = s.links;
deleted = s.deleted;
setSync("ok");
} catch (e) {
setSync("err", e.message);
}
render();
}
const save = () => store.set({ links });
function setSync(state, msg = "") {
syncState = state;
$("syncBtn").title = msg || "Mit WebDAV synchronisieren";
}
function render() {
const q = $("search").value.trim().toLowerCase();
@@ -57,6 +91,8 @@ function render() {
document.body.classList.toggle("editing", editing);
for (const id of ["add", "export", "import"]) $(id).hidden = !editing;
$("editToggle").textContent = editing ? "[FERTIG]" : "[EDIT]";
$("syncBtn").hidden = !syncOn;
$("syncBtn").className = syncState;
}
function btn(label, fn) {
@@ -77,6 +113,7 @@ async function move(id, dir) {
async function remove(id) {
links = links.filter((l) => l.id !== id);
deleted = [...deleted.filter((t) => t.id !== id), { id, updated: Date.now() }];
await save();
render();
}
@@ -99,7 +136,7 @@ $("form").addEventListener("submit", async (e) => {
e.preventDefault();
const url = normalizeUrl($("fUrl").value);
if (!url) { $("fUrl").focus(); return; }
const entry = { name: $("fName").value.trim(), url, icon: $("fIcon").value.trim() };
const entry = { name: $("fName").value.trim(), url, icon: $("fIcon").value.trim(), updated: Date.now() };
if (editId === "new") links.push({ id: uid(), ...entry });
else Object.assign(links.find((l) => l.id === editId), entry);
await save();
@@ -114,6 +151,8 @@ $("editToggle").addEventListener("click", () => {
if (!editing) { editId = null; $("form").hidden = true; }
render();
});
$("syncBtn").addEventListener("click", runSync);
$("settings").addEventListener("click", () => chrome.runtime.openOptionsPage());
// Enter im Suchfeld öffnet den ersten Treffer
$("search").addEventListener("keydown", (e) => {
@@ -132,9 +171,12 @@ $("file").addEventListener("change", async (e) => {
if (!file) return;
try {
const arr = JSON.parse(await file.text());
links = arr
const now = Date.now();
const imported = arr
.filter((x) => x && x.name && x.url)
.map((x) => ({ id: uid(), name: String(x.name), url: normalizeUrl(String(x.url)) || String(x.url), icon: x.icon || "" }));
.map((x) => ({ id: uid(), name: String(x.name), url: normalizeUrl(String(x.url)) || String(x.url), icon: x.icon || "", updated: now }));
deleted = [...deleted, ...links.map((l) => ({ id: l.id, updated: now }))];
links = imported;
await save();
render();
} catch { alert("Ungültige JSON-Datei."); }