Compose: Show 'search for username' by itself more often

This commit is contained in:
Scott Nonnenberg
2025-03-19 01:41:52 +10:00
committed by GitHub
parent 5e825e03ba
commit a3335929b3
4 changed files with 134 additions and 27 deletions

View File

@@ -18,6 +18,7 @@ import {
isFetchingByE164,
} from '../../util/uuidFetchState';
import type { GroupListItemConversationType } from '../conversationList/GroupListItem';
import { isProbablyAUsername } from '../../util/Username';
export type LeftPaneComposePropsType = {
composeContacts: ReadonlyArray<ContactListItemConversationType>;
@@ -136,15 +137,15 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
}
getRowCount(): number {
let result = this.#composeContacts.length + this.#composeGroups.length;
let result = 0;
if (this.#hasTopButtons()) {
result += 3;
}
if (this.#hasContactsHeader()) {
result += 1;
result += 1 + this.#composeContacts.length;
}
if (this.#hasGroupsHeader()) {
result += 1;
result += 1 + this.#composeGroups.length;
}
if (this.#isUsernameVisible) {
result += 2;
@@ -304,11 +305,17 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
}
#hasContactsHeader(): boolean {
return Boolean(this.#composeContacts.length);
return (
Boolean(this.#composeContacts.length) &&
!isProbablyAUsername(this.#searchTerm)
);
}
#hasGroupsHeader(): boolean {
return Boolean(this.#composeGroups.length);
return (
Boolean(this.#composeGroups.length) &&
!isProbablyAUsername(this.#searchTerm)
);
}
#getHeaderIndices(): {

View File

@@ -9,12 +9,9 @@ describe('Username', () => {
describe('getUsernameFromSearch', () => {
const { getUsernameFromSearch } = Username;
it('matches invalid username searches', () => {
assert.isUndefined(getUsernameFromSearch('us'));
assert.isUndefined(getUsernameFromSearch('123'));
});
it('matches partial username searches without discriminator', () => {
assert.strictEqual(getUsernameFromSearch('u'), 'u.01');
assert.strictEqual(getUsernameFromSearch('us'), 'us.01');
assert.strictEqual(getUsernameFromSearch('use'), 'use.01');
assert.strictEqual(getUsernameFromSearch('use.'), 'use.01');
});
@@ -25,6 +22,11 @@ describe('Username', () => {
assert.strictEqual(getUsernameFromSearch('@user.01'), 'user.01');
});
it('adds a 1 if discriminator is one digit', () => {
assert.strictEqual(getUsernameFromSearch('@user.0'), 'user.01');
assert.strictEqual(getUsernameFromSearch('@user.2'), 'user.21');
});
it('matches valid username searches', () => {
assert.strictEqual(getUsernameFromSearch('username.12'), 'username.12');
assert.strictEqual(getUsernameFromSearch('xyz.568'), 'xyz.568');
@@ -37,4 +39,39 @@ describe('Username', () => {
assert.isUndefined(getUsernameFromSearch('+234234234233'));
});
});
describe('probablyAUsername', () => {
const { isProbablyAUsername: probablyAUsername } = Username;
it('returns true if it starts with @', () => {
assert.isTrue(probablyAUsername('@'));
assert.isTrue(probablyAUsername('@5551115555'));
assert.isTrue(probablyAUsername('@.324'));
});
it('returns true if it ends with a discriminator', () => {
assert.isTrue(probablyAUsername('someone.00'));
assert.isTrue(probablyAUsername('32423423.04'));
assert.isTrue(probablyAUsername('d.04'));
});
it('returns false if just a discriminator', () => {
assert.isFalse(probablyAUsername('.01'));
assert.isFalse(probablyAUsername('.99'));
});
it('returns false for normal searches', () => {
assert.isFalse(probablyAUsername('group'));
assert.isFalse(probablyAUsername('climbers'));
assert.isFalse(probablyAUsername('sarah'));
assert.isFalse(probablyAUsername('john'));
});
it('returns false for something that looks like a phone number', () => {
assert.isFalse(probablyAUsername('+'));
assert.isFalse(probablyAUsername('2223'));
assert.isFalse(probablyAUsername('+3'));
assert.isFalse(probablyAUsername('+234234234233'));
});
});
});

View File

@@ -82,7 +82,7 @@ describe('LeftPaneComposeHelper', () => {
);
});
it('returns the number of contacts, number groups + 4 (for headers and username)', () => {
it('returns two if the search term looks like a username', () => {
assert.strictEqual(
new LeftPaneComposeHelper({
composeContacts: [getDefaultConversation(), getDefaultConversation()],
@@ -92,6 +92,45 @@ describe('LeftPaneComposeHelper', () => {
uuidFetchState: {},
username: 'someone.01',
}).getRowCount(),
2,
'ends with discriminator'
);
assert.strictEqual(
new LeftPaneComposeHelper({
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
regionCode: 'US',
searchTerm: '@someone',
uuidFetchState: {},
username: 'someone',
}).getRowCount(),
2,
'starts with @'
);
assert.strictEqual(
new LeftPaneComposeHelper({
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
regionCode: 'US',
searchTerm: '23432',
uuidFetchState: {},
username: '23432',
}).getRowCount(),
8,
"all numbers, so it doesn't look like a username"
);
});
it('returns the number of contacts, number groups + 4 (for headers and username)', () => {
assert.strictEqual(
new LeftPaneComposeHelper({
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
regionCode: 'US',
searchTerm: 'someone',
uuidFetchState: {},
username: 'someone',
}).getRowCount(),
8
);
});
@@ -102,9 +141,9 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [],
composeGroups: [],
regionCode: 'US',
searchTerm: 'foobar.01',
searchTerm: '5550101',
uuidFetchState: {},
username: 'foobar.01',
username: undefined,
}).getRowCount(),
2
);
@@ -113,9 +152,9 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [],
regionCode: 'US',
searchTerm: 'foobar.01',
searchTerm: '5550101',
uuidFetchState: {},
username: 'foobar.01',
username: undefined,
}).getRowCount(),
5
);
@@ -124,9 +163,9 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [getDefaultGroupListItem()],
regionCode: 'US',
searchTerm: 'foobar.01',
searchTerm: '5550101',
uuidFetchState: {},
username: 'foobar.01',
username: undefined,
}).getRowCount(),
7
);
@@ -152,9 +191,9 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [],
composeGroups: [],
regionCode: 'US',
searchTerm: 'someone.02',
searchTerm: 'someone',
uuidFetchState: {},
username: 'someone.02',
username: 'someone',
}).getRowCount(),
2
);

View File

@@ -1,10 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { usernames } from '@signalapp/libsignal-client';
import * as RemoteConfig from '../RemoteConfig';
import { getNickname } from '../types/Username';
import { parseIntWithFallback } from './parseIntWithFallback';
export function getMaxNickname(): number {
@@ -17,29 +14,56 @@ export function getMinNickname(): number {
return parseIntWithFallback(RemoteConfig.getValue('global.nicknames.min'), 3);
}
const USERNAME_CHARS = /^@?[a-zA-Z0-9]+(.\d+)?$/;
const ALL_DIGITS = /^\d+$/;
export function getUsernameFromSearch(searchTerm: string): string | undefined {
const nickname = getNickname(searchTerm);
if (nickname == null || nickname.length < getMinNickname()) {
let modifiedTerm = searchTerm;
if (ALL_DIGITS.test(searchTerm)) {
return undefined;
}
let modifiedTerm = searchTerm;
if (modifiedTerm.startsWith('@')) {
modifiedTerm = modifiedTerm.slice(1);
}
if (modifiedTerm.endsWith('.')) {
// Allow nicknames without full discriminator
modifiedTerm = `${modifiedTerm}01`;
} else if (/\.\d$/.test(modifiedTerm)) {
// Add one more digit if they only have one
modifiedTerm = `${modifiedTerm}1`;
} else if (!/\.\d*$/.test(modifiedTerm)) {
// Allow nicknames without discriminator
modifiedTerm = `${modifiedTerm}.01`;
}
if (!USERNAME_CHARS.test(modifiedTerm)) {
return undefined;
}
try {
usernames.hash(modifiedTerm);
return modifiedTerm;
} catch {
return undefined;
}
}
export function isProbablyAUsername(searchTerm: string): boolean {
if (searchTerm.startsWith('@')) {
return true;
}
if (!USERNAME_CHARS.test(searchTerm)) {
return false;
}
if (ALL_DIGITS.test(searchTerm)) {
return false;
}
if (/.+\.\d\d\d?$/.test(searchTerm)) {
return true;
}
return false;
}