Open Library in the book store; podcast topic search and transcript search; Recipes (Mealie) with a shopping list synced to Tasks; Contacts linked to Mail and the Calendar
build-container / build (push) Successful in 7m21s

- Store: Open Library search, books fetched from the Internet Archive (free editions only)
- Podcast: Podcast Index discovery by name and topic (optional key); search what is said across transcripts and play from that moment
- Recipes: browse, import from a link, add ingredients to Mealie's shopping list, mirrored two ways with a Shopping list in Tasks
- Contacts: per-person address book, vCard import/export, recipient suggestions in Mail, birthdays in the Calendar

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-26 08:55:32 -04:00
1 parent 0617664d97
commit 176b1b8169
28 files changed
+799 -46

No files matched your search

+3
View File
@@ -473,6 +473,7 @@ function mailDrawReader() {
<iframe class="mbody" title="Message" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox" referrerpolicy="no-referrer" srcdoc="${esc(mailDoc(m.html, mail.images))}"></iframe>`;
if ($('#mshow')) $('#mshow').onclick = () => { mail.images = true; mailDrawReader(); };
mailFit(rd.querySelector('.mbody'));
if (typeof ctMailHook === 'function') ctMailHook(rd, m);
rd.querySelectorAll('[data-a]').forEach(b => b.onclick = () => mailAction(b.dataset.a));
}
@@ -513,6 +514,8 @@ function mailCompose(o = {}) {
${reply ? '' : `<div class="bar" style="margin:8px 0 0;justify-content:flex-start"><label class="btn ghost" style="cursor:pointer;margin:0">${icon('clip')}Attach<input type="file" id="mfile" multiple hidden></label><small id="mfl" style="color:var(--muted)"></small></div>`}
<div class="bar"><button class="btn ghost" type="button" id="sx" style="flex:1;justify-content:center">Cancel</button><button class="btn" id="msend" style="flex:1;justify-content:center">Send</button></div><div class="err" id="mer"></div></form>`);
el.querySelector('.sheet').classList.add('wide'); el.querySelector('#sx').onclick = closeSheet;
if (o.to && el.querySelector('#mto')) el.querySelector('#mto').value = o.to;
if (typeof ctSuggest === 'function') for (const id of ['#mto', '#mcc2', '#mbcc']) ctSuggest(el.querySelector(id));
const cc = el.querySelector('#mcc'); if (cc) cc.onclick = e => { e.preventDefault(); el.querySelector('#mcb').hidden = false; el.querySelector('#mccw').hidden = true; };
const fi = el.querySelector('#mfile');
const total = () => files.reduce((n, f) => n + f.size, 0);