Enable tsconfig noUncheckedIndexedAccess

This commit is contained in:
Jamie
2026-03-12 16:24:01 -07:00
committed by GitHub
parent 34b0f9cd50
commit 1d45a52da7
311 changed files with 2146 additions and 1589 deletions
+3 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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',
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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(
+1 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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 */
}
+3 -1
View File
@@ -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);
}
};
}
+7 -14
View File
@@ -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);
+2 -1
View File
@@ -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>
)}
+4 -2
View File
@@ -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 {
+1 -1
View File
@@ -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;
}
+1 -8
View File
@@ -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;
+2 -1
View File
@@ -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();
};
+2 -1
View File
@@ -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 => {
+26 -20
View File
@@ -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(),
],
+2 -1
View File
@@ -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),
+11 -5
View File
@@ -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 {
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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],
})}
/>
);
+3 -3
View File
@@ -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,
+30 -26
View File
@@ -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">
+4 -2
View File
@@ -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,
+1 -2
View File
@@ -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)
);
+7 -2
View File
@@ -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) {
+2 -2
View File
@@ -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');
+13 -16
View File
@@ -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>
);
+2 -1
View File
@@ -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;
+23 -21
View File
@@ -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,
+10 -7
View File
@@ -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 {
+2 -2
View File
@@ -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,
+8 -1
View File
@@ -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}
+6 -3
View File
@@ -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();
};
+6 -3
View File
@@ -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',
+3 -2
View File
@@ -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
];
]!;
};
+1 -1
View File
@@ -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;
+8 -4
View File
@@ -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
+3 -4
View File
@@ -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 = (
<>
+2 -1
View File
@@ -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;
+14 -12
View File
@@ -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')}</>;
}
+1 -1
View File
@@ -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' && (
+105 -81
View File
@@ -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}
+13 -6
View File
@@ -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
@@ -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>;
@@ -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]!,
])
);
@@ -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 };
}
+2 -1
View File
@@ -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
View File
@@ -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({
+1 -1
View File
@@ -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);
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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();
+2 -1
View File
@@ -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]!)
);
}
+4 -2
View File
@@ -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: {
+2 -1
View File
@@ -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}`
+2 -1
View File
@@ -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,
+2 -1
View File
@@ -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,
+26 -33
View File
@@ -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;
}
}
+2 -1
View File
@@ -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;
+4 -2
View File
@@ -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 = (
+2 -1
View File
@@ -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