@@ -2650,7 +2640,6 @@ export class RoomView extends React.Component { { private heightUpdateInProgress = false; public divScroll: HTMLDivElement | null = null; - public constructor(props: IProps) { - super(props); + public static contextType = SDKContext; + declare public context: React.ContextType; + + public constructor(props: IProps, context: React.ContextType) { + super(props, context); this.resetScrollState(); } public componentDidMount(): void { this.unmounted = false; - this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize); + this.context?.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize); this.checkScroll(); } @@ -217,14 +216,14 @@ export default class ScrollPanel extends React.Component { // (We could use isMounted(), but facebook have deprecated that.) this.unmounted = true; - this.props.resizeNotifier?.removeListener("middlePanelResizedNoisy", this.onResize); + this.context?.resizeNotifier?.removeListener("middlePanelResizedNoisy", this.onResize); this.divScroll = null; } private onScroll = (ev: Event): void => { // skip scroll events caused by resizing - if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return; + if (this.context?.resizeNotifier && this.context.resizeNotifier.isResizing) return; debuglog("onScroll called past resize gate; scroll node top:", this.getScrollNode().scrollTop); this.scrollTimeout?.restart(); this.saveScrollState(); diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 59ec657b02..c2aa7f2f10 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -763,7 +763,7 @@ export default class SpaceRoomView extends React.PureComponent { return (
- + {this.renderBody()} diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 8346a0ab31..c112d341b3 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -47,7 +47,6 @@ import shouldHideEvent from "../../shouldHideEvent"; import MessagePanel from "./MessagePanel"; import { type IScrollState } from "./ScrollPanel"; import { type ActionPayload } from "../../dispatcher/payloads"; -import type ResizeNotifier from "../../utils/ResizeNotifier"; import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import Spinner from "../views/elements/Spinner"; import type EditorStateTransfer from "../../utils/EditorStateTransfer"; @@ -123,7 +122,6 @@ interface IProps { // whether to always show timestamps for an event alwaysShowTimestamps?: boolean; - resizeNotifier?: ResizeNotifier; editState?: EditorStateTransfer; permalinkCreator?: RoomPermalinkCreator; membersLoaded?: boolean; @@ -1849,7 +1847,6 @@ class TimelinePanel extends React.Component { this.state.alwaysShowTimestamps } className={this.props.className} - resizeNotifier={this.props.resizeNotifier} getRelationsForEvent={this.getRelationsForEvent} editState={this.props.editState} showReactions={this.props.showReactions} diff --git a/src/components/structures/UserView.tsx b/src/components/structures/UserView.tsx index 58aed9932b..ded92b6eb9 100644 --- a/src/components/structures/UserView.tsx +++ b/src/components/structures/UserView.tsx @@ -87,12 +87,7 @@ export default class UserView extends React.Component { /> ); return ( - + ); diff --git a/src/components/structures/WaitingForThirdPartyRoomView.tsx b/src/components/structures/WaitingForThirdPartyRoomView.tsx index 3983b286f1..a02a807c9b 100644 --- a/src/components/structures/WaitingForThirdPartyRoomView.tsx +++ b/src/components/structures/WaitingForThirdPartyRoomView.tsx @@ -41,7 +41,7 @@ export const WaitingForThirdPartyRoomView: React.FC = ({ roomView, resize
- + ; @@ -126,6 +127,9 @@ export function useMemberListViewModel(roomId: string): MemberListViewState { const [isLoading, setIsLoading] = useState(true); // This is the last known total number of members in this room. const [totalMemberCount, setTotalMemberCount] = useState(0); + + const memberCountWithout3Pid = useRoomMemberCount(room, { includeInvited: true }); + /** * This is the current number of members in the list. * This number will be less than the total number of members @@ -168,7 +172,7 @@ export function useMemberListViewModel(roomId: string): MemberListViewState { } setMemberMap(newMemberMap); - setMemberCount(joinedSdk.length + invitedSdk.length + threePidInvited.length); + setMemberCount(memberCountWithout3Pid + threePidInvited.length); if (!searchQuery) { /** * Since searching for members only gives you the relevant @@ -180,7 +184,7 @@ export function useMemberListViewModel(roomId: string): MemberListViewState { 500, { leading: true, trailing: true }, ), - [sdkContext.memberListStore, roomId, room], + [sdkContext.memberListStore, roomId, room, memberCountWithout3Pid], ); const isPresenceEnabled = useMemo( diff --git a/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts b/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts index 60ad21236f..ca15b6a6e3 100644 --- a/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts +++ b/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts @@ -5,11 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useState } from "react"; +import { CryptoEvent } from "matrix-js-sdk/src/crypto-api"; import { logger } from "matrix-js-sdk/src/logger"; import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../../../DeviceListener"; +import { useEventEmitterAsyncState } from "../../../../hooks/useEventEmitter"; interface KeyStoragePanelState { /** @@ -37,31 +39,37 @@ interface KeyStoragePanelState { /** Returns a ViewModel for use in {@link KeyStoragePanel} and {@link DeleteKeyStoragePanel}. */ export function useKeyStoragePanelViewModel(): KeyStoragePanelState { - const [isEnabled, setIsEnabled] = useState(undefined); const [loading, setLoading] = useState(true); // Whilst the change is being made, the toggle will reflect the pending value rather than the actual state const [pendingValue, setPendingValue] = useState(undefined); const matrixClient = useMatrixClientContext(); - const checkStatus = useCallback(async () => { - const crypto = matrixClient.getCrypto(); - if (!crypto) { - logger.error("Can't check key backup status: no crypto module available"); - return; - } - // The toggle is enabled only if this device will upload megolm keys to the backup. - // This is consistent with EX. - const activeBackupVersion = await crypto.getActiveSessionBackupVersion(); - setIsEnabled(activeBackupVersion !== null); - }, [matrixClient]); + const isEnabled = useEventEmitterAsyncState( + matrixClient, + CryptoEvent.KeyBackupStatus, + async (enabled?: boolean) => { + // If we're called as a result of an event, rather than during + // initialisation, we can get the backup status from the event + // instead of having to query the backup version. + if (enabled !== undefined) { + return enabled; + } - useEffect(() => { - (async () => { - await checkStatus(); + const crypto = matrixClient.getCrypto(); + if (!crypto) { + logger.error("Can't check key backup status: no crypto module available"); + return; + } + // The toggle is enabled only if this device will upload megolm keys to the backup. + // This is consistent with EX. + const activeBackupVersion = await crypto.getActiveSessionBackupVersion(); setLoading(false); - })(); - }, [checkStatus]); + return activeBackupVersion !== null; + }, + [matrixClient], + undefined, + ); const setEnabled = useCallback( async (enable: boolean) => { @@ -121,14 +129,12 @@ export function useKeyStoragePanelViewModel(): KeyStoragePanelState { // so this will stop EX turning it back on spontaneously. await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: true }); } - - await checkStatus(); } finally { setPendingValue(undefined); DeviceListener.sharedInstance().start(matrixClient); } }, - [setPendingValue, checkStatus, matrixClient], + [setPendingValue, matrixClient], ); return { isEnabled: pendingValue ?? isEnabled, setEnabled, loading, busy: pendingValue !== undefined }; diff --git a/src/components/views/audio_messages/LegacySeekBar.tsx b/src/components/views/audio_messages/LegacySeekBar.tsx index 798a4322b7..8177abf1be 100644 --- a/src/components/views/audio_messages/LegacySeekBar.tsx +++ b/src/components/views/audio_messages/LegacySeekBar.tsx @@ -10,7 +10,7 @@ import React, { type ChangeEvent, type CSSProperties, type ReactNode } from "rea import { type PlaybackInterface } from "../../../audio/Playback"; import { MarkedExecution } from "../../../utils/MarkedExecution"; -import { percentageOf } from "../../../shared-components/utils/numbers"; +import { percentageOf } from "../../../../packages/shared-components/src/utils/numbers"; import { _t } from "../../../languageHandler"; interface IProps { diff --git a/src/components/views/audio_messages/LiveRecordingClock.tsx b/src/components/views/audio_messages/LiveRecordingClock.tsx index 7807f30b72..8c3d2d2668 100644 --- a/src/components/views/audio_messages/LiveRecordingClock.tsx +++ b/src/components/views/audio_messages/LiveRecordingClock.tsx @@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { type IRecordingUpdate } from "../../../audio/VoiceRecording"; -import { Clock } from "../../../shared-components/audio/Clock"; +import { Clock } from "../../../../packages/shared-components/src/audio/Clock"; import { MarkedExecution } from "../../../utils/MarkedExecution"; import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording"; diff --git a/src/components/views/audio_messages/PlaybackClock.tsx b/src/components/views/audio_messages/PlaybackClock.tsx index 9dc6cdced3..b4a51ba464 100644 --- a/src/components/views/audio_messages/PlaybackClock.tsx +++ b/src/components/views/audio_messages/PlaybackClock.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import React from "react"; -import { Clock } from "../../../shared-components/audio/Clock"; +import { Clock } from "../../../../packages/shared-components/src/audio/Clock"; import { type Playback, PlaybackState } from "../../../audio/Playback"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; diff --git a/src/components/views/audio_messages/PlaybackWaveform.tsx b/src/components/views/audio_messages/PlaybackWaveform.tsx index 895348fb51..4694946cc1 100644 --- a/src/components/views/audio_messages/PlaybackWaveform.tsx +++ b/src/components/views/audio_messages/PlaybackWaveform.tsx @@ -11,7 +11,7 @@ import React from "react"; import { arraySeed, arrayTrimFill } from "../../../utils/arrays"; import Waveform from "./Waveform"; import { type Playback } from "../../../audio/Playback"; -import { percentageOf } from "../../../shared-components/utils/numbers"; +import { percentageOf } from "../../../../packages/shared-components/src/utils/numbers"; import { PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/consts"; interface IProps { diff --git a/src/components/views/beacon/BeaconListItem.tsx b/src/components/views/beacon/BeaconListItem.tsx index b41916efd8..4c5b777011 100644 --- a/src/components/views/beacon/BeaconListItem.tsx +++ b/src/components/views/beacon/BeaconListItem.tsx @@ -18,7 +18,7 @@ import BeaconStatus from "./BeaconStatus"; import { BeaconDisplayStatus } from "./displayStatus"; import StyledLiveBeaconIcon from "./StyledLiveBeaconIcon"; import ShareLatestLocation from "./ShareLatestLocation"; -import { humanizeTime } from "../../../shared-components/utils/humanize"; +import { humanizeTime } from "../../../../packages/shared-components/src/utils/humanize"; interface Props { beacon: Beacon; diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 6108883c88..afaad55ae3 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -63,10 +63,10 @@ import AskInviteAnywayDialog, { type UnknownProfiles } from "./AskInviteAnywayDi import { SdkContextClass } from "../../../contexts/SDKContext"; import { type UserProfilesStore } from "../../../stores/UserProfilesStore"; import InviteProgressBody from "./InviteProgressBody.tsx"; -import { RichList } from "../../../shared-components/rich-list/RichList"; -import { RichItem } from "../../../shared-components/rich-list/RichItem"; -import { PillInput } from "../../../shared-components/pill-input/PillInput"; -import { Pill } from "../../../shared-components/pill-input/Pill"; +import { RichList } from "../../../../packages/shared-components/src/rich-list/RichList"; +import { RichItem } from "../../../../packages/shared-components/src/rich-list/RichItem"; +import { PillInput } from "../../../../packages/shared-components/src/pill-input/PillInput"; +import { Pill } from "../../../../packages/shared-components/src/pill-input/Pill"; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */ diff --git a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx index da9a67a644..94d9a30e54 100644 --- a/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx +++ b/src/components/views/dialogs/security/AccessSecretStorageDialog.tsx @@ -13,7 +13,7 @@ import classNames from "classnames"; import React, { type ChangeEvent, type FormEvent } from "react"; import { type SecretStorage } from "matrix-js-sdk/src/matrix"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { _t } from "../../../../languageHandler"; import { EncryptionCard } from "../../settings/encryption/EncryptionCard"; import { EncryptionCardButtons } from "../../settings/encryption/EncryptionCardButtons"; diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index 7e6ea442e3..9eec103c27 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -180,7 +180,6 @@ export default class EventListSummary extends React.Component { private onEventSentinelUpdated = throttle( (): void => { - console.log("@@ SENTINEL UPDATED"); this.setState(this.generateState()); }, 500, diff --git a/src/components/views/elements/SettingsDropdown.tsx b/src/components/views/elements/SettingsDropdown.tsx new file mode 100644 index 0000000000..4aa746ea15 --- /dev/null +++ b/src/components/views/elements/SettingsDropdown.tsx @@ -0,0 +1,81 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import React, { type JSX, useCallback, useId, useState } from "react"; + +import SettingsStore from "../../../settings/SettingsStore"; +import { type SettingLevel } from "../../../settings/SettingLevel"; +import { SETTINGS, type StringSettingKey } from "../../../settings/Settings"; +import { useSettingValueAt } from "../../../hooks/useSettings.ts"; +import Dropdown, { type DropdownProps } from "./Dropdown.tsx"; +import { _t } from "../../../../packages/shared-components/src/utils/i18n.tsx"; + +interface Props { + settingKey: StringSettingKey; + level: SettingLevel; + roomId?: string; // for per-room settings + label?: string; + isExplicit?: boolean; + hideIfCannotSet?: boolean; + onChange?(option: string): void; +} + +const SettingsDropdown = ({ + settingKey, + roomId, + level, + label: specificLabel, + isExplicit, + hideIfCannotSet, + onChange, +}: Props): JSX.Element => { + const id = useId(); + const settingValue = useSettingValueAt(level, settingKey, roomId ?? null, isExplicit); + const [value, setValue] = useState(settingValue); + const setting = SETTINGS[settingKey]; + + const onOptionChange = useCallback( + (value: string): void => { + setValue(value); // local echo + SettingsStore.setValue(settingKey, roomId ?? null, level, value); + onChange?.(value); + }, + [settingKey, roomId, level, onChange], + ); + + const disabled = !SettingsStore.canSetValue(settingKey, roomId ?? null, level); + if (disabled && hideIfCannotSet) return <>; + if (!setting.options) { + console.error("SettingsDropdown used for a setting with no `options`"); + return <>; + } + + const label = specificLabel ?? SettingsStore.getDisplayName(settingKey, level)!; + const options = setting.options.map((option) => { + return
{_t(option.label)}
; + }) as DropdownProps["children"]; + + return ( +
+ + + {options} + +
+ ); +}; + +export default SettingsDropdown; diff --git a/src/components/views/emojipicker/EmojiPicker.tsx b/src/components/views/emojipicker/EmojiPicker.tsx index 13caa817ae..c225bfcfae 100644 --- a/src/components/views/emojipicker/EmojiPicker.tsx +++ b/src/components/views/emojipicker/EmojiPicker.tsx @@ -26,7 +26,7 @@ import { Type, } from "../../../accessibility/RovingTabIndex"; import { Key } from "../../../Keyboard"; -import { clamp } from "../../../shared-components/utils/numbers"; +import { clamp } from "../../../../packages/shared-components/src/utils/numbers"; import { type ButtonEvent } from "../elements/AccessibleButton"; export const CATEGORY_HEADER_HEIGHT = 20; diff --git a/src/components/views/messages/LegacyCallEvent.tsx b/src/components/views/messages/LegacyCallEvent.tsx index 6d144c5641..97a13db7e5 100644 --- a/src/components/views/messages/LegacyCallEvent.tsx +++ b/src/components/views/messages/LegacyCallEvent.tsx @@ -18,7 +18,7 @@ import { LegacyCallEventGrouperEvent } from "../../structures/LegacyCallEventGro import AccessibleButton from "../elements/AccessibleButton"; import InfoTooltip, { InfoTooltipKind } from "../elements/InfoTooltip"; import { formatPreciseDuration } from "../../../DateUtils"; -import { Clock } from "../../../shared-components/audio/Clock"; +import { Clock } from "../../../../packages/shared-components/src/audio/Clock"; const MAX_NON_NARROW_WIDTH = (450 / 70) * 100; diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 0b25a7647f..d29058d081 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -20,7 +20,7 @@ import { PlaybackManager } from "../../../audio/PlaybackManager"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import MediaProcessingError from "./shared/MediaProcessingError"; import { AudioPlayerViewModel } from "../../../viewmodels/audio/AudioPlayerViewModel"; -import { AudioPlayerView } from "../../../shared-components/audio/AudioPlayerView"; +import { AudioPlayerView } from "../../../../packages/shared-components/src/audio/AudioPlayerView"; interface IState { error?: boolean; diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index c113c36c41..023322476e 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -311,10 +311,7 @@ export class MImageBodyInner extends React.Component { // then we need to check if the image is animated by downloading it. if ( content.info?.["org.matrix.msc4230.is_animated"] === false || - !(await blobIsAnimated( - content.info?.mimetype, - await this.props.mediaEventHelper!.sourceBlob.value, - )) + (await blobIsAnimated(await this.props.mediaEventHelper!.sourceBlob.value)) === false ) { isAnimated = false; } diff --git a/src/components/views/right_panel/EmptyState.tsx b/src/components/views/right_panel/EmptyState.tsx index 547f365463..1915cac8ce 100644 --- a/src/components/views/right_panel/EmptyState.tsx +++ b/src/components/views/right_panel/EmptyState.tsx @@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details. import React, { type ComponentType } from "react"; import { Text } from "@vector-im/compound-web"; -import { Flex } from "../../../shared-components/utils/Flex"; +import { Flex } from "../../../../packages/shared-components/src/utils/Flex"; interface Props { Icon: ComponentType>; diff --git a/src/components/views/right_panel/RoomSummaryCardView.tsx b/src/components/views/right_panel/RoomSummaryCardView.tsx index 9aa4914475..6ebd219ada 100644 --- a/src/components/views/right_panel/RoomSummaryCardView.tsx +++ b/src/components/views/right_panel/RoomSummaryCardView.tsx @@ -46,9 +46,9 @@ import RoomAvatar from "../avatars/RoomAvatar.tsx"; import { E2EStatus } from "../../../utils/ShieldUtils.ts"; import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks.ts"; import RoomName from "../elements/RoomName.tsx"; -import { Flex } from "../../../shared-components/utils/Flex"; +import { Flex } from "../../../../packages/shared-components/src/utils/Flex"; import { Linkify, topicToHtml } from "../../../HtmlUtils.tsx"; -import { Box } from "../../../shared-components/utils/Box"; +import { Box } from "../../../../packages/shared-components/src/utils/Box"; import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx"; import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx"; diff --git a/src/components/views/right_panel/TimelineCard.tsx b/src/components/views/right_panel/TimelineCard.tsx index a7683ae28e..52366f099e 100644 --- a/src/components/views/right_panel/TimelineCard.tsx +++ b/src/components/views/right_panel/TimelineCard.tsx @@ -234,7 +234,6 @@ export default class TimelineCard extends React.Component { membersLoaded={true} editState={this.state.editState} eventId={this.state.initialEventId} - resizeNotifier={this.props.resizeNotifier} highlightedEventId={highlightedEventId} onScroll={this.onScroll} /> diff --git a/src/components/views/right_panel/user_info/UserInfoHeaderVerificationView.tsx b/src/components/views/right_panel/user_info/UserInfoHeaderVerificationView.tsx index 9d42f618dd..e5ef652379 100644 --- a/src/components/views/right_panel/user_info/UserInfoHeaderVerificationView.tsx +++ b/src/components/views/right_panel/user_info/UserInfoHeaderVerificationView.tsx @@ -12,7 +12,7 @@ import { VerifiedIcon } from "@vector-im/compound-design-tokens/assets/web/icons import { useUserInfoVerificationViewModel } from "../../../viewmodels/right_panel/user_info/UserInfoHeaderVerificationViewModel"; import { type IDevice } from "../UserInfo"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { _t } from "../../../../languageHandler"; export const UserInfoHeaderVerificationView: React.FC<{ diff --git a/src/components/views/right_panel/user_info/UserInfoHeaderView.tsx b/src/components/views/right_panel/user_info/UserInfoHeaderView.tsx index 71c38f3baa..1b88d7814e 100644 --- a/src/components/views/right_panel/user_info/UserInfoHeaderView.tsx +++ b/src/components/views/right_panel/user_info/UserInfoHeaderView.tsx @@ -12,7 +12,7 @@ import { Heading, Tooltip, Text } from "@vector-im/compound-web"; import { useUserfoHeaderViewModel } from "../../../viewmodels/right_panel/user_info/UserInfoHeaderViewModel"; import MemberAvatar from "../../avatars/MemberAvatar"; import { Container, type Member, type IDevice } from "../UserInfo"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import PresenceLabel from "../../rooms/PresenceLabel"; import CopyableText from "../../elements/CopyableText"; import { UserInfoHeaderVerificationView } from "./UserInfoHeaderVerificationView"; diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index ac6328c3bf..c40f756651 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -22,16 +22,16 @@ import ResizeHandle from "../elements/ResizeHandle"; import Resizer, { type IConfig } from "../../../resizer/resizer"; import PercentageDistributor from "../../../resizer/distributors/percentage"; import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; -import { clamp, percentageOf, percentageWithin } from "../../../shared-components/utils/numbers"; +import { clamp, percentageOf, percentageWithin } from "../../../../packages/shared-components/src/utils/numbers"; import UIStore from "../../../stores/UIStore"; import { type ActionPayload } from "../../../dispatcher/payloads"; import Spinner from "../elements/Spinner"; import SdkConfig from "../../../SdkConfig"; +import { SDKContext } from "../../../contexts/SDKContext"; interface IProps { userId: string; room: Room; - resizeNotifier: ResizeNotifier; showApps?: boolean; // Should apps be rendered maxHeight: number; role?: AriaRole; @@ -57,8 +57,11 @@ export default class AppsDrawer extends React.Component { showApps: true, }; - public constructor(props: IProps) { - super(props); + public static contextType = SDKContext; + declare public context: React.ContextType; + + public constructor(props: IProps, context: React.ContextType) { + super(props, context); this.state = { apps: this.getApps(), @@ -73,7 +76,7 @@ export default class AppsDrawer extends React.Component { public componentDidMount(): void { this.unmounted = false; - this.props.resizeNotifier.on("isResizing", this.onIsResizing); + this.context.resizeNotifier.on("isResizing", this.onIsResizing); ScalarMessaging.startListening(); WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps); @@ -88,7 +91,7 @@ export default class AppsDrawer extends React.Component { if (this.resizeContainer) { this.resizer.detach(); } - this.props.resizeNotifier.off("isResizing", this.onIsResizing); + this.context.resizeNotifier.off("isResizing", this.onIsResizing); } private onIsResizing = (resizing: boolean): void => { @@ -281,7 +284,7 @@ export default class AppsDrawer extends React.Component { className="mx_AppsDrawer_resizer" handleWrapperClass="mx_AppsDrawer_resizer_container" handleClass="mx_AppsDrawer_resizer_container_handle" - resizeNotifier={this.props.resizeNotifier} + resizeNotifier={this.context.resizeNotifier} > {appContainers} diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 9e34a6f40d..f547c29d2e 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -13,7 +13,6 @@ import AppsDrawer from "./AppsDrawer"; import SettingsStore from "../../../settings/SettingsStore"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import { UIFeature } from "../../../settings/UIFeature"; -import type ResizeNotifier from "../../../utils/ResizeNotifier"; import LegacyCallViewForRoom from "../voip/LegacyCallViewForRoom"; import { objectHasDiff } from "../../../utils/objects"; @@ -22,7 +21,6 @@ interface IProps { room: Room; userId: string; showApps: boolean; // Render apps - resizeNotifier: ResizeNotifier; children?: ReactNode; } @@ -36,23 +34,12 @@ export default class AuxPanel extends React.Component { } public render(): React.ReactNode { - const callView = ( - - ); + const callView = ; let appsDrawer; if (SettingsStore.getValue(UIFeature.Widgets)) { appsDrawer = ( - + ); } diff --git a/src/components/views/rooms/MemberList/MemberListHeaderView.tsx b/src/components/views/rooms/MemberList/MemberListHeaderView.tsx index 5d096c7228..e6e612b598 100644 --- a/src/components/views/rooms/MemberList/MemberListHeaderView.tsx +++ b/src/components/views/rooms/MemberList/MemberListHeaderView.tsx @@ -10,7 +10,7 @@ import React from "react"; import InviteIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add"; import { UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { type MemberListViewState } from "../../../viewmodels/memberlist/MemberListViewModel"; import { _t } from "../../../../languageHandler"; diff --git a/src/components/views/rooms/MemberList/MemberListView.tsx b/src/components/views/rooms/MemberList/MemberListView.tsx index 198c1920ca..1193e245c5 100644 --- a/src/components/views/rooms/MemberList/MemberListView.tsx +++ b/src/components/views/rooms/MemberList/MemberListView.tsx @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import { Form } from "@vector-im/compound-web"; import React, { type JSX, useCallback } from "react"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { type MemberWithSeparator, SEPARATOR, diff --git a/src/components/views/rooms/MemberList/tiles/common/InvitedIconView.tsx b/src/components/views/rooms/MemberList/tiles/common/InvitedIconView.tsx index f8b6788e2b..a22ac031c2 100644 --- a/src/components/views/rooms/MemberList/tiles/common/InvitedIconView.tsx +++ b/src/components/views/rooms/MemberList/tiles/common/InvitedIconView.tsx @@ -9,7 +9,7 @@ import React, { type JSX } from "react"; import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid"; import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add-solid"; -import { Flex } from "../../../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../../../packages/shared-components/src/utils/Flex"; interface Props { isThreePid: boolean; diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx index a6c6c5c93a..7195966131 100644 --- a/src/components/views/rooms/NotificationDecoration.tsx +++ b/src/components/views/rooms/NotificationDecoration.tsx @@ -13,7 +13,7 @@ import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/vi import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid"; import { UnreadCounter, Unread } from "@vector-im/compound-web"; -import { Flex } from "../../../shared-components/utils/Flex"; +import { Flex } from "../../../../packages/shared-components/src/utils/Flex"; import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState"; import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; diff --git a/src/components/views/rooms/PinnedMessageBanner.tsx b/src/components/views/rooms/PinnedMessageBanner.tsx index 200b35a73d..552aa2a4b2 100644 --- a/src/components/views/rooms/PinnedMessageBanner.tsx +++ b/src/components/views/rooms/PinnedMessageBanner.tsx @@ -6,7 +6,7 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, useEffect, useId, useRef, useState } from "react"; +import React, { type JSX, useContext, useEffect, useId, useRef, useState } from "react"; import PinIcon from "@vector-im/compound-design-tokens/assets/web/icons/pin-solid"; import { Button } from "@vector-im/compound-web"; import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix"; @@ -25,7 +25,7 @@ import { Action } from "../../../dispatcher/actions"; import MessageEvent from "../messages/MessageEvent"; import PosthogTrackers from "../../../PosthogTrackers.ts"; import { EventPreview } from "./EventPreview.tsx"; -import type ResizeNotifier from "../../../utils/ResizeNotifier"; +import { SDKContext } from "../../../contexts/SDKContext.ts"; /** * The props for the {@link PinnedMessageBanner} component. @@ -39,20 +39,12 @@ interface PinnedMessageBannerProps { * The room where the banner is displayed */ room: Room; - /** - * The resize notifier to notify the timeline to resize itself when the banner is displayed or hidden. - */ - resizeNotifier: ResizeNotifier; } /** * A banner that displays the pinned messages in a room. */ -export function PinnedMessageBanner({ - room, - permalinkCreator, - resizeNotifier, -}: PinnedMessageBannerProps): JSX.Element | null { +export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBannerProps): JSX.Element | null { const pinnedEventIds = usePinnedEvents(room); const pinnedEvents = useSortedFetchedPinnedEvents(room, pinnedEventIds); const eventCount = pinnedEvents.length; @@ -67,7 +59,7 @@ export function PinnedMessageBanner({ const isLastMessage = currentEventIndex === eventCount - 1; const pinnedEvent = pinnedEvents[currentEventIndex]; - useNotifyTimeline(pinnedEvent, resizeNotifier); + useNotifyTimeline(pinnedEvent); const id = useId(); @@ -152,9 +144,10 @@ export function PinnedMessageBanner({ /** * When the banner is displayed or hidden, we want to notify the timeline to resize itself. * @param pinnedEvent - * @param resizeNotifier */ -function useNotifyTimeline(pinnedEvent: MatrixEvent | null, resizeNotifier: ResizeNotifier): void { +function useNotifyTimeline(pinnedEvent: MatrixEvent | null): void { + const resizeNotifier = useContext(SDKContext).resizeNotifier; + const previousEvent = useRef(null); useEffect(() => { // If we switch from a pinned message to no pinned message or the opposite, we want to resize the timeline diff --git a/src/components/views/rooms/RoomHeader/RoomHeader.tsx b/src/components/views/rooms/RoomHeader/RoomHeader.tsx index 7e88a31eac..8c162216ba 100644 --- a/src/components/views/rooms/RoomHeader/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader/RoomHeader.tsx @@ -25,8 +25,8 @@ import { RightPanelPhases } from "../../../../stores/right-panel/RightPanelStore import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx"; import { useRoomMemberCount, useRoomMembers } from "../../../../hooks/useRoomMembers.ts"; import { _t } from "../../../../languageHandler.tsx"; -import { Flex } from "../../../../shared-components/utils/Flex"; -import { Box } from "../../../../shared-components/utils/Box"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; +import { Box } from "../../../../../packages/shared-components/src/utils/Box"; import { getPlatformCallTypeProps, useRoomCall } from "../../../../hooks/room/useRoomCall.tsx"; import { useRoomThreadNotifications } from "../../../../hooks/room/useRoomThreadNotifications.ts"; import { useGlobalNotificationState } from "../../../../hooks/useGlobalNotificationState.ts"; @@ -54,13 +54,14 @@ import { RoomSettingsTab } from "../../dialogs/RoomSettingsDialog.tsx"; import { useScopedRoomContext } from "../../../../contexts/ScopedRoomContext.tsx"; import { ToggleableIcon } from "./toggle/ToggleableIcon.tsx"; import { CurrentRightPanelPhaseContextProvider } from "../../../../contexts/CurrentRightPanelPhaseContext.tsx"; +import { type LocalRoom } from "../../../../models/LocalRoom.ts"; export default function RoomHeader({ room, additionalButtons, oobData, }: { - room: Room; + room: Room | LocalRoom; additionalButtons?: ViewRoomOpts["buttons"]; oobData?: IOOBData; }): JSX.Element { @@ -70,7 +71,7 @@ export default function RoomHeader({ const joinRule = useRoomState(room, (state) => state.getJoinRule()); const members = useRoomMembers(room, 2500); - const memberCount = useRoomMemberCount(room, { throttleWait: 2500 }); + const memberCount = useRoomMemberCount(room, { throttleWait: 2500, includeInvited: true }); const { voiceCallDisabledReason, diff --git a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx index 66d881cfdb..27fce169af 100644 --- a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx +++ b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx @@ -11,7 +11,7 @@ import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat"; import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room"; import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { _t } from "../../../../languageHandler"; import { FilterKey } from "../../../../stores/room-list-v3/skip-list/filters"; import { type PrimaryFilter } from "../../../viewmodels/roomlist/useFilteredRooms"; diff --git a/src/components/views/rooms/RoomListPanel/RoomList.tsx b/src/components/views/rooms/RoomListPanel/RoomList.tsx index cf72ef4392..4aae621d96 100644 --- a/src/components/views/rooms/RoomListPanel/RoomList.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomList.tsx @@ -25,7 +25,18 @@ interface RoomListProps { */ vm: RoomListViewState; } - +/** + * Height of a single room list item + */ +const ROOM_LIST_ITEM_HEIGHT = 48; +/** + * Amount to extend the top and bottom of the viewport by. + * From manual testing and user feedback 25 items is reported to be enough to avoid blank space when using the mouse wheel, + * and the trackpad scrolling at a slow to moderate speed where you can still see/read the content. + * Using the trackpad to sling through a large percentage of the list quickly will still show blank space. + * We would likely need to simplify the item content to improve this case. + */ +const EXTENDED_VIEWPORT_HEIGHT = 25 * ROOM_LIST_ITEM_HEIGHT; /** * A virtualized list of rooms. */ @@ -112,13 +123,17 @@ export function RoomList({ vm: { roomsResult, activeIndex } }: RoomListProps): J data-testid="room-list" role="listbox" aria-label={_t("room_list|list_title")} - fixedItemHeight={48} + fixedItemHeight={ROOM_LIST_ITEM_HEIGHT} items={roomsResult.rooms} getItemComponent={getItemComponent} getItemKey={getItemKey} isItemFocusable={() => true} onKeyDown={keyDownCallback} isScrolling={setIsScrolling} + increaseViewportBy={{ + bottom: EXTENDED_VIEWPORT_HEIGHT, + top: EXTENDED_VIEWPORT_HEIGHT, + }} /> ); } diff --git a/src/components/views/rooms/RoomListPanel/RoomListHeaderView.tsx b/src/components/views/rooms/RoomListPanel/RoomListHeaderView.tsx index 02e565b6f9..0a12c91cda 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListHeaderView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListHeaderView.tsx @@ -17,7 +17,7 @@ import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/vi import ChatIcon from "@vector-im/compound-design-tokens/assets/web/icons/chat"; import { _t } from "../../../../languageHandler"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { type RoomListHeaderViewState, useRoomListHeaderViewModel, diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx index 60643a4441..4a37d90d9a 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx @@ -21,7 +21,7 @@ import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check" import { type Room } from "matrix-js-sdk/src/matrix"; import { _t } from "../../../../languageHandler"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { type RoomListItemMenuViewState, useRoomListItemMenuViewModel, diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index 500fbe953f..a18cd337be 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -10,7 +10,7 @@ import { type Room } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { RoomListItemMenuView } from "./RoomListItemMenuView"; import { NotificationDecoration } from "../NotificationDecoration"; import { RoomAvatarView } from "../../avatars/RoomAvatarView"; diff --git a/src/components/views/rooms/RoomListPanel/RoomListPanel.tsx b/src/components/views/rooms/RoomListPanel/RoomListPanel.tsx index c9c810a28b..f667c5af16 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListPanel.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListPanel.tsx @@ -12,7 +12,7 @@ import { UIComponent } from "../../../../settings/UIFeature"; import { RoomListSearch } from "./RoomListSearch"; import { RoomListHeaderView } from "./RoomListHeaderView"; import { RoomListView } from "./RoomListView"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { _t } from "../../../../languageHandler"; import { getKeyBindingsManager } from "../../../../KeyBindingsManager"; import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts"; diff --git a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx index 86dea71d02..c786078d01 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters.tsx @@ -10,7 +10,7 @@ import { ChatFilter, IconButton } from "@vector-im/compound-web"; import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down"; import type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { _t } from "../../../../languageHandler"; interface RoomListPrimaryFiltersProps { diff --git a/src/components/views/rooms/RoomListPanel/RoomListSearch.tsx b/src/components/views/rooms/RoomListPanel/RoomListSearch.tsx index 39ac3209c0..fba28b6ee2 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListSearch.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListSearch.tsx @@ -20,7 +20,7 @@ import { MetaSpace } from "../../../../stores/spaces"; import { Action } from "../../../../dispatcher/actions"; import PosthogTrackers from "../../../../PosthogTrackers"; import defaultDispatcher from "../../../../dispatcher/dispatcher"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; import { useTypedEventEmitterState } from "../../../../hooks/useEventEmitter"; import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../../LegacyCallHandler"; diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index 536669dca8..d919e0fb31 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -19,7 +19,7 @@ import { isValid3pidInvite } from "../../../RoomInvite"; import { Action } from "../../../dispatcher/actions"; import ErrorDialog from "../dialogs/ErrorDialog"; import BaseCard from "../right_panel/BaseCard"; -import { Flex } from "../../../shared-components/utils/Flex"; +import { Flex } from "../../../../packages/shared-components/src/utils/Flex"; interface IProps { event: MatrixEvent; diff --git a/src/components/views/settings/UserProfileSettings.tsx b/src/components/views/settings/UserProfileSettings.tsx index 86e77b6b47..d58b050837 100644 --- a/src/components/views/settings/UserProfileSettings.tsx +++ b/src/components/views/settings/UserProfileSettings.tsx @@ -26,7 +26,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import LogoutDialog, { shouldShowLogoutDialog } from "../dialogs/LogoutDialog"; import Modal from "../../../Modal"; import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { Flex } from "../../../shared-components/utils/Flex"; +import { Flex } from "../../../../packages/shared-components/src/utils/Flex"; const SpinnerToast: React.FC<{ children?: ReactNode }> = ({ children }) => ( <> diff --git a/src/components/views/settings/encryption/EncryptionCardEmphasisedContent.tsx b/src/components/views/settings/encryption/EncryptionCardEmphasisedContent.tsx index 3e343b638c..4ad472ee82 100644 --- a/src/components/views/settings/encryption/EncryptionCardEmphasisedContent.tsx +++ b/src/components/views/settings/encryption/EncryptionCardEmphasisedContent.tsx @@ -7,7 +7,7 @@ import React, { type JSX, type PropsWithChildren } from "react"; -import { Flex } from "../../../../shared-components/utils/Flex"; +import { Flex } from "../../../../../packages/shared-components/src/utils/Flex"; /** * A component for emphasised text within an {@link EncryptionCard} diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index b1cae7d634..d373aee60e 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -34,6 +34,7 @@ import * as TimezoneHandler from "../../../../../TimezoneHandler"; import { type BooleanSettingKey } from "../../../../../settings/Settings.tsx"; import { MediaPreviewAccountSettings } from "./MediaPreviewAccountSettings.tsx"; import { InviteRulesAccountSetting } from "./InviteRulesAccountSettings.tsx"; +import SettingsDropdown from "../../../elements/SettingsDropdown.tsx"; interface IProps { closeSettingsFn(success: boolean): void; @@ -248,11 +249,12 @@ export default class PreferencesUserSettingsTab extends React.Component { return
{tz}
; }); + // Always prepend the default option timezones.unshift(
{browserTimezoneLabel}
); return ( @@ -264,6 +266,17 @@ export default class PreferencesUserSettingsTab extends React.Component + {SettingsStore.canSetValue("Electron.autoLaunch", null, SettingLevel.PLATFORM) && ( + + + + )} + {!newRoomListEnabled && this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} {/* The settings is on device level where the other room list settings are on account level */} @@ -356,7 +369,7 @@ export default class PreferencesUserSettingsTab extends React.Component - { - public constructor(props: IProps) { - super(props); + public static contextType = SDKContext; + declare public context: React.ContextType; + + public constructor(props: IProps, context: React.ContextType) { + super(props, context); const call = this.getCall(); this.state = { call, @@ -73,15 +74,15 @@ export default class LegacyCallViewForRoom extends React.Component { - this.props.resizeNotifier.startResizing(); + this.context.resizeNotifier.startResizing(); }; private onResize = (): void => { - this.props.resizeNotifier.notifyTimelineHeightChanged(); + this.context.resizeNotifier.notifyTimelineHeightChanged(); }; private onResizeStop = (): void => { - this.props.resizeNotifier.stopResizing(); + this.context.resizeNotifier.stopResizing(); }; private setSidebarShown = (sidebarShown: boolean): void => { diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index e6616a7e21..b7af7124fb 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -331,12 +331,6 @@ export enum Action { */ OverwriteLogin = "overwrite_login", - /** - * Fired when the PlatformPeg gets a new platform set upon it, should only happen once per app load lifecycle. - * Fires with the PlatformSetPayload. - */ - PlatformSet = "platform_set", - /** * Fired when we want to view a thread, either a new one or an existing one */ diff --git a/src/dispatcher/payloads/PlatformSetPayload.ts b/src/dispatcher/payloads/PlatformSetPayload.ts deleted file mode 100644 index 6d12978f8f..0000000000 --- a/src/dispatcher/payloads/PlatformSetPayload.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2022 The Matrix.org Foundation C.I.C. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import { type Action } from "../actions"; -import { type ActionPayload } from "../payloads"; -import type BasePlatform from "../../BasePlatform"; - -export interface PlatformSetPayload extends ActionPayload { - action: Action.PlatformSet; - platform: BasePlatform; -} diff --git a/src/events/EventTileFactory.tsx b/src/events/EventTileFactory.tsx index 57cafcbab1..d732a38e49 100644 --- a/src/events/EventTileFactory.tsx +++ b/src/events/EventTileFactory.tsx @@ -43,7 +43,7 @@ import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline"; import { type IBodyProps } from "../components/views/messages/IBodyProps"; import ModuleApi from "../modules/Api"; import { TextualEventViewModel } from "../viewmodels/event-tiles/TextualEventViewModel"; -import { TextualEventView } from "../shared-components/event-tiles/TextualEventView"; +import { TextualEventView } from "../../packages/shared-components/src/event-tiles/TextualEventView"; import { ElementCallEventType } from "../call-types"; // Subset of EventTile's IProps plus some mixins diff --git a/src/hooks/room/useRoomCall.tsx b/src/hooks/room/useRoomCall.tsx index 39e7725659..92f68d02f8 100644 --- a/src/hooks/room/useRoomCall.tsx +++ b/src/hooks/room/useRoomCall.tsx @@ -36,6 +36,7 @@ import { isVideoRoom } from "../../utils/video-rooms"; import { UIFeature } from "../../settings/UIFeature"; import { type InteractionName } from "../../PosthogTrackers"; import { ElementCallMemberEventType } from "../../call-types"; +import { LocalRoom, LocalRoomState } from "../../models/LocalRoom"; export enum PlatformCallType { ElementCall, @@ -83,7 +84,7 @@ const enum State { * @returns the call button attributes for the given room */ export const useRoomCall = ( - room: Room, + room: Room | LocalRoom, ): { voiceCallDisabledReason: string | null; voiceCallClick(evt: React.MouseEvent | undefined, selectedType: PlatformCallType): void; @@ -274,11 +275,16 @@ export const useRoomCall = ( }); }, [isViewingCall, room.roomId]); + const roomDoesNotExist = room instanceof LocalRoom && room.state !== LocalRoomState.CREATED; + // We hide the voice call button if it'd have the same effect as the video call button let hideVoiceCallButton = isManagedHybridWidgetEnabled(room) || !callOptions.includes(PlatformCallType.LegacyCall); let hideVideoCallButton = false; - // We hide both buttons if they require widgets but widgets are disabled, or if the Voip feature is disabled. - if ((memberCount > 2 && !widgetsFeatureEnabled) || !voipFeatureEnabled) { + // We hide both buttons if: + // - they require widgets but widgets are disabled + // - if the Voip feature is disabled. + // - The room is not created yet (rendering "send first message view") + if ((memberCount > 2 && !widgetsFeatureEnabled) || !voipFeatureEnabled || roomDoesNotExist) { hideVoiceCallButton = true; hideVideoCallButton = true; } diff --git a/src/hooks/useRoomMembers.ts b/src/hooks/useRoomMembers.ts index 14448ba307..44007c82e1 100644 --- a/src/hooks/useRoomMembers.ts +++ b/src/hooks/useRoomMembers.ts @@ -38,6 +38,11 @@ type RoomMemberCountOpts = { * Wait time between room member count update */ throttleWait?: number; + /** + * Whether to include invited members in the count + * @default false + */ + includeInvited?: boolean; }; /** @@ -48,19 +53,21 @@ type RoomMemberCountOpts = { */ export const useRoomMemberCount = ( room: Room, - { throttleWait }: RoomMemberCountOpts = { throttleWait: 250 }, + { throttleWait, includeInvited }: RoomMemberCountOpts = { throttleWait: 250, includeInvited: false }, ): number => { - const [count, setCount] = useState(room.getJoinedMemberCount()); + const [count, setCount] = useState( + includeInvited ? room.getInvitedAndJoinedMemberCount() : room.getJoinedMemberCount(), + ); const throttledUpdate = useMemo( () => throttle( () => { - setCount(room.getJoinedMemberCount()); + setCount(includeInvited ? room.getInvitedAndJoinedMemberCount() : room.getJoinedMemberCount()); }, throttleWait, { leading: true, trailing: true }, ), - [room, throttleWait], + [room, throttleWait, includeInvited], ); useTypedEventEmitter(room.currentState, RoomStateEvent.Members, throttledUpdate); diff --git a/src/hooks/useUserTimezone.ts b/src/hooks/useUserTimezone.ts index 0e5f046d74..04de7c60e1 100644 --- a/src/hooks/useUserTimezone.ts +++ b/src/hooks/useUserTimezone.ts @@ -5,11 +5,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ import { useEffect, useState } from "react"; -import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; +import { + type MatrixClient, + MatrixError, + ProfileKeyMSC4175Timezone, + ProfileKeyTimezone, +} from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; import { getTwelveHourOptions } from "../DateUtils.ts"; import { useSettingValue } from "./useSettings.ts"; +const log = logger.getChild("useUserTimezone"); + /** * Fetch a user's delclared timezone through their profile, and return * a friendly string of the current time for that user. This will keep @@ -52,11 +60,13 @@ export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone: return; } (async () => { - console.log("Trying to fetch TZ"); + log.debug("Trying to fetch TZ for", userId); try { - const tz = await cli.getExtendedProfileProperty(userId, "us.cloke.msc4175.tz"); + const userProfile = await cli.getExtendedProfile(userId); + // In a future spec release, remove support for legacy key. + const tz = userProfile[ProfileKeyTimezone] ?? userProfile[ProfileKeyMSC4175Timezone]; if (typeof tz !== "string") { - // Err, definitely not a tz. + // Definitely not a tz. throw Error("Timezone value was not a string"); } // This will validate the timezone for us. @@ -85,7 +95,7 @@ export const useUserTimezone = (cli: MatrixClient, userId: string): { timezone: // No timezone set, ignore. return; } - console.error("Could not render current timezone for user", ex); + log.warn(`Could not render current timezone for ${userId}`, ex); } })(); }, [supported, userId, cli, showTwelveHour]); diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 6e6b8e8114..56607b9186 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -3052,7 +3052,6 @@ "spaces_explainer": "Prostory jsou způsoby, jak seskupit místnosti a lidi. Kromě prostor, ve kterých se nacházíte, můžete použít i některé předem vytvořené.", "title": "Postranní panel" }, - "start_automatically": "Zahájit automaticky po přihlášení do systému", "tac_only_notifications": "Zobrazení oznámení pouze v centru aktivity vlákna", "use_12_hour_format": "Zobrazovat čas v 12hodinovém formátu (např. 2:30 odp.)", "use_command_enter_send_message": "K odeslání zprávy použijte Command + Enter", diff --git a/src/i18n/strings/cy.json b/src/i18n/strings/cy.json index 4d00f3158a..ed6652f306 100644 --- a/src/i18n/strings/cy.json +++ b/src/i18n/strings/cy.json @@ -3058,7 +3058,6 @@ "spaces_explainer": "Mae gofodau yn ffyrdd o grwpio ystafelloedd a phobl. Ochr yn ochr â'r lleoedd rydych chi ynddynt, gallwch chi ddefnyddio rhai sydd wedi'u hadeiladu ymlaen llaw hefyd.", "title": "Bar Ochr" }, - "start_automatically": "Dechrau'n awtomatig ar ôl mewngofnodi i'r system", "tac_only_notifications": "Dim ond dangos hysbysiadau yng nghanolfan gweithgaredd yr edefyn", "use_12_hour_format": "Dangos stampiau amser mewn fformat 12 awr (e.e. 2:30pm)", "use_command_enter_send_message": "Defnyddiwch Command + Enter i anfon neges", diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 2da4dd109d..db60962e58 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -645,7 +645,7 @@ "no_perms_notice": "Du darfst in diesem Chat nichts schreiben", "placeholder": "Unverschlüsselte Nachricht senden...", "placeholder_encrypted": "Nachricht senden …", - "placeholder_reply": "Eine unverschlüsselte Antwort senden…", + "placeholder_reply": "Unverschlüsselte Antwort senden…", "placeholder_reply_encrypted": "Antwort senden…", "placeholder_thread": "Antwort auf unverschlüsselten Thread…", "placeholder_thread_encrypted": "Auf Thread antworten…", @@ -2511,10 +2511,10 @@ }, "seshat": { "error_initialising": "Initialisierung der Suche fehlgeschlagen, für weitere Informationen öffne deine Einstellungen", - "reset_button": "Ereignisspeicher zurück setzen", + "reset_button": "Ereignisspeicher zurücksetzen", "reset_description": "Es ist wahrscheinlich, dass du den Ereignis-Indexspeicher nicht zurück setzen möchtest", "reset_explainer": "Falls du es wirklich willst: Es werden keine Nachrichten gelöscht. Außerdem wird die Suche, während der Index erstellt wird, etwas langsamer sein", - "reset_title": "Ereignisspeicher zurück setzen?", + "reset_title": "Ereignisspeicher zurücksetzen?", "warning_kind_files": "Diese Version von %(brand)s kann nicht alle verschlüsselten Dateien anzuzeigen", "warning_kind_files_app": "Nutze die Desktop-App um alle verschlüsselten Dateien zu sehen", "warning_kind_search": "Diese Version von %(brand)s kann verschlüsselte Nachrichten nicht durchsuchen", @@ -2663,6 +2663,7 @@ "allow_spellcheck": "Rechtschreibprüfung zulassen", "application_language": "Anwendungssprache", "application_language_reload_hint": "Nach Änderung der Sprache wird die App neu gestartet", + "avatar_open_menu": "Avatar-Menü öffnen", "avatar_remove_progress": "Bild wird entfernt...", "avatar_save_progress": "Bild wird hochgeladen...", "avatar_upload_error_text": "Das Dateiformat wird nicht unterstützt oder das Bild ist größer als %(size)s.", @@ -2860,6 +2861,7 @@ "rule_tombstone": "Wenn die Chat-Version aktualisiert wird", "show_message_desktop_notification": "Nachrichteninhalt in der Desktopbenachrichtigung anzeigen", "sounds_release_announcement": { + "description": "Deine Benachrichtigungs- und Anruftöne wurden verbessert – sie sind jetzt klarer, schneller und weniger störend.", "title": "Wir haben deine Sounds aktualisiert" }, "voip": "Audio- und Videoanrufe" @@ -2888,6 +2890,7 @@ "room_list_heading": "Chatliste", "show_avatars_pills": "Avatare in Nutzer-, Chat- und Ereigniserwähnungen anzeigen", "show_polls_button": "Zeige Pol button", + "startup_window_behaviour_label": "Start- und Fensterverhalten", "surround_text": "Sonderzeichen automatisch vor und hinter Textauswahl setzen", "time_heading": "Zeitanzeige", "user_timezone": "Zeitzone festlegen" @@ -3061,7 +3064,12 @@ "spaces_explainer": "Spaces sind eine Möglichkeit, Gruppen und Personen zu bündeln. Neben den Spaces, in denen du dich befindest, kannst du auch einige vorgefertigte verwenden.", "title": "Seitenleiste" }, - "start_automatically": "Nach Systemstart automatisch starten", + "start_automatically": { + "disabled": "Nein", + "enabled": "Ja", + "label": "Öffne %(brand)s, wenn du dich an deinem Computer anmeldest", + "minimised": "Minimiert" + }, "tac_only_notifications": "Benachrichtigungen nur im Thread Aktivitätszentrum anzeigen", "use_12_hour_format": "Uhrzeiten im 12-Stundenformat (z. B. 2:30 p. m.)", "use_command_enter_send_message": "Benutze Betriebssystemtaste + Eingabe um eine Nachricht zu senden", diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json index 1c8e2416f9..9548bf567a 100644 --- a/src/i18n/strings/el.json +++ b/src/i18n/strings/el.json @@ -2352,7 +2352,6 @@ "spaces_explainer": "Οι χώροι είναι τρόποι ομαδοποίησης αιθουσών και ανθρώπων. Παράλληλα με τους χώρους στους οποίους βρίσκεστε, μπορείτε να χρησιμοποιήσετε και κάποιους προκατασκευασμένους χώρους.", "title": "Πλαϊνή μπάρα" }, - "start_automatically": "Αυτόματη έναρξη μετά τη σύνδεση", "use_12_hour_format": "Εμφάνιση χρονικών σημάνσεων σε 12ωρη μορφή ώρας (π.χ. 2:30 μ.μ.)", "use_command_enter_send_message": "Χρησιμοποιήστε Command + Enter για να στείλετε ένα μήνυμα", "use_command_f_search": "Χρησιμοποιήστε τα πλήκτρα Command + F για αναζήτηση στο χρονολόγιο", diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b4405e2190..2da705e932 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2891,6 +2891,7 @@ "room_list_heading": "Room list", "show_avatars_pills": "Show avatars in user, room and event mentions", "show_polls_button": "Show polls button", + "startup_window_behaviour_label": "Start-up and window behaviour", "surround_text": "Surround selected text when typing special characters", "time_heading": "Displaying time", "user_timezone": "Set timezone" @@ -3064,7 +3065,12 @@ "spaces_explainer": "Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.", "title": "Sidebar" }, - "start_automatically": "Start automatically after system login", + "start_automatically": { + "disabled": "No", + "enabled": "Yes", + "label": "Open %(brand)s when you log in to your computer", + "minimised": "Minimised" + }, "tac_only_notifications": "Only show notifications in the thread activity centre", "use_12_hour_format": "Show timestamps in 12 hour format (e.g. 2:30pm)", "use_command_enter_send_message": "Use Command + Enter to send a message", diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json index 16006e0da3..34d43260cd 100644 --- a/src/i18n/strings/eo.json +++ b/src/i18n/strings/eo.json @@ -1835,7 +1835,6 @@ "metaspaces_home_all_rooms": "Montri ĉiujn ĉambrojn", "title": "Flanka kolumno" }, - "start_automatically": "Memfare ruli post operaciuma saluto", "use_12_hour_format": "Montri tempindikojn en 12-hora formo (ekz. 2:30 post.)", "use_command_enter_send_message": "Sendu mesaĝon per komanda klavo + eniga klavo", "use_control_enter_send_message": "Sendu mesaĝon per stirklavo (Ctrl) + eniga klavo", diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 73e0388230..3c513fda8e 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -2340,7 +2340,6 @@ "metaspaces_subsection": "Qué espacios mostrar", "title": "Barra lateral" }, - "start_automatically": "Abrir automáticamente después de iniciar sesión en el sistema", "use_12_hour_format": "Mostrar las horas con el modelo de 12 horas (ej.: 2:30pm)", "use_command_enter_send_message": "Usa Comando + Intro para enviar un mensje", "use_command_f_search": "Usa Control + F para buscar", diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 936209e140..df8b95d274 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2664,6 +2664,7 @@ "allow_spellcheck": "Kasuta õigekontrolli", "application_language": "Rakenduse keel", "application_language_reload_hint": "Teise keele valimisel rakendus käivitub uuesti", + "avatar_open_menu": "Ava tunnuspiltide menüü", "avatar_remove_progress": "Eemaldame pilti...", "avatar_save_progress": "Laadime pilti üles...", "avatar_upload_error_text": "Failivormingu tugi puudub või fail on suurem, kui %(size)s.", @@ -3063,7 +3064,9 @@ "spaces_explainer": "Kogukonnad on võimalus jututubade ja inimeste ühendamiseks. Lisaks nendele, mille liige sa juba olev, võid kasutada süsteemi poolt loodud kogukondi.", "title": "Külgpaan" }, - "start_automatically": "Käivita Element automaatselt peale arvutisse sisselogimist", + "start_automatically": { + "minimised": "Minimeeritud" + }, "tac_only_notifications": "Näita teavitusi vaid jutulõngade ülevaates", "use_12_hour_format": "Näita ajatempleid 12-tunnises vormingus (näiteks 2:30pl)", "use_command_enter_send_message": "Sõnumi saatmiseks vajuta Command + Enter klahve", diff --git a/src/i18n/strings/fa.json b/src/i18n/strings/fa.json index dbfec0590b..3738aa3b40 100644 --- a/src/i18n/strings/fa.json +++ b/src/i18n/strings/fa.json @@ -1597,7 +1597,6 @@ "metaspaces_home_description": "خانه برای داشتن یک نمای کلی از همه چیز مفید است.", "title": "نوارکناری" }, - "start_automatically": "پس از ورود به سیستم به صورت خودکار آغاز کن", "use_12_hour_format": "زمان را با فرمت ۱۲ ساعته نشان بده (مثلا ۲:۳۰ بعدازظهر)", "use_command_enter_send_message": "استفاده از Command + Enter برای ارسال پیام", "use_control_enter_send_message": "استفاده از Ctrl + Enter برای ارسال پیام", diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index 51bb9c7c73..e62cd57240 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -2544,7 +2544,6 @@ "spaces_explainer": "Avaruudet ovat tapa ryhmitellä huoneita ja ihmisiä. Voit käyttää nykyisten avaruuksiesi lisäksi esivalmisteltuja avaruuksia.", "title": "Sivupalkki" }, - "start_automatically": "Käynnistä automaattisesti käyttöjärjestelmään kirjautumisen jälkeen", "use_12_hour_format": "Näytä aikaleimat 12 tunnin muodossa (esim. 2:30pm)", "use_command_enter_send_message": "Komento + Enter lähettää viestin", "use_command_f_search": "Komento + F hakee aikajanalta", diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 06af8550f0..7a3ae70b5a 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3062,7 +3062,6 @@ "spaces_explainer": "Les espaces sont un nouveau moyen de regrouper les salons et les gens. En plus des espaces auxquels vous participez, vous pouvez également utiliser ceux qui sont prédéfinis.", "title": "Barre latérale" }, - "start_automatically": "Démarrer automatiquement après la phase d'authentification du système", "tac_only_notifications": "Afficher uniquement les notifications dans le centre d'activité des fils de discussions", "use_12_hour_format": "Afficher l’heure au format am/pm (par ex. 2:30pm)", "use_command_enter_send_message": "Utilisez Ctrl + Entrée pour envoyer un message", diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index fda88d4dba..b56e4dd5b0 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2124,7 +2124,6 @@ "metaspaces_subsection": "Espazos a mostrar", "title": "Barra lateral" }, - "start_automatically": "Iniciar automaticamente despois de iniciar sesión", "use_12_hour_format": "Mostrar marcas de tempo con formato 12 horas (ex. 2:30pm)", "use_command_enter_send_message": "Usar Command + Enter para enviar unha mensaxe", "use_command_f_search": "Usar Command + F para buscar na cronoloxía", diff --git a/src/i18n/strings/he.json b/src/i18n/strings/he.json index 5ba894181d..e7fdf20cae 100644 --- a/src/i18n/strings/he.json +++ b/src/i18n/strings/he.json @@ -1746,7 +1746,6 @@ "metaspaces_subsection": "מרחבי עבודה להצגה", "title": "סרגל צד" }, - "start_automatically": "התחל באופן אוטומטי לאחר הכניסה", "use_12_hour_format": "הצג חותמות זמן של 12 שעות (כלומר 2:30pm)", "use_command_enter_send_message": "השתמש במקלדת Command + Enter על מנת לשלוח הודעה", "use_control_enter_send_message": "השתמש ב Ctrl + Enter על מנת לשלוח הודעה", diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index cf76d0cdc2..4d7469469b 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3022,7 +3022,6 @@ "spaces_explainer": "A terek a szobák és az emberek csoportosításának módjai. A terek mellett, amelyekben tartózkodik, használhat néhány előre beépítettet is.", "title": "Oldalsáv" }, - "start_automatically": "Automatikus indítás rendszerindítás után", "tac_only_notifications": "Csak az üzenetszálak központban jelenítsen meg értesítéseket", "use_12_hour_format": "Az időbélyegek megjelenítése 12 órás formátumban (például du. 2:30)", "use_command_enter_send_message": "Command + Enter használata az üzenet küldéséhez", diff --git a/src/i18n/strings/hy.json b/src/i18n/strings/hy.json index 828ed08a0b..ac5f13969d 100644 --- a/src/i18n/strings/hy.json +++ b/src/i18n/strings/hy.json @@ -3007,7 +3007,6 @@ "spaces_explainer": "Տարածքները սենյակներն ու մարդկանց խմբավորելու միջոցներ են: Ձեր գտնվելու վայրերից բացի, կարող եք օգտագործել նաև նախապես կառուցված տարածքներ:", "title": "Կողային վահանակ" }, - "start_automatically": "Ավտոմատ մեկնարկ համակարգի մուտք գործելուց հետո", "tac_only_notifications": "Միայն ցուցադրել ծանուցումները թեմայի գործունեության կենտրոնում", "use_12_hour_format": "Ցուցադրել ժամանակային նշանները 12 ժամյա ձևաչափով (օրինակ՝ 2:30pm)", "use_command_enter_send_message": "Հաղորդագրություն ուղարկելու համար օգտագործեք Command + Enter", diff --git a/src/i18n/strings/id.json b/src/i18n/strings/id.json index d8f0ac227b..1151f891c0 100644 --- a/src/i18n/strings/id.json +++ b/src/i18n/strings/id.json @@ -651,6 +651,7 @@ "poll_button_no_perms_description": "Anda tidak memiliki izin untuk memulai sebuah poll di ruangan ini.", "poll_button_no_perms_title": "Izin Dibutuhkan", "replying_title": "Membalas", + "room_unencrypted": "Pesan dalam ruangan ini tidak dienkripsi secara ujung ke ujung", "room_upgraded_link": "Obrolannya dilanjutkan di sini.", "room_upgraded_notice": "Ruangan ini telah diganti dan tidak aktif lagi.", "send_button_title": "Kirim pesan", @@ -714,6 +715,7 @@ "personal_space_description": "Sebuah space pribadi untuk mengorganisir ruangan Anda", "private_description": "Undangan saja, baik untuk Anda sendiri atau tim", "private_heading": "Space pribadi Anda", + "private_only_heading": "Ruangan Anda", "private_personal_description": "Pastikan orang-orang tertentu punya akses ke %(name)s", "private_personal_heading": "Dengan siapa Anda bekerja?", "private_space": "Saya dan tim saya", @@ -987,10 +989,15 @@ "skip_verification": "Lewatkan verifikasi untuk sementara", "verify_this_device": "Verifikasi perangkat ini" }, + "cancelled_verification": "Permintaan telah kedaluwarsa, permintaan ditolak, atau terdapat ketidaksesuaian verifikasi.", "cancelling": "Membatalkan…", + "cant_confirm": "Tidak dapat mengonfirmasi?", "complete_action": "Mengerti", "complete_description": "Anda berhasil memverifikasi pengguna ini.", "complete_title": "Terverifikasi!", + "confirm_identity_description": "Verifikasi perangkat ini untuk menyiapkan perpesanan aman", + "confirm_identity_title": "Konfirmasikan identitas Anda", + "confirm_the_emojis": "Pastikan emoji di bawah ini sesuai dengan yang ditampilkan di perangkat lain Anda.", "error_starting_description": "Kami tidak dapat memulai sebuah obrolan dengan pengguna lain.", "error_starting_title": "Terjadi kesalahan memulai verifikasi", "explainer": "Pesan dengan pengguna ini terenkripsi secara ujung ke ujung dan tidak dapat dibaca oleh pihak ketiga.", @@ -1017,15 +1024,21 @@ "wrong_fingerprint": "Tidak dapat memverifikasi perangkat '%(deviceId)s' - sidik jari yang disediakan '%(fingerprint)s' tidak cocok dengan sidik jari perangkat, '%(fprint)s'" }, "no_support_qr_emoji": "Perangkat yang Anda sedang verifikasi tidak mendukung pemindaian kode QR atau verifikasi emoji, yang didukung oleh %(brand)s. Coba menggunakan klien yang lain.", + "now_you_can": "Sekarang Anda dapat membaca atau mengirim pesan dengan aman, dan siapa pun yang mengobrol dengan Anda juga dapat mempercayai perangkat ini.", + "once_accepted_can_continue": "Setelah diterima, Anda dapat melanjutkan verifikasi.", "other_party_cancelled": "Pengguna yang lain membatalkan proses verifikasi ini.", "prompt_encrypted": "Verifikasi semua pengguna di sebuah ruangan untuk memastikan keamanannya.", "prompt_unencrypted": "Di ruangan terenkripsi, verifikasi semua pengguna untuk memastikan keamanannya.", "qr_or_sas": "%(qrCode)s atau %(emojiCompare)s", "qr_prompt": "Pindai kode unik ini", + "qr_reciprocate_check_again_device": "Periksa kembali di perangkat lain Anda untuk menyelesaikan verifikasi.", + "qr_reciprocate_no": "Tidak, saya tidak melihat perisai hijau", "qr_reciprocate_same_shield_user": "Hampir selesai! Apakah %(displayName)s menampilkan perisai yang sama?", + "qr_reciprocate_yes": "Ya, saya melihat perisai hijau", "request_toast_accept_user": "Verifikasi Pengguna", "request_toast_decline_counter": "Abaikan (%(counter)s)", "request_toast_detail": "%(deviceId)s dari %(ip)s", + "request_toast_start_verification": "Mulai Verifikasi", "sas_caption_self": "Verifikasi perangkat ini dengan mengkonfirmasi nomor berikut ini yang ditampilkan di layarnya.", "sas_caption_user": "Verifikasi pengguna ini dengan mengkonfirmasi nomor berikut yang ditampilkan.", "sas_description": "Bandingkan emoji jika Anda tidak memiliki sebuah kamera di kedua perangkat", @@ -1043,11 +1056,20 @@ "unverified_sessions_toast_description": "Periksa untuk memastikan akun Anda aman", "unverified_sessions_toast_reject": "Nanti", "unverified_sessions_toast_title": "Anda memiliki sesi yang belum diverifikasi", + "use_another_device": "Gunakan perangkat lain", + "use_recovery_key": "Gunakan kunci pemulihan", + "verification_dialog_title_choose": "Pilih cara verifikasi", + "verification_dialog_title_compare_emojis": "Bandingkan emoji", + "verification_dialog_title_confirm_green_shield": "Konfirmasikan bahwa Anda melihat perisai hijau di perangkat Anda yang lain", "verification_dialog_title_device": "Verifikasi perangkat lain", + "verification_dialog_title_failed": "Verifikasi gagal", + "verification_dialog_title_start_on_other_device": "Mulai verifikasi di perangkat lain", "verification_dialog_title_user": "Permintaan Verifikasi", + "verification_dialog_title_verified": "Perangkat telah diverifikasi", "verification_skip_warning": "Tanpa memverifikasi, Anda tidak akan memiliki akses ke semua pesan Anda dan tampak tidak dipercayai kepada lainnya.", "verification_success_with_backup": "Perangkat baru Anda telah diverifikasi. Perangkat baru Anda dapat mengakses pesan-pesan terenkripsi Anda, dan pengguna lain akan melihat perangkat baru Anda sebagai dipercayai.", "verification_success_without_backup": "Perangkat baru Anda telah diverifikasi. Pengguna lain akan melihat perangkat baru Anda sebagai dipercayai.", + "verify_by_completing_one_of": "Verifikasi dengan menyelesaikan salah satu dari berikut ini:", "verify_emoji": "Verifikasi dengan emoji", "verify_emoji_prompt": "Verifikasi dengan membandingkan emoji unik.", "verify_emoji_prompt_qr": "Jika Anda tidak dapat memindai kode di atas, verifikasi dengan membandingkan emoji yang unik.", @@ -1101,6 +1123,7 @@ "tls": "Tidak dapat terhubung ke homeserver — harap cek koneksi anda, pastikan sertifikat SSL homeserver Anda terpercaya, dan ekstensi browser tidak memblokir permintaan.", "unknown": "Kesalahan tidak diketahui", "unknown_error_code": "kode kesalahan tidak diketahui", + "update_history_visibility": "Gagal mengubah visibilitas riwayat", "update_power_level": "Gagal untuk mengubah tingkat daya" }, "error_app_open_in_another_tab": "%(brand)s telah dibuka di tab lain.", @@ -1968,7 +1991,9 @@ "inaccessible_subtitle_1": "Coba ulang nanti, atau tanya kepada admin ruangan atau space untuk memeriksa jika Anda memiliki akses.", "inaccessible_subtitle_2": "%(errcode)s didapatkan saat mencoba mengakses ruangan atau space. Jika Anda pikir Anda melihat pesan ini secara tidak benar, silakan kirim sebuah laporan kutu.", "intro": { + "display_topic": "Topik: ", "dm_caption": "Hanya Anda berdua yang ada dalam percakapan ini, kecuali jika salah satu dari Anda mengundang siapa saja untuk bergabung.", + "edit_topic": "Topik: (sunting)", "enable_encryption_prompt": "Aktifkan enkripsi di pengaturan.", "encrypted_3pid_dm_pending_join": "Setelah semuanya bergabung, Anda akan dapat mengobrol", "no_avatar_label": "Tambahkan sebuah foto supaya orang-orang dapat menemukan ruangan Anda.", @@ -2032,7 +2057,9 @@ "pinned_message_banner": { "button_close_list": "Tutup daftar", "button_view_all": "Lihat semua", - "description": "Ruangan ini memiliki pesan yang disematkan. Klik untuk melihatnya.", + "description": "Pesan yang disematkan", + "go_to_newest_message": "Lihat pesan yang disematkan di lini masa dan pesan terbaru yang disematkan di sini", + "go_to_next_message": "Lihat pesan yang disematkan di lini masa dan pesan terlama berikutnya yang disematkan di sini", "title": "%(index)s dari %(length)s Pesan yang disematkan" }, "read_topic": "Klik untuk membaca topik", @@ -2140,6 +2167,26 @@ "one": "Saat ini menghapus pesan-pesan di %(count)s ruangan", "other": "Saat ini menghapus pesan-pesan di %(count)s ruangan" }, + "release_announcement": { + "done": "Selesai", + "filter": { + "description": "Saring obrolan Anda dengan satu klik. Klik untuk melihat lebih banyak filter.", + "title": "Filter cepat baru" + }, + "intro": { + "description": "Daftar obrolan telah diperbarui agar lebih jelas dan mudah digunakan.", + "title": "Obrolan memiliki tampilan baru!" + }, + "next": "Berikutnya", + "settings": { + "description": "Untuk menampilkan atau menyembunyikan pratinjau pesan, buka Semua pengaturan > Preferensi > Daftar ruangan", + "title": "Beberapa pengaturan telah dipindahkan" + }, + "sort": { + "description": "Ubah urutan obrolan Anda dari yang terbaru ke A-Z", + "title": "Urutkan obrolan Anda" + } + }, "room": { "more_options": "Opsi Lainnya", "open_room": "Buka ruangan %(roomName)s" @@ -2329,6 +2376,10 @@ "users_default": "Peran bawaan" }, "security": { + "cannot_change_to_private_due_to_missing_history_visiblity_permissions": { + "description": "Anda tidak memiliki izin untuk mengubah visibilitas riwayat ruangan. Hal ini berbahaya karena dapat memungkinkan pengguna yang tidak bergabung untuk membaca pesan.", + "title": "Tidak dapat menjadikan ruangan privat" + }, "enable_encryption_confirm_description": "Ketika diaktifkan, enkripsi untuk sebuah ruangan tidak dapat dinonaktifkan. Pesan-pesan yang terkirim di sebuah ruangan terenkripsi tidak dapat dilihat oleh server, hanya anggota di ruangan. Pelajari lebih lanjut tentang enkripsi.", "enable_encryption_confirm_title": "Aktifkan enkripsi?", "enable_encryption_public_room_confirm_description_1": "Menambahkan enkripsi pada ruangan publik tidak disarankan. Siapa pun dapat menemukan dan bergabung dengan ruangan publik, supaya siapa pun dapat membaca pesan di ruangan. Anda tidak akan mendapatkan manfaat dari enkripsi, dan Anda tidak akan dapat menonaktifkan nanti. Mengenkripsi pesan di ruangan publik akan membuat penerimaan dan pengiriman pesan lebih lambat.", @@ -2346,7 +2397,7 @@ "history_visibility_joined": "Anggota saja (sejak mereka bergabung)", "history_visibility_legend": "Siapa yang dapat membaca riwayat?", "history_visibility_shared": "Anggota saja (sejak memilih opsi ini)", - "history_visibility_warning": "Perubahan siapa yang dapat membaca riwayat hanya akan berlaku untuk pesan berikutnya di ruangan ini. Visibilitas riwayat yang ada tidak akan berubah.", + "history_visibility_warning": "Visibilitas riwayat yang ada tidak akan berubah.", "history_visibility_world_readable": "Siapa Saja", "join_rule_description": "Putuskan siapa yang dapat bergabung %(roomName)s.", "join_rule_invite": "Privat (undangan saja)", @@ -2389,6 +2440,7 @@ "other": "Memperbarui space... (%(progress)s dari %(count)s)" }, "join_rule_upgrade_upgrading_room": "Meningkatkan ruangan", + "join_rule_world_readable_description": "Mengubah siapa yang dapat bergabung ke dalam ruangan juga akan mengubah visibilitas pesan di masa mendatang.", "public_without_alias_warning": "Untuk menautkan ruangan ini, mohon tambahkan sebuah alamat.", "publish_room": "Buat ruangan ini terlihat di direktori ruangan publik.", "publish_space": "Buat ruang ini terlihat di direktori ruangan publik.", @@ -2527,6 +2579,7 @@ "breadcrumb_second_description": "Anda akan kehilangan semua riwayat pesan yang hanya disimpan di server", "breadcrumb_third_description": "Anda perlu memverifikasi ulang semua perangkat dan kontak yang ada", "breadcrumb_title": "Apakah Anda yakin ingin mengatur ulang identitas Anda?", + "breadcrumb_title_cant_confirm": "Anda perlu mengatur ulang identitas Anda", "breadcrumb_title_forgot": "Lupa kunci pemulihan Anda? Anda harus mengatur ulang identitas Anda.", "breadcrumb_title_sync_failed": "Gagal menyinkronkan penyimpanan kunci. Anda perlu mengatur ulang identitas Anda.", "breadcrumb_warning": "Lakukan ini hanya jika Anda yakin akun Anda telah terkompromi.", @@ -2603,6 +2656,7 @@ "allow_spellcheck": "Izinkan pemeriksaan ejaan", "application_language": "Bahasa aplikasi", "application_language_reload_hint": "Aplikasi akan dimuat ulang setelah memilih bahasa lain", + "avatar_open_menu": "Buka menu avatar", "avatar_remove_progress": "Menghapus gambar...", "avatar_save_progress": "Mengunggah gambar...", "avatar_upload_error_text": "Format berkas tidak didukung atau gambar lebih besar dari %(size)s.", @@ -2799,6 +2853,10 @@ "rule_suppress_notices": "Pesan dikirim oleh bot", "rule_tombstone": "Ketika ruangan telah ditingkatkan", "show_message_desktop_notification": "Tampilkan pesan di notifikasi desktop", + "sounds_release_announcement": { + "description": "Notifikasi ping dan dering panggilan Anda telah diperbarui—lebih jelas, lebih cepat, dan tidak mengganggu", + "title": "Kami telah menyegarkan suara Anda" + }, "voip": "Panggilan Audio dan Video" }, "preferences": { @@ -2825,6 +2883,7 @@ "room_list_heading": "Daftar ruangan", "show_avatars_pills": "Tampilkan avatar di sebutan pengguna, ruangan, dan peristiwa", "show_polls_button": "Tampilkan tombol pemungutan suara", + "startup_window_behaviour_label": "Perilaku saat memulai dan jendela", "surround_text": "Kelilingi teks yang dipilih saat mengetik karakter khusus", "time_heading": "Tampilkan waktu", "user_timezone": "Atur zona waktu" @@ -2998,7 +3057,12 @@ "spaces_explainer": "Space adalah cara untuk mengelompokkan ruangan dan orang-orang. Di samping space yang Anda berada, Anda juga dapat menggunakan beberapa yang sudah dibuat sebelumnya.", "title": "Bilah Samping" }, - "start_automatically": "Mulai setelah login sistem secara otomatis", + "start_automatically": { + "disabled": "Tidak", + "enabled": "Ya", + "label": "Buka %(brand)s saat Anda masuk ke komputer Anda", + "minimised": "Diminimalkan" + }, "tac_only_notifications": "Hanya tampilkan notifikasi dalam pusat aktivitas utas", "use_12_hour_format": "Tampilkan stempel waktu dalam format 12 jam (mis. 2:30pm)", "use_command_enter_send_message": "Gunakan ⌘ + Enter untuk mengirim pesan", @@ -3474,7 +3538,7 @@ "unknown": "%(senderDisplayName)s mengubah akses tamu ke %(rule)s" }, "m.room.history_visibility": { - "invited": "%(senderName)s membuat semua riwayat ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan, sejak mereka diundang.", + "invited": "%(senderName)s membuat riwayat ruangan masa depan terlihat oleh semua anggota ruangan, sejak mereka diundang.", "joined": "%(senderName)s membuat semua riwayat ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan, sejak mereka bergabung.", "shared": "%(senderName)s membuat semua riwayat ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan.", "unknown": "%(senderName)s membuat semua riwayat ruangan di masa mendatang dapat dilihat oleh orang yang tidak dikenal (%(visibility)s).", @@ -3927,6 +3991,7 @@ "connection_lost": "Koneksi ke server telah hilang", "connection_lost_description": "Anda tidak dapat membuat panggilan tanpa terhubung ke server.", "consulting": "Mengkonsultasi dengan %(transferTarget)s. Transfer ke %(transferee)s", + "decline_call": "Tolak", "default_device": "Perangkat Bawaan", "dial": "Panggil", "dialpad": "Tombol Penyetel", @@ -3978,6 +4043,7 @@ "show_sidebar_button": "Tampilkan sisi bilah", "silence": "Diamkan panggilan", "silenced": "Notifikasi dibisukan", + "skip_lobby_toggle_option": "Segera bergabung", "start_screenshare": "Mulai membagikan layar Anda", "stop_screenshare": "Berhenti membagikan layar Anda", "too_many_calls": "Terlalu Banyak Panggilan", diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json index dfd36768c9..eaa64a76e4 100644 --- a/src/i18n/strings/is.json +++ b/src/i18n/strings/is.json @@ -2047,7 +2047,6 @@ "metaspaces_subsection": "Svæði sem á að birta", "title": "Hliðarspjald" }, - "start_automatically": "Ræsa sjálfvirkt við innskráningu í kerfi", "use_12_hour_format": "Birta tímamerki á 12 stunda sniði (t.d. 2:30 fh)", "use_command_enter_send_message": "Notaðu Command + Enter til að senda skilaboð", "use_command_f_search": "Notaðu Command + F til að leita í tímalínu", diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index cb879b33c6..24cbc5c9a0 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2559,7 +2559,6 @@ "spaces_explainer": "Gli spazi sono modi per raggruppare stanze e persone. Oltre agli spazi in cui ti trovi, puoi usarne anche altri già integrati.", "title": "Barra laterale" }, - "start_automatically": "Esegui automaticamente all'avvio del sistema", "use_12_hour_format": "Mostra gli orari nel formato 12 ore (es. 2:30pm)", "use_command_enter_send_message": "Usa Comando + Invio per inviare un messaggio", "use_command_f_search": "Usa Comando + F per cercare nella linea temporale", diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json index fa5150619b..7063d6c9fe 100644 --- a/src/i18n/strings/ja.json +++ b/src/i18n/strings/ja.json @@ -2318,7 +2318,6 @@ "metaspaces_subsection": "表示するスペース", "title": "サイドバー" }, - "start_automatically": "システムログイン後に自動的に起動", "use_12_hour_format": "発言時刻を12時間形式で表示(例:2:30午後)", "use_command_enter_send_message": "Command+Enterでメッセージを送信", "use_command_f_search": "Command+Fでタイムラインを検索", diff --git a/src/i18n/strings/ka.json b/src/i18n/strings/ka.json index 9d8eec81d4..c48553682e 100644 --- a/src/i18n/strings/ka.json +++ b/src/i18n/strings/ka.json @@ -1997,7 +1997,6 @@ "spaces_explainer": "სივრცეები არის ოთახების და ადამიანების დაჯგუფების გზები. იმ სივრცეებთან ერთად, სადაც იმყოფებით, შეგიძლიათ გამოიყენოთ რამდენიმე წინასწარ აშენებული.", "title": "გვერდითი ზოლი" }, - "start_automatically": "ავტომატურად დაიწყება სისტემაში შესვლის შემდეგ", "use_12_hour_format": "დროის ანაბეჭდების ჩვენება 12 საათის ფორმატში (მაგ. 14:30)", "use_command_enter_send_message": "გამოიყენეთ Command + Enter შეტყობინების გასაგზავნად", "use_command_f_search": "გამოიყენეთ Command + F დროის ხაზის მოსაძებნად", diff --git a/src/i18n/strings/lo.json b/src/i18n/strings/lo.json index 3e4bd9321a..f58c5384f8 100644 --- a/src/i18n/strings/lo.json +++ b/src/i18n/strings/lo.json @@ -2052,7 +2052,6 @@ "metaspaces_subsection": "ພຶ້ນທີ່ຈະສະແດງ", "title": "ແຖບດ້ານຂ້າງ" }, - "start_automatically": "ເລີ່ມອັດຕະໂນມັດຫຼັງຈາກເຂົ້າສູ່ລະບົບ", "use_12_hour_format": "ສະແດງເວລາໃນຮູບແບບ 12 ຊົ່ວໂມງ (ເຊັ່ນ: 2:30 ໂມງແລງ)", "use_command_enter_send_message": "ໃຊ້ Command + Enter ເພື່ອສົ່ງຂໍ້ຄວາມ", "use_command_f_search": "ໃຊ້ຄໍາສັ່ງ + F ເພື່ອຊອກຫາເສັ້ນເວລາ", diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json index e2417cf7df..7a499eaaa0 100644 --- a/src/i18n/strings/lt.json +++ b/src/i18n/strings/lt.json @@ -1633,7 +1633,6 @@ "metaspaces_subsection": "Kurias erdves rodyti", "title": "Šoninė juosta" }, - "start_automatically": "Pradėti automatiškai prisijungus prie sistemos", "use_12_hour_format": "Rodyti laiko žymes 12 valandų formatu (pvz. 2:30pm)", "use_command_enter_send_message": "Naudokite Command + Enter žinutės išsiuntimui", "use_command_f_search": "Naudokite Command + F ieškojimui laiko juostoje", diff --git a/src/i18n/strings/lv.json b/src/i18n/strings/lv.json index ee4d1ace47..062d761a8f 100644 --- a/src/i18n/strings/lv.json +++ b/src/i18n/strings/lv.json @@ -2487,7 +2487,6 @@ "spaces_explainer": "Telpas ir veids, kā grupēt istabas un cilvēkus. Papildus telpām, kurās atrodaties, varat izmantot arī dažas sākotnēji veidotās telpas.", "title": "Sānjoslas" }, - "start_automatically": "Startēt pie ierīces ielādes", "use_12_hour_format": "Rādīt laiku 12 stundu formātā (piemēram 2:30pm)", "use_command_enter_send_message": "Lietot Command + Enter ziņas nosūtīšanai", "use_command_f_search": "Lietot Command + F meklēšanai laika skalā", diff --git a/src/i18n/strings/mg_MG.json b/src/i18n/strings/mg_MG.json index 6b330d447f..9d206f45aa 100644 --- a/src/i18n/strings/mg_MG.json +++ b/src/i18n/strings/mg_MG.json @@ -2543,7 +2543,6 @@ "spaces_explainer": "Ny habaka dia fomba ivondronana efitrano sy olona. Miaraka amin'ireo habaka misy anao, azonao atao koa ny mampiasa ireo efa vita.", "title": "Sidebar" }, - "start_automatically": "Manomboka ho azy aoriany fidirana amin'ny rafitra", "use_12_hour_format": "Asehoy ny mari-pamantarana amin'ny endrika adiny 12 (oh 2:30 ariva)", "use_command_enter_send_message": "Ampiasao Command + Enter raha handefa hafatra", "use_command_f_search": "Ampiasao ny baiko + F hitadiavana ny fandaharam-potoana", diff --git a/src/i18n/strings/nb_NO.json b/src/i18n/strings/nb_NO.json index 4cc7144a45..f09ad4938f 100644 --- a/src/i18n/strings/nb_NO.json +++ b/src/i18n/strings/nb_NO.json @@ -547,7 +547,7 @@ "orphan_rooms": "Andre rom", "password": "Passord", "people": "Folk", - "preferences": "Brukervalg", + "preferences": "Innstillinger", "presence": "Tilstedeværelse", "preview_message": "Hei der. Du er fantastisk!", "privacy": "Personvern", @@ -717,6 +717,7 @@ "personal_space_description": "Et privat område for å organisere rommene dine", "private_description": "Kun for inviterte, best for deg selv eller team", "private_heading": "Ditt private område", + "private_only_heading": "Ditt område", "private_personal_description": "Sørg for at de riktige personene har tilgang til %(name)s", "private_personal_heading": "Hvem jobber du med?", "private_space": "Meg og mine teammedlemmer", @@ -991,10 +992,15 @@ "skip_verification": "Hopp over verifisering for nå", "verify_this_device": "Verifiser denne enheten" }, + "cancelled_verification": "Enten gikk forespørselen ut på tid, ble forespørselen avslått, eller var det en uoverensstemmelse i verifiseringen.", "cancelling": "Avbryter …", + "cant_confirm": "Kan du ikke bekrefte?", "complete_action": "Skjønner", "complete_description": "Du har vellykket verifisert denne brukeren.", "complete_title": "Verifisert!", + "confirm_identity_description": "Verifiser denne enheten for å konfigurere sikker meldingsutveksling", + "confirm_identity_title": "Bekreft identiteten din", + "confirm_the_emojis": "Bekreft at emojiene nedenfor samsvarer med de som vises på den andre enheten din.", "error_starting_description": "Vi kunne ikke starte en chat med den andre brukeren.", "error_starting_title": "Feil ved start av bekreftelse", "explainer": "Sikre meldinger med denne brukeren er ende-til-ende-kryptert og kan ikke leses av tredjeparter.", @@ -1021,15 +1027,21 @@ "wrong_fingerprint": "Kan ikke verifisere enheten %(deviceId)s '- det medfølgende fingeravtrykket'%(fingerprint)s «samsvarer ikke med enhetens fingeravtrykk»%(fprint)s '" }, "no_support_qr_emoji": "Enheten du prøver å bekrefte støtter ikke skanning av en QR-kode eller emoji-verifikasjon, som er det som %(brand)s støtter. Prøv med en annen klient.", + "now_you_can": "Nå kan du lese eller sende meldinger på en sikker måte, og alle du chatter med kan også stole på denne enheten.", + "once_accepted_can_continue": "Når du er godkjent, kan du fortsette med verifiseringen.", "other_party_cancelled": "Den andre parten kansellerte verifiseringen.", "prompt_encrypted": "Bekreft alle brukere i et rom for å sikre at det er sikkert.", "prompt_unencrypted": "Bekreft alle brukere i krypterte rom for å sikre at det er sikkert.", "qr_or_sas": "%(qrCode)s eller %(emojiCompare)s", "qr_prompt": "Skann denne unike koden", + "qr_reciprocate_check_again_device": "Sjekk igjen på den andre enheten din for å fullføre verifiseringen.", + "qr_reciprocate_no": "Nei, jeg ser ikke et grønt skjold.", "qr_reciprocate_same_shield_user": "Nesten der! Viser %(displayName)s det samme skjoldet?", + "qr_reciprocate_yes": "Ja, jeg ser et grønt skjold.", "request_toast_accept_user": "Verifiser bruker", "request_toast_decline_counter": "Ignorer (%(counter)s)", "request_toast_detail": "%(deviceId)s fra %(ip)s", + "request_toast_start_verification": "Start verifisering", "sas_caption_self": "Bekreft denne enheten ved å bekrefte at følgende nummer vises på skjermen.", "sas_caption_user": "Bekreft denne brukeren ved å bekrefte at følgende nummer vises på skjermen.", "sas_description": "Sammenlign et unikt sett med emojier hvis du ikke har et kamera på noen av enhetene", @@ -1047,11 +1059,20 @@ "unverified_sessions_toast_description": "Se gjennom for å sikre at kontoen din er trygg", "unverified_sessions_toast_reject": "Senere", "unverified_sessions_toast_title": "Du har ubekreftede økter", + "use_another_device": "Bruk en annen enhet", + "use_recovery_key": "Bruk gjenopprettingsnøkkel", + "verification_dialog_title_choose": "Velg hvordan du vil verifisere", + "verification_dialog_title_compare_emojis": "Sammenlign emojier", + "verification_dialog_title_confirm_green_shield": "Bekreft at du ser et grønt skjold på den andre enheten din.", "verification_dialog_title_device": "Bekreft annen enhet", + "verification_dialog_title_failed": "Verifiseringen mislyktes", + "verification_dialog_title_start_on_other_device": "Start verifiseringen på den andre enheten", "verification_dialog_title_user": "Verifiseringsforespørsel", + "verification_dialog_title_verified": "Enhet verifisert", "verification_skip_warning": "Uten verifisering vil du ikke ha tilgang til alle meldingene dine, og du kan fremstå som upålitelig for andre.", "verification_success_with_backup": "Den nye enheten din er nå bekreftet. Den har tilgang til dine krypterte meldinger, og andre brukere vil se det som klarert.", "verification_success_without_backup": "Den nye enheten din er nå bekreftet. Andre brukere vil se det som pålitelig.", + "verify_by_completing_one_of": "Verifiser ved å fullføre ett av følgende:", "verify_emoji": "Verifiser med emoji", "verify_emoji_prompt": "Bekreft ved å sammenligne unike emoji.", "verify_emoji_prompt_qr": "Hvis du ikke kan skanne koden ovenfor, bekreft ved å sammenligne unike emoji.", @@ -1105,6 +1126,7 @@ "tls": "Kan ikke koble til hjemmeserveren - sjekk tilkoblingen din, sørg for at hjemmeserverens SSL-sertifikat er klarert, og at en nettleserutvidelse ikke blokkerer forespørsler.", "unknown": "Ukjent feil", "unknown_error_code": "ukjent feilkode", + "update_history_visibility": "Kunne ikke endre historikksynligheten", "update_power_level": "Kan ikke endre tilgangsnivå" }, "error_app_open_in_another_tab": "Bytt til den andre fanen for å koble til %(brand)s . Denne fanen kan nå lukkes.", @@ -1976,7 +1998,9 @@ "inaccessible_subtitle_1": "Prøv igjen senere, eller be en områdeadministrator om å sjekke om du har tilgang.", "inaccessible_subtitle_2": "%(errcode)s ble returnert mens du prøvde å få tilgang til rommet eller området. Hvis du tror du ser denne meldingen ved en feil, kan du sende inn en feilrapport.", "intro": { + "display_topic": "Emne: ", "dm_caption": "Bare dere to er i denne samtalen, med mindre noen av dere inviterer noen til å bli med.", + "edit_topic": "Emne: (rediger)", "enable_encryption_prompt": "Aktiver kryptering i innstillinger.", "encrypted_3pid_dm_pending_join": "Når alle har blitt med, vil du kunne chatte", "no_avatar_label": "Legg til et bilde så folk lettere kan finne rommet ditt.", @@ -2161,7 +2185,15 @@ "description": "Chat-listen er oppdatert for å være mer oversiktlig og enkel å bruke.", "title": "Chatten har fått et nytt utseende!" }, - "next": "Neste" + "next": "Neste", + "settings": { + "description": "For å vise eller skjule forhåndsvisning av meldinger, gå til Alle innstillinger > Innstillinger > Romliste", + "title": "Noen innstillinger er flyttet" + }, + "sort": { + "description": "Endre rekkefølgen på chatene dine fra nyeste til A-Å", + "title": "Sorter chatene dine" + } }, "room": { "more_options": "Flere alternativer", @@ -2351,6 +2383,10 @@ "users_default": "Forvalgt rolle" }, "security": { + "cannot_change_to_private_due_to_missing_history_visiblity_permissions": { + "description": "Du har ikke tillatelse til å endre synligheten av historikken for rommet. Dette er farlig, da det kan gjøre det mulig for brukere som ikke er med i rommet å lese meldinger.", + "title": "Kan ikke gjøre rommet privat" + }, "enable_encryption_confirm_description": "Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldinger som sendes i et kryptert rom, kan ikke ses av serveren, bare av deltakerne i rommet. Aktivering av kryptering kan hindre mange boter og broer i å fungere korrekt. Finn ut mer om kryptering.", "enable_encryption_confirm_title": "Vil du skru på kryptering?", "enable_encryption_public_room_confirm_description_1": "Det anbefales ikke å legge til kryptering i offentlige rom. Alle kan finne og bli med i offentlige rom, slik at alle kan lese meldinger i dem. Du får ingen av fordelene med kryptering, og du vil ikke kunne slå den av senere. Kryptering av meldinger i et offentlig rom vil gjøre mottak og sending av meldinger tregere.", @@ -2411,6 +2447,7 @@ "other": "Oppdaterer områder... (%(progress)s out of %(count)s)" }, "join_rule_upgrade_upgrading_room": "Oppgraderer rom", + "join_rule_world_readable_description": "Hvis du endrer hvem som kan bli med i rommet, vil det også endre synligheten til fremtidige meldinger.", "public_without_alias_warning": "For å lenke til dette rommet, vennligst legg til en adresse.", "publish_room": "Gjør dette rommet synlig i katalogen for offentlige rom.", "publish_space": "Gjør dette området synlig i katalogen for offentlige rom.", @@ -2549,6 +2586,7 @@ "breadcrumb_second_description": "Du mister all meldingshistorikk som bare er lagret på serveren", "breadcrumb_third_description": "Du må bekrefte alle eksisterende enheter og kontakter på nytt", "breadcrumb_title": "Er du sikker på at du vil tilbakestille identiteten din?", + "breadcrumb_title_cant_confirm": "Du må tilbakestille identiteten din", "breadcrumb_title_forgot": "Har du glemt gjenopprettingsnøkkelen din? Du må tilbakestille identiteten din.", "breadcrumb_title_sync_failed": "Kunne ikke synkronisere nøkkellageret. Du må tilbakestille identiteten din.", "breadcrumb_warning": "Gjør dette bare hvis du tror at kontoen din har blitt kompromittert.", @@ -2625,6 +2663,7 @@ "allow_spellcheck": "Tillat stavekontroll", "application_language": "Applikasjonsspråk", "application_language_reload_hint": "Appen lastes inn på nytt etter å ha valgt et annet språk", + "avatar_open_menu": "Åpne avatarmenyen", "avatar_remove_progress": "Fjerner bilde...", "avatar_save_progress": "Laster opp bilde...", "avatar_upload_error_text": "Filformatet støttes ikke, eller bildet er større enn %(size)s.", @@ -2821,6 +2860,10 @@ "rule_suppress_notices": "Meldinger sendt av bot", "rule_tombstone": "Når rom blir oppgradert", "show_message_desktop_notification": "Vis meldingen i skrivebordsvarselet", + "sounds_release_announcement": { + "description": "Varslingslyden og ringetonen din er oppdatert – klarere, raskere og mindre forstyrrende.", + "title": "Vi har oppdatert lydene dine" + }, "voip": "Lyd- og videosamtaler" }, "preferences": { @@ -2847,6 +2890,7 @@ "room_list_heading": "Romliste", "show_avatars_pills": "Vis avatarer i bruker-, rom- og hendelsesomtaler", "show_polls_button": "Vis avstemninger-knapp", + "startup_window_behaviour_label": "Oppstart og vindusatferd", "surround_text": "Omgi valgt tekst når du skriver spesialtegn", "time_heading": "Viser tid", "user_timezone": "Angi tidssone" @@ -3020,7 +3064,12 @@ "spaces_explainer": "Områder er måter å gruppere rom og mennesker på. Ved siden av områdene du er i, kan du også bruke noen forhåndsbygde.", "title": "Sidepanel" }, - "start_automatically": "Start automatisk etter systempålogging", + "start_automatically": { + "disabled": "Nei", + "enabled": "Ja", + "label": "Åpne %(brand)s når du logger deg på datamaskinen din.", + "minimised": "Minimert" + }, "tac_only_notifications": "Vis bare varsler i trådens aktivitetssenter", "use_12_hour_format": "Vis tidsstempler i 12-timersformat (f.eks. 2:30pm)", "use_command_enter_send_message": "Bruk Kommando + Enter for å sende en melding", @@ -3946,6 +3995,7 @@ "connection_lost": "Mistet forbindelsen til serveren", "connection_lost_description": "Du kan ikke ringe uten tilkobling til serveren.", "consulting": "Rådføring med %(transferTarget)s. Overfør til %(transferee)s", + "decline_call": "Avslå", "default_device": "Standardenhet", "dial": "Ring", "dialpad": "Nummerpanel", @@ -3997,6 +4047,7 @@ "show_sidebar_button": "Vis sidepanel", "silence": "Demp samtale", "silenced": "Varslinger er dempet", + "skip_lobby_toggle_option": "Bli med umiddelbart", "start_screenshare": "Begynn å dele skjermen din", "stop_screenshare": "Slutt å dele skjermen din", "too_many_calls": "For mange samtaler", diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index fd673d7d7c..8c4323a0e3 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -2170,7 +2170,6 @@ "metaspaces_subsection": "Spaces om te tonen", "title": "Zijbalk" }, - "start_automatically": "Automatisch starten na systeemlogin", "use_12_hour_format": "Tijd in 12-uursformaat tonen (bv. 2:30pm)", "use_command_enter_send_message": "Gebruik Command (⌘) + Enter om een bericht te sturen", "use_command_f_search": "Gebruik Command + F om te zoeken in de tijdlijn", diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index f3bbdd2cf2..82176f021d 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -32,7 +32,7 @@ }, "a11y_jump_first_unread_room": "Przejdź do pierwszego nieprzeczytanego pokoju.", "action": { - "accept": "Akceptuj", + "accept": "Akceptuję", "add": "Dodaj", "add_existing_room": "Dodaj istniejący pokój", "add_people": "Dodaj osoby", @@ -497,7 +497,7 @@ "edited": "edytowane", "email_address": "Adres e-mail", "emoji": "Emoji", - "encrypted": "Szyfrowane", + "encrypted": "Szyfrowanie", "encryption_enabled": "Włączono szyfrowanie", "error": "Błąd", "faq": "Najczęściej zadawane pytania", @@ -647,16 +647,17 @@ "mode_plain": "Ukryj formatowanie", "mode_rich_text": "Pokaż formatowanie", "no_perms_notice": "Nie masz uprawnień do pisania w tym pokoju", - "placeholder": "Wyślij wiadomość…", - "placeholder_encrypted": "Wyślij zaszyfrowaną wiadomość…", - "placeholder_reply": "Wyślij odpowiedź…", - "placeholder_reply_encrypted": "Wyślij zaszyfrowaną odpowiedź…", - "placeholder_thread": "Odpowiedz do wątku…", - "placeholder_thread_encrypted": "Odpowiedz do wątku szyfrowanego…", + "placeholder": "Wyślij niezaszyfrowaną wiadomość…", + "placeholder_encrypted": "Wyślij wiadomość…", + "placeholder_reply": "Wyślij niezaszyfrowaną odpowiedź…", + "placeholder_reply_encrypted": "Wyślij odpowiedź…", + "placeholder_thread": "Odpowiedz na niezaszyfrowany wątek…", + "placeholder_thread_encrypted": "Odpowiedz na wątek…", "poll_button": "Ankieta", "poll_button_no_perms_description": "Nie masz uprawnień do rozpoczęcia ankiety w tym pokoju", "poll_button_no_perms_title": "Wymagane Uprawnienia", "replying_title": "Odpowiadanie", + "room_unencrypted": "Wiadomości w tym pokoju nie są szyfrowane end-to-end.", "room_upgraded_link": "Konwersacja jest kontynuowana tutaj.", "room_upgraded_notice": "Ten pokój został zamieniony i nie jest już aktywny.", "send_button_title": "Wyślij wiadomość", @@ -720,6 +721,7 @@ "personal_space_description": "Prywatna przestrzeń do organizacji Twoich pokoi", "private_description": "Tylko na zaproszenie, najlepsza dla siebie lub zespołów", "private_heading": "Twoja prywatna przestrzeń", + "private_only_heading": "Twoja przestrzeń", "private_personal_description": "Upewnij się, że odpowiednie osoby mają dostęp do %(name)s", "private_personal_heading": "Z kim pracujesz?", "private_space": "Ja i moi koledzy z drużyny", @@ -925,7 +927,8 @@ }, "privacy_warning": "Upewnij się, że nikt nie widzi tego ekranu!", "restoring": "Przywracanie kluczy z kopii zapasowej", - "security_key_title": "Klucz przywracania" + "security_key_label": "Klucz przywracania", + "security_key_title": "Wprowadź klucz przywracania" }, "bootstrap_title": "Konfigurowanie kluczy", "confirm_encryption_setup_body": "Kliknij przycisk poniżej, aby potwierdzić ustawienie szyfrowania.", @@ -995,10 +998,15 @@ "skip_verification": "Pomiń weryfikację na razie", "verify_this_device": "Zweryfikuj to urządzenie" }, + "cancelled_verification": "Albo upłynął limit czasu oczekiwania, żądanie zostało odrzucone, albo wystąpiła niezgodność w trakcie weryfikacji.", "cancelling": "Anulowanie…", + "cant_confirm": "Nie możesz potwierdzić?", "complete_action": "Zrobione", "complete_description": "Pomyślnie zweryfikowałeś tego użytkownika.", "complete_title": "Zweryfikowano!", + "confirm_identity_description": "Zweryfikuj to urządzenie, aby ustawić bezpieczną komunikację", + "confirm_identity_title": "Potwierdź swoją tożsamość", + "confirm_the_emojis": "Upewnij się, że emoji poniżej pasują do tych pokazanych na innej sesji.", "error_starting_description": "Nie byliśmy w stanie rozpocząć czatu z innym użytkownikiem.", "error_starting_title": "Wystąpił błąd w trakcie weryfikacji", "explainer": "Bezpieczne wiadomości z tym użytkownikiem są szyfrowane end-to-end i nie mogą zostać odczytane przez osoby trzecie.", @@ -1025,15 +1033,21 @@ "wrong_fingerprint": "Nie można zweryfikować urządzenia '%(deviceId)s' - podany odcisk palca '%(fingerprint)s' nie pasuje do odcisku palca na urządzeniu, '%(fprint)s'" }, "no_support_qr_emoji": "Urządzenie, które próbujesz zweryfikować nie wspiera skanowania kodu QR lub weryfikacji emoji, czyli tego co obsługuje %(brand)s. Spróbuj użyć innego klienta.", + "now_you_can": "Teraz możesz bezpiecznie czytać i wysyłać wiadomości, a każdy z kim czatujesz również może ufać temu urządzeniu.", + "once_accepted_can_continue": "Po zaakceptowaniu będziesz mógł kontynuować weryfikację.", "other_party_cancelled": "Druga strona anulowała weryfikację.", "prompt_encrypted": "Zweryfikuj wszystkich użytkowników w pokoju, aby upewnić się, że jest bezpieczny.", "prompt_unencrypted": "W pokojach szyfrowanych, zweryfikuj wszystkich użytkowników, aby upewnić się, że są bezpieczne.", "qr_or_sas": "%(qrCode)s lub %(emojiCompare)s", "qr_prompt": "Zeskanuj ten unikatowy kod", + "qr_reciprocate_check_again_device": "Sprawdź ponownie drugie urządzenie, aby zakończyć weryfikację.", + "qr_reciprocate_no": "Nie, nie widzę zielonej tarczy", "qr_reciprocate_same_shield_user": "Prawie gotowe! Czy %(displayName)s pokazuje tę samą tarczę?", + "qr_reciprocate_yes": "Tak, widzę zieloną tarczę", "request_toast_accept_user": "Zweryfikuj użytkownika", "request_toast_decline_counter": "Ignoruj (%(counter)s)", "request_toast_detail": "%(deviceId)s z %(ip)s", + "request_toast_start_verification": "Rozpocznij weryfikację", "sas_caption_self": "Zweryfikuj to urządzenie, upewniając się że poniższy numer wyświetlony jest na jego ekranie.", "sas_caption_user": "Sprawdź tego użytkownika potwierdzając, że następujące liczby pojawiają się na ekranie rozmówcy.", "sas_description": "Porównaj unikatowy zestaw emoji, jeżeli nie masz aparatu na którymś z urządzeń", @@ -1051,11 +1065,20 @@ "unverified_sessions_toast_description": "Sprawdź, by upewnić się że Twoje konto jest bezpieczne", "unverified_sessions_toast_reject": "Później", "unverified_sessions_toast_title": "Masz niezweryfikowane sesje", + "use_another_device": "Użyj innego urządzenia", + "use_recovery_key": "Użyj klucza przywracania", + "verification_dialog_title_choose": "Wybierz sposób weryfikacji", + "verification_dialog_title_compare_emojis": "Porównaj emoji", + "verification_dialog_title_confirm_green_shield": "Sprawdź, czy na drugim urządzeniu widzisz zieloną tarczę", "verification_dialog_title_device": "Zweryfikuj drugie urządzenie", + "verification_dialog_title_failed": "Weryfikacja nie powiodła się", + "verification_dialog_title_start_on_other_device": "Rozpocznij weryfikację na drugim urządzeniu", "verification_dialog_title_user": "Żądanie weryfikacji", + "verification_dialog_title_verified": "Urządzenie zweryfikowane", "verification_skip_warning": "Bez weryfikacji, nie będziesz posiadać dostępu do wszystkich swoich wiadomości, a inni będą Cię widzieć jako niezaufanego.", "verification_success_with_backup": "Twoje nowe urządzenie zostało zweryfikowane. Posiada dostęp do Twoich wiadomości szyfrowanych, a inni użytkownicy będą je widzieć jako zaufane.", "verification_success_without_backup": "Twoje nowe urządzenie zostało zweryfikowane. Inni użytkownicy będą je widzieć jako zaufane.", + "verify_by_completing_one_of": "Zweryfikuj, wykonując jedną z poniższych czynności:", "verify_emoji": "Zweryfikuj przez Emoji", "verify_emoji_prompt": "Zweryfikuj, porównując unikalne emotikony.", "verify_emoji_prompt_qr": "Jeśli nie jesteś w stanie skanować kodu powyżej, zweryfikuj porównując emoji.", @@ -1109,6 +1132,7 @@ "tls": "Nie można nawiązać połączenia z serwerem - proszę sprawdź twoje połączenie, upewnij się, że certyfikat SSL serwera jest zaufany, i że dodatki przeglądarki nie blokują żądania.", "unknown": "Nieznany błąd", "unknown_error_code": "nieznany kod błędu", + "update_history_visibility": "Nie udało się zmienić widoczności historii", "update_power_level": "Nie udało się zmienić poziomu mocy" }, "error_app_open_in_another_tab": "Zmień kartę aby połączyć się do %(brand)s. Ta karta może zostać zamknięta.", @@ -1354,6 +1378,10 @@ "name_email_mxid_share_space": "Zaproś kogoś za pomocą jego imienia, adresu e-mail, nazwy użytkownika (takiej jak ) lub udostępnij tą przestrzeń.", "name_mxid_share_room": "Zaproś kogoś za pomocą jego imienia, nazwy użytkownika (takiej jak ) lub udostępnij ten pokój.", "name_mxid_share_space": "Zaproś kogoś za pomocą jego imienia, nazwy użytkownika (takiej jak ) lub udostępnij tą przestrzeń.", + "progress": { + "dont_close": "Nie zamykaj aplikacji przed zakończeniem.", + "preparing": "Przygotowywanie zaproszeń..." + }, "recents_section": "Najnowsze rozmowy", "room_failed_partial": "Wysłaliśmy pozostałym, ale osoby poniżej nie mogły zostać zaproszone do ", "room_failed_partial_title": "Niektóre zaproszenia nie mogły zostać wysłane", @@ -1524,6 +1552,9 @@ "render_reaction_images_description": "Czasami określane jako \"emoji niestandardowe\".", "report_to_moderators": "Zgłoś do moderatorów", "report_to_moderators_description": "W pokojach, które wspierają moderacje, przycisk \"Zgłoś\" pozwoli Ci zgłosić nadużycia moderatorom.", + "share_history_on_invite": "Udostępnij zaszyfrowaną historię nowym członkom", + "share_history_on_invite_description": "Gdy zapraszasz użytkownika do zaszyfrowanego pokoju, w którym widoczność historii jest ustawiona na „udostępniona”, udostępnij historię wiadomości temu użytkownikowi lub zaakceptuj ją, gdy dołączysz do takiego pokoju.", + "share_history_on_invite_warning": "Ta funkcja jest EKSPERYMENTALNA i nie wszystkie środki bezpieczeństwa są w niej zaimplementowane. Nie włączaj na kontach produkcyjnych.", "sliding_sync": "Tryb synchronizacji przesuwanej", "sliding_sync_description": "W trakcie aktywnego rozwoju, nie można wyłączyć.", "sliding_sync_disabled_notice": "Zaloguj się ponownie, aby wyłączyć", @@ -1646,6 +1677,7 @@ "filter_placeholder": "Filtruj członków pokoju", "invite_button_no_perms_tooltip": "Nie posiadasz uprawnień, aby zapraszać użytkowników", "invited_label": "Zaproszono", + "list_title": "Lista członków", "no_matches": "Brak wyników" }, "member_list_back_action_label": "Członkowie pokoju", @@ -1750,6 +1782,7 @@ }, "power_level": { "admin": "Administrator", + "creator": "Właściciel", "custom": "Własny (%(level)s)", "custom_level": "Własny poziom", "default": "Domyślne", @@ -1898,6 +1931,7 @@ "thread_list": { "context_menu_label": "Opcje wątków" }, + "title": "Prawy panel", "video_room_chat": { "title": "Czat" } @@ -1979,7 +2013,9 @@ "inaccessible_subtitle_1": "Spróbuj ponownie później lub spytaj się administratora przestrzeni, aby sprawdził czy masz dostęp.", "inaccessible_subtitle_2": "Wystąpił błąd %(errcode)s podczas próby dostępu do pokoju lub przestrzeni. Jeśli widzisz tą wiadomość w błędzie, zgłoś ten błąd.", "intro": { + "display_topic": "Temat: ", "dm_caption": "Tylko Wy jesteście w tej konwersacji, dopóki ktoś z Was nie zaprosi tu innej osoby.", + "edit_topic": "Temat: (edytuj)", "enable_encryption_prompt": "Włącz szyfrowanie w ustawieniach.", "encrypted_3pid_dm_pending_join": "Kiedy wszyscy dołączą, będziesz mógł rozmawiać", "no_avatar_label": "Dodaj zdjęcie, aby inni mogli łatwo zauważyć Twój pokój.", @@ -2043,7 +2079,9 @@ "pinned_message_banner": { "button_close_list": "Zamknij listę", "button_view_all": "Pokaż wszystkie", - "description": "Ten pokój ma przypięte wiadomości. Kliknij, aby je wyświetlić.", + "description": "Przypięte wiadomości", + "go_to_newest_message": "Zobacz przypiętą wiadomość na osi czasu i najnowszą przypiętą wiadomość tutaj", + "go_to_next_message": "Zobacz przypiętą wiadomość na osi czasu i najstarszą przypiętą wiadomość tutaj", "title": "%(index)s z %(length)s przypiętych wiadomości" }, "read_topic": "Kliknij, aby przeczytać temat", @@ -2153,6 +2191,26 @@ "one": "Aktualnie usuwanie wiadomości z %(count)s pokoju", "other": "Aktualnie usuwanie wiadomości z %(count)s pokoi" }, + "release_announcement": { + "done": "Gotowe", + "filter": { + "description": "Filtruj swoje czaty jednym kliknięciem. Rozwiń, aby zobaczyć więcej filtrów.", + "title": "Nowe szybkie filtry" + }, + "intro": { + "description": "Lista czatów została zaktualizowana, aby była bardziej przejrzysta i łatwiejsza w użyciu.", + "title": "Czaty dostały nowy wygląd!" + }, + "next": "Dalej", + "settings": { + "description": "Aby wyświetlić lub ukryć podglądy wiadomości, przejdź do Wszystkie ustawienia > Preferencje > Lista pokoi", + "title": "Niektóre ustawienia zostały przeniesione" + }, + "sort": { + "description": "Zmień kolejność swoich czatów od najnowszych do A-Z", + "title": "Sortuj swoje czaty" + } + }, "room": { "more_options": "Więcej opcji", "open_room": "Pokój otwarty %(roomName)s" @@ -2343,6 +2401,10 @@ "users_default": "Domyślna rola" }, "security": { + "cannot_change_to_private_due_to_missing_history_visiblity_permissions": { + "description": "Nie masz uprawnień do zmiany widoczności historii pokoju. Jest to niebezpieczne, ponieważ niezaproszeni użytkownicy mogliby czytać historię wiadomości.", + "title": "Nie można ustawić pokoju jako prywatnego" + }, "enable_encryption_confirm_description": "Po włączeniu, szyfrowania pokoju nie będzie się dało wyłączyć. Wiadomości wysłane w pokoju szyfrowanym nie mogą zostać odczytane przez serwer, tylko wyłącznie przez uczestników pokoju. Włączenie szyfrowania może uniemożliwić wielu botom i mostkom działanie. Dowiedz się więcej o szyfrowaniu.", "enable_encryption_confirm_title": "Włączyć szyfrowanie?", "enable_encryption_public_room_confirm_description_1": "Nie zaleca się dodawania szyfrowania do pokojów publicznych. Każdy może znaleźć Twój pokój, więc jest w stanie czytać wszystkie zawarte w nim wiadomości. Nie uzyskasz żadnych benefitów szyfrowania, a tej zmiany nie będzie można cofnąć. Szyfrowanie wiadomości w pokoju publicznym sprawi, że wysyłanie i odbieranie wiadomości będzie wolniejsze.", @@ -2360,7 +2422,7 @@ "history_visibility_joined": "Tylko członkowie (od kiedy dołączyli)", "history_visibility_legend": "Kto może czytać historię?", "history_visibility_shared": "Tylko członkowie (od momentu włączenia tej opcji)", - "history_visibility_warning": "Zmiany tego, kto może przeglądać historię wyszukiwania dotyczą tylko przyszłych wiadomości w pokoju. Widoczność wcześniejszej historii nie zmieni się.", + "history_visibility_warning": "Widoczność istniejącej historii nie ulegnie zmianie.", "history_visibility_world_readable": "Każdy", "join_rule_description": "Decyduj kto może dołączyć do %(roomName)s.", "join_rule_invite": "Prywatny (tylko na zaproszenie)", @@ -2403,6 +2465,7 @@ "other": "Aktualizowanie przestrzeni... (%(progress)s z %(count)s)" }, "join_rule_upgrade_upgrading_room": "Aktualizowanie pokoju", + "join_rule_world_readable_description": "Zmiana osób, które mogą dołączyć do pokoju, wpłynie również na widoczność przyszłych wiadomości.", "public_without_alias_warning": "Aby powiązać ten pokój, dodaj adres.", "publish_room": "Uczyń ten pokój widocznym w katalogu pokoi publicznych.", "publish_space": "Uczyń tę przestrzeń widoczną w katalogu pomieszczeń publicznych.", @@ -2541,6 +2604,7 @@ "breadcrumb_second_description": "Stracisz całą historię wiadomości przechowywaną na serwerze", "breadcrumb_third_description": "Konieczne będzie ponowne zweryfikowanie wszystkich istniejących urządzeń i kontaktów", "breadcrumb_title": "Czy na pewno chcesz zresetować swoją tożsamość?", + "breadcrumb_title_cant_confirm": "Musisz zresetować swoją tożsamość", "breadcrumb_title_forgot": "Nie pamiętasz klucza przywracania? Musisz zresetować swoją tożsamość.", "breadcrumb_title_sync_failed": "Nie udało się zsynchronizować magazynu kluczy. Zresetuj swoją tożsamość.", "breadcrumb_warning": "Zrób to tylko wtedy, gdy uważasz, że Twoje konto zostało naruszone.", @@ -2617,6 +2681,7 @@ "allow_spellcheck": "Zezwól na sprawdzanie pisowni", "application_language": "Język aplikacji", "application_language_reload_hint": "Aplikacja włączy się ponownie po zmianie języka", + "avatar_open_menu": "Otwórz menu awatara", "avatar_remove_progress": "Usuwanie obrazu...", "avatar_save_progress": "Przesyłanie obrazu...", "avatar_upload_error_text": "Format pliku nie jest obsługiwany lub obraz jest większy niż %(size)s.", @@ -2813,6 +2878,10 @@ "rule_suppress_notices": "Wiadomości wysłane przez bota", "rule_tombstone": "Kiedy pokoje są uaktualniane", "show_message_desktop_notification": "Pokaż wiadomość w powiadomieniu na pulpicie", + "sounds_release_announcement": { + "description": "Twoje powiadomienia dźwiękowe i dzwonek telefonu zostały zaktualizowane — są teraz wyraźniejsze, szybsze i mniej uciążliwe", + "title": "Odświeżyliśmy Twoje dzwonki" + }, "voip": "Połączenia audio i wideo" }, "preferences": { @@ -2839,6 +2908,7 @@ "room_list_heading": "Lista pokojów", "show_avatars_pills": "Pokaż awatary w wzmiankach użytkownika, pokoju i wydarzenia", "show_polls_button": "Pokaż przycisk ankiet", + "startup_window_behaviour_label": "Uruchamianie i zachowanie okna", "surround_text": "Otocz zaznaczony tekst podczas wpisywania specjalnych znaków", "time_heading": "Wyświetlanie czasu", "user_timezone": "Ustaw strefę czasową" @@ -3012,7 +3082,12 @@ "spaces_explainer": "Przestrzenie to sposób grupowania pokoi i osób. Oprócz przestrzeni, w których się znajdujesz, możesz również korzystać z już wstępnie zbudowanych.", "title": "Pasek boczny" }, - "start_automatically": "Uruchom automatycznie po zalogowaniu się do systemu", + "start_automatically": { + "disabled": "Nie", + "enabled": "Tak", + "label": "Otwórz %(brand)s po zalogowaniu do komputera", + "minimised": "Zminimalizowany" + }, "tac_only_notifications": "Pokaż powiadomienia tylko w centrum aktywności wątków", "use_12_hour_format": "Pokaż czas w formacie 12-sto godzinnym (np. 2:30pm)", "use_command_enter_send_message": "Użyj Command + Enter, aby wysłać wiadomość", @@ -3380,6 +3455,7 @@ "unable_to_find": "Próbowano załadować konkretny punkt na osi czasu w tym pokoju, ale nie można go znaleźć." }, "m.audio": { + "audio_player": "Odtwarzacz audio", "error_downloading_audio": "Wystąpił błąd w trakcie pobierania audio", "error_processing_audio": "Wystąpił błąd procesowania wiadomości audio", "error_processing_voice_message": "Wystąpił błąd procesowania wiadomości głosowej", @@ -3947,6 +4023,7 @@ "connection_lost": "Połączenie z serwerem zostało przerwane", "connection_lost_description": "Nie możesz wykonywać rozmów bez połączenia z serwerem.", "consulting": "Konsultowanie z %(transferTarget)s. Transfer do %(transferee)s", + "decline_call": "Odrzuć", "default_device": "Urządzenie domyślne", "dial": "Wybierz numer", "dialpad": "Klawiatura telefoniczna", @@ -3999,6 +4076,7 @@ "show_sidebar_button": "Pokaż pasek boczny", "silence": "Wycisz rozmowę", "silenced": "Wyciszono powiadomienia", + "skip_lobby_toggle_option": "Dołącz teraz", "start_screenshare": "Udostępnij ekran", "stop_screenshare": "Przestań udostępniać ekran", "too_many_calls": "Zbyt wiele połączeń", diff --git a/src/i18n/strings/pt.json b/src/i18n/strings/pt.json index 219abebe18..10dea8e235 100644 --- a/src/i18n/strings/pt.json +++ b/src/i18n/strings/pt.json @@ -2893,7 +2893,6 @@ "spaces_explainer": "Os espaços são formas de agrupar divisões e pessoas. Para além dos espaços em que te encontras, também podes utilizar alguns espaços pré-construídos.", "title": "Barra lateral" }, - "start_automatically": "Iniciar automaticamente ao iniciar o sistema", "tac_only_notifications": "Mostra apenas as notificações no centro de actividades do tópico", "use_12_hour_format": "Mostrar os horários em formato de 12h (p.ex: 2:30pm)", "use_command_enter_send_message": "Utiliza Command + Enter para enviar uma mensagem", diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 09e025bd31..92ff88d304 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -3043,7 +3043,6 @@ "spaces_explainer": "Os espaços são formas de agrupar salas e pessoas. Além dos espaços em que você está, você também pode usar alguns pré-construídos.", "title": "Barra lateral" }, - "start_automatically": "Iniciar automaticamente ao iniciar o sistema", "tac_only_notifications": "Mostrar apenas notificações no centro de atividades do tópico", "use_12_hour_format": "Mostrar os horários em formato de 12h (p.ex: 2:30pm)", "use_command_enter_send_message": "Usar Command + Enter para enviar uma mensagem", diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index d8bfde4fc9..6cfea98ccf 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -646,16 +646,17 @@ "mode_plain": "Скрыть форматирование", "mode_rich_text": "Показать форматирование", "no_perms_notice": "Вы не можете писать в эту комнату", - "placeholder": "Отправить сообщение…", + "placeholder": "Отправить незашифрованное сообщение...", "placeholder_encrypted": "Отправить зашифрованное сообщение…", - "placeholder_reply": "Отправить ответ…", - "placeholder_reply_encrypted": "Отправить зашифрованный ответ…", + "placeholder_reply": "Отправить незашифрованный ответ…", + "placeholder_reply_encrypted": "Отправить ответ…", "placeholder_thread": "Ответить на обсуждение…", "placeholder_thread_encrypted": "Ответить на зашифрованное обсуждение…", "poll_button": "Опрос", "poll_button_no_perms_description": "У вас нет разрешения начинать опросы в этой комнате.", "poll_button_no_perms_title": "Требуется разрешение", "replying_title": "Отвечает", + "room_unencrypted": "Сообщения в этой комнате не защищены сквозным шифрованием.", "room_upgraded_link": "Разговор продолжается здесь.", "room_upgraded_notice": "Эта комната заменена и более неактивна.", "send_button_title": "Отправить сообщение", @@ -719,6 +720,7 @@ "personal_space_description": "Приватное пространство для организации ваших комнат", "private_description": "Только по приглашениям, лучший вариант для себя или команды", "private_heading": "Ваше приватное пространство", + "private_only_heading": "Ваше пространство", "private_personal_description": "Убедитесь, что правильные люди имеют доступ к %(name)s", "private_personal_heading": "С кем вы работаете?", "private_space": "Я и мои товарищи по команде", @@ -865,6 +867,7 @@ "elementCallUrl": "URL-адрес Element Call" }, "settings_explorer": "Посмотреть настройки", + "show_empty_content_events": "Показывать события с пустым содержимым", "show_hidden_events": "Показывать скрытые события в ленте сообщений", "spaces": { "one": "", @@ -923,7 +926,8 @@ }, "privacy_warning": "Убедитесь, что никто не видит этот экран!", "restoring": "Восстановление ключей из резервной копии", - "security_key_title": "Ключ восстановления" + "security_key_label": "Ключ восстановления", + "security_key_title": "Введите ключ восстановления" }, "bootstrap_title": "Настройка ключей", "confirm_encryption_setup_body": "Нажмите кнопку ниже, чтобы подтвердить настройку шифрования.", @@ -936,6 +940,7 @@ "cross_signing_user_warning": "Этот пользователь не подтвердил все свои сеансы.", "enter_recovery_key": "Введите ключ восстановления", "event_shield_reason_authenticity_not_guaranteed": "Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве.", + "event_shield_reason_mismatched_sender": "Отправитель события и владелец устройства не совпадают.", "event_shield_reason_mismatched_sender_key": "Зашифровано неподтверждённым сеансом", "event_shield_reason_unknown_device": "Зашифровано неизвестным или удаленным устройством.", "event_shield_reason_unsigned_device": "Зашифровано устройством, не проверенным владельцем.", @@ -992,10 +997,15 @@ "skip_verification": "Пока пропустить проверку", "verify_this_device": "Заверьте этот сеанс" }, + "cancelled_verification": "Время ожидания подтверждения истекло, запрос был отклонён, или при подтверждении произошло несоответствие.", "cancelling": "Отмена…", + "cant_confirm": "Не можете подтвердить?", "complete_action": "Понятно", "complete_description": "Вы успешно подтвердили этого пользователя.", "complete_title": "Верифицировано!", + "confirm_identity_description": "Подтвердите это устройство, чтобы настроить безопасный обмен сообщениями.", + "confirm_identity_title": "Подтвердите свою личность", + "confirm_the_emojis": "Убедитесь, что приведенные ниже эмодзи совпадают с эмодзи показанными на другом устройстве.", "error_starting_description": "Нам не удалось начать чат с другим пользователем.", "error_starting_title": "Ошибка при запуске подтверждения", "explainer": "Сообщения с этим пользователем защищены сквозным шифрованием и недоступны третьим лицам.", @@ -1021,15 +1031,21 @@ "text": "Предоставьте идентификатор и отпечаток пальца одного из ваших устройств, чтобы подтвердить это. ПРИМЕЧАНИЕ. Это позволяет другому устройству отправлять и получать сообщения так же, как и вы. ЕСЛИ КТО-ТО СКАЗАЛ ВАМ ЧТО-ТО ВСТАВИТЬ СЮДА, СКОРЕЕ ВСЕГО, ВАС ОБМАНУЛИ!" }, "no_support_qr_emoji": "Устройство, которое вы пытаетесь проверить, не поддерживает сканирование QR-кода или проверку смайликов, которые поддерживает %(brand)s. Попробуйте использовать другой клиент.", + "now_you_can": "Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству.", + "once_accepted_can_continue": "После одобрения вы сможете продолжить проверку.", "other_party_cancelled": "Другая сторона отменила проверку.", "prompt_encrypted": "Подтвердите всех пользователей в комнате, чтобы обеспечить безопасность.", "prompt_unencrypted": "В зашифрованных комнатах, проверьте всех пользователей, чтобы убедиться в их безопасности.", "qr_or_sas": "%(qrCode)s или %(emojiCompare)s", "qr_prompt": "Отсканируйте этот уникальный код", + "qr_reciprocate_check_again_device": "Повторите попытку на другом устройстве, чтобы завершить проверку.", + "qr_reciprocate_no": "Нет, я не вижу зеленый щит", "qr_reciprocate_same_shield_user": "Почти готово! Отображает ли %(displayName)s такой же щит?", + "qr_reciprocate_yes": "Да, я вижу зеленый щит", "request_toast_accept_user": "Подтвердить пользователя", "request_toast_decline_counter": "Игнорировать (%(counter)s)", "request_toast_detail": "%(deviceId)s с %(ip)s", + "request_toast_start_verification": "Начать проверку", "sas_caption_self": "Проверьте это устройство, убедившись, что на его экране отображается следующее число.", "sas_caption_user": "Подтвердите пользователя, убедившись, что на его экране отображается следующее число.", "sas_description": "Сравните уникальный набор смайликов, если у вас нет камеры ни на одном из устройств", @@ -1047,11 +1063,20 @@ "unverified_sessions_toast_description": "Проверьте, чтобы убедиться, что ваша учётная запись в безопасности", "unverified_sessions_toast_reject": "Позже", "unverified_sessions_toast_title": "У вас есть незаверенные сеансы", + "use_another_device": "Используйте другое устройство", + "use_recovery_key": "Используйте recovery key", + "verification_dialog_title_choose": "Выберите способ проверки", + "verification_dialog_title_compare_emojis": "Сравните емодзи", + "verification_dialog_title_confirm_green_shield": "Убедитесь, что на другом устройстве вы видите зеленый щит.", "verification_dialog_title_device": "Проверить другое устройство", + "verification_dialog_title_failed": "Сбой проверки", + "verification_dialog_title_start_on_other_device": "Начать проверку на другом устройстве", "verification_dialog_title_user": "Запрос на сверку", + "verification_dialog_title_verified": "Устройство проверено", "verification_skip_warning": "Без проверки вы не сможете получить доступ ко всем своим сообщениям и можете показаться другим людям недоверенным.", "verification_success_with_backup": "Ваш новый сеанс заверен. Он имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут видеть его как заверенный.", "verification_success_without_backup": "Ваш новый сеанс заверен. Другие пользователи будут видеть его как заверенный.", + "verify_by_completing_one_of": "Подтвердите, выполнив одно из следующих действий:", "verify_emoji": "Подтверждение с помощью смайлов", "verify_emoji_prompt": "Подтверждение сравнением уникальных смайлов.", "verify_emoji_prompt_qr": "Если вы не можете отсканировать код выше, попробуйте сравнить уникальные смайлы.", @@ -1104,6 +1129,7 @@ "tls": "Не удается подключиться к домашнему серверу — проверьте подключение, убедитесь, что ваш SSL-сертификат домашнего сервера является доверенным и что расширение браузера не блокирует запросы.", "unknown": "Неизвестная ошибка", "unknown_error_code": "неизвестный код ошибки", + "update_history_visibility": "Не удалось изменить видимость истории.", "update_power_level": "Не удалось изменить уровень прав" }, "error_app_open_in_another_tab": "Переключитесь на другую вкладку, чтобы подключиться к %(brand)s . Теперь эту вкладку можно закрыть.", @@ -1344,6 +1370,9 @@ "name_email_mxid_share_space": "Пригласите кого-нибудь, используя их имя, адрес электронной почты, имя пользователя (например, ) или поделитесь этим пространством.", "name_mxid_share_room": "Пригласите кого-нибудь, используя его имя, имя пользователя (например, ) или поделитесь этой комнатой.", "name_mxid_share_space": "Пригласите кого-нибудь, используя их отображаемое имя или имя учётной записи (например, ) или поделитесь этим пространством.", + "progress": { + "preparing": "Подготовка приглашений..." + }, "recents_section": "Недавние Диалоги", "room_failed_partial": "Мы отправили остальных, но нижеперечисленные люди не могут быть приглашены в ", "room_failed_partial_title": "Некоторые приглашения не могут быть отправлены", @@ -1381,7 +1410,7 @@ "autocomplete_force": "Принудительно завершить", "autocomplete_navigate_next": "Следующее предложение автозаполнения", "autocomplete_navigate_prev": "Предыдущее предложение автозаполнения", - "backspace": "Очистить", + "backspace": "Backspace", "cancel_reply": "Отмена ответа на сообщение", "category_autocomplete": "Автодополнение", "category_calls": "Звонки", @@ -1431,6 +1460,7 @@ "room_list_navigate_down": "Перейти вниз в списке комнат", "room_list_navigate_up": "Перейти вверх в списке комнат", "room_list_select_room": "Выбрать комнату из списка комнат", + "save": "Сохранить", "scroll_down_timeline": "Листать ленту сообщений вниз", "scroll_up_timeline": "Листать временную шкалу вверх", "search": "Поиск (должен быть включен)", @@ -1634,6 +1664,7 @@ "filter_placeholder": "Поиск по участникам", "invite_button_no_perms_tooltip": "У вас нет разрешения приглашать пользователей", "invited_label": "Приглашены", + "list_title": "Список участников", "no_matches": "Нет совпадений" }, "member_list_back_action_label": "Участники комнаты", @@ -1738,6 +1769,7 @@ }, "power_level": { "admin": "Администратор", + "creator": "Владелец", "custom": "Пользовательский (%(level)s)", "custom_level": "Специальные права", "default": "По умолчанию", @@ -1888,6 +1920,7 @@ "thread_list": { "context_menu_label": "Параметры обсуждения" }, + "title": "Правая панель", "video_room_chat": { "title": "Чат" } @@ -2143,6 +2176,9 @@ "one": "Удаляются сообщения в %(count)s комнате", "other": "Удаляются сообщения в %(count)s комнатах" }, + "release_announcement": { + "next": "Далее" + }, "room": { "more_options": "Дополнительные параметры", "open_room": "Открыть комнату %(roomName)s" @@ -2990,7 +3026,6 @@ "spaces_explainer": "Пространства — это способ сгруппировать комнаты и людей. Помимо пространств, в которых вы находитесь, вы также можете использовать готовые помещения.", "title": "Боковая панель" }, - "start_automatically": "Автозапуск при входе в систему", "tac_only_notifications": "Показывать уведомления только в центре активностей обсуждений", "use_12_hour_format": "Отображать время в 12 часовом формате (напр. 2:30pm)", "use_command_enter_send_message": "Cmd + Enter, чтобы отправить сообщение", diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index 985a7396b6..4a506e6265 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -3046,7 +3046,6 @@ "spaces_explainer": "Priestory sú spôsobom zoskupovania miestností a osôb. Popri priestoroch, v ktorých sa nachádzate, môžete použiť aj niektoré predpripravené.", "title": "Bočný panel" }, - "start_automatically": "Spustiť automaticky po prihlásení do systému", "tac_only_notifications": "Zobraziť upozornenia iba v centre aktivít vlákna", "use_12_hour_format": "Pri zobrazovaní časových značiek používať 12 hodinový formát (napr. 2:30pm)", "use_command_enter_send_message": "Použite Command + Enter na odoslanie správy", diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 47ae1adb59..c649c6022b 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2384,7 +2384,6 @@ "metaspaces_subsection": "Hapësira për shfaqje", "title": "Anështyllë" }, - "start_automatically": "Nisu vetvetiu pas hyrjes në sistem", "use_12_hour_format": "Vulat kohore shfaqi në formatin 12 orësh (p.sh. 2:30pm)", "use_command_enter_send_message": "Që të dërgoni një mesazh, përdorni tastet Command + Enter", "use_command_f_search": "Përdorni Command + F që të kërkohet te rrjedha kohore", diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index f8ca960df8..ed4e04c190 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -113,7 +113,7 @@ "rename": "Döp om", "reply": "Svara", "reply_in_thread": "Svara i tråd", - "report_content": "Rapportera innehåll", + "report_content": "Anmäl innehåll", "report_room": "Anmäl rum", "resend": "Skicka igen", "reset": "Återställ", @@ -123,7 +123,7 @@ "revoke": "Återkalla", "save": "Spara", "search": "Sök", - "send_report": "Skicka rapport", + "send_report": "Skicka anmälan", "set_avatar": "Ställ in profilbild", "share": "Dela", "show": "Visa", @@ -2702,8 +2702,8 @@ "enter_phrase_description": "Ange en säkerhetsfras som bara du känner till, eftersom den används för att säkra din data. För att vara säker, bör du inte återanvända ditt kontolösenord.", "enter_phrase_title": "Ange en säkerhetsfras", "enter_phrase_to_confirm": "Ange din säkerhetsfras igen för att bekräfta den.", - "generate_security_key_description": "Vi kommer att generera en säkerhetsnyckel så du kan lagra någonstans säkert, som en lösenordshanterare eller ett kassaskåp.", - "generate_security_key_title": "Generera en säkerhetsnyckel", + "generate_security_key_description": "Vi kommer att generera en återställningsnyckel så du kan lagra någonstans säkert, som en lösenordshanterare eller ett kassaskåp.", + "generate_security_key_title": "Generera en återställningsnyckel", "pass_phrase_match_failed": "Det matchar inte.", "pass_phrase_match_success": "Det matchar!", "phrase_strong_enough": "Fantastiskt! Den här säkerhetsfrasen ser tillräckligt stark ut.", @@ -2716,7 +2716,7 @@ "title_set_phrase": "Sätt en säkerhetsfras", "unable_to_setup": "Kunde inte sätta upp hemlig lagring", "use_different_passphrase": "Använd en annan lösenfras?", - "use_phrase_only_you_know": "Använd en hemlig fras endast du känner till, och spara valfritt en säkerhetsnyckel att använda för säkerhetskopiering." + "use_phrase_only_you_know": "Använd en hemlig fras endast du känner till, och spara valfritt en återställningsnyckel att använda för säkerhetskopiering." } }, "key_export_import": { @@ -2872,7 +2872,7 @@ "message_search_unsupported_web": "%(brand)s kan inte säkert cacha krypterade meddelanden lokalt när den kör i en webbläsare. Använd %(brand)s Skrivbord för att krypterade meddelanden ska visas i sökresultaten.", "record_session_details": "Spara klientens namn, version och URL för att lättare känna igen sessioner i sessionshanteraren", "send_analytics": "Skicka statistik", - "strict_encryption": "Skicka aldrig krypterade meddelanden till overifierade sessioner från den här sessionen" + "strict_encryption": "Skicka bara meddelanden till verifierade användare" }, "send_read_receipts": "Skicka läskvitton", "send_read_receipts_unsupported": "Din server stöder inte inaktivering av läskvitton.", @@ -3005,7 +3005,6 @@ "spaces_explainer": "Utrymmen är sätt att gruppera rum och personer. Vid sidan av de utrymmen du befinner dig i kan du också använda några förbyggda.", "title": "Sidofält" }, - "start_automatically": "Starta automatiskt vid systeminloggning", "tac_only_notifications": "Visa endast notiser i aktivitetscentret för trådar", "use_12_hour_format": "Visa tidsstämplar i 12-timmarsformat (t.ex. 2:30em)", "use_command_enter_send_message": "Använd Kommando + Enter för att skicka ett meddelande", diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json index 90fdb2e7a2..145c3ce1ca 100644 --- a/src/i18n/strings/tr.json +++ b/src/i18n/strings/tr.json @@ -2863,7 +2863,6 @@ "spaces_explainer": "Alanlar, odaları ve kişileri gruplamanın bir yoludur. İçinde bulunduğunuz alanların yanı sıra, önceden oluşturulmuş bazı alanları da kullanabilirsiniz.", "title": "Kenar çubuğu" }, - "start_automatically": "Sisteme giriş yaptıktan sonra otomatik başlat", "tac_only_notifications": "Bildirimleri yalnızca mesaj dizisi etkinlik merkezinde göster", "use_12_hour_format": "Zaman damgalarını 12 biçiminde göster (örn. 2:30 pm)", "use_command_enter_send_message": "Mesaj göndermek için Command + Enter tuşlarını kullanın", diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 3a3d3f4843..5a927b7cca 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -3037,7 +3037,6 @@ "spaces_explainer": "Простір це спосіб групування кімнат та людей. Окрім просторів, які ви створюєте, ви також можете використовувати вже готові.", "title": "Бічна панель" }, - "start_automatically": "Автозапуск при вході в систему", "tac_only_notifications": "Показувати сповіщення лише в центрі діяльності в гілках", "use_12_hour_format": "Показувати час у 12-годинному форматі (напр. 2:30 пп)", "use_command_enter_send_message": "Натисніть Command + Enter, щоб надіслати повідомлення", diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json index fb59d359c3..42e383139f 100644 --- a/src/i18n/strings/vi.json +++ b/src/i18n/strings/vi.json @@ -2337,7 +2337,6 @@ "metaspaces_subsection": "Space để hiển thị", "title": "Thanh bên" }, - "start_automatically": "Tự động khởi động sau khi đăng nhập hệ thống", "use_12_hour_format": "Hiển thị thời gian theo mẫu 12 giờ (ví dụ 2:30pm)", "use_command_enter_send_message": "Sử dụng Command + Enter để gửi tin nhắn", "use_command_f_search": "Sử dụng Command + F để tìm kiếm dòng thời gian", diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index fa7b8bd074..28ea5f09e8 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -2251,7 +2251,6 @@ "metaspaces_subsection": "要显示的空间", "title": "侧边栏" }, - "start_automatically": "开机自启", "use_12_hour_format": "使用 12 小时制显示时间戳 (下午 2:30)", "use_command_enter_send_message": "使用 Command + Enter 发送消息", "use_command_f_search": "使用 Command + F 搜索时间线", diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 2a08f3fb41..96ea50b06d 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2494,7 +2494,6 @@ "spaces_explainer": "空間是將房間和人群分組的方式。除了您所在的空間之外,您也可以使用一些預先構建的空間。", "title": "側邊欄" }, - "start_automatically": "在系統登入後自動開始", "use_12_hour_format": "用 12 小時制顯示時間戳記(如:下午 2:30)", "use_command_enter_send_message": "使用 Command + Enter 來傳送訊息", "use_command_f_search": "使用 Command + F 來搜尋時間軸", diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 17a8432c27..569db2d360 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -25,7 +25,7 @@ import { type IVariables, KEY_SEPARATOR, getLangsJson, -} from "./shared-components/utils/i18n"; +} from "../packages/shared-components/src/utils/i18n"; export { _t, @@ -40,7 +40,7 @@ export { normalizeLanguageKey, getNormalizedLanguageKeys, substitute, -} from "./shared-components/utils/i18n"; +} from "../packages/shared-components/src/utils/i18n"; const i18nFolder = "i18n/"; diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index dfb869c31c..f390544abe 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -12,7 +12,7 @@ import { STABLE_MSC4133_EXTENDED_PROFILES, UNSTABLE_MSC4133_EXTENDED_PROFILES } import { type MediaPreviewConfig } from "../@types/media_preview.ts"; // Import i18n.tsx instead of languageHandler to avoid circular deps -import { _t, _td, type TranslationKey } from "../shared-components/utils/i18n"; +import { _t, _td, type TranslationKey } from "../../packages/shared-components/src/utils/i18n"; import DeviceIsolationModeController from "./controllers/DeviceIsolationModeController.ts"; import { NotificationBodyEnabledController, @@ -179,6 +179,14 @@ export interface IBaseSetting { * Whether the setting should be exported in a rageshake report. */ shouldExportToRageshake?: boolean; + + /** + * Options array for a setting controlled by a dropdown. + */ + options?: { + value: T; + label: TranslationKey; + }[]; } export interface IFeature extends Omit, "isFeature"> { @@ -353,7 +361,7 @@ export interface Settings { "videoInputMuted": IBaseSetting; "activeCallRoomIds": IBaseSetting; "releaseAnnouncementData": IBaseSetting; - "Electron.autoLaunch": IBaseSetting; + "Electron.autoLaunch": IBaseSetting<"enabled" | "minimised" | "disabled">; "Electron.warnBeforeExit": IBaseSetting; "Electron.alwaysShowMenuBar": IBaseSetting; "Electron.showTrayIcon": IBaseSetting; @@ -1438,8 +1446,13 @@ export const SETTINGS: Settings = { // We store them over there are they are necessary to know before the renderer process launches. "Electron.autoLaunch": { supportedLevels: [SettingLevel.PLATFORM], - displayName: _td("settings|start_automatically"), - default: false, + displayName: _td("settings|start_automatically|label"), + options: [ + { value: "enabled", label: _td("settings|start_automatically|enabled") }, + { value: "disabled", label: _td("settings|start_automatically|disabled") }, + { value: "minimised", label: _td("settings|start_automatically|minimised") }, + ], + default: "disabled", }, "Electron.warnBeforeExit": { supportedLevels: [SettingLevel.PLATFORM], diff --git a/src/settings/handlers/PlatformSettingsHandler.ts b/src/settings/handlers/PlatformSettingsHandler.ts index 48420650b1..4741f35530 100644 --- a/src/settings/handlers/PlatformSettingsHandler.ts +++ b/src/settings/handlers/PlatformSettingsHandler.ts @@ -10,9 +10,6 @@ import SettingsHandler from "./SettingsHandler"; import PlatformPeg from "../../PlatformPeg"; import { SETTINGS } from "../Settings"; import { SettingLevel } from "../SettingLevel"; -import defaultDispatcher from "../../dispatcher/dispatcher"; -import { type ActionPayload } from "../../dispatcher/payloads"; -import { Action } from "../../dispatcher/actions"; /** * Gets and sets settings at the "platform" level for the current device. @@ -24,22 +21,22 @@ export default class PlatformSettingsHandler extends SettingsHandler { public constructor() { super(); - defaultDispatcher.register(this.onAction); + void this.setup(); } - private onAction = (payload: ActionPayload): void => { - if (payload.action === Action.PlatformSet) { - this.store = {}; - // Load setting values as they are async and `getValue` must be synchronous - Object.entries(SETTINGS).forEach(([key, setting]) => { - if (setting.supportedLevels?.includes(SettingLevel.PLATFORM) && payload.platform.supportsSetting(key)) { - payload.platform.getSettingValue(key).then((value: any) => { - this.store[key] = value; - }); - } - }); - } - }; + private async setup(): Promise { + const platform = await PlatformPeg.platformPromise; + await platform.initialised; + + // Load setting values as they are async and `getValue` must be synchronous + Object.entries(SETTINGS).forEach(([key, setting]) => { + if (setting.supportedLevels?.includes(SettingLevel.PLATFORM) && platform.supportsSetting(key)) { + platform.getSettingValue(key).then((value: any) => { + this.store[key] = value; + }); + } + }); + } public canSetValue(settingName: string, roomId: string): boolean { return PlatformPeg.get()?.supportsSetting(settingName) ?? false; diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index bb1c8161bc..990de130ad 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -14,7 +14,7 @@ import { type IWidget } from "matrix-widget-api"; import SettingsStore from "../../settings/SettingsStore"; import WidgetStore, { type IApp } from "../WidgetStore"; import { WidgetType } from "../../widgets/WidgetType"; -import { clamp, defaultNumber, sum } from "../../shared-components/utils/numbers"; +import { clamp, defaultNumber, sum } from "../../../packages/shared-components/src/utils/numbers"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { ReadyWatchingStore } from "../ReadyWatchingStore"; import { SettingLevel } from "../../settings/SettingLevel"; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index b5f426986e..18fd846fd2 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -35,7 +35,7 @@ import { type Call, CallEvent } from "../models/Call"; import LegacyCallHandler, { AudioID } from "../LegacyCallHandler"; import { useEventEmitter } from "../hooks/useEventEmitter"; import { CallStore, CallStoreEvent } from "../stores/CallStore"; -import { AvatarWithDetails } from "../shared-components/avatar/AvatarWithDetails"; +import { AvatarWithDetails } from "../../packages/shared-components/src/avatar/AvatarWithDetails"; /** * Get the key for the incoming call toast. A combination of the event ID and room ID. diff --git a/src/utils/FormattingUtils.ts b/src/utils/FormattingUtils.ts index c18600280c..0e10136438 100644 --- a/src/utils/FormattingUtils.ts +++ b/src/utils/FormattingUtils.ts @@ -13,7 +13,7 @@ import { useIdColorHash } from "@vector-im/compound-web"; import { _t, getCurrentLanguage, getUserLanguage } from "../languageHandler"; import { jsxJoin } from "./ReactUtils"; -export { formatBytes } from "../shared-components/utils/FormattingUtils"; +export { formatBytes } from "../../packages/shared-components/src/utils/FormattingUtils"; const locale = getCurrentLanguage(); diff --git a/src/utils/Image.ts b/src/utils/Image.ts index ab7c67d477..99352e26a0 100644 --- a/src/utils/Image.ts +++ b/src/utils/Image.ts @@ -9,7 +9,6 @@ import { arrayHasDiff } from "./arrays"; export function mayBeAnimated(mimeType?: string): boolean { - // AVIF animation support at the time of writing is only available in Chrome hence not having `blobIsAnimated` check return ["image/gif", "image/webp", "image/png", "image/apng", "image/avif"].includes(mimeType!); } @@ -26,8 +25,28 @@ function arrayBufferReadStr(arr: ArrayBuffer, start: number, len: number): strin return String.fromCharCode.apply(null, Array.from(arrayBufferRead(arr, start, len))); } -export async function blobIsAnimated(mimeType: string | undefined, blob: Blob): Promise { - switch (mimeType) { +/** + * Check if a Blob contains an animated image. + * @param blob The Blob to check. + * @returns True if the image is animated, false if not, or undefined if it could not be determined. + */ +export async function blobIsAnimated(blob: Blob): Promise { + try { + // Try parse the image using ImageDecoder as this is the most coherent way of asserting whether a piece of media + // is or is not animated. Limited availability at time of writing, notably Safari lacks support. + // https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoder + const data = await blob.arrayBuffer(); + const decoder = new ImageDecoder({ data, type: blob.type }); + await decoder.tracks.ready; + if ([...decoder.tracks].some((track) => track.animated)) { + return true; + } + } catch (e) { + console.warn("ImageDecoder not supported or failed to decode image", e); + // Not supported by this browser, fall through to manual checks + } + + switch (blob.type) { case "image/webp": { // Only extended file format WEBP images support animation, so grab the expected data range and verify header. // Based on https://developers.google.com/speed/webp/docs/riff_container#extended_file_format @@ -42,7 +61,7 @@ export async function blobIsAnimated(mimeType: string | undefined, blob: Blob): const animationFlagMask = 1 << 1; return (flags & animationFlagMask) != 0; } - break; + return false; } case "image/gif": { @@ -100,9 +119,7 @@ export async function blobIsAnimated(mimeType: string | undefined, blob: Blob): } i += length + 4; } - break; + return false; } } - - return false; } diff --git a/src/utils/MediaEventHelper.ts b/src/utils/MediaEventHelper.ts index 075ef11c4e..308d85b2fb 100644 --- a/src/utils/MediaEventHelper.ts +++ b/src/utils/MediaEventHelper.ts @@ -72,18 +72,25 @@ export class MediaEventHelper implements IDestroyable { }; private fetchSource = (): Promise => { + const content = this.event.getContent(); if (this.media.isEncrypted) { - const content = this.event.getContent(); return decryptFile(content.file!, content.info); } - return this.media.downloadSource().then((r) => r.blob()); + + return ( + this.media + .downloadSource() + .then((r) => r.blob()) + // Set the mime type from the event info on the blob + .then((blob) => blob.slice(0, blob.size, content.info?.mimetype ?? blob.type)) + ); }; private fetchThumbnail = (): Promise => { if (!this.media.hasThumbnail) return Promise.resolve(null); + const content = this.event.getContent(); if (this.media.isEncrypted) { - const content = this.event.getContent(); if (content.info?.thumbnail_file) { return decryptFile(content.info.thumbnail_file, content.info.thumbnail_info); } else { @@ -96,7 +103,12 @@ export class MediaEventHelper implements IDestroyable { const thumbnailHttp = this.media.thumbnailHttp; if (!thumbnailHttp) return Promise.resolve(null); - return fetch(thumbnailHttp).then((r) => r.blob()); + return ( + fetch(thumbnailHttp) + .then((r) => r.blob()) + // Set the mime type from the event info on the blob + .then((blob) => blob.slice(0, blob.size, content.info?.thumbnail_info?.mimetype ?? blob.type)) + ); }; public static isEligible(event: MatrixEvent): boolean { diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts index c87be4aee9..80896db71c 100644 --- a/src/utils/arrays.ts +++ b/src/utils/arrays.ts @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { percentageOf, percentageWithin } from "../shared-components/utils/numbers"; +import { percentageOf, percentageWithin } from "../../packages/shared-components/src/utils/numbers"; /** * Quickly resample an array to have less/more data points. If an input which is larger diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index 236703922c..0671801f09 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -374,11 +374,13 @@ export default class ElectronPlatform extends BasePlatform { return this.supportedSettings?.[settingName] === true; } - public getSettingValue(settingName: string): Promise { + public async getSettingValue(settingName: string): Promise { + await this.initialised; return this.electron.getSettingValue(settingName); } - public setSettingValue(settingName: string, value: any): Promise { + public async setSettingValue(settingName: string, value: any): Promise { + await this.initialised; return this.electron.setSettingValue(settingName, value); } diff --git a/src/viewmodels/audio/AudioPlayerViewModel.ts b/src/viewmodels/audio/AudioPlayerViewModel.ts index e028ba7860..025265b250 100644 --- a/src/viewmodels/audio/AudioPlayerViewModel.ts +++ b/src/viewmodels/audio/AudioPlayerViewModel.ts @@ -11,10 +11,10 @@ import { logger } from "matrix-js-sdk/src/logger"; import { type AudioPlayerViewSnapshot, type AudioPlayerViewModel as AudioPlayerViewModelInterface, -} from "../../shared-components/audio/AudioPlayerView"; +} from "../../../packages/shared-components/src/audio/AudioPlayerView"; import { type Playback } from "../../audio/Playback"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; -import { percentageOf } from "../../shared-components/utils/numbers"; +import { percentageOf } from "../../../packages/shared-components/src/utils/numbers"; import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { BaseViewModel } from "../base/BaseViewModel"; diff --git a/src/viewmodels/base/BaseViewModel.ts b/src/viewmodels/base/BaseViewModel.ts index a9ed6d2dc9..1bd58b9196 100644 --- a/src/viewmodels/base/BaseViewModel.ts +++ b/src/viewmodels/base/BaseViewModel.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type ViewModel } from "../../shared-components/ViewModel"; +import { type ViewModel } from "../../../packages/shared-components/src/ViewModel"; import { Disposables } from "./Disposables"; import { Snapshot } from "./Snapshot"; import { ViewModelSubscriptions } from "./ViewModelSubscriptions"; diff --git a/src/viewmodels/event-tiles/TextualEventViewModel.ts b/src/viewmodels/event-tiles/TextualEventViewModel.ts index dd8132e7ae..e887da82b0 100644 --- a/src/viewmodels/event-tiles/TextualEventViewModel.ts +++ b/src/viewmodels/event-tiles/TextualEventViewModel.ts @@ -10,7 +10,7 @@ import { MatrixEventEvent } from "matrix-js-sdk/src/matrix"; import { type EventTileTypeProps } from "../../events/EventTileFactory"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import { textForEvent } from "../../TextForEvent"; -import { type TextualEventViewSnapshot } from "../../shared-components/event-tiles/TextualEventView/TextualEventView"; +import { type TextualEventViewSnapshot } from "../../../packages/shared-components/src/event-tiles/TextualEventView/TextualEventView"; import { BaseViewModel } from "../base/BaseViewModel"; export class TextualEventViewModel extends BaseViewModel { diff --git a/test/test-utils/wrappers.tsx b/test/test-utils/wrappers.tsx index 7c4fd4b968..99ceae7f94 100644 --- a/test/test-utils/wrappers.tsx +++ b/test/test-utils/wrappers.tsx @@ -62,3 +62,13 @@ export function withClientContextRenderOptions(client: MatrixClient): RenderOpti ), }; } + +export function clientAndSDKContextRenderOptions(client: MatrixClient, sdkContext: SdkContextClass): RenderOptions { + return { + wrapper: ({ children }) => ( + + {children} + + ), + }; +} diff --git a/test/unit-tests/Image-test.ts b/test/unit-tests/Image-test.ts index 966236bc8a..1104788e6f 100644 --- a/test/unit-tests/Image-test.ts +++ b/test/unit-tests/Image-test.ts @@ -32,42 +32,55 @@ describe("Image", () => { describe("blobIsAnimated", () => { it("Animated GIF", async () => { - const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.gif"))]); - expect(await blobIsAnimated("image/gif", img)).toBeTruthy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.gif"))], { + type: "image/gif", + }); + expect(await blobIsAnimated(img)).toBeTruthy(); }); it("Static GIF", async () => { - const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.gif"))]); - expect(await blobIsAnimated("image/gif", img)).toBeFalsy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.gif"))], { + type: "image/gif", + }); + expect(await blobIsAnimated(img)).toBeFalsy(); }); it("Animated WEBP", async () => { - const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.webp"))]); - expect(await blobIsAnimated("image/webp", img)).toBeTruthy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.webp"))], { + type: "image/webp", + }); + expect(await blobIsAnimated(img)).toBeTruthy(); }); it("Static WEBP", async () => { - const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.webp"))]); - expect(await blobIsAnimated("image/webp", img)).toBeFalsy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.webp"))], { + type: "image/webp", + }); + expect(await blobIsAnimated(img)).toBeFalsy(); }); it("Static WEBP in extended file format", async () => { - const img = new Blob([ - fs.readFileSync(path.resolve(__dirname, "images", "static-logo-extended-file-format.webp")), - ]); - expect(await blobIsAnimated("image/webp", img)).toBeFalsy(); + const img = new Blob( + [fs.readFileSync(path.resolve(__dirname, "images", "static-logo-extended-file-format.webp"))], + { type: "image/webp" }, + ); + expect(await blobIsAnimated(img)).toBeFalsy(); }); it("Animated PNG", async () => { const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.apng"))]); - expect(await blobIsAnimated("image/png", img)).toBeTruthy(); - expect(await blobIsAnimated("image/apng", img)).toBeTruthy(); + const pngBlob = img.slice(0, img.size, "image/png"); + const apngBlob = img.slice(0, img.size, "image/apng"); + expect(await blobIsAnimated(pngBlob)).toBeTruthy(); + expect(await blobIsAnimated(apngBlob)).toBeTruthy(); }); it("Static PNG", async () => { const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.png"))]); - expect(await blobIsAnimated("image/png", img)).toBeFalsy(); - expect(await blobIsAnimated("image/apng", img)).toBeFalsy(); + const pngBlob = img.slice(0, img.size, "image/png"); + const apngBlob = img.slice(0, img.size, "image/apng"); + expect(await blobIsAnimated(pngBlob)).toBeFalsy(); + expect(await blobIsAnimated(apngBlob)).toBeFalsy(); }); }); }); diff --git a/test/unit-tests/audio/PlaybackQueue-test.ts b/test/unit-tests/audio/PlaybackQueue-test.ts new file mode 100644 index 0000000000..59e4cd9ab4 --- /dev/null +++ b/test/unit-tests/audio/PlaybackQueue-test.ts @@ -0,0 +1,87 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { type Mocked } from "jest-mock"; +import { type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix"; + +import { PlaybackQueue } from "../../../src/audio/PlaybackQueue"; +import { type Playback, PlaybackState } from "../../../src/audio/Playback"; +import { UPDATE_EVENT } from "../../../src/stores/AsyncStore"; +import { MockedPlayback } from "./MockedPlayback"; + +describe("PlaybackQueue", () => { + let playbackQueue: PlaybackQueue; + let mockRoom: Mocked; + + beforeEach(() => { + mockRoom = { + getMember: jest.fn(), + } as unknown as Mocked; + playbackQueue = new PlaybackQueue(mockRoom); + }); + + it.each([ + [PlaybackState.Playing, true], + [PlaybackState.Paused, true], + [PlaybackState.Preparing, false], + [PlaybackState.Decoding, false], + [PlaybackState.Stopped, false], + ])("should save (or not) the clock PlayBackState=%s expected=%s", (playbackState, expected) => { + const mockEvent = { + getId: jest.fn().mockReturnValue("$foo:bar"), + } as unknown as Mocked; + const mockPlayback = new MockedPlayback(playbackState, 0, 0) as unknown as Mocked; + + // Enqueue + playbackQueue.unsortedEnqueue(mockEvent, mockPlayback); + + // Emit our clockInfo of 0, which will playbackQueue to save the state. + mockPlayback.clockInfo.liveData.update([1]); + + // @ts-ignore + expect(playbackQueue.clockStates.has(mockEvent.getId()!)).toBe(expected); + }); + + it("does call skipTo on playback if clock advances to 1s", () => { + const mockEvent = { + getId: jest.fn().mockReturnValue("$foo:bar"), + } as unknown as Mocked; + const mockPlayback = new MockedPlayback(PlaybackState.Playing, 0, 0) as unknown as Mocked; + + // Enqueue + playbackQueue.unsortedEnqueue(mockEvent, mockPlayback); + + // Emit our clockInfo of 0, which will playbackQueue to save the state. + mockPlayback.clockInfo.liveData.update([1]); + + // Fire an update event to say that we have stopped. + // Note that Playback really emits an UPDATE_EVENT whenever state changes, the types are lies. + mockPlayback.emit(UPDATE_EVENT as any, PlaybackState.Stopped); + + expect(mockPlayback.skipTo).toHaveBeenCalledWith(1); + }); + + it("should ignore the nullish clock state when loading", () => { + const clockStates = new Map([ + ["a", 1], + ["b", null], + ["c", 3], + ]); + localStorage.setItem( + `mx_voice_message_clocks_${mockRoom.roomId}`, + JSON.stringify(Array.from(clockStates.entries())), + ); + playbackQueue = new PlaybackQueue(mockRoom); + + // @ts-ignore + expect(playbackQueue.clockStates.has("a")).toBe(true); + // @ts-ignore + expect(playbackQueue.clockStates.has("b")).toBe(false); + // @ts-ignore + expect(playbackQueue.clockStates.has("c")).toBe(true); + }); +}); diff --git a/test/unit-tests/components/structures/FilePanel-test.tsx b/test/unit-tests/components/structures/FilePanel-test.tsx index 0b9b18c38e..eab6725293 100644 --- a/test/unit-tests/components/structures/FilePanel-test.tsx +++ b/test/unit-tests/components/structures/FilePanel-test.tsx @@ -12,7 +12,6 @@ import { screen, render, waitFor } from "jest-matrix-react"; import { mocked } from "jest-mock"; import FilePanel from "../../../../src/components/structures/FilePanel"; -import ResizeNotifier from "../../../../src/utils/ResizeNotifier"; import { mkEvent, stubClient } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; @@ -39,9 +38,7 @@ describe("FilePanel", () => { room.getOrCreateFilteredTimelineSet = jest.fn().mockReturnValue(timelineSet); mocked(cli.getRoom).mockReturnValue(room); - const { asFragment } = render( - , - ); + const { asFragment } = render(); await waitFor(() => { expect(screen.getByText("No files visible in this room")).toBeInTheDocument(); }); @@ -64,7 +61,6 @@ describe("FilePanel", () => { { filePanel = ref; }} diff --git a/test/unit-tests/components/structures/LoggedInView-test.tsx b/test/unit-tests/components/structures/LoggedInView-test.tsx index dd88625509..1bbbd5b6d3 100644 --- a/test/unit-tests/components/structures/LoggedInView-test.tsx +++ b/test/unit-tests/components/structures/LoggedInView-test.tsx @@ -15,6 +15,8 @@ import { MatrixEvent, ClientEvent, PushRuleKind, + ProfileKeyTimezone, + ProfileKeyMSC4175Timezone, } from "matrix-js-sdk/src/matrix"; import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler"; import { logger } from "matrix-js-sdk/src/logger"; @@ -470,30 +472,36 @@ describe("", () => { it("does not update the timezone when userTimezonePublish is off", async () => { getComponent(); await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, false); - expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz"); + expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone); + expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone); expect(mockClient.setExtendedProfileProperty).not.toHaveBeenCalled(); }); it("should set the user timezone when userTimezonePublish is enabled", async () => { getComponent(); await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, true); - expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", userTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, userTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, userTimezone); }); it("should set the user timezone when the timezone is changed", async () => { const newTimezone = "Europe/Paris"; getComponent(); await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, true); - expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", userTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, userTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, userTimezone); await SettingsStore.setValue("userTimezone", null, SettingLevel.DEVICE, newTimezone); - expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", newTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, newTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, newTimezone); }); it("should clear the timezone when the publish feature is turned off", async () => { getComponent(); await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, true); - expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz", userTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone, userTimezone); + expect(mockClient.setExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone, userTimezone); await SettingsStore.setValue("userTimezonePublish", null, SettingLevel.DEVICE, false); - expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith("us.cloke.msc4175.tz"); + expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyTimezone); + expect(mockClient.deleteExtendedProfileProperty).toHaveBeenCalledWith(ProfileKeyMSC4175Timezone); }); }); diff --git a/test/unit-tests/components/structures/MainSplit-test.tsx b/test/unit-tests/components/structures/MainSplit-test.tsx index 2feb9b22f6..da3c29cd36 100644 --- a/test/unit-tests/components/structures/MainSplit-test.tsx +++ b/test/unit-tests/components/structures/MainSplit-test.tsx @@ -10,30 +10,26 @@ import React from "react"; import { render, fireEvent } from "jest-matrix-react"; import MainSplit from "../../../../src/components/structures/MainSplit"; -import ResizeNotifier from "../../../../src/utils/ResizeNotifier"; import { PosthogAnalytics } from "../../../../src/PosthogAnalytics.ts"; +import { SDKContext, SdkContextClass } from "../../../../src/contexts/SDKContext.ts"; describe("", () => { - const resizeNotifier = new ResizeNotifier(); const children = (
ChildFooBar
); const panel =
Right panel
; + let sdkContext: SdkContextClass; beforeEach(() => { localStorage.clear(); + sdkContext = new SdkContextClass(); }); it("renders", () => { const { asFragment, container } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); // Assert it matches the default width of 320 @@ -42,13 +38,7 @@ describe("", () => { it("respects defaultSize prop", () => { const { asFragment, container } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); // Assert it matches the default width of 350 @@ -59,7 +49,6 @@ describe("", () => { localStorage.setItem("mx_rhs_size_thread", "333"); const { container } = render( ", () => { it("should report to analytics on resize stop", () => { const { container } = render( , + { wrapper: ({ children }) => {children} }, ); const spy = jest.spyOn(PosthogAnalytics.instance, "trackEvent"); const handle = container.querySelector(".mx_ResizeHandle--horizontal")!; + expect(handle).toBeInTheDocument(); fireEvent.mouseDown(handle); fireEvent.resize(handle, { clientX: 0 }); fireEvent.mouseUp(handle); diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx index 3ebe826b1c..c772f6b743 100644 --- a/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -307,6 +307,30 @@ describe("", () => { }); }); + it("should notify resizenotifier when left panel hidden", async () => { + getComponent(); + + jest.spyOn(SdkContextClass.instance.resizeNotifier, "notifyLeftHandleResized"); + + defaultDispatcher.dispatch({ action: "hide_left_panel" }); + + await waitFor(() => + expect(mocked(SdkContextClass.instance.resizeNotifier.notifyLeftHandleResized)).toHaveBeenCalled(), + ); + }); + + it("should notify resizenotifier when left panel shown", async () => { + getComponent(); + + jest.spyOn(SdkContextClass.instance.resizeNotifier, "notifyLeftHandleResized"); + + defaultDispatcher.dispatch({ action: "show_left_panel" }); + + await waitFor(() => + expect(mocked(SdkContextClass.instance.resizeNotifier.notifyLeftHandleResized)).toHaveBeenCalled(), + ); + }); + describe("when query params have a OIDC params", () => { const issuer = "https://auth.com/"; const homeserverUrl = "https://matrix.org"; diff --git a/test/unit-tests/components/structures/MessagePanel-test.tsx b/test/unit-tests/components/structures/MessagePanel-test.tsx index ae12768c2f..0f3b40e3f0 100644 --- a/test/unit-tests/components/structures/MessagePanel-test.tsx +++ b/test/unit-tests/components/structures/MessagePanel-test.tsx @@ -15,11 +15,11 @@ import { render } from "jest-matrix-react"; import MessagePanel, { shouldFormContinuation } from "../../../../src/components/structures/MessagePanel"; import SettingsStore from "../../../../src/settings/SettingsStore"; -import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; import * as TestUtilsMatrix from "../../../test-utils"; import { + clientAndSDKContextRenderOptions, createTestClient, getMockClientWithEventEmitter, makeBeaconInfoEvent, @@ -32,6 +32,7 @@ import type ResizeNotifier from "../../../../src/utils/ResizeNotifier"; import { type IRoomState } from "../../../../src/components/structures/RoomView"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx"; +import { SdkContextClass } from "../../../../src/contexts/SDKContext.ts"; jest.mock("../../../../src/utils/beacon", () => ({ useBeacon: jest.fn(), @@ -54,6 +55,7 @@ describe("MessagePanel", function () { getClientWellKnown: jest.fn().mockReturnValue({}), supportsThreads: jest.fn().mockReturnValue(true), }); + let sdkContext: SdkContextClass; jest.spyOn(MatrixClientPeg, "get").mockReturnValue(client); const room = new Room(roomId, client, userId); @@ -93,11 +95,9 @@ describe("MessagePanel", function () { } as unknown as IRoomState; const getComponent = (props = {}, roomContext: Partial = {}) => ( - - - - - + + + ); beforeEach(function () { @@ -107,6 +107,8 @@ describe("MessagePanel", function () { return arg === "showDisplaynameChanges"; }); + sdkContext = new SdkContextClass(); + DMRoomMap.makeShared(client); }); @@ -314,7 +316,7 @@ describe("MessagePanel", function () { } it("should show the events", function () { - const { container } = render(getComponent({ events })); + const { container } = render(getComponent({ events }), clientAndSDKContextRenderOptions(client, sdkContext)); // just check we have the right number of tiles for now const tiles = container.getElementsByClassName("mx_EventTile"); @@ -322,7 +324,10 @@ describe("MessagePanel", function () { }); it("should collapse adjacent member events", function () { - const { container } = render(getComponent({ events: mkMelsEvents() })); + const { container } = render( + getComponent({ events: mkMelsEvents() }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); // just check we have the right number of tiles for now const tiles = container.getElementsByClassName("mx_EventTile"); @@ -339,6 +344,7 @@ describe("MessagePanel", function () { readMarkerEventId: events[4].getId(), readMarkerVisible: true, }), + clientAndSDKContextRenderOptions(client, sdkContext), ); const tiles = container.getElementsByClassName("mx_EventTile"); @@ -359,6 +365,7 @@ describe("MessagePanel", function () { readMarkerEventId: melsEvents[4].getId(), readMarkerVisible: true, }), + clientAndSDKContextRenderOptions(client, sdkContext), ); const [summary] = container.getElementsByClassName("mx_GenericEventListSummary"); @@ -381,6 +388,7 @@ describe("MessagePanel", function () { readMarkerEventId: melsEvents[9].getId(), readMarkerVisible: true, }), + clientAndSDKContextRenderOptions(client, sdkContext), ); const [summary] = container.getElementsByClassName("mx_GenericEventListSummary"); @@ -406,6 +414,7 @@ describe("MessagePanel", function () { readMarkerVisible: true, })}
, + clientAndSDKContextRenderOptions(client, sdkContext), ); const tiles = container.getElementsByClassName("mx_EventTile"); @@ -448,7 +457,7 @@ describe("MessagePanel", function () { client.getRoom.mockImplementation((id) => (id === createEvent!.getRoomId() ? room : null)); TestUtilsMatrix.upsertRoomStateEvents(room, events); - const { container } = render(getComponent({ events })); + const { container } = render(getComponent({ events }), clientAndSDKContextRenderOptions(client, sdkContext)); // we expect that // - the room creation event, the room encryption event, and Alice inviting Bob, @@ -476,7 +485,10 @@ describe("MessagePanel", function () { }); const combinedEvents = [...events, beaconInfoEvent]; TestUtilsMatrix.upsertRoomStateEvents(room, combinedEvents); - const { container } = render(getComponent({ events: combinedEvents })); + const { container } = render( + getComponent({ events: combinedEvents }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); const [summaryTile] = container.getElementsByClassName("mx_GenericEventListSummary"); @@ -498,6 +510,7 @@ describe("MessagePanel", function () { readMarkerEventId: events[5].getId(), readMarkerVisible: true, }), + clientAndSDKContextRenderOptions(client, sdkContext), ); // find the
  • which wraps the read marker @@ -514,7 +527,10 @@ describe("MessagePanel", function () { it("should render Date separators for the events", function () { const events = mkOneDayEvents(); - const { queryAllByRole } = render(getComponent({ events })); + const { queryAllByRole } = render( + getComponent({ events }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); const dates = queryAllByRole("separator"); expect(dates.length).toEqual(1); @@ -523,7 +539,10 @@ describe("MessagePanel", function () { it("appends events into summaries during forward pagination without changing key", () => { const events = mkMelsEvents().slice(1, 11); - const { container, rerender } = render(getComponent({ events })); + const { container, rerender } = render( + getComponent({ events }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); let els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); @@ -553,7 +572,10 @@ describe("MessagePanel", function () { it("prepends events into summaries during backward pagination without changing key", () => { const events = mkMelsEvents().slice(1, 11); - const { container, rerender } = render(getComponent({ events })); + const { container, rerender } = render( + getComponent({ events }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); let els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId()); @@ -583,7 +605,10 @@ describe("MessagePanel", function () { it("assigns different keys to summaries that get split up", () => { const events = mkMelsEvents().slice(1, 11); - const { container, rerender } = render(getComponent({ events })); + const { container, rerender } = render( + getComponent({ events }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); let els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`); @@ -616,7 +641,7 @@ describe("MessagePanel", function () { it("doesn't lookup showHiddenEventsInTimeline while rendering", () => { // We're only interested in the setting lookups that happen on every render, // rather than those happening on first mount, so let's get those out of the way - const { rerender } = render(getComponent({ events: [] })); + const { rerender } = render(getComponent({ events: [] }), clientAndSDKContextRenderOptions(client, sdkContext)); // Set up our spy and re-render with new events const settingsSpy = jest.spyOn(SettingsStore, "getValue").mockClear(); @@ -654,7 +679,10 @@ describe("MessagePanel", function () { ts: 3, }), ]; - const { container } = render(getComponent({ events }, { showHiddenEvents: true })); + const { container } = render( + getComponent({ events }, { showHiddenEvents: true }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); const els = container.getElementsByClassName("mx_GenericEventListSummary"); expect(els.length).toEqual(1); @@ -678,7 +706,10 @@ describe("MessagePanel", function () { }), ); } - const { asFragment } = render(getComponent({ events }, { showHiddenEvents: false })); + const { asFragment } = render( + getComponent({ events }, { showHiddenEvents: false }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); expect(asFragment()).toMatchSnapshot(); }); @@ -699,7 +730,10 @@ describe("MessagePanel", function () { }), ); } - const { asFragment } = render(getComponent({ events }, { showHiddenEvents: false })); + const { asFragment } = render( + getComponent({ events }, { showHiddenEvents: false }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); expect(asFragment()).toMatchSnapshot(); }); @@ -720,7 +754,10 @@ describe("MessagePanel", function () { }), ); } - const { asFragment } = render(getComponent({ events }, { showHiddenEvents: true })); + const { asFragment } = render( + getComponent({ events }, { showHiddenEvents: true }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); const cpt = asFragment(); // Ignore properties that change every time @@ -751,7 +788,10 @@ describe("MessagePanel", function () { content: { topic: "TOPIC" }, }), ]; - const { container } = render(getComponent({ events, showReadReceipts: true })); + const { container } = render( + getComponent({ events, showReadReceipts: true }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); const tiles = container.getElementsByClassName("mx_EventTile"); expect(tiles.length).toEqual(2); @@ -784,7 +824,10 @@ describe("MessagePanel", function () { }, true, ); - const { container } = render(getComponent({ events, showReadReceipts: true })); + const { container } = render( + getComponent({ events, showReadReceipts: true }), + clientAndSDKContextRenderOptions(client, sdkContext), + ); const tiles = container.getElementsByClassName("mx_EventTile"); expect(tiles.length).toEqual(2); diff --git a/test/unit-tests/components/structures/RoomSearchView-test.tsx b/test/unit-tests/components/structures/RoomSearchView-test.tsx index ab52e8012b..ab939a026b 100644 --- a/test/unit-tests/components/structures/RoomSearchView-test.tsx +++ b/test/unit-tests/components/structures/RoomSearchView-test.tsx @@ -20,11 +20,11 @@ import { } from "matrix-js-sdk/src/matrix"; import { RoomSearchView } from "../../../../src/components/structures/RoomSearchView"; -import ResizeNotifier from "../../../../src/utils/ResizeNotifier"; -import { stubClient } from "../../../test-utils"; +import { clientAndSDKContextRenderOptions, stubClient } from "../../../test-utils"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { searchPagination, SearchScope } from "../../../../src/Searching"; +import { SdkContextClass } from "../../../../src/contexts/SDKContext"; jest.mock("../../../../src/Searching", () => ({ searchPagination: jest.fn(), @@ -33,13 +33,14 @@ jest.mock("../../../../src/Searching", () => ({ describe("", () => { const eventMapper = (obj: Partial) => new MatrixEvent(obj); - const resizeNotifier = new ResizeNotifier(); let client: MatrixClient; + let sdkContext: SdkContextClass; let room: Room; beforeEach(async () => { stubClient(); client = MatrixClientPeg.safeGet(); + sdkContext = new SdkContextClass(); client.supportsThreads = jest.fn().mockReturnValue(true); room = new Room("!room:server", client, client.getSafeUserId()); mocked(client.getRoom).mockReturnValue(room); @@ -60,7 +61,6 @@ describe("", () => { term="search term" scope={SearchScope.All} promise={deferred.promise} - resizeNotifier={resizeNotifier} className="someClass" onUpdate={jest.fn()} />, @@ -71,59 +71,57 @@ describe("", () => { it("should render results when the promise resolves", async () => { render( - - ({ - results: [ - SearchResult.fromJson( - { - rank: 1, - result: { - room_id: room.roomId, - event_id: "$2", - sender: client.getSafeUserId(), - origin_server_ts: 1, - content: { body: "Foo Test Bar", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - context: { - profile_info: {}, - events_before: [ - { - room_id: room.roomId, - event_id: "$1", - sender: client.getSafeUserId(), - origin_server_ts: 1, - content: { body: "Before", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - ], - events_after: [ - { - room_id: room.roomId, - event_id: "$3", - sender: client.getSafeUserId(), - origin_server_ts: 1, - content: { body: "After", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - ], - }, + ({ + results: [ + SearchResult.fromJson( + { + rank: 1, + result: { + room_id: room.roomId, + event_id: "$2", + sender: client.getSafeUserId(), + origin_server_ts: 1, + content: { body: "Foo Test Bar", msgtype: "m.text" }, + type: EventType.RoomMessage, }, - eventMapper, - ), - ], - highlights: [], - count: 1, - })} - resizeNotifier={resizeNotifier} - className="someClass" - onUpdate={jest.fn()} - /> - , + context: { + profile_info: {}, + events_before: [ + { + room_id: room.roomId, + event_id: "$1", + sender: client.getSafeUserId(), + origin_server_ts: 1, + content: { body: "Before", msgtype: "m.text" }, + type: EventType.RoomMessage, + }, + ], + events_after: [ + { + room_id: room.roomId, + event_id: "$3", + sender: client.getSafeUserId(), + origin_server_ts: 1, + content: { body: "After", msgtype: "m.text" }, + type: EventType.RoomMessage, + }, + ], + }, + }, + eventMapper, + ), + ], + highlights: [], + count: 1, + })} + className="someClass" + onUpdate={jest.fn()} + />, + clientAndSDKContextRenderOptions(client, sdkContext), ); await screen.findByText("Before"); @@ -133,41 +131,39 @@ describe("", () => { it("should highlight words correctly", async () => { render( - - ({ - results: [ - SearchResult.fromJson( - { - rank: 1, - result: { - room_id: room.roomId, - event_id: "$2", - sender: client.getSafeUserId(), - origin_server_ts: 1, - content: { body: "Foo Test Bar", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - context: { - profile_info: {}, - events_before: [], - events_after: [], - }, + ({ + results: [ + SearchResult.fromJson( + { + rank: 1, + result: { + room_id: room.roomId, + event_id: "$2", + sender: client.getSafeUserId(), + origin_server_ts: 1, + content: { body: "Foo Test Bar", msgtype: "m.text" }, + type: EventType.RoomMessage, }, - eventMapper, - ), - ], - highlights: ["test"], - count: 1, - })} - resizeNotifier={resizeNotifier} - className="someClass" - onUpdate={jest.fn()} - /> - , + context: { + profile_info: {}, + events_before: [], + events_after: [], + }, + }, + eventMapper, + ), + ], + highlights: ["test"], + count: 1, + })} + className="someClass" + onUpdate={jest.fn()} + />, + clientAndSDKContextRenderOptions(client, sdkContext), ); const text = await screen.findByText("Test"); @@ -231,17 +227,15 @@ describe("", () => { const onUpdate = jest.fn(); const { rerender } = render( - - - , + , + clientAndSDKContextRenderOptions(client, sdkContext), ); await screen.findByRole("progressbar"); @@ -249,17 +243,14 @@ describe("", () => { expect(onUpdate).toHaveBeenCalledWith(false, expect.objectContaining({}), null); rerender( - - - , + , ); expect(screen.queryByRole("progressbar")).toBeFalsy(); @@ -275,7 +266,6 @@ describe("", () => { term="search term" scope={SearchScope.All} promise={deferred.promise} - resizeNotifier={resizeNotifier} className="someClass" onUpdate={jest.fn()} /> @@ -299,7 +289,6 @@ describe("", () => { term="search term" scope={SearchScope.All} promise={deferred.promise} - resizeNotifier={resizeNotifier} className="someClass" onUpdate={jest.fn()} /> @@ -324,7 +313,6 @@ describe("", () => { term="search term" scope={SearchScope.All} promise={deferred.promise} - resizeNotifier={resizeNotifier} className="someClass" onUpdate={onUpdate} /> @@ -424,17 +412,15 @@ describe("", () => { }; render( - - - , + , + clientAndSDKContextRenderOptions(client, sdkContext), ); const beforeNode = await screen.findByText("Before"); @@ -459,98 +445,96 @@ describe("", () => { ); render( - - ({ - results: [ - SearchResult.fromJson( - { - rank: 1, - result: { - room_id: room.roomId, - event_id: "$2", - sender: client.getSafeUserId(), - origin_server_ts: 1, - content: { body: "Room 1", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - context: { - profile_info: {}, - events_before: [], - events_after: [], - }, + ({ + results: [ + SearchResult.fromJson( + { + rank: 1, + result: { + room_id: room.roomId, + event_id: "$2", + sender: client.getSafeUserId(), + origin_server_ts: 1, + content: { body: "Room 1", msgtype: "m.text" }, + type: EventType.RoomMessage, }, - eventMapper, - ), - SearchResult.fromJson( - { - rank: 2, - result: { - room_id: room2.roomId, - event_id: "$22", - sender: client.getSafeUserId(), - origin_server_ts: 1, - content: { body: "Room 2", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - context: { - profile_info: {}, - events_before: [], - events_after: [], - }, + context: { + profile_info: {}, + events_before: [], + events_after: [], }, - eventMapper, - ), - SearchResult.fromJson( - { - rank: 2, - result: { - room_id: room2.roomId, - event_id: "$23", - sender: client.getSafeUserId(), - origin_server_ts: 2, - content: { body: "Room 2 message 2", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - context: { - profile_info: {}, - events_before: [], - events_after: [], - }, + }, + eventMapper, + ), + SearchResult.fromJson( + { + rank: 2, + result: { + room_id: room2.roomId, + event_id: "$22", + sender: client.getSafeUserId(), + origin_server_ts: 1, + content: { body: "Room 2", msgtype: "m.text" }, + type: EventType.RoomMessage, }, - eventMapper, - ), - SearchResult.fromJson( - { - rank: 3, - result: { - room_id: room3.roomId, - event_id: "$32", - sender: client.getSafeUserId(), - origin_server_ts: 1, - content: { body: "Room 3", msgtype: "m.text" }, - type: EventType.RoomMessage, - }, - context: { - profile_info: {}, - events_before: [], - events_after: [], - }, + context: { + profile_info: {}, + events_before: [], + events_after: [], }, - eventMapper, - ), - ], - highlights: [], - count: 1, - })} - resizeNotifier={resizeNotifier} - className="someClass" - onUpdate={jest.fn()} - /> - , + }, + eventMapper, + ), + SearchResult.fromJson( + { + rank: 2, + result: { + room_id: room2.roomId, + event_id: "$23", + sender: client.getSafeUserId(), + origin_server_ts: 2, + content: { body: "Room 2 message 2", msgtype: "m.text" }, + type: EventType.RoomMessage, + }, + context: { + profile_info: {}, + events_before: [], + events_after: [], + }, + }, + eventMapper, + ), + SearchResult.fromJson( + { + rank: 3, + result: { + room_id: room3.roomId, + event_id: "$32", + sender: client.getSafeUserId(), + origin_server_ts: 1, + content: { body: "Room 3", msgtype: "m.text" }, + type: EventType.RoomMessage, + }, + context: { + profile_info: {}, + events_before: [], + events_after: [], + }, + }, + eventMapper, + ), + ], + highlights: [], + count: 1, + })} + className="someClass" + onUpdate={jest.fn()} + />, + clientAndSDKContextRenderOptions(client, sdkContext), ); const event1 = await screen.findByText("Room 1"); diff --git a/test/unit-tests/components/structures/RoomView-test.tsx b/test/unit-tests/components/structures/RoomView-test.tsx index bb722e6f51..b0efc27dda 100644 --- a/test/unit-tests/components/structures/RoomView-test.tsx +++ b/test/unit-tests/components/structures/RoomView-test.tsx @@ -55,7 +55,6 @@ import { Action } from "../../../../src/dispatcher/actions"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; import { type ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload"; import { RoomView } from "../../../../src/components/structures/RoomView"; -import ResizeNotifier from "../../../../src/utils/ResizeNotifier"; import SettingsStore from "../../../../src/settings/SettingsStore"; import { SettingLevel } from "../../../../src/settings/SettingLevel"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; @@ -157,7 +156,6 @@ describe("RoomView", () => { // threepidInvite should be optional on RoomView props // it is treated as optional in RoomView threepidInvite={undefined as any} - resizeNotifier={new ResizeNotifier()} forceTimeline={false} ref={ref} /> @@ -196,7 +194,6 @@ describe("RoomView", () => { // threepidInvite should be optional on RoomView props // it is treated as optional in RoomView threepidInvite={undefined} - resizeNotifier={new ResizeNotifier()} forceTimeline={false} onRegistered={jest.fn()} /> diff --git a/test/unit-tests/components/structures/TimelinePanel-test.tsx b/test/unit-tests/components/structures/TimelinePanel-test.tsx index 354036e282..3602dd8322 100644 --- a/test/unit-tests/components/structures/TimelinePanel-test.tsx +++ b/test/unit-tests/components/structures/TimelinePanel-test.tsx @@ -33,15 +33,14 @@ import { type Mocked, mocked } from "jest-mock"; import { forEachRight } from "lodash"; import TimelinePanel from "../../../../src/components/structures/TimelinePanel"; -import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { + clientAndSDKContextRenderOptions, filterConsole, flushPromises, mkMembership, mkRoom, stubClient, - withClientContextRenderOptions, } from "../../../test-utils"; import { mkThread } from "../../../test-utils/threads"; import { createMessageEventContent } from "../../../test-utils/events"; @@ -51,6 +50,7 @@ import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; import { Action } from "../../../../src/dispatcher/actions"; import { SettingLevel } from "../../../../src/settings/SettingLevel"; import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController"; +import { SdkContextClass } from "../../../../src/contexts/SDKContext"; // ScrollPanel calls this, but jsdom doesn't mock it for us HTMLDivElement.prototype.scrollBy = () => {}; @@ -159,6 +159,7 @@ const setupPagination = ( describe("TimelinePanel", () => { let client: Mocked; + let sdkContext: SdkContextClass; let userId: string; filterConsole("checkForPreJoinUISI: showing all messages, skipping check"); @@ -166,6 +167,7 @@ describe("TimelinePanel", () => { beforeEach(() => { client = mocked(stubClient()); userId = client.getSafeUserId(); + sdkContext = new SdkContextClass(); }); describe("read receipts and markers", () => { @@ -200,7 +202,7 @@ describe("TimelinePanel", () => { timelinePanel = ref; }} />, - withClientContextRenderOptions(MatrixClientPeg.safeGet()), + clientAndSDKContextRenderOptions(client, sdkContext), ); await flushPromises(); await waitFor(() => expect(timelinePanel).toBeTruthy()); @@ -396,7 +398,7 @@ describe("TimelinePanel", () => { await withScrollPanelMountSpy(async (mountSpy) => { const { container } = render( , - withClientContextRenderOptions(MatrixClientPeg.safeGet()), + clientAndSDKContextRenderOptions(client, sdkContext), ); await waitFor(() => expectEvents(container, [events[1]])); @@ -416,7 +418,7 @@ describe("TimelinePanel", () => { await withScrollPanelMountSpy(async (mountSpy) => { const { container } = render( , - withClientContextRenderOptions(MatrixClientPeg.safeGet()), + clientAndSDKContextRenderOptions(client, sdkContext), ); await waitFor(() => expectEvents(container, [events[0], events[1]])); @@ -493,7 +495,7 @@ describe("TimelinePanel", () => { const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear(); - render(); + render(, clientAndSDKContextRenderOptions(client, sdkContext)); const event = new MatrixEvent({ type: RoomEvent.Timeline, origin_server_ts: 0 }); const data = { timeline: props.timelineSet.getLiveTimeline(), liveEvent: true }; @@ -590,9 +592,8 @@ describe("TimelinePanel", () => { const replyToEvent = jest.spyOn(thread, "replyToEvent", "get"); const dom = render( - - - , + , + clientAndSDKContextRenderOptions(client, sdkContext), ); await dom.findByText("RootEvent"); await dom.findByText("ReplyEvent1"); @@ -645,9 +646,8 @@ describe("TimelinePanel", () => { }; const dom = render( - - - , + , + clientAndSDKContextRenderOptions(client, sdkContext), ); await dom.findByText("RootEvent"); await dom.findByText("ReplyEvent1"); @@ -718,9 +718,8 @@ describe("TimelinePanel", () => { } const { container } = render( - - - , + , + clientAndSDKContextRenderOptions(client, sdkContext), ); await waitFor(() => expect(screen.queryByRole("progressbar")).toBeNull()); @@ -740,7 +739,7 @@ describe("TimelinePanel", () => { await withScrollPanelMountSpy(async () => { const { container } = render( , - withClientContextRenderOptions(MatrixClientPeg.safeGet()), + clientAndSDKContextRenderOptions(client, sdkContext), ); await waitFor(() => expectEvents(container, [events[1]])); diff --git a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap index c36a22d066..350f3d9b60 100644 --- a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`RoomView for a local room in state CREATING should match the snapshot 1`] = `
    @@ -45,63 +45,9 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1