Skip to content

Commit

Permalink
chat: simplify input handling (owncast#3124)
Browse files Browse the repository at this point in the history
* 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 owncast#3121

* Prettified Code!

---------

Co-authored-by: jprjr <[email protected]>
  • Loading branch information
jprjr and jprjr authored Jun 30, 2023
1 parent 8ad8154 commit bf1ccf2
Showing 1 changed file with 10 additions and 93 deletions.
103 changes: 10 additions & 93 deletions web/components/chat/ChatTextField/ChatTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,44 +33,10 @@ export type ChatTextFieldProps = {

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 }) => {
const [characterCount, setCharacterCount] = useState(defaultText?.length);
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const text = useRef(defaultText || '');
const [savedCursorLocation, setSavedCursorLocation] = useState(0);
const [customEmoji, setCustomEmoji] = useState([]);

// This is a bit of a hack to force the component to re-render when the text changes.
Expand All @@ -80,11 +46,15 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
const getCharacterCount = () => text.current.length;

const sendMessage = () => {
const count = getCharacterCount();

if (!websocketService) {
console.log('websocketService is not defined');
return;
}

if (count === 0 || count > characterLimit) return;

let message = text.current;
// Strip the opening and closing <p> tags.
message = message.replace(/^<p>|<\/p>$/g, '');
Expand All @@ -99,6 +69,8 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
const insertTextAtEnd = (textToInsert: string) => {
const output = text.current + textToInsert;
text.current = output;

setCharacterCount(getCharacterCount());
forceUpdate();
};

Expand All @@ -114,42 +86,10 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
};

const onKeyDown = (e: React.KeyboardEvent) => {
// Allow native line breaks
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') {
if (e.key === 'Enter' && !(e.shiftKey || e.metaKey || e.ctrlKey || e.altKey)) {
e.preventDefault();
sendMessage();
return;
}

setCharacterCount(charCount + 1);
};

const handleChange = evt => {
Expand All @@ -167,31 +107,10 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
h3: 'p',
},
});
text.current = sanitized;

const charCountString = sanitized.replace(/<\/?[^>]+(>|$)/g, '');
setCharacterCount(charCountString.length);

setSavedCursorLocation(
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;
}
if (text.current !== sanitized) text.current = sanitized;

// Restore the cursor location.
setCaretPosition(document.getElementById('chat-input-content-editable'), savedCursorLocation);
setSavedCursorLocation(0);
setCharacterCount(getCharacterCount());
};

// Focus the input when the component mounts.
Expand Down Expand Up @@ -229,7 +148,7 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
<div
className={classNames(
styles.inputWrap,
characterCount >= characterLimit && styles.maxCharacters,
characterCount > characterLimit && styles.maxCharacters,
)}
>
<ContentEditable
Expand All @@ -239,8 +158,6 @@ export const ChatTextField: FC<ChatTextFieldProps> = ({ defaultText, enabled, fo
disabled={!enabled}
onKeyDown={onKeyDown}
onChange={handleChange}
onBlur={handleBlur}
onFocus={handleFocus}
style={{ width: '100%' }}
role="textbox"
aria-label="Chat text input"
Expand Down

0 comments on commit bf1ccf2

Please sign in to comment.