diff --git a/contacts.js b/contacts.js
index 8f9d255..f4e89f0 100644
--- a/contacts.js
+++ b/contacts.js
@@ -28,7 +28,7 @@ function clean(b, old = {}) {
const c = {
id: old.id || crypto.randomBytes(6).toString('hex'), name: str(b.name, 120), org: str(b.org, 120),
emails: list(b.emails, 10, v => EMAIL.test(v)), phones: list(b.phones, 10), address: str(b.address, 300),
- birthday: cleanBirthday(b.birthday), notes: str(b.notes, 2000), updated: Date.now(),
+ birthday: cleanBirthday(b.birthday), anniversary: cleanBirthday(b.anniversary), gifts: str(b.gifts, 2000), notes: str(b.notes, 2000), updated: Date.now(),
};
c.emails.forEach(e => { e.value = e.value.toLowerCase(); });
if (!c.name) c.name = c.emails[0] ? c.emails[0].value.split('@')[0] : c.org;
@@ -57,6 +57,7 @@ function parseVcf(text) {
else if (name === 'TEL') cur.phones.push({ label: label.toLowerCase(), value: unesc(val).replace(/^tel:/i, '') });
else if (name === 'ADR') cur.address = val.split(';').map(unesc).filter(Boolean).join(', ');
else if (name === 'BDAY') cur.birthday = val.replace(/T.*$/, '');
+ else if (name === 'ANNIVERSARY' || name === 'X-ANNIVERSARY') cur.anniversary = val.replace(/T.*$/, '');
else if (name === 'NOTE') cur.notes = unesc(val);
}
return out.map(c => ({ ...c, name: c.name || c.n || '' }));
@@ -66,19 +67,22 @@ function toVcf(l) {
...c.emails.map(e => `EMAIL;TYPE=${/^(home|work)$/.test(e.label) ? e.label.toUpperCase() : 'INTERNET'}:${esc(e.value)}`),
...c.phones.map(p => `TEL;TYPE=${/^(home|work|cell|fax)$/.test(p.label) ? p.label.toUpperCase() : 'VOICE'}:${esc(p.value)}`),
c.address && 'ADR:;;' + esc(c.address) + ';;;;', c.birthday && 'BDAY:' + (c.birthday.startsWith('--') ? c.birthday.replace(/-/g, '').replace(/^/, '--') : c.birthday.replace(/-/g, '')),
+ c.anniversary && 'X-ANNIVERSARY:' + c.anniversary.replace(/-/g, '').replace(/^/, c.anniversary.startsWith('--') ? '--' : ''),
c.notes && 'NOTE:' + esc(c.notes), 'END:VCARD'].filter(Boolean).join('\r\n')).join('\r\n') + (l.length ? '\r\n' : '');
}
-/** Birthdays as all-day calendar events inside `win` ({ fd, td }: local dates, inclusive) for the calendar view. Repeats every year. */
+/** Birthdays and anniversaries as all-day calendar events inside `win` ({ fd, td }: local dates, inclusive) for the calendar view. Each repeats every year. */
function birthdayEvents(win, uid) {
const out = [], y0 = +win.fd.slice(0, 4), y1 = +win.td.slice(0, 4);
for (const c of load(uid)) {
- if (!c.birthday) continue;
- const [mo, d] = c.birthday.startsWith('--') ? [c.birthday.slice(2, 4), c.birthday.slice(5, 7)] : [c.birthday.slice(5, 7), c.birthday.slice(8, 10)], born = c.birthday.startsWith('--') ? 0 : +c.birthday.slice(0, 4);
- for (let y = y0; y <= y1; y++) {
- const day = `${y}-${mo}-${d}`; if (!real(y, +mo, +d) || day < win.fd || day > win.td) continue;
- const end = new Date(Date.UTC(y, +mo - 1, +d + 1)).toISOString().slice(0, 10);
- out.push({ title: `${c.name}'s birthday${born && y > born ? ` (${y - born})` : ''}`, location: '', allDay: true, start: day, end, uid: 'birthday:' + c.id + ':' + y, series: false, cal: 'Birthdays', calId: 'contacts', color: '#d9578a', editable: false, contactId: c.id });
+ for (const [field, label, color] of [['birthday', "'s birthday", '#d9578a'], ['anniversary', "'s anniversary", '#c9a227']]) {
+ const v = c[field]; if (!v) continue;
+ const [mo, d] = v.startsWith('--') ? [v.slice(2, 4), v.slice(5, 7)] : [v.slice(5, 7), v.slice(8, 10)], born = v.startsWith('--') ? 0 : +v.slice(0, 4);
+ for (let y = y0; y <= y1; y++) {
+ const day = `${y}-${mo}-${d}`; if (!real(y, +mo, +d) || day < win.fd || day > win.td) continue;
+ const end = new Date(Date.UTC(y, +mo - 1, +d + 1)).toISOString().slice(0, 10);
+ out.push({ title: `${c.name}${label}${born && y > born ? ` (${y - born})` : ''}`, location: '', allDay: true, start: day, end, uid: field + ':' + c.id + ':' + y, series: false, cal: 'Birthdays', calId: 'contacts', color, editable: false, contactId: c.id });
+ }
}
}
return out;
diff --git a/public/contacts-view.js b/public/contacts-view.js
index 828320b..6d11057 100644
--- a/public/contacts-view.js
+++ b/public/contacts-view.js
@@ -51,7 +51,7 @@ function ctShow(c) {
${c.emails.map(e => line(e.label, mailOn ? `${esc(e.value)}` : esc(e.value))).join('')}
${c.phones.map(p => line(p.label, `${esc(p.value)}`)).join('')}
${line('address', c.address ? `${esc(c.address)}` : '')}
- ${line('birthday', esc(ctBday(c.birthday)))}${line('notes', esc(c.notes).replace(/\n/g, '
'))}
+ ${line('birthday', esc(ctBday(c.birthday)))}${line('anniversary', esc(ctBday(c.anniversary)))}${line('gift ideas', esc(c.gifts || '').replace(/\n/g, '
'))}${line('notes', esc(c.notes).replace(/\n/g, '
'))}