Add /contacts and /calendar pages; email signature settings; v1.1.12

- /contacts: standalone page with hybrid contact list (submissions + manual entry), inline edit, search, archive
- /calendar: monthly release scheduling calendar reading/writing podcast checklist episode dates
- /email settings: editable signature panel; signature persisted server-side and injected into outgoing emails
- Move contacts out of /admin panel (now links to /contacts route)
- Partial PATCH for contact submissions (name, notes, archived independently)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmert
2026-07-28 10:12:52 -04:00
parent 7670b44d27
commit ce235ba9ef
13 changed files with 1952 additions and 145 deletions
+31
View File
@@ -30,6 +30,7 @@ import {
DOWNLOAD_COUNTS_FILE,
EPISODE_PLAYS_FILE,
ANALYTICS_EVENTS_FILE,
EMAIL_SETTINGS_FILE,
EMPTY_HIT_STATS,
EMPTY_VISITOR_STATS,
DEFAULT_REPLY_TEMPLATES,
@@ -385,6 +386,36 @@ export function loadReplyHistoryFromDisk() {
})
}
// ── Email settings ────────────────────────────────────────────────────────
export function queueEmailSettingsWrite() {
state.emailSettingsWritePromise = state.emailSettingsWritePromise
.then(async () => {
await mkdir(DATA_DIR, { recursive: true })
await writeFile(
EMAIL_SETTINGS_FILE,
JSON.stringify({ settings: state.emailSettings, updatedAt: new Date().toISOString() }, null, 2),
'utf8',
)
})
.catch(err => {
console.error('[email-settings] failed to write settings:', err)
})
}
export function loadEmailSettingsFromDisk() {
return readFile(EMAIL_SETTINGS_FILE, 'utf8')
.then(raw => {
const parsed = JSON.parse(raw)
if (parsed?.settings && typeof parsed.settings === 'object') {
state.emailSettings = { ...state.emailSettings, ...parsed.settings }
}
})
.catch(() => {
// Keep defaults from state initializer
})
}
// ── Podcast checklist ──────────────────────────────────────────────────────
export function queuePodcastChecklistWrite() {