From 4a6ad534321eedef968c9ea922e41001c3989bf1 Mon Sep 17 00:00:00 2001 From: witbrock Date: Mon, 14 Sep 2026 13:15:07 -0400 Subject: [PATCH] Keep message attachments inline with the composer --- .../static/js/utils/conversationImages.js | 18 ++++++- .../web/von_interface/static/styles.css | 52 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/frontend/web/von_interface/static/js/utils/conversationImages.js b/src/frontend/web/von_interface/static/js/utils/conversationImages.js index b6aff5d8..41b37c96 100644 --- a/src/frontend/web/von_interface/static/js/utils/conversationImages.js +++ b/src/frontend/web/von_interface/static/js/utils/conversationImages.js @@ -215,7 +215,10 @@ export function bindAttachmentComposer(root, input, sessionKey, headers, changed const picker = document.createElement('input'); picker.type = 'file'; picker.multiple = true; picker.hidden = true; const button = document.createElement('button'); - button.type = 'button'; button.textContent = 'Attach files'; + button.type = 'button'; button.textContent = '+'; + button.className = 'message-attachment-button'; + button.title = 'Attach files'; + button.setAttribute('aria-label', 'Attach files'); button.onclick = () => picker.click(); const pendingRoot = document.createElement('div'); pendingRoot.className = 'pending-message-attachments'; @@ -225,7 +228,18 @@ export function bindAttachmentComposer(root, input, sessionKey, headers, changed for (const file of files) await uploadConversationImage(file, key, headers(), refresh); }; picker.onchange = () => { void upload(Array.from(picker.files)); picker.value = ''; }; - root.append(button, picker, pendingRoot); + // Reply already has a row shared with Send; the new-message modal needs + // the same inline attachment affordance beside its text field. + let row = input.parentElement; + if (!row.classList.contains('message-compose-row')) { + row = document.createElement('div'); + row.className = 'message-attachment-input-row'; + input.before(row); + row.append(input); + } + row.classList.add('message-attachment-input-row'); + input.before(button); + root.append(picker, pendingRoot); input.addEventListener('paste', event => { const files = Array.from(event.clipboardData?.files || []).filter(f => f.type.startsWith('image/')); if (!files.length) return; diff --git a/src/frontend/web/von_interface/static/styles.css b/src/frontend/web/von_interface/static/styles.css index a4f14fad..83ab9aa3 100644 --- a/src/frontend/web/von_interface/static/styles.css +++ b/src/frontend/web/von_interface/static/styles.css @@ -4541,6 +4541,58 @@ button.unified-search-conversation-main { align-items: flex-end; } +.message-attachment-input-row { + display: flex; + align-items: flex-end; + gap: 8px; +} + +.message-compose-row.message-attachment-input-row { + flex-direction: row; + align-items: flex-end; +} + +.message-attachment-input-row > .send-message-btn { + flex: none; + width: auto; +} + +.message-attachment-input-row > .message-content-input { + margin-bottom: 0; +} + +.message-attachment-input-row > textarea, +.message-compose-row > textarea { + flex: 1; + min-width: 0; +} + +.message-compose-row > .message-attachment-button, +.message-attachment-input-row > .message-attachment-button { + flex: 0 0 44px; + width: 44px; + height: 44px; + min-height: 44px; + margin: 0; + padding: 0; + border: 1px solid var(--border-color, #cfd9e3); + border-radius: 50%; + background: transparent; + color: var(--text-color, #215f8d); + font-size: 28px; + line-height: 1; + cursor: pointer; +} + +.message-attachment-button:focus-visible { + outline: 2px solid #256b76; + outline-offset: 2px; +} + +.pending-message-attachments:empty { + display: none; +} + .message-input { flex: 1; padding: 10px 12px;