Compose: Show 'search for username' by itself more often
This commit is contained in:
@@ -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(): {
|
||||
|
@@ -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'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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
|
||||
);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user