Signal conversations open inside Chat instead of switching to the Signal screen

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-27 14:01:57 -04:00
1 parent 78fd07bd0d
commit 7a07ac5e11
5 files changed
+26 -10

No files matched your search

+10 -4
View File
@@ -7,6 +7,8 @@ const sgTime = ts => { const d = new Date(ts), n = new Date(); return d.toDateSt
const sgInitial = n => (String(n).replace(/^\+/, '').trim()[0] || '?').toUpperCase();
const sgColor = id => `hsl(${[...String(id)].reduce((a, c) => (a * 31 + c.charCodeAt(0)) % 360, 7)} 45% 42%)`;
const sgOnView = () => location.hash.startsWith('#/signal');
/** Where a Signal conversation opens: inside Chat when the person has both (the Chat list shows Signal chats too), otherwise the Signal screen. */
const sgHref = id => typeof sgInChat === 'function' && sgInChat() ? '#/chat/' + encodeURIComponent('sg:' + id) : '#/signal/' + encodeURIComponent(id);
const sgMounted = () => !!sg.root && document.contains(sg.root);
async function sgStart() { // keep the unread count and live updates running whatever screen is open
if (!allowed('signal') || sg.es) return;
@@ -38,7 +40,7 @@ function sgAlert(ev) { // a message from someone else arrived and you are not lo
if (a.sound) sgChime();
if (a.notify && 'Notification' in window && Notification.permission === 'granted' && (document.hidden || !document.hasFocus())) {
try { const n = new Notification(ev.name || 'Signal', { body: ev.preview || 'New message', tag: 'sg:' + ev.conv, icon: '/icons/icon-192.png', silent: true });
n.onclick = () => { window.focus(); n.close(); sg.cur = ev.conv; sgSaveDock(); if (sgOnView()) location.hash = '#/signal/' + encodeURIComponent(ev.conv); else { sg.dockOpen = true; sgSaveDock(); sgDockSync(activeTab); } }; } catch { /* the browser refused */ }
n.onclick = () => { window.focus(); n.close(); sg.cur = ev.conv; sgSaveDock(); if (sgOnView() || sg.inChat) location.hash = sgHref(ev.conv); else { sg.dockOpen = true; sgSaveDock(); sgDockSync(activeTab); } }; } catch { /* the browser refused */ }
}
}
let sgEvT = null;
@@ -49,6 +51,9 @@ function sgEvent(ev) {
if (!looking) { toast('Signal · ' + (ev.name || '') + ': ' + (ev.preview || '')); sgAlert(ev); }
}
if (ev.t === 'gone') sgGone(ev.conv, true);
if (sg.inChat) { // a Signal conversation is open inside Chat: refresh it and the combined list, never turn the Chat screen into the Signal screen
if (!sgMounted()) return; clearTimeout(sgEvT); sgEvT = setTimeout(() => { if (sg.cur && ev.t !== 'status') sgDrawThread(false); if (typeof chSigChanged === 'function') chSigChanged(); }, 150); return;
}
if (!sgMounted()) return;
clearTimeout(sgEvT); sgEvT = setTimeout(() => { if (ev.t === 'status') return sgMount(sg.root, sg.dock); sgRefresh(); }, 150);
}
@@ -57,6 +62,7 @@ function sgEvent(ev) {
function viewSignal(args = []) { sg.cur = args[0] || null; sgDockHide(); return sgMount($('#view'), false); }
// ... and the same screens in a panel that floats over every other screen.
async function sgMount(root, dock) {
sg.inChat = false; // (a full Signal screen or the floating panel; Chat sets this itself when it shows a Signal conversation)
sg.root = root; sg.dock = dock; const cls = dock ? 'sgdockin' : 'wrap sgwrap', title = dock ? '' : `<h1>Signal</h1>${typeof sgInChat === 'function' && sgInChat() ? `<p class="sub" style="margin:0 0 8px"><a href="#/chat" style="color:var(--accent)">‹ Back to Chat</a> · your Signal chats are also listed there</p>` : ''}`;
root.innerHTML = `<div class="${cls}">${title}<p class="empty">Loading…</p></div>`;
let st; try { st = await sgApi('/status'); } catch (e) { root.innerHTML = `<div class="${cls}">${title}<p class="err">${esc(e.message)}</p></div>`; return; }
@@ -114,7 +120,7 @@ async function sgDrawThread(first) {
if (sg.cur !== id || !sgq('#sgm')) return; sg.names = r.names || {};
const c = r.conv, old = box.querySelector('.sgmsgs'), stick = !old || old.scrollHeight - old.scrollTop - old.clientHeight < 80;
if (!box.querySelector('.sgbar')) {
box.innerHTML = `<div class="sgbar">${sg.dock ? `<button type="button" class="ibtn sgback" id="sgbk" aria-label="Back to chats">${icon('back')}</button>` : `<a class="ibtn sgback" href="#/signal" aria-label="Back to chats">${icon('back')}</a>`}<span class="sgav" style="background:${sgColor(c.id)}">${c.type === 'group' ? icon('chat') : esc(sgInitial(c.name || c.id))}</span><span class="grow"><b id="sgtitle"></b><small id="sgsub"></small></span><button type="button" class="ibtn" id="sgmore" aria-label="Chat options" title="Chat options">${icon('more')}</button></div>
box.innerHTML = `<div class="sgbar">${sg.dock ? `<button type="button" class="ibtn sgback" id="sgbk" aria-label="Back to chats">${icon('back')}</button>` : `<a class="ibtn sgback" href="${sg.inChat ? '#/chat' : '#/signal'}" aria-label="Back to chats">${icon('back')}</a>`}<span class="sgav" style="background:${sgColor(c.id)}">${c.type === 'group' ? icon('chat') : esc(sgInitial(c.name || c.id))}</span><span class="grow"><b id="sgtitle"></b><small id="sgsub"></small></span><button type="button" class="ibtn" id="sgmore" aria-label="Chat options" title="Chat options">${icon('more')}</button></div>
<div class="sgmsgs" id="sgmsgs"></div><div id="sgrep" class="sgrep" hidden></div><div id="sgtray" class="sgtray" hidden></div>
<form class="sgc" id="sgform"><button type="button" class="ibtn" id="sgat" aria-label="Attach a file">${icon('clip')}</button><input type="file" id="sgfile" multiple hidden>
<textarea id="sgtext" rows="1" placeholder="Message" enterkeyhint="send"></textarea><button class="ibtn sgsend" id="sgsend" aria-label="Send">${icon('send')}</button></form>`;
@@ -186,7 +192,7 @@ function sgChatMenu(id) {
}
function sgGone(id, fromServer) {
sg.convs = sg.convs.filter(x => x.id !== id);
if (sg.cur === id) { sg.cur = null; sgSaveDock(); if (sg.dock && sgMounted()) sgOpen(null); else if (sgOnView()) location.hash = '#/signal'; }
if (sg.cur === id) { sg.cur = null; sgSaveDock(); if (sg.dock && sgMounted()) sgOpen(null); else if (sg.inChat) { location.hash = '#/chat'; } else if (sgOnView()) location.hash = '#/signal'; }
if (!fromServer || sgMounted()) sgRefresh();
}
function sgEditContact(c, after) {
@@ -205,7 +211,7 @@ async function sgNewChat() {
const input = el.querySelector('#sgnn'), draw = () => { const q = input.value.toLowerCase().trim(); el.querySelector('#sgcl').innerHTML = contacts.filter(c => !q || (c.name || '').toLowerCase().includes(q) || c.number.includes(q)).slice(0, 60).map(c => `<div class="row it"><button type="button" class="rmain" data-n="${esc(c.number)}"><span class="sgav" style="background:${sgColor(c.number)}">${esc(sgInitial(c.name || c.number))}</span><span class="grow"><b>${esc(c.name || c.number)}</b><small>${esc(c.number)}</small></span></button><button type="button" class="ibtn" data-e="${esc(c.number)}" aria-label="Edit ${esc(c.name || c.number)}" title="Edit contact">${icon('edit')}</button></div>`).join('');
el.querySelectorAll('[data-n]').forEach(b => b.onclick = () => go(b.dataset.n));
el.querySelectorAll('[data-e]').forEach(b => b.onclick = () => { const k = contacts.find(x => x.number === b.dataset.e); sgEditContact(k, async () => { try { contacts = (await sgApi('/contacts')).contacts; } catch {} }); }); };
const go = async n => { try { const r = await sgPost('/new', { number: n, name: el.querySelector('#sgnm').value }); closeSheet(); if (sg.dock) { await sgRefresh(); sgOpen(r.conv.id); } else location.hash = '#/signal/' + encodeURIComponent(r.conv.id); } catch (e) { el.querySelector('#sgne').textContent = e.message; } };
const go = async n => { try { const r = await sgPost('/new', { number: n, name: el.querySelector('#sgnm').value }); closeSheet(); if (sg.dock) { await sgRefresh(); sgOpen(r.conv.id); } else location.hash = sgHref(r.conv.id); } catch (e) { el.querySelector('#sgne').textContent = e.message; } };
input.oninput = draw; draw(); el.querySelector('#sgnf').onsubmit = e => { e.preventDefault(); go(input.value); }; setTimeout(() => input.focus(), 60);
}
function sgLinkScreen(root, dock) {