relay-companion 0.1.34 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/overlay/inbox.html +57 -1
- package/overlay/main.cjs +14 -2
- package/overlay/preload.cjs +1 -0
- package/package.json +1 -1
- package/src/client.js +4 -0
package/overlay/inbox.html
CHANGED
|
@@ -452,6 +452,14 @@
|
|
|
452
452
|
.cv-input::placeholder { color:var(--muted-3); }
|
|
453
453
|
.cv-input:focus { border-color:rgba(48,85,102,.42); box-shadow:0 0 0 3px rgba(48,85,102,.08); }
|
|
454
454
|
.cv-form-actions { display:flex; align-items:center; justify-content:flex-end; gap:14px; margin-top:1px; }
|
|
455
|
+
.cv-delete {
|
|
456
|
+
appearance:none; border:0; background:transparent; color:#b4332a; font-family:var(--sans);
|
|
457
|
+
font-size:12.5px; font-weight:600; cursor:pointer; transition:opacity .15s var(--settle), color .15s var(--settle);
|
|
458
|
+
margin-right:auto; padding:0;
|
|
459
|
+
}
|
|
460
|
+
.cv-delete:hover { opacity:.72; }
|
|
461
|
+
.cv-delete.armed { color:#8f261f; }
|
|
462
|
+
.cv-delete:disabled { opacity:.4; cursor:default; }
|
|
455
463
|
.cv-cancel { appearance:none; border:0; background:transparent; color:var(--muted); font-family:var(--sans); font-size:12.5px; font-weight:500; cursor:pointer; transition:color .15s var(--settle); }
|
|
456
464
|
.cv-cancel:hover { color:var(--ink-2); }
|
|
457
465
|
.cv-save {
|
|
@@ -601,6 +609,7 @@
|
|
|
601
609
|
<div class="cv-hint" id="cvHint"></div>
|
|
602
610
|
<div class="cv-error" id="cvError"></div>
|
|
603
611
|
<div class="cv-form-actions">
|
|
612
|
+
<button class="cv-delete gone" type="button" id="cvDelete">Delete</button>
|
|
604
613
|
<button class="cv-cancel" type="button" id="cvCancel">Cancel</button>
|
|
605
614
|
<button class="cv-save" type="submit" id="cvSave">Save</button>
|
|
606
615
|
</div>
|
|
@@ -643,6 +652,7 @@
|
|
|
643
652
|
const cvErrorEl = document.getElementById("cvError");
|
|
644
653
|
const cvSaveEl = document.getElementById("cvSave");
|
|
645
654
|
const cvCancelEl = document.getElementById("cvCancel");
|
|
655
|
+
const cvDeleteEl = document.getElementById("cvDelete");
|
|
646
656
|
|
|
647
657
|
const REDUCED = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
648
658
|
const EXPANDED = { w: 344, h: 524 };
|
|
@@ -1848,6 +1858,8 @@
|
|
|
1848
1858
|
const COMMON_DOMAINS = ["gmail.com","outlook.com","hotmail.com","yahoo.com","icloud.com","me.com","proton.me","protonmail.com","live.com","aol.com"];
|
|
1849
1859
|
let cvEmailsList = []; // committed, lowercased, deduped addresses (the chips)
|
|
1850
1860
|
let cvSuggIdx = 0; // highlighted suggestion row
|
|
1861
|
+
let cvDeleteArmed = false;
|
|
1862
|
+
let cvDeleteTimer = null;
|
|
1851
1863
|
function isValidEmail(s) { return EMAIL_RE.test(String(s).trim()); }
|
|
1852
1864
|
function cvSuggestions() {
|
|
1853
1865
|
const draft = cvEmailEl.value;
|
|
@@ -1907,6 +1919,21 @@
|
|
|
1907
1919
|
cvEmailsList = cvEmailsList.filter((x) => x !== e);
|
|
1908
1920
|
renderEmailChips();
|
|
1909
1921
|
}
|
|
1922
|
+
function editingContactId() {
|
|
1923
|
+
return cvEditing && (cvEditing.id || cvEditing.contactId) ? String(cvEditing.id || cvEditing.contactId) : "";
|
|
1924
|
+
}
|
|
1925
|
+
function resetDeleteButton() {
|
|
1926
|
+
cvDeleteArmed = false;
|
|
1927
|
+
if (cvDeleteTimer) { clearTimeout(cvDeleteTimer); cvDeleteTimer = null; }
|
|
1928
|
+
cvDeleteEl.textContent = "Delete";
|
|
1929
|
+
cvDeleteEl.classList.remove("armed");
|
|
1930
|
+
cvDeleteEl.disabled = false;
|
|
1931
|
+
}
|
|
1932
|
+
function syncDeleteButton() {
|
|
1933
|
+
resetDeleteButton();
|
|
1934
|
+
const canDelete = Boolean(editingContactId());
|
|
1935
|
+
cvDeleteEl.classList.toggle("gone", !canDelete);
|
|
1936
|
+
}
|
|
1910
1937
|
cvEmailsEl.addEventListener("mousedown", (e) => {
|
|
1911
1938
|
if (e.target === cvEmailsEl) { e.preventDefault(); cvEmailEl.focus(); }
|
|
1912
1939
|
});
|
|
@@ -1944,6 +1971,7 @@
|
|
|
1944
1971
|
cvErrorEl.textContent = "";
|
|
1945
1972
|
cvSuggestEl.classList.add("hidden"); cvSuggestEl.innerHTML = "";
|
|
1946
1973
|
renderEmailChips();
|
|
1974
|
+
syncDeleteButton();
|
|
1947
1975
|
cvFormEl.classList.remove("hidden");
|
|
1948
1976
|
if (window.relay.setFocusable) window.relay.setFocusable(true);
|
|
1949
1977
|
setInteractive(true);
|
|
@@ -1957,10 +1985,37 @@
|
|
|
1957
1985
|
cvErrorEl.textContent = "";
|
|
1958
1986
|
cvHintEl.textContent = "";
|
|
1959
1987
|
cvSuggestEl.classList.add("hidden"); cvSuggestEl.innerHTML = "";
|
|
1988
|
+
cvDeleteEl.classList.add("gone");
|
|
1989
|
+
resetDeleteButton();
|
|
1960
1990
|
if (window.relay.setFocusable) window.relay.setFocusable(false);
|
|
1961
1991
|
}
|
|
1962
1992
|
cvAddEl.addEventListener("click", () => openContactForm(null));
|
|
1963
1993
|
cvCancelEl.addEventListener("click", closeContactForm);
|
|
1994
|
+
cvDeleteEl.addEventListener("click", async () => {
|
|
1995
|
+
const contactId = editingContactId();
|
|
1996
|
+
if (!contactId || cvDeleteEl.disabled) return;
|
|
1997
|
+
if (!cvDeleteArmed) {
|
|
1998
|
+
cvDeleteArmed = true;
|
|
1999
|
+
cvDeleteEl.textContent = "Delete?";
|
|
2000
|
+
cvDeleteEl.classList.add("armed");
|
|
2001
|
+
cvDeleteTimer = setTimeout(resetDeleteButton, 3500);
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
cvDeleteEl.disabled = true;
|
|
2005
|
+
cvSaveEl.disabled = true;
|
|
2006
|
+
cvErrorEl.textContent = "";
|
|
2007
|
+
try {
|
|
2008
|
+
const next = await window.relay.contactDelete({ contactId });
|
|
2009
|
+
if (Array.isArray(next)) contactsList = next;
|
|
2010
|
+
closeContactForm();
|
|
2011
|
+
renderContacts();
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
cvErrorEl.textContent = (err && err.message) ? err.message : "Could not delete.";
|
|
2014
|
+
cvDeleteEl.disabled = false;
|
|
2015
|
+
cvSaveEl.disabled = false;
|
|
2016
|
+
resetDeleteButton();
|
|
2017
|
+
}
|
|
2018
|
+
});
|
|
1964
2019
|
cvFormEl.addEventListener("submit", async (e) => {
|
|
1965
2020
|
e.preventDefault();
|
|
1966
2021
|
const name = [cvFirstEl.value, cvLastEl.value].map((s) => s.trim()).filter(Boolean).join(" ");
|
|
@@ -1973,7 +2028,8 @@
|
|
|
1973
2028
|
cvErrorEl.textContent = "";
|
|
1974
2029
|
try {
|
|
1975
2030
|
// Match the website's ContactWrite shape: emails[] is the identity, email = primary.
|
|
1976
|
-
const
|
|
2031
|
+
const contactId = editingContactId();
|
|
2032
|
+
const next = await window.relay.contactSave({ contactId, name, emails, email: emails[0] });
|
|
1977
2033
|
if (Array.isArray(next)) contactsList = next;
|
|
1978
2034
|
closeContactForm();
|
|
1979
2035
|
renderContacts();
|
package/overlay/main.cjs
CHANGED
|
@@ -309,10 +309,11 @@ async function readContacts() {
|
|
|
309
309
|
.map((c) => {
|
|
310
310
|
const emails = Array.isArray(c.emails) ? c.emails.filter(Boolean) : c.email ? [c.email] : [];
|
|
311
311
|
return {
|
|
312
|
-
id: c.id || "",
|
|
312
|
+
id: c.id || c.contactId || "",
|
|
313
313
|
name: c.name || emails[0] || "",
|
|
314
314
|
email: emails[0] || c.email || "",
|
|
315
315
|
emails,
|
|
316
|
+
onRelay: Boolean(c.onRelay),
|
|
316
317
|
updatedAt: c.updatedAt || null,
|
|
317
318
|
};
|
|
318
319
|
})
|
|
@@ -324,6 +325,7 @@ async function readContacts() {
|
|
|
324
325
|
}
|
|
325
326
|
|
|
326
327
|
async function saveContact(input) {
|
|
328
|
+
const contactId = String((input && input.contactId) || "").trim();
|
|
327
329
|
const name = String((input && input.name) || "").trim();
|
|
328
330
|
const emails = Array.isArray(input && input.emails)
|
|
329
331
|
? input.emails.map((e) => String(e || "").trim()).filter(Boolean)
|
|
@@ -333,7 +335,16 @@ async function saveContact(input) {
|
|
|
333
335
|
if (!name) throw new Error("A name is required");
|
|
334
336
|
for (const e of emails) if (!e.includes("@")) throw new Error("That email looks off.");
|
|
335
337
|
const client = await relayClient();
|
|
336
|
-
await client.
|
|
338
|
+
if (contactId) await client.updateContact(contactId, { name, emails, email: emails[0] || "" });
|
|
339
|
+
else await client.upsertContact({ name, emails, email: emails[0] || "" });
|
|
340
|
+
return readContacts();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function deleteContactFromBook(input) {
|
|
344
|
+
const contactId = String((input && input.contactId) || input || "").trim();
|
|
345
|
+
if (!contactId) throw new Error("Missing contact id.");
|
|
346
|
+
const client = await relayClient();
|
|
347
|
+
await client.deleteContact(contactId);
|
|
337
348
|
return readContacts();
|
|
338
349
|
}
|
|
339
350
|
|
|
@@ -1188,6 +1199,7 @@ ipcMain.handle("relay:decline", (_e, taskId, approvalId) => declineShare(taskId,
|
|
|
1188
1199
|
ipcMain.handle("relay:answer", (_e, taskId, messageId, text, packetId) => answerQuestion(taskId, messageId, text, packetId));
|
|
1189
1200
|
ipcMain.handle("relay:contacts", () => readContacts());
|
|
1190
1201
|
ipcMain.handle("relay:contactSave", (_e, input) => saveContact(input));
|
|
1202
|
+
ipcMain.handle("relay:contactDelete", (_e, input) => deleteContactFromBook(input));
|
|
1191
1203
|
|
|
1192
1204
|
// The overlay is normally focusable:false so it never steals keyboard focus from
|
|
1193
1205
|
// Claude/Codex. Text fields (the contact form, quick reply) need focus, so the renderer
|
package/overlay/preload.cjs
CHANGED
|
@@ -32,6 +32,7 @@ contextBridge.exposeInMainWorld("relay", {
|
|
|
32
32
|
// contacts
|
|
33
33
|
contacts: () => ipcRenderer.invoke("relay:contacts"),
|
|
34
34
|
contactSave: (input) => ipcRenderer.invoke("relay:contactSave", input),
|
|
35
|
+
contactDelete: (input) => ipcRenderer.invoke("relay:contactDelete", input),
|
|
35
36
|
|
|
36
37
|
// window plumbing
|
|
37
38
|
setFocusable: (v) => ipcRenderer.send("relay:setFocusable", v),
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -207,6 +207,10 @@ export class RelayClient {
|
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
deleteContact(contactId) {
|
|
211
|
+
return this.#req("DELETE", `/v1/contacts/${encodeURIComponent(contactId)}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
210
214
|
importContacts(contacts, source = "imported") {
|
|
211
215
|
return this.#req("POST", "/v1/contacts/import", { contacts, source });
|
|
212
216
|
}
|