hermes settings: scrollable modal + Address book vCard import
Two Settings-pane fixes: 1. .modal was clipped on short viewports (mobile landscape, small laptop screens): place-items:center + fixed max-width with no max-height meant the content below Phone discovery / Backup fell off the bottom. Add max-height: calc(100vh - 24px) + overflow-y: auto so the whole thing scrolls inside the backdrop. This is why the user couldn't see Phone discovery on mobile. 2. Address book section between Phone discovery and Backup: 'Import contacts from vCard' triggers a hidden file picker (<input type=file accept=.vcf>) that reads the file client-side, extracts FN + TEL per VCARD block, hands the phone list to findByPhones, and auto-adds the matched Hermes identities as contacts with the vCard name as nickname. Lives in both the Theseus addon panel and the hermes.x web messenger. Phone discovery was already in Settings — the modal clipping just hid it on smaller viewports.
This commit is contained in:
parent
5984d39253
commit
6b4654231b
1 changed files with 53 additions and 1 deletions
|
|
@ -144,7 +144,8 @@
|
|||
display: none; place-items: center; z-index: 100; padding: 12px; }
|
||||
.modal-bg.show { display: grid; }
|
||||
.modal { background: var(--panel); border: 1px solid var(--line);
|
||||
border-radius: 12px; padding: 16px; width: 100%; max-width: 320px; }
|
||||
border-radius: 12px; padding: 16px; width: 100%; max-width: 320px;
|
||||
max-height: calc(100vh - 24px); overflow-y: auto; -webkit-overflow-scrolling: touch; }
|
||||
.modal h3 { margin: 0 0 10px; font-size: 14px; }
|
||||
.modal input { font: inherit; font-size: 12.5px; width: 100%; padding: 8px 10px;
|
||||
background: var(--panel-2); color: var(--ink);
|
||||
|
|
@ -216,6 +217,15 @@
|
|||
We hash the number locally and publish a Nostr claim for the hash. Anyone who <i>already knows</i> your number can find you — same tradeoff as number-based messengers. Unlink any time.
|
||||
</div>
|
||||
|
||||
<div style="font-size:11.5px;color:var(--mut);margin-bottom:4px">Address book</div>
|
||||
<input id="vcardFileInput" type="file" accept=".vcf,text/vcard,text/x-vcard" style="display:none">
|
||||
<button class="secondary" id="importVcardBtn" style="font:inherit;font-size:12.5px;background:var(--panel-2);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:8px 12px;cursor:pointer;width:100%;margin-bottom:6px">Import contacts from vCard</button>
|
||||
<div class="err" id="vcardErr" style="color:var(--err);font-size:11.5px;min-height:1em"></div>
|
||||
<div id="vcardResult" style="color:var(--dim);font-size:11px;min-height:1em"></div>
|
||||
<div style="color:var(--dim);font-size:11px;margin-bottom:14px">
|
||||
Open a .vcf export from your contacts. We parse it locally, look up any linked phone numbers against Hermes, and show the matches to add — nothing uploaded.
|
||||
</div>
|
||||
|
||||
<div style="font-size:11.5px;color:var(--mut);margin-bottom:4px">Backup</div>
|
||||
<div id="backupBox">
|
||||
<div id="backupNone" style="font-size:12px;color:var(--mut);margin-bottom:12px">No seed phrase stored for this account.<br><span style="color:var(--dim);font-size:11px">(created-elsewhere / vault / mnemonic signins don't retain the words)</span></div>
|
||||
|
|
@ -774,6 +784,48 @@ $("openSettingsBtn").addEventListener("click", async () => {
|
|||
await openSettings();
|
||||
});
|
||||
$("settingsClose").addEventListener("click", () => $("settingsModal").classList.remove("show"));
|
||||
// --- vCard import (Address book) ---
|
||||
$("importVcardBtn").addEventListener("click", () => $("vcardFileInput").click());
|
||||
$("vcardFileInput").addEventListener("change", async () => {
|
||||
const f = $("vcardFileInput").files && $("vcardFileInput").files[0];
|
||||
if (!f) return;
|
||||
err($("vcardErr"), "");
|
||||
$("vcardResult").textContent = "Reading " + f.name + "\u2026";
|
||||
try {
|
||||
const text = await f.text();
|
||||
// Minimal RFC 6350 subset: pull FN + TEL per VCARD block.
|
||||
const entries = [];
|
||||
for (const block of text.split(/BEGIN:VCARD/i).slice(1)) {
|
||||
const body = block.split(/END:VCARD/i)[0] || "";
|
||||
const fn = (body.match(/^FN(?:;[^:]*)?:(.+)$/mi) || [])[1];
|
||||
const tel = (body.match(/^TEL(?:;[^:]*)?:(.+)$/mi) || [])[1];
|
||||
if (!tel) continue;
|
||||
entries.push({ name: (fn || "").trim(), phone: tel.trim() });
|
||||
}
|
||||
if (!entries.length) { $("vcardResult").textContent = "No phone numbers in that file."; return; }
|
||||
$("vcardResult").textContent = "Looking up " + entries.length + " number(s) on Hermes\u2026";
|
||||
const r = await bridge.invoke("findByPhones", { phones: entries.map(e => e.phone) });
|
||||
if (!r || !r.ok) throw new Error((r && r.err) || "lookup failed");
|
||||
const found = r.matches || [];
|
||||
if (!found.length) { $("vcardResult").textContent = "No matches among your contacts yet."; return; }
|
||||
let added = 0;
|
||||
for (const m of found) {
|
||||
const label = (entries.find(e => e.phone.replace(/\D+/g, "") === m.phone.replace(/\D+/g, "")) || {}).name || m.phone;
|
||||
try {
|
||||
const a = await bridge.invoke("contactsAdd", { name: m.pkHex, nickname: label });
|
||||
if (a && a.ok) added++;
|
||||
} catch {}
|
||||
}
|
||||
$("vcardResult").textContent = "Added " + added + " of " + found.length + " matches. Refresh the chat list.";
|
||||
try { const r2 = await bridge.invoke("contactsList"); if (r2 && r2.ok) { state.contacts = r2.contacts || []; renderContacts(); } } catch {}
|
||||
} catch (e) {
|
||||
err($("vcardErr"), (e && e.message) || String(e));
|
||||
$("vcardResult").textContent = "";
|
||||
} finally {
|
||||
$("vcardFileInput").value = "";
|
||||
}
|
||||
});
|
||||
|
||||
$("settingsModal").addEventListener("click", (e) => { if (e.target === $("settingsModal")) $("settingsModal").classList.remove("show"); });
|
||||
|
||||
// Display-mode picker. Persisted per-profile via silentmode.storage (the
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue