Add birthday feature (web)
- views render display_title (age) and a cake icon for is_birthday events (month/week/agenda + quarter tooltip) via shared eventTitle/birthdayIconSvg - create split-button caret opens a menu: new event / new birthday - new birthday modal: name + date + "year unknown" + target birthday calendar, saved as an all-day FREQ=YEARLY local event with birth_year - local-calendar modal gains a "birthday calendar" toggle + "notify N days before" - i18n de/en strings Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { isPast } from '../utils.js';
|
||||
import { isPast, eventTitle, birthdayIconSvg } from '../utils.js';
|
||||
import { t, getLang } from '../i18n.js';
|
||||
|
||||
export function renderAgenda(container, currentDate, events, onEventClick) {
|
||||
@@ -45,7 +45,7 @@ export function renderAgenda(container, currentDate, events, onEventClick) {
|
||||
return `<div class="agenda-event ${pastCls}" data-id="${ev.id}" data-url="${escAttr(ev.url)}">
|
||||
<div class="agenda-ev-color" style="background:${color}"></div>
|
||||
<div class="agenda-ev-info">
|
||||
<div class="agenda-ev-title">${escHtml(ev.title)}</div>
|
||||
<div class="agenda-ev-title">${ev.is_birthday ? birthdayIconSvg() : ''}${escHtml(eventTitle(ev))}</div>
|
||||
<div class="agenda-ev-meta">${timeStr}${locHtml}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isToday, isPast, isSameDay, dayOfWeek, weekStart, getISOWeekNumber } from '../utils.js';
|
||||
import { isToday, isPast, isSameDay, dayOfWeek, weekStart, getISOWeekNumber, eventTitle, birthdayIconSvg } from '../utils.js';
|
||||
import { t } from '../i18n.js';
|
||||
|
||||
const LANE_H = 20; // px per lane (event height 18px + 2px gap)
|
||||
@@ -110,14 +110,15 @@ export function renderMonth(container, currentDate, events, onDayClick, onEventC
|
||||
const pastCls = isPast(ev) ? 'past' : '';
|
||||
const cL = continuesLeft ? 'continues-left' : '';
|
||||
const cR = continuesRight ? 'continues-right' : '';
|
||||
const titleEsc = escHtml(ev.title);
|
||||
const titleEsc = escHtml(eventTitle(ev));
|
||||
const icon = ev.is_birthday ? birthdayIconSvg() : '';
|
||||
const labelHtml = ev.allDay
|
||||
? titleEsc
|
||||
: `<span class="month-event-time">${escHtml(fmtTime(new Date(ev.start)))}</span> ${titleEsc}`;
|
||||
? icon + titleEsc
|
||||
: `<span class="month-event-time">${escHtml(fmtTime(new Date(ev.start)))}</span> ${icon}${titleEsc}`;
|
||||
eventsHtml += `<div class="month-span-event ${pastCls} ${cL} ${cR}"
|
||||
data-id="${ev.id}" data-url="${escAttr(ev.url)}"
|
||||
style="left:${leftPct.toFixed(3)}%;width:${widthPct.toFixed(3)}%;top:${topPx}px;background:${color}"
|
||||
title="${escAttr(ev.title)}">${labelHtml}</div>`;
|
||||
title="${escAttr(eventTitle(ev))}">${labelHtml}</div>`;
|
||||
});
|
||||
|
||||
// "+N more" per column
|
||||
@@ -292,7 +293,7 @@ function showOverflowPopup(anchor, date, events, onEventClick) {
|
||||
+ (continuesLeft ? ' continues-left' : '')
|
||||
+ (continuesRight ? ' continues-right' : '');
|
||||
bar.style.background = color;
|
||||
bar.textContent = ev.title;
|
||||
bar.innerHTML = (ev.is_birthday ? birthdayIconSvg() : '') + escHtml(eventTitle(ev));
|
||||
bar.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
popup.remove();
|
||||
@@ -314,7 +315,7 @@ function showOverflowPopup(anchor, date, events, onEventClick) {
|
||||
|
||||
const title = document.createElement('span');
|
||||
title.className = 'mop-title';
|
||||
title.textContent = ev.title;
|
||||
title.innerHTML = (ev.is_birthday ? birthdayIconSvg() : '') + escHtml(eventTitle(ev));
|
||||
|
||||
row.append(dot, time, title);
|
||||
row.addEventListener('click', e => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isToday, isPast, dayOfWeek } from '../utils.js';
|
||||
import { isToday, isPast, dayOfWeek, eventTitle } from '../utils.js';
|
||||
import { t } from '../i18n.js';
|
||||
|
||||
export function renderQuarter(container, currentDate, events, onDayClick, onEventClick, weekStartDay = 'monday') {
|
||||
@@ -64,7 +64,7 @@ export function renderQuarter(container, currentDate, events, onDayClick, onEven
|
||||
const dots = cellEvs.slice(0, 3).map(ev => {
|
||||
const color = ev.color || ev.calendarColor || '#4285f4';
|
||||
const pastCls = isPast(ev) ? 'past' : '';
|
||||
return `<span class="qtr-dot ${pastCls}" style="background:${color}" title="${escAttr(ev.title)}" data-id="${ev.id}" data-url="${escAttr(ev.url || '')}"></span>`;
|
||||
return `<span class="qtr-dot ${pastCls}" style="background:${color}" title="${escAttr(eventTitle(ev))}" data-id="${ev.id}" data-url="${escAttr(ev.url || '')}"></span>`;
|
||||
}).join('');
|
||||
const moreDot = cellEvs.length > 3
|
||||
? `<span class="qtr-dot-more">+${cellEvs.length - 3}</span>`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isToday, isPast, dayOfWeek, weekStart, getISOWeekNumber } from '../utils.js';
|
||||
import { isToday, isPast, dayOfWeek, weekStart, getISOWeekNumber, eventTitle, birthdayIconSvg } from '../utils.js';
|
||||
import { t } from '../i18n.js';
|
||||
|
||||
export function renderWeek(container, currentDate, events, onSlotClick, onEventClick, isSingleDay = false, weekStartDay = 'monday', hourH = 60) {
|
||||
@@ -77,11 +77,12 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC
|
||||
const cL = evStart < firstDay ? 'continues-left' : '';
|
||||
const cR = (ev.allDay ? evEnd > lastDay : evEnd > lastDayMidnight) ? 'continues-right' : '';
|
||||
const label = isMultiTimed && isSameDay(new Date(ev.start), days[colStart])
|
||||
? `${fmtTime(new Date(ev.start))} ${ev.title}`
|
||||
: ev.title;
|
||||
? `${fmtTime(new Date(ev.start))} ${eventTitle(ev)}`
|
||||
: eventTitle(ev);
|
||||
const icon = ev.is_birthday ? birthdayIconSvg() : '';
|
||||
return `<div class="allday-span ${pastCls} ${multiCls} ${cL} ${cR}"
|
||||
style="left:calc(${left.toFixed(2)}% + 1px);width:calc(${width.toFixed(2)}% - 2px);top:${top}px;background:${color};color:#fff"
|
||||
data-id="${ev.id}" data-url="${escAttr(ev.url)}" title="${escAttr(ev.title)}">${escHtml(label)}</div>`;
|
||||
data-id="${ev.id}" data-url="${escAttr(ev.url)}" title="${escAttr(eventTitle(ev))}">${icon}${escHtml(label)}</div>`;
|
||||
}).join('');
|
||||
|
||||
const alldayBgCols = days.map(day =>
|
||||
@@ -129,11 +130,12 @@ export function renderWeek(container, currentDate, events, onSlotClick, onEventC
|
||||
const isShort = height < 34;
|
||||
const shortCls = isShort ? 'short' : '';
|
||||
const locHtml = (!isShort && ev.location) ? `<div class="ev-loc">${escHtml(ev.location)}</div>` : '';
|
||||
const icon = ev.is_birthday ? birthdayIconSvg() : '';
|
||||
return `<div class="timed-event ${pastCls} ${shortCls}"
|
||||
style="top:${top}px;height:${height}px;left:${left}%;width:${width}%;background:${color};color:#fff"
|
||||
data-id="${ev.id}" data-url="${escAttr(ev.url)}" title="${escAttr(ev.title)}">
|
||||
data-id="${ev.id}" data-url="${escAttr(ev.url)}" title="${escAttr(eventTitle(ev))}">
|
||||
<div class="ev-time">${startStr}</div>
|
||||
<div class="ev-title">${escHtml(ev.title)}</div>
|
||||
<div class="ev-title">${icon}${escHtml(eventTitle(ev))}</div>
|
||||
${locHtml}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
Reference in New Issue
Block a user