From 491904e04fe3d69aa6b417c266aa8b550ffa0534 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 11 Oct 2026 08:46:47 -0700 Subject: Messages: attach and GIF inside the field, as in the post composer. The DM and room composer still had them as buttons in its row — Adam's long DM to yorgos sat squeezed between a clip, GIF and Send. They now sit at the right of the field's last line (the field keeps room on that side, less with GIF alone), leaving the row as field + Send. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3 --- src/features/messages.js | 73 +++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 35 deletions(-) (limited to 'src/features') diff --git a/src/features/messages.js b/src/features/messages.js index cedd8db..94a5173 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -12535,45 +12535,48 @@ export function messagesFeature(ctx) { signerNotice(), emojiAcStrip(draftKey), h('div', { class: 'chat-compose' }, - onAttach ? h('button', { - class: 'iconbtn chat-clip', title: t('msgAttach'), 'aria-label': t('msgAttach'), - disabled: !!ui.msgUploading, - onClick: () => document.getElementById('msg-file')?.click(), - }, ui.msgUploading ? h('span', { class: 'spinner sm' }) : h('span', { style: 'display:flex', html: CLIP })) : null, onAttach ? h('input', { type: 'file', id: 'msg-file', accept: 'image/*,video/*', style: 'display:none', onChange: (e) => { const f = e.target.files && e.target.files[0]; e.target.value = ''; if (f) onAttach(f); }, }) : null, - h('coinos-text', { - class: 'grow', id: 'msg-draft', placeholder, - value: draftFor(draftKey), - onMedia: onAttach ? (f) => onAttach(f) : null, - onInput: (e) => { setDraft(draftKey, e.target.value); growComposer(e.target); updateEmojiAc(e.target, draftKey); if (onType && e.target.value) onType(); }, - onClick: (e) => updateEmojiAc(e.target, draftKey), // the caret moved - onBlur: () => { if (ui.emojiAc) { ui.emojiAc = null; render(); } }, - onKeydown: (e) => { - const ac = ui.emojiAc && ui.emojiAc.key === draftKey ? ui.emojiAc : null; - if (ac && (e.key === 'Tab' || e.key === 'Enter') && !e.shiftKey) { - const hits = emojiAcHits(ac.q); - if (hits.length) { e.preventDefault(); pickEmojiAc(hits[0]); return; } - } - if (ac && e.key === 'Escape') { e.preventDefault(); ui.emojiAc = null; render(); return; } - 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); }, - }), - gifs.button('chat:' + draftKey, 'iconbtn chat-gif'), + // attach + GIF sit inside the field, as in the post composer + h('div', { class: 'compose-box chat-box' }, + h('coinos-text', { + class: 'grow', id: 'msg-draft', placeholder, + value: draftFor(draftKey), + onMedia: onAttach ? (f) => onAttach(f) : null, + onInput: (e) => { setDraft(draftKey, e.target.value); growComposer(e.target); updateEmojiAc(e.target, draftKey); if (onType && e.target.value) onType(); }, + onClick: (e) => updateEmojiAc(e.target, draftKey), // the caret moved + onBlur: () => { if (ui.emojiAc) { ui.emojiAc = null; render(); } }, + onKeydown: (e) => { + const ac = ui.emojiAc && ui.emojiAc.key === draftKey ? ui.emojiAc : null; + if (ac && (e.key === 'Tab' || e.key === 'Enter') && !e.shiftKey) { + const hits = emojiAcHits(ac.q); + if (hits.length) { e.preventDefault(); pickEmojiAc(hits[0]); return; } + } + if (ac && e.key === 'Escape') { e.preventDefault(); ui.emojiAc = null; render(); return; } + 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); }, + }), + h('div', { class: 'compose-tools' }, + onAttach ? h('button', { + type: 'button', class: 'compose-tool chat-clip', title: t('msgAttach'), 'aria-label': t('msgAttach'), + disabled: !!ui.msgUploading, + onClick: () => document.getElementById('msg-file')?.click(), + }, ui.msgUploading ? h('span', { class: 'spinner sm' }) : h('span', { style: 'display:flex', html: CLIP })) : null, + gifs.button('chat:' + draftKey, 'compose-tool gif-btn chat-gif'))), h('button', { class: 'btn-primary btn-sm', onClick: onSend }, t('msgSend'))), // a picked GIF goes out at once, after whatever was typed gifs.sheet('chat:' + draftKey, (g) => { -- cgit v1.2.3