Fix New chat button: replace window.prompt with in-app modal
- window.prompt/alert are suppressed in Brave and installed PWAs, so the button appeared to do nothing. Replace with a proper modal (inline error, Enter/Escape, click-outside to close). - Mobile nav: sidebar was fully hidden (button unreachable); now sidebar shows by default and a back button toggles between list and thread. - Bump SW cache v1->v2 so the new shell actually ships past the cache. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
afe4fa08f8
commit
fc3f79379e
3 changed files with 98 additions and 19 deletions
|
|
@ -69,6 +69,7 @@
|
|||
linear-gradient(rgba(11,20,26,.95), rgba(11,20,26,.95)); }
|
||||
.empty { margin: auto; color: var(--muted); }
|
||||
.chat-head { display: flex; gap: 12px; align-items: center; padding: 12px 16px; background: var(--panel); border-bottom: 1px solid #0a0f13; }
|
||||
.back-btn { display: none; background: transparent; color: var(--text); font-size: 26px; line-height: 1; padding: 0 6px; }
|
||||
.thread { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 6px; }
|
||||
.bubble { max-width: 65%; padding: 8px 10px 6px; border-radius: 10px; font-size: 14.5px; line-height: 1.35; position: relative; word-wrap: break-word; }
|
||||
.bubble.in { background: var(--bubble-in); align-self: flex-start; border-top-left-radius: 2px; }
|
||||
|
|
@ -84,10 +85,32 @@
|
|||
.attach { cursor: pointer; font-size: 20px; user-select: none; }
|
||||
.send { background: var(--accent); color: #04140f; font-weight: 600; border-radius: 20px; }
|
||||
|
||||
/* ---- modal ---- */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,.6);
|
||||
display: grid; place-items: center; z-index: 100; padding: 20px;
|
||||
}
|
||||
.modal-card {
|
||||
background: var(--panel); border-radius: 14px; padding: 24px;
|
||||
width: 100%; max-width: 360px; display: flex; flex-direction: column; gap: 14px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,.5);
|
||||
}
|
||||
.modal-title { margin: 0; font-size: 18px; }
|
||||
.modal-input {
|
||||
padding: 12px 14px; border-radius: 10px; border: 1px solid var(--panel-2);
|
||||
background: var(--panel-2); color: var(--text); font-size: 15px;
|
||||
}
|
||||
.modal-input:focus { outline: 2px solid var(--accent); }
|
||||
.modal-err { color: #f15c6d; font-size: 13px; min-height: 16px; }
|
||||
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
|
||||
.modal-actions button { padding: 10px 16px; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.shell { grid-template-columns: 1fr; }
|
||||
.sidebar { display: none; }
|
||||
/* Sidebar shows by default; hide it once a chat is open so the thread is full-width. */
|
||||
.shell.chat-open .sidebar { display: none; }
|
||||
.shell:not(.chat-open) .main { display: none; }
|
||||
.back-btn { display: block; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -149,33 +149,89 @@ async function refreshContacts() {
|
|||
}
|
||||
}
|
||||
|
||||
async function startNewChat() {
|
||||
const username = prompt("Username to message:");
|
||||
if (!username) return;
|
||||
try {
|
||||
const user = await api.lookup(username.trim().toLowerCase());
|
||||
const contact = {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
deviceIds: [],
|
||||
};
|
||||
await contacts.put(contact);
|
||||
await refreshContacts();
|
||||
await openChat(contact);
|
||||
} catch (ex) {
|
||||
alert("Could not find user: " + ex.message);
|
||||
}
|
||||
function startNewChat() {
|
||||
openModal({
|
||||
title: "New chat",
|
||||
placeholder: "Enter a username",
|
||||
action: "Start chat",
|
||||
onSubmit: async (value, setError, close) => {
|
||||
const uname = value.trim().toLowerCase();
|
||||
if (!uname) return setError("Enter a username");
|
||||
if (uname === state.me.username) return setError("That's you 🙂");
|
||||
try {
|
||||
const user = await api.lookup(uname);
|
||||
const contact = {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
deviceIds: [],
|
||||
};
|
||||
await contacts.put(contact);
|
||||
await refreshContacts();
|
||||
close();
|
||||
await openChat(contact);
|
||||
} catch (ex) {
|
||||
setError(ex.message === "not found" ? "No user found: @" + uname : ex.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Lightweight in-app modal (replaces window.prompt, which Brave/PWAs suppress).
|
||||
function openModal({ title, placeholder, action, onSubmit }) {
|
||||
const input = el("input", { className: "modal-input", placeholder, autocomplete: "off" });
|
||||
const err = el("div", { className: "modal-err" });
|
||||
const cancel = el("button", { type: "button", className: "ghost", textContent: "Cancel" });
|
||||
const submit = el("button", { type: "button", className: "primary", textContent: action });
|
||||
const card = el("div", { className: "modal-card" },
|
||||
el("h2", { className: "modal-title", textContent: title }),
|
||||
input,
|
||||
err,
|
||||
el("div", { className: "modal-actions" }, cancel, submit)
|
||||
);
|
||||
const overlay = el("div", { className: "modal-overlay" }, card);
|
||||
document.body.append(overlay);
|
||||
setTimeout(() => input.focus(), 0);
|
||||
|
||||
const close = () => overlay.remove();
|
||||
const setError = (m) => { err.textContent = m; };
|
||||
const go = async () => {
|
||||
submit.disabled = true;
|
||||
setError("");
|
||||
try {
|
||||
await onSubmit(input.value, setError, close);
|
||||
} finally {
|
||||
submit.disabled = false;
|
||||
}
|
||||
};
|
||||
cancel.onclick = close;
|
||||
submit.onclick = go;
|
||||
overlay.onclick = (e) => { if (e.target === overlay) close(); };
|
||||
input.onkeydown = (e) => {
|
||||
if (e.key === "Enter") { e.preventDefault(); go(); }
|
||||
if (e.key === "Escape") close();
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- chat view ----------
|
||||
async function openChat(contact) {
|
||||
state.activeContact = contact;
|
||||
$(".shell")?.classList.add("chat-open");
|
||||
const main = $("#main");
|
||||
main.innerHTML = "";
|
||||
|
||||
const back = el("button", {
|
||||
className: "back-btn",
|
||||
textContent: "‹",
|
||||
title: "Back",
|
||||
onclick: () => {
|
||||
state.activeContact = null;
|
||||
$(".shell")?.classList.remove("chat-open");
|
||||
},
|
||||
});
|
||||
const head = el("div", { className: "chat-head" });
|
||||
head.append(
|
||||
back,
|
||||
el("div", { className: "avatar", textContent: (contact.displayName || contact.username)[0].toUpperCase() }),
|
||||
el("div", {},
|
||||
el("div", { className: "chat-name", textContent: contact.displayName || contact.username }),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Service worker: offline app shell + Web Push wake-up.
|
||||
// It never touches message plaintext — decryption happens only in the page.
|
||||
|
||||
const CACHE = "cipher-v1";
|
||||
const CACHE = "cipher-v2";
|
||||
const SHELL = [
|
||||
"/",
|
||||
"/index.html",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue