feat(web): multiline composer — Enter adds a newline, arrow sends
Stops fat-fingered early sends. The single-line input is now an auto-growing textarea (Claude-app style): Enter inserts a newline and expands the box (up to a cap, then it scrolls); you tap the ↑ arrow to send. ⌘/Ctrl+Enter still sends from a hardware keyboard. Send button is now a round arrow; box resets to one line after sending. Mobile button is a 42-44px touch target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,8 +116,8 @@
|
|||||||
|
|
||||||
<!-- Input box -->
|
<!-- Input box -->
|
||||||
<div id="input">
|
<div id="input">
|
||||||
<input id="userInput" type="text" placeholder="Type a message..." autofocus />
|
<textarea id="userInput" rows="1" placeholder="Type a message…" autofocus></textarea>
|
||||||
<button id="sendBtn">Send</button>
|
<button id="sendBtn" aria-label="Send" title="Send (or ⌘/Ctrl+Enter)">↑</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom tab bar (mobile only; hides while the keyboard is open) -->
|
<!-- Bottom tab bar (mobile only; hides while the keyboard is open) -->
|
||||||
@@ -287,6 +287,8 @@
|
|||||||
if (!msg) return;
|
if (!msg) return;
|
||||||
inputEl.value = "";
|
inputEl.value = "";
|
||||||
|
|
||||||
|
autoGrow(inputEl); // collapse the box back to one line after clearing
|
||||||
|
|
||||||
addMessage("user", msg);
|
addMessage("user", msg);
|
||||||
history.push({ role: "user", content: msg });
|
history.push({ role: "user", content: msg });
|
||||||
await saveSession(); // ✅ persist both user + assistant messages
|
await saveSession(); // ✅ persist both user + assistant messages
|
||||||
@@ -548,6 +550,13 @@
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grow the input textarea to fit its content (up to a cap, then it scrolls).
|
||||||
|
function autoGrow(el) {
|
||||||
|
if (!el) return;
|
||||||
|
el.style.height = "auto";
|
||||||
|
el.style.height = Math.min(el.scrollHeight, 140) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
function addMessage(role, text, autoScroll = true) {
|
function addMessage(role, text, autoScroll = true) {
|
||||||
const messagesEl = document.getElementById("messages");
|
const messagesEl = document.getElementById("messages");
|
||||||
|
|
||||||
@@ -1004,11 +1013,15 @@
|
|||||||
checkHealth();
|
checkHealth();
|
||||||
setInterval(checkHealth, 10000);
|
setInterval(checkHealth, 10000);
|
||||||
|
|
||||||
// Input events
|
// Input events. Enter inserts a newline and grows the box (like the Claude
|
||||||
|
// app) — you tap the arrow to send. ⌘/Ctrl+Enter sends from the keyboard.
|
||||||
document.getElementById("sendBtn").addEventListener("click", sendMessage);
|
document.getElementById("sendBtn").addEventListener("click", sendMessage);
|
||||||
document.getElementById("userInput").addEventListener("keypress", e => {
|
const inputBox = document.getElementById("userInput");
|
||||||
if (e.key === "Enter") sendMessage();
|
inputBox.addEventListener("input", () => autoGrow(inputBox));
|
||||||
|
inputBox.addEventListener("keydown", e => {
|
||||||
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); sendMessage(); }
|
||||||
});
|
});
|
||||||
|
autoGrow(inputBox);
|
||||||
|
|
||||||
// ========== THINKING STREAM INTEGRATION ==========
|
// ========== THINKING STREAM INTEGRATION ==========
|
||||||
const thinkingPanel = document.getElementById("thinkingPanel");
|
const thinkingPanel = document.getElementById("thinkingPanel");
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ button:hover, select:hover {
|
|||||||
/* Input bar */
|
/* Input bar */
|
||||||
#input {
|
#input {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: flex-end; /* arrow stays at the bottom as the textarea grows */
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
background: var(--bg-elev);
|
background: var(--bg-elev);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -208,9 +209,14 @@ button:hover, select:hover {
|
|||||||
background: var(--bg-line);
|
background: var(--bg-line);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: 16px;
|
||||||
padding: 9px 12px;
|
padding: 9px 12px;
|
||||||
font-family: var(--font-console);
|
font-family: var(--font-console);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
resize: none; /* grown programmatically, not by the drag handle */
|
||||||
|
max-height: 140px;
|
||||||
|
overflow-y: auto;
|
||||||
transition: border-color .15s, box-shadow .15s;
|
transition: border-color .15s, box-shadow .15s;
|
||||||
}
|
}
|
||||||
#userInput::placeholder { color: var(--text-fade); }
|
#userInput::placeholder { color: var(--text-fade); }
|
||||||
@@ -221,6 +227,16 @@ button:hover, select:hover {
|
|||||||
}
|
}
|
||||||
#sendBtn {
|
#sendBtn {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
flex: none;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1;
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: #0a0a0a;
|
color: #0a0a0a;
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
@@ -936,12 +952,14 @@ select:hover {
|
|||||||
|
|
||||||
#userInput {
|
#userInput {
|
||||||
font-size: 16px; /* Prevents zoom on iOS */
|
font-size: 16px; /* Prevents zoom on iOS */
|
||||||
padding: 12px;
|
padding: 11px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendBtn {
|
#sendBtn {
|
||||||
padding: 12px 16px;
|
width: 44px; /* comfortable touch target */
|
||||||
font-size: 1rem;
|
height: 44px;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal - full width on mobile */
|
/* Modal - full width on mobile */
|
||||||
@@ -1040,12 +1058,14 @@ select:hover {
|
|||||||
|
|
||||||
#userInput {
|
#userInput {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 10px;
|
padding: 10px 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendBtn {
|
#sendBtn {
|
||||||
padding: 10px 14px;
|
width: 42px;
|
||||||
font-size: 0.95rem;
|
height: 42px;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header h3 {
|
.modal-header h3 {
|
||||||
|
|||||||
Reference in New Issue
Block a user