mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-12 19:50:04 +05:00
feat(chat): support Ctrl+Enter and auto-resize for multiline chat messages
- Enable Ctrl+Enter (and Cmd+Enter) to insert a newline without sending - Prevent plain Enter send handler when modifier keys (Ctrl/Cmd) are pressed - Support dynamic multiline auto-resizing across Lobby, People, and Network chats - Preserve Shift+Enter standard newline behavior
This commit is contained in:
parent
2bca038a4a
commit
87f1aa8e28
@ -14,6 +14,7 @@ const {
|
||||
ChatRoomsModel,
|
||||
ChatLobbyModel,
|
||||
ChatHubState,
|
||||
autoResizeTextarea,
|
||||
} = chatState;
|
||||
|
||||
chatEmoji.setDependencies({ ChatHubState });
|
||||
@ -222,6 +223,7 @@ function pollHashStatus(localpath, delay = HASH_POLL_START_MS) {
|
||||
if (textarea) {
|
||||
const val = textarea.value;
|
||||
textarea.value = val ? val + '\n' + fileLink : fileLink;
|
||||
autoResizeTextarea(textarea);
|
||||
}
|
||||
|
||||
ChatHubState.showAttachModal = false;
|
||||
@ -528,6 +530,7 @@ const ChatConversationView = () => {
|
||||
const end = textarea.selectionEnd || 0;
|
||||
const val = textarea.value;
|
||||
textarea.value = val.substring(0, start) + imgTag + val.substring(end);
|
||||
autoResizeTextarea(textarea);
|
||||
m.redraw();
|
||||
}
|
||||
});
|
||||
@ -539,6 +542,10 @@ const ChatConversationView = () => {
|
||||
placeholder: 'Type a message...',
|
||||
disabled: !canTalk,
|
||||
enterkeyhint: 'send',
|
||||
rows: 1,
|
||||
oncreate: (vnode) => autoResizeTextarea(vnode.dom),
|
||||
onupdate: (vnode) => autoResizeTextarea(vnode.dom),
|
||||
oninput: (e) => autoResizeTextarea(e.target),
|
||||
onpaste: (e) => {
|
||||
if (!canTalk) return;
|
||||
const items = (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData))?.items;
|
||||
@ -554,6 +561,7 @@ const ChatConversationView = () => {
|
||||
const end = textarea.selectionEnd || 0;
|
||||
const val = textarea.value;
|
||||
textarea.value = val.substring(0, start) + imgTag + val.substring(end);
|
||||
autoResizeTextarea(textarea);
|
||||
m.redraw();
|
||||
}
|
||||
});
|
||||
@ -562,16 +570,31 @@ const ChatConversationView = () => {
|
||||
}
|
||||
},
|
||||
onkeydown: (e) => {
|
||||
if ((e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey) {
|
||||
if (!canTalk) return false;
|
||||
const msg = e.target.value;
|
||||
if (msg.trim() === '') return false;
|
||||
e.target.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => {
|
||||
e.target.value = '';
|
||||
scrollChatToBottom();
|
||||
});
|
||||
return false;
|
||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||
if (!e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||
e.preventDefault();
|
||||
if (!canTalk) return false;
|
||||
const msg = e.target.value;
|
||||
if (msg.trim() === '') return false;
|
||||
e.target.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => {
|
||||
e.target.value = '';
|
||||
autoResizeTextarea(e.target);
|
||||
scrollChatToBottom();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
if (!document.execCommand || !document.execCommand('insertText', false, '\n')) {
|
||||
const start = e.target.selectionStart || 0;
|
||||
const end = e.target.selectionEnd || 0;
|
||||
const val = e.target.value;
|
||||
e.target.value = val.substring(0, start) + '\n' + val.substring(end);
|
||||
e.target.selectionStart = e.target.selectionEnd = start + 1;
|
||||
}
|
||||
autoResizeTextarea(e.target);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
@ -588,6 +611,7 @@ const ChatConversationView = () => {
|
||||
textarea.value = ' sending ... ';
|
||||
ChatLobbyModel.sendMessage(msg, () => {
|
||||
textarea.value = '';
|
||||
autoResizeTextarea(textarea);
|
||||
scrollChatToBottom();
|
||||
});
|
||||
},
|
||||
|
||||
@ -92,6 +92,7 @@ function insertEmojiIntoTextarea(emoji, onSelect) {
|
||||
textarea.selectionStart = newPos;
|
||||
textarea.selectionEnd = newPos;
|
||||
textarea.focus();
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
const EmojiPicker = () => ({
|
||||
|
||||
@ -1325,4 +1325,15 @@ module.exports = {
|
||||
ChatLobbyModel,
|
||||
ChatHubState,
|
||||
receiveLobbyChatMessage,
|
||||
autoResizeTextarea,
|
||||
};
|
||||
|
||||
function autoResizeTextarea(el) {
|
||||
if (!el) return;
|
||||
el.style.height = 'auto';
|
||||
const maxHeight = 160;
|
||||
const scrollHeight = el.scrollHeight;
|
||||
const newHeight = Math.min(Math.max(scrollHeight, 40), maxHeight);
|
||||
el.style.height = newHeight + 'px';
|
||||
el.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden';
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ const {
|
||||
sendDirectChatMessage,
|
||||
loadAllDirectChatHistory,
|
||||
} = require('network/network_state');
|
||||
const { renderChatMessage } = require('chat/chat_state');
|
||||
const { renderChatMessage, autoResizeTextarea } = require('chat/chat_state');
|
||||
const chatEmoji = require('chat/chat_emoji');
|
||||
const HistoryBrowserModal = require('people/people_history');
|
||||
|
||||
@ -218,7 +218,7 @@ const ChatTab = () => {
|
||||
name: friend.name,
|
||||
ownName: State.ownProfile.name || 'You',
|
||||
}),
|
||||
m('.chat-input-area', { style: 'display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem; background: #ffffff; border-top: 1px solid #cbd5e1;' }, [
|
||||
m('.chat-input-area', { style: 'display: flex; align-items: flex-end; gap: 0.5rem; padding: 0.75rem; background: #ffffff; border-top: 1px solid #cbd5e1;' }, [
|
||||
m('button.chat-hub-action-btn.desktop-chat-attachment', {
|
||||
title: 'Attach file link',
|
||||
onclick: () => {
|
||||
@ -313,9 +313,13 @@ const ChatTab = () => {
|
||||
m('textarea.chat-textarea', {
|
||||
placeholder: 'Type a message here...',
|
||||
value: State.chatInputMsg,
|
||||
style: 'flex: 1; resize: none; border: 1px solid #cbd5e1; border-radius: 6px; padding: 0.5rem; font-family: inherit; font-size: 0.9rem; outline: none; min-height: 40px; max-height: 120px;',
|
||||
rows: 1,
|
||||
style: 'flex: 1; resize: none; border: 1px solid #cbd5e1; border-radius: 0.625rem; padding: 0.55rem 0.75rem; font-family: inherit; font-size: 0.9rem; line-height: 1.45; outline: none; min-height: 40px; max-height: 160px; height: 40px; box-sizing: border-box; overflow-y: hidden;',
|
||||
oncreate: (vnode) => autoResizeTextarea(vnode.dom),
|
||||
onupdate: (vnode) => autoResizeTextarea(vnode.dom),
|
||||
oninput: (e) => {
|
||||
State.chatInputMsg = e.target.value;
|
||||
autoResizeTextarea(e.target);
|
||||
},
|
||||
onpaste: (e) => {
|
||||
const items = (e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData))?.items;
|
||||
@ -335,9 +339,25 @@ const ChatTab = () => {
|
||||
}
|
||||
},
|
||||
onkeydown: (e) => {
|
||||
if (e.code === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendDirectChatMessage();
|
||||
if (e.key === 'Enter' || e.code === 'Enter' || e.keyCode === 13) {
|
||||
if (!e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||
e.preventDefault();
|
||||
sendDirectChatMessage();
|
||||
} else if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
if (!document.execCommand || !document.execCommand('insertText', false, '\n')) {
|
||||
const start = e.target.selectionStart || 0;
|
||||
const end = e.target.selectionEnd || 0;
|
||||
const val = e.target.value;
|
||||
const newVal = val.substring(0, start) + '\n' + val.substring(end);
|
||||
State.chatInputMsg = newVal;
|
||||
e.target.value = newVal;
|
||||
e.target.selectionStart = e.target.selectionEnd = start + 1;
|
||||
} else {
|
||||
State.chatInputMsg = e.target.value;
|
||||
}
|
||||
autoResizeTextarea(e.target);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
@ -13,7 +13,7 @@ const {
|
||||
switchChatIdentity,
|
||||
} = require('people/people_state');
|
||||
const { startAttachHash, stopAttachHash } = require('people/people_attach');
|
||||
const { renderChatMessage } = require('chat/chat_state');
|
||||
const { renderChatMessage, autoResizeTextarea } = require('chat/chat_state');
|
||||
const chatEmoji = require('chat/chat_emoji');
|
||||
const peopleUtil = require('people/people_util');
|
||||
const HistoryBrowserModal = require('people/people_history');
|
||||
@ -295,7 +295,7 @@ const ChatTab = () => {
|
||||
}, 'Dismiss'),
|
||||
]),
|
||||
|
||||
m('.chat-input-area', { style: 'display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem; background: #ffffff; border-top: 1px solid #cbd5e1;' }, [
|
||||
m('.chat-input-area', { style: 'display: flex; align-items: flex-end; gap: 0.5rem; padding: 0.75rem; background: #ffffff; border-top: 1px solid #cbd5e1;' }, [
|
||||
m('button.chat-hub-action-btn.desktop-chat-attachment', {
|
||||
disabled: !canTalk,
|
||||
style: !canTalk ? 'opacity: 0.5; cursor: not-allowed;' : '',
|
||||
@ -377,9 +377,13 @@ const ChatTab = () => {
|
||||
placeholder: canTalk ? 'Type a message here...' : 'Waiting for tunnel to be secured...',
|
||||
disabled: !canTalk,
|
||||
value: State.chatInputMsg,
|
||||
style: 'flex: 1; resize: none; border: 1px solid #cbd5e1; border-radius: 6px; padding: 0.5rem; font-family: inherit; font-size: 0.9rem; outline: none; min-height: 40px; max-height: 120px;',
|
||||
rows: 1,
|
||||
style: 'flex: 1; resize: none; border: 1px solid #cbd5e1; border-radius: 0.625rem; padding: 0.55rem 0.75rem; font-family: inherit; font-size: 0.9rem; line-height: 1.45; outline: none; min-height: 40px; max-height: 160px; height: 40px; box-sizing: border-box; overflow-y: hidden;',
|
||||
oncreate: (vnode) => autoResizeTextarea(vnode.dom),
|
||||
onupdate: (vnode) => autoResizeTextarea(vnode.dom),
|
||||
oninput: (e) => {
|
||||
setChatDraft(e.target.value);
|
||||
autoResizeTextarea(e.target);
|
||||
},
|
||||
onpaste: (e) => {
|
||||
if (!canTalk) return;
|
||||
@ -400,9 +404,25 @@ const ChatTab = () => {
|
||||
}
|
||||
},
|
||||
onkeydown: (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
if (canTalk) sendDistantChatMessage();
|
||||
if (e.key === 'Enter' || e.keyCode === 13) {
|
||||
if (!e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||
e.preventDefault();
|
||||
if (canTalk) sendDistantChatMessage();
|
||||
} else if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
if (!document.execCommand || !document.execCommand('insertText', false, '\n')) {
|
||||
const start = e.target.selectionStart || 0;
|
||||
const end = e.target.selectionEnd || 0;
|
||||
const val = e.target.value;
|
||||
const newVal = val.substring(0, start) + '\n' + val.substring(end);
|
||||
setChatDraft(newVal);
|
||||
e.target.value = newVal;
|
||||
e.target.selectionStart = e.target.selectionEnd = start + 1;
|
||||
} else {
|
||||
setChatDraft(e.target.value);
|
||||
}
|
||||
autoResizeTextarea(e.target);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
@ -1309,15 +1309,18 @@ textarea.chatMsg {
|
||||
|
||||
.chat-hub-input-area textarea.chat-hub-textarea {
|
||||
flex: 1;
|
||||
resize: vertical;
|
||||
resize: none !important;
|
||||
min-height: 40px;
|
||||
max-height: 250px;
|
||||
max-height: 160px;
|
||||
height: 40px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 0.375rem;
|
||||
border-radius: 0.625rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
outline: none;
|
||||
overflow-y: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
@ -1444,12 +1447,15 @@ label.mobile-chat-attachment__option:hover {
|
||||
min-width: 0;
|
||||
min-height: 40px;
|
||||
height: 40px;
|
||||
max-height: 120px;
|
||||
max-height: 140px;
|
||||
padding: 0.55rem 0.7rem;
|
||||
resize: none;
|
||||
resize: none !important;
|
||||
border-color: #dbe2ea;
|
||||
border-radius: 1.25rem;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
overflow-y: hidden;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.chat-hub-input-area .emoji-picker-wrapper {
|
||||
|
||||
@ -804,17 +804,22 @@
|
||||
border-top: 1px solid #cbd5e1;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
|
||||
textarea.chat-textarea {
|
||||
flex: 1;
|
||||
resize: none;
|
||||
resize: none !important;
|
||||
min-height: 40px;
|
||||
max-height: 160px;
|
||||
height: 40px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 0.375rem;
|
||||
border-radius: 0.625rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.45;
|
||||
outline: none;
|
||||
overflow-y: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:focus {
|
||||
@ -923,7 +928,7 @@
|
||||
|
||||
/* Direct-chat composer: preserve room for typing on a narrow screen. */
|
||||
.network-chat-view .chat-input-area {
|
||||
align-items: center !important;
|
||||
align-items: flex-end !important;
|
||||
padding: .35rem .45rem !important;
|
||||
gap: .2rem !important;
|
||||
min-width: 0;
|
||||
@ -959,11 +964,14 @@
|
||||
min-width: 0 !important;
|
||||
min-height: 40px !important;
|
||||
height: 40px !important;
|
||||
max-height: 120px !important;
|
||||
max-height: 140px !important;
|
||||
padding: .55rem .7rem !important;
|
||||
border-color: #dbe2ea !important;
|
||||
border-radius: 1.25rem !important;
|
||||
background: #fff !important;
|
||||
box-sizing: border-box;
|
||||
overflow-y: hidden;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.network-chat-view .chat-input-area .send-btn {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user