Feature: Enddatum im Popup + Kopieren-nach-Kalender-Button

Enddatum wird im Event-Popup angezeigt wenn Termin über Mitternacht geht. Neuer Kopieren-Button (📋) im Popup öffnet Kalender-Auswahl und dupliziert den Termin in den gewählten Kalender (CalDAV / Lokal / Google).
This commit is contained in:
2026-04-08 14:34:01 +02:00
parent b40e8c6731
commit cae39e6086
4 changed files with 100 additions and 3 deletions

View File

@@ -791,6 +791,21 @@ a { color: var(--primary); text-decoration: none; }
.popup-body { padding: 12px 16px; }
.popup-time, .popup-location, .popup-calendar { font-size: 13px; color: var(--text-2); margin-bottom: 6px; }
.popup-description { font-size: 13px; color: var(--text-1); margin-bottom: 6px; white-space: pre-wrap; }
.popup-copy-menu {
border-top: 1px solid var(--border);
padding: 4px 0;
}
.popup-copy-label {
font-size: 11px; font-weight: 600; text-transform: uppercase;
letter-spacing: .5px; color: var(--text-3);
padding: 4px 14px 6px;
}
.popup-copy-item {
display: flex; align-items: center; gap: 9px;
padding: 7px 14px; cursor: pointer; font-size: 13px; color: var(--text-1);
}
.popup-copy-item:hover { background: var(--bg-hover); }
.popup-copy-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
/* ── Settings Page ──────────────────────────────────────── */
#modal-settings.modal-overlay {

View File

@@ -273,6 +273,9 @@
<button class="icon-btn popup-action" id="popup-edit" title="Bearbeiten">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
</button>
<button class="icon-btn popup-action" id="popup-copy" title="Kopieren nach…">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
</button>
<button class="icon-btn popup-action" id="popup-delete" title="Löschen">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
</button>
@@ -284,6 +287,7 @@
<div class="popup-description" id="popup-description"></div>
<div class="popup-calendar" id="popup-calendar"></div>
</div>
<div id="popup-copy-menu" class="popup-copy-menu hidden"></div>
</div>
<!-- Add CalDAV Account Modal -->

View File

@@ -731,12 +731,16 @@ function showEventPopup(ev, anchor) {
// Time
if (ev.allDay) {
document.getElementById('popup-time').textContent = 'Ganztägig';
document.getElementById('popup-time').textContent = t('allday_cap');
} else {
const s = new Date(ev.start);
const e = new Date(ev.end);
document.getElementById('popup-time').textContent =
`${fmtDatetime(s)} ${fmtTime(e)}`;
const sameDay = s.getFullYear() === e.getFullYear() &&
s.getMonth() === e.getMonth() &&
s.getDate() === e.getDate();
document.getElementById('popup-time').textContent = sameDay
? `${fmtDatetime(s)} ${fmtTime(e)}`
: `${fmtDatetime(s)} ${fmtDatetime(e)}`;
}
document.getElementById('popup-location').textContent = ev.location || '';
@@ -759,6 +763,33 @@ function showEventPopup(ev, anchor) {
popup.classList.add('hidden');
openEditEventModal(ev);
};
// Copy to calendar
document.getElementById('popup-copy').onclick = e => {
e.stopPropagation();
const menu = document.getElementById('popup-copy-menu');
if (!menu.classList.contains('hidden')) { menu.classList.add('hidden'); return; }
const targets = buildWritableCalendars(ev);
if (!targets.length) { showToast('Keine Zielkalender verfügbar', true); return; }
menu.innerHTML = `<div class="popup-copy-label">${t('copy_to_calendar')}</div>` +
targets.map(c =>
`<div class="popup-copy-item" data-cal-idx="${c._idx}">
<span class="popup-copy-dot" style="background:${c.color}"></span>
<span>${escHtml(c.name)}</span>
</div>`
).join('');
menu.classList.remove('hidden');
menu.querySelectorAll('.popup-copy-item').forEach(el => {
el.addEventListener('click', async ev2 => {
ev2.stopPropagation();
menu.classList.add('hidden');
popup.classList.add('hidden');
const cal = targets[parseInt(el.dataset.calIdx)];
await copyEventToCalendar(ev, cal);
});
});
};
document.getElementById('popup-delete').onclick = async () => {
if (!confirm(t("confirm_delete_event", {title: ev.title}))) return;
popup.classList.add('hidden');
@@ -1881,3 +1912,48 @@ function fmtDatetime(d) {
function escHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
function buildWritableCalendars(_excludeEv) {
const list = [];
let idx = 0;
for (const acc of state.accounts) {
for (const cal of acc.calendars) {
if (cal.sidebar_hidden) continue;
list.push({ _idx: idx++, id: cal.id, name: `${acc.name} / ${cal.name}`, color: cal.color || '#4285f4', type: 'caldav' });
}
}
for (const cal of state.localCalendars) {
list.push({ _idx: idx++, id: cal.id, name: cal.name, color: cal.color || '#34a853', type: 'local' });
}
for (const acc of state.googleAccounts) {
for (const cal of acc.calendars) {
if (cal.sidebar_hidden) continue;
list.push({ _idx: idx++, id: cal.id, name: `${acc.email} / ${cal.name}`, color: cal.color || '#4285f4', type: 'google' });
}
}
return list;
}
async function copyEventToCalendar(ev, cal) {
const { title, start, end, allDay, location, description, color } = ev;
try {
if (cal.type === 'google') {
await api.post('/google/events', {
calendar_db_id: cal.id, title, start, end, allDay,
location: location || '', description: description || '',
});
} else if (cal.type === 'local') {
await api.post('/local/events', {
calendar_id: cal.id, title, start, end, allDay,
location: location || '', description: description || '', color: color || null,
});
} else {
await api.post('/caldav/events', {
calendar_id: cal.id, title, start, end, allDay,
location: location || '', description: description || '', color: color || null,
});
}
showToast(t('event_copied'));
fetchAndRender(true);
} catch (e) { showToast(e.message, true); }
}

View File

@@ -142,6 +142,7 @@ const translations = {
error_enter_title: 'Bitte Titel eingeben',
error_enter_date: 'Bitte Datum eingeben',
error_enter_start: 'Bitte Start-Zeit eingeben',
copy_to_calendar: 'Kopieren nach…', event_copied: 'Termin kopiert',
event_updated: 'Termin aktualisiert', event_created: 'Termin erstellt',
confirm_delete_event: '"{title}" wirklich löschen?',
event_deleted: 'Termin gelöscht',
@@ -336,6 +337,7 @@ const translations = {
error_enter_title: 'Please enter a title',
error_enter_date: 'Please enter a date',
error_enter_start: 'Please enter a start time',
copy_to_calendar: 'Copy to…', event_copied: 'Event copied',
event_updated: 'Event updated', event_created: 'Event created',
confirm_delete_event: 'Really delete "{title}"?',
event_deleted: 'Event deleted',