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:
@@ -733,6 +733,43 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
}
|
}
|
||||||
.month-more:hover { color: var(--primary); }
|
.month-more:hover { color: var(--primary); }
|
||||||
|
|
||||||
|
/* Overflow popup ("+N weitere") */
|
||||||
|
.month-overflow-popup {
|
||||||
|
position: fixed; z-index: 800;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
padding: 10px 0;
|
||||||
|
min-width: 220px; max-width: 300px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.mop-header {
|
||||||
|
font-size: 12px; font-weight: 600;
|
||||||
|
color: var(--text-2);
|
||||||
|
padding: 0 12px 8px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.mop-row {
|
||||||
|
display: flex; align-items: center; gap: 7px;
|
||||||
|
padding: 4px 12px; cursor: pointer;
|
||||||
|
border-radius: 6px; margin: 0 4px;
|
||||||
|
}
|
||||||
|
.mop-row:hover { background: var(--bg-hover); }
|
||||||
|
.mop-dot {
|
||||||
|
width: 8px; height: 8px; border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mop-time {
|
||||||
|
font-size: 11px; color: var(--text-2);
|
||||||
|
min-width: 42px; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mop-title {
|
||||||
|
font-size: 12px; font-weight: 500; color: var(--text-1);
|
||||||
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Week / Day Views ───────────────────────────────────── */
|
/* ── Week / Day Views ───────────────────────────────────── */
|
||||||
.week-view, .day-view { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: scroll; }
|
.week-view, .day-view { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: scroll; }
|
||||||
.week-head-sticky {
|
.week-head-sticky {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
<button type="submit" class="btn btn-primary btn-full">Anmelden</button>
|
<button type="submit" class="btn btn-primary btn-full">Anmelden</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<button class="impressum-link" onclick="openImpressum()">© 2026 Scarriffleservices · v18</button>
|
<button class="impressum-link" onclick="openImpressum()">© 2026 Scarriffleservices</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ─── MAIN APP ──────────────────────────────────────────── -->
|
<!-- ─── MAIN APP ──────────────────────────────────────────── -->
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { openDatePicker, formatDtDisplay } from './date-picker.js';
|
|||||||
import { t, setLang, getLang } from './i18n.js';
|
import { t, setLang, getLang } from './i18n.js';
|
||||||
import { APP_VERSION } from './version.js';
|
import { APP_VERSION } from './version.js';
|
||||||
|
|
||||||
// Version sofort beim Modul-Load ueberall sichtbar setzen.
|
// Version im Impressum/Sidebar sichtbar, nicht im Tab-Titel.
|
||||||
document.title = `Calendarr ${APP_VERSION}`;
|
document.title = 'Calendarr';
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const imp = document.getElementById('impressum-version');
|
const imp = document.getElementById('impressum-version');
|
||||||
if (imp) imp.textContent = `Calendarr ${APP_VERSION}`;
|
if (imp) imp.textContent = `Calendarr ${APP_VERSION}`;
|
||||||
@@ -543,7 +543,7 @@ function updateTitle() {
|
|||||||
titleEl.innerHTML =
|
titleEl.innerHTML =
|
||||||
`<span class="view-title-main">${main}</span>` +
|
`<span class="view-title-main">${main}</span>` +
|
||||||
(year ? `<span class="view-title-year">${year}</span>` : '');
|
(year ? `<span class="view-title-year">${year}</span>` : '');
|
||||||
document.title = `Calendarr ${APP_VERSION} - ${fullText}`;
|
document.title = `Calendarr – ${fullText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateViewButtons() {
|
function updateViewButtons() {
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Increment APP_VERSION with every code change
|
// 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);
|
return new Date(a.ev.start) - new Date(b.ev.start);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assign lanes (greedy interval packing)
|
// Assign lanes using a 2D occupancy grid (per-column tracking).
|
||||||
const lanes = [];
|
// 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 => {
|
rowItems.forEach(item => {
|
||||||
let laneIdx = lanes.findIndex(l => item.colStart >= l.colEnd);
|
const cols = Array.from({ length: item.span }, (_, i) => item.colStart + i);
|
||||||
if (laneIdx === -1) { laneIdx = lanes.length; lanes.push({ colEnd: 0 }); }
|
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;
|
item.lane = laneIdx;
|
||||||
lanes[laneIdx].colEnd = item.colStart + item.span;
|
break;
|
||||||
|
}
|
||||||
|
laneIdx++;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Track overflow per column
|
// Track overflow per column
|
||||||
@@ -190,11 +198,20 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC
|
|||||||
if (ev) onEventClick(ev, spanEl);
|
if (ev) onEventClick(ev, spanEl);
|
||||||
return;
|
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');
|
const moreEl = e.target.closest('.month-more');
|
||||||
if (moreEl) {
|
if (moreEl) {
|
||||||
e.stopPropagation();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// Column click → select day
|
// Column click → select day
|
||||||
@@ -243,3 +260,66 @@ function escHtml(s) {
|
|||||||
function escAttr(s) {
|
function escAttr(s) {
|
||||||
return String(s).replace(/"/g,'"').replace(/'/g,''');
|
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