aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-29 07:29:49 -0700
committerAdam Soltys <[email protected]>2026-08-29 07:29:49 -0700
commitb61198fc49e55213ee29a545f7bab3691f43d6b5 (patch)
treed74ca8c23da05125d2bf1ba156e72924f5868ca1 /src/features
parentafff4e068fa327b3420255b5c21c92e5d4683c4a (diff)
downloadcoinosv3-b61198fc49e55213ee29a545f7bab3691f43d6b5.tar.gz
coinosv3-b61198fc49e55213ee29a545f7bab3691f43d6b5.zip
The composer learns the second dimension: a textarea that grows with the text (capped at five lines), Enter still sends, and a newline is one Shift+Enter or Ctrl+J away — bubbles already knew how to show them
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019WboeoetMF3MTmgT7Ew8yi
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js26
1 files changed, 22 insertions, 4 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index ea368ee..8b0f6c9 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -2007,15 +2007,33 @@ export function messagesFeature(ctx) {
}, ui.msgReconnecting ? h('span', { class: 'spinner sm' }) : t('msgReconnect')));
}
+ // Enter sends; Shift+Enter (the textarea's native edit) and Ctrl+J insert
+ // a newline. Height follows the text as it's typed — imperatively, since
+ // typing doesn't re-render — with rows as the re-render fallback.
+ const growComposer = (el) => {
+ el.style.height = 'auto';
+ el.style.height = Math.min(el.scrollHeight, 120) + 'px';
+ };
const composer = (placeholder, onSend, onType, draftKey) =>
h('div', { class: 'col', style: 'gap:6px' },
signerNotice(),
h('div', { class: 'chat-compose' },
- h('input', {
- class: 'grow', type: 'text', id: 'msg-draft', placeholder,
+ h('textarea', {
+ class: 'grow', id: 'msg-draft', placeholder, rows: String(Math.min(5, draftFor(draftKey).split('\n').length)),
value: draftFor(draftKey), maxlength: '2000',
- onInput: (e) => { setDraft(draftKey, e.target.value); if (onType && e.target.value) onType(); },
- onKeydown: (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSend(); } },
+ onInput: (e) => { setDraft(draftKey, e.target.value); growComposer(e.target); if (onType && e.target.value) onType(); },
+ onKeydown: (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSend(); return; }
+ if (e.ctrlKey && (e.key === 'j' || e.key === 'J')) {
+ e.preventDefault();
+ const el = e.target;
+ const { selectionStart: s0, selectionEnd: s1, value } = el;
+ el.value = value.slice(0, s0) + '\n' + value.slice(s1);
+ el.selectionStart = el.selectionEnd = s0 + 1;
+ setDraft(draftKey, el.value);
+ growComposer(el);
+ }
+ },
// Focus summons the keyboard; some browsers resize only after its
// animation, so re-stick once now and once when it has settled.
onFocus: () => { stickToBottom(); setTimeout(stickToBottom, 350); },