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-body { padding: 12px 16px; }
|
||||||
.popup-time, .popup-location, .popup-calendar { font-size: 13px; color: var(--text-2); margin-bottom: 6px; }
|
.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-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 ──────────────────────────────────────── */
|
/* ── Settings Page ──────────────────────────────────────── */
|
||||||
#modal-settings.modal-overlay {
|
#modal-settings.modal-overlay {
|
||||||
|
|||||||
@@ -273,6 +273,9 @@
|
|||||||
<button class="icon-btn popup-action" id="popup-edit" title="Bearbeiten">
|
<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>
|
<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>
|
||||||
|
<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">
|
<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>
|
<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>
|
</button>
|
||||||
@@ -284,6 +287,7 @@
|
|||||||
<div class="popup-description" id="popup-description"></div>
|
<div class="popup-description" id="popup-description"></div>
|
||||||
<div class="popup-calendar" id="popup-calendar"></div>
|
<div class="popup-calendar" id="popup-calendar"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="popup-copy-menu" class="popup-copy-menu hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add CalDAV Account Modal -->
|
<!-- Add CalDAV Account Modal -->
|
||||||
|
|||||||
@@ -731,12 +731,16 @@ function showEventPopup(ev, anchor) {
|
|||||||
|
|
||||||
// Time
|
// Time
|
||||||
if (ev.allDay) {
|
if (ev.allDay) {
|
||||||
document.getElementById('popup-time').textContent = 'Ganztägig';
|
document.getElementById('popup-time').textContent = t('allday_cap');
|
||||||
} else {
|
} else {
|
||||||
const s = new Date(ev.start);
|
const s = new Date(ev.start);
|
||||||
const e = new Date(ev.end);
|
const e = new Date(ev.end);
|
||||||
document.getElementById('popup-time').textContent =
|
const sameDay = s.getFullYear() === e.getFullYear() &&
|
||||||
`${fmtDatetime(s)} – ${fmtTime(e)}`;
|
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 || '';
|
document.getElementById('popup-location').textContent = ev.location || '';
|
||||||
@@ -759,6 +763,33 @@ function showEventPopup(ev, anchor) {
|
|||||||
popup.classList.add('hidden');
|
popup.classList.add('hidden');
|
||||||
openEditEventModal(ev);
|
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 () => {
|
document.getElementById('popup-delete').onclick = async () => {
|
||||||
if (!confirm(t("confirm_delete_event", {title: ev.title}))) return;
|
if (!confirm(t("confirm_delete_event", {title: ev.title}))) return;
|
||||||
popup.classList.add('hidden');
|
popup.classList.add('hidden');
|
||||||
@@ -1881,3 +1912,48 @@ function fmtDatetime(d) {
|
|||||||
function escHtml(s) {
|
function escHtml(s) {
|
||||||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
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_title: 'Bitte Titel eingeben',
|
||||||
error_enter_date: 'Bitte Datum eingeben',
|
error_enter_date: 'Bitte Datum eingeben',
|
||||||
error_enter_start: 'Bitte Start-Zeit 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',
|
event_updated: 'Termin aktualisiert', event_created: 'Termin erstellt',
|
||||||
confirm_delete_event: '"{title}" wirklich löschen?',
|
confirm_delete_event: '"{title}" wirklich löschen?',
|
||||||
event_deleted: 'Termin gelöscht',
|
event_deleted: 'Termin gelöscht',
|
||||||
@@ -336,6 +337,7 @@ const translations = {
|
|||||||
error_enter_title: 'Please enter a title',
|
error_enter_title: 'Please enter a title',
|
||||||
error_enter_date: 'Please enter a date',
|
error_enter_date: 'Please enter a date',
|
||||||
error_enter_start: 'Please enter a start time',
|
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',
|
event_updated: 'Event updated', event_created: 'Event created',
|
||||||
confirm_delete_event: 'Really delete "{title}"?',
|
confirm_delete_event: 'Really delete "{title}"?',
|
||||||
event_deleted: 'Event deleted',
|
event_deleted: 'Event deleted',
|
||||||
|
|||||||
Reference in New Issue
Block a user