From 3f65099910b772e44e0d20b6536d893743c20117 Mon Sep 17 00:00:00 2001 From: John Regan Date: Tue, 11 Jul 2023 13:44:09 -0400 Subject: [PATCH] Name change: better unicode handling (#3164) * Name change: better unicode handling Client-side: * Changes the NameChangeModal to show text "Over limit" when a proposed display name is too long. * Allows names to go over limit to prevent splitting graphemes on input. Server-side: * Changes the MakeSafeStringOfLength to count number of unicode code points instead of string bytes. * name modal: check that newName is defined before iterating --- utils/strings.go | 8 ++++++-- .../modals/NameChangeModal/NameChangeModal.tsx | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/utils/strings.go b/utils/strings.go index 1ad45c547..25785b554 100644 --- a/utils/strings.go +++ b/utils/strings.go @@ -18,10 +18,14 @@ func MakeSafeStringOfLength(s string, length int) string { newString := s newString = StripHTML(newString) - if len(newString) > length { - newString = newString[:length] + // Convert utf-8 string into Unicode code points. + codePoints := []rune(newString) + + if len(codePoints) > length { + codePoints = codePoints[:length] } + newString = string(codePoints) newString = strings.ReplaceAll(newString, "\r", "") newString = strings.TrimSpace(newString) diff --git a/web/components/modals/NameChangeModal/NameChangeModal.tsx b/web/components/modals/NameChangeModal/NameChangeModal.tsx index 4865a20d6..6b0049323 100644 --- a/web/components/modals/NameChangeModal/NameChangeModal.tsx +++ b/web/components/modals/NameChangeModal/NameChangeModal.tsx @@ -31,14 +31,23 @@ export const NameChangeModal: FC = ({ closeModal }) => { const websocketService = useRecoilValue(websocketServiceAtom); const [newName, setNewName] = useState(currentUser?.displayName); + const characterLimit = 30; + if (!currentUser) { return null; } const { displayName, displayColor } = currentUser; - const saveEnabled = () => - newName !== displayName && newName !== '' && websocketService?.isConnected(); + const saveEnabled = () => { + const count = newName !== undefined ? Array.from(newName).length : 0; + return ( + newName !== displayName && + count > 0 && + count <= characterLimit && + websocketService?.isConnected() + ); + }; const handleNameChange = () => { if (!saveEnabled()) return; @@ -59,6 +68,8 @@ export const NameChangeModal: FC = ({ closeModal }) => { websocketService.send(colorChange); }; + const showCount = info => (info.count > characterLimit ? 'Over limit' : ''); + const maxColor = 8; // 0...n const colorOptions = [...Array(maxColor)].map((_, i) => i); @@ -84,8 +95,7 @@ export const NameChangeModal: FC = ({ closeModal }) => { onChange={e => setNewName(e.target.value)} placeholder="Your chat display name" aria-label="Your chat display name" - maxLength={30} - showCount + showCount={{ formatter: showCount }} defaultValue={displayName} className={styles.inputGroup} />