haufen sachen

This commit is contained in:
th committed 2026-04-08 22:46:48 +02:00
1 parent 416ca9e854
commit e6243701a3
20 files changed
+1562 -116

No files matched your search

+70 -22
View File
@@ -21,7 +21,7 @@ const CAL_COLORS = ['#38bdf8', '#8b5cf6', '#fb7185', '#fbbf24', '#4ade80', '#fb9
let calendarConfigs = {}; // slug → { color, icon }
const CAL_CONFIG_KEY = 'hyprcrd-cal-configs';
const overlay = $('caldav-settings');
const settingsOverlay = $('settings-overlay');
const urlInput = $('caldav-url');
const userInput = $('caldav-user');
const passInput = $('caldav-pass');
@@ -30,12 +30,15 @@ const syncStatus = $('caldav-sync-status');
const logEl = $('caldav-log');
export function openCaldavSettings() {
overlay?.classList.add('open');
// Open settings overlay on sync tab
settingsOverlay?.classList.add('open');
settingsOverlay?.querySelectorAll('.settings-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === 'sync'));
settingsOverlay?.querySelectorAll('[id^="settings-panel-"]').forEach(p => p.classList.toggle('hidden', p.id !== 'settings-panel-sync'));
loadSettings();
}
export function closeCaldavSettings() {
overlay?.classList.remove('open');
settingsOverlay?.classList.remove('open');
}
function loadCalConfigs() {
@@ -97,7 +100,7 @@ function renderCalList(calendars) {
});
}
async function loadSettings() {
export async function loadSettings() {
const s = await api('api/caldav/settings');
if (!s) return;
if (urlInput) urlInput.value = s.url || '';
@@ -115,8 +118,6 @@ async function loadSettings() {
}
}
$('caldav-close')?.addEventListener('click', closeCaldavSettings);
overlay?.addEventListener('click', e => { if (e.target === overlay) closeCaldavSettings(); });
$('caldav-test')?.addEventListener('click', async () => {
testStatus.textContent = 'testing...';
@@ -149,6 +150,19 @@ $('caldav-save')?.addEventListener('click', async () => {
});
$('caldav-sync-now')?.addEventListener('click', () => triggerSync());
$('notes-reset')?.addEventListener('click', async () => {
if (syncStatus) { syncStatus.textContent = 'resetting notes...'; syncStatus.style.color = 'var(--t-dim)'; }
const res = await fetch('api/notes/reset', {
method: 'POST', headers: { 'X-CSRF-Token': getCsrf() },
}).then(r => r.json()).catch(() => null);
if (res?.ok) {
const s = res.stats || {};
if (syncStatus) { syncStatus.textContent = `reset: ${res.reset || 0} cleared, ${s.pulled || 0} re-pulled`; syncStatus.style.color = 'var(--t-accent)'; }
await window._reloadAll?.();
} else {
if (syncStatus) { syncStatus.textContent = res?.error || 'reset failed'; syncStatus.style.color = 'var(--t-danger)'; }
}
});
export async function triggerSync() {
const btn = $('cal-sync');
@@ -161,14 +175,22 @@ export async function triggerSync() {
if (btn) btn.style.opacity = '';
// Also sync WebDAV Notes
const notesRes = await fetch('api/notes/sync', {
method: 'POST', headers: { 'X-CSRF-Token': getCsrf() },
}).then(r => r.json()).catch(() => null);
if (res.ok) {
const s = res.stats || {};
const hasErrors = (res.log || []).some(l => l.status === 'error');
const msg = `pushed:${s.pushed || 0} pulled:${s.pulled || 0} del:${s.deleted || 0}${s.conflicts ? ' conflicts:' + s.conflicts : ''}`;
const ns = notesRes?.stats || {};
const total = { pushed: (s.pushed || 0) + (ns.pushed || 0), pulled: (s.pulled || 0) + (ns.pulled || 0), deleted: (s.deleted || 0) + (ns.deleted || 0) };
const allLog = [...(res.log || []), ...(notesRes?.log || [])];
const hasErrors = allLog.some(l => l.status === 'error');
const msg = `pushed:${total.pushed} pulled:${total.pulled} del:${total.deleted}${ns.pulled ? ' notes:' + ns.pulled : ''}`;
if (syncStatus) { syncStatus.textContent = msg; syncStatus.style.color = hasErrors ? 'var(--t-danger)' : 'var(--t-accent)'; }
await window._reloadAll?.();
showLog(res.log);
showSyncNotification(s);
showLog(allLog);
showSyncNotification(total);
} else {
if (syncStatus) { syncStatus.textContent = res.error || 'sync failed'; syncStatus.style.color = 'var(--t-danger)'; }
}
@@ -190,6 +212,25 @@ function showConnectionLog(lines) {
).join('');
}
$('notes-rekey')?.addEventListener('click', async () => {
const { showDialog } = await import('./dialog.js');
const oldPass = await showDialog({ title: 're-key notes', input: 'password', placeholder: 'old password', okLabel: 're-key' });
if (!oldPass) return;
if (syncStatus) { syncStatus.textContent = 're-keying notes...'; syncStatus.style.color = 'var(--t-dim)'; }
const res = await fetch('api/notes/rekey', {
method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() },
body: JSON.stringify({ old_password: oldPass }),
}).then(r => r.json()).catch(() => null);
if (res?.ok) {
if (syncStatus) { syncStatus.textContent = `re-keyed ${res.rekeyed} notes — syncing...`; syncStatus.style.color = 'var(--t-accent)'; }
// Re-pull with correct key
const pullRes = await fetch('api/notes/reset', { method: 'POST', headers: { 'X-CSRF-Token': getCsrf() } }).then(r => r.json()).catch(() => null);
await window._reloadAll?.();
if (syncStatus) syncStatus.textContent = `re-keyed ${res.rekeyed} notes, ${pullRes?.stats?.pulled || 0} re-pulled`;
} else {
if (syncStatus) { syncStatus.textContent = res?.error || 're-key failed'; syncStatus.style.color = 'var(--t-danger)'; }
}
});
$('cal-caldav-settings')?.addEventListener('click', openCaldavSettings);
$('cal-sync')?.addEventListener('click', () => triggerSync());
@@ -218,25 +259,32 @@ function showSyncNotification(stats) {
setTimeout(() => { note.style.transition = 'opacity 0.3s'; note.style.opacity = '0'; setTimeout(() => note.remove(), 350); }, 4000);
}
let bgSyncRunning = false;
async function backgroundSync() {
if (bgSyncRunning) return;
bgSyncRunning = true;
try {
const res = await fetch('api/caldav/sync', {
method: 'POST', headers: { 'X-CSRF-Token': getCsrf() },
}).then(r => r.json()).catch(() => null);
if (res?.ok) {
const s = res.stats || {};
if (s.pulled > 0 || s.pushed > 0 || s.deleted > 0) {
await window._reloadAll?.();
showSyncNotification(s);
}
const headers = { 'X-CSRF-Token': getCsrf() };
const [res, notesRes] = await Promise.all([
fetch('api/caldav/sync', { method: 'POST', headers }).then(r => r.ok ? r.json() : null).catch(() => null),
fetch('api/notes/sync', { method: 'POST', headers }).then(r => r.ok ? r.json() : null).catch(() => null),
]);
const s = res?.stats || {};
const ns = notesRes?.stats || {};
const total = { pushed: (s.pushed || 0) + (ns.pushed || 0), pulled: (s.pulled || 0) + (ns.pulled || 0), deleted: (s.deleted || 0) + (ns.deleted || 0) };
if (total.pulled > 0 || total.pushed > 0 || total.deleted > 0) {
await window._reloadAll?.();
showSyncNotification(total);
}
} catch {}
} catch {} finally {
bgSyncRunning = false;
}
}
// Start background sync after app is ready
let bgSyncInterval = null;
export function startBackgroundSync() {
if (bgSyncInterval) return;
backgroundSync(); // first sync immediately
bgSyncInterval = setInterval(backgroundSync, 120000); // then every 2 minutes
backgroundSync();
bgSyncInterval = setInterval(backgroundSync, 45000); // every 45 seconds
}