Web: remove version from tab title, fix month event packing, add day overflow popup
- Tab title shows only "Calendarr" (version stays in sidebar/impressum) - Fix lane assignment in month view: use 2D per-column occupancy grid instead of global colEnd tracking; eliminates empty rows caused by non-overlapping events being pushed to lower lanes - "+N weitere" now opens an inline popup listing all events for that day (all-day first, then by time); clicking an event opens its detail popup - Double-click on day cell navigates to day view (was already implemented) - Remove hardcoded "v18" from login screen impressum button - Bump version to v49 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,8 @@ import { openDatePicker, formatDtDisplay } from './date-picker.js';
|
||||
import { t, setLang, getLang } from './i18n.js';
|
||||
import { APP_VERSION } from './version.js';
|
||||
|
||||
// Version sofort beim Modul-Load ueberall sichtbar setzen.
|
||||
document.title = `Calendarr ${APP_VERSION}`;
|
||||
// Version im Impressum/Sidebar sichtbar, nicht im Tab-Titel.
|
||||
document.title = 'Calendarr';
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const imp = document.getElementById('impressum-version');
|
||||
if (imp) imp.textContent = `Calendarr ${APP_VERSION}`;
|
||||
@@ -543,7 +543,7 @@ function updateTitle() {
|
||||
titleEl.innerHTML =
|
||||
`<span class="view-title-main">${main}</span>` +
|
||||
(year ? `<span class="view-title-year">${year}</span>` : '');
|
||||
document.title = `Calendarr ${APP_VERSION} - ${fullText}`;
|
||||
document.title = `Calendarr – ${fullText}`;
|
||||
}
|
||||
|
||||
function updateViewButtons() {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// Increment APP_VERSION with every code change
|
||||
export const APP_VERSION = 'v48';
|
||||
export const APP_VERSION = 'v49';
|
||||
|
||||
@@ -71,13 +71,21 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC
|
||||
return new Date(a.ev.start) - new Date(b.ev.start);
|
||||
});
|
||||
|
||||
// Assign lanes (greedy interval packing)
|
||||
const lanes = [];
|
||||
// Assign lanes using a 2D occupancy grid (per-column tracking).
|
||||
// This prevents gaps: an event in cols 3-6 no longer blocks col 0-2 in the same lane.
|
||||
const occupiedGrid = []; // occupiedGrid[lane] = Set<col>
|
||||
rowItems.forEach(item => {
|
||||
let laneIdx = lanes.findIndex(l => item.colStart >= l.colEnd);
|
||||
if (laneIdx === -1) { laneIdx = lanes.length; lanes.push({ colEnd: 0 }); }
|
||||
item.lane = laneIdx;
|
||||
lanes[laneIdx].colEnd = item.colStart + item.span;
|
||||
const cols = Array.from({ length: item.span }, (_, i) => item.colStart + i);
|
||||
let laneIdx = 0;
|
||||
for (;;) {
|
||||
if (!occupiedGrid[laneIdx]) occupiedGrid[laneIdx] = new Set();
|
||||
if (cols.every(c => !occupiedGrid[laneIdx].has(c))) {
|
||||
cols.forEach(c => occupiedGrid[laneIdx].add(c));
|
||||
item.lane = laneIdx;
|
||||
break;
|
||||
}
|
||||
laneIdx++;
|
||||
}
|
||||
});
|
||||
|
||||
// Track overflow per column
|
||||
@@ -190,11 +198,20 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC
|
||||
if (ev) onEventClick(ev, spanEl);
|
||||
return;
|
||||
}
|
||||
// "+N more" → navigate to day view
|
||||
// "+N more" → show overflow popup with all events for that day
|
||||
const moreEl = e.target.closest('.month-more');
|
||||
if (moreEl) {
|
||||
e.stopPropagation();
|
||||
onDayClick(new Date(moreEl.dataset.date + 'T00:00:00'), 'navigate');
|
||||
const dayDate = new Date(moreEl.dataset.date + 'T00:00:00');
|
||||
const dayEvents = normed
|
||||
.filter(({ ns, ne }) => ns <= dayDate && ne >= dayDate)
|
||||
.map(({ ev }) => ev)
|
||||
.sort((a, b) => {
|
||||
if (a.allDay && !b.allDay) return -1;
|
||||
if (!a.allDay && b.allDay) return 1;
|
||||
return new Date(a.start) - new Date(b.start);
|
||||
});
|
||||
showOverflowPopup(moreEl, dayDate, dayEvents, onEventClick);
|
||||
return;
|
||||
}
|
||||
// Column click → select day
|
||||
@@ -243,3 +260,66 @@ function escHtml(s) {
|
||||
function escAttr(s) {
|
||||
return String(s).replace(/"/g,'"').replace(/'/g,''');
|
||||
}
|
||||
|
||||
function showOverflowPopup(anchor, date, events, onEventClick) {
|
||||
document.querySelectorAll('.month-overflow-popup').forEach(p => p.remove());
|
||||
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'month-overflow-popup';
|
||||
|
||||
// Header: "Mo, 3. Jul"
|
||||
const months = t('months');
|
||||
const dow = t('dow_monday');
|
||||
const dowIdx = (date.getDay() + 6) % 7; // 0=Mon
|
||||
const header = document.createElement('div');
|
||||
header.className = 'mop-header';
|
||||
header.textContent = `${dow[dowIdx]}, ${date.getDate()}. ${months[date.getMonth()]}`;
|
||||
popup.appendChild(header);
|
||||
|
||||
events.forEach(ev => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'mop-row';
|
||||
|
||||
const dot = document.createElement('span');
|
||||
dot.className = 'mop-dot';
|
||||
dot.style.background = ev.color || ev.calendarColor || '#4285f4';
|
||||
|
||||
const time = document.createElement('span');
|
||||
time.className = 'mop-time';
|
||||
time.textContent = ev.allDay ? t('allday_cap') : fmtTime(new Date(ev.start));
|
||||
|
||||
const title = document.createElement('span');
|
||||
title.className = 'mop-title';
|
||||
title.textContent = ev.title;
|
||||
|
||||
row.append(dot, time, title);
|
||||
row.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
popup.remove();
|
||||
onEventClick(ev, row);
|
||||
});
|
||||
popup.appendChild(row);
|
||||
});
|
||||
|
||||
document.body.appendChild(popup);
|
||||
|
||||
// Position near anchor, stay in viewport
|
||||
const r = anchor.getBoundingClientRect();
|
||||
const pw = popup.offsetWidth || 260;
|
||||
const ph = popup.offsetHeight || 200;
|
||||
let left = r.left;
|
||||
let top = r.bottom + 4;
|
||||
if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
|
||||
if (top + ph > window.innerHeight - 8) top = r.top - ph - 4;
|
||||
if (left < 8) left = 8;
|
||||
if (top < 8) top = 8;
|
||||
popup.style.left = left + 'px';
|
||||
popup.style.top = top + 'px';
|
||||
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', function close() {
|
||||
popup.remove();
|
||||
document.removeEventListener('click', close);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user