mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-07-08 06:14:14 +01:00
Enable tsconfig noUncheckedIndexedAccess
This commit is contained in:
@@ -791,13 +791,15 @@ function handleRangeRequest({
|
||||
}
|
||||
|
||||
// Chromium only sends open-ended ranges: "start-"
|
||||
type Match = RegExpMatchArray & { 1: string };
|
||||
const match = range.match(/^bytes=(\d+)-$/);
|
||||
if (match == null) {
|
||||
log.error(`invalid range header: ${range}`);
|
||||
return create200Response();
|
||||
}
|
||||
|
||||
const startParam = safeParseInteger(match[1]);
|
||||
const [startInput] = match as Match;
|
||||
const startParam = safeParseInteger(startInput);
|
||||
if (startParam == null) {
|
||||
log.error(`invalid range header: ${range}`);
|
||||
return create200Response();
|
||||
|
||||
+12
-6
@@ -819,10 +819,14 @@ async function createWindow() {
|
||||
maximized: mainWindow.isMaximized(),
|
||||
autoHideMenuBar: mainWindow.autoHideMenuBar,
|
||||
fullscreen: mainWindow.isFullScreen(),
|
||||
width: size[0],
|
||||
height: size[1],
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
width: size[0]!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
height: size[1]!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
x: position[0]!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
y: position[1]!,
|
||||
};
|
||||
|
||||
if (
|
||||
@@ -1565,8 +1569,10 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
|
||||
|
||||
const size = mainWindow.getSize();
|
||||
const options = {
|
||||
width: Math.min(400, size[0]),
|
||||
height: Math.min(150, size[1]),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
width: Math.min(400, size[0]!),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
height: Math.min(150, size[1]!),
|
||||
resizable: false,
|
||||
title: getResolvedMessagesLocale().i18n('icu:allowAccess'),
|
||||
titleBarStyle: nonMainTitleBarStyle,
|
||||
|
||||
+9
-2
@@ -9,6 +9,7 @@ import type {
|
||||
MenuOptionsType,
|
||||
MenuActionsType,
|
||||
} from '../ts/types/menu.std.js';
|
||||
import { strictAssert } from '../ts/util/assert.std.js';
|
||||
|
||||
const { isString } = lodash;
|
||||
|
||||
@@ -220,6 +221,7 @@ export const createTemplate = (
|
||||
|
||||
if (includeSetup) {
|
||||
const fileMenu = template[0];
|
||||
strictAssert(fileMenu, 'Missing fileMenu');
|
||||
|
||||
if (Array.isArray(fileMenu.submenu)) {
|
||||
// These are in reverse order, since we're prepending them one at a time
|
||||
@@ -265,6 +267,7 @@ function updateForMac(
|
||||
|
||||
// Remove About item and separator from Help menu, since they're in the app menu
|
||||
const aboutMenu = template[4];
|
||||
strictAssert(aboutMenu, 'Missing aboutMenu');
|
||||
if (Array.isArray(aboutMenu.submenu)) {
|
||||
aboutMenu.submenu.pop();
|
||||
aboutMenu.submenu.pop();
|
||||
@@ -275,6 +278,7 @@ function updateForMac(
|
||||
// Remove preferences, separator, and quit from the File menu, since they're
|
||||
// in the app menu
|
||||
const fileMenu = template[0];
|
||||
strictAssert(fileMenu, 'Missing fileMenu');
|
||||
if (Array.isArray(fileMenu.submenu)) {
|
||||
fileMenu.submenu.pop();
|
||||
fileMenu.submenu.pop();
|
||||
@@ -343,6 +347,7 @@ function updateForMac(
|
||||
});
|
||||
|
||||
const editMenu = template[2];
|
||||
strictAssert(editMenu, 'Missing editMenu');
|
||||
if (Array.isArray(editMenu.submenu)) {
|
||||
editMenu.submenu.push(
|
||||
{
|
||||
@@ -366,9 +371,11 @@ function updateForMac(
|
||||
throw new Error('updateForMac: edit.submenu was not an array!');
|
||||
}
|
||||
|
||||
const windowMenu = template[4];
|
||||
strictAssert(windowMenu, 'Missing windowMenu');
|
||||
// Replace Window menu
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
template[4].submenu = [
|
||||
|
||||
windowMenu.submenu = [
|
||||
{
|
||||
label: i18n('icu:windowMenuMinimize'),
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
|
||||
@@ -174,7 +174,8 @@ async function lintMessages() {
|
||||
const key = topProp.key.value;
|
||||
|
||||
if (process.argv.includes('--test')) {
|
||||
const test = tests[key];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const test = tests[key]!;
|
||||
const actualErrors = reports.map(report => report.id);
|
||||
deepEqual(actualErrors, test.expectErrors);
|
||||
continue;
|
||||
|
||||
@@ -37,7 +37,8 @@ export default rule('wrapEmoji', context => {
|
||||
return;
|
||||
}
|
||||
|
||||
const child = element.children[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const child = element.children[0]!;
|
||||
if (!isLiteralElement(child)) {
|
||||
// non-literal
|
||||
context.report(
|
||||
|
||||
@@ -1587,9 +1587,7 @@ export class ConversationController {
|
||||
`forceRerender: Starting to loop through ${conversations.length} conversations`
|
||||
);
|
||||
|
||||
for (let i = 0, max = conversations.length; i < max; i += 1) {
|
||||
const conversation = conversations[i];
|
||||
|
||||
for (const conversation of conversations) {
|
||||
if (conversation.cachedProps) {
|
||||
conversation.oldCachedProps = conversation.cachedProps;
|
||||
conversation.cachedProps = null;
|
||||
|
||||
+10
-7
@@ -357,8 +357,8 @@ export function verifyHmacSha256(
|
||||
let result = 0;
|
||||
|
||||
for (let i = 0; i < theirMac.byteLength; i += 1) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
result |= ourMac[i] ^ theirMac[i];
|
||||
// eslint-disable-next-line no-bitwise, @typescript-eslint/no-non-null-assertion
|
||||
result |= ourMac[i]! ^ theirMac[i]!;
|
||||
}
|
||||
if (result !== 0) {
|
||||
throw new Error('Bad MAC');
|
||||
@@ -482,8 +482,8 @@ function verifyDigest(data: Uint8Array, theirDigest: Uint8Array): void {
|
||||
const ourDigest = sha256(data);
|
||||
let result = 0;
|
||||
for (let i = 0; i < theirDigest.byteLength; i += 1) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
result |= ourDigest[i] ^ theirDigest[i];
|
||||
// eslint-disable-next-line no-bitwise, @typescript-eslint/no-non-null-assertion
|
||||
result |= ourDigest[i]! ^ theirDigest[i]!;
|
||||
}
|
||||
if (result !== 0) {
|
||||
throw new Error('Bad digest');
|
||||
@@ -744,7 +744,8 @@ export function getIdentifierHash({
|
||||
}
|
||||
|
||||
const digest = hash(HashType.size256, identifier);
|
||||
return digest[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return digest[0]!;
|
||||
}
|
||||
|
||||
export function generateAvatarColor({
|
||||
@@ -761,8 +762,10 @@ export function generateAvatarColor({
|
||||
const hashValue = getIdentifierHash({ aci, e164, pni, groupId });
|
||||
|
||||
if (hashValue == null) {
|
||||
return sample(AvatarColors) || AvatarColors[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return sample(AvatarColors) || AvatarColors[0]!;
|
||||
}
|
||||
|
||||
return AvatarColors[hashValue % AVATAR_COLOR_COUNT];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return AvatarColors[hashValue % AVATAR_COLOR_COUNT]!;
|
||||
}
|
||||
|
||||
+7
-6
@@ -145,10 +145,11 @@ export function setPublicKeyTypeByte(publicKey: Uint8Array): void {
|
||||
}
|
||||
|
||||
export function clampPrivateKey(privateKey: Uint8Array): void {
|
||||
// eslint-disable-next-line no-bitwise, no-param-reassign
|
||||
privateKey[0] &= 248;
|
||||
// eslint-disable-next-line no-bitwise, no-param-reassign
|
||||
privateKey[31] &= 127;
|
||||
// eslint-disable-next-line no-bitwise, no-param-reassign
|
||||
privateKey[31] |= 64;
|
||||
/* eslint-disable no-bitwise, no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
privateKey[0]! &= 248;
|
||||
privateKey[31]! &= 127;
|
||||
privateKey[31]! |= 64;
|
||||
/* eslint-enable no-bitwise, no-param-reassign */
|
||||
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||
}
|
||||
|
||||
@@ -166,7 +166,9 @@ export function onChange(
|
||||
listeners[key] = keyListeners;
|
||||
|
||||
return () => {
|
||||
listeners[key] = listeners[key].filter(l => l !== fn);
|
||||
if (listeners[key]) {
|
||||
listeners[key] = listeners[key].filter(l => l !== fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -176,8 +176,7 @@ async function _fillCaches<ID, T extends HasIdType<ID>, HydratedType>(
|
||||
const items = await itemsPromise;
|
||||
|
||||
const cache = new Map<ID, CacheEntryType<T, HydratedType>>();
|
||||
for (let i = 0, max = items.length; i < max; i += 1) {
|
||||
const fromDB = items[i];
|
||||
for (const fromDB of items) {
|
||||
const { id } = fromDB;
|
||||
|
||||
cache.set(id, {
|
||||
@@ -304,12 +303,12 @@ export class SignalProtocolStore extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const serviceId of Object.keys(map.value)) {
|
||||
for (const [serviceId, keyPair] of Object.entries(map.value)) {
|
||||
strictAssert(
|
||||
isServiceIdString(serviceId),
|
||||
'Invalid identity key serviceId'
|
||||
);
|
||||
const { privKey, pubKey } = map.value[serviceId];
|
||||
const { privKey, pubKey } = keyPair;
|
||||
const privateKey = PrivateKey.deserialize(privKey);
|
||||
const publicKey = PublicKey.deserialize(pubKey);
|
||||
this.#ourIdentityKeys.set(
|
||||
@@ -327,12 +326,12 @@ export class SignalProtocolStore extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const serviceId of Object.keys(map.value)) {
|
||||
for (const [serviceId, registrationId] of Object.entries(map.value)) {
|
||||
strictAssert(
|
||||
isServiceIdString(serviceId),
|
||||
'Invalid registration id serviceId'
|
||||
);
|
||||
this.#ourRegistrationIds.set(serviceId, map.value[serviceId]);
|
||||
this.#ourRegistrationIds.set(serviceId, registrationId);
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
@@ -1670,10 +1669,7 @@ export class SignalProtocolStore extends EventEmitter {
|
||||
`removeSessionsByConversation: Conversation not found: ${identifier}`
|
||||
);
|
||||
|
||||
const entries = Array.from(this.sessions.values());
|
||||
|
||||
for (let i = 0, max = entries.length; i < max; i += 1) {
|
||||
const entry = entries[i];
|
||||
for (const entry of this.sessions.values()) {
|
||||
if (entry.fromDB.conversationId === id) {
|
||||
this.sessions.delete(entry.fromDB.id);
|
||||
this.#pendingSessions.delete(entry.fromDB.id);
|
||||
@@ -1695,10 +1691,7 @@ export class SignalProtocolStore extends EventEmitter {
|
||||
|
||||
log.info('removeSessionsByServiceId: deleting sessions for', serviceId);
|
||||
|
||||
const entries = Array.from(this.sessions.values());
|
||||
|
||||
for (let i = 0, max = entries.length; i < max; i += 1) {
|
||||
const entry = entries[i];
|
||||
for (const entry of this.sessions.values()) {
|
||||
if (entry.fromDB.serviceId === serviceId) {
|
||||
this.sessions.delete(entry.fromDB.id);
|
||||
this.#pendingSessions.delete(entry.fromDB.id);
|
||||
|
||||
@@ -185,7 +185,8 @@ export function Icons(): JSX.Element {
|
||||
{size => (
|
||||
<AxoAvatar.Root size={size}>
|
||||
<AxoAvatar.Content label={null}>
|
||||
<AxoAvatar.Icon symbol={icons[size % icons.length]} />
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||
<AxoAvatar.Icon symbol={icons[size % icons.length]!} />
|
||||
</AxoAvatar.Content>
|
||||
</AxoAvatar.Root>
|
||||
)}
|
||||
|
||||
@@ -60,7 +60,8 @@ export namespace AxoTokens {
|
||||
Number.isInteger(hash) && hash >= 0,
|
||||
'Hash must be positive integer'
|
||||
);
|
||||
return ALL_COLOR_NAMES[hash % ALL_COLOR_NAMES.length];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return ALL_COLOR_NAMES[hash % ALL_COLOR_NAMES.length]!;
|
||||
}
|
||||
|
||||
export type GradientValues = Readonly<{
|
||||
@@ -96,7 +97,8 @@ export namespace AxoTokens {
|
||||
Number.isInteger(hash) && hash >= 0,
|
||||
'Hash must be positive integer'
|
||||
);
|
||||
return Gradients[hash % Gradients.length];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return Gradients[hash % Gradients.length]!;
|
||||
}
|
||||
|
||||
export function getGradientsCount(): number {
|
||||
|
||||
@@ -92,7 +92,7 @@ export class GumVideoCapturer {
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = this.mediaStream.getVideoTracks()?.[0].getSettings();
|
||||
const settings = this.mediaStream.getVideoTracks()?.[0]?.getSettings();
|
||||
if (!settings?.width || !settings?.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,12 +58,5 @@ export function findBestMatchingCameraId(
|
||||
|
||||
// By default, pick the first non-IR camera (but allow the user to pick the
|
||||
// infrared if they so desire)
|
||||
if (matchingId.length > 0) {
|
||||
return matchingId[0].deviceId;
|
||||
}
|
||||
if (nonInfrared.length > 0) {
|
||||
return nonInfrared[0].deviceId;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return matchingId[0]?.deviceId ?? nonInfrared[0]?.deviceId;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { ListView } from './ListView.dom.js';
|
||||
import { ListTile } from './ListTile.dom.js';
|
||||
import type { ShowToastAction } from '../state/ducks/toast.preload.js';
|
||||
import { SizeObserver } from '../hooks/useSizeObserver.dom.js';
|
||||
import { strictAssert } from '../util/assert.std.js';
|
||||
|
||||
const { pick } = lodash;
|
||||
|
||||
@@ -111,6 +112,7 @@ export function AddUserToAnotherGroupModal({
|
||||
const handleGetRow = React.useCallback(
|
||||
(idx: number): GroupListItemConversationType => {
|
||||
const convo = filteredConversations[idx];
|
||||
strictAssert(convo, 'Missing conversation');
|
||||
|
||||
// these are always populated in the case of a group
|
||||
const memberships = convo.memberships ?? [];
|
||||
|
||||
@@ -12,11 +12,12 @@ import type { PreferredBadgeSelectorType } from '../state/selectors/badges.prelo
|
||||
import { tw } from '../axo/tw.dom.js';
|
||||
import type { AdminMembershipType } from '../state/selectors/conversations.dom.js';
|
||||
import { UserText } from './UserText.dom.js';
|
||||
import type { ContactNameColorType } from '../types/Colors.std.js';
|
||||
|
||||
type PropsType = {
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
groupAdmins: Array<AdminMembershipType>;
|
||||
memberColors: Map<string, string>;
|
||||
memberColors: Map<string, ContactNameColorType>;
|
||||
i18n: LocalizerType;
|
||||
showConversation: ShowConversationType;
|
||||
theme: ThemeType;
|
||||
|
||||
@@ -109,7 +109,8 @@ Default.play = async (context: any) => {
|
||||
const { args, canvasElement } = context;
|
||||
const canvas = within(canvasElement);
|
||||
const [avatar] = canvas.getAllByRole('button');
|
||||
await userEvent.click(avatar);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await userEvent.click(avatar!);
|
||||
await expect(args.onClick).toHaveBeenCalled();
|
||||
};
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ function BadgeDialogWithBadges({
|
||||
currentBadgeIndex = 0;
|
||||
currentBadge = firstBadge;
|
||||
} else {
|
||||
currentBadge = badges[currentBadgeIndex];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
currentBadge = badges[currentBadgeIndex]!;
|
||||
}
|
||||
|
||||
const setCurrentBadgeIndex = (index: number): void => {
|
||||
|
||||
@@ -36,6 +36,14 @@ import { allRemoteParticipants } from './CallScreen.dom.stories.js';
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
const [participant1, participant2, participant3, participant4] =
|
||||
allRemoteParticipants as [
|
||||
GroupCallRemoteParticipantType,
|
||||
GroupCallRemoteParticipantType,
|
||||
GroupCallRemoteParticipantType,
|
||||
GroupCallRemoteParticipantType,
|
||||
];
|
||||
|
||||
const getConversation = () =>
|
||||
getDefaultConversation({
|
||||
id: '3051234567',
|
||||
@@ -344,7 +352,7 @@ export function CallLinkLobbyParticipants1Known1Unknown(): React.JSX.Element {
|
||||
<CallManager
|
||||
{...createProps({
|
||||
activeCall: getActiveCallForCallLink({
|
||||
peekedParticipants: [allRemoteParticipants[0], getUnknownContact()],
|
||||
peekedParticipants: [participant1, getUnknownContact()],
|
||||
}),
|
||||
callLink: FAKE_CALL_LINK,
|
||||
})}
|
||||
@@ -359,7 +367,7 @@ export function CallLinkLobbyParticipants1Known2Unknown(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
peekedParticipants: [
|
||||
getUnknownContact(),
|
||||
allRemoteParticipants[0],
|
||||
participant1,
|
||||
getUnknownContact(),
|
||||
],
|
||||
}),
|
||||
@@ -370,9 +378,7 @@ export function CallLinkLobbyParticipants1Known2Unknown(): React.JSX.Element {
|
||||
}
|
||||
|
||||
export function CallLinkLobbyParticipants1Known12Unknown(): React.JSX.Element {
|
||||
const peekedParticipants: Array<ConversationType> = [
|
||||
allRemoteParticipants[0],
|
||||
];
|
||||
const peekedParticipants: Array<ConversationType> = [participant1];
|
||||
for (let n = 12; n > 0; n -= 1) {
|
||||
peekedParticipants.push(getUnknownContact());
|
||||
}
|
||||
@@ -412,8 +418,8 @@ export function CallLinkWithJoinRequestsOne(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
peekedParticipants: [allRemoteParticipants[0]],
|
||||
pendingParticipants: [allRemoteParticipants[1]],
|
||||
peekedParticipants: [participant1],
|
||||
pendingParticipants: [participant2],
|
||||
showParticipantsList: false,
|
||||
}),
|
||||
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||
@@ -429,7 +435,7 @@ export function CallLinkWithJoinRequestsTwo(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
peekedParticipants: [allRemoteParticipants[0]],
|
||||
peekedParticipants: [participant1],
|
||||
pendingParticipants: allRemoteParticipants.slice(1, 3),
|
||||
showParticipantsList: false,
|
||||
}),
|
||||
@@ -446,7 +452,7 @@ export function CallLinkWithJoinRequestsMany(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
peekedParticipants: [allRemoteParticipants[0]],
|
||||
peekedParticipants: [participant1],
|
||||
pendingParticipants: allRemoteParticipants.slice(1, 11),
|
||||
showParticipantsList: false,
|
||||
}),
|
||||
@@ -463,11 +469,11 @@ export function CallLinkWithJoinRequestUnknownContact(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
peekedParticipants: [allRemoteParticipants[0]],
|
||||
peekedParticipants: [participant1],
|
||||
pendingParticipants: [
|
||||
getUnknownContact(),
|
||||
allRemoteParticipants[1],
|
||||
allRemoteParticipants[2],
|
||||
participant2,
|
||||
participant3,
|
||||
],
|
||||
showParticipantsList: false,
|
||||
}),
|
||||
@@ -484,9 +490,9 @@ export function CallLinkWithJoinRequestsSystemContact(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
peekedParticipants: [allRemoteParticipants[0]],
|
||||
peekedParticipants: [participant1],
|
||||
pendingParticipants: [
|
||||
{ ...allRemoteParticipants[1], name: 'My System Contact Friend' },
|
||||
{ ...participant2, name: 'My System Contact Friend' },
|
||||
],
|
||||
showParticipantsList: false,
|
||||
}),
|
||||
@@ -503,11 +509,11 @@ export function CallLinkWithJoinRequestsSystemContactMany(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
peekedParticipants: [allRemoteParticipants[0]],
|
||||
peekedParticipants: [participant1],
|
||||
pendingParticipants: [
|
||||
{ ...allRemoteParticipants[1], name: 'My System Contact Friend' },
|
||||
allRemoteParticipants[2],
|
||||
allRemoteParticipants[3],
|
||||
{ ...participant2, name: 'My System Contact Friend' },
|
||||
participant3,
|
||||
participant4,
|
||||
],
|
||||
showParticipantsList: false,
|
||||
}),
|
||||
@@ -524,7 +530,7 @@ export function CallLinkWithJoinRequestsParticipantsOpen(): React.JSX.Element {
|
||||
activeCall: getActiveCallForCallLink({
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
peekedParticipants: [allRemoteParticipants[0]],
|
||||
peekedParticipants: [participant1],
|
||||
pendingParticipants: allRemoteParticipants.slice(1, 4),
|
||||
}),
|
||||
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
|
||||
@@ -541,7 +547,7 @@ export function CallLinkWithUnknownContacts(): React.JSX.Element {
|
||||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joined,
|
||||
remoteParticipants: [
|
||||
allRemoteParticipants[0],
|
||||
participant1,
|
||||
getUnknownParticipant(),
|
||||
getUnknownParticipant(),
|
||||
],
|
||||
|
||||
@@ -40,7 +40,8 @@ export function CallReactionBurstEmoji({
|
||||
(index: number) => {
|
||||
return {
|
||||
key: uuid(),
|
||||
value: values[index % values.length],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
value: values[index % values.length]!,
|
||||
springConfig: {
|
||||
mass: random(10, 20),
|
||||
tension: random(45, 60),
|
||||
|
||||
@@ -36,6 +36,7 @@ import { fakeGetGroupCallVideoFrameSource } from '../test-helpers/fakeGetGroupCa
|
||||
import { CallingToastProvider, useCallingToasts } from './CallingToast.dom.js';
|
||||
import type { CallingImageDataCache } from './CallManager.dom.js';
|
||||
import { MINUTE } from '../util/durations/index.std.js';
|
||||
import { strictAssert } from '../util/assert.std.js';
|
||||
|
||||
const { sample, shuffle, times } = lodash;
|
||||
|
||||
@@ -542,7 +543,7 @@ export const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => {
|
||||
: ''
|
||||
} ${index + 1}`,
|
||||
}),
|
||||
};
|
||||
} satisfies GroupCallRemoteParticipantType;
|
||||
});
|
||||
|
||||
export function GroupCallManyPaginated(): React.JSX.Element {
|
||||
@@ -851,6 +852,7 @@ export function GroupCallReactionsManyInOrder(): React.JSX.Element {
|
||||
DEFAULT_PREFERRED_REACTION_EMOJI[
|
||||
i % DEFAULT_PREFERRED_REACTION_EMOJI.length
|
||||
];
|
||||
strictAssert(value, 'Missing value');
|
||||
return { timestamp, demuxId, value };
|
||||
});
|
||||
const [props] = React.useState(
|
||||
@@ -886,7 +888,9 @@ function useReactionsEmitter({
|
||||
const participantIndex = Math.floor(
|
||||
Math.random() * call.remoteParticipants.length
|
||||
);
|
||||
const { demuxId } = call.remoteParticipants[participantIndex];
|
||||
const participant = call.remoteParticipants[participantIndex];
|
||||
strictAssert(participant, 'Missing participant');
|
||||
const { demuxId } = participant;
|
||||
|
||||
const reactions: ActiveCallReactionsType = [
|
||||
...(state.reactions ?? []).filter(
|
||||
@@ -986,9 +990,11 @@ function useHandRaiser(
|
||||
);
|
||||
|
||||
const raisedHands = new Set(
|
||||
participantIndices.map(
|
||||
index => call.remoteParticipants[index].demuxId
|
||||
)
|
||||
participantIndices.map(index => {
|
||||
const participant = call.remoteParticipants[index];
|
||||
strictAssert(participant, 'Missing participant');
|
||||
return participant.demuxId;
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -483,7 +483,8 @@ export function CallScreen({
|
||||
isRinging =
|
||||
activeCall.outgoingRing &&
|
||||
!activeCall.remoteParticipants.length &&
|
||||
!(groupMembers?.length === 1 && groupMembers[0].id === me.id);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
!(groupMembers?.length === 1 && groupMembers[0]!.id === me.id);
|
||||
hasCallStarted = activeCall.joinState !== GroupCallJoinState.NotJoined;
|
||||
participantCount = activeCall.remoteParticipants.length + 1;
|
||||
conversationsByDemuxId = activeCall.conversationsByDemuxId;
|
||||
|
||||
@@ -80,7 +80,8 @@ function createAudioChangeHandler(
|
||||
return (value: string): void => {
|
||||
changeIODevice({
|
||||
type,
|
||||
selectedDevice: devices[Number(value)],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
selectedDevice: devices[Number(value)]!,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,12 +7,18 @@ import type { Meta } from '@storybook/react';
|
||||
import type { PropsType } from './CallingPendingParticipants.dom.js';
|
||||
import { CallingPendingParticipants } from './CallingPendingParticipants.dom.js';
|
||||
import { allRemoteParticipants } from './CallScreen.dom.stories.js';
|
||||
import { strictAssert } from '../util/assert.std.js';
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
strictAssert(allRemoteParticipants[0], 'Missing allRemoteParticipants[0]');
|
||||
strictAssert(allRemoteParticipants[1], 'Missing allRemoteParticipants[1]');
|
||||
const participant1 = allRemoteParticipants[0];
|
||||
const participant2 = allRemoteParticipants[1];
|
||||
|
||||
const createProps = (storyProps: Partial<PropsType> = {}): PropsType => ({
|
||||
i18n,
|
||||
participants: [allRemoteParticipants[0], allRemoteParticipants[1]],
|
||||
participants: [participant1, participant2],
|
||||
approveUser: action('approve-user'),
|
||||
batchUserAction: action('batch-user-action'),
|
||||
denyUser: action('deny-user'),
|
||||
@@ -32,7 +38,7 @@ export function One(): React.JSX.Element {
|
||||
return (
|
||||
<CallingPendingParticipants
|
||||
{...createProps({
|
||||
participants: [allRemoteParticipants[0]],
|
||||
participants: [participant1],
|
||||
})}
|
||||
/>
|
||||
);
|
||||
@@ -83,7 +89,7 @@ export function ExpandedOne(): React.JSX.Element {
|
||||
<CallingPendingParticipants
|
||||
{...createProps({
|
||||
defaultIsExpanded: true,
|
||||
participants: [allRemoteParticipants[0]],
|
||||
participants: [participant1],
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -204,7 +204,7 @@ export function CallingPip({
|
||||
distanceToRightEdge = innerWidth - (offsetX + width);
|
||||
}
|
||||
|
||||
const snapCandidates: Array<SnapCandidate> = [
|
||||
const snapCandidates = [
|
||||
{
|
||||
mode: PositionMode.SnapToLeft,
|
||||
distanceToEdge: distanceToLeftEdge,
|
||||
@@ -221,7 +221,7 @@ export function CallingPip({
|
||||
mode: PositionMode.SnapToBottom,
|
||||
distanceToEdge: innerHeight - (offsetY + height),
|
||||
},
|
||||
];
|
||||
] as const satisfies Array<SnapCandidate>;
|
||||
|
||||
// This fallback is mostly for TypeScript, because `minBy` says it can return
|
||||
// `undefined`.
|
||||
@@ -245,7 +245,7 @@ export function CallingPip({
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(snapTo.mode);
|
||||
throw missingCaseError(snapTo);
|
||||
}
|
||||
}
|
||||
}, [height, isRTL, positionState, setPositionState, width]);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import lodash from 'lodash';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { getDefaultConversation } from '../test-helpers/getDefaultConversation.std.js';
|
||||
import type { PropsType } from './CallingPreCallInfo.dom.js';
|
||||
@@ -12,8 +11,6 @@ import { generateAci } from '../types/ServiceId.std.js';
|
||||
import { FAKE_CALL_LINK } from '../test-helpers/fakeCallLink.std.js';
|
||||
import { callLinkToConversation } from '../util/callLinks.std.js';
|
||||
|
||||
const { times } = lodash;
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
const getDefaultGroupConversation = () =>
|
||||
getDefaultConversation({
|
||||
@@ -23,7 +20,14 @@ const getDefaultGroupConversation = () =>
|
||||
title: 'Tahoe Trip',
|
||||
type: 'group',
|
||||
});
|
||||
const otherMembers = times(6, () => getDefaultConversation());
|
||||
const otherMembers = [
|
||||
getDefaultConversation(),
|
||||
getDefaultConversation(),
|
||||
getDefaultConversation(),
|
||||
getDefaultConversation(),
|
||||
getDefaultConversation(),
|
||||
getDefaultConversation(),
|
||||
] as const;
|
||||
|
||||
const getUnknownContact = (): ConversationType => ({
|
||||
acceptedMessageRequest: false,
|
||||
|
||||
@@ -50,7 +50,7 @@ export type PropsType = {
|
||||
ringMode: RingMode;
|
||||
|
||||
// The following should only be set for group conversations.
|
||||
groupMembers?: Array<
|
||||
groupMembers?: ReadonlyArray<
|
||||
Pick<
|
||||
ConversationType,
|
||||
'id' | 'firstName' | 'systemGivenName' | 'systemNickname' | 'title'
|
||||
@@ -58,7 +58,7 @@ export type PropsType = {
|
||||
>;
|
||||
isCallFull?: boolean;
|
||||
isConnecting?: boolean;
|
||||
peekedParticipants?: Array<PeekedParticipantType>;
|
||||
peekedParticipants?: ReadonlyArray<PeekedParticipantType>;
|
||||
};
|
||||
|
||||
export function CallingPreCallInfo({
|
||||
@@ -110,36 +110,38 @@ export function CallingPreCallInfo({
|
||||
}
|
||||
return getParticipantName(participant);
|
||||
});
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
switch (participantNames.length) {
|
||||
case 1:
|
||||
subtitle = hasYou
|
||||
? i18n('icu:calling__pre-call-info--another-device-in-call')
|
||||
: i18n('icu:calling__pre-call-info--1-person-in-call', {
|
||||
first: participantNames[0],
|
||||
first: participantNames[0]!,
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
subtitle = i18n('icu:calling__pre-call-info--2-people-in-call', {
|
||||
first: participantNames[0],
|
||||
second: participantNames[1],
|
||||
first: participantNames[0]!,
|
||||
second: participantNames[1]!,
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
subtitle = i18n('icu:calling__pre-call-info--3-people-in-call', {
|
||||
first: participantNames[0],
|
||||
second: participantNames[1],
|
||||
third: participantNames[2],
|
||||
first: participantNames[0]!,
|
||||
second: participantNames[1]!,
|
||||
third: participantNames[2]!,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
subtitle = i18n('icu:calling__pre-call-info--many-people-in-call', {
|
||||
first: participantNames[0],
|
||||
second: participantNames[1],
|
||||
first: participantNames[0]!,
|
||||
second: participantNames[1]!,
|
||||
others: participantNames.length - 2,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||
} else {
|
||||
let memberNames: Array<string>;
|
||||
switch (conversation.type) {
|
||||
@@ -158,6 +160,7 @@ export function CallingPreCallInfo({
|
||||
|
||||
const ring = ringMode === RingMode.WillRing;
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
switch (memberNames.length) {
|
||||
case 0:
|
||||
subtitle = i18n('icu:calling__pre-call-info--empty-group');
|
||||
@@ -165,55 +168,56 @@ export function CallingPreCallInfo({
|
||||
case 1: {
|
||||
subtitle = ring
|
||||
? i18n('icu:calling__pre-call-info--will-ring-1', {
|
||||
person: memberNames[0],
|
||||
person: memberNames[0]!,
|
||||
})
|
||||
: i18n('icu:calling__pre-call-info--will-notify-1', {
|
||||
person: memberNames[0],
|
||||
person: memberNames[0]!,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
subtitle = ring
|
||||
? i18n('icu:calling__pre-call-info--will-ring-2', {
|
||||
first: memberNames[0],
|
||||
second: memberNames[1],
|
||||
first: memberNames[0]!,
|
||||
second: memberNames[1]!,
|
||||
})
|
||||
: i18n('icu:calling__pre-call-info--will-notify-2', {
|
||||
first: memberNames[0],
|
||||
second: memberNames[1],
|
||||
first: memberNames[0]!,
|
||||
second: memberNames[1]!,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
subtitle = ring
|
||||
? i18n('icu:calling__pre-call-info--will-ring-3', {
|
||||
first: memberNames[0],
|
||||
second: memberNames[1],
|
||||
third: memberNames[2],
|
||||
first: memberNames[0]!,
|
||||
second: memberNames[1]!,
|
||||
third: memberNames[2]!,
|
||||
})
|
||||
: i18n('icu:calling__pre-call-info--will-notify-3', {
|
||||
first: memberNames[0],
|
||||
second: memberNames[1],
|
||||
third: memberNames[2],
|
||||
first: memberNames[0]!,
|
||||
second: memberNames[1]!,
|
||||
third: memberNames[2]!,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
subtitle = ring
|
||||
? i18n('icu:calling__pre-call-info--will-ring-many', {
|
||||
first: memberNames[0],
|
||||
second: memberNames[1],
|
||||
first: memberNames[0]!,
|
||||
second: memberNames[1]!,
|
||||
others: memberNames.length - 2,
|
||||
})
|
||||
: i18n('icu:calling__pre-call-info--will-notify-many', {
|
||||
first: memberNames[0],
|
||||
second: memberNames[1],
|
||||
first: memberNames[0]!,
|
||||
second: memberNames[1]!,
|
||||
others: memberNames.length - 2,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
return (
|
||||
<div className="module-CallingPreCallInfo">
|
||||
|
||||
@@ -235,7 +235,8 @@ export function CallingToastProvider({
|
||||
toast => toast.key === item.key
|
||||
);
|
||||
const isToastReplacingAnExistingOneAtThisPosition = toastsRemoved.has(
|
||||
previousToasts[enteringItemIndex]
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
previousToasts[enteringItemIndex]!
|
||||
);
|
||||
return {
|
||||
...transitionFrom,
|
||||
@@ -275,7 +276,8 @@ export function CallingToastProvider({
|
||||
toast => toast.key === item.key
|
||||
);
|
||||
const isToastBeingReplacedByANewOneAtThisPosition = toastsAdded.has(
|
||||
toasts[leavingItemIndex]
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
toasts[leavingItemIndex]!
|
||||
);
|
||||
return {
|
||||
zIndex: 0,
|
||||
|
||||
@@ -196,8 +196,7 @@ export function ChatColorPicker({
|
||||
ref={i === 0 ? focusRef : undefined}
|
||||
/>
|
||||
))}
|
||||
{Object.keys(customColors).map(colorId => {
|
||||
const colorValues = customColors[colorId];
|
||||
{Object.entries(customColors).map(([colorId, colorValues]) => {
|
||||
return (
|
||||
<CustomColorBubble
|
||||
color={colorValues}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext.st
|
||||
import { fakeDraftAttachment } from '../test-helpers/fakeAttachment.std.js';
|
||||
import { landscapeGreenUrl } from '../storybook/Fixtures.std.js';
|
||||
import { RecordingState } from '../types/AudioRecorder.std.js';
|
||||
import type { ContactNameColorType } from '../types/Colors.std.js';
|
||||
import { ContactNameColors, ConversationColors } from '../types/Colors.std.js';
|
||||
import { getDefaultConversation } from '../test-helpers/getDefaultConversation.std.js';
|
||||
import { PaymentEventKind } from '../types/Payment.std.js';
|
||||
@@ -44,11 +45,15 @@ const groupAdmins = [
|
||||
];
|
||||
const memberColors = new Map(
|
||||
groupAdmins
|
||||
.map((admin, i): [string, string] | null => {
|
||||
.map((admin, i): [string, ContactNameColorType] | null => {
|
||||
if (!admin.member.id) {
|
||||
return null;
|
||||
}
|
||||
return [admin.member.id?.toString(), ContactNameColors[i]];
|
||||
return [
|
||||
admin.member.id?.toString(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
ContactNameColors[i % ContactNameColors.length]!,
|
||||
];
|
||||
})
|
||||
.filter(isNotNil)
|
||||
);
|
||||
|
||||
@@ -91,6 +91,7 @@ import type { PollCreateType } from '../types/Polls.dom.js';
|
||||
import { PollCreateModal } from './PollCreateModal.dom.js';
|
||||
import { useDocumentKeyDown } from '../hooks/useDocumentKeyDown.dom.js';
|
||||
import { hasDraft } from '../util/hasDraft.std.js';
|
||||
import type { ContactNameColorType } from '../types/Colors.std.js';
|
||||
|
||||
export type OwnProps = Readonly<{
|
||||
acceptedMessageRequest: boolean | null;
|
||||
@@ -139,7 +140,7 @@ export type OwnProps = Readonly<{
|
||||
lastEditableMessageId: string | null;
|
||||
recordingState: RecordingState;
|
||||
messageCompositionId: string;
|
||||
memberColors: Map<string, string>;
|
||||
memberColors: Map<string, ContactNameColorType>;
|
||||
shouldHidePopovers: boolean | null;
|
||||
isMuted: boolean;
|
||||
isSmsOnlyOrUnregistered: boolean | null;
|
||||
@@ -1101,7 +1102,11 @@ export const CompositionArea = memo(function CompositionArea({
|
||||
return renderSmartCompositionRecording();
|
||||
}
|
||||
|
||||
if (draftAttachments.length === 1 && isVoiceMessage(draftAttachments[0])) {
|
||||
if (
|
||||
draftAttachments.length === 1 &&
|
||||
draftAttachments[0] != null &&
|
||||
isVoiceMessage(draftAttachments[0])
|
||||
) {
|
||||
const voiceNoteAttachment = draftAttachments[0];
|
||||
|
||||
if (!voiceNoteAttachment.pending && voiceNoteAttachment.url) {
|
||||
|
||||
@@ -934,9 +934,9 @@ export function CompositionInput(props: Props): React.ReactElement {
|
||||
key: tabKey,
|
||||
handler: () => callbacksRef.current.onTab(),
|
||||
});
|
||||
const ourHandler = quill.keyboard.bindings[tabKey].pop();
|
||||
const ourHandler = quill.keyboard.bindings[tabKey]?.pop();
|
||||
if (ourHandler) {
|
||||
quill.keyboard.bindings[tabKey].unshift(ourHandler);
|
||||
quill.keyboard.bindings[tabKey]?.unshift(ourHandler);
|
||||
}
|
||||
|
||||
const emojiCompletion = quill.getModule('emojiCompletion');
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import lodash from 'lodash';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
@@ -13,8 +12,6 @@ import { ContactPill } from './ContactPill.dom.js';
|
||||
import { gifUrl } from '../storybook/Fixtures.std.js';
|
||||
import { getDefaultConversation } from '../test-helpers/getDefaultConversation.std.js';
|
||||
|
||||
const { times } = lodash;
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
export default {
|
||||
@@ -23,15 +20,15 @@ export default {
|
||||
|
||||
type ContactType = Omit<ContactPillPropsType, 'i18n' | 'onClickRemove'>;
|
||||
|
||||
const contacts: Array<ContactType> = times(50, index =>
|
||||
getDefaultConversation({
|
||||
function createContact(index: number) {
|
||||
return getDefaultConversation({
|
||||
id: `contact-${index}`,
|
||||
name: `Contact ${index}`,
|
||||
phoneNumber: '(202) 555-0001',
|
||||
profileName: `C${index}`,
|
||||
title: `Contact ${index}`,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const contactPillProps = (
|
||||
overrideProps?: ContactType
|
||||
@@ -67,9 +64,9 @@ export function OneContact(): React.JSX.Element {
|
||||
export function ThreeContacts(): React.JSX.Element {
|
||||
return (
|
||||
<ContactPills>
|
||||
<ContactPill {...contactPillProps(contacts[0])} />
|
||||
<ContactPill {...contactPillProps(contacts[1])} />
|
||||
<ContactPill {...contactPillProps(contacts[2])} />
|
||||
<ContactPill {...contactPillProps(createContact(0))} />
|
||||
<ContactPill {...contactPillProps(createContact(1))} />
|
||||
<ContactPill {...contactPillProps(createContact(2))} />
|
||||
</ContactPills>
|
||||
);
|
||||
}
|
||||
@@ -77,16 +74,16 @@ export function ThreeContacts(): React.JSX.Element {
|
||||
export function FourContactsOneWithALongName(): React.JSX.Element {
|
||||
return (
|
||||
<ContactPills>
|
||||
<ContactPill {...contactPillProps(contacts[0])} />
|
||||
<ContactPill {...contactPillProps(createContact(0))} />
|
||||
<ContactPill
|
||||
{...contactPillProps({
|
||||
...contacts[1],
|
||||
...createContact(1),
|
||||
title:
|
||||
'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
|
||||
})}
|
||||
/>
|
||||
<ContactPill {...contactPillProps(contacts[2])} />
|
||||
<ContactPill {...contactPillProps(contacts[3])} />
|
||||
<ContactPill {...contactPillProps(createContact(2))} />
|
||||
<ContactPill {...contactPillProps(createContact(3))} />
|
||||
</ContactPills>
|
||||
);
|
||||
}
|
||||
@@ -94,8 +91,8 @@ export function FourContactsOneWithALongName(): React.JSX.Element {
|
||||
export function FiftyContacts(): React.JSX.Element {
|
||||
return (
|
||||
<ContactPills>
|
||||
{contacts.map(contact => (
|
||||
<ContactPill key={contact.id} {...contactPillProps(contact)} />
|
||||
{Array.from({ length: 50 }, (_, index) => (
|
||||
<ContactPill key={index} {...contactPillProps(createContact(index))} />
|
||||
))}
|
||||
</ContactPills>
|
||||
);
|
||||
|
||||
@@ -191,7 +191,8 @@ export function ContextMenu<T>({
|
||||
|
||||
if (ev.key === 'Enter') {
|
||||
if (focusedIndex !== undefined) {
|
||||
const focusedOption = menuOptions[focusedIndex];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const focusedOption = menuOptions[focusedIndex]!;
|
||||
focusedOption.onClick(focusedOption.value);
|
||||
}
|
||||
setIsMenuShowing(false);
|
||||
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
args: {},
|
||||
} satisfies Meta<PropsType>;
|
||||
|
||||
const defaultConversations: Array<ConversationListItemPropsType> = [
|
||||
const defaultConversations = [
|
||||
getDefaultConversation({
|
||||
id: 'fred-convo',
|
||||
title: 'Fred Willard',
|
||||
@@ -48,7 +48,7 @@ const defaultConversations: Array<ConversationListItemPropsType> = [
|
||||
'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso',
|
||||
}),
|
||||
getDefaultConversation(),
|
||||
];
|
||||
] as const satisfies Array<ConversationListItemPropsType>;
|
||||
|
||||
function Wrapper({
|
||||
rows,
|
||||
|
||||
@@ -23,6 +23,7 @@ import { TimelineDateHeader } from './conversation/TimelineDateHeader.dom.js';
|
||||
import { AxoContextMenu } from '../axo/AxoContextMenu.dom.js';
|
||||
import { drop } from '../util/drop.std.js';
|
||||
import type { AxoMenuBuilder } from '../axo/AxoMenuBuilder.dom.js';
|
||||
import { strictAssert } from '../util/assert.std.js';
|
||||
|
||||
const { noop } = lodash;
|
||||
|
||||
@@ -115,6 +116,7 @@ export function EditHistoryMessagesModal({
|
||||
>({});
|
||||
|
||||
const [currentMessage, ...pastEdits] = editHistoryMessages;
|
||||
strictAssert(currentMessage, 'Missing currentMessage');
|
||||
const currentMessageId = `${currentMessage.id}.${currentMessage.timestamp}`;
|
||||
|
||||
let previousItem = currentMessage;
|
||||
|
||||
@@ -192,9 +192,12 @@ export function GroupCallRemoteParticipants({
|
||||
}
|
||||
|
||||
if (isInSpeakerView) {
|
||||
const firstParticipiant = prioritySortedParticipants[0];
|
||||
strictAssert(firstParticipiant, 'Missing firstParticipiant');
|
||||
|
||||
return [
|
||||
{
|
||||
rows: [[prioritySortedParticipants[0]]],
|
||||
rows: [[firstParticipiant]],
|
||||
hasSpaceRemaining: false,
|
||||
numParticipants: 1,
|
||||
},
|
||||
@@ -222,7 +225,7 @@ export function GroupCallRemoteParticipants({
|
||||
|
||||
const isSomeonePresenting =
|
||||
prioritySortedParticipants.length &&
|
||||
prioritySortedParticipants[0].presenting;
|
||||
prioritySortedParticipants[0]?.presenting;
|
||||
|
||||
// Make sure we're not on a page that no longer exists (e.g. if people left the call)
|
||||
if (
|
||||
@@ -750,6 +753,10 @@ function getGridParticipantsByPage({
|
||||
PARTICIPANTS_TO_REMOVE_PER_ATTEMPT.length - 1
|
||||
)
|
||||
];
|
||||
strictAssert(
|
||||
numLeastPrioritizedParticipantsToRemove,
|
||||
'Missing numLeastPrioritizedParticipantsToRemove'
|
||||
);
|
||||
|
||||
const leastPrioritizedParticipantIds = new Set(
|
||||
priorityParticipantsOnNextPage
|
||||
@@ -804,7 +811,9 @@ function getGridParticipantsByPage({
|
||||
|
||||
// Add a previous page tile if needed
|
||||
if (pages.length > 0) {
|
||||
nextPageTiles.rows[0].unshift({
|
||||
const firstRow = nextPageTiles.rows[0];
|
||||
strictAssert(firstRow, 'Missing firstRow');
|
||||
firstRow.unshift({
|
||||
isPaginationButton: true,
|
||||
paginationButtonType: 'prev',
|
||||
videoAspectRatio: PAGINATION_BUTTON_ASPECT_RATIO,
|
||||
@@ -877,11 +886,11 @@ function getNextPage({
|
||||
// Initialize fresh page with empty first row
|
||||
const rows: Array<Array<GroupCallRemoteParticipantType>> = [[]];
|
||||
let row = rows[0];
|
||||
strictAssert(row, 'Missing row');
|
||||
let numParticipants = 0;
|
||||
|
||||
// Start looping through participants and adding them to the rows one-by-one
|
||||
for (let i = 0; i < participants.length; i += 1) {
|
||||
const participant = participants[i];
|
||||
for (const [i, participant] of participants.entries()) {
|
||||
const isLastParticipant =
|
||||
!isSubsetOfAllParticipants && i === participants.length - 1;
|
||||
|
||||
|
||||
@@ -54,33 +54,35 @@ function MemberList({
|
||||
i18n: LocalizerType;
|
||||
onOtherMembersClick?: () => void;
|
||||
}): React.JSX.Element {
|
||||
const [member1, member2, member3] = firstThreeMemberNames;
|
||||
|
||||
if (areWeInGroup) {
|
||||
if (otherMemberNames.length === 0) {
|
||||
if (member1 == null) {
|
||||
return (
|
||||
<I18n i18n={i18n} id="icu:ConversationHero--group-members-only-you" />
|
||||
);
|
||||
}
|
||||
|
||||
if (otherMemberNames.length === 1) {
|
||||
if (member2 == null) {
|
||||
return (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:ConversationHero--group-members-one-and-you"
|
||||
components={{
|
||||
member: firstThreeMemberNames[0],
|
||||
member: member1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (otherMemberNames.length === 2) {
|
||||
if (member3 == null) {
|
||||
return (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:ConversationHero--group-members-two-and-you"
|
||||
components={{
|
||||
member1: firstThreeMemberNames[0],
|
||||
member2: firstThreeMemberNames[1],
|
||||
member1,
|
||||
member2,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -93,9 +95,9 @@ function MemberList({
|
||||
i18n={i18n}
|
||||
id="icu:ConversationHero--group-members-other-and-you"
|
||||
components={{
|
||||
member1: firstThreeMemberNames[0],
|
||||
member2: firstThreeMemberNames[1],
|
||||
member3: firstThreeMemberNames[2],
|
||||
member1,
|
||||
member2,
|
||||
member3,
|
||||
clickable: (parts: ReactNode) =>
|
||||
renderClickableButton(parts, onOtherMembersClick),
|
||||
remainingCount,
|
||||
@@ -106,30 +108,30 @@ function MemberList({
|
||||
|
||||
// When the user is not in the group
|
||||
|
||||
if (otherMemberNames.length === 0) {
|
||||
if (member1 == null) {
|
||||
return <I18n i18n={i18n} id="icu:ConversationHero--group-members-zero" />;
|
||||
}
|
||||
|
||||
if (otherMemberNames.length === 1) {
|
||||
if (member2 == null) {
|
||||
return (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:ConversationHero--group-members-one"
|
||||
components={{
|
||||
member: firstThreeMemberNames[0],
|
||||
member: member1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (otherMemberNames.length === 2) {
|
||||
if (member3 == null) {
|
||||
return (
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:ConversationHero--group-members-two"
|
||||
components={{
|
||||
member1: firstThreeMemberNames[0],
|
||||
member2: firstThreeMemberNames[1],
|
||||
member1,
|
||||
member2,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -141,9 +143,9 @@ function MemberList({
|
||||
i18n={i18n}
|
||||
id="icu:ConversationHero--group-members-three"
|
||||
components={{
|
||||
member1: firstThreeMemberNames[0],
|
||||
member2: firstThreeMemberNames[1],
|
||||
member3: firstThreeMemberNames[2],
|
||||
member1,
|
||||
member2,
|
||||
member3,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -156,9 +158,9 @@ function MemberList({
|
||||
i18n={i18n}
|
||||
id="icu:ConversationHero--group-members-other"
|
||||
components={{
|
||||
member1: firstThreeMemberNames[0],
|
||||
member2: firstThreeMemberNames[1],
|
||||
member3: firstThreeMemberNames[2],
|
||||
member1,
|
||||
member2,
|
||||
member3,
|
||||
clickable: (parts: ReactNode) =>
|
||||
renderClickableButton(parts, onOtherMembersClick),
|
||||
remainingCount,
|
||||
|
||||
@@ -123,6 +123,7 @@ function GroupCallMessage({
|
||||
.map(member => <UserText text={getParticipantName(member)} />);
|
||||
const ringerNode = <UserText text={getParticipantName(ringer)} />;
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
switch (otherMembersRung.length) {
|
||||
case 0:
|
||||
return (
|
||||
@@ -139,7 +140,8 @@ function GroupCallMessage({
|
||||
i18n={i18n}
|
||||
components={{
|
||||
ringer: ringerNode,
|
||||
otherMember: first,
|
||||
|
||||
otherMember: first!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -150,8 +152,8 @@ function GroupCallMessage({
|
||||
i18n={i18n}
|
||||
components={{
|
||||
ringer: ringerNode,
|
||||
first,
|
||||
second,
|
||||
first: first!,
|
||||
second: second!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -162,8 +164,8 @@ function GroupCallMessage({
|
||||
i18n={i18n}
|
||||
components={{
|
||||
ringer: ringerNode,
|
||||
first,
|
||||
second,
|
||||
first: first!,
|
||||
second: second!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -174,13 +176,14 @@ function GroupCallMessage({
|
||||
i18n={i18n}
|
||||
components={{
|
||||
ringer: ringerNode,
|
||||
first,
|
||||
second,
|
||||
first: first!,
|
||||
second: second!,
|
||||
remaining: otherMembersRung.length - 2,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||
}
|
||||
|
||||
export function IncomingCallBar(props: PropsType): React.JSX.Element | null {
|
||||
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
args: {},
|
||||
} satisfies Meta<PropsType>;
|
||||
|
||||
const defaultConversations: Array<ConversationType> = [
|
||||
const defaultConversations = [
|
||||
getDefaultConversation({
|
||||
id: 'fred-convo',
|
||||
title: 'Fred Willard',
|
||||
@@ -61,7 +61,7 @@ const defaultConversations: Array<ConversationType> = [
|
||||
isSelected: true,
|
||||
title: 'Marc Barraca',
|
||||
}),
|
||||
];
|
||||
] as const satisfies Array<ConversationType>;
|
||||
|
||||
const defaultSearchProps = {
|
||||
filterByUnread: false,
|
||||
|
||||
@@ -35,6 +35,7 @@ import { formatFileSize } from '../util/formatFileSize.std.js';
|
||||
import { SECOND } from '../util/durations/index.std.js';
|
||||
import { Toast } from './Toast.dom.js';
|
||||
import { isAbortError } from '../util/isAbortError.std.js';
|
||||
import { strictAssert } from '../util/assert.std.js';
|
||||
|
||||
const { noop } = lodash;
|
||||
|
||||
@@ -146,6 +147,7 @@ export function Lightbox({
|
||||
>();
|
||||
|
||||
const currentItem = media[selectedIndex];
|
||||
|
||||
const attachment = currentItem?.attachment;
|
||||
const url = attachment?.url;
|
||||
const incrementalUrl = attachment?.incrementalUrl;
|
||||
@@ -242,6 +244,7 @@ export function Lightbox({
|
||||
event.preventDefault();
|
||||
|
||||
const mediaItem = media[selectedIndex];
|
||||
strictAssert(mediaItem, 'Missing mediaItem');
|
||||
const { attachment: attachmentToSave, message, index } = mediaItem;
|
||||
|
||||
saveAttachment(attachmentToSave, message.sentAt, index + 1);
|
||||
@@ -261,6 +264,8 @@ export function Lightbox({
|
||||
|
||||
closeLightbox();
|
||||
const mediaItem = media[selectedIndex];
|
||||
strictAssert(mediaItem, 'Missing mediaItem');
|
||||
|
||||
toggleForwardMessagesModal({
|
||||
type: ForwardMessagesModalType.Forward,
|
||||
messageIds: [mediaItem.message.id],
|
||||
@@ -473,6 +478,7 @@ export function Lightbox({
|
||||
const handleTouchStart = useCallback(
|
||||
(ev: TouchEvent) => {
|
||||
const [touch] = ev.touches;
|
||||
strictAssert(touch, 'Missing touch');
|
||||
|
||||
dragCacheRef.current = {
|
||||
startX: touch.clientX,
|
||||
@@ -493,6 +499,7 @@ export function Lightbox({
|
||||
}
|
||||
|
||||
const [touch] = ev.touches;
|
||||
strictAssert(touch, 'Missing touch');
|
||||
|
||||
const deltaX = touch.clientX - dragCache.startX;
|
||||
const deltaY = touch.clientY - dragCache.startY;
|
||||
@@ -724,7 +731,7 @@ export function Lightbox({
|
||||
ref={focusRef}
|
||||
>
|
||||
<div className="Lightbox__header">
|
||||
{getConversation ? (
|
||||
{getConversation && currentItem != null ? (
|
||||
<LightboxHeader
|
||||
getConversation={getConversation}
|
||||
i18n={i18n}
|
||||
|
||||
@@ -54,7 +54,8 @@ const interactionTest: PlayFunction<ReactRenderer, PropsType> = async ({
|
||||
}) => {
|
||||
const canvas = within(canvasElement);
|
||||
const [btnDownload] = canvas.getAllByLabelText('Download story');
|
||||
await userEvent.click(btnDownload);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await userEvent.click(btnDownload!);
|
||||
await expect(args.onSave).toHaveBeenCalled();
|
||||
|
||||
const btnBack = canvas.getByText('Back');
|
||||
@@ -63,10 +64,12 @@ const interactionTest: PlayFunction<ReactRenderer, PropsType> = async ({
|
||||
|
||||
const [btnCtxMenu] = canvas.getAllByLabelText('Context menu');
|
||||
|
||||
await userEvent.click(btnCtxMenu);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await userEvent.click(btnCtxMenu!);
|
||||
await sleep(300);
|
||||
const [btnFwd] = canvas.getAllByLabelText('Forward');
|
||||
await userEvent.click(btnFwd);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await userEvent.click(btnFwd!);
|
||||
await expect(args.onForward).toHaveBeenCalled();
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ export type PropsType = {
|
||||
};
|
||||
|
||||
function getNewestMyStory(story: MyStoryType): StoryViewType {
|
||||
return story.stories[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return story.stories[0]!;
|
||||
}
|
||||
|
||||
export function MyStoryButton({
|
||||
@@ -45,7 +46,8 @@ export function MyStoryButton({
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
const newestStory = myStories.length
|
||||
? getNewestMyStory(myStories[0])
|
||||
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
getNewestMyStory(myStories[0]!)
|
||||
: undefined;
|
||||
|
||||
const { avatarUrl, color, profileName, title } = me;
|
||||
@@ -85,7 +87,8 @@ export function MyStoryButton({
|
||||
}
|
||||
|
||||
const hasMultiple = myStories.length
|
||||
? myStories[0].stories.length > 1
|
||||
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
myStories[0]!.stories.length > 1
|
||||
: false;
|
||||
|
||||
const reducedSendStatus: ResolvedSendStatus = myStories.reduce(
|
||||
|
||||
@@ -14,10 +14,10 @@ import { ThemeType } from '../types/Util.std.js';
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
const conversations: Array<ConversationType> = [
|
||||
const conversations = [
|
||||
getDefaultConversation({ title: 'Fred Willard' }),
|
||||
getDefaultConversation({ title: 'Marc Barraca' }),
|
||||
];
|
||||
] as const satisfies Array<ConversationType>;
|
||||
|
||||
export default {
|
||||
title: 'Components/NewlyCreatedGroupInvitedContactsDialog',
|
||||
|
||||
@@ -29,7 +29,8 @@ export function NewlyCreatedGroupInvitedContactsDialog({
|
||||
}: PropsType): React.JSX.Element {
|
||||
let body: ReactNode;
|
||||
if (contacts.length === 1) {
|
||||
const contact = contacts[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const contact = contacts[0]!;
|
||||
|
||||
body = (
|
||||
<>
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { NotificationProfileIdString } from '../types/NotificationProfile.s
|
||||
import { HOUR } from '../util/durations/index.std.js';
|
||||
import { AxoDropdownMenu } from '../axo/AxoDropdownMenu.dom.js';
|
||||
import { AxoButton } from '../axo/AxoButton.dom.js';
|
||||
import type { ConversationType } from '../state/ducks/conversations.preload.js';
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
@@ -25,6 +26,11 @@ const conversations = shuffle([
|
||||
...Array.from(Array(20), getDefaultConversation),
|
||||
]);
|
||||
|
||||
const [conversation1, conversation2] = conversations as [
|
||||
ConversationType,
|
||||
ConversationType,
|
||||
];
|
||||
|
||||
const threeProfiles = [
|
||||
{
|
||||
id: 'Weekday' as NotificationProfileIdString,
|
||||
@@ -37,7 +43,7 @@ const threeProfiles = [
|
||||
allowAllCalls: true,
|
||||
allowAllMentions: true,
|
||||
|
||||
allowedMembers: new Set([conversations[0].id, conversations[1].id]),
|
||||
allowedMembers: new Set([conversation1.id, conversation2.id]),
|
||||
scheduleEnabled: true,
|
||||
|
||||
scheduleStartTime: 1800,
|
||||
@@ -66,7 +72,7 @@ const threeProfiles = [
|
||||
allowAllCalls: true,
|
||||
allowAllMentions: true,
|
||||
|
||||
allowedMembers: new Set([conversations[0].id, conversations[1].id]),
|
||||
allowedMembers: new Set([conversation1.id, conversation2.id]),
|
||||
scheduleEnabled: true,
|
||||
|
||||
scheduleStartTime: 1800,
|
||||
@@ -95,7 +101,7 @@ const threeProfiles = [
|
||||
allowAllCalls: true,
|
||||
allowAllMentions: true,
|
||||
|
||||
allowedMembers: new Set([conversations[0].id, conversations[1].id]),
|
||||
allowedMembers: new Set([conversation1.id, conversation2.id]),
|
||||
scheduleEnabled: true,
|
||||
|
||||
scheduleStartTime: 1800,
|
||||
@@ -113,7 +119,7 @@ const threeProfiles = [
|
||||
deletedAtTimestampMs: undefined,
|
||||
storageNeedsSync: true,
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
export default {
|
||||
title: 'Components/NotificationProfilesMenu',
|
||||
|
||||
@@ -76,7 +76,7 @@ export function PlaybackRateButton({
|
||||
};
|
||||
|
||||
const label = playbackRate
|
||||
? playbackRateLabels[playbackRate].toString()
|
||||
? playbackRateLabels[playbackRate]?.toString()
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
@@ -105,7 +105,8 @@ const playbackRates = [1, 1.5, 2, 0.5];
|
||||
|
||||
PlaybackRateButton.nextPlaybackRate = (currentRate: number): number => {
|
||||
// cycle through the rates
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return playbackRates[
|
||||
(playbackRates.indexOf(currentRate) + 1) % playbackRates.length
|
||||
];
|
||||
]!;
|
||||
};
|
||||
|
||||
@@ -75,7 +75,7 @@ export function PollCreateModal({
|
||||
const isLastOption = changedIndex === resultOptions.length - 1;
|
||||
const isSecondToLast = changedIndex === resultOptions.length - 2;
|
||||
const changedOption = resultOptions[changedIndex];
|
||||
const hasText = changedOption?.value.trim().length > 0;
|
||||
const hasText = (changedOption?.value.trim().length ?? 0) > 0;
|
||||
const canAddMore = resultOptions.length < POLL_OPTIONS_MAX_COUNT;
|
||||
const canRemove = resultOptions.length > POLL_OPTIONS_MIN_COUNT;
|
||||
let removedIndex: number | undefined;
|
||||
|
||||
@@ -76,6 +76,10 @@ const conversations = shuffle([
|
||||
...Array.from(Array(20), getDefaultConversation),
|
||||
]);
|
||||
|
||||
const [conversation1, conversation2] = conversations;
|
||||
strictAssert(conversation1, 'Missing conversation1');
|
||||
strictAssert(conversation2, 'Missing conversation2');
|
||||
|
||||
function conversationSelector(conversationId?: string) {
|
||||
strictAssert(conversationId, 'Missing conversation id');
|
||||
const found = conversations.find(conversation => {
|
||||
@@ -734,7 +738,7 @@ const threeProfiles = [
|
||||
allowAllCalls: true,
|
||||
allowAllMentions: true,
|
||||
|
||||
allowedMembers: new Set([conversations[0].id, conversations[1].id]),
|
||||
allowedMembers: new Set([conversation1.id, conversation2.id]),
|
||||
scheduleEnabled: true,
|
||||
|
||||
scheduleStartTime: 1800,
|
||||
@@ -763,7 +767,7 @@ const threeProfiles = [
|
||||
allowAllCalls: true,
|
||||
allowAllMentions: true,
|
||||
|
||||
allowedMembers: new Set([conversations[0].id, conversations[1].id]),
|
||||
allowedMembers: new Set([conversation1.id, conversation2.id]),
|
||||
scheduleEnabled: true,
|
||||
|
||||
scheduleStartTime: 100,
|
||||
@@ -792,7 +796,7 @@ const threeProfiles = [
|
||||
allowAllCalls: true,
|
||||
allowAllMentions: true,
|
||||
|
||||
allowedMembers: new Set([conversations[0].id, conversations[1].id]),
|
||||
allowedMembers: new Set([conversation1.id, conversation2.id]),
|
||||
scheduleEnabled: true,
|
||||
|
||||
scheduleStartTime: 1800,
|
||||
@@ -810,7 +814,7 @@ const threeProfiles = [
|
||||
deletedAtTimestampMs: undefined,
|
||||
storageNeedsSync: true,
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
NotificationsPageWithThreeProfiles.args = {
|
||||
settingsLocation: { page: SettingsPage.Notifications },
|
||||
|
||||
@@ -243,7 +243,8 @@ const ARGB_BITS = 0xff000000;
|
||||
const A100_BACKGROUND_ARGB = 0xffe3e3fe;
|
||||
|
||||
function getRandomColor(): number {
|
||||
const colorName = sample(AvatarColors) || AvatarColors[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const colorName = sample(AvatarColors) || AvatarColors[0]!;
|
||||
const color = AvatarColorMap.get(colorName);
|
||||
if (!color) {
|
||||
return A100_BACKGROUND_ARGB; // A100, background, with bits for ARGB
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
ConversationDetailsIcon,
|
||||
IconType,
|
||||
} from './conversation/conversation-details/ConversationDetailsIcon.dom.js';
|
||||
import { isWhitespace, trim } from '../util/whitespaceStringUtil.std.js';
|
||||
import { UserText } from './UserText.dom.js';
|
||||
import { Tooltip, TooltipPlacement } from './Tooltip.dom.js';
|
||||
import { offsetDistanceModifier } from '../util/popperUtil.std.js';
|
||||
@@ -294,9 +293,9 @@ export function ProfileEditor({
|
||||
onProfileChanged(
|
||||
{
|
||||
...stagedProfile,
|
||||
firstName: trim(stagedProfile.firstName),
|
||||
firstName: stagedProfile.firstName.trim(),
|
||||
familyName: stagedProfile.familyName
|
||||
? trim(stagedProfile.familyName)
|
||||
? stagedProfile.familyName.trim()
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
@@ -376,7 +375,7 @@ export function ProfileEditor({
|
||||
!stagedProfile.firstName ||
|
||||
(stagedProfile.firstName === fullName.firstName &&
|
||||
stagedProfile.familyName === fullName.familyName) ||
|
||||
isWhitespace(stagedProfile.firstName);
|
||||
stagedProfile.firstName.trim() === '';
|
||||
|
||||
content = (
|
||||
<>
|
||||
|
||||
@@ -56,7 +56,8 @@ export function SafetyTipsModal({
|
||||
const [cardWrapperId] = useState(() => uuid());
|
||||
|
||||
function getCardIdForPage(pageIndex: number) {
|
||||
return `${cardWrapperId}_${pages[pageIndex].key}`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return `${cardWrapperId}_${pages[pageIndex]!.key}`;
|
||||
}
|
||||
|
||||
const maxPageIndex = pages.length - 1;
|
||||
|
||||
@@ -29,6 +29,7 @@ export function SharedGroupNames({
|
||||
</strong>
|
||||
));
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
if (sharedGroupNames.length >= 5) {
|
||||
const remainingCount = sharedGroupNames.length - 3;
|
||||
return (
|
||||
@@ -36,9 +37,9 @@ export function SharedGroupNames({
|
||||
i18n={i18n}
|
||||
id="icu:member-of-more-than-3-groups--multiple-more"
|
||||
components={{
|
||||
group1: firstThreeGroups[0],
|
||||
group2: firstThreeGroups[1],
|
||||
group3: firstThreeGroups[2],
|
||||
group1: firstThreeGroups[0]!,
|
||||
group2: firstThreeGroups[1]!,
|
||||
group3: firstThreeGroups[2]!,
|
||||
remainingCount,
|
||||
}}
|
||||
/>
|
||||
@@ -50,9 +51,9 @@ export function SharedGroupNames({
|
||||
i18n={i18n}
|
||||
id="icu:member-of-more-than-3-groups--one-more"
|
||||
components={{
|
||||
group1: firstThreeGroups[0],
|
||||
group2: firstThreeGroups[1],
|
||||
group3: firstThreeGroups[2],
|
||||
group1: firstThreeGroups[0]!,
|
||||
group2: firstThreeGroups[1]!,
|
||||
group3: firstThreeGroups[2]!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -63,9 +64,9 @@ export function SharedGroupNames({
|
||||
i18n={i18n}
|
||||
id="icu:member-of-3-groups"
|
||||
components={{
|
||||
group1: firstThreeGroups[0],
|
||||
group2: firstThreeGroups[1],
|
||||
group3: firstThreeGroups[2],
|
||||
group1: firstThreeGroups[0]!,
|
||||
group2: firstThreeGroups[1]!,
|
||||
group3: firstThreeGroups[2]!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -76,8 +77,8 @@ export function SharedGroupNames({
|
||||
i18n={i18n}
|
||||
id="icu:member-of-2-groups"
|
||||
components={{
|
||||
group1: firstThreeGroups[0],
|
||||
group2: firstThreeGroups[1],
|
||||
group1: firstThreeGroups[0]!,
|
||||
group2: firstThreeGroups[1]!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -88,11 +89,12 @@ export function SharedGroupNames({
|
||||
i18n={i18n}
|
||||
id="icu:member-of-1-group"
|
||||
components={{
|
||||
group: firstThreeGroups[0],
|
||||
group: firstThreeGroups[0]!,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
return <>{i18n('icu:no-groups-in-common')}</>;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ function getFont(
|
||||
textStyle?: TextAttachmentStyleType | null,
|
||||
i18n?: LocalizerType
|
||||
): string {
|
||||
const textStyleIndex = Number(textStyle) || 0;
|
||||
const textStyleIndex = textStyle ?? TextAttachmentStyleType.DEFAULT;
|
||||
const fontName = getFontNameByTextScript(text, textStyleIndex, i18n);
|
||||
|
||||
let fontSize = FONT_SIZE_SMALL;
|
||||
|
||||
@@ -122,9 +122,9 @@ async function doComputePeaks(
|
||||
) {
|
||||
const channel = data.getChannelData(channelNum);
|
||||
|
||||
for (let sample = 0; sample < channel.length; sample += 1) {
|
||||
for (const [sample, sampleData] of channel.entries()) {
|
||||
const i = Math.floor(sample / samplesPerPeak);
|
||||
peaks[i] += channel[sample] ** 2;
|
||||
peaks[i] += sampleData ** 2;
|
||||
norms[i] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ export function AttachmentList<
|
||||
<div className="module-attachments__rail">
|
||||
{attachments.map((attachment, index) => {
|
||||
const url = getUrl(attachment);
|
||||
const forUI = attachmentsForUI[index];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const forUI = attachmentsForUI[index]!;
|
||||
|
||||
const key =
|
||||
attachment.clientUuid ||
|
||||
|
||||
@@ -39,6 +39,7 @@ import { AxoSymbol } from '../../axo/AxoSymbol.dom.js';
|
||||
import { tw } from '../../axo/tw.dom.js';
|
||||
import { strictAssert } from '../../util/assert.std.js';
|
||||
import type { RemoveClientType } from '../../types/Calling.std.js';
|
||||
import type { ContactNameColorType } from '../../types/Colors.std.js';
|
||||
|
||||
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
||||
|
||||
@@ -52,7 +53,7 @@ export type PropsDataType = {
|
||||
contact?: ConversationType;
|
||||
contactLabelEmoji: string | undefined;
|
||||
contactLabelString: string | undefined;
|
||||
contactNameColor: string | undefined;
|
||||
contactNameColor: ContactNameColorType | undefined;
|
||||
conversation?: ConversationType;
|
||||
hasStories?: HasStories;
|
||||
readonly i18n: LocalizerType;
|
||||
|
||||
@@ -114,8 +114,8 @@ export function ConversationView({
|
||||
});
|
||||
if (allVisual) {
|
||||
const files: Array<File> = [];
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
const file = getAsFile(items[i]);
|
||||
for (const item of items) {
|
||||
const file = getAsFile(item);
|
||||
if (file) {
|
||||
files.push(file);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,8 @@ function GroupNotificationChange({
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:joinedTheGroup"
|
||||
components={{ name: otherPeople[0] }}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
components={{ name: otherPeople[0]! }}
|
||||
/>
|
||||
) : (
|
||||
<I18n
|
||||
@@ -123,7 +124,8 @@ function GroupNotificationChange({
|
||||
<I18n
|
||||
id="icu:multipleLeftTheGroup"
|
||||
i18n={i18n}
|
||||
components={{ name: otherPeople[0] }}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
components={{ name: otherPeople[0]! }}
|
||||
/>
|
||||
) : (
|
||||
<I18n
|
||||
|
||||
@@ -128,7 +128,8 @@ function renderUsers({
|
||||
}
|
||||
|
||||
if (members && count === 1) {
|
||||
const contact = <ContactName title={members[0].title} />;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const contact = <ContactName title={members[0]!.title} />;
|
||||
return (
|
||||
<p>
|
||||
{kind === 'invited' && (
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Image, CurveType } from './Image.dom.js';
|
||||
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util.std.js';
|
||||
import { AttachmentDetailPill } from './AttachmentDetailPill.dom.js';
|
||||
import { strictAssert } from '../../util/assert.std.js';
|
||||
|
||||
export type DirectionType = 'incoming' | 'outgoing';
|
||||
|
||||
@@ -158,10 +159,13 @@ export function ImageGrid({
|
||||
);
|
||||
|
||||
const showAttachmentOrNoLongerAvailableToast = React.useCallback(
|
||||
(attachmentIndex: number) =>
|
||||
attachments[attachmentIndex].isPermanentlyUndownloadable
|
||||
(attachmentIndex: number) => {
|
||||
const attachment = attachments[attachmentIndex];
|
||||
strictAssert(attachment, 'Missing attachment');
|
||||
return attachment.isPermanentlyUndownloadable
|
||||
? showMediaNoLongerAvailableToast
|
||||
: showVisualAttachment,
|
||||
: showVisualAttachment;
|
||||
},
|
||||
[attachments, showVisualAttachment, showMediaNoLongerAvailableToast]
|
||||
);
|
||||
|
||||
@@ -189,8 +193,10 @@ export function ImageGrid({
|
||||
});
|
||||
|
||||
if (attachments.length === 1 || !areAllAttachmentsVisual(attachments)) {
|
||||
const [attachment] = attachments;
|
||||
strictAssert(attachment, 'Missing attachment');
|
||||
const { height, width } = getImageDimensionsForTimeline(
|
||||
attachments[0],
|
||||
attachment,
|
||||
isSticker ? stickerSize : undefined
|
||||
);
|
||||
|
||||
@@ -203,10 +209,10 @@ export function ImageGrid({
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
alt={getAlt(attachments[0], i18n)}
|
||||
alt={getAlt(attachment, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[0].blurHash}
|
||||
blurHash={attachment.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={isSticker}
|
||||
noBackground={isSticker}
|
||||
@@ -214,13 +220,11 @@ export function ImageGrid({
|
||||
curveTopRight={curveTopRight}
|
||||
curveBottomLeft={curveBottomLeft}
|
||||
curveBottomRight={curveBottomRight}
|
||||
attachment={attachments[0]}
|
||||
playIconOverlay={isVideoAttachment(attachments[0])}
|
||||
attachment={attachment}
|
||||
playIconOverlay={isVideoAttachment(attachment)}
|
||||
height={height}
|
||||
width={width}
|
||||
url={
|
||||
getUrl(attachments[0]) ?? attachments[0].thumbnailFromBackup?.url
|
||||
}
|
||||
url={getUrl(attachment) ?? attachment.thumbnailFromBackup?.url}
|
||||
tabIndex={tabIndex}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(0)}
|
||||
@@ -234,23 +238,26 @@ export function ImageGrid({
|
||||
}
|
||||
|
||||
if (attachments.length === 2) {
|
||||
const [attachment1, attachment2] = attachments;
|
||||
strictAssert(attachment1, 'Missing attachment 1');
|
||||
strictAssert(attachment2, 'Missing attachment 2');
|
||||
return (
|
||||
<div className="module-image-grid">
|
||||
<Image
|
||||
alt={getAlt(attachments[0], i18n)}
|
||||
alt={getAlt(attachment1, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
attachment={attachments[0]}
|
||||
blurHash={attachments[0].blurHash}
|
||||
attachment={attachment1}
|
||||
blurHash={attachment1.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={false}
|
||||
curveTopLeft={curveTopLeft}
|
||||
curveBottomLeft={curveBottomLeft}
|
||||
playIconOverlay={isVideoAttachment(attachments[0])}
|
||||
playIconOverlay={isVideoAttachment(attachment1)}
|
||||
height={150}
|
||||
width={150}
|
||||
cropWidth={GAP}
|
||||
url={getThumbnailUrl(attachments[0])}
|
||||
url={getThumbnailUrl(attachment1)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(0)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -258,19 +265,19 @@ export function ImageGrid({
|
||||
onError={onError}
|
||||
/>
|
||||
<Image
|
||||
alt={getAlt(attachments[1], i18n)}
|
||||
alt={getAlt(attachment2, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[1].blurHash}
|
||||
blurHash={attachment2.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={false}
|
||||
curveTopRight={curveTopRight}
|
||||
curveBottomRight={curveBottomRight}
|
||||
playIconOverlay={isVideoAttachment(attachments[1])}
|
||||
playIconOverlay={isVideoAttachment(attachment2)}
|
||||
height={150}
|
||||
width={150}
|
||||
attachment={attachments[1]}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
attachment={attachment2}
|
||||
url={getThumbnailUrl(attachment2)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(1)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -284,23 +291,27 @@ export function ImageGrid({
|
||||
}
|
||||
|
||||
if (attachments.length === 3) {
|
||||
const [attachment1, attachment2, attachment3] = attachments;
|
||||
strictAssert(attachment1, 'Missing attachment 1');
|
||||
strictAssert(attachment2, 'Missing attachment 2');
|
||||
strictAssert(attachment3, 'Missing attachment 3');
|
||||
return (
|
||||
<div className="module-image-grid">
|
||||
<Image
|
||||
alt={getAlt(attachments[0], i18n)}
|
||||
alt={getAlt(attachment1, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[0].blurHash}
|
||||
blurHash={attachment1.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={false}
|
||||
curveTopLeft={curveTopLeft}
|
||||
curveBottomLeft={curveBottomLeft}
|
||||
attachment={attachments[0]}
|
||||
playIconOverlay={isVideoAttachment(attachments[0])}
|
||||
attachment={attachment1}
|
||||
playIconOverlay={isVideoAttachment(attachment1)}
|
||||
height={200}
|
||||
width={200}
|
||||
cropWidth={GAP}
|
||||
url={getUrl(attachments[0])}
|
||||
url={getUrl(attachment1)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(0)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -309,17 +320,17 @@ export function ImageGrid({
|
||||
/>
|
||||
<div className="module-image-grid__column">
|
||||
<Image
|
||||
alt={getAlt(attachments[1], i18n)}
|
||||
alt={getAlt(attachment2, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[1].blurHash}
|
||||
blurHash={attachment2.blurHash}
|
||||
curveTopRight={curveTopRight}
|
||||
height={100}
|
||||
width={100}
|
||||
cropHeight={GAP}
|
||||
attachment={attachments[1]}
|
||||
playIconOverlay={isVideoAttachment(attachments[1])}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
attachment={attachment2}
|
||||
playIconOverlay={isVideoAttachment(attachment2)}
|
||||
url={getThumbnailUrl(attachment2)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(1)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -327,18 +338,18 @@ export function ImageGrid({
|
||||
onError={onError}
|
||||
/>
|
||||
<Image
|
||||
alt={getAlt(attachments[2], i18n)}
|
||||
alt={getAlt(attachment3, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[2].blurHash}
|
||||
blurHash={attachment3.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={false}
|
||||
curveBottomRight={curveBottomRight}
|
||||
height={100}
|
||||
width={100}
|
||||
attachment={attachments[2]}
|
||||
playIconOverlay={isVideoAttachment(attachments[2])}
|
||||
url={getThumbnailUrl(attachments[2])}
|
||||
attachment={attachment3}
|
||||
playIconOverlay={isVideoAttachment(attachment3)}
|
||||
url={getThumbnailUrl(attachment3)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(2)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -353,24 +364,29 @@ export function ImageGrid({
|
||||
}
|
||||
|
||||
if (attachments.length === 4) {
|
||||
const [attachment1, attachment2, attachment3, attachment4] = attachments;
|
||||
strictAssert(attachment1, 'Missing attachment 1');
|
||||
strictAssert(attachment2, 'Missing attachment 2');
|
||||
strictAssert(attachment3, 'Missing attachment 3');
|
||||
strictAssert(attachment4, 'Missing attachment 4');
|
||||
return (
|
||||
<div className="module-image-grid">
|
||||
<div className="module-image-grid__column">
|
||||
<div className="module-image-grid__row">
|
||||
<Image
|
||||
alt={getAlt(attachments[0], i18n)}
|
||||
alt={getAlt(attachment1, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[0].blurHash}
|
||||
blurHash={attachment1.blurHash}
|
||||
curveTopLeft={curveTopLeft}
|
||||
noBorder={false}
|
||||
attachment={attachments[0]}
|
||||
playIconOverlay={isVideoAttachment(attachments[0])}
|
||||
attachment={attachment1}
|
||||
playIconOverlay={isVideoAttachment(attachment1)}
|
||||
height={150}
|
||||
width={150}
|
||||
cropHeight={GAP}
|
||||
cropWidth={GAP}
|
||||
url={getThumbnailUrl(attachments[0])}
|
||||
url={getThumbnailUrl(attachment1)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(0)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -378,18 +394,18 @@ export function ImageGrid({
|
||||
onError={onError}
|
||||
/>
|
||||
<Image
|
||||
alt={getAlt(attachments[1], i18n)}
|
||||
alt={getAlt(attachment2, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[1].blurHash}
|
||||
blurHash={attachment2.blurHash}
|
||||
curveTopRight={curveTopRight}
|
||||
playIconOverlay={isVideoAttachment(attachments[1])}
|
||||
playIconOverlay={isVideoAttachment(attachment2)}
|
||||
noBorder={false}
|
||||
height={150}
|
||||
width={150}
|
||||
cropHeight={GAP}
|
||||
attachment={attachments[1]}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
attachment={attachment2}
|
||||
url={getThumbnailUrl(attachment2)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(1)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -399,19 +415,19 @@ export function ImageGrid({
|
||||
</div>
|
||||
<div className="module-image-grid__row">
|
||||
<Image
|
||||
alt={getAlt(attachments[2], i18n)}
|
||||
alt={getAlt(attachment3, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[2].blurHash}
|
||||
blurHash={attachment3.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={false}
|
||||
curveBottomLeft={curveBottomLeft}
|
||||
playIconOverlay={isVideoAttachment(attachments[2])}
|
||||
playIconOverlay={isVideoAttachment(attachment3)}
|
||||
height={150}
|
||||
width={150}
|
||||
cropWidth={GAP}
|
||||
attachment={attachments[2]}
|
||||
url={getThumbnailUrl(attachments[2])}
|
||||
attachment={attachment3}
|
||||
url={getThumbnailUrl(attachment3)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(2)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -419,18 +435,18 @@ export function ImageGrid({
|
||||
onError={onError}
|
||||
/>
|
||||
<Image
|
||||
alt={getAlt(attachments[3], i18n)}
|
||||
alt={getAlt(attachment4, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[3].blurHash}
|
||||
blurHash={attachment4.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={false}
|
||||
curveBottomRight={curveBottomRight}
|
||||
playIconOverlay={isVideoAttachment(attachments[3])}
|
||||
playIconOverlay={isVideoAttachment(attachment4)}
|
||||
height={150}
|
||||
width={150}
|
||||
attachment={attachments[3]}
|
||||
url={getThumbnailUrl(attachments[3])}
|
||||
attachment={attachment4}
|
||||
url={getThumbnailUrl(attachment4)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showAttachmentOrNoLongerAvailableToast(3)}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -450,22 +466,30 @@ export function ImageGrid({
|
||||
? `+${attachments.length - 5}`
|
||||
: undefined;
|
||||
|
||||
const [attachment1, attachment2, attachment3, attachment4, attachment5] =
|
||||
attachments;
|
||||
strictAssert(attachment1, 'Missing attachment 1');
|
||||
strictAssert(attachment2, 'Missing attachment 2');
|
||||
strictAssert(attachment3, 'Missing attachment 3');
|
||||
strictAssert(attachment4, 'Missing attachment 4');
|
||||
strictAssert(attachment5, 'Missing attachment 4');
|
||||
|
||||
return (
|
||||
<div className="module-image-grid">
|
||||
<div className="module-image-grid__column">
|
||||
<div className="module-image-grid__row">
|
||||
<Image
|
||||
alt={getAlt(attachments[0], i18n)}
|
||||
alt={getAlt(attachment1, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[0].blurHash}
|
||||
blurHash={attachment1.blurHash}
|
||||
curveTopLeft={curveTopLeft}
|
||||
attachment={attachments[0]}
|
||||
playIconOverlay={isVideoAttachment(attachments[0])}
|
||||
attachment={attachment1}
|
||||
playIconOverlay={isVideoAttachment(attachment1)}
|
||||
height={150}
|
||||
width={150}
|
||||
cropWidth={GAP}
|
||||
url={getThumbnailUrl(attachments[0])}
|
||||
url={getThumbnailUrl(attachment1)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -473,16 +497,16 @@ export function ImageGrid({
|
||||
onError={onError}
|
||||
/>
|
||||
<Image
|
||||
alt={getAlt(attachments[1], i18n)}
|
||||
alt={getAlt(attachment2, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[1].blurHash}
|
||||
blurHash={attachment2.blurHash}
|
||||
curveTopRight={curveTopRight}
|
||||
playIconOverlay={isVideoAttachment(attachments[1])}
|
||||
playIconOverlay={isVideoAttachment(attachment2)}
|
||||
height={150}
|
||||
width={150}
|
||||
attachment={attachments[1]}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
attachment={attachment2}
|
||||
url={getThumbnailUrl(attachment2)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -492,19 +516,19 @@ export function ImageGrid({
|
||||
</div>
|
||||
<div className="module-image-grid__row">
|
||||
<Image
|
||||
alt={getAlt(attachments[2], i18n)}
|
||||
alt={getAlt(attachment3, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[2].blurHash}
|
||||
blurHash={attachment3.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={isSticker}
|
||||
curveBottomLeft={curveBottomLeft}
|
||||
playIconOverlay={isVideoAttachment(attachments[2])}
|
||||
playIconOverlay={isVideoAttachment(attachment3)}
|
||||
height={100}
|
||||
width={100}
|
||||
cropWidth={GAP}
|
||||
attachment={attachments[2]}
|
||||
url={getThumbnailUrl(attachments[2])}
|
||||
attachment={attachment3}
|
||||
url={getThumbnailUrl(attachment3)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -512,18 +536,18 @@ export function ImageGrid({
|
||||
onError={onError}
|
||||
/>
|
||||
<Image
|
||||
alt={getAlt(attachments[3], i18n)}
|
||||
alt={getAlt(attachment4, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[3].blurHash}
|
||||
blurHash={attachment4.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={isSticker}
|
||||
playIconOverlay={isVideoAttachment(attachments[3])}
|
||||
playIconOverlay={isVideoAttachment(attachment4)}
|
||||
height={100}
|
||||
width={100}
|
||||
cropWidth={GAP}
|
||||
attachment={attachments[3]}
|
||||
url={getThumbnailUrl(attachments[3])}
|
||||
attachment={attachment4}
|
||||
url={getThumbnailUrl(attachment4)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
@@ -531,20 +555,20 @@ export function ImageGrid({
|
||||
onError={onError}
|
||||
/>
|
||||
<Image
|
||||
alt={getAlt(attachments[4], i18n)}
|
||||
alt={getAlt(attachment5, i18n)}
|
||||
i18n={i18n}
|
||||
theme={theme}
|
||||
blurHash={attachments[4].blurHash}
|
||||
blurHash={attachment5.blurHash}
|
||||
bottomOverlay={withBottomOverlay}
|
||||
noBorder={isSticker}
|
||||
curveBottomRight={curveBottomRight}
|
||||
playIconOverlay={isVideoAttachment(attachments[4])}
|
||||
playIconOverlay={isVideoAttachment(attachment5)}
|
||||
height={100}
|
||||
width={100}
|
||||
darkOverlay={moreMessagesOverlay}
|
||||
overlayText={moreMessagesOverlayText}
|
||||
attachment={attachments[4]}
|
||||
url={getThumbnailUrl(attachments[4])}
|
||||
attachment={attachment5}
|
||||
url={getThumbnailUrl(attachment5)}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={undefined}
|
||||
|
||||
@@ -108,7 +108,7 @@ import { getColorForCallLink } from '../../util/getColorForCallLink.std.js';
|
||||
import { getKeyFromCallLink } from '../../util/callLinks.std.js';
|
||||
import { InAnotherCallTooltip } from './InAnotherCallTooltip.dom.js';
|
||||
import { formatFileSize } from '../../util/formatFileSize.std.js';
|
||||
import { assertDev } from '../../util/assert.std.js';
|
||||
import { assertDev, strictAssert } from '../../util/assert.std.js';
|
||||
import { AttachmentStatusIcon } from './AttachmentStatusIcon.dom.js';
|
||||
import { TapToViewNotAvailableType } from '../TapToViewNotAvailableModal.dom.js';
|
||||
import type { DataPropsType as TapToViewNotAvailablePropsType } from '../TapToViewNotAvailableModal.dom.js';
|
||||
@@ -491,17 +491,19 @@ const MessageReactions = forwardRef(function MessageReactions(
|
||||
const reactionsContainerRefMerger = useRef(createRefMerger());
|
||||
|
||||
// Take the first three groups for rendering
|
||||
const toRender = take(ordered, 3).map(res => {
|
||||
const isMe = res.some(re => Boolean(re.from.isMe));
|
||||
const count = res.length;
|
||||
const { emoji } = res[0];
|
||||
const toRender = take(ordered, 3).map(group => {
|
||||
const isMe = group.some(re => Boolean(re.from.isMe));
|
||||
const count = group.length;
|
||||
const firstReaction = group[0];
|
||||
strictAssert(firstReaction, 'Missing firstReaction');
|
||||
const { emoji } = firstReaction;
|
||||
|
||||
let label: string;
|
||||
if (isMe) {
|
||||
label = i18n('icu:Message__reaction-emoji-label--you', { emoji });
|
||||
} else if (count === 1) {
|
||||
label = i18n('icu:Message__reaction-emoji-label--single', {
|
||||
title: res[0].from.title,
|
||||
title: firstReaction.from.title,
|
||||
emoji,
|
||||
});
|
||||
} else {
|
||||
@@ -2543,6 +2545,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
const onlyPreview = previews[0];
|
||||
strictAssert(onlyPreview, 'Missing onlyPreview');
|
||||
return Boolean(onlyPreview.isCallLink);
|
||||
}
|
||||
|
||||
@@ -2551,6 +2554,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||
|
||||
if (this.#shouldShowJoinButton()) {
|
||||
const firstPreview = previews[0];
|
||||
strictAssert(firstPreview, 'Missing firstPreview');
|
||||
const inAnotherCall = Boolean(
|
||||
activeCallConversationId &&
|
||||
(!firstPreview.callLinkRoomId ||
|
||||
@@ -2672,6 +2676,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||
|
||||
if (previews && previews.length) {
|
||||
const first = previews[0];
|
||||
strictAssert(first, 'Missing first');
|
||||
const { image } = first;
|
||||
|
||||
return isImageAttachment(image);
|
||||
@@ -2688,6 +2693,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
const first = attachments[0];
|
||||
strictAssert(first, 'Missing first');
|
||||
|
||||
return Boolean(first.pending);
|
||||
}
|
||||
@@ -3216,6 +3222,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||
event.stopPropagation();
|
||||
|
||||
const attachment = attachments[0];
|
||||
strictAssert(attachment, 'Missing attachment');
|
||||
|
||||
showLightbox({ attachment, messageId: id });
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ export const ReactionViewer = React.forwardRef<HTMLDivElement, Props>(
|
||||
// Find the local user's reaction first, then fall back to most recent
|
||||
const localUserReaction = groupedReactions.find(r => r.from.isMe);
|
||||
const firstReaction = localUserReaction || groupedReactions[0];
|
||||
strictAssert(firstReaction, 'Missing firstReaction');
|
||||
return {
|
||||
id: firstReaction.parentKey,
|
||||
index: DEFAULT_EMOJI_ORDER.includes(firstReaction.parentKey)
|
||||
|
||||
@@ -260,6 +260,7 @@ export class Timeline extends React.Component<
|
||||
if (setFocus && items && items.length > 0) {
|
||||
const lastIndex = items.length - 1;
|
||||
const lastMessageId = items[lastIndex];
|
||||
strictAssert(lastMessageId, 'Missing lastMessageId');
|
||||
targetMessage(lastMessageId, id);
|
||||
} else {
|
||||
const containerEl = this.#containerRef.current;
|
||||
@@ -307,6 +308,7 @@ export class Timeline extends React.Component<
|
||||
) {
|
||||
if (setFocus) {
|
||||
const messageId = items[oldestUnseenIndex];
|
||||
strictAssert(messageId, 'Missing messageId');
|
||||
targetMessage(messageId, id);
|
||||
} else {
|
||||
this.#lastSeenIndicatorRef.current?.scrollIntoView();
|
||||
@@ -824,6 +826,7 @@ export class Timeline extends React.Component<
|
||||
}
|
||||
|
||||
const messageId = items[targetIndex];
|
||||
strictAssert(messageId, 'Missing messageId');
|
||||
targetMessage(messageId, id);
|
||||
|
||||
event.preventDefault();
|
||||
@@ -851,6 +854,7 @@ export class Timeline extends React.Component<
|
||||
}
|
||||
|
||||
const messageId = items[targetIndex];
|
||||
strictAssert(messageId, 'Missing messageId');
|
||||
targetMessage(messageId, id);
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
@@ -3424,7 +3424,8 @@ export const EmbeddedContactWithSendMessage = Template.bind({});
|
||||
EmbeddedContactWithSendMessage.args = {
|
||||
contact: {
|
||||
...fullContact,
|
||||
firstNumber: fullContact.number[0].value,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
firstNumber: fullContact.number[0]!.value,
|
||||
serviceId: generateAci(),
|
||||
},
|
||||
direction: 'incoming',
|
||||
|
||||
@@ -250,7 +250,8 @@ export function TimelineMessage(props: Props): React.JSX.Element {
|
||||
if (attachments.length !== 1) {
|
||||
saveAttachments(attachments, timestamp);
|
||||
} else {
|
||||
saveAttachment(attachments[0], timestamp);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
saveAttachment(attachments[0]!, timestamp);
|
||||
}
|
||||
},
|
||||
[
|
||||
|
||||
@@ -341,8 +341,10 @@ export function TypingBubble({
|
||||
return;
|
||||
}
|
||||
|
||||
const lastTypingContactId = typingContactIds[0];
|
||||
const lastTypingTimestamp = typingContactIdTimestamps[lastTypingContactId];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const lastTypingContactId = typingContactIds[0]!;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const lastTypingTimestamp = typingContactIdTimestamps[lastTypingContactId]!;
|
||||
if (
|
||||
lastItemAuthorId === lastTypingContactId &&
|
||||
lastItemTimestamp > lastTypingTimestamp
|
||||
|
||||
+1
@@ -216,6 +216,7 @@ export function ChooseGroupMembersModal({
|
||||
|
||||
if (virtualIndex < filteredContacts.length) {
|
||||
const contact = filteredContacts[virtualIndex];
|
||||
strictAssert(contact, 'Missing contact');
|
||||
|
||||
const isSelected = selectedConversationIdsSet.has(contact.id);
|
||||
const isAlreadyInGroup = conversationIdsAlreadyInGroup.has(contact.id);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ThemeType } from '../../../types/Util.std.js';
|
||||
import { DurationInSeconds } from '../../../util/durations/index.std.js';
|
||||
import { NavTab } from '../../../types/Nav.std.js';
|
||||
import { getFakeCallHistoryGroup } from '../../../test-helpers/getFakeCallHistoryGroup.std.js';
|
||||
import type { ContactNameColorType } from '../../../types/Colors.std.js';
|
||||
import { ContactNameColors } from '../../../types/Colors.std.js';
|
||||
import { isNotNil } from '../../../util/isNotNil.std.js';
|
||||
|
||||
@@ -52,16 +53,20 @@ const createProps = (
|
||||
isMe: i === 2,
|
||||
}),
|
||||
}));
|
||||
const memberColors = new Map<string, string>(
|
||||
const memberColors = new Map<string, ContactNameColorType>(
|
||||
memberships
|
||||
.map((membership, i): [string, string] | null => {
|
||||
.map((membership, i): [string, ContactNameColorType] | null => {
|
||||
const { serviceId } = membership.member;
|
||||
|
||||
if (!serviceId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [serviceId.toString(), ContactNameColors[i]];
|
||||
return [
|
||||
serviceId.toString(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
ContactNameColors[i % ContactNameColors.length]!,
|
||||
];
|
||||
})
|
||||
.filter(isNotNil)
|
||||
);
|
||||
|
||||
@@ -68,6 +68,7 @@ import { BadgeSustainerInstructionsDialog } from '../../BadgeSustainerInstructio
|
||||
import type { ContactModalStateType } from '../../../types/globalModals.std.js';
|
||||
import type { ShowToastAction } from '../../../state/ducks/toast.preload.js';
|
||||
import { ToastType } from '../../../types/Toast.dom.js';
|
||||
import type { ContactNameColorType } from '../../../types/Colors.std.js';
|
||||
|
||||
enum ModalState {
|
||||
AddingGroupMembers,
|
||||
@@ -101,7 +102,7 @@ export type StateProps = {
|
||||
maxGroupSize: number;
|
||||
maxRecommendedGroupSize: number;
|
||||
memberships: ReadonlyArray<GroupV2Membership>;
|
||||
memberColors: Map<string, string>;
|
||||
memberColors: Map<string, ContactNameColorType>;
|
||||
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
||||
pendingAvatarDownload?: boolean;
|
||||
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||
|
||||
+4
-2
@@ -12,6 +12,7 @@ import type {
|
||||
GroupV2Membership,
|
||||
} from './ConversationDetailsMembershipList.dom.js';
|
||||
import { ConversationDetailsMembershipList } from './ConversationDetailsMembershipList.dom.js';
|
||||
import type { ContactNameColorType } from '../../../types/Colors.std.js';
|
||||
import { ContactNameColors } from '../../../types/Colors.std.js';
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
@@ -33,11 +34,12 @@ const createMemberships = (
|
||||
|
||||
const getMemberColors = (
|
||||
memberships: Array<GroupV2Membership>
|
||||
): Map<string, string> =>
|
||||
): Map<string, ContactNameColorType> =>
|
||||
new Map(
|
||||
memberships.map((membership, i) => [
|
||||
membership.member.id,
|
||||
ContactNameColors[i],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
ContactNameColors[i % ContactNameColors.length]!,
|
||||
])
|
||||
);
|
||||
|
||||
|
||||
+2
-1
@@ -19,6 +19,7 @@ import { PanelSection } from './PanelSection.dom.js';
|
||||
import { GroupMemberLabel } from '../ContactName.dom.js';
|
||||
import { AriaClickable } from '../../../axo/AriaClickable.dom.js';
|
||||
import type { ContactModalStateType } from '../../../types/globalModals.std.js';
|
||||
import type { ContactNameColorType } from '../../../types/Colors.std.js';
|
||||
|
||||
export type GroupV2Membership = {
|
||||
isAdmin: boolean;
|
||||
@@ -36,7 +37,7 @@ export type Props = {
|
||||
isEditMemberLabelEnabled: boolean;
|
||||
maxShownMemberCount?: number;
|
||||
memberships: ReadonlyArray<GroupV2Membership>;
|
||||
memberColors: Map<string, string>;
|
||||
memberColors: Map<string, ContactNameColorType>;
|
||||
showContactModal: (payload: ContactModalStateType) => void;
|
||||
showLabelEditor: () => void;
|
||||
startAddingNewMembers?: () => void;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
MessageInteractivity,
|
||||
TextDirection,
|
||||
} from '../Message.dom.js';
|
||||
import type { ContactNameColorType } from '../../../types/Colors.std.js';
|
||||
import { ConversationColors } from '../../../types/Colors.std.js';
|
||||
import { WidthBreakpoint } from '../../_util.std.js';
|
||||
import { AxoAlertDialog } from '../../../axo/AxoAlertDialog.dom.js';
|
||||
@@ -52,13 +53,13 @@ export type PropsDataType = {
|
||||
isActive: boolean;
|
||||
me: ConversationType;
|
||||
membersWithLabel: Array<{
|
||||
contactNameColor: string;
|
||||
contactNameColor: ContactNameColorType;
|
||||
isAdmin: boolean;
|
||||
labelEmoji: string | undefined;
|
||||
labelString: string;
|
||||
member: ConversationType;
|
||||
}>;
|
||||
ourColor: string | undefined;
|
||||
ourColor: ContactNameColorType | undefined;
|
||||
theme: ThemeType;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from './ConversationDetailsIcon.dom.js';
|
||||
import { isAccessControlEnabled } from '../../../groups/util.std.js';
|
||||
import { Tabs } from '../../Tabs.dom.js';
|
||||
import { assertDev } from '../../../util/assert.std.js';
|
||||
|
||||
export type PropsDataType = {
|
||||
readonly conversation?: ConversationType;
|
||||
@@ -197,11 +196,13 @@ function MembershipActionConfirmation({
|
||||
}
|
||||
approvePendingMembershipFromGroupV2(
|
||||
conversation.id,
|
||||
stagedMemberships[0].membership.member.id
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
stagedMemberships[0]!.membership.member.id
|
||||
);
|
||||
};
|
||||
|
||||
const membershipType = stagedMemberships[0].type;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const membershipType = stagedMemberships[0]!.type;
|
||||
|
||||
const modalAction =
|
||||
membershipType === StageType.APPROVE_REQUEST
|
||||
@@ -255,12 +256,13 @@ function getConfirmationMessage({
|
||||
ourAci: AciString;
|
||||
stagedMemberships: ReadonlyArray<StagedMembershipType>;
|
||||
}>): string {
|
||||
if (!stagedMemberships || !stagedMemberships.length) {
|
||||
const [stagedMembership] = stagedMemberships;
|
||||
if (stagedMembership == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const membershipType = stagedMemberships[0].type;
|
||||
const firstMembership = stagedMemberships[0].membership;
|
||||
const membershipType = stagedMembership.type;
|
||||
const firstMembership = stagedMembership.membership;
|
||||
|
||||
// Requesting a membership since they weren't added by anyone
|
||||
if (membershipType === StageType.DENY_REQUEST) {
|
||||
@@ -419,19 +421,23 @@ function MembersPendingProfileKey({
|
||||
const { [ourAci]: ourPendingMemberships, ...otherPendingMembershipGroups } =
|
||||
groupedPendingMemberships;
|
||||
|
||||
const otherPendingMemberships = Object.keys(otherPendingMembershipGroups)
|
||||
.map(id => members.find(member => member.serviceId === id))
|
||||
.filter((member): member is ConversationType => member !== undefined)
|
||||
.map(member => {
|
||||
assertDev(
|
||||
member.serviceId,
|
||||
'We just verified that member has serviceId above'
|
||||
);
|
||||
return {
|
||||
member,
|
||||
pendingMemberships: otherPendingMembershipGroups[member.serviceId],
|
||||
};
|
||||
const otherPendingMemberships: Array<{
|
||||
member: ConversationType;
|
||||
pendingMemberships: Array<GroupV2PendingMembership>;
|
||||
}> = [];
|
||||
|
||||
for (const [id, pendingMemberships] of Object.entries(
|
||||
otherPendingMembershipGroups
|
||||
)) {
|
||||
const member = members.find(m => m.serviceId === id);
|
||||
if (member == null) {
|
||||
continue;
|
||||
}
|
||||
otherPendingMemberships.push({
|
||||
member,
|
||||
pendingMemberships,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelSection>
|
||||
|
||||
@@ -15,19 +15,15 @@ export const bemGenerator =
|
||||
const base = `${block}__${element}`;
|
||||
const classes = [base];
|
||||
|
||||
let conditionals: Record<string, boolean> = {};
|
||||
const conditionals: Record<string, boolean> = {};
|
||||
|
||||
if (modifier) {
|
||||
if (typeof modifier === 'string') {
|
||||
classes.push(`${base}--${modifier}`);
|
||||
} else {
|
||||
conditionals = Object.keys(modifier).reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[`${base}--${key}`]: modifier[key],
|
||||
}),
|
||||
{} as Record<string, boolean>
|
||||
);
|
||||
for (const [key, value] of Object.entries(modifier)) {
|
||||
conditionals[`${base}--${key}`] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,8 @@ function MediaSection({
|
||||
|
||||
const sections = groupedItems.map((section, index) => {
|
||||
const isLast = index === groupedItems.length - 1;
|
||||
const first = section.mediaItems[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const first = section.mediaItems[0]!;
|
||||
const { message } = first;
|
||||
const date = moment(message.receivedAtMs || message.receivedAt);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import lodash from 'lodash';
|
||||
import { type MIMEType, IMAGE_JPEG } from '../../../../types/MIME.std.js';
|
||||
import { IMAGE_JPEG, stringToMIMEType } from '../../../../types/MIME.std.js';
|
||||
import type {
|
||||
MediaItemType,
|
||||
ContactMediaItemType,
|
||||
@@ -21,17 +21,22 @@ export const days = (n: number): number => n * DAY_MS;
|
||||
const tokens = ['foo', 'bar', 'baz', 'qux', 'quux'];
|
||||
|
||||
const contentTypes = {
|
||||
gif: 'image/gif',
|
||||
jpg: 'image/jpeg',
|
||||
png: 'image/png',
|
||||
mp4: 'video/mp4',
|
||||
docx: 'application/text',
|
||||
pdf: 'application/pdf',
|
||||
exe: 'application/exe',
|
||||
txt: 'application/text',
|
||||
} as unknown as Record<string, MIMEType>;
|
||||
gif: stringToMIMEType('image/gif'),
|
||||
jpg: stringToMIMEType('image/jpeg'),
|
||||
png: stringToMIMEType('image/png'),
|
||||
mp3: stringToMIMEType('audio/mp3'),
|
||||
mp4: stringToMIMEType('video/mp4'),
|
||||
docx: stringToMIMEType('application/text'),
|
||||
pdf: stringToMIMEType('application/pdf'),
|
||||
exe: stringToMIMEType('application/exe'),
|
||||
txt: stringToMIMEType('application/text'),
|
||||
} as const;
|
||||
|
||||
function createRandomAttachment(fileExtension: string): AttachmentForUIType {
|
||||
type FileExtension = keyof typeof contentTypes;
|
||||
|
||||
function createRandomAttachment(
|
||||
fileExtension: FileExtension
|
||||
): AttachmentForUIType {
|
||||
const contentType = contentTypes[fileExtension];
|
||||
const fileName = `${sample(tokens)}${sample(tokens)}.${fileExtension}`;
|
||||
|
||||
@@ -101,7 +106,7 @@ function createRandomFile(
|
||||
type: 'media' | 'document' | 'audio',
|
||||
startTime: number,
|
||||
timeWindow: number,
|
||||
fileExtension: string
|
||||
fileExtension: FileExtension
|
||||
): MediaItemType {
|
||||
return {
|
||||
type,
|
||||
@@ -154,15 +159,11 @@ function createRandomFiles(
|
||||
type: 'media' | 'document' | 'audio',
|
||||
startTime: number,
|
||||
timeWindow: number,
|
||||
fileExtensions: Array<string>
|
||||
fileExtensions: Array<FileExtension>
|
||||
): Array<MediaItemType> {
|
||||
return range(random(5, 20)).map(() =>
|
||||
createRandomFile(
|
||||
type,
|
||||
startTime,
|
||||
timeWindow,
|
||||
sample(fileExtensions) as string
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
createRandomFile(type, startTime, timeWindow, sample(fileExtensions)!)
|
||||
);
|
||||
}
|
||||
export function createRandomDocuments(
|
||||
|
||||
@@ -105,8 +105,7 @@ export const PinnedMessagesBar = memo(function PinnedMessagesBar(
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let index = 0; index < pins.length; index += 1) {
|
||||
const value = pins[index];
|
||||
for (const [index, value] of pins.entries()) {
|
||||
if (value.id === current) {
|
||||
return { index, value };
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ export function All(props: AllProps): React.JSX.Element {
|
||||
}}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map(rowItem => {
|
||||
const row = rows[rowItem.index];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const row = rows[rowItem.index]!;
|
||||
return (
|
||||
<div
|
||||
key={rowItem.index}
|
||||
|
||||
@@ -319,6 +319,7 @@ export function FunPanelGifs({
|
||||
const estimateSize = useCallback(
|
||||
(index: number) => {
|
||||
const gif = items[index];
|
||||
strictAssert(gif, 'Missing gif');
|
||||
const aspectRatio = gif.previewMedia.width / gif.previewMedia.height;
|
||||
const baseHeight = GIF_WATERFALL_ITEM_WIDTH / aspectRatio;
|
||||
return baseHeight + GIF_WATERFALL_ITEM_MARGIN + GIF_WATERFALL_ITEM_MARGIN;
|
||||
@@ -350,7 +351,8 @@ export function FunPanelGifs({
|
||||
|
||||
const getItemKey = useCallback(
|
||||
(index: number) => {
|
||||
return items[index].id;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return items[index]!.id;
|
||||
},
|
||||
[items]
|
||||
);
|
||||
@@ -578,6 +580,7 @@ export function FunPanelGifs({
|
||||
<FunWaterfallContainer totalSize={virtualizer.getTotalSize()}>
|
||||
{virtualizer.getVirtualItems().map(item => {
|
||||
const gif = items[item.index];
|
||||
strictAssert(gif, 'Missing gif');
|
||||
const key = String(item.key);
|
||||
const isTabbable =
|
||||
selectedItemKey != null
|
||||
|
||||
@@ -605,6 +605,7 @@ const Cell = memo(function Cell(props: {
|
||||
}): React.JSX.Element {
|
||||
const { onClickSticker, onClickTimeSticker } = props;
|
||||
const stickerLookupItem = props.stickerLookup[props.value];
|
||||
strictAssert(stickerLookupItem, 'Missing stickerLookupItem');
|
||||
|
||||
const handleClick = useCallback(
|
||||
(event: PointerEvent) => {
|
||||
|
||||
@@ -207,11 +207,15 @@ function buildLayout(
|
||||
totalHeight: number
|
||||
): Layout {
|
||||
const groups = groupBy(virtualItems, virtualItem => {
|
||||
return list.listItems[virtualItem.index].sectionMeta.sectionKey;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return list.listItems[virtualItem.index]!.sectionMeta.sectionKey;
|
||||
});
|
||||
|
||||
const sections = Object.keys(groups).map((sectionKey): SectionLayoutNode => {
|
||||
const [headerVirtualItem, ...rowVirtualItems] = groups[sectionKey];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const [maybeHeaderVirtualItem, ...rowVirtualItems] = groups[sectionKey]!;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const headerVirtualItem = maybeHeaderVirtualItem!;
|
||||
const headerListItem = list.listItems.at(headerVirtualItem.index);
|
||||
|
||||
strictAssert(
|
||||
@@ -446,7 +450,8 @@ export function useFunVirtualGrid({
|
||||
|
||||
const getItemKey = useCallback(
|
||||
(index: number) => {
|
||||
return list.listItems[index].key;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return list.listItems[index]!.key;
|
||||
},
|
||||
[list]
|
||||
);
|
||||
|
||||
@@ -315,7 +315,8 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
||||
}
|
||||
|
||||
if (virtualRowIndex <= this.#candidateContacts.length) {
|
||||
const contact = this.#candidateContacts[virtualRowIndex - 1];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const contact = this.#candidateContacts[virtualRowIndex - 1]!;
|
||||
|
||||
const isChecked = this.#selectedConversationIdsSet.has(contact.id);
|
||||
const disabledReason =
|
||||
|
||||
@@ -239,7 +239,8 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
||||
if (index < pinnedConversations.length) {
|
||||
return {
|
||||
type: RowType.Conversation,
|
||||
conversation: pinnedConversations[index],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
conversation: pinnedConversations[index]!,
|
||||
};
|
||||
}
|
||||
index -= pinnedConversations.length;
|
||||
@@ -259,7 +260,8 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
||||
if (index < conversations.length) {
|
||||
return {
|
||||
type: RowType.Conversation,
|
||||
conversation: conversations[index],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
conversation: conversations[index]!,
|
||||
};
|
||||
}
|
||||
index -= conversations.length;
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
import { LeftPaneSearchInput } from '../LeftPaneSearchInput.dom.js';
|
||||
|
||||
import { I18n } from '../I18n.dom.js';
|
||||
import { assertDev } from '../../util/assert.std.js';
|
||||
import { assertDev, strictAssert } from '../../util/assert.std.js';
|
||||
import { UserText } from '../UserText.dom.js';
|
||||
|
||||
// The "correct" thing to do is to measure the size of the left pane and render enough
|
||||
@@ -381,6 +381,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
||||
}
|
||||
if (pointer < list.results.length) {
|
||||
const result = list.results[pointer];
|
||||
strictAssert(result, 'Missing result');
|
||||
return result.type === 'incoming' || result.type === 'outgoing' // message
|
||||
? {
|
||||
conversationId: result.conversationId,
|
||||
|
||||
+16
-14
@@ -3356,6 +3356,10 @@ export function _mergeGroupChangeMessages(
|
||||
|
||||
const [firstDetail] = firstChange.details;
|
||||
const [secondDetail] = secondChange.details;
|
||||
|
||||
strictAssert(firstDetail, 'Missing firstDetail');
|
||||
strictAssert(secondDetail, 'Missing secondDetail');
|
||||
|
||||
let isApprovalPending: boolean;
|
||||
if (secondDetail.type === 'admin-approval-add-one') {
|
||||
isApprovalPending = true;
|
||||
@@ -3428,6 +3432,7 @@ export function _isGroupChangeMessageBounceable(
|
||||
}
|
||||
|
||||
const [first] = groupV2Change.details;
|
||||
strictAssert(first, 'Missing first');
|
||||
if (
|
||||
first.type === 'admin-approval-add-one' ||
|
||||
first.type === 'admin-approval-bounce'
|
||||
@@ -4267,18 +4272,14 @@ async function integrateGroupChanges({
|
||||
const finalMessages: Array<Array<GroupChangeMessageType>> = [];
|
||||
const finalNewProfileKeys = new Map<AciString, string>();
|
||||
|
||||
const imax = changes.length;
|
||||
for (let i = 0; i < imax; i += 1) {
|
||||
const { groupChanges } = changes[i];
|
||||
for (const change of changes) {
|
||||
const { groupChanges } = change;
|
||||
|
||||
if (!groupChanges) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const jmax = groupChanges.length;
|
||||
for (let j = 0; j < jmax; j += 1) {
|
||||
const changeState = groupChanges[j];
|
||||
|
||||
for (const changeState of groupChanges) {
|
||||
const { groupChange, groupState } = changeState;
|
||||
|
||||
if (!groupChange && !groupState) {
|
||||
@@ -4323,8 +4324,8 @@ async function integrateGroupChanges({
|
||||
if (isFirstFetch && moreThanOneVersion) {
|
||||
// The first array in finalMessages is from the first revision we could process. It
|
||||
// should contain a message about how we joined the group.
|
||||
const joinMessages = finalMessages[0];
|
||||
const alreadyHaveJoinMessage = joinMessages && joinMessages.length > 0;
|
||||
const joinMessage = finalMessages[0]?.[0];
|
||||
const alreadyHaveJoinMessage = joinMessage != null;
|
||||
|
||||
// There have been other changes since that first revision, so we generate diffs for
|
||||
// the whole of the change since then, likely without the initial join message.
|
||||
@@ -4334,9 +4335,8 @@ async function integrateGroupChanges({
|
||||
dropInitialJoinMessage: alreadyHaveJoinMessage,
|
||||
});
|
||||
|
||||
const groupChangeMessages = alreadyHaveJoinMessage
|
||||
? [joinMessages[0], ...otherMessages]
|
||||
: otherMessages;
|
||||
const groupChangeMessages =
|
||||
joinMessage != null ? [joinMessage, ...otherMessages] : otherMessages;
|
||||
|
||||
return {
|
||||
newAttributes: attributes,
|
||||
@@ -4897,6 +4897,7 @@ function extractDiffs({
|
||||
const removedPendingMemberIds = Array.from(oldPendingMemberLookup.keys());
|
||||
if (removedPendingMemberIds.length > 1) {
|
||||
const firstUuid = removedPendingMemberIds[0];
|
||||
strictAssert(firstUuid, 'Missing firstUuid');
|
||||
const firstRemovedMember = oldPendingMemberLookup.get(firstUuid);
|
||||
strictAssert(
|
||||
firstRemovedMember !== undefined,
|
||||
@@ -4913,6 +4914,7 @@ function extractDiffs({
|
||||
});
|
||||
} else if (removedPendingMemberIds.length === 1) {
|
||||
const serviceId = removedPendingMemberIds[0];
|
||||
strictAssert(serviceId, 'Missing serviceId');
|
||||
const removedMember = oldPendingMemberLookup.get(serviceId);
|
||||
strictAssert(removedMember !== undefined, 'Removed member not found');
|
||||
|
||||
@@ -5434,7 +5436,7 @@ async function applyGroupChange({
|
||||
members[aci] = {
|
||||
aci,
|
||||
joinedAtVersion: version,
|
||||
role: previousRecord.role || MemberRole.DEFAULT,
|
||||
role: previousRecord?.role || MemberRole.DEFAULT,
|
||||
};
|
||||
|
||||
newProfileKeys.push({
|
||||
@@ -5478,7 +5480,7 @@ async function applyGroupChange({
|
||||
members[aci] = {
|
||||
aci,
|
||||
joinedAtVersion: version,
|
||||
role: previousRecord.role || MemberRole.DEFAULT,
|
||||
role: previousRecord?.role || MemberRole.DEFAULT,
|
||||
};
|
||||
|
||||
newProfileKeys.push({
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as React from 'react';
|
||||
type CallbackType = (toFocus: HTMLElement | null | undefined) => void;
|
||||
|
||||
// Restore focus on teardown
|
||||
export const useRestoreFocus = (): Array<CallbackType> => {
|
||||
export const useRestoreFocus = (): [CallbackType] => {
|
||||
const toFocusRef = React.useRef<HTMLElement | null>(null);
|
||||
const lastFocusedRef = React.useRef<HTMLElement | null>(null);
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ export function useSizeObserver<T extends Element = Element>(
|
||||
// We're only ever observing one element, and `ResizeObserver` for some
|
||||
// reason is an array of exactly one rect (I assume to support wrapped
|
||||
// inline elements in the future)
|
||||
const borderBoxSize = entries[0].borderBoxSize[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const borderBoxSize = entries[0]!.borderBoxSize[0]!;
|
||||
// We are assuming a horizontal writing-mode here, we could call
|
||||
// `getBoundingClientRect()` here but MDN says not to. In the future if
|
||||
// we are adding support for a vertical locale we may need to change this
|
||||
|
||||
@@ -52,7 +52,8 @@ export function useTabs(options: TabsOptionsType): TabsProps {
|
||||
// This is enforced by the type system.
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [tabState, setTabState] = useState<string>(
|
||||
options.initialSelectedTab || options.tabs[0].id
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
options.initialSelectedTab || options.tabs[0]!.id
|
||||
);
|
||||
|
||||
selectedTab = tabState;
|
||||
|
||||
@@ -604,6 +604,7 @@ async function copyToBackupTier({
|
||||
});
|
||||
|
||||
const response = responses[0];
|
||||
strictAssert(response, 'Missing response');
|
||||
if (!response.isSuccess) {
|
||||
if (response.status === FILE_NOT_FOUND_ON_TRANSIT_TIER_STATUS) {
|
||||
throw new FileNotFoundOnTransitTierError();
|
||||
|
||||
@@ -615,7 +615,8 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
|
||||
#getRetryWithBackoff(attempts: number) {
|
||||
return (
|
||||
Date.now() +
|
||||
MINUTE * (FIBONACCI[attempts] ?? FIBONACCI[FIBONACCI.length - 1])
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
MINUTE * (FIBONACCI[attempts] ?? FIBONACCI[FIBONACCI.length - 1]!)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -392,7 +392,8 @@ export async function sendNormalMessage(
|
||||
|
||||
log.info('sending direct message');
|
||||
innerPromise = messaging.sendMessageToServiceId({
|
||||
serviceId: recipientServiceIdsWithoutMe[0],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
serviceId: recipientServiceIdsWithoutMe[0]!,
|
||||
messageOptions: {
|
||||
attachments,
|
||||
body,
|
||||
@@ -951,7 +952,8 @@ async function uploadMessageQuote({
|
||||
);
|
||||
|
||||
const attachmentAfterThumbnailUpload =
|
||||
attachmentsAfterThumbnailUpload[index];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
attachmentsAfterThumbnailUpload[index]!;
|
||||
return {
|
||||
...attachment,
|
||||
thumbnail: {
|
||||
|
||||
@@ -137,7 +137,8 @@ export async function sendPollTerminate(
|
||||
return;
|
||||
}
|
||||
|
||||
const recipientServiceId = recipients[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const recipientServiceId = recipients[0]!;
|
||||
|
||||
jobLog.info(
|
||||
`${logId}: Sending direct poll terminate for poll timestamp ${targetTimestamp}`
|
||||
|
||||
@@ -242,7 +242,8 @@ export async function sendPollVote(
|
||||
|
||||
promise = messaging.sendMessageProtoAndWait({
|
||||
timestamp: currentTimestamp,
|
||||
recipients: [recipientServiceIdsWithoutMe[0]],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
recipients: [recipientServiceIdsWithoutMe[0]!],
|
||||
proto: contentMessage,
|
||||
contentHint: ContentHint.Resendable,
|
||||
groupId: undefined,
|
||||
|
||||
@@ -231,7 +231,8 @@ export async function sendReaction(
|
||||
|
||||
log.info('sending direct reaction message');
|
||||
promise = messaging.sendMessageToServiceId({
|
||||
serviceId: recipientServiceIdsWithoutMe[0],
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
serviceId: recipientServiceIdsWithoutMe[0]!,
|
||||
messageOptions: {
|
||||
reaction: reactionForSend,
|
||||
timestamp: pendingReaction.timestamp,
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
saveErrorsOnMessage,
|
||||
} from '../../test-node/util/messageFailures.preload.js';
|
||||
import { send } from '../../messages/send.preload.js';
|
||||
import { strictAssert } from '../../util/assert.std.js';
|
||||
|
||||
const { isEqual } = lodash;
|
||||
|
||||
@@ -167,9 +168,9 @@ export async function sendStory(
|
||||
preview: undefined,
|
||||
};
|
||||
} else {
|
||||
const hydratedPreview = (
|
||||
await loadPreviewData([localAttachment.preview])
|
||||
)[0];
|
||||
const previewData = await loadPreviewData([localAttachment.preview]);
|
||||
const hydratedPreview = previewData[0];
|
||||
strictAssert(hydratedPreview, 'Missing hydratedPreview');
|
||||
|
||||
textAttachment = {
|
||||
...localAttachment,
|
||||
@@ -495,57 +496,48 @@ export async function sendStory(
|
||||
|
||||
let hasFailedSends = false;
|
||||
|
||||
const newSendStateByConversationId = Object.keys(
|
||||
oldSendStateByConversationId
|
||||
).reduce((acc, conversationId) => {
|
||||
const sendState = sentConversationIds.get(conversationId);
|
||||
if (sendState) {
|
||||
return {
|
||||
...acc,
|
||||
[conversationId]: sendState,
|
||||
};
|
||||
}
|
||||
const newSendStateByConversationId: SendStateByConversationId = {};
|
||||
|
||||
const oldSendState = {
|
||||
...oldSendStateByConversationId[conversationId],
|
||||
};
|
||||
if (!oldSendState) {
|
||||
return acc;
|
||||
for (const [conversationId, oldSendState] of Object.entries(
|
||||
oldSendStateByConversationId
|
||||
)) {
|
||||
const sendState = sentConversationIds.get(conversationId);
|
||||
|
||||
if (sendState) {
|
||||
newSendStateByConversationId[conversationId] = sendState;
|
||||
continue;
|
||||
}
|
||||
|
||||
const recipient = window.ConversationController.get(conversationId);
|
||||
if (!recipient) {
|
||||
return acc;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isMe(recipient.attributes)) {
|
||||
return acc;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (recipient.isEverUnregistered()) {
|
||||
if (!isSent(oldSendState.status)) {
|
||||
// We should have filtered this out on initial send, but we'll drop them from
|
||||
// send list here if needed.
|
||||
return acc;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a previous send to them did succeed, we'll keep that status around
|
||||
return {
|
||||
...acc,
|
||||
[conversationId]: oldSendState,
|
||||
};
|
||||
newSendStateByConversationId[conversationId] = oldSendState;
|
||||
}
|
||||
|
||||
hasFailedSends = true;
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[conversationId]: sendStateReducer(oldSendState, {
|
||||
newSendStateByConversationId[conversationId] = sendStateReducer(
|
||||
oldSendState,
|
||||
{
|
||||
type: SendActionType.Failed,
|
||||
updatedAt: Date.now(),
|
||||
}),
|
||||
};
|
||||
}, {} as SendStateByConversationId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (hasFailedSends) {
|
||||
notifyStorySendFailed(message);
|
||||
@@ -609,8 +601,9 @@ export async function sendStory(
|
||||
sendErrors.push(result);
|
||||
}
|
||||
});
|
||||
if (sendErrors.length) {
|
||||
throw sendErrors[0].reason;
|
||||
const [firstError] = sendErrors;
|
||||
if (firstError != null) {
|
||||
throw firstError.reason;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,8 @@ function getSubsystemColor(name: string): string {
|
||||
hash >>>= 0;
|
||||
/* eslint-enable no-bitwise */
|
||||
|
||||
const result = COLORS[hash % COLORS.length];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const result = COLORS[hash % COLORS.length]!;
|
||||
SUBSYSTEM_COLORS.set(name, result);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -210,8 +210,10 @@ export function eliminateOutOfDateFiles(
|
||||
path: target,
|
||||
start: isLineAfterDate(start, date),
|
||||
end:
|
||||
isLineAfterDate(end[end.length - 1], date) ||
|
||||
isLineAfterDate(end[end.length - 2], date),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
isLineAfterDate(end[end.length - 1]!, date) ||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
isLineAfterDate(end[end.length - 2]!, date),
|
||||
};
|
||||
|
||||
if (!file.start && !file.end) {
|
||||
|
||||
@@ -105,7 +105,8 @@ const processReceiptBatcher = createWaitBatcher({
|
||||
continue;
|
||||
}
|
||||
// All receipts have the same sentAt, so we can grab it from the first
|
||||
const sentAt = receiptsForMessageSentAt[0].receiptSync.messageSentAt;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const sentAt = receiptsForMessageSentAt[0]!.receiptSync.messageSentAt;
|
||||
|
||||
const messagesMatchingTimestamp =
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
@@ -374,7 +375,8 @@ function getTargetMessage({
|
||||
`);
|
||||
}
|
||||
|
||||
const message = matchingMessages[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const message = matchingMessages[0]!;
|
||||
return window.MessageCache.register(new MessageModel(message));
|
||||
}
|
||||
const wasDeliveredWithSealedSender = (
|
||||
|
||||
@@ -436,7 +436,8 @@ export async function handlePollVote(
|
||||
);
|
||||
|
||||
if (existingVoteIndex !== -1) {
|
||||
const existingVote = currentVotes[existingVoteIndex];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const existingVote = currentVotes[existingVoteIndex]!;
|
||||
|
||||
if (newVote.voteCount > existingVote.voteCount) {
|
||||
updatedVotes = [...currentVotes];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user