chat: simplify input handling (#3124)

* chat: simplify input handling

Removes the cursor save/restore functionality.

Removes most key handling.

Allows message to go over limit.

Moves the message length check into SendMessage.

Changes the chat input to change to the max style only when over the limit,
rather than at the limit. Makes it apparent that something is wrong.

Fixes #3121

* Prettified Code!

---------

Co-authored-by: jprjr <jprjr@users.noreply.github.com>
This commit is contained in:
John Regan
2023-06-30 15:22:01 -04:00
committed by GitHub
parent 8ad81544f1
commit bf1ccf21d2

View File

@@ -33,44 +33,10 @@ export type ChatTextFieldProps = {
const characterLimit = 300; const characterLimit = 300;
function getCaretPosition(node) {
const selection = window.getSelection();
if (selection.rangeCount === 0) {
return 0;
}
const range = selection.getRangeAt(0);
const preCaretRange = range.cloneRange();
const tempElement = document.createElement('div');
preCaretRange.selectNodeContents(node);
preCaretRange.setEnd(range.endContainer, range.endOffset);
tempElement.appendChild(preCaretRange.cloneContents());
return tempElement.innerHTML.length;
}
function setCaretPosition(editableDiv, position) {
try {
const range = document.createRange();
const sel = window.getSelection();
range.selectNode(editableDiv);
range.setStart(editableDiv.childNodes[0], position);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} catch (e) {
console.debug(e);
}
}
export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, focusInput }) => { export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, focusInput }) => {
const [characterCount, setCharacterCount] = useState(defaultText?.length); const [characterCount, setCharacterCount] = useState(defaultText?.length);
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom); const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const text = useRef(defaultText || ''); const text = useRef(defaultText || '');
const [savedCursorLocation, setSavedCursorLocation] = useState(0);
const [customEmoji, setCustomEmoji] = useState([]); const [customEmoji, setCustomEmoji] = useState([]);
// This is a bit of a hack to force the component to re-render when the text changes. // This is a bit of a hack to force the component to re-render when the text changes.
@@ -80,11 +46,15 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
const getCharacterCount = () => text.current.length; const getCharacterCount = () => text.current.length;
const sendMessage = () => { const sendMessage = () => {
const count = getCharacterCount();
if (!websocketService) { if (!websocketService) {
console.log('websocketService is not defined'); console.log('websocketService is not defined');
return; return;
} }
if (count === 0 || count > characterLimit) return;
let message = text.current; let message = text.current;
// Strip the opening and closing <p> tags. // Strip the opening and closing <p> tags.
message = message.replace(/^<p>|<\/p>$/g, ''); message = message.replace(/^<p>|<\/p>$/g, '');
@@ -99,6 +69,8 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
const insertTextAtEnd = (textToInsert: string) => { const insertTextAtEnd = (textToInsert: string) => {
const output = text.current + textToInsert; const output = text.current + textToInsert;
text.current = output; text.current = output;
setCharacterCount(getCharacterCount());
forceUpdate(); forceUpdate();
}; };
@@ -114,42 +86,10 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
}; };
const onKeyDown = (e: React.KeyboardEvent) => { const onKeyDown = (e: React.KeyboardEvent) => {
// Allow native line breaks if (e.key === 'Enter' && !(e.shiftKey || e.metaKey || e.ctrlKey || e.altKey)) {
if (e.key === 'Enter' && e.shiftKey) {
return;
}
const charCount = getCharacterCount() + 1;
// Always allow backspace.
if (e.key === 'Backspace') {
return;
}
// Always allow delete.
if (e.key === 'Delete') {
return;
}
// Always allow ctrl + a.
if (e.key === 'a' && e.ctrlKey) {
return;
}
// Limit the number of characters.
if (charCount + 1 > characterLimit) {
e.preventDefault();
return;
}
// Send the message when hitting enter.
if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
sendMessage(); sendMessage();
return;
} }
setCharacterCount(charCount + 1);
}; };
const handleChange = evt => { const handleChange = evt => {
@@ -167,31 +107,10 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
h3: 'p', h3: 'p',
}, },
}); });
text.current = sanitized;
const charCountString = sanitized.replace(/<\/?[^>]+(>|$)/g, ''); if (text.current !== sanitized) text.current = sanitized;
setCharacterCount(charCountString.length);
setSavedCursorLocation( setCharacterCount(getCharacterCount());
getCaretPosition(document.getElementById('chat-input-content-editable')),
);
};
const handleBlur = () => {
// Save the cursor location.
setSavedCursorLocation(
getCaretPosition(document.getElementById('chat-input-content-editable')),
);
};
const handleFocus = () => {
if (!savedCursorLocation) {
return;
}
// Restore the cursor location.
setCaretPosition(document.getElementById('chat-input-content-editable'), savedCursorLocation);
setSavedCursorLocation(0);
}; };
// Focus the input when the component mounts. // Focus the input when the component mounts.
@@ -229,7 +148,7 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
<div <div
className={classNames( className={classNames(
styles.inputWrap, styles.inputWrap,
characterCount >= characterLimit && styles.maxCharacters, characterCount > characterLimit && styles.maxCharacters,
)} )}
> >
<ContentEditable <ContentEditable
@@ -239,8 +158,6 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
disabled={!enabled} disabled={!enabled}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur}
onFocus={handleFocus}
style={{ width: '100%' }} style={{ width: '100%' }}
role="textbox" role="textbox"
aria-label="Chat text input" aria-label="Chat text input"