diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index 83c7187c1..78fe8ded2 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -70,7 +70,7 @@ function makeUIAAction( type SetupVerificationProps = { onComplete: (recoveryKey: string) => void; }; -function SetupVerification({ onComplete }: SetupVerificationProps) { +function SetupVerification({ onComplete }: Readonly) { const mx = useMatrixClient(); const alive = useAlive(); @@ -227,7 +227,7 @@ function SetupVerification({ onComplete }: SetupVerificationProps) { type RecoveryKeyDisplayProps = { recoveryKey: string; }; -function RecoveryKeyDisplay({ recoveryKey }: RecoveryKeyDisplayProps) { +function RecoveryKeyDisplay({ recoveryKey }: Readonly) { const [show, setShow] = useState(false); const handleCopy = () => { @@ -241,7 +241,7 @@ function RecoveryKeyDisplay({ recoveryKey }: RecoveryKeyDisplayProps) { FileSaver.saveAs(blob, 'recovery-key.txt'); }; - const safeToDisplayKey = show ? recoveryKey : recoveryKey.replace(/[^\s]/g, '*'); + const safeToDisplayKey = show ? recoveryKey : recoveryKey.replaceAll(/[^\s]/g, '*'); return ( diff --git a/src/app/components/Pdf-viewer/PdfViewer.tsx b/src/app/components/Pdf-viewer/PdfViewer.tsx index e3072e6ff..71ab77efb 100644 --- a/src/app/components/Pdf-viewer/PdfViewer.tsx +++ b/src/app/components/Pdf-viewer/PdfViewer.tsx @@ -85,7 +85,7 @@ export const PdfViewer = as<'div', PdfViewerProps>( if (docState.status !== AsyncStatus.Success) return; const jumpInput = evt.currentTarget.jumpInput as HTMLInputElement; if (!jumpInput) return; - const jumpTo = parseInt(jumpInput.value, 10); + const jumpTo = Number.parseInt(jumpInput.value, 10); setPageNo(Math.max(1, Math.min(docState.data.numPages, jumpTo))); setJumpAnchor(undefined); }; diff --git a/src/app/components/RenderMessageContent.tsx b/src/app/components/RenderMessageContent.tsx index 1a1a5376f..502799249 100644 --- a/src/app/components/RenderMessageContent.tsx +++ b/src/app/components/RenderMessageContent.tsx @@ -71,7 +71,7 @@ function RenderMessageContentInternal({ htmlReactParserOptions, linkifyOpts, outlineAttachment, -}: RenderMessageContentProps) { +}: Readonly) { const content = useMemo(() => getContent(), [getContent]); const [autoplayGifs] = useSetting(settingsAtom, 'autoplayGifs'); diff --git a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx index c8466ae98..1f590235c 100644 --- a/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/EmoticonAutocomplete.tsx @@ -57,13 +57,10 @@ export function EmoticonAutocomplete({ const [emojiThreshold] = useSetting(settingsAtom, 'emojiSuggestThreshold'); - const searchList = useMemo(() => { - const list: Array = []; - return list.concat( - imagePacks.flatMap((pack) => pack.getImages(ImageUsage.Emoticon)), - emojis - ); - }, [imagePacks]); + const searchList = useMemo>( + () => [...imagePacks.flatMap((pack) => pack.getImages(ImageUsage.Emoticon)), ...emojis], + [imagePacks] + ); const [result, search, resetSearch] = useAsyncSearch( searchList, diff --git a/src/app/components/editor/input.ts b/src/app/components/editor/input.ts index 017c33b8d..27a6b68fc 100644 --- a/src/app/components/editor/input.ts +++ b/src/app/components/editor/input.ts @@ -322,7 +322,7 @@ const parseHeadingNode = ( const headingMatch = node.name.match(/^h([123456])$/); const [, g1AsLevel] = headingMatch ?? ['h3', '3']; - const level = parseInt(g1AsLevel, 10); + const level = Number.parseInt(g1AsLevel, 10); const mdSequence = node.attribs['data-md']; if (mdSequence !== undefined) { @@ -334,7 +334,7 @@ const parseHeadingNode = ( return { type: BlockType.Heading, - level: (level <= 3 ? level : 3) as HeadingLevel, + level: Math.min(level, 3) as HeadingLevel, children, }; }; diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts index 2f797e04f..9e3901925 100644 --- a/src/app/components/editor/output.ts +++ b/src/app/components/editor/output.ts @@ -128,10 +128,13 @@ export const toMatrixCustomHTML = ( return `${parsedMarkdown}${toMatrixCustomHTML(n, { ...opts, allowBlockMarkdown: false })}`; }; - if (Array.isArray(node)) return node.map(parseNode).join(''); + if (Array.isArray(node)) + return node.map((element, index, array) => parseNode(element, index, array)).join(''); if (Text.isText(node)) return textToCustomHtml(node, opts); - const children = node.children.map(parseNode).join(''); + const children = node.children + .map((element, index, array) => parseNode(element, index, array)) + .join(''); return elementToCustomHtml(node, children); }; @@ -193,14 +196,14 @@ export const toPlainText = (node: Descendant | Descendant[], isMarkdown: boolean * @returns boolean */ export const customHtmlEqualsPlainText = (customHtml: string, plain: string): boolean => - customHtml.replace(//g, '\n') === sanitizeText(plain); + customHtml.replaceAll('
', '\n') === sanitizeText(plain); -export const trimCustomHtml = (customHtml: string) => customHtml.replace(/$/g, '').trim(); +export const trimCustomHtml = (customHtml: string) => customHtml.replaceAll(/$/g, '').trim(); export const trimCommand = (cmdName: string, str: string) => { const cmdRegX = new RegExp(`^(\\s+)?(\\/${sanitizeForRegex(cmdName)})([^\\S\n]+)?`); - const match = str.match(cmdRegX); + const match = new RegExp(cmdRegX).exec(str); if (!match) return str; return str.slice(match[0].length); }; diff --git a/src/app/components/emoji-board/EmojiBoard.tsx b/src/app/components/emoji-board/EmojiBoard.tsx index 83804b968..ff407fdd2 100644 --- a/src/app/components/emoji-board/EmojiBoard.tsx +++ b/src/app/components/emoji-board/EmojiBoard.tsx @@ -169,7 +169,7 @@ type EmojiSidebarProps = { packs: ImagePack[]; onScrollToGroup: (groupId: string) => void; }; -function EmojiSidebar({ activeGroupAtom, packs, onScrollToGroup }: EmojiSidebarProps) { +function EmojiSidebar({ activeGroupAtom, packs, onScrollToGroup }: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); @@ -245,7 +245,11 @@ type StickerSidebarProps = { packs: ImagePack[]; onScrollToGroup: (groupId: string) => void; }; -function StickerSidebar({ activeGroupAtom, packs, onScrollToGroup }: StickerSidebarProps) { +function StickerSidebar({ + activeGroupAtom, + packs, + onScrollToGroup, +}: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); @@ -294,7 +298,7 @@ function EmojiGroupHolder({ previewAtom, onGroupItemClick, children, -}: EmojiGroupHolderProps) { +}: Readonly) { const setPreviewData = useSetAtom(previewAtom); const handleEmojiPreview = useCallback( @@ -375,7 +379,7 @@ export function EmojiBoard({ onStickerSelect, allowTextCustomEmoji, addToRecentEmoji = true, -}: EmojiBoardProps) { +}: Readonly) { const mx = useMatrixClient(); const emojiTab = tab === EmojiBoardTab.Emoji; @@ -543,7 +547,7 @@ export function EmojiBoard({ id={SEARCH_GROUP_ID} label={searchedItems.length ? 'Search Results' : 'No Results found'} > - {searchedItems.map(renderItem)} + {searchedItems.map((element, index) => renderItem(element, index))} )}
( [room, setReplyDraft] ); - function MenuOptions({ mEvent }: { mEvent: MatrixEvent }) { + function MenuOptions({ mEvent }: Readonly<{ mEvent: MatrixEvent }>) { const setModal = useSetAtom(modalAtom); return ( @@ -177,7 +177,10 @@ export const EventHistory = as<'div', EventHistoryProps>( ); } - function EventItem({ mEvent, EventContent }: { mEvent: MatrixEvent; EventContent: IContent }) { + function EventItem({ + mEvent, + EventContent, + }: Readonly<{ mEvent: MatrixEvent; EventContent: IContent }>) { const [isHovered, setIsHovered] = useState(false); return ( ) { if (body === '') return ; if (customBody) { if (customBody === '') return ; diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx index eddc13e27..2afd2aae3 100644 --- a/src/app/components/message/Reply.tsx +++ b/src/app/components/message/Reply.tsx @@ -68,10 +68,10 @@ export const ThreadIndicator = as<'div'>(({ ...props }, ref) => ( type ReplyProps = { room: Room; - timelineSet?: EventTimelineSet | undefined; + timelineSet?: EventTimelineSet; replyEventId: string; - threadRootId?: string | undefined; - onClick?: MouseEventHandler | undefined; + threadRootId?: string; + onClick?: MouseEventHandler; }; export const Reply = as<'div', ReplyProps>( @@ -119,10 +119,10 @@ export const Reply = as<'div', ReplyProps>( if (format === 'org.matrix.custom.html' && formattedBody) { const strippedHtml = trimReplyFromFormattedBody(formattedBody) - .replace(//gi, ' ') - .replace(/<\/p>\s*]*>/gi, ' ') - .replace(/<\/?p[^>]*>/gi, '') - .replace(/(?:\r\n|\r|\n)/g, ' '); + .replaceAll(//gi, ' ') + .replaceAll(/<\/p>\s*]*>/gi, ' ') + .replaceAll(/<\/?p[^>]*>/gi, '') + .replaceAll(/(?:\r\n|\r|\n)/g, ' '); const parserOpts = getReactCustomHtmlParser(mx, room.roomId, { linkifyOpts: LINKIFY_OPTS, useAuthentication, @@ -130,7 +130,7 @@ export const Reply = as<'div', ReplyProps>( }); bodyJSX = parse(strippedHtml, parserOpts) as JSX.Element; } else if (body) { - const strippedBody = trimReplyFromBody(body).replace(/(?:\r\n|\r|\n)/g, ' '); + const strippedBody = trimReplyFromBody(body).replaceAll(/(?:\r\n|\r|\n)/g, ' '); bodyJSX = scaleSystemEmoji(strippedBody); } diff --git a/src/app/components/time-date/DatePicker.tsx b/src/app/components/time-date/DatePicker.tsx index c2daca427..078d91d8e 100644 --- a/src/app/components/time-date/DatePicker.tsx +++ b/src/app/components/time-date/DatePicker.tsx @@ -33,7 +33,7 @@ export const DatePicker = forwardRef( const currentDate = dateFor(selectedYear, selectedMonth, selectedDay); const time = value - currentDate; - const newDate = dateFor(year, month, mDays < selectedDay ? mDays : selectedDay); + const newDate = dateFor(year, month, Math.min(mDays, selectedDay)); const newValue = newDate + time; handleSubmit(newValue); @@ -60,7 +60,7 @@ export const DatePicker = forwardRef( - {Array.from(Array(daysInMonth(selectedMonth, selectedYear)).keys()) + {Array.from(new Array(daysInMonth(selectedMonth, selectedYear)).keys()) .map((i) => i + 1) .map((day) => ( ( ))} - {Array.from(Array(12).keys()) + {Array.from(new Array(12).keys()) .map((i) => i + 1) .map((month) => ( ( ))} - {Array.from(Array(yearsRange).keys()) + {Array.from(new Array(yearsRange).keys()) .map((i) => minYear + i) .map((year) => ( ( {hour24Clock - ? Array.from(Array(24).keys()).map((hour) => ( + ? Array.from(new Array(24).keys()).map((hour) => ( ( {hour < 10 ? `0${hour}` : hour} )) - : Array.from(Array(12).keys()) + : Array.from(new Array(12).keys()) .map((i) => { if (i === 0) return 12; return i; @@ -102,7 +102,7 @@ export const TimePicker = forwardRef( ))} - {Array.from(Array(60).keys()).map((minute) => ( + {Array.from(new Array(60).keys()).map((minute) => ( ( ) { const { originalFile, metadata } = fileItem; const fileUrl = useObjectURL(originalFile); @@ -53,7 +53,7 @@ function PreviewImage({ fileItem }: PreviewImageProps) { type PreviewVideoProps = { fileItem: TUploadItem; }; -function PreviewVideo({ fileItem }: PreviewVideoProps) { +function PreviewVideo({ fileItem }: Readonly) { const { originalFile, metadata } = fileItem; const fileUrl = useObjectURL(originalFile); @@ -131,7 +131,7 @@ export function UploadCardRenderer({ onRemove, onComplete, roomId, -}: UploadCardRendererProps) { +}: Readonly) { const mx = useMatrixClient(); const mediaConfig = useMediaConfig(); const allowSize = mediaConfig['m.upload.size'] || Infinity; diff --git a/src/app/components/upload-card/UploadDescriptionEditor.tsx b/src/app/components/upload-card/UploadDescriptionEditor.tsx index 447f02437..563f06eda 100644 --- a/src/app/components/upload-card/UploadDescriptionEditor.tsx +++ b/src/app/components/upload-card/UploadDescriptionEditor.tsx @@ -53,7 +53,7 @@ export function DescriptionEditor({ imagePackRooms, onSave, onCancel, -}: DescriptionEditorProps) { +}: Readonly) { const editor = useEditor(); const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline'); const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown'); diff --git a/src/app/components/user-profile/UserRoomProfile.tsx b/src/app/components/user-profile/UserRoomProfile.tsx index 09e2ab970..5d0f9e7e3 100644 --- a/src/app/components/user-profile/UserRoomProfile.tsx +++ b/src/app/components/user-profile/UserRoomProfile.tsx @@ -65,7 +65,7 @@ function UserExtendedSection({ profile, htmlReactParserOptions, linkifyOpts, -}: UserExtendedSectionProps) { +}: Readonly) { const clamp = (str: any, len: number) => { const stringified = String(str ?? ''); return stringified.length > len ? `${stringified.slice(0, len)}...` : stringified; @@ -108,7 +108,7 @@ function UserExtendedSection({ return new Intl.DateTimeFormat([], { hour: 'numeric', minute: '2-digit', - timeZone: profile.timezone.replace(/^["']|["']$/g, ''), + timeZone: profile.timezone.replaceAll(/^["']|["']$/g, ''), }).format(new Date()); } catch { return null; @@ -133,7 +133,7 @@ function UserExtendedSection({ const safetyTrim = rawBio.length > 2048 ? rawBio.slice(0, 2048) : rawBio; - const visibleText = safetyTrim.replace(/<[^>]*>?/gm, ''); + const visibleText = safetyTrim.replaceAll(/<[^>]*>?/gm, ''); const VISIBLE_LIMIT = 1024; if (visibleText.length <= VISIBLE_LIMIT) { @@ -163,7 +163,7 @@ function UserExtendedSection({ - {localTime} ({profile.timezone.replace(/^["']|["']$/g, '')}) + {localTime} ({profile.timezone.replaceAll(/^["']|["']$/g, '')}) )} @@ -252,7 +252,7 @@ type UserRoomProfileProps = { userId: string; initialProfile?: Partial; }; -export function UserRoomProfile({ userId, initialProfile }: UserRoomProfileProps) { +export function UserRoomProfile({ userId, initialProfile }: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const navigate = useNavigate(); @@ -296,7 +296,7 @@ export function UserRoomProfile({ userId, initialProfile }: UserRoomProfileProps const parsedBanner = typeof extendedProfile.bannerUrl === 'string' - ? extendedProfile.bannerUrl.replace(/^"|"$/g, '') + ? extendedProfile.bannerUrl.replaceAll(/^"|"$/g, '') : undefined; const bannerHttpUrl = parsedBanner diff --git a/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx b/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx index 4aacd518d..6162e4c0b 100644 --- a/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx +++ b/src/app/features/common-settings/emojis-stickers/RoomPacks.tsx @@ -45,7 +45,7 @@ type CreatePackTileProps = { packs: ImagePack[]; roomId: string; }; -function CreatePackTile({ packs, roomId }: CreatePackTileProps) { +function CreatePackTile({ packs, roomId }: Readonly) { const mx = useMatrixClient(); const alive = useAlive(); @@ -75,9 +75,9 @@ function CreatePackTile({ packs, roomId }: CreatePackTileProps) { const name = nameInput?.value.trim(); if (!name) return; - let packKey = name.replace(/\s/g, '-'); + let packKey = name.replaceAll(/\s/g, '-'); - const hasPack = (k: string): boolean => !!packs.find((pack) => pack.address?.stateKey === k); + const hasPack = (k: string): boolean => packs.some((pack) => pack.address?.stateKey === k); if (hasPack(packKey)) { packKey = suffixRename(packKey, hasPack); } @@ -141,7 +141,7 @@ function CreatePackTile({ packs, roomId }: CreatePackTileProps) { type RoomPacksProps = { onViewPack: (imagePack: ImagePack) => void; }; -export function RoomPacks({ onViewPack }: RoomPacksProps) { +export function RoomPacks({ onViewPack }: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const room = useRoom(); @@ -193,7 +193,7 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) { const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication) : undefined; const { address } = pack; if (!address) return null; - const removed = !!removedPacks.find((addr) => packAddressEqual(addr, address)); + const removed = removedPacks.some((addr) => packAddressEqual(addr, address)); return ( void; onClose: () => void; }; -function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) { +function EditPower({ maxPower, power, tag, onSave, onClose }: Readonly) { const mx = useMatrixClient(); const room = useRoom(); const roomToParents = useAtomValue(roomToParentsAtom); @@ -103,7 +103,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) { const nameInput = target?.nameInput as HTMLInputElement | undefined; if (!powerInput || !nameInput) return; - const tagPower = parseInt(powerInput.value, 10); + const tagPower = Number.parseInt(powerInput.value, 10); if (Number.isNaN(tagPower)) return; const tagName = nameInput.value.trim(); @@ -307,7 +307,7 @@ type PowersEditorProps = { powerLevels: IPowerLevels; requestClose: () => void; }; -export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) { +export function PowersEditor({ powerLevels, requestClose }: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const room = useRoom(); diff --git a/src/app/features/lobby/DnD.tsx b/src/app/features/lobby/DnD.tsx index 71c6bf8e3..fa4584a40 100644 --- a/src/app/features/lobby/DnD.tsx +++ b/src/app/features/lobby/DnD.tsx @@ -29,9 +29,8 @@ export const useDraggableItem = ( const target = targetRef.current; const dragHandle = dragHandleRef?.current ?? undefined; - return !target - ? undefined - : draggable({ + return target + ? draggable({ element: target, dragHandle, getInitialData: () => item, @@ -43,7 +42,8 @@ export const useDraggableItem = ( setDragging(false); onDragging(undefined); }, - }); + }) + : undefined; }, [targetRef, dragHandleRef, item, onDragging]); return dragging; @@ -72,14 +72,14 @@ export function AfterItemDropTarget({ afterSpace, nextRoomId, canDrop, -}: AfterItemDropTargetProps) { +}: Readonly) { const targetRef = useRef(null); const [dropState, setDropState] = useState<'idle' | 'allow' | 'not-allow'>('idle'); useEffect(() => { const target = targetRef.current; if (!target) { - throw Error('drop target ref is not set properly'); + throw new Error('drop target ref is not set properly'); } return dropTargetForElements({ @@ -126,7 +126,7 @@ export const useDnDMonitor = ( useEffect(() => { const scrollElement = scrollRef.current; if (!scrollElement) { - throw Error('Scroll element ref not configured'); + throw new Error('Scroll element ref not configured'); } return combine( diff --git a/src/app/features/lobby/Lobby.tsx b/src/app/features/lobby/Lobby.tsx index 0bc474260..180e0d81f 100644 --- a/src/app/features/lobby/Lobby.tsx +++ b/src/app/features/lobby/Lobby.tsx @@ -179,7 +179,7 @@ export function Lobby() { return new Set(sideSpaces); }, [sidebarItems]); - const [spacesItems, setSpacesItem] = useState>(() => new Map()); + const [spacesItems, setSpacesItems] = useState>(() => new Map()); useElementSizeObserver( useCallback(() => heroSectionRef.current, []), @@ -302,8 +302,7 @@ export function Lobby() { } if ( - itemRoom && - itemRoom.getJoinRule() === JoinRule.Restricted && + itemRoom?.getJoinRule() === JoinRule.Restricted && item.parentId !== containerParentId ) { // change join rule allow parameter when dragging @@ -394,7 +393,7 @@ export function Lobby() { const handleSpacesFound = useCallback( (sItems: IHierarchyRoom[]) => { setSpaceRooms({ type: 'PUT', roomIds: sItems.map((i) => i.room_id) }); - setSpacesItem((current) => { + setSpacesItems((current) => { const newItems = produce(current, (draft) => { sItems.forEach((item) => draft.set(item.room_id, item)); }); diff --git a/src/app/features/message-search/MessageSearch.tsx b/src/app/features/message-search/MessageSearch.tsx index 46c948f67..db9191725 100644 --- a/src/app/features/message-search/MessageSearch.tsx +++ b/src/app/features/message-search/MessageSearch.tsx @@ -49,7 +49,7 @@ export function MessageSearch({ rooms, senders, scrollRef, -}: MessageSearchProps) { +}: Readonly) { const mx = useMatrixClient(); const mDirects = useAtomValue(mDirectAtom); const allRooms = useRooms(mx, allRoomsAtom, mDirects); @@ -175,7 +175,7 @@ export function MessageSearch({ }); }; - const lastVItem = vItems[vItems.length - 1]; + const lastVItem = vItems.at(-1); const lastVItemIndex: number | undefined = lastVItem?.index; const lastGroupIndex = groups.length - 1; useEffect(() => { @@ -253,7 +253,7 @@ export function MessageSearch({ {((msgSearchParams.term && status === 'pending') || (groups.length > 0 && vItems.length === 0)) && ( - {[...Array(8).keys()].map((key) => ( + {[...new Array(8).keys()].map((key) => ( ))} diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 6365f851f..495cdd57c 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -205,7 +205,9 @@ export const timelineToEventsCount = (t: EventTimeline) => { export const getTimelinesEventsCount = (timelines: EventTimeline[]): number => { const timelineEventCountReducer = (count: number, tm: EventTimeline) => count + timelineToEventsCount(tm); - return (timelines || []).filter(Boolean).reduce(timelineEventCountReducer, 0); + return (timelines || []) + .filter(Boolean) + .reduce((accumulator, element) => timelineEventCountReducer(accumulator, element), 0); }; export const getTimelineAndBaseIndex = ( @@ -576,7 +578,12 @@ type ThreadReplyChipProps = { onToggle: () => void; }; -function ThreadReplyChip({ room, mEventId, openThreadId, onToggle }: ThreadReplyChipProps) { +function ThreadReplyChip({ + room, + mEventId, + openThreadId, + onToggle, +}: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const nicknames = useAtomValue(nicknamesAtom); @@ -602,7 +609,7 @@ function ThreadReplyChip({ room, mEventId, openThreadId, onToggle }: ThreadReply } }); - const latestReply = replyEvents[replyEvents.length - 1]; + const latestReply = replyEvents.at(-1); const latestSenderId = latestReply?.getSender() ?? ''; const latestSenderName = getMemberDisplayName(room, latestSenderId, nicknames) ?? @@ -706,7 +713,7 @@ export function RoomTimeline({ roomInputRef, editor, onEditorReset, -}: RoomTimelineProps) { +}: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const pushProcessor = useMemo(() => new PushProcessor(mx), [mx]); @@ -772,6 +779,7 @@ export function RoomTimeline({ const atBottomAnchorRef = useRef(null); + // TODO: The return value of "useState" should be destructured and named symmetrically (typescript:S6754) const [atBottom, setAtBottomState] = useState(true); const atBottomRef = useRef(atBottom); const setAtBottom = useCallback((val: boolean) => { diff --git a/src/app/features/room/RoomViewHeader.tsx b/src/app/features/room/RoomViewHeader.tsx index a73df7871..55719e42a 100644 --- a/src/app/features/room/RoomViewHeader.tsx +++ b/src/app/features/room/RoomViewHeader.tsx @@ -340,7 +340,7 @@ const RoomMenu = forwardRef(({ room, requestClose }); RoomMenu.displayName = 'RoomMenu'; -export function RoomViewHeader({ callView }: { callView?: boolean }) { +export function RoomViewHeader({ callView }: Readonly<{ callView?: boolean }>) { const navigate = useNavigate(); const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); @@ -408,14 +408,14 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) { } const lastSeenIndex = pinnedIds.indexOf(pinMarker?.last_seen_id); - if (lastSeenIndex !== -1) { - const newPins = pinnedIds.slice(lastSeenIndex + 1); - setUnreadPinsCount(newPins.length); - } else { + if (lastSeenIndex === -1) { const oldCount = pinMarker?.count ?? 0; const startIndex = Math.max(0, oldCount - 1); const newCount = pinnedIds.length > 0 ? pinnedIds.length - startIndex : 0; setUnreadPinsCount(Math.max(0, newCount)); + } else { + const newPins = pinnedIds.slice(lastSeenIndex + 1); + setUnreadPinsCount(newPins.length); } }; checkUnreads().catch((err) => { @@ -557,7 +557,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) { await mx.setRoomAccountData(room.roomId, AccountDataEvent.SablePinStatus, { hash, count: pinnedIds.length, - last_seen_id: pinnedIds[pinnedIds.length - 1], + last_seen_id: pinnedIds.at(-1), }); }; diff --git a/src/app/features/settings/about/About.tsx b/src/app/features/settings/about/About.tsx index be5418159..1f399991c 100644 --- a/src/app/features/settings/about/About.tsx +++ b/src/app/features/settings/about/About.tsx @@ -35,7 +35,7 @@ export function HomeserverInfo() { setFederationUrl(newUrl); } }) - .catch((error2) => setVersion({ error: error2 })); + .catch((error_) => setVersion({ error: error_ })); } else { setVersion({ error }); } @@ -144,7 +144,7 @@ export function HomeserverInfo() { type AboutProps = { requestClose: () => void; }; -export function About({ requestClose }: AboutProps) { +export function About({ requestClose }: Readonly) { const mx = useMatrixClient(); const devLabel = IS_RELEASE_TAG ? '' : '-dev'; const buildLabel = BUILD_HASH ? ` (${BUILD_HASH})` : ''; @@ -293,7 +293,7 @@ export function About({ requestClose }: AboutProps) { target="_blank" > Cinny - {' '} + is ©{' '} ) { const stripQuotes = (str?: string) => { if (!str) return ''; // to solve the silly tuwunel - return str.replace(/^["']|["']$/g, ''); + return str.replaceAll(/^["']|["']$/g, ''); }; const [tempColor, setTempColor] = useState(stripQuotes(current) || '#FFFFFF'); diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx index 42204d177..68daf9bf8 100644 --- a/src/app/features/settings/account/Profile.tsx +++ b/src/app/features/settings/account/Profile.tsx @@ -64,7 +64,7 @@ type ProfileProps = { profile: UserProfile; userId: string; }; -function ProfileAvatar({ profile, userId }: ProfileProps) { +function ProfileAvatar({ profile, userId }: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const capabilities = useCapabilities(); @@ -218,7 +218,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -function ProfileBanner({ profile, userId }: ProfileProps) { +function ProfileBanner({ profile, userId }: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const [alertRemove, setAlertRemove] = useState(false); @@ -387,7 +387,7 @@ function ProfileBanner({ profile, userId }: ProfileProps) { ); } -function ProfileDisplayName({ profile, userId }: ProfileProps) { +function ProfileDisplayName({ profile, userId }: Readonly) { const mx = useMatrixClient(); const capabilities = useCapabilities(); const disableSetDisplayname = capabilities['m.set_displayname']?.enabled === false; @@ -479,7 +479,7 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) { ); } -function ProfileExtended({ profile, userId }: ProfileProps) { +function ProfileExtended({ profile, userId }: Readonly) { const mx = useMatrixClient(); const setGlobalProfiles = useSetAtom(profilesCacheAtom); @@ -488,12 +488,12 @@ function ProfileExtended({ profile, userId }: ProfileProps) { const currentStatus = presence?.status || ''; // Keys we don't render here nor handle seperately but still need to exclude - const EXCLUDED_KEYS = ['kitty.meow.is_cat', 'kitty.meow.has_cats']; + const EXCLUDED_KEYS = new Set(['kitty.meow.is_cat', 'kitty.meow.has_cats']); // Unknown fields / unimplemented non-matrix-spec fields // Only renders them, can't edit or set const extendedFields = Object.entries(profile.extended || {}).filter( - ([key]) => !EXCLUDED_KEYS.includes(key) + ([key]) => !EXCLUDED_KEYS.has(key) ); const handleSaveField = useCallback( @@ -585,7 +585,7 @@ function ProfileExtended({ profile, userId }: ProfileProps) { onSave={(htmlBio) => { handleSaveField('moe.sable.app.bio', htmlBio); - const cleanedHtml = htmlBio.replace(/<\/blockquote>/g, ''); + const cleanedHtml = htmlBio.replaceAll('
', ''); handleSaveField('chat.commet.profile_bio', { format: 'org.matrix.custom.html', formatted_body: cleanedHtml, @@ -642,7 +642,7 @@ type AnimalCosmeticsProps = { profile: UserProfile; userId: string; }; -function AnimalCosmetics({ profile, userId }: AnimalCosmeticsProps) { +function AnimalCosmetics({ profile, userId }: Readonly) { const mx = useMatrixClient(); const setGlobalProfiles = useSetAtom(profilesCacheAtom); const [renderAnimals, setRenderAnimals] = useSetting(settingsAtom, 'renderAnimals'); diff --git a/src/app/features/settings/cosmetics/Themes.tsx b/src/app/features/settings/cosmetics/Themes.tsx index a57af34a0..8d0f75e7e 100644 --- a/src/app/features/settings/cosmetics/Themes.tsx +++ b/src/app/features/settings/cosmetics/Themes.tsx @@ -60,7 +60,7 @@ export const ThemeSelector = as<'div', ThemeSelectorProps>( ) ); -function SelectTheme({ disabled }: { disabled?: boolean }) { +function SelectTheme({ disabled }: Readonly<{ disabled?: boolean }>) { const themes = useThemes(); const themeNames = useThemeNames(); const [themeId, setThemeId] = useSetting(settingsAtom, 'themeId'); @@ -288,7 +288,7 @@ function ThemeSettings() { max="100" step="1" value={saturation} - onChange={(e) => setSaturation(parseInt(e.target.value, 10))} + onChange={(e) => setSaturation(Number.parseInt(e.target.value, 10))} style={{ width: toRem(160), cursor: 'pointer', @@ -361,7 +361,7 @@ function PageZoomInput() { 'value' in evt.target && typeof evt.target.value === 'string' ) { - const newZoom = parseInt(evt.target.value, 10); + const newZoom = Number.parseInt(evt.target.value, 10); if (Number.isNaN(newZoom)) return; const safeZoom = Math.max(Math.min(newZoom, 150), 75); setPageZoom(safeZoom); @@ -372,7 +372,7 @@ function PageZoomInput() { return ( void; }; -export function Experimental({ requestClose }: ExperimentalProps) { +export function Experimental({ requestClose }: Readonly) { return ( @@ -32,8 +32,8 @@ export function Experimental({ requestClose }: ExperimentalProps) { variant="Warning" description={ <> - The features listed below may be unstable or incomplete, - use at your own risk. + The features listed below may be unstable or incomplete,{' '} + use at your own risk.
Please report any new issues potentially caused by these features! diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index fd0d9d705..1be1c8c0b 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -56,7 +56,7 @@ type DateHintProps = { hasChanges: boolean; handleReset: () => void; }; -function DateHint({ hasChanges, handleReset }: DateHintProps) { +function DateHint({ hasChanges, handleReset }: Readonly) { const [anchor, setAnchor] = useState(); const categoryPadding = { padding: config.space.S200, paddingTop: 0 }; @@ -223,7 +223,7 @@ type CustomDateFormatProps = { value: string; onChange: (format: string) => void; }; -function CustomDateFormat({ value, onChange }: CustomDateFormatProps) { +function CustomDateFormat({ value, onChange }: Readonly) { const [dateFormatCustom, setDateFormatCustom] = useState(value); useEffect(() => { @@ -288,12 +288,12 @@ type PresetDateFormatProps = { value: string; onChange: (format: string) => void; }; -function PresetDateFormat({ value, onChange }: PresetDateFormatProps) { +function PresetDateFormat({ value, onChange }: Readonly) { const [menuCords, setMenuCords] = useState(); const dateFormatItems = useDateFormatItems(); const getDisplayDate = (format: string): string => - format !== '' ? dayjs().format(format) : 'Custom'; + format === '' ? 'Custom' : dayjs().format(format); const handleMenu: MouseEventHandler = (evt) => { setMenuCords(evt.currentTarget.getBoundingClientRect()); @@ -415,7 +415,7 @@ function DateAndTime() { ); } -function Editor({ isMobile }: { isMobile: boolean }) { +function Editor({ isMobile }: Readonly<{ isMobile: boolean }>) { const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline'); const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown'); const [hideActivity, setHideActivity] = useSetting(settingsAtom, 'hideActivity'); @@ -681,7 +681,7 @@ function SelectMessageSpacing() { ); } -function SelectRightSwipeAction({ disabled }: { disabled?: boolean }) { +function SelectRightSwipeAction({ disabled }: Readonly<{ disabled?: boolean }>) { const [menuCords, setMenuCords] = useState(); const [action, setAction] = useSetting(settingsAtom, 'rightSwipeAction'); @@ -749,7 +749,7 @@ function SelectRightSwipeAction({ disabled }: { disabled?: boolean }) { ); } -function Gestures({ isMobile }: { isMobile: boolean }) { +function Gestures({ isMobile }: Readonly<{ isMobile: boolean }>) { const [mobileGestures, setMobileGestures] = useSetting(settingsAtom, 'mobileGestures'); return ( @@ -788,7 +788,7 @@ function EmojiSelectorThresholdInput() { const val = evt.target.value; setInputValue(val); - const parsed = parseInt(val, 10); + const parsed = Number.parseInt(val, 10); if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) { setEmojiThreshold(parsed); } @@ -809,7 +809,7 @@ function EmojiSelectorThresholdInput() { return ( void; }; -export function General({ requestClose }: GeneralProps) { +export function General({ requestClose }: Readonly) { return ( diff --git a/src/app/hooks/useCommands.ts b/src/app/hooks/useCommands.ts index 0c2008c01..3a9b2a1ca 100644 --- a/src/app/hooks/useCommands.ts +++ b/src/app/hooks/useCommands.ts @@ -36,16 +36,16 @@ import { useRoomNavigate } from './useRoomNavigate'; import { enrichWidgetUrl } from './useRoomWidgets'; import { useUserProfile } from './useUserProfile'; -export const SHRUG = '¯\\_(ツ)_/¯'; +export const SHRUG = String.raw`¯\_(ツ)_/¯`; export const TABLEFLIP = '(╯°□°)╯︵ ┻━┻'; export const UNFLIP = '┬─┬ノ( º_ºノ)'; -const FLAG_PAT = '(?:^|\\s)-(\\w+)\\b'; +const FLAG_PAT = String.raw`(?:^|\s)-(\w+)\b`; const FLAG_REG = new RegExp(FLAG_PAT); const FLAG_REG_G = new RegExp(FLAG_PAT, 'g'); export const splitPayloadContentAndFlags = (payload: string): [string, string | undefined] => { - const flagMatch = payload.match(FLAG_REG); + const flagMatch = new RegExp(FLAG_REG).exec(payload); if (!flagMatch) { return [payload, undefined]; @@ -116,7 +116,7 @@ export const parseTimestampFlag = (input: string): number | undefined => { return undefined; } - const value = parseFloat(match[1]); // supports decimal values + const value = Number.parseFloat(match[1]); // supports decimal values const unit = match[2]; const now = Date.now(); // in milliseconds @@ -282,7 +282,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { }, [Command.Shrug]: { name: Command.Shrug, - description: 'Send ¯\\_(ツ)_/¯ as message', + description: String.raw`Send ¯\_(ツ)_/¯ as message`, exe: async () => undefined, }, [Command.TableFlip]: { @@ -559,8 +559,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { room.roomId, token, 20, - Direction.Forward, - undefined + Direction.Forward ); const { end, chunk } = response; // remove until the latest event; @@ -749,7 +748,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { exe: async (payload) => { const input = payload .trim() - .replace(/[;{}<>]/g, '') + .replaceAll(/[;{}<>]/g, '') .slice(0, 32); const userId = mx.getSafeUserId(); @@ -793,7 +792,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => { exe: async (payload) => { const input = payload .trim() - .replace(/[;{}<>]/g, '') + .replaceAll(/[;{}<>]/g, '') .slice(0, 32); const userId = mx.getSafeUserId(); diff --git a/src/app/hooks/useRoomWidgets.ts b/src/app/hooks/useRoomWidgets.ts index a424e2eca..65381ddd3 100644 --- a/src/app/hooks/useRoomWidgets.ts +++ b/src/app/hooks/useRoomWidgets.ts @@ -27,16 +27,16 @@ export const resolveWidgetUrl = ( const theme = document.body.classList.contains('dark-theme') ? 'dark' : 'light'; let resolved = url - .replace(/\$matrix_user_id/g, encodeURIComponent(userId)) - .replace(/\$matrix_room_id/g, encodeURIComponent(roomId)) - .replace(/\$matrix_display_name/g, encodeURIComponent(displayName)) - .replace(/\$matrix_avatar_url/g, encodeURIComponent(avatarUrl)) - .replace(/\$matrix_widget_id/g, encodeURIComponent(widgetId)) - .replace(/\$org\.matrix\.msc2873\.client_id/g, encodeURIComponent(clientId)) - .replace(/\$org\.matrix\.msc2873\.client_theme/g, encodeURIComponent(theme)) - .replace(/\$org\.matrix\.msc2873\.client_language/g, encodeURIComponent(lang)) - .replace(/\$org\.matrix\.msc3819\.matrix_device_id/g, encodeURIComponent(deviceId)) - .replace(/\$org\.matrix\.msc4039\.matrix_base_url/g, encodeURIComponent(baseUrl)); + .replaceAll('$matrix_user_id', encodeURIComponent(userId)) + .replaceAll('$matrix_room_id', encodeURIComponent(roomId)) + .replaceAll('$matrix_display_name', encodeURIComponent(displayName)) + .replaceAll('$matrix_avatar_url', encodeURIComponent(avatarUrl)) + .replaceAll('$matrix_widget_id', encodeURIComponent(widgetId)) + .replaceAll('$org.matrix.msc2873.client_id', encodeURIComponent(clientId)) + .replaceAll('$org.matrix.msc2873.client_theme', encodeURIComponent(theme)) + .replaceAll('$org.matrix.msc2873.client_language', encodeURIComponent(lang)) + .replaceAll('$org.matrix.msc3819.matrix_device_id', encodeURIComponent(deviceId)) + .replaceAll('$org.matrix.msc4039.matrix_base_url', encodeURIComponent(baseUrl)); try { const u = new URL(resolved); @@ -112,7 +112,7 @@ export const useRoomWidgets = (room: Room): RoomWidget[] => { return events.reduce((widgets, event) => { const content = event.getContent(); - if (!content || !content.url || Object.keys(content).length === 0) return widgets; + if (!content?.url || Object.keys(content).length === 0) return widgets; const stateKey = event.getStateKey(); if (!stateKey) return widgets; diff --git a/src/app/hooks/useUserProfile.ts b/src/app/hooks/useUserProfile.ts index e5f1a013a..22ffa5b2a 100644 --- a/src/app/hooks/useUserProfile.ts +++ b/src/app/hooks/useUserProfile.ts @@ -27,7 +27,7 @@ export type UserProfile = { }; const normalizeInfo = (info: any): UserProfile => { - const knownKeys = [ + const knownKeys = new Set([ 'avatar_url', 'displayname', 'io.fsky.nyx.pronouns', @@ -40,11 +40,11 @@ const normalizeInfo = (info: any): UserProfile => { 'moe.sable.app.name_color', 'kitty.meow.has_cats', 'kitty.meow.is_cat', - ]; + ]); const extended: Record = {}; Object.entries(info).forEach(([key, value]) => { - if (!knownKeys.includes(key)) { + if (!knownKeys.has(key)) { extended[key] = value; } }); @@ -68,10 +68,10 @@ const normalizeInfo = (info: any): UserProfile => { const isValidHex = (c: any): string | undefined => { if (typeof c !== 'string') return undefined; // silly tuwunel smh - const cleaned = c.replace(/["']/g, '').trim(); + const cleaned = c.replaceAll(/["']/g, '').trim(); return /^#([0-9A-F]{3,6})$/i.test(cleaned) ? cleaned : undefined; }; -const sanitizeFont = (f: string) => f.replace(/[;{}<>]/g, '').slice(0, 32); +const sanitizeFont = (f: string) => f.replaceAll(/[;{}<>]/g, '').slice(0, 32); export const useUserProfile = ( userId: string, diff --git a/src/app/pages/client/explore/Server.tsx b/src/app/pages/client/explore/Server.tsx index 84fd8f36f..0ff06dd54 100644 --- a/src/app/pages/client/explore/Server.tsx +++ b/src/app/pages/client/explore/Server.tsx @@ -90,7 +90,7 @@ type SearchProps = { onSearch: (term: string) => void; onReset: () => void; }; -function Search({ active, loading, searchInputRef, onSearch, onReset }: SearchProps) { +function Search({ active, loading, searchInputRef, onSearch, onReset }: Readonly) { const handleSearchSubmit: FormEventHandler = (evt) => { evt.preventDefault(); const { searchInput } = evt.target as HTMLFormElement & { @@ -149,10 +149,10 @@ const DEFAULT_INSTANCE_NAME = 'Matrix'; function ThirdPartyProtocolsSelector({ instanceId, onChange, -}: { +}: Readonly<{ instanceId?: string; onChange: (instanceId?: string) => void; -}) { +}>) { const mx = useMatrixClient(); const [menuAnchor, setMenuAnchor] = useState(); @@ -251,7 +251,7 @@ type LimitButtonProps = { limit: number; onLimitChange: (limit: string) => void; }; -function LimitButton({ limit, onLimitChange }: LimitButtonProps) { +function LimitButton({ limit, onLimitChange }: Readonly) { const [menuAnchor, setMenuAnchor] = useState(); const handleLimitSubmit: FormEventHandler = (evt) => { @@ -360,7 +360,7 @@ export function PublicRooms() { const currentLimit: number = useMemo(() => { const limitParam = serverSearchParams.limit; if (!limitParam) return FALLBACK_ROOMS_LIMIT; - return parseInt(limitParam, 10) || FALLBACK_ROOMS_LIMIT; + return Number.parseInt(limitParam, 10) || FALLBACK_ROOMS_LIMIT; }, [serverSearchParams.limit]); const resetScroll = useCallback(() => { @@ -371,7 +371,7 @@ export function PublicRooms() { const fetchPublicRooms = useCallback(() => { const limit = typeof serverSearchParams.limit === 'string' - ? parseInt(serverSearchParams.limit, 10) + ? Number.parseInt(serverSearchParams.limit, 10) : FALLBACK_ROOMS_LIMIT; const roomType: string | null | undefined = serverSearchParams.type === 'null' ? null : serverSearchParams.type; @@ -387,7 +387,7 @@ export function PublicRooms() { since: serverSearchParams.since, filter: { generic_search_term: serverSearchParams.term, - room_types: roomType !== undefined ? [roomType] : undefined, + room_types: roomType === undefined ? undefined : [roomType], }, third_party_instance_id: serverSearchParams.instance, } @@ -574,7 +574,7 @@ export function PublicRooms() {
{isLoading && ( - {[...Array(currentLimit).keys()].map((item) => ( + {[...new Array(currentLimit).keys()].map((item) => ( ))} diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx index adc79ed2b..7543720d0 100644 --- a/src/app/pages/client/inbox/Notifications.tsx +++ b/src/app/pages/client/inbox/Notifications.tsx @@ -121,7 +121,7 @@ const groupNotifications = ( const groupIndex = groups.length - 1; const lastAddedGroup: RoomNotificationsGroup | undefined = groups[groupIndex]; - if (lastAddedGroup && notification.room_id === lastAddedGroup.roomId) { + if (notification.room_id === lastAddedGroup?.roomId) { lastAddedGroup.notifications.push(notification); return; } @@ -223,7 +223,7 @@ function RoomNotificationsGroupComp({ legacyUsernameColor, hour24Clock, dateFormatString, -}: RoomNotificationsGroupProps) { +}: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const unread = useRoomUnread(room.roomId, roomToUnreadAtom); @@ -623,7 +623,7 @@ export function Notifications() { loadTimeline(); }, [loadTimeline]); - const lastVItem = vItems[vItems.length - 1]; + const lastVItem = vItems.at(-1); const lastVItemIndex: number | undefined = lastVItem?.index; useEffect(() => { if ( @@ -671,7 +671,7 @@ export function Notifications() { setOnlyHighlighted(false)} - variant={!onlyHighlight ? 'Success' : 'Surface'} + variant={onlyHighlight ? 'Surface' : 'Success'} aria-pressed={!onlyHighlight} before={!onlyHighlight && } outlined @@ -758,7 +758,7 @@ export function Notifications() { {timelineState.status === AsyncStatus.Loading && ( - {[...Array(8).keys()].map((key) => ( + {[...new Array(8).keys()].map((key) => ( ({ item }), @@ -258,7 +257,8 @@ const useDraggableItem = ( setDragging(false); onDragging?.(undefined); }, - }); + }) + : undefined; }, [targetRef, dragHandleRef, item, onDragging]); return dragging; @@ -357,7 +357,7 @@ const useDnDMonitor = ( useEffect(() => { const scrollElement = scrollRef.current; if (!scrollElement) { - throw Error('Scroll element ref not configured'); + throw new Error('Scroll element ref not configured'); } return combine( @@ -399,7 +399,7 @@ function SpaceTab({ onDragging, disabled, onUnpin, -}: SpaceTabProps) { +}: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const targetRef = useRef(null); @@ -515,7 +515,7 @@ type OpenedSpaceFolderProps = { onClose: MouseEventHandler; children?: ReactNode; }; -function OpenedSpaceFolder({ folder, onClose, children }: OpenedSpaceFolderProps) { +function OpenedSpaceFolder({ folder, onClose, children }: Readonly) { const aboveTargetRef = useRef(null); const belowTargetRef = useRef(null); @@ -555,7 +555,7 @@ function ClosedSpaceFolder({ onOpen, onDragging, disabled, -}: ClosedSpaceFolderProps) { +}: Readonly) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const handlerRef = useRef(null); @@ -628,7 +628,7 @@ function ClosedSpaceFolder({ type SpaceTabsProps = { scrollRef: RefObject; }; -export function SpaceTabs({ scrollRef }: SpaceTabsProps) { +export function SpaceTabs({ scrollRef }: Readonly) { const navigate = useNavigate(); const mx = useMatrixClient(); const screenSize = useScreenSizeContext(); @@ -786,7 +786,7 @@ export function SpaceTabs({ scrollRef }: SpaceTabsProps) { } const activePath = navToActivePath.get(targetSpaceId); - if (activePath && activePath.pathname.startsWith(spacePath)) { + if (activePath?.pathname.startsWith(spacePath)) { navigate(joinPathComponent(activePath)); return; } diff --git a/src/app/utils/ASCIILexicalTable.ts b/src/app/utils/ASCIILexicalTable.ts index 3dcd43d05..46c35c393 100644 --- a/src/app/utils/ASCIILexicalTable.ts +++ b/src/app/utils/ASCIILexicalTable.ts @@ -61,13 +61,13 @@ export class ASCIILexicalTable { } first(): string { - return String.fromCharCode(this.startCode); + return String.fromCodePoint(this.startCode); } last(): string { let str = ''; for (let i = 0; i < this.maxStrWidth; i += 1) { - str += String.fromCharCode(this.endCode); + str += String.fromCodePoint(this.endCode); } return str; } @@ -164,7 +164,7 @@ export class ASCIILexicalTable { prev += String.fromCharCode(lastCode - 1); while (prev.length < this.maxStrWidth) { - prev += String.fromCharCode(this.endCode); + prev += String.fromCodePoint(this.endCode); } return prev; } diff --git a/src/app/utils/AsyncSearch.ts b/src/app/utils/AsyncSearch.ts index 26dd92047..c799c60bb 100644 --- a/src/app/utils/AsyncSearch.ts +++ b/src/app/utils/AsyncSearch.ts @@ -27,12 +27,12 @@ export type TerminateAsyncSearch = () => void; export const normalize = (str: string, options?: NormalizeOption) => { let nStr = str.normalize((options?.normalizeUnicode ?? true) ? 'NFKC' : 'NFC'); if (!options?.caseSensitive) nStr = nStr.toLocaleLowerCase(); - if (options?.ignoreWhitespace ?? true) nStr = nStr.replace(/\s/g, ''); + if (options?.ignoreWhitespace ?? true) nStr = nStr.replaceAll(/\s/g, ''); return nStr; }; export const matchQuery = (item: string, query: string, options?: MatchQueryOption): boolean => { - if (options?.contain) return item.indexOf(query) !== -1; + if (options?.contain) return item.includes(query); return item.startsWith(query); }; @@ -46,7 +46,7 @@ export const AsyncSearch = ( let searchIndex = 0; let sessionStartTimestamp = 0; - let sessionScheduleId: number | undefined; + let sessionScheduleId: ReturnType | undefined; const terminateSearch: TerminateAsyncSearch = () => { resultList = []; @@ -62,7 +62,7 @@ export const AsyncSearch = ( // return if find session got reset if (sessionTimestamp !== sessionStartTimestamp) return; - sessionStartTimestamp = window.performance.now(); + sessionStartTimestamp = globalThis.performance.now(); for (; searchIndex < list.length; searchIndex += 1) { if (match(list[searchIndex], query)) { resultList.push(list[searchIndex]); @@ -71,14 +71,14 @@ export const AsyncSearch = ( } } - const matchFinishTime = window.performance.now(); + const matchFinishTime = globalThis.performance.now(); if (matchFinishTime - sessionStartTimestamp > 8) { const currentFindingCount = resultList.length; const thisSessionTimestamp = sessionStartTimestamp; if (findingCount !== currentFindingCount) onResult(resultList, query); searchIndex += 1; - sessionScheduleId = window.setTimeout(() => find(query, thisSessionTimestamp), 1); + sessionScheduleId = globalThis.setTimeout(() => find(query, thisSessionTimestamp), 1); return; } } diff --git a/src/app/utils/MegolmExportEncryption.ts b/src/app/utils/MegolmExportEncryption.ts index 896a8d9d4..96250e5bd 100644 --- a/src/app/utils/MegolmExportEncryption.ts +++ b/src/app/utils/MegolmExportEncryption.ts @@ -5,7 +5,7 @@ import { createLogger } from './debug'; const logger = createLogger('MegolmExportEncryption'); -const subtleCrypto = window.crypto.subtle; +const subtleCrypto = globalThis.crypto.subtle; export type FriendlyError = { message: string; @@ -43,9 +43,9 @@ function toArrayBufferView(data: Uint8Array): Uint8Array { function encodeBase64(uint8Array: Uint8Array): string { // Misinterpt the Uint8Array as Latin-1. // window.btoa expects a unicode string with codepoints in the range 0-255. - const latin1String = String.fromCharCode.apply(null, Array.from(uint8Array)); + const latin1String = String.fromCodePoint.apply(null, Array.from(uint8Array)); // Use the builtin base64 encoder. - return window.btoa(latin1String); + return globalThis.btoa(latin1String); } /** @@ -55,7 +55,7 @@ function encodeBase64(uint8Array: Uint8Array): string { */ function decodeBase64(base64: string): Uint8Array { // window.atob returns a unicode string with codepoints in the range 0-255. - const latin1String = window.atob(base64); + const latin1String = globalThis.atob(base64); // Encode the string as a Uint8Array const uint8Array = new Uint8Array(new ArrayBuffer(latin1String.length)); for (let i = 0; i < latin1String.length; i += 1) { @@ -303,10 +303,10 @@ export async function encryptMegolmKeyFile( const kdfRounds = normalizedOptions.kdf_rounds || 500000; const salt = new Uint8Array(new ArrayBuffer(16)); - window.crypto.getRandomValues(salt); + globalThis.crypto.getRandomValues(salt); const iv = new Uint8Array(new ArrayBuffer(16)); - window.crypto.getRandomValues(iv); + globalThis.crypto.getRandomValues(iv); // clear bit 63 of the IV to stop us hitting the 64-bit counter boundary // (which would mean we wouldn't be able to decrypt on Android). The loss diff --git a/src/app/utils/colorMXID.ts b/src/app/utils/colorMXID.ts index 6b40b9596..c94a19d48 100644 --- a/src/app/utils/colorMXID.ts +++ b/src/app/utils/colorMXID.ts @@ -6,11 +6,11 @@ function hashCode(str?: string): number { return hash; } for (let i = 0; i < str.length; i += 1) { - const chr = str.charCodeAt(i); + const chr = str.codePointAt(i) ?? 0; // eslint-disable-next-line no-bitwise hash = (hash << 5) - hash + chr; // eslint-disable-next-line no-bitwise - hash |= 0; + hash = Math.trunc(hash); } return Math.abs(hash); }