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:
@@ -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 {
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
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); }
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user