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
This commit is contained in:
John Regan 2023-07-11 13:44:09 -04:00 committed by GitHub
parent dfa3a2a273
commit 3f65099910
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 20 additions and 6 deletions

View File

@ -18,10 +18,14 @@ func MakeSafeStringOfLength(s string, length int) string {
newString := s newString := s
newString = StripHTML(newString) newString = StripHTML(newString)
if len(newString) > length { // Convert utf-8 string into Unicode code points.
newString = newString[:length] codePoints := []rune(newString)
if len(codePoints) > length {
codePoints = codePoints[:length]
} }
newString = string(codePoints)
newString = strings.ReplaceAll(newString, "\r", "") newString = strings.ReplaceAll(newString, "\r", "")
newString = strings.TrimSpace(newString) newString = strings.TrimSpace(newString)

View File

@ -31,14 +31,23 @@ export const NameChangeModal: FC<NameChangeModalProps> = ({ closeModal }) => {
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom); const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const [newName, setNewName] = useState<string>(currentUser?.displayName); const [newName, setNewName] = useState<string>(currentUser?.displayName);
const characterLimit = 30;
if (!currentUser) { if (!currentUser) {
return null; return null;
} }
const { displayName, displayColor } = currentUser; const { displayName, displayColor } = currentUser;
const saveEnabled = () => const saveEnabled = () => {
newName !== displayName && newName !== '' && websocketService?.isConnected(); const count = newName !== undefined ? Array.from(newName).length : 0;
return (
newName !== displayName &&
count > 0 &&
count <= characterLimit &&
websocketService?.isConnected()
);
};
const handleNameChange = () => { const handleNameChange = () => {
if (!saveEnabled()) return; if (!saveEnabled()) return;
@ -59,6 +68,8 @@ export const NameChangeModal: FC<NameChangeModalProps> = ({ closeModal }) => {
websocketService.send(colorChange); websocketService.send(colorChange);
}; };
const showCount = info => (info.count > characterLimit ? 'Over limit' : '');
const maxColor = 8; // 0...n const maxColor = 8; // 0...n
const colorOptions = [...Array(maxColor)].map((_, i) => i); const colorOptions = [...Array(maxColor)].map((_, i) => i);
@ -84,8 +95,7 @@ export const NameChangeModal: FC<NameChangeModalProps> = ({ closeModal }) => {
onChange={e => setNewName(e.target.value)} onChange={e => setNewName(e.target.value)}
placeholder="Your chat display name" placeholder="Your chat display name"
aria-label="Your chat display name" aria-label="Your chat display name"
maxLength={30} showCount={{ formatter: showCount }}
showCount
defaultValue={displayName} defaultValue={displayName}
className={styles.inputGroup} className={styles.inputGroup}
/> />