diff --git a/package.json b/package.json index c6010721268..d305b6f8890 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "focus-visible": "^5.2.0", "gfm.css": "^1.1.2", "glob-to-regexp": "^0.4.1", - "highlight.js": "^10.5.0", + "highlight.js": "^11.3.1", "html-entities": "^1.4.0", "is-ip": "^3.1.0", "jszip": "^3.7.0", @@ -154,7 +154,7 @@ "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", - "allchange": "^1.0.3", + "allchange": "^1.0.4", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", @@ -188,7 +188,9 @@ "@types/react": "17.0.14" }, "jest": { - "snapshotSerializers": ["enzyme-to-json/serializer"], + "snapshotSerializers": [ + "enzyme-to-json/serializer" + ], "testEnvironment": "./__test-utils__/environment.js", "testMatch": [ "/test/**/*-test.[jt]s?(x)" diff --git a/res/css/_components.scss b/res/css/_components.scss index 4abcbbc9d4a..8ea46c42430 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -148,7 +148,7 @@ @import "./views/elements/_PowerSelector.scss"; @import "./views/elements/_ProgressBar.scss"; @import "./views/elements/_QRCode.scss"; -@import "./views/elements/_ReplyThread.scss"; +@import "./views/elements/_ReplyChain.scss"; @import "./views/elements/_ResizeHandle.scss"; @import "./views/elements/_RichText.scss"; @import "./views/elements/_RoleButton.scss"; @@ -244,9 +244,11 @@ @import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/settings/_AvatarSetting.scss"; @import "./views/settings/_CrossSigningPanel.scss"; +@import "./views/settings/_CryptographyPanel.scss"; @import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; +@import "./views/settings/_FontScalingPanel.scss"; @import "./views/settings/_IntegrationManager.scss"; @import "./views/settings/_JoinRuleSettings.scss"; @import "./views/settings/_LayoutSwitcher.scss"; diff --git a/res/css/structures/_GroupFilterPanel.scss b/res/css/structures/_GroupFilterPanel.scss index ceea20ed790..a101aba7e5a 100644 --- a/res/css/structures/_GroupFilterPanel.scss +++ b/res/css/structures/_GroupFilterPanel.scss @@ -51,7 +51,7 @@ $groupFilterPanelWidth: 56px; // only applies in this file, used for calculation height: 0px; width: 90%; border: none; - border-bottom: 1px solid $groupFilterPanel-divider-color; + border-bottom: 1px solid $tertiary-content; } .mx_GroupFilterPanel .mx_GroupFilterPanel_scroller { diff --git a/res/css/structures/_HomePage.scss b/res/css/structures/_HomePage.scss index 9f72213d1a9..b530d994c61 100644 --- a/res/css/structures/_HomePage.scss +++ b/res/css/structures/_HomePage.scss @@ -56,6 +56,7 @@ limitations under the License. } .mx_HomePage_default_buttons { + display: flex; margin: 60px auto 0; width: fit-content; @@ -63,7 +64,7 @@ limitations under the License. padding: 73px 8px 15px; // top: 20px top padding + 40px icon + 13px margin width: 160px; - height: 132px; + min-height: 132px; margin: 20px; position: relative; display: inline-block; diff --git a/res/css/structures/_LeftPanelWidget.scss b/res/css/structures/_LeftPanelWidget.scss index 93c2898395b..bb04b856245 100644 --- a/res/css/structures/_LeftPanelWidget.scss +++ b/res/css/structures/_LeftPanelWidget.scss @@ -24,7 +24,7 @@ limitations under the License. align-items: center; height: 24px; - color: $roomlist-header-color; + color: $tertiary-content; margin-top: 4px; .mx_LeftPanelWidget_stickable { @@ -62,7 +62,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background-color: $roomlist-header-color; + background-color: $tertiary-content; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 30d421a3517..316bc460b5f 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -19,7 +19,7 @@ $nestedHeight: 24px; $gutterSize: 16px; $activeBorderTransparentGap: 1px; -$activeBackgroundColor: $roomtile-selected-bg-color; +$activeBackgroundColor: $panel-actions; $activeBorderColor: $secondary-content; .mx_SpacePanel { @@ -49,7 +49,7 @@ $activeBorderColor: $secondary-content; mask-repeat: no-repeat; margin-left: $gutterSize; margin-bottom: 12px; - background-color: $roomlist-header-color; + background-color: $tertiary-content; mask-image: url('$(res)/img/element-icons/expand-space-panel.svg'); &.expanded { @@ -146,13 +146,6 @@ $activeBorderColor: $secondary-content; padding: 4px; } - &:not(.mx_SpaceButton_narrow) { - .mx_SpaceButton_selectionWrapper { - width: 100%; - overflow: hidden; - } - } - .mx_SpaceButton_name { flex: 1; margin-left: 8px; @@ -170,7 +163,7 @@ $activeBorderColor: $secondary-content; mask-position: center; mask-size: 20px; mask-repeat: no-repeat; - background-color: $roomlist-header-color; + background-color: $tertiary-content; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } @@ -227,7 +220,7 @@ $activeBorderColor: $secondary-content; height: 20px; margin-top: auto; margin-bottom: auto; - display: none; + visibility: hidden; position: relative; &::before { @@ -246,67 +239,45 @@ $activeBorderColor: $secondary-content; } } + .mx_SpaceButton_avatarWrapper { + position: relative; + } + .mx_SpacePanel_badgeContainer { // Create a flexbox to make aligning dot badges easier display: flex; align-items: center; + position: absolute; + right: -3px; + top: -3px; .mx_NotificationBadge { margin: 0 2px; // centering + background-clip: padding-box; } .mx_NotificationBadge_dot { // make the smaller dot occupy the same width for centering - margin: 0 7px; + margin: 0 -1px 0 0; + border: 3px solid $groupFilterPanel-bg-color; } - } - &.collapsed { - .mx_SpaceButton { - .mx_SpacePanel_badgeContainer { - position: absolute; - right: 0; - top: 0; - - .mx_NotificationBadge { - background-clip: padding-box; - } - - .mx_NotificationBadge_dot { - margin: 0 -1px 0 0; - border: 3px solid $groupFilterPanel-bg-color; - } - - .mx_NotificationBadge_2char, - .mx_NotificationBadge_3char { - margin: -5px -5px 0 0; - border: 2px solid $groupFilterPanel-bg-color; - } - } - - &.mx_SpaceButton_active .mx_SpacePanel_badgeContainer { - // when we draw the selection border we move the relative bounds of our parent - // so update our position within the bounds of the parent to maintain position overall - right: -3px; - top: -3px; - } + .mx_NotificationBadge_2char, + .mx_NotificationBadge_3char { + margin: -5px -5px 0 0; + border: 2px solid $groupFilterPanel-bg-color; } } - &:not(.collapsed) { - .mx_SpaceButton:hover, - .mx_SpaceButton:focus-within, - .mx_SpaceButton_hasMenuOpen { - &:not(.mx_SpaceButton_invite) { - // Hide the badge container on hover because it'll be a menu button - .mx_SpacePanel_badgeContainer { - display: none; - } - - .mx_SpaceButton_menuButton { - display: block; - } - } + .mx_SpaceButton_narrow .mx_SpaceButton_menuButton { + display: none; + } + + .mx_SpaceButton:hover, + .mx_SpaceButton:focus-within, + .mx_SpaceButton_hasMenuOpen { + &:not(.mx_SpaceButton_invite) .mx_SpaceButton_menuButton { + visibility: visible; } } diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index e1168850470..e6394525c59 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -511,10 +511,11 @@ $SpaceRoomViewInnerWidth: 428px; mask-image: url("$(res)/img/element-icons/lock.svg"); } - .mx_AccessibleButton_kind_link { + .mx_SpaceRoomView_info_memberCount { color: inherit; position: relative; - padding-left: 16px; + padding: 0 0 0 16px; + font-size: $font-15px; &::before { content: "ยท"; // visual separator diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss index 566507211c3..2c45069cfe1 100644 --- a/res/css/structures/auth/_CompleteSecurity.scss +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -51,7 +51,7 @@ limitations under the License. } .mx_CompleteSecurity_waiting { - color: $notice-secondary-color; + color: $tertiary-content; } .mx_CompleteSecurity_actionRow { diff --git a/res/css/views/audio_messages/_PlayPauseButton.scss b/res/css/views/audio_messages/_PlayPauseButton.scss index 714da3e6053..9548b62dfda 100644 --- a/res/css/views/audio_messages/_PlayPauseButton.scss +++ b/res/css/views/audio_messages/_PlayPauseButton.scss @@ -21,12 +21,12 @@ limitations under the License. min-width: 32px; // for when the button is used in a flexbox min-height: 32px; // for when the button is used in a flexbox border-radius: 32px; - background-color: $voice-playback-button-bg-color; + background-color: $system; &::before { content: ''; position: absolute; // sizing varies by icon - background-color: $voice-playback-button-fg-color; + background-color: $secondary-content; mask-repeat: no-repeat; mask-size: contain; } diff --git a/res/css/views/audio_messages/_PlaybackContainer.scss b/res/css/views/audio_messages/_PlaybackContainer.scss index 3886e385834..408a2953893 100644 --- a/res/css/views/audio_messages/_PlaybackContainer.scss +++ b/res/css/views/audio_messages/_PlaybackContainer.scss @@ -31,7 +31,7 @@ limitations under the License. .mx_Waveform { .mx_Waveform_bar { - background-color: $voice-record-waveform-incomplete-fg-color; + background-color: $quaternary-content; height: 100%; /* Variable set by a JS component */ transform: scaleY(max(0.05, var(--barHeight))); diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index a6b61d3eada..ae7922e8027 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_BetaCard { margin-bottom: 20px; padding: 24px; - background-color: $settings-profile-placeholder-bg-color; + background-color: $system; border-radius: 8px; box-sizing: border-box; diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index 5af748e28d1..47646c38209 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -81,4 +81,8 @@ limitations under the License. .mx_MessageContextMenu_iconUnpin::before { mask-image: url('$(res)/img/element-icons/room/pin.svg'); } + + .mx_MessageContextMenu_iconViewInRoom::before { + mask-image: url('$(res)/img/element-icons/view-in-room.svg'); + } } diff --git a/res/css/views/dialogs/_NewSessionReviewDialog.scss b/res/css/views/dialogs/_NewSessionReviewDialog.scss index b35c570c804..0016b5b91ba 100644 --- a/res/css/views/dialogs/_NewSessionReviewDialog.scss +++ b/res/css/views/dialogs/_NewSessionReviewDialog.scss @@ -33,5 +33,5 @@ limitations under the License. .mx_NewSessionReviewDialog_deviceID { font-size: $font-12px; - color: $notice-secondary-color; + color: $tertiary-content; } diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 37d335b76d5..7bbb91a066a 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -128,7 +128,7 @@ limitations under the License. font-size: $font-10px; transform: translateY(-13px); padding: 0 2px; - background-color: $field-focused-label-bg-color; + background-color: $background; pointer-events: initial; } @@ -144,7 +144,7 @@ limitations under the License. .mx_Field input:disabled + label, .mx_Field textarea:disabled, .mx_Field textarea:disabled + label { - background-color: $field-focused-label-bg-color; + background-color: $background; color: $greyed-fg-color; } diff --git a/res/css/views/elements/_ReplyThread.scss b/res/css/views/elements/_ReplyChain.scss similarity index 83% rename from res/css/views/elements/_ReplyThread.scss rename to res/css/views/elements/_ReplyChain.scss index e19be82e251..c6b9c2bbaac 100644 --- a/res/css/views/elements/_ReplyThread.scss +++ b/res/css/views/elements/_ReplyChain.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ReplyThread { +.mx_ReplyChain { margin-top: 0; margin-left: 0; margin-right: 0; @@ -23,44 +23,44 @@ limitations under the License. border-left: 2px solid $button-bg-color; border-radius: 2px; - .mx_ReplyThread_show { + .mx_ReplyChain_show { cursor: pointer; } - &.mx_ReplyThread_color1 { + &.mx_ReplyChain_color1 { border-left-color: $username-variant1-color; } - &.mx_ReplyThread_color2 { + &.mx_ReplyChain_color2 { border-left-color: $username-variant2-color; } - &.mx_ReplyThread_color3 { + &.mx_ReplyChain_color3 { border-left-color: $username-variant3-color; } - &.mx_ReplyThread_color4 { + &.mx_ReplyChain_color4 { border-left-color: $username-variant4-color; } - &.mx_ReplyThread_color5 { + &.mx_ReplyChain_color5 { border-left-color: $username-variant5-color; } - &.mx_ReplyThread_color6 { + &.mx_ReplyChain_color6 { border-left-color: $username-variant6-color; } - &.mx_ReplyThread_color7 { + &.mx_ReplyChain_color7 { border-left-color: $username-variant7-color; } - &.mx_ReplyThread_color8 { + &.mx_ReplyChain_color8 { border-left-color: $username-variant8-color; } } -.mx_ReplyThread--expanded { +.mx_ReplyChain--expanded { .mx_EventTile_body { display: block; overflow-y: scroll !important; diff --git a/res/css/views/messages/_common_CryptoEvent.scss b/res/css/views/messages/_common_CryptoEvent.scss index afaed50fa4f..b400a933aec 100644 --- a/res/css/views/messages/_common_CryptoEvent.scss +++ b/res/css/views/messages/_common_CryptoEvent.scss @@ -56,7 +56,7 @@ limitations under the License. padding: 10px 20px; margin: auto 0; text-align: center; - color: $notice-secondary-color; + color: $tertiary-content; overflow-wrap: break-word; font-size: $font-12px; } diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index edc82cfdbf1..9a09d96bc96 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -122,7 +122,7 @@ limitations under the License. h3 { text-transform: uppercase; - color: $notice-secondary-color; + color: $tertiary-content; font-weight: 600; font-size: $font-12px; margin: 4px 0; diff --git a/res/css/views/rooms/_EntityTile.scss b/res/css/views/rooms/_EntityTile.scss index a2ebd6c11b2..23a5ffc99f5 100644 --- a/res/css/views/rooms/_EntityTile.scss +++ b/res/css/views/rooms/_EntityTile.scss @@ -122,7 +122,7 @@ limitations under the License. .mx_EntityTile_power { padding-inline-start: 6px; font-size: $font-10px; - color: $notice-secondary-color; + color: $tertiary-content; max-width: 6em; overflow: hidden; text-overflow: ellipsis; diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 389a5c97061..2a419530d86 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -29,6 +29,11 @@ limitations under the License. margin-right: 100px; font-size: $font-14px; + .mx_ThreadInfo { + clear: both; + width: fit-content; + } + &.mx_EventTile_continuation { margin-top: 2px; } @@ -108,6 +113,12 @@ limitations under the License. right: -68px; } } + + .mx_ThreadInfo { + float: right; + margin-right: calc(-1 * var(--gutterSize)); + } + .mx_SenderProfile { display: none; } @@ -192,11 +203,11 @@ limitations under the License. flex-direction: column; } - .mx_ReplyThread_show { + .mx_ReplyChain_show { order: 99999; } - .mx_ReplyThread { + .mx_ReplyChain { .mx_EventTile_reply { max-width: 90%; padding: 0; @@ -247,7 +258,7 @@ limitations under the License. .mx_EventTile_keyRequestInfo { grid-area: link; } - .mx_ReplyThread_wrapper { + .mx_ReplyChain_wrapper { grid-area: reply; } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 470851654b2..ea53cc4f130 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -500,12 +500,11 @@ $hover-select-border: 4px; float: left; margin: 0 0.5em 0 -1.5em; color: gray; -} - -.mx_EventTile_lineNumber { - text-align: right; - display: block; - padding-left: 1em; + & span { + text-align: right; + display: block; + padding-left: 1em; + } } .mx_EventTile_collapsedCodeBlock { @@ -590,10 +589,6 @@ $hover-select-border: 4px; padding: 0 10px; } -.mx_EventTile_content .markdown-body .hljs { - display: inline !important; -} - /* // actually, removing the Italic TTF provides // better results seemingly @@ -676,10 +671,58 @@ $hover-select-border: 4px; } } -.mx_ThreadInfo:hover { - cursor: pointer; +.mx_ThreadInfo { + height: 35px; + position: relative; + background-color: $system; + padding-left: 12px; + display: flex; + align-items: center; + border-radius: 8px; + padding-right: 16px; + padding-top: 8px; + padding-bottom: 8px; + font-size: 12px; + color: $secondary-content; + box-sizing: border-box; + justify-content: flex-start; + clear: both; + + &:hover, &-active { + cursor: pointer; + border: 1px solid $quinary-content; + padding-top: 7px; + padding-bottom: 7px; + padding-left: 11px; + padding-right: 15px; + } + + .mx_ThreadInfo_content { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding-left: 8px; + } + + .mx_ThreadInfo_thread-icon { + mask-image: url('$(res)/img/element-icons/thread-summary.svg'); + mask-position: center; + height: 16px; + min-width: 16px; + background-color: $secondary-content; + mask-repeat: no-repeat; + mask-size: contain; + } + .mx_ThreadInfo_threads-amount { + font-weight: 600; + position: relative; + padding: 0 8px; + white-space: nowrap; + } } + + .mx_ThreadView { display: flex; flex-direction: column; diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 578c0325d22..7edfff1e857 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -200,7 +200,7 @@ $irc-line-height: $font-18px; } } - .mx_ReplyThread { + .mx_ReplyChain { margin: 0; .mx_SenderProfile { order: unset; diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index 0cf5f5a9e0c..011824d9d40 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -48,7 +48,7 @@ limitations under the License. // to work correctly. padding-bottom: 8px; height: 24px; - color: $roomlist-header-color; + color: $tertiary-content; .mx_RoomSublist_stickable { flex: 1; @@ -165,7 +165,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background-color: $roomlist-header-color; + background-color: $tertiary-content; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } @@ -236,7 +236,7 @@ limitations under the License. cursor: pointer; font-size: $font-13px; line-height: $font-18px; - color: $roomtile-preview-color; + color: $secondary-content; // Update the render() function for RoomSublist if these change // Update the ListLayout class for minVisibleTiles if these change. @@ -256,7 +256,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $roomlist-header-color; + background: $tertiary-content; left: -1px; // adjust for image position } @@ -368,7 +368,7 @@ limitations under the License. margin-top: 16px; margin-bottom: 16px; margin-right: 16px; // additional 16px - border: 1px solid $roomsublist-divider-color; + border: 1px solid $primary-content; opacity: 0.1; } diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 0c04f271156..5cd75fd69ed 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -30,7 +30,7 @@ limitations under the License. &:hover, &:focus-within, &.mx_RoomTile_hasMenuOpen { - background-color: $roomtile-selected-bg-color; + background-color: $panel-actions; border-radius: 8px; } @@ -71,7 +71,7 @@ limitations under the License. .mx_RoomTile_messagePreview { font-size: $font-13px; line-height: $font-18px; - color: $roomtile-preview-color; + color: $secondary-content; } .mx_RoomTile_nameWithPreview { diff --git a/res/css/views/settings/_AvatarSetting.scss b/res/css/views/settings/_AvatarSetting.scss index a350605ab12..9b2bd328cce 100644 --- a/res/css/views/settings/_AvatarSetting.scss +++ b/res/css/views/settings/_AvatarSetting.scss @@ -129,5 +129,5 @@ limitations under the License. } .mx_AvatarSetting_avatar .mx_AvatarSetting_avatarPlaceholder { - background-color: $settings-profile-placeholder-bg-color; + background-color: $system; } diff --git a/res/css/views/settings/_CryptographyPanel.scss b/res/css/views/settings/_CryptographyPanel.scss new file mode 100644 index 00000000000..095218addff --- /dev/null +++ b/res/css/views/settings/_CryptographyPanel.scss @@ -0,0 +1,23 @@ +.mx_CryptographyPanel_sessionInfo { + padding: 0em; + border-spacing: 0px; +} +.mx_CryptographyPanel_sessionInfo > tr { + vertical-align: baseline; + padding: 0em; +} + +.mx_CryptographyPanel_sessionInfo > tr > td { + padding-bottom: 0em; + padding-left: 0em; + padding-right: 1em; + padding-top: 0em; +} + +.mx_CryptographyPanel_importExportButtons .mx_AccessibleButton { + margin-right: 10px; +} + +.mx_CryptographyPanel_importExportButtons { + margin-bottom: 15px; +} diff --git a/res/css/views/settings/_DevicesPanel.scss b/res/css/views/settings/_DevicesPanel.scss index 7e836e0d87c..7d6db7bc96b 100644 --- a/res/css/views/settings/_DevicesPanel.scss +++ b/res/css/views/settings/_DevicesPanel.scss @@ -15,7 +15,6 @@ limitations under the License. */ .mx_DevicesPanel { - display: table; table-layout: fixed; // Normally the panel is 880px, however this can easily overflow the container. // TODO: Fix the table to not be squishy @@ -25,16 +24,17 @@ limitations under the License. } .mx_DevicesPanel_header { - display: table-header-group; font-weight: bold; } -.mx_DevicesPanel_header > .mx_DevicesPanel_deviceButtons { +.mx_DevicesPanel_header .mx_DevicesPanel_deviceButtons { height: 48px; // make this tall so the table doesn't move down when the delete button appears + width: 20%; } -.mx_DevicesPanel_header > div { - display: table-cell; +.mx_DevicesPanel_header th { + padding: 0px; + text-align: left; vertical-align: middle; } @@ -46,16 +46,9 @@ limitations under the License. width: 30%; } -.mx_DevicesPanel_header .mx_DevicesPanel_deviceButtons { - width: 20%; -} - -.mx_DevicesPanel_device { - display: table-row; -} - -.mx_DevicesPanel_device > div { - display: table-cell; +.mx_DevicesPanel_device td { + vertical-align: baseline; + padding: 0px; } .mx_DevicesPanel_myDevice { diff --git a/res/css/views/settings/_FontScalingPanel.scss b/res/css/views/settings/_FontScalingPanel.scss new file mode 100644 index 00000000000..bac1f92a4f5 --- /dev/null +++ b/res/css/views/settings/_FontScalingPanel.scss @@ -0,0 +1,81 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_FontScalingPanel { + color: $primary-content; + > .mx_SettingsTab_SubHeading { + margin-bottom: 32px; + } +} + +.mx_FontScalingPanel .mx_Field { + width: 256px; +} + +.mx_FontScalingPanel_fontSlider, +.mx_FontScalingPanel_fontSlider_preview { + @mixin mx_Settings_fullWidthField; +} + +.mx_FontScalingPanel_fontSlider { + display: flex; + flex-direction: row; + align-items: center; + padding: 15px; + background: rgba($appearance-tab-border-color, 0.2); + border-radius: 10px; + font-size: 10px; + margin-top: 24px; + margin-bottom: 24px; +} + +.mx_FontScalingPanel_fontSlider_preview { + border: 1px solid $appearance-tab-border-color; + border-radius: 10px; + padding: 0 16px 9px 16px; + pointer-events: none; + display: flow-root; + + .mx_EventTile[data-layout=bubble] { + margin-top: 30px; + } + + .mx_EventTile_msgOption { + display: none; + } + + &.mx_IRCLayout { + padding-top: 9px; + } +} + +.mx_FontScalingPanel_fontSlider_smallText { + font-size: 15px; + padding-right: 20px; + padding-left: 5px; + font-weight: 500; +} + +.mx_FontScalingPanel_fontSlider_largeText { + font-size: 18px; + padding-left: 20px; + padding-right: 5px; + font-weight: 500; +} + +.mx_FontScalingPanel_customFontSizeField { + margin-left: calc($font-16px + 10px); +} diff --git a/res/css/views/settings/_Notifications.scss b/res/css/views/settings/_Notifications.scss index a0e46c00717..571d8141ec9 100644 --- a/res/css/views/settings/_Notifications.scss +++ b/res/css/views/settings/_Notifications.scss @@ -90,3 +90,7 @@ limitations under the License. margin-top: 35px; // lots of distance from the last line of the table } } + +.mx_AccessibleButton.mx_NotificationSound_browse { + margin-right: 10px; +} diff --git a/res/css/views/settings/_SetIntegrationManager.scss b/res/css/views/settings/_SetIntegrationManager.scss index 3e59ac73ac9..521b1ee8ab7 100644 --- a/res/css/views/settings/_SetIntegrationManager.scss +++ b/res/css/views/settings/_SetIntegrationManager.scss @@ -26,12 +26,11 @@ limitations under the License. .mx_SetIntegrationManager > .mx_SettingsTab_heading > .mx_SettingsTab_subheading { display: inline-block; padding-left: 5px; + margin-top: 0px; } .mx_SetIntegrationManager .mx_ToggleSwitch { display: inline-block; float: right; top: 9px; - - @mixin mx_Settings_fullWidthField; } diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index 57c6e9b8654..21082786e95 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -14,65 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_AppearanceUserSettingsTab_fontSlider, -.mx_AppearanceUserSettingsTab_fontSlider_preview { - @mixin mx_Settings_fullWidthField; -} - .mx_AppearanceUserSettingsTab .mx_Field { width: 256px; } -.mx_AppearanceUserSettingsTab_fontScaling { - color: $primary-content; -} - -.mx_AppearanceUserSettingsTab_fontSlider { - display: flex; - flex-direction: row; - align-items: center; - padding: 15px; - background: rgba($appearance-tab-border-color, 0.2); - border-radius: 10px; - font-size: 10px; - margin-top: 24px; - margin-bottom: 24px; -} - -.mx_AppearanceUserSettingsTab_fontSlider_preview { - border: 1px solid $appearance-tab-border-color; - border-radius: 10px; - padding: 0 16px 9px 16px; - pointer-events: none; - display: flow-root; - - .mx_EventTile[data-layout=bubble] { - margin-top: 30px; - } - - .mx_EventTile_msgOption { - display: none; - } - - &.mx_IRCLayout { - padding-top: 9px; - } -} - -.mx_AppearanceUserSettingsTab_fontSlider_smallText { - font-size: 15px; - padding-right: 20px; - padding-left: 5px; - font-weight: 500; -} - -.mx_AppearanceUserSettingsTab_fontSlider_largeText { - font-size: 18px; - padding-left: 20px; - padding-right: 5px; - font-weight: 500; -} - .mx_AppearanceUserSettingsTab { > .mx_SettingsTab_SubHeading { margin-bottom: 32px; @@ -151,10 +96,6 @@ limitations under the License. } } -.mx_SettingsTab_customFontSizeField { - margin-left: calc($font-16px + 10px); -} - .mx_AppearanceUserSettingsTab_Advanced { color: $primary-content; diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index 8cc51e7e352..f1ad2352f22 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -14,33 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SecurityUserSettingsTab_deviceInfo { - display: table; - padding-left: 0; -} - -.mx_SecurityUserSettingsTab_deviceInfo > li { - display: table-row; -} - -.mx_SecurityUserSettingsTab_deviceInfo > li > label, -.mx_SecurityUserSettingsTab_deviceInfo > li > span { - display: table-cell; - padding-right: 1em; -} - -.mx_SecurityUserSettingsTab_importExportButtons .mx_AccessibleButton { - margin-right: 10px; -} - .mx_SecurityUserSettingsTab_bulkOptions .mx_AccessibleButton { margin-right: 10px; } -.mx_SecurityUserSettingsTab_importExportButtons { - margin-bottom: 15px; -} - .mx_SecurityUserSettingsTab_ignoredUser { margin-bottom: 5px; } diff --git a/res/css/views/verification/_VerificationShowSas.scss b/res/css/views/verification/_VerificationShowSas.scss index af003112f7e..5910711fcd0 100644 --- a/res/css/views/verification/_VerificationShowSas.scss +++ b/res/css/views/verification/_VerificationShowSas.scss @@ -62,20 +62,13 @@ limitations under the License. flex-basis: 100%; } -.mx_VerificationShowSas { - .mx_Dialog_buttons { - // this is more specific than the DialogButtons css so gets preference - button.mx_VerificationShowSas_matchButton { - color: $accent-color; - background-color: $accent-bg-color; - border: none; - } +.mx_VerificationShowSas_buttonRow { + text-align: center; + display: flex; + flex-wrap: wrap; + justify-content: center; - // this is more specific than the DialogButtons css so gets preference - button.mx_VerificationShowSas_noMatchButton { - color: $notice-primary-color; - background-color: $notice-primary-bg-color; - border: none; - } + .mx_AccessibleButton { + margin-inline: 9px; } } diff --git a/res/img/element-icons/thread-summary.svg b/res/img/element-icons/thread-summary.svg new file mode 100644 index 00000000000..2c4f0ead0cf --- /dev/null +++ b/res/img/element-icons/thread-summary.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/element-icons/view-in-room.svg b/res/img/element-icons/view-in-room.svg new file mode 100644 index 00000000000..27758b10f61 --- /dev/null +++ b/res/img/element-icons/view-in-room.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index f012dff0a12..ca06d38b1f3 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -71,8 +71,6 @@ $input-focused-border-color: #238cf5; $input-valid-border-color: $accent-color; $input-invalid-border-color: $warning-color; -$field-focused-label-bg-color: $background; - $resend-button-divider-color: #b9bec64a; // muted-text with a 4A opacity. // scrollbars @@ -100,7 +98,6 @@ $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -115,7 +112,6 @@ $groupheader-button-color: $header-panel-text-primary-color; $rightpanel-button-color: $header-panel-text-primary-color; $icon-button-color: $tertiary-content; $roomtopic-color: $text-secondary-color; -$eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; @@ -126,20 +122,12 @@ $theme-button-bg-color: #e3e8f0; $roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons $roomlist-bg-color: rgba(33, 38, 44, 0.90); -$roomlist-header-color: $tertiary-content; -$roomsublist-divider-color: $primary-content; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); -$groupFilterPanel-divider-color: $roomlist-header-color; - -$roomtile-preview-color: $secondary-content; $roomtile-default-badge-bg-color: #61708b; -$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2); // ******************** -$notice-secondary-color: $roomlist-header-color; - $panel-divider-color: transparent; $widget-menu-bar-bg-color: $header-panel-bg-color; @@ -209,10 +197,7 @@ $tooltip-timeline-fg-color: $primary-content; $breadcrumb-placeholder-bg-color: #272c35; $voice-record-stop-border-color: $quaternary-content; -$voice-record-waveform-incomplete-fg-color: $quaternary-content; $voice-record-icon-color: $quaternary-content; -$voice-playback-button-bg-color: $system; -$voice-playback-button-fg-color: $secondary-content; // Appearance tab colors $appearance-tab-border-color: $room-highlight-color; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index bed1e9c6617..0fe9f22dde8 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -23,13 +23,7 @@ $tertiary-fg-color: $primary-fg-color; $primary-bg-color: $bg-color; $muted-fg-color: $header-panel-text-primary-color; -// Legacy theme backports -$primary-content: $primary-fg-color; -$secondary-content: $secondary-fg-color; -$tertiary-content: $tertiary-fg-color; -$quaternary-content: #6F7882; -$quinary-content: $quaternary-content; -$background: $primary-bg-color; + // used for dialog box text $light-fg-color: $header-panel-text-secondary-color; @@ -70,8 +64,6 @@ $input-focused-border-color: #238cf5; $input-valid-border-color: $accent-color; $input-invalid-border-color: $warning-color; -$field-focused-label-bg-color: $bg-color; - $resend-button-divider-color: $muted-fg-color; // scrollbars @@ -99,7 +91,6 @@ $lightbox-background-bg-color: #000; $lightbox-background-bg-opacity: 0.85; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #e7e7e7; $settings-profile-overlay-placeholder-fg-color: #454545; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -114,11 +105,30 @@ $groupheader-button-color: $header-panel-text-primary-color; $rightpanel-button-color: $header-panel-text-primary-color; $icon-button-color: $header-panel-text-primary-color; $roomtopic-color: $text-secondary-color; -$eventtile-meta-color: $roomtopic-color; $header-divider-color: $header-panel-text-primary-color; $composer-e2e-icon-color: $header-panel-text-primary-color; +// Legacy theme backports +$accent: #0DBD8B; +$alert: #FF5B55; +$links: #0086e6; +$primary-content: $primary-fg-color; +$secondary-content: $secondary-fg-color; +$tertiary-content: $tertiary-fg-color; +$quaternary-content: #6F7882; +$quinary-content: $quaternary-content; +$system: #21262C; +$background: $primary-bg-color; +$panels: rgba($system, 0.9); +$panel-base: #8D97A5; // This color is not intended for use in the app +$panel-selected: rgba($panel-base, 0.3); +$panel-hover: rgba($panel-base, 0.1); +$panel-actions: $roomtile-selected-bg-color; +$space-nav: rgba($panel-base, 0.1); + +// Legacy theme backports + // ******************** $theme-button-bg-color: #e3e8f0; @@ -128,12 +138,10 @@ $roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore b $roomlist-filter-active-bg-color: $roomlist-button-bg-color; $roomlist-bg-color: $header-panel-bg-color; -$roomsublist-divider-color: $primary-fg-color; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); -$groupFilterPanel-divider-color: $roomlist-header-color; +$groupFilterPanel-divider-color: $tertiary-content; -$roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: #1A1D23; @@ -206,10 +214,7 @@ $breadcrumb-placeholder-bg-color: #272c35; // See non-legacy dark for variable information $voice-record-stop-border-color: #6F7882; -$voice-record-waveform-incomplete-fg-color: #6F7882; $voice-record-icon-color: #6F7882; -$voice-playback-button-bg-color: $tertiary-fg-color; -$voice-playback-button-fg-color: #21262C; // Appearance tab colors $appearance-tab-border-color: $room-highlight-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 4ac52234064..09dd46d1511 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -18,7 +18,6 @@ $accent-color: #03b381; $accent-bg-color: rgba(3, 179, 129, 0.16); $notice-primary-color: #ff4b55; $notice-primary-bg-color: rgba(255, 75, 85, 0.16); -$notice-secondary-color: #61708b; $header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) @@ -28,24 +27,6 @@ $tertiary-fg-color: $primary-fg-color; $primary-bg-color: #ffffff; $muted-fg-color: #61708b; // Commonly used in headings and relevant alt text -// Legacy theme backports -$accent: #0DBD8B; -$alert: #FF5B55; -$links: #0086e6; -$primary-content: $primary-fg-color; -$secondary-content: $secondary-fg-color; -$tertiary-content: $tertiary-fg-color; -$quaternary-content: #C1C6CD; -$quinary-content: #e3e8f0; -$system: #F4F6FA; -$background: $primary-bg-color; -$panels: rgba($system, 0.9); -$panel-base: #8D97A5; // This color is not intended for use in the app -$panel-selected: rgba($tertiary-content, 0.3); -$panel-hover: rgba($tertiary-content, 0.1); -$panel-actions: rgba($tertiary-content, 0.2); -$space-nav: rgba($tertiary-content, 0.15); - // used for dialog box text $light-fg-color: #747474; @@ -71,10 +52,6 @@ $info-bg-color: #2a9edf; $mention-user-pill-bg-color: $warning-color; $other-user-pill-bg-color: rgba(0, 0, 0, 0.1); -// pinned events indicator -$pinned-unread-color: $notice-primary-color; -$pinned-color: $notice-secondary-color; - // informational plinth $info-plinth-bg-color: #f7f7f7; $info-plinth-fg-color: #888; @@ -113,8 +90,6 @@ $input-focused-border-color: #238cf5; $input-valid-border-color: $accent-color; $input-invalid-border-color: $warning-color; -$field-focused-label-bg-color: #ffffff; - $resend-button-divider-color: $input-darker-bg-color; $button-bg-color: $accent-color; @@ -164,7 +139,6 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: #e7e7e7; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -188,13 +162,10 @@ $groupheader-button-color: #91a1c0; $rightpanel-button-color: #91a1c0; $icon-button-color: #91a1c0; $roomtopic-color: #9e9e9e; -$eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91a1c0; $header-divider-color: #91a1c0; -$voipcall-plinth-color: $system; - // ******************** $theme-button-bg-color: #e3e8f0; @@ -203,12 +174,8 @@ $roomlist-button-bg-color: #fff; // Buttons include the filter box, explore butt $roomlist-filter-active-bg-color: $roomlist-button-bg-color; $roomlist-bg-color: $header-panel-bg-color; $roomlist-header-color: $primary-fg-color; -$roomsublist-divider-color: $primary-fg-color; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%); -$groupFilterPanel-divider-color: $roomlist-header-color; - -$roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: #fff; @@ -216,8 +183,29 @@ $presence-online: $accent-color; $presence-away: #d9b072; $presence-offline: #e3e8f0; +// Legacy theme backports +$accent: #0DBD8B; +$alert: #FF5B55; +$links: #0086e6; +$primary-content: $primary-fg-color; +$secondary-content: $secondary-fg-color; +$tertiary-content: $tertiary-fg-color; +$quaternary-content: #6F7882; +$quinary-content: $quaternary-content; +$system: #F4F6FA; +$background: $primary-bg-color; +$panels: rgba($system, 0.9); +$panel-base: #8D97A5; // This color is not intended for use in the app +$panel-selected: rgba($tertiary-content, 0.3); +$panel-hover: rgba($tertiary-content, 0.1); +$panel-actions: $roomtile-selected-bg-color; +$space-nav: rgba($tertiary-content, 0.15); +// Legacy theme backports + // ******************** +$voipcall-plinth-color: $system; + $username-variant1-color: #368bd6; $username-variant2-color: #ac3ba8; $username-variant3-color: #03b381; @@ -339,10 +327,7 @@ $breadcrumb-placeholder-bg-color: #e8eef5; $voice-record-stop-symbol-color: #ff4b55; $voice-record-live-circle-color: #ff4b55; $voice-record-stop-border-color: #E3E8F0; -$voice-record-waveform-incomplete-fg-color: #C1C6CD; $voice-record-icon-color: $tertiary-fg-color; -$voice-playback-button-bg-color: $system; -$voice-playback-button-fg-color: $secondary-content; // FontSlider colors $appearance-tab-border-color: $input-darker-bg-color; @@ -356,6 +341,12 @@ $eventbubble-bg-hover: #FAFBFD; $eventbubble-avatar-outline: #fff; $eventbubble-reply-color: #C1C6CD; +// pinned events indicator +$pinned-unread-color: $notice-primary-color; +$pinned-color: $tertiary-content; + +$groupFilterPanel-divider-color: $tertiary-content; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index fca4f88a9e1..2fbb91c5236 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -52,7 +52,6 @@ $accent-color-50pct: var(--accent-color-50pct); //still needs alpha at .5 // --timeline-background-color $authpage-body-bg-color: var(--timeline-background-color); $button-secondary-bg-color: var(--timeline-background-color); -$field-focused-label-bg-color: var(--timeline-background-color); $lightbox-border-color: var(--timeline-background-color); $menu-bg-color: var(--timeline-background-color); $avatar-bg-color: var(--timeline-background-color); @@ -64,7 +63,7 @@ $authpage-modal-bg-color: var(--timeline-background-color-50pct); //still needs $roomheader-bg-color: var(--timeline-background-color); // // --roomlist-highlights-color -$roomtile-selected-bg-color: var(--roomlist-highlights-color); +$panel-actions: var(--roomlist-highlights-color); // // --sidebar-color $groupFilterPanel-bg-color: var(--sidebar-color); @@ -103,8 +102,6 @@ $roomheader-color: var(--timeline-text-color); // was #232f32 $authpage-primary-color: var(--timeline-text-color); // --roomlist-text-secondary-color -$roomtile-preview-color: var(--roomlist-text-secondary-color); -$roomlist-header-color: var(--roomlist-text-secondary-color); $roomtile-default-badge-bg-color: var(--roomlist-text-secondary-color); // @@ -112,12 +109,10 @@ $roomtile-default-badge-bg-color: var(--roomlist-text-secondary-color); $input-darker-bg-color: var(--roomlist-separator-color); $panel-divider-color: var(--roomlist-separator-color);// originally #dee1f3, but close enough $primary-hairline-color: var(--roomlist-separator-color);// originally #e5e5e5, but close enough -$roomsublist-divider-color: var(--roomlist-separator-color); // // --timeline-text-secondary-color $authpage-secondary-color: var(--timeline-text-secondary-color); $memberstatus-placeholder-color: var(--timeline-text-secondary-color); -$notice-secondary-color: var(--timeline-text-secondary-color); $pinned-color: var(--timeline-text-secondary-color); $settings-subsection-fg-color: var(--timeline-text-secondary-color); $roomheader-addroom-bg-color: var(--timeline-text-secondary-color); diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 26cd1766c1e..1733831b951 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -101,8 +101,6 @@ $input-focused-border-color: #238cf5; $input-valid-border-color: $accent-color; $input-invalid-border-color: $warning-color; -$field-focused-label-bg-color: $background; - $button-bg-color: $accent-color; $button-fg-color: white; @@ -152,7 +150,6 @@ $blockquote-bar-color: #ddd; $blockquote-fg-color: #777; $settings-grey-fg-color: #a2a2a2; -$settings-profile-placeholder-bg-color: $system; $settings-profile-overlay-placeholder-fg-color: #2e2f32; $settings-profile-button-bg-color: #e7e7e7; $settings-profile-button-fg-color: $settings-profile-overlay-placeholder-fg-color; @@ -175,7 +172,6 @@ $groupheader-button-color: #91A1C0; $rightpanel-button-color: #91A1C0; $icon-button-color: $quaternary-content; $roomtopic-color: #9e9e9e; -$eventtile-meta-color: $roomtopic-color; $composer-e2e-icon-color: #91A1C0; $header-divider-color: #91A1C0; @@ -188,15 +184,9 @@ $theme-button-bg-color: $quinary-content; $roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons $roomlist-bg-color: rgba(245, 245, 245, 0.90); -$roomlist-header-color: $tertiary-content; -$roomsublist-divider-color: $primary-content; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%); -$groupFilterPanel-divider-color: $roomlist-header-color; - -$roomtile-preview-color: $secondary-content; $roomtile-default-badge-bg-color: #61708b; -$roomtile-selected-bg-color: #FFF; $presence-online: $accent-color; $presence-away: #d9b072; @@ -213,13 +203,11 @@ $username-variant6-color: #2dc2c5; $username-variant7-color: #5c56f5; $username-variant8-color: #74d12c; -$notice-secondary-color: $roomlist-header-color; - $panel-divider-color: transparent; // pinned events indicator $pinned-unread-color: $notice-primary-color; -$pinned-color: $notice-secondary-color; +$pinned-color: $tertiary-content; // ******************** @@ -334,10 +322,7 @@ $voice-record-stop-symbol-color: #ff4b55; $voice-record-live-circle-color: #ff4b55; $voice-record-stop-border-color: $quinary-content; -$voice-record-waveform-incomplete-fg-color: $quaternary-content; $voice-record-icon-color: $tertiary-content; -$voice-playback-button-bg-color: $system; -$voice-playback-button-fg-color: $secondary-content; // FontSlider colors $appearance-tab-border-color: $input-darker-bg-color; diff --git a/src/ActiveRoomObserver.ts b/src/ActiveRoomObserver.ts index c7423fab8fc..4638509cc9f 100644 --- a/src/ActiveRoomObserver.ts +++ b/src/ActiveRoomObserver.ts @@ -17,6 +17,8 @@ limitations under the License. import { EventSubscription } from 'fbemitter'; import RoomViewStore from './stores/RoomViewStore'; +import { logger } from "matrix-js-sdk/src/logger"; + type Listener = (isActive: boolean) => void; /** @@ -54,7 +56,7 @@ export class ActiveRoomObserver { this.listeners[roomId].splice(i, 1); } } else { - console.warn("Unregistering unrecognised listener (roomId=" + roomId + ")"); + logger.warn("Unregistering unrecognised listener (roomId=" + roomId + ")"); } } diff --git a/src/Analytics.tsx b/src/Analytics.tsx index fc4664039f6..3ff7b2a7678 100644 --- a/src/Analytics.tsx +++ b/src/Analytics.tsx @@ -23,6 +23,8 @@ import SdkConfig from './SdkConfig'; import Modal from './Modal'; import * as sdk from './index'; +import { logger } from "matrix-js-sdk/src/logger"; + const hashRegex = /#\/(groups?|room|user|settings|register|login|forgot_password|home|directory)/; const hashVarRegex = /#\/(group|room|user)\/.*$/; @@ -31,7 +33,7 @@ function getRedactedHash(hash: string): string { // Don't leak URLs we aren't expecting - they could contain tokens/PII const match = hashRegex.exec(hash); if (!match) { - console.warn(`Unexpected hash location "${hash}"`); + logger.warn(`Unexpected hash location "${hash}"`); return '#/'; } @@ -156,7 +158,7 @@ function getUid(): string { } return data; } catch (e) { - console.error("Analytics error: ", e); + logger.error("Analytics error: ", e); return ""; } } @@ -299,7 +301,7 @@ export class Analytics { redirect: "follow", }); } catch (e) { - console.error("Analytics error: ", e); + logger.error("Analytics error: ", e); } } @@ -320,7 +322,7 @@ export class Analytics { } if (typeof generationTimeMs !== 'number') { - console.warn('Analytics.trackPageChange: expected generationTimeMs to be a number'); + logger.warn('Analytics.trackPageChange: expected generationTimeMs to be a number'); // But continue anyway because we still want to track the change } diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx index 68e33e02fe9..aea0f42ac4c 100644 --- a/src/AsyncWrapper.tsx +++ b/src/AsyncWrapper.tsx @@ -61,7 +61,7 @@ export default class AsyncWrapper extends React.Component { : result as ComponentType; this.setState({ component }); }).catch((e) => { - console.warn('AsyncWrapper promise failed', e); + logger.warn('AsyncWrapper promise failed', e); this.setState({ error: e }); }); } diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 5b4b15cc674..f3b26ec5104 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -28,6 +28,8 @@ import { hideToast as hideUpdateToast } from "./toasts/UpdateToast"; import { MatrixClientPeg } from "./MatrixClientPeg"; import { idbLoad, idbSave, idbDelete } from "./utils/StorageManager"; +import { logger } from "matrix-js-sdk/src/logger"; + export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url"; export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url"; export const SSO_IDP_ID_KEY = "mx_sso_idp_id"; @@ -320,7 +322,7 @@ export default abstract class BasePlatform { return null; } if (!data.encrypted || !data.iv || !data.cryptoKey) { - console.error("Badly formatted pickle key"); + logger.error("Badly formatted pickle key"); return null; } @@ -340,7 +342,7 @@ export default abstract class BasePlatform { ); return encodeUnpaddedBase64(key); } catch (e) { - console.error("Error decrypting pickle key"); + logger.error("Error decrypting pickle key"); return null; } } diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index bc9ccc4bcd7..69b00927a9c 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -444,7 +444,7 @@ export default class CallHandler extends EventEmitter { if (!this.matchesCallForThisRoom(call)) return; Analytics.trackEvent('voip', 'callError', 'error', err.toString()); - console.error("Call error:", err); + logger.error("Call error:", err); if (err.code === CallErrorCode.NoUserMedia) { this.showMediaCaptureError(call); @@ -777,7 +777,7 @@ export default class CallHandler extends EventEmitter { } else if (type === 'video') { call.placeVideoCall(); } else { - console.error("Unknown conf call type: " + type); + logger.error("Unknown conf call type: " + type); } } @@ -811,7 +811,7 @@ export default class CallHandler extends EventEmitter { const room = MatrixClientPeg.get().getRoom(payload.room_id); if (!room) { - console.error(`Room ${payload.room_id} does not exist.`); + logger.error(`Room ${payload.room_id} does not exist.`); return; } @@ -825,7 +825,7 @@ export default class CallHandler extends EventEmitter { }); return; } else if (members.length === 2) { - console.info(`Place ${payload.type} call in ${payload.room_id}`); + logger.info(`Place ${payload.type} call in ${payload.room_id}`); this.placeCall(payload.room_id, payload.type, payload.transferee); } else { // > 2 @@ -838,17 +838,17 @@ export default class CallHandler extends EventEmitter { } break; case 'place_conference_call': - console.info("Place conference call in " + payload.room_id); + logger.info("Place conference call in " + payload.room_id); Analytics.trackEvent('voip', 'placeConferenceCall'); CountlyAnalytics.instance.trackStartCall(payload.room_id, payload.type === PlaceCallType.Video, true); this.startCallApp(payload.room_id, payload.type); break; case 'end_conference': - console.info("Terminating conference call in " + payload.room_id); + logger.info("Terminating conference call in " + payload.room_id); this.terminateCallApp(payload.room_id); break; case 'hangup_conference': - console.info("Leaving conference call in "+ payload.room_id); + logger.info("Leaving conference call in "+ payload.room_id); this.hangupCallApp(payload.room_id); break; case 'incoming_call': @@ -1112,7 +1112,7 @@ export default class CallHandler extends EventEmitter { description: _t("You do not have permission to start a conference call in this room"), }); } - console.error(e); + logger.error(e); }); } diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index 60242b373a2..9831a9b41db 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -419,7 +419,7 @@ export default class ContentMessages { sendStickerContentToRoom(url: string, roomId: string, info: IImageInfo, text: string, matrixClient: MatrixClient) { const startTime = CountlyAnalytics.getTimestamp(); const prom = matrixClient.sendStickerMessage(roomId, url, info, text).catch((e) => { - console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e); + logger.warn(`Failed to send content with URL ${url} to room ${roomId}`, e); throw e; }); CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, false, { msgtype: "m.sticker" }); @@ -559,7 +559,7 @@ export default class ContentMessages { Object.assign(content.info, imageInfo); resolve(); }, (e) => { - console.error(e); + logger.error(e); content.msgtype = 'm.file'; resolve(); }); diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts index aa47d3063f5..d99ec3d78a5 100644 --- a/src/CountlyAnalytics.ts +++ b/src/CountlyAnalytics.ts @@ -25,6 +25,8 @@ import { MatrixClientPeg } from "./MatrixClientPeg"; import RoomViewStore from "./stores/RoomViewStore"; import { Action } from "./dispatcher/actions"; +import { logger } from "matrix-js-sdk/src/logger"; + const INACTIVITY_TIME = 20; // seconds const HEARTBEAT_INTERVAL = 5_000; // ms const SESSION_UPDATE_INTERVAL = 60; // seconds @@ -427,7 +429,7 @@ export default class CountlyAnalytics { try { this.appVersion = await platform.getAppVersion(); } catch (e) { - console.warn("Failed to get app version, using 'unknown'"); + logger.warn("Failed to get app version, using 'unknown'"); } // start heartbeat @@ -651,7 +653,7 @@ export default class CountlyAnalytics { body: params, }); } catch (e) { - console.error("Analytics error: ", e); + logger.error("Analytics error: ", e); } } diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 2eee5214af5..7087386128d 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -34,7 +34,7 @@ import linkifyMatrix from './linkify-matrix'; import SettingsStore from './settings/SettingsStore'; import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks"; import { getEmojiFromUnicode } from "./emoji"; -import ReplyThread from "./components/views/elements/ReplyThread"; +import ReplyChain from "./components/views/elements/ReplyChain"; import { mediaFromMxc } from "./customisations/Media"; linkifyMatrix(linkify); @@ -446,8 +446,8 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts let formattedBody = typeof content.formatted_body === 'string' ? content.formatted_body : null; const plainBody = typeof content.body === 'string' ? content.body : ""; - if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody); - strippedBody = opts.stripReplyFallback ? ReplyThread.stripPlainReply(plainBody) : plainBody; + if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyChain.stripHTMLReply(formattedBody); + strippedBody = opts.stripReplyFallback ? ReplyChain.stripPlainReply(plainBody) : plainBody; bodyHasEmoji = mightContainEmoji(isHtmlMessage ? formattedBody : plainBody); diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 3685f7b9388..d351c46180a 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -111,7 +111,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise const defaultDeviceDisplayName = opts.defaultDeviceDisplayName; if (enableGuest && !guestHsUrl) { - console.warn("Cannot enable guest access: can't determine HS URL to use"); + logger.warn("Cannot enable guest access: can't determine HS URL to use"); enableGuest = false; } @@ -188,7 +188,7 @@ export function attemptTokenLogin( const homeserver = localStorage.getItem(SSO_HOMESERVER_URL_KEY); const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY); if (!homeserver) { - console.warn("Cannot log in with token: can't determine HS URL to use"); + logger.warn("Cannot log in with token: can't determine HS URL to use"); Modal.createTrackedDialog("SSO", "Unknown HS", ErrorDialog, { title: _t("We couldn't log you in"), description: _t("We asked the browser to remember which homeserver you use to let you sign in, " + @@ -234,8 +234,8 @@ export function attemptTokenLogin( } }, }); - console.error("Failed to log in with login token:"); - console.error(err); + logger.error("Failed to log in with login token:"); + logger.error(err); return false; }); } @@ -297,7 +297,7 @@ function registerAsGuest( guest: true, }, true).then(() => true); }, (err) => { - console.error("Failed to register as guest", err); + logger.error("Failed to register as guest", err); return false; }); } @@ -452,7 +452,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): } async function handleLoadSessionFailure(e: Error): Promise { - console.error("Unable to load session", e); + logger.error("Unable to load session", e); const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, { error: e.message, @@ -523,7 +523,7 @@ export function hydrateSession(credentials: IMatrixClientCreds): Promise { await EventIndexPeg.init(); await MatrixClientPeg.start(); } else { - console.warn("Caller requested only auxiliary services be started"); + logger.warn("Caller requested only auxiliary services be started"); await MatrixClientPeg.assign(); } diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 3860a5c133b..1a92f0f058b 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -173,12 +173,12 @@ class MatrixClientPegClass implements IMatrixClientPeg { break; } catch (err) { if (dbType === 'indexeddb') { - console.error('Error starting matrixclient store - falling back to memory store', err); + logger.error('Error starting matrixclient store - falling back to memory store', err); this.matrixClient.store = new MemoryStore({ localStorage: localStorage, }); } else { - console.error('Failed to start memory store!', err); + logger.error('Failed to start memory store!', err); throw err; } } @@ -207,7 +207,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { } // this can happen for a number of reasons, the most likely being // that the olm library was missing. It's not fatal. - console.warn("Unable to initialise e2e", e); + logger.warn("Unable to initialise e2e", e); } const opts = utils.deepCopy(this.opts); diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts index 154f167745a..9123d45bc3b 100644 --- a/src/MediaDeviceHandler.ts +++ b/src/MediaDeviceHandler.ts @@ -20,6 +20,8 @@ import { SettingLevel } from "./settings/SettingLevel"; import EventEmitter from 'events'; import { MatrixClientPeg } from "./MatrixClientPeg"; +import { logger } from "matrix-js-sdk/src/logger"; + // XXX: MediaDeviceKind is a union type, so we make our own enum export enum MediaDeviceKindEnum { AudioOutput = "audiooutput", @@ -63,7 +65,7 @@ export default class MediaDeviceHandler extends EventEmitter { devices.forEach((device) => output[device.kind].push(device)); return output; } catch (error) { - console.warn('Unable to refresh WebRTC Devices: ', error); + logger.warn('Unable to refresh WebRTC Devices: ', error); } } diff --git a/src/Notifier.ts b/src/Notifier.ts index 81c9bf7f4fd..1acd028b084 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -141,12 +141,12 @@ export const Notifier = { } if (!content.url) { - console.warn(`${roomId} has custom notification sound event, but no url key`); + logger.warn(`${roomId} has custom notification sound event, but no url key`); return null; } if (!content.url.startsWith("mxc://")) { - console.warn(`${roomId} has custom notification sound event, but url is not a mxc url`); + logger.warn(`${roomId} has custom notification sound event, but url is not a mxc url`); return null; } @@ -170,7 +170,7 @@ export const Notifier = { let audioElement = selector; if (!selector) { if (!sound) { - console.error("No audio element or sound to play for notification"); + logger.error("No audio element or sound to play for notification"); return; } audioElement = new Audio(sound.url); @@ -181,7 +181,7 @@ export const Notifier = { } await audioElement.play(); } catch (ex) { - console.warn("Caught error when trying to fetch room notification sound:", ex); + logger.warn("Caught error when trying to fetch room notification sound:", ex); } }, diff --git a/src/Presence.ts b/src/Presence.ts index af350603633..e58ed61901a 100644 --- a/src/Presence.ts +++ b/src/Presence.ts @@ -21,6 +21,8 @@ import dis from "./dispatcher/dispatcher"; import Timer from './utils/Timer'; import { ActionPayload } from "./dispatcher/payloads"; +import { logger } from "matrix-js-sdk/src/logger"; + // Time in ms after that a user is considered as unavailable/away const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins @@ -99,9 +101,9 @@ class Presence { try { await MatrixClientPeg.get().setPresence({ presence: this.state }); - console.info("Presence:", newState); + logger.info("Presence:", newState); } catch (err) { - console.error("Failed to set presence:", err); + logger.error("Failed to set presence:", err); this.state = oldState; } } diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx index 5c9d96f5093..305cf47b273 100644 --- a/src/RoomInvite.tsx +++ b/src/RoomInvite.tsx @@ -30,6 +30,8 @@ import BaseAvatar from "./components/views/avatars/BaseAvatar"; import { mediaFromMxc } from "./customisations/Media"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; +import { logger } from "matrix-js-sdk/src/logger"; + export interface IInviteResult { states: CompletionStates; inviter: MultiInviter; @@ -114,7 +116,7 @@ export function inviteUsersToRoom(roomId: string, userIds: string[], progressCal const room = MatrixClientPeg.get().getRoom(roomId); showAnyInviteErrors(result.states, room, result.inviter); }).catch((err) => { - console.error(err.stack); + logger.error(err.stack); Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { title: _t("Failed to invite"), description: ((err && err.message) ? err.message : _t("Operation failed")), diff --git a/src/ScalarMessaging.ts b/src/ScalarMessaging.ts index d068c1f9247..3d390ea095d 100644 --- a/src/ScalarMessaging.ts +++ b/src/ScalarMessaging.ts @@ -272,7 +272,7 @@ function sendResponse(event: MessageEvent, res: any): void { } function sendError(event: MessageEvent, msg: string, nestedError?: Error): void { - console.error("Action:" + event.data.action + " failed with message: " + msg); + logger.error("Action:" + event.data.action + " failed with message: " + msg); const data = objectClone(event.data); data.response = { error: { @@ -695,7 +695,7 @@ const onMessage = function(event: MessageEvent): void { setBotPower(event, roomId, userId, event.data.level, event.data.ignoreIfGreater); break; default: - console.warn("Unhandled postMessage event with action '" + event.data.action +"'"); + logger.warn("Unhandled postMessage event with action '" + event.data.action +"'"); break; } }; @@ -721,7 +721,7 @@ export function stopListening(): void { "ScalarMessaging: mismatched startListening / stopListening detected." + " Negative count", ); - console.error(e); + logger.error(e); } } diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index 925b023584a..a184e6e9cbd 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -283,7 +283,7 @@ async function onSecretRequested( } return key && encodeBase64(key); } - console.warn("onSecretRequested didn't recognise the secret named ", name); + logger.warn("onSecretRequested didn't recognise the secret named ", name); } export const crossSigningCallbacks: ICryptoCallbacks = { @@ -388,7 +388,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f logger.log("Setting dehydration key"); await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device"); } else if (!keyId) { - console.warn("Not setting dehydration key: no SSSS key found"); + logger.warn("Not setting dehydration key: no SSSS key found"); } else { logger.log("Not setting dehydration key: feature disabled"); } @@ -399,7 +399,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f return await func(); } catch (e) { SecurityCustomisations.catchAccessSecretStorageError?.(e); - console.error(e); + logger.error(e); // Re-throw so that higher level logic can abort as needed throw e; } finally { diff --git a/src/SendHistoryManager.ts b/src/SendHistoryManager.ts index eeba643d81b..9a593adbee6 100644 --- a/src/SendHistoryManager.ts +++ b/src/SendHistoryManager.ts @@ -20,6 +20,8 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { SerializedPart } from "./editor/parts"; import EditorModel from "./editor/model"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IHistoryItem { parts: SerializedPart[]; replyEventId?: string; @@ -42,7 +44,7 @@ export default class SendHistoryManager { try { this.history.push(JSON.parse(itemJSON)); } catch (e) { - console.warn("Throwing away unserialisable history", e); + logger.warn("Throwing away unserialisable history", e); break; } ++index; diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 6fb4107d200..f853d86cc01 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -27,6 +27,8 @@ import { SetRightPanelPhasePayload } from './dispatcher/payloads/SetRightPanelPh import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixClientPeg } from "./MatrixClientPeg"; +import { logger } from "matrix-js-sdk/src/logger"; + // These functions are frequently used just to check whether an event has // any text to display at all. For this reason they return deferred values // to avoid the expense of looking up translations when they're not needed. @@ -122,7 +124,7 @@ function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents return null; } } else { - if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); + if (!ev.target) logger.warn("Join message has no target! -- " + ev.getContent().state_key); return () => _t('%(targetName)s joined the room', { targetName }); } case 'leave': diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts index a7f629c40de..d248197f7f7 100644 --- a/src/actions/RoomListActions.ts +++ b/src/actions/RoomListActions.ts @@ -27,6 +27,8 @@ import { SortAlgorithm } from "../stores/room-list/algorithms/models"; import { DefaultTagID } from "../stores/room-list/models"; import ErrorDialog from '../components/views/dialogs/ErrorDialog'; +import { logger } from "matrix-js-sdk/src/logger"; + export default class RoomListActions { /** * Creates an action thunk that will do an asynchronous request to @@ -88,7 +90,7 @@ export default class RoomListActions { return Rooms.guessAndSetDMRoom( room, newTag === DefaultTagID.DM, ).catch((err) => { - console.error("Failed to set direct chat tag " + err); + logger.error("Failed to set direct chat tag " + err); Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, { title: _t('Failed to set direct chat tag'), description: ((err && err.message) ? err.message : _t('Operation failed')), @@ -108,7 +110,7 @@ export default class RoomListActions { const promiseToDelete = matrixClient.deleteRoomTag( roomId, oldTag, ).catch(function(err) { - console.error("Failed to remove tag " + oldTag + " from room: " + err); + logger.error("Failed to remove tag " + oldTag + " from room: " + err); Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, { title: _t('Failed to remove tag %(tagName)s from room', { tagName: oldTag }), description: ((err && err.message) ? err.message : _t('Operation failed')), @@ -127,7 +129,7 @@ export default class RoomListActions { metaData = metaData || {}; const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function(err) { - console.error("Failed to add tag " + newTag + " to room: " + err); + logger.error("Failed to add tag " + newTag + " to room: " + err); Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, { title: _t('Failed to add tag %(tagName)s to room', { tagName: newTag }), description: ((err && err.message) ? err.message : _t('Operation failed')), diff --git a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js index 2cef1c0e417..8527f788959 100644 --- a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js @@ -26,6 +26,8 @@ import AccessibleButton from "../../../../components/views/elements/AccessibleBu import { copyNode } from "../../../../utils/strings"; import PassphraseField from "../../../../components/views/auth/PassphraseField"; +import { logger } from "matrix-js-sdk/src/logger"; + const PHASE_PASSPHRASE = 0; const PHASE_PASSPHRASE_CONFIRM = 1; const PHASE_SHOWKEY = 2; @@ -129,7 +131,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent { phase: PHASE_DONE, }); } catch (e) { - console.error("Error creating key backup", e); + logger.error("Error creating key backup", e); // TODO: If creating a version succeeds, but backup fails, should we // delete the version, disable backup, or do nothing? If we just // disable without deleting, we'll enable on next app reload since diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js index 5fbc97d2f15..7a21b7075bb 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js @@ -349,7 +349,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } else { this.setState({ error: e }); } - console.error("Error bootstrapping secret storage", e); + logger.error("Error bootstrapping secret storage", e); } } diff --git a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js index dbed9f39683..c21e17a7a12 100644 --- a/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js @@ -23,6 +23,8 @@ import { MatrixClient } from 'matrix-js-sdk/src/client'; import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; import * as sdk from '../../../../index'; +import { logger } from "matrix-js-sdk/src/logger"; + const PHASE_EDIT = 1; const PHASE_EXPORTING = 2; @@ -83,7 +85,7 @@ export default class ExportE2eKeysDialog extends React.Component { FileSaver.saveAs(blob, 'element-keys.txt'); this.props.onFinished(true); }).catch((e) => { - console.error("Error exporting e2e keys:", e); + logger.error("Error exporting e2e keys:", e); if (this._unmounted) { return; } diff --git a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js index 0936ad696d4..51d2861396f 100644 --- a/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js @@ -22,6 +22,8 @@ import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryptio import * as sdk from '../../../../index'; import { _t } from '../../../../languageHandler'; +import { logger } from "matrix-js-sdk/src/logger"; + function readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -91,7 +93,7 @@ export default class ImportE2eKeysDialog extends React.Component { // TODO: it would probably be nice to give some feedback about what we've imported here. this.props.onFinished(true); }).catch((e) => { - console.error("Error importing e2e keys:", e); + logger.error("Error importing e2e keys:", e); if (this._unmounted) { return; } diff --git a/src/audio/Playback.ts b/src/audio/Playback.ts index 9ad4c85df52..f3baa71a1ea 100644 --- a/src/audio/Playback.ts +++ b/src/audio/Playback.ts @@ -156,18 +156,18 @@ export class Playback extends EventEmitter implements IDestroyable { try { // This error handler is largely for Safari as well, which doesn't support Opus/Ogg // very well. - console.error("Error decoding recording: ", e); - console.warn("Trying to re-encode to WAV instead..."); + logger.error("Error decoding recording: ", e); + logger.warn("Trying to re-encode to WAV instead..."); const wav = await decodeOgg(this.buf); // noinspection ES6MissingAwait - not needed when using callbacks this.context.decodeAudioData(wav, b => resolve(b), e => { - console.error("Still failed to decode recording: ", e); + logger.error("Still failed to decode recording: ", e); reject(e); }); } catch (e) { - console.error("Caught decoding error:", e); + logger.error("Caught decoding error:", e); reject(e); } }); diff --git a/src/audio/PlaybackQueue.ts b/src/audio/PlaybackQueue.ts index 611b88938aa..a1a86f6613f 100644 --- a/src/audio/PlaybackQueue.ts +++ b/src/audio/PlaybackQueue.ts @@ -26,6 +26,8 @@ import { isVoiceMessage } from "../utils/EventUtils"; import RoomViewStore from "../stores/RoomViewStore"; import { EventType } from "matrix-js-sdk/src/@types/event"; +import { logger } from "matrix-js-sdk/src/logger"; + /** * Audio playback queue management for a given room. This keeps track of where the user * was at for each playback, what order the playbacks were played in, and triggers subsequent @@ -114,7 +116,7 @@ export class PlaybackQueue { if (next) { const instance = this.playbacks.get(next); if (!instance) { - console.warn( + logger.warn( "Voice message queue desync: Missing playback for next message: " + `Current=${this.currentPlaybackId} Last=${last} Next=${next}`, ); @@ -173,7 +175,7 @@ export class PlaybackQueue { } } } else { - console.warn( + logger.warn( "Voice message queue desync: Expected playback stop to be last in order. " + `Current=${this.currentPlaybackId} Last=${last} EventID=${mxEvent.getId()}`, ); diff --git a/src/audio/VoiceRecording.ts b/src/audio/VoiceRecording.ts index 67b2acda0c2..3ff02be7749 100644 --- a/src/audio/VoiceRecording.ts +++ b/src/audio/VoiceRecording.ts @@ -32,6 +32,8 @@ import { FixedRollingArray } from "../utils/FixedRollingArray"; import { clamp } from "../utils/numbers"; import mxRecorderWorkletPath from "./RecorderWorklet"; +import { logger } from "matrix-js-sdk/src/logger"; + const CHANNELS = 1; // stereo isn't important export const SAMPLE_RATE = 48000; // 48khz is what WebRTC uses. 12khz is where we lose quality. const BITRATE = 24000; // 24kbps is pretty high quality for our use case in opus. @@ -171,9 +173,9 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { this.buffer = newBuf; }; } catch (e) { - console.error("Error starting recording: ", e); + logger.error("Error starting recording: ", e); if (e instanceof DOMException) { // Unhelpful DOMExceptions are common - parse them sanely - console.error(`${e.name} (${e.code}): ${e.message}`); + logger.error(`${e.name} (${e.code}): ${e.message}`); } // Clean up as best as possible diff --git a/src/components/structures/EmbeddedPage.tsx b/src/components/structures/EmbeddedPage.tsx index ec37eab2549..40aea55c703 100644 --- a/src/components/structures/EmbeddedPage.tsx +++ b/src/components/structures/EmbeddedPage.tsx @@ -27,6 +27,8 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import AutoHideScrollbar from "./AutoHideScrollbar"; import { ActionPayload } from "../../dispatcher/payloads"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { // URL to request embedded page content from url?: string; @@ -79,7 +81,7 @@ export default class EmbeddedPage extends React.PureComponent { } if (err || response.status < 200 || response.status >= 300) { - console.warn(`Error loading page: ${err}`); + logger.warn(`Error loading page: ${err}`); this.setState({ page: _t("Couldn't load page") }); return; } diff --git a/src/components/structures/FilePanel.tsx b/src/components/structures/FilePanel.tsx index 77629839d93..c57c2f7ecf2 100644 --- a/src/components/structures/FilePanel.tsx +++ b/src/components/structures/FilePanel.tsx @@ -39,6 +39,8 @@ import { TileShape } from '../views/rooms/EventTile'; import { Layout } from "../../settings/Layout"; import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { roomId: string; onClose: () => void; @@ -210,10 +212,10 @@ class FilePanel extends React.Component { this.setState({ timelineSet: timelineSet }); } catch (error) { - console.error("Failed to get or create file panel filter", error); + logger.error("Failed to get or create file panel filter", error); } } else { - console.error("Failed to add filtered timelineSet for FilePanel as no room!"); + logger.error("Failed to add filtered timelineSet for FilePanel as no room!"); } } diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index f4f1d50d634..298fa0a4b64 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -45,6 +45,8 @@ import { createSpaceFromCommunity } from "../../utils/space"; import { Action } from "../../dispatcher/actions"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; +import { logger } from "matrix-js-sdk/src/logger"; + const LONG_DESC_PLACEHOLDER = _td( `

HTML for your community's page

@@ -186,7 +188,7 @@ class FeaturedRoom extends React.Component { this.props.groupId, this.props.summaryInfo.room_id, ).catch((err) => { - console.error('Error whilst removing room from group summary', err); + logger.error('Error whilst removing room from group summary', err); const roomName = this.props.summaryInfo.name || this.props.summaryInfo.canonical_alias || this.props.summaryInfo.room_id; @@ -352,7 +354,7 @@ class FeaturedUser extends React.Component { this.props.groupId, this.props.summaryInfo.user_id, ).catch((err) => { - console.error('Error whilst removing user from group summary', err); + logger.error('Error whilst removing user from group summary', err); const displayName = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id; const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog( @@ -551,7 +553,7 @@ export default class GroupView extends React.Component { }, }); }).catch((e) => { - console.error('Error getting group inviter profile', e); + logger.error('Error getting group inviter profile', e); }).finally(() => { if (this._unmounted) return; this.setState({ @@ -641,7 +643,7 @@ export default class GroupView extends React.Component { }).catch((e) => { this.setState({ uploadingAvatar: false }); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Failed to upload avatar image", e); + logger.error("Failed to upload avatar image", e); Modal.createTrackedDialog('Failed to upload image', '', ErrorDialog, { title: _t('Error'), description: _t('Failed to upload image'), @@ -675,7 +677,7 @@ export default class GroupView extends React.Component { saving: false, }); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Failed to save community profile", e); + logger.error("Failed to save community profile", e); Modal.createTrackedDialog('Failed to update group', '', ErrorDialog, { title: _t('Error'), description: _t('Failed to update community'), @@ -1421,7 +1423,7 @@ export default class GroupView extends React.Component { ); } } else { - console.error("Invalid state for GroupView"); + logger.error("Invalid state for GroupView"); return

; } } diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index 970ea26309c..36489ac4a04 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -29,6 +29,8 @@ import getEntryComponentForLoginType, { IStageComponent } from '../views/auth/In import Spinner from "../views/elements/Spinner"; import { replaceableComponent } from "../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); interface IProps { @@ -137,7 +139,7 @@ export default class InteractiveAuthComponent extends React.Component { this.props.onAuthFinished(false, error); - console.error("Error during user-interactive auth:", error); + logger.error("Error during user-interactive auth:", error); if (this.unmounted) { return; } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d649c702bcf..f0a7e634b04 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -872,6 +872,15 @@ export default class MatrixChat extends React.PureComponent { params.hs_url, params.is_url, ); + // If the hs url matches then take the hs name we know locally as it is likely prettier + const defaultConfig = SdkConfig.get()["validated_server_config"] as ValidatedServerConfig; + if (defaultConfig && defaultConfig.hsUrl === newState.serverConfig.hsUrl) { + newState.serverConfig.hsName = defaultConfig.hsName; + newState.serverConfig.hsNameIsDifferent = defaultConfig.hsNameIsDifferent; + newState.serverConfig.isDefault = defaultConfig.isDefault; + newState.serverConfig.isNameResolvable = defaultConfig.isNameResolvable; + } + newState.register_client_secret = params.client_secret; newState.register_session_id = params.session_id; newState.register_id_sid = params.sid; @@ -917,7 +926,7 @@ export default class MatrixChat extends React.PureComponent { let waitFor = Promise.resolve(null); if (!this.firstSyncComplete) { if (!this.firstSyncPromise) { - console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id); + logger.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id); return; } waitFor = this.firstSyncPromise.promise; @@ -973,7 +982,7 @@ export default class MatrixChat extends React.PureComponent { // Wait for the first sync to complete if (!this.firstSyncComplete) { if (!this.firstSyncPromise) { - console.warn('Cannot view a group before first sync. group_id:', groupId); + logger.warn('Cannot view a group before first sync. group_id:', groupId); return; } await this.firstSyncPromise.promise; @@ -1452,7 +1461,7 @@ export default class MatrixChat extends React.PureComponent { if (state === "SYNCING" && prevState === "SYNCING") { return; } - console.info("MatrixClient sync state => %s", state); + logger.info("MatrixClient sync state => %s", state); if (state !== "PREPARED") { return; } this.firstSyncComplete = true; @@ -1475,7 +1484,7 @@ export default class MatrixChat extends React.PureComponent { Modal.closeCurrentModal('Session.logged_out'); if (errObj.httpStatus === 401 && errObj.data && errObj.data['soft_logout']) { - console.warn("Soft logout issued by server - avoiding data deletion"); + logger.warn("Soft logout issued by server - avoiding data deletion"); Lifecycle.softLogout(); return; } @@ -1580,7 +1589,7 @@ export default class MatrixChat extends React.PureComponent { newVersionInfo = await MatrixClientPeg.get().getKeyBackupVersion(); if (newVersionInfo !== null) haveNewVersion = true; } catch (e) { - console.error("Saw key backup error but failed to check backup version!", e); + logger.error("Saw key backup error but failed to check backup version!", e); return; } } @@ -1818,7 +1827,7 @@ export default class MatrixChat extends React.PureComponent { group_id: groupId, }); } else { - console.info("Ignoring showScreen for '%s'", screen); + logger.info("Ignoring showScreen for '%s'", screen); } } @@ -2121,7 +2130,7 @@ export default class MatrixChat extends React.PureComponent { /> ); } else { - console.error(`Unknown view ${this.state.view}`); + logger.error(`Unknown view ${this.state.view}`); } return diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index c1d22ecedff..79aeea8321c 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -460,7 +460,7 @@ export default class MessagePanel extends React.Component { // Checking if the message has a "parentEventId" as we do not // want to hide the root event of the thread - if (mxEv.isThreadRoot && this.props.hideThreadedMessages + if (mxEv.isThreadRelation && this.props.hideThreadedMessages && SettingsStore.getValue("feature_thread")) { return false; } diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index c37eeca0913..52046c18559 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -26,6 +26,8 @@ import { TileShape } from "../views/rooms/EventTile"; import { Layout } from "../../settings/Layout"; import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { onClose(): void; } @@ -59,7 +61,7 @@ export default class NotificationPanel extends React.PureComponent { /> ); } else { - console.error("No notifTimelineSet available!"); + logger.error("No notifTimelineSet available!"); content = ; } diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx index 3c5f99cc7de..56e6b2dfb81 100644 --- a/src/components/structures/RoomDirectory.tsx +++ b/src/components/structures/RoomDirectory.tsx @@ -48,6 +48,8 @@ import Spinner from "../views/elements/Spinner"; import { ActionPayload } from "../../dispatcher/payloads"; import { getDisplayAliasForAliasSet } from "../../Rooms"; +import { logger } from "matrix-js-sdk/src/logger"; + const MAX_NAME_LENGTH = 80; const MAX_TOPIC_LENGTH = 800; @@ -131,7 +133,7 @@ export default class RoomDirectory extends React.Component { } this.setState({ protocolsLoading: false }); }, (err) => { - console.warn(`error loading third party protocols: ${err}`); + logger.warn(`error loading third party protocols: ${err}`); this.setState({ protocolsLoading: false }); if (MatrixClientPeg.get().isGuest()) { // Guests currently aren't allowed to use this API, so @@ -285,7 +287,7 @@ export default class RoomDirectory extends React.Component { return false; } - console.error("Failed to get publicRooms: %s", JSON.stringify(err)); + logger.error("Failed to get publicRooms: %s", JSON.stringify(err)); track('Failed to get public room list'); const brand = SdkConfig.get().brand; this.setState({ @@ -335,7 +337,7 @@ export default class RoomDirectory extends React.Component { }, (err) => { modal.close(); this.refreshRoomList(); - console.error("Failed to " + step + ": " + err); + logger.error("Failed to " + step + ": " + err); Modal.createTrackedDialog('Remove from Directory Error', '', ErrorDialog, { title: _t('Error'), description: (err && err.message) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index e5067f1fcf5..c2e071b8362 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -498,7 +498,7 @@ export class RoomView extends React.Component { // making it impossible to indicate a newly joined room. if (!joining && roomId) { if (!room && shouldPeek) { - console.info("Attempting to peek into room %s", roomId); + logger.info("Attempting to peek into room %s", roomId); this.setState({ peekLoading: true, isPeeking: true, // this will change to false if peeking fails @@ -779,7 +779,10 @@ export class RoomView extends React.Component { }); break; case 'reply_to_event': - if (this.state.searchResults && payload.event.getRoomId() === this.state.roomId && !this.unmounted) { + if (this.state.searchResults + && payload.event.getRoomId() === this.state.roomId + && !this.unmounted + && payload.context === TimelineRenderingType.Room) { this.onCancelSearchClick(); } break; @@ -841,7 +844,9 @@ export class RoomView extends React.Component { } case "scroll_to_bottom": - this.messagePanel?.jumpToLiveTimeline(); + if (payload.timelineRenderingType === this.context.timelineRenderingType) { + this.messagePanel?.jumpToLiveTimeline(); + } break; } }; @@ -963,8 +968,8 @@ export class RoomView extends React.Component { } catch (err) { const errorMessage = `Fetching room members for ${room.roomId} failed.` + " Room members will appear incomplete."; - console.error(errorMessage); - console.error(err); + logger.error(errorMessage); + logger.error(err); } } } @@ -1315,7 +1320,7 @@ export class RoomView extends React.Component { return searchPromise.then((results) => { debuglog("search complete"); if (this.unmounted || !this.state.searching || this.searchId != localSearchId) { - console.error("Discarding stale search results"); + logger.error("Discarding stale search results"); return false; } @@ -1341,7 +1346,7 @@ export class RoomView extends React.Component { searchResults: results, }); }, (error) => { - console.error("Search failed", error); + logger.error("Search failed", error); Modal.createTrackedDialog('Search failed', '', ErrorDialog, { title: _t("Search failed"), description: ((error && error.message) ? error.message : @@ -1472,7 +1477,7 @@ export class RoomView extends React.Component { rejecting: false, }); }, (error) => { - console.error("Failed to reject invite: %s", error); + logger.error("Failed to reject invite: %s", error); const msg = error.message ? error.message : JSON.stringify(error); Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, { @@ -1505,7 +1510,7 @@ export class RoomView extends React.Component { rejecting: false, }); } catch (error) { - console.error("Failed to reject invite: %s", error); + logger.error("Failed to reject invite: %s", error); const msg = error.message ? error.message : JSON.stringify(error); Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, { diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index 0ea070627a1..1699b535d99 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -408,7 +408,7 @@ export default class ScrollPanel extends React.Component { try { await Promise.all(fillPromises); } catch (err) { - console.error(err); + logger.error(err); } } if (isFirstCall) { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 383524b738b..4a43f0380df 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -127,8 +127,17 @@ const useMyRoomMembership = (room: Room) => { return membership; }; -const SpaceInfo = ({ space }) => { +const SpaceInfo = ({ space }: { space: Room }) => { + const summary = useAsyncMemo(async () => { + if (space.getMyMembership() !== "invite") return; + try { + return space.client.getRoomSummary(space.roomId); + } catch (e) { + return null; + } + }, [space]); const joinRule = useRoomState(space, state => state.getJoinRule()); + const membership = useMyRoomMembership(space); let visibilitySection; if (joinRule === "public") { @@ -141,12 +150,18 @@ const SpaceInfo = ({ space }) => { ; } - return
- { visibilitySection } - { joinRule === "public" && + let memberSection; + if (membership === "invite" && summary) { + // Don't trust local state and instead use the summary API + memberSection = + { _t("%(count)s members", { count: summary.num_joined_members }) } + ; + } else if (summary === null) { + memberSection = { (count) => count > 0 ? ( { defaultDispatcher.dispatch({ action: Action.SetRightPanelPhase, @@ -158,7 +173,12 @@ const SpaceInfo = ({ space }) => { { _t("%(count)s members", { count }) } ) : null } - } + ; + } + + return
+ { visibilitySection } + { memberSection }
; }; @@ -275,8 +295,11 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }: ISp { - setBusy(true); onJoinButtonClicked(); + if (!cli.isGuest()) { + // user will be shown a modal that won't fire a room join error + setBusy(true); + } }} disabled={!spacesEnabled || cannotJoin} > @@ -536,7 +559,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { })); onFinished(roomIds[0]); } catch (e) { - console.error("Failed to create initial space rooms", e); + logger.error("Failed to create initial space rooms", e); setError(_t("Failed to create initial space rooms")); } setBusy(false); @@ -713,7 +736,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => { onFinished(); } } catch (err) { - console.error("Failed to invite users to space: ", err); + logger.error("Failed to invite users to space: ", err); setError(_t("We couldn't invite those users. Please check the users you want to invite and try again.")); } setBusy(false); @@ -906,21 +929,21 @@ export default class SpaceRoomView extends React.PureComponent { space={this.props.space} justCreatedOpts={this.props.justCreatedOpts} onFinished={(invite: boolean) => { - this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateExistingRooms }); + this.setState({ phase: invite ? Phase.PrivateCreateRooms : Phase.PrivateExistingRooms }); }} />; case Phase.PrivateInvite: return this.setState({ phase: Phase.PrivateCreateRooms })} + onFinished={() => this.setState({ phase: Phase.Landing })} />; case Phase.PrivateCreateRooms: return this.setState({ phase: Phase.Landing, firstRoomId })} + onFinished={(firstRoomId: string) => this.setState({ phase: Phase.PrivateInvite, firstRoomId })} />; case Phase.PrivateExistingRooms: return { if (this.props.onChange) this.props.onChange(tab.id); this.setState({ activeTabIndex: idx }); } else { - console.error("Could not find tab " + tab.label + " in tabs"); + logger.error("Could not find tab " + tab.label + " in tabs"); } } diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 40e94792511..2c3cba683b3 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -15,7 +15,6 @@ limitations under the License. */ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set'; import { Room } from 'matrix-js-sdk/src/models/room'; @@ -24,7 +23,6 @@ import BaseCard from "../views/right_panel/BaseCard"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import ResizeNotifier from '../../utils/ResizeNotifier'; -import EventTile, { TileShape } from '../views/rooms/EventTile'; import MatrixClientContext from '../../contexts/MatrixClientContext'; import { _t } from '../../languageHandler'; import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuButton'; @@ -34,6 +32,7 @@ import TimelinePanel from './TimelinePanel'; import { Layout } from '../../settings/Layout'; import { useEventEmitter } from '../../hooks/useEventEmitter'; import AccessibleButton from '../views/elements/AccessibleButton'; +import { TileShape } from '../views/rooms/EventTile'; interface IProps { roomId: string; @@ -41,18 +40,6 @@ interface IProps { resizeNotifier: ResizeNotifier; } -export const ThreadPanelItem: React.FC<{ event: MatrixEvent }> = ({ event }) => { - return ; -}; - export enum ThreadFilterType { "My", "All" @@ -230,7 +217,7 @@ const ThreadPanel: React.FC = ({ roomId, onClose }) => { showReactions={true} className="mx_RoomView_messagePanel mx_GroupLayout" membersLoaded={true} - tileShape={TileShape.ThreadPanel} + tileShape={TileShape.Thread} /> diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 34622128349..b0851665a7a 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -48,10 +48,9 @@ interface IProps { } interface IState { - replyToEvent?: MatrixEvent; thread?: Thread; editState?: EditorStateTransfer; - + replyToEvent?: MatrixEvent; } @replaceableComponent("structures.ThreadView") @@ -69,11 +68,16 @@ export default class ThreadView extends React.Component { public componentDidMount(): void { this.setupThread(this.props.mxEvent); this.dispatcherRef = dis.register(this.onAction); + + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + room.on(ThreadEvent.New, this.onNewThread); } public componentWillUnmount(): void { this.teardownThread(); dis.unregister(this.dispatcherRef); + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + room.on(ThreadEvent.New, this.onNewThread); } public componentDidUpdate(prevProps) { @@ -111,6 +115,13 @@ export default class ThreadView extends React.Component { }); break; } + case 'reply_to_event': + if (payload.context === TimelineRenderingType.Thread) { + this.setState({ + replyToEvent: payload.event, + }); + } + break; default: break; } @@ -135,11 +146,17 @@ export default class ThreadView extends React.Component { } }; + private onNewThread = (thread: Thread) => { + if (thread.id === this.props.mxEvent.getId()) { + this.teardownThread(); + this.setupThread(this.props.mxEvent); + } + }; + private updateThread = (thread?: Thread) => { if (thread) { this.setState({ thread, - replyToEvent: thread.replyToEvent, }); } @@ -190,7 +207,7 @@ export default class ThreadView extends React.Component { rel_type: RelationType.Thread, event_id: this.state.thread.id, }} - showReplyPreview={false} + replyToEvent={this.state.replyToEvent} permalinkCreator={this.props.permalinkCreator} e2eStatus={this.props.e2eStatus} compact={true} diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index 69241811327..495d3c438ff 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -312,7 +312,7 @@ class TimelinePanel extends React.Component { // // for now, just warn about this. But we're going to end up paginating // both rooms separately, and it's all bad. - console.warn("Replacing timelineSet on a TimelinePanel - confusion may ensue"); + logger.warn("Replacing timelineSet on a TimelinePanel - confusion may ensue"); } const differentEventId = newProps.eventId != this.props.eventId; @@ -799,11 +799,11 @@ class TimelinePanel extends React.Component { lastReadEvent, {}, ).catch((e) => { - console.error(e); + logger.error(e); this.lastRRSentEventId = undefined; }); } else { - console.error(e); + logger.error(e); } // it failed, so allow retries next time the user is active this.lastRRSentEventId = undefined; @@ -1121,7 +1121,7 @@ class TimelinePanel extends React.Component { if (this.unmounted) return; this.setState({ timelineLoading: false }); - console.error( + logger.error( `Error loading timeline panel at ${eventId}: ${error}`, ); @@ -1266,7 +1266,7 @@ class TimelinePanel extends React.Component { // Somehow, it seems to be possible for live events to not have // a timeline, even though that should not happen. :( // https://github.com/vector-im/element-web/issues/12120 - console.warn( + logger.warn( `Event ${events[i].getId()} in room ${room.roomId} is live, ` + `but it does not have a timeline`, ); diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 24b47bfa032..4c65fac9834 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -29,10 +29,12 @@ import ServerPicker from "../../views/elements/ServerPicker"; import PassphraseField from '../../views/auth/PassphraseField'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm'; - -import { IValidationResult } from "../../views/elements/Validation"; +import withValidation, { IValidationResult } from "../../views/elements/Validation"; +import * as Email from "../../../email"; import InlineSpinner from '../../views/elements/InlineSpinner'; +import { logger } from "matrix-js-sdk/src/logger"; + enum Phase { // Show the forgot password inputs Forgot = 1, @@ -66,6 +68,7 @@ interface IState { serverErrorIsFatal: boolean; serverDeadError: string; + emailFieldValid: boolean; passwordFieldValid: boolean; currentHttpRequest?: Promise; } @@ -88,6 +91,7 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", + emailFieldValid: false, passwordFieldValid: false, }; @@ -147,7 +151,7 @@ export default class ForgotPassword extends React.Component { private onVerify = async (ev: React.MouseEvent): Promise => { ev.preventDefault(); if (!this.reset) { - console.error("onVerify called before submitPasswordReset!"); + logger.error("onVerify called before submitPasswordReset!"); return; } if (this.state.currentHttpRequest) return; @@ -167,10 +171,13 @@ export default class ForgotPassword extends React.Component { // refresh the server errors, just in case the server came back online await this.handleHttpRequest(this.checkServerLiveliness(this.props.serverConfig)); + await this['email_field'].validate({ allowEmpty: false }); await this['password_field'].validate({ allowEmpty: false }); if (!this.state.email) { this.showErrorDialog(_t('The email address linked to your account must be entered.')); + } else if (!this.state.emailFieldValid) { + this.showErrorDialog(_t("The email address doesn't appear to be valid.")); } else if (!this.state.password || !this.state.password2) { this.showErrorDialog(_t('A new password must be entered.')); } else if (!this.state.passwordFieldValid) { @@ -220,6 +227,32 @@ export default class ForgotPassword extends React.Component { }); } + private validateEmailRules = withValidation({ + rules: [ + { + key: "required", + test({ value, allowEmpty }) { + return allowEmpty || !!value; + }, + invalid: () => _t("Enter email address"), + }, { + key: "email", + test: ({ value }) => !value || Email.looksValid(value), + invalid: () => _t("Doesn't look like a valid email address"), + }, + ], + }); + + private onEmailValidate = async (fieldState) => { + const result = await this.validateEmailRules(fieldState); + + this.setState({ + emailFieldValid: result.valid, + }); + + return result; + }; + private onPasswordValidate(result: IValidationResult) { this.setState({ passwordFieldValid: result.valid, @@ -275,7 +308,9 @@ export default class ForgotPassword extends React.Component { label={_t('Email')} value={this.state.email} onChange={this.onInputChanged.bind(this, "email")} + ref={field => this['email_field'] = field} autoFocus + onValidate={this.onEmailValidate} onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_focus")} onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_blur")} /> diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index c8f208476ba..403c013e64d 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -309,7 +309,7 @@ export default class LoginComponent extends React.PureComponent busy: false, }); } catch (e) { - console.error("Problem parsing URL or unhandled error doing .well-known discovery:", e); + logger.error("Problem parsing URL or unhandled error doing .well-known discovery:", e); let message = _t("Failed to perform homeserver discovery"); if (e.translatedMessage) { diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 4cffed4348b..ab1956dd6a2 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -195,7 +195,7 @@ export default class Registration extends React.Component { const loginFlows = await this.loginLogic.getFlows(); ssoFlow = loginFlows.find(f => f.type === "m.login.sso" || f.type === "m.login.cas") as ISSOFlow; } catch (e) { - console.error("Failed to get login flows to check for SSO support", e); + logger.error("Failed to get login flows to check for SSO support", e); } this.setState({ @@ -272,7 +272,7 @@ export default class Registration extends React.Component { private onUIAuthFinished = async (success: boolean, response: any) => { if (!success) { - let msg = response.message || response.toString(); + let errorText = response.message || response.toString(); // can we give a better error message? if (response.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') { const errorTop = messageForResourceLimitError( @@ -291,7 +291,7 @@ export default class Registration extends React.Component { '': _td("Please contact your service administrator to continue using this service."), }, ); - msg =
+ errorText =

{ errorTop }

{ errorDetail }

; @@ -301,15 +301,18 @@ export default class Registration extends React.Component { msisdnAvailable = msisdnAvailable || flow.stages.includes('m.login.msisdn'); } if (!msisdnAvailable) { - msg = _t('This server does not support authentication with a phone number.'); + errorText = _t('This server does not support authentication with a phone number.'); } } else if (response.errcode === "M_USER_IN_USE") { - msg = _t("That username already exists, please try another."); + errorText = _t("That username already exists, please try another."); + } else if (response.errcode === "M_THREEPID_IN_USE") { + errorText = _t("That e-mail address is already in use."); } + this.setState({ busy: false, doingUIAuth: false, - errorText: msg, + errorText, }); return; } @@ -370,12 +373,12 @@ export default class Registration extends React.Component { matrixClient.setPusher(emailPusher).then(() => { logger.log("Set email branding to " + this.props.brand); }, (error) => { - console.error("Couldn't set email branding: " + error); + logger.error("Couldn't set email branding: " + error); }); } } }, (error) => { - console.error("Couldn't get pushers: " + error); + logger.error("Couldn't get pushers: " + error); }); } diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index a943f47e66d..db93d30c27f 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -173,7 +173,7 @@ export default class SoftLogout extends React.Component { } Lifecycle.hydrateSession(credentials).catch((e) => { - console.error(e); + logger.error(e); this.setState({ busy: false, errorText: _t("Failed to re-authenticate") }); }); }; @@ -193,7 +193,7 @@ export default class SoftLogout extends React.Component { try { credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams); } catch (e) { - console.error(e); + logger.error(e); this.setState({ busy: false, loginView: LOGIN_VIEW.UNSUPPORTED }); return; } @@ -201,7 +201,7 @@ export default class SoftLogout extends React.Component { Lifecycle.hydrateSession(credentials).then(() => { if (this.props.onTokenLoginCompleted) this.props.onTokenLoginCompleted(); }).catch((e) => { - console.error(e); + logger.error(e); this.setState({ busy: false, loginView: LOGIN_VIEW.UNSUPPORTED }); }); } diff --git a/src/components/views/audio_messages/AudioPlayerBase.tsx b/src/components/views/audio_messages/AudioPlayerBase.tsx index d8fc9d507f4..5158e878270 100644 --- a/src/components/views/audio_messages/AudioPlayerBase.tsx +++ b/src/components/views/audio_messages/AudioPlayerBase.tsx @@ -21,6 +21,8 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { _t } from "../../../languageHandler"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { // Playback instance to render. Cannot change during component lifecycle: create // an all-new component instead. @@ -50,7 +52,7 @@ export default abstract class AudioPlayerBase extends React.PureComponent { - console.error("Error processing audio file:", e); + logger.error("Error processing audio file:", e); this.setState({ error: true }); }); } diff --git a/src/components/views/auth/CaptchaForm.tsx b/src/components/views/auth/CaptchaForm.tsx index db0e07e0467..846efcc9ca2 100644 --- a/src/components/views/auth/CaptchaForm.tsx +++ b/src/components/views/auth/CaptchaForm.tsx @@ -85,19 +85,19 @@ export default class CaptchaForm extends React.Component + { errorSection }
{ submitButtonOrSpinner }
- { errorSection }
); } diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 8a0fb34f3cf..c66d6b80fd6 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -32,6 +32,8 @@ import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDia import { replaceableComponent } from "../../../utils/replaceableComponent"; import CountryDropdown from "./CountryDropdown"; +import { logger } from "matrix-js-sdk/src/logger"; + enum RegistrationField { Email = "field_email", PhoneNumber = "field_phone_number", @@ -84,7 +86,7 @@ interface IState { @replaceableComponent("views.auth.RegistrationForm") export default class RegistrationForm extends React.PureComponent { static defaultProps = { - onValidationChange: console.error, + onValidationChange: logger.error, canSubmit: true, }; diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 001df16d405..48b2575cd25 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -25,6 +25,8 @@ import BaseAvatar from "./BaseAvatar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps extends Omit, "name" | "idName" | "url"> { member: RoomMember; fallbackUserId?: string; @@ -85,7 +87,7 @@ export default class MemberAvatar extends React.Component { title: props.fallbackUserId, }; } else { - console.error("MemberAvatar called somehow with null member or fallbackUserId"); + logger.error("MemberAvatar called somehow with null member or fallbackUserId"); } } diff --git a/src/components/views/context_menus/GroupInviteTileContextMenu.js b/src/components/views/context_menus/GroupInviteTileContextMenu.js index 1529723ac86..ed5a45d4323 100644 --- a/src/components/views/context_menus/GroupInviteTileContextMenu.js +++ b/src/components/views/context_menus/GroupInviteTileContextMenu.js @@ -25,6 +25,8 @@ import GroupStore from "../../../stores/GroupStore"; import { MenuItem } from "../../structures/ContextMenu"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + @replaceableComponent("views.context_menus.GroupInviteTileContextMenu") export default class GroupInviteTileContextMenu extends React.Component { static propTypes = { @@ -62,7 +64,7 @@ export default class GroupInviteTileContextMenu extends React.Component { try { await GroupStore.leaveGroup(this.props.group.groupId); } catch (e) { - console.error("Error rejecting community invite: ", e); + logger.error("Error rejecting community invite: ", e); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, { title: _t("Error"), diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 22dd3ac4387..71a35ba6a16 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -38,6 +38,7 @@ import { createRedactEventDialog } from '../dialogs/ConfirmRedactDialog'; import ShareDialog from '../dialogs/ShareDialog'; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { IPosition, ChevronFace } from '../../structures/ContextMenu'; +import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; export function canCancel(eventStatus: EventStatus): boolean { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -60,7 +61,7 @@ interface IProps extends IPosition { eventTileOps?: IEventTileOps; permalinkCreator?: RoomPermalinkCreator; /* an optional function to be called when the user clicks collapse thread, if not provided hide button */ - collapseReplyThread?(): void; + collapseReplyChain?(): void; /* callback called when the menu is dismissed */ onFinished(): void; /* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */ @@ -74,6 +75,8 @@ interface IState { @replaceableComponent("views.context_menus.MessageContextMenu") export default class MessageContextMenu extends React.Component { + static contextType = RoomContext; + state = { canRedact: false, canPin: false, @@ -203,8 +206,8 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; - private onCollapseReplyThreadClick = (): void => { - this.props.collapseReplyThread(); + private onCollapseReplyChainClick = (): void => { + this.props.collapseReplyChain(); this.closeMenu(); }; @@ -226,6 +229,16 @@ export default class MessageContextMenu extends React.Component return this.getReactions(e => e.status === EventStatus.NOT_SENT); } + private viewInRoom = () => { + dis.dispatch({ + action: 'view_room', + event_id: this.props.mxEvent.getId(), + highlighted: true, + room_id: this.props.mxEvent.getRoomId(), + }); + this.closeMenu(); + }; + render() { const cli = MatrixClientPeg.get(); const me = cli.getUserId(); @@ -240,7 +253,7 @@ export default class MessageContextMenu extends React.Component let unhidePreviewButton: JSX.Element; let externalURLButton: JSX.Element; let quoteButton: JSX.Element; - let collapseReplyThread: JSX.Element; + let collapseReplyChain: JSX.Element; let redactItemList: JSX.Element; // status is SENT before remote-echo, null after @@ -360,12 +373,12 @@ export default class MessageContextMenu extends React.Component ); } - if (this.props.collapseReplyThread) { - collapseReplyThread = ( + if (this.props.collapseReplyChain) { + collapseReplyChain = ( ); } @@ -381,8 +394,20 @@ export default class MessageContextMenu extends React.Component ); } + const { timelineRenderingType } = this.context; + const isThread = ( + timelineRenderingType === TimelineRenderingType.Thread || + timelineRenderingType === TimelineRenderingType.ThreadsList + ); + const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent; + const commonItemsList = ( + { isThreadRootEvent && } { quoteButton } { forwardButton } { pinButton } @@ -392,7 +417,7 @@ export default class MessageContextMenu extends React.Component { unhidePreviewButton } { viewSourceButton } { resendReactionsButton } - { collapseReplyThread } + { collapseReplyChain } ); @@ -403,7 +428,6 @@ export default class MessageContextMenu extends React.Component ); } - return ( { app: IApp; userWidget?: boolean; @@ -65,7 +67,7 @@ const WidgetContextMenu: React.FC = ({ try { await startJitsiAudioLivestream(widgetMessaging, roomId); } catch (err) { - console.error("Failed to start livestream", err); + logger.error("Failed to start livestream", err); // XXX: won't i18n well, but looks like widget api only support 'message'? const message = err.message || _t("Unable to start audio streaming."); Modal.createTrackedDialog('WidgetContext Menu', 'Livestream failed', ErrorDialog, { @@ -114,7 +116,7 @@ const WidgetContextMenu: React.FC = ({ file: data.screenshot, }); }).catch(err => { - console.error("Failed to take screenshot: ", err); + logger.error("Failed to take screenshot: ", err); }); onFinished(); }; @@ -160,12 +162,12 @@ const WidgetContextMenu: React.FC = ({ let revokeButton; if (!userWidget && !isLocalWidget && isAllowedWidget) { const onRevokeClick = () => { - console.info("Revoking permission for widget to load: " + app.eventId); + logger.info("Revoking permission for widget to load: " + app.eventId); const current = SettingsStore.getValue("allowedWidgets", roomId); current[app.eventId] = false; const level = SettingsStore.firstSupportedLevel("allowedWidgets"); SettingsStore.setValue("allowedWidgets", roomId, level, current).catch(err => { - console.error(err); + logger.error(err); // We don't really need to do anything about this - the user will just hit the button again. }); onFinished(); diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 01a767bf147..337941ce5fd 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -41,6 +41,8 @@ import TruncatedList from "../elements/TruncatedList"; import EntityTile from "../rooms/EntityTile"; import BaseAvatar from "../avatars/BaseAvatar"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { space: Room; onCreateRoomClick(): void; @@ -160,7 +162,7 @@ export const AddExistingToSpace: React.FC = ({ }); setProgress(i => i + 1); } catch (e) { - console.error("Failed to add rooms to space", e); + logger.error("Failed to add rooms to space", e); setError(error = e); break; } diff --git a/src/components/views/dialogs/AddressPickerDialog.tsx b/src/components/views/dialogs/AddressPickerDialog.tsx index 6b239ee5703..9484859509b 100644 --- a/src/components/views/dialogs/AddressPickerDialog.tsx +++ b/src/components/views/dialogs/AddressPickerDialog.tsx @@ -37,6 +37,8 @@ import AddressTile from '../elements/AddressTile'; import BaseDialog from "./BaseDialog"; import DialogButtons from "../elements/DialogButtons"; +import { logger } from "matrix-js-sdk/src/logger"; + const TRUNCATE_QUERY_LIST = 40; const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200; @@ -225,7 +227,7 @@ export default class AddressPickerDialog extends React.Component this.doRoomSearch(query); } } else { - console.error('Unknown pickerType', this.props.pickerType); + logger.error('Unknown pickerType', this.props.pickerType); } }, QUERY_USER_DIRECTORY_DEBOUNCE_MS); } else { @@ -282,7 +284,7 @@ export default class AddressPickerDialog extends React.Component }); this.processResults(results, query); }).catch((err) => { - console.error('Error whilst searching group rooms: ', err); + logger.error('Error whilst searching group rooms: ', err); this.setState({ searchError: err.errcode ? err.message : _t('Something went wrong!'), }); @@ -388,7 +390,7 @@ export default class AddressPickerDialog extends React.Component } this.processResults(resp.results, query); }).catch((err) => { - console.error('Error whilst searching user directory: ', err); + logger.error('Error whilst searching user directory: ', err); this.setState({ searchError: err.errcode ? err.message : _t('Something went wrong!'), }); @@ -582,7 +584,7 @@ export default class AddressPickerDialog extends React.Component }], }); } catch (e) { - console.error(e); + logger.error(e); this.setState({ searchError: _t('Something went wrong!'), }); diff --git a/src/components/views/dialogs/CommunityPrototypeInviteDialog.tsx b/src/components/views/dialogs/CommunityPrototypeInviteDialog.tsx index 6a8773ce45e..d5a54aaa237 100644 --- a/src/components/views/dialogs/CommunityPrototypeInviteDialog.tsx +++ b/src/components/views/dialogs/CommunityPrototypeInviteDialog.tsx @@ -33,6 +33,8 @@ import ErrorDialog from "./ErrorDialog"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps extends IDialogProps { roomId: string; communityName: string; @@ -99,7 +101,7 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent< } } catch (e) { this.setState({ busy: false }); - console.error(e); + logger.error(e); Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { title: _t("Failed to invite"), description: ((e && e.message) ? e.message : _t("Operation failed")), diff --git a/src/components/views/dialogs/CreateCommunityPrototypeDialog.tsx b/src/components/views/dialogs/CreateCommunityPrototypeDialog.tsx index ccac45fbcc2..ed48778b1d8 100644 --- a/src/components/views/dialogs/CreateCommunityPrototypeDialog.tsx +++ b/src/components/views/dialogs/CreateCommunityPrototypeDialog.tsx @@ -27,6 +27,8 @@ import { showCommunityRoomInviteDialog } from "../../../RoomInvite"; import GroupStore from "../../../stores/GroupStore"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps extends IDialogProps { } @@ -110,7 +112,7 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent< }); } } catch (e) { - console.error(e); + logger.error(e); this.setState({ busy: false, error: _t( diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx index c7706c115cb..b19c8d64961 100644 --- a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -42,6 +42,8 @@ import TagOrderActions from "../../../actions/TagOrderActions"; import { inviteUsersToRoom } from "../../../RoomInvite"; import ProgressBar from "../elements/ProgressBar"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { matrixClient: MatrixClient; groupId: string; @@ -174,7 +176,7 @@ const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, g const { servers } = await cli.getRoomIdForAlias(canonicalAlias); viaMap.set(roomId, servers); } catch (e) { - console.warn("Failed to resolve alias during community migration", e); + logger.warn("Failed to resolve alias during community migration", e); } } @@ -219,7 +221,7 @@ const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, g _t("This community has been upgraded into a Space") + `
` + groupSummary.profile.long_description, } as IGroupSummary["profile"]).catch(e => { - console.warn("Failed to update community profile during migration", e); + logger.warn("Failed to update community profile during migration", e); }); onFinished(roomId); @@ -271,7 +273,7 @@ const CreateSpaceFromCommunityDialog: React.FC = ({ matrixClient: cli, g }, }, "mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog"); } catch (e) { - console.error(e); + logger.error(e); setError(e); } diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 0d7facb4763..44ffd2afdd2 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -30,6 +30,8 @@ import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; import { SubspaceSelector } from "./AddExistingToSpaceDialog"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { space: Room; onAddExistingSpaceClick(): void; @@ -83,7 +85,7 @@ const CreateSubspaceDialog: React.FC = ({ space, onAddExistingSpaceClick onFinished(true); } catch (e) { - console.error(e); + logger.error(e); } }; diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx index 6548bd78fcf..35556aacd99 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.tsx +++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx @@ -28,6 +28,8 @@ import StyledCheckbox from "../elements/StyledCheckbox"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import BaseDialog from "./BaseDialog"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { onFinished: (success: boolean) => void; } @@ -112,7 +114,7 @@ export default class DeactivateAccountDialog extends React.Component { - console.error(e); + logger.error(e); this.setState({ errStr: _t("There was a problem communicating with the server. Please try again.") }); }); }; @@ -156,7 +158,7 @@ export default class DeactivateAccountDialog extends React.Component { if (e && e.httpStatus === 401 && e.data) { diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 7f34b75055f..377911d0958 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -991,7 +991,7 @@ class SettingsExplorer extends React.PureComponent = ({ room, onFinished }) => { ); break; default: - console.error("Unknown export format"); + logger.error("Unknown export format"); return; } }; diff --git a/src/components/views/dialogs/HostSignupDialog.tsx b/src/components/views/dialogs/HostSignupDialog.tsx index 4b8b7f32f03..12284957476 100644 --- a/src/components/views/dialogs/HostSignupDialog.tsx +++ b/src/components/views/dialogs/HostSignupDialog.tsx @@ -33,6 +33,8 @@ import { } from "./HostSignupDialogTypes"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + const HOST_SIGNUP_KEY = "host_signup"; interface IProps {} @@ -146,7 +148,7 @@ export default class HostSignupDialog extends React.PureComponent b.lastActive - a.lastActive); @@ -731,7 +731,7 @@ export default class InviteDialog extends React.PureComponent { - console.error("Error searching user directory:"); - console.error(e); + logger.error("Error searching user directory:"); + logger.error(e); this.setState({ serverResultsMixin: [] }); // clear results because it's moderately fatal }); @@ -948,8 +948,8 @@ export default class InviteDialog extends React.PureComponent
; } else { - console.error("Unknown kind of InviteDialog: " + this.props.kind); + logger.error("Unknown kind of InviteDialog: " + this.props.kind); } const goButton = this.props.kind == KIND_CALL_TRANSFER ? null : = ({ space, onFinished }) => { primaryButton={_t("Leave space")} onPrimaryButtonClick={() => onFinished(true, roomsToLeave)} hasCancel={true} - onCancel={onFinished} + onCancel={() => onFinished(false)} /> ; }; diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.tsx b/src/components/views/dialogs/MessageEditHistoryDialog.tsx index 7753eba1993..f5a990e4091 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.tsx +++ b/src/components/views/dialogs/MessageEditHistoryDialog.tsx @@ -30,6 +30,8 @@ import { IDialogProps } from "./IDialogProps"; import { EventType, RelationType } from "matrix-js-sdk/src/@types/event"; import { defer } from "matrix-js-sdk/src/utils"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps extends IDialogProps { mxEvent: MatrixEvent; } @@ -78,7 +80,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent reject(error)); return promise; diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 7a79791b3c6..4c355c6c0e9 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -28,6 +28,8 @@ import TextWithTooltip from "../elements/TextWithTooltip"; import withValidation, { IFieldState } from "../elements/Validation"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { title?: string; serverConfig: ValidatedServerConfig; @@ -93,7 +95,7 @@ export default class ServerPickerDialog extends React.PureComponent { }); }, (err) => { this.setState({ emailBusy: false }); - console.error("Unable to add email address " + emailAddress + " " + err); + logger.error("Unable to add email address " + emailAddress + " " + err); Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, { title: _t("Unable to add email address"), description: ((err && err.message) ? err.message : _t("Operation failed")), @@ -119,7 +121,7 @@ export default class SetEmailDialog extends React.Component { onFinished: this.onEmailDialogFinished, }); } else { - console.error("Unable to verify email address: " + err); + logger.error("Unable to verify email address: " + err); Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, { title: _t("Unable to verify email address."), description: ((err && err.message) ? err.message : _t("Operation failed")), diff --git a/src/components/views/dialogs/TabbedIntegrationManagerDialog.tsx b/src/components/views/dialogs/TabbedIntegrationManagerDialog.tsx index 0f87b5c18de..dab1a1f222c 100644 --- a/src/components/views/dialogs/TabbedIntegrationManagerDialog.tsx +++ b/src/components/views/dialogs/TabbedIntegrationManagerDialog.tsx @@ -27,6 +27,8 @@ import AccessibleButton from "../elements/AccessibleButton"; import IntegrationManager from "../settings/IntegrationManager"; import { IDialogProps } from "./IDialogProps"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps extends IDialogProps { /** * Optional room where the integration manager should be open to @@ -116,7 +118,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component

{ _t( - "Enter your Security Phrase or to continue.", {}, + "Enter your Security Phrase or to continue.", {}, { button: s => { const u = url.parse(this.props.app.url); const childContentProtocol = u.protocol; if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') { - console.warn("Refusing to load mixed-content app:", + logger.warn("Refusing to load mixed-content app:", parentContentProtocol, childContentProtocol, window.location, this.props.app.url); return true; } @@ -305,7 +305,7 @@ export default class AppTile extends React.Component { dis.dispatch({ action: 'post_sticker_message', data: payload.data }); dis.dispatch({ action: 'stickerpicker_close' }); } else { - console.warn('Ignoring sticker message. Invalid capability'); + logger.warn('Ignoring sticker message. Invalid capability'); } break; } @@ -314,7 +314,7 @@ export default class AppTile extends React.Component { private grantWidgetPermission = (): void => { const roomId = this.props.room.roomId; - console.info("Granting permission for widget to load: " + this.props.app.eventId); + logger.info("Granting permission for widget to load: " + this.props.app.eventId); const current = SettingsStore.getValue("allowedWidgets", roomId); current[this.props.app.eventId] = true; const level = SettingsStore.firstSupportedLevel("allowedWidgets"); @@ -324,7 +324,7 @@ export default class AppTile extends React.Component { // Fetch a token for the integration manager, now that we're allowed to this.startWidget(); }).catch(err => { - console.error(err); + logger.error(err); // We don't really need to do anything about this - the user will just hit the button again. }); }; diff --git a/src/components/views/elements/DesktopBuildsNotice.tsx b/src/components/views/elements/DesktopBuildsNotice.tsx index f2441b83a47..5938bc6dfee 100644 --- a/src/components/views/elements/DesktopBuildsNotice.tsx +++ b/src/components/views/elements/DesktopBuildsNotice.tsx @@ -22,6 +22,8 @@ import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; import { UserTab } from "../dialogs/UserSettingsDialog"; +import { logger } from "matrix-js-sdk/src/logger"; + export enum WarningKind { Files, Search, @@ -83,7 +85,7 @@ export default function DesktopBuildsNotice({ isRoomEncrypted, kind }: IProps) { // for safety if (!text) { - console.warn("Unknown desktop builds warning kind: ", kind); + logger.warn("Unknown desktop builds warning kind: ", kind); return null; } diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx index 9e6833696f2..3e90d9e3a5b 100644 --- a/src/components/views/elements/EffectsOverlay.tsx +++ b/src/components/views/elements/EffectsOverlay.tsx @@ -20,6 +20,8 @@ import ICanvasEffect from '../../../effects/ICanvasEffect'; import { CHAT_EFFECTS } from '../../../effects'; import UIStore, { UI_EVENTS } from "../../../stores/UIStore"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { roomWidth: number; } @@ -38,7 +40,7 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { effect = new Effect(options); effectsRef.current[name] = effect; } catch (err) { - console.warn(`Unable to load effect module at '../../../effects/${name}.`, err); + logger.warn(`Unable to load effect module at '../../../effects/${name}.`, err); } } return effect; diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx index 50ea7d9a569..7e8686a35c4 100644 --- a/src/components/views/elements/ErrorBoundary.tsx +++ b/src/components/views/elements/ErrorBoundary.tsx @@ -25,6 +25,8 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import BugReportDialog from '../dialogs/BugReportDialog'; import AccessibleButton from './AccessibleButton'; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState { error: Error; } @@ -52,8 +54,8 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> { componentDidCatch(error: Error, { componentStack }: ErrorInfo): void { // Browser consoles are better at formatting output when native errors are passed // in their own `console.error` invocation. - console.error(error); - console.error( + logger.error(error); + logger.error( "The above error occured while React was rendering the following components:", componentStack, ); diff --git a/src/components/views/elements/Flair.js b/src/components/views/elements/Flair.js index 873d65d5bd4..280cac503e5 100644 --- a/src/components/views/elements/Flair.js +++ b/src/components/views/elements/Flair.js @@ -22,6 +22,8 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import { logger } from "matrix-js-sdk/src/logger"; + class FlairAvatar extends React.Component { constructor() { super(); @@ -92,7 +94,7 @@ export default class Flair extends React.Component { try { groupProfile = await FlairStore.getGroupProfileCached(this.context, groupId); } catch (err) { - console.error('Could not get profile for group', groupId, err); + logger.error('Could not get profile for group', groupId, err); } profiles.push(groupProfile); } diff --git a/src/components/views/elements/PersistentApp.tsx b/src/components/views/elements/PersistentApp.tsx index 8d0751cc1d0..d80a00584c3 100644 --- a/src/components/views/elements/PersistentApp.tsx +++ b/src/components/views/elements/PersistentApp.tsx @@ -25,17 +25,21 @@ import { EventSubscription } from 'fbemitter'; import AppTile from "./AppTile"; import { Room } from "matrix-js-sdk/src/models/room"; +interface IProps { + // none +} + interface IState { roomId: string; persistentWidgetId: string; } @replaceableComponent("views.elements.PersistentApp") -export default class PersistentApp extends React.Component<{}, IState> { +export default class PersistentApp extends React.Component { private roomStoreToken: EventSubscription; - constructor() { - super({}); + constructor(props: IProps) { + super(props); this.state = { roomId: RoomViewStore.getRoomId(), diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index 95d29fc9ae8..4a2b39f8914 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -29,6 +29,8 @@ import { mediaFromMxc } from "../../../customisations/Media"; import Tooltip from './Tooltip'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + @replaceableComponent("views.elements.Pill") class Pill extends React.Component { static roomNotifPos(text) { @@ -188,7 +190,7 @@ class Pill extends React.Component { }; this.setState({ member }); }).catch((err) => { - console.error('Could not retrieve profile data for ' + userId + ':', err); + logger.error('Could not retrieve profile data for ' + userId + ':', err); }); } diff --git a/src/components/views/elements/ReplyThread.tsx b/src/components/views/elements/ReplyChain.tsx similarity index 89% rename from src/components/views/elements/ReplyThread.tsx rename to src/components/views/elements/ReplyChain.tsx index 7dd81f469a8..50efdc92fb5 100644 --- a/src/components/views/elements/ReplyThread.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -35,6 +35,7 @@ import Spinner from './Spinner'; import ReplyTile from "../rooms/ReplyTile"; import Pill from './Pill'; import { Room } from 'matrix-js-sdk/src/models/room'; +import { RelationType } from 'matrix-js-sdk/src/@types/event'; /** * This number is based on the previous behavior - if we have message of height @@ -45,7 +46,7 @@ const SHOW_EXPAND_QUOTE_PIXELS = 60; interface IProps { // the latest event in this chain of replies parentEv?: MatrixEvent; - // called when the ReplyThread contents has changed, including EventTiles thereof + // called when the ReplyChain contents has changed, including EventTiles thereof onHeightChanged: () => void; permalinkCreator: RoomPermalinkCreator; // Specifies which layout to use. @@ -71,8 +72,8 @@ interface IState { // This component does no cycle detection, simply because the only way to make such a cycle would be to // craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would // be low as each event being loaded (after the first) is triggered by an explicit user action. -@replaceableComponent("views.elements.ReplyThread") -export default class ReplyThread extends React.Component { +@replaceableComponent("views.elements.ReplyChain") +export default class ReplyChain extends React.Component { static contextType = MatrixClientContext; private unmounted = false; private room: Room; @@ -226,17 +227,34 @@ export default class ReplyThread extends React.Component { public static makeReplyMixIn(ev: MatrixEvent) { if (!ev) return {}; - return { + + const mixin: any = { 'm.relates_to': { 'm.in_reply_to': { 'event_id': ev.getId(), }, }, }; + + /** + * If the event replied is part of a thread + * Add the `m.thread` relation so that clients + * that know how to handle that relation will + * be able to render them more accurately + */ + if (ev.isThreadRelation) { + mixin['m.relates_to'] = { + ...mixin['m.relates_to'], + rel_type: RelationType.Thread, + event_id: ev.threadRootId, + }; + } + + return mixin; } - public static hasThreadReply(event: MatrixEvent) { - return Boolean(ReplyThread.getParentEventId(event)); + public static hasReply(event: MatrixEvent) { + return Boolean(ReplyChain.getParentEventId(event)); } componentDidMount() { @@ -270,7 +288,7 @@ export default class ReplyThread extends React.Component { private async initialize(): Promise { const { parentEv } = this.props; // at time of making this component we checked that props.parentEv has a parentEventId - const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv)); + const ev = await this.getEvent(ReplyChain.getParentEventId(parentEv)); if (this.unmounted) return; @@ -288,7 +306,7 @@ export default class ReplyThread extends React.Component { private async getNextEvent(ev: MatrixEvent): Promise { try { - const inReplyToEventId = ReplyThread.getParentEventId(ev); + const inReplyToEventId = ReplyChain.getParentEventId(ev); return await this.getEvent(inReplyToEventId); } catch (e) { return null; @@ -336,15 +354,15 @@ export default class ReplyThread extends React.Component { dis.fire(Action.FocusSendMessageComposer); }; - private getReplyThreadColorClass(ev: MatrixEvent): string { - return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyThread"); + private getReplyChainColorClass(ev: MatrixEvent): string { + return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyChain"); } render() { let header = null; if (this.state.err) { - header =

+ header =
{ _t('Unable to load event that was replied to, ' + 'it either does not exist or you do not have permission to view it.') @@ -353,10 +371,10 @@ export default class ReplyThread extends React.Component { } else if (this.state.loadedEv) { const ev = this.state.loadedEv; const room = this.context.getRoom(ev.getRoomId()); - header =
+ header =
{ _t('In reply to ', {}, { - 'a': (sub) => { sub }, + 'a': (sub) => { sub }, 'pill': ( { }
; } else if (this.props.forExport) { - const eventId = ReplyThread.getParentEventId(this.props.parentEv); - header =

+ const eventId = ReplyChain.getParentEventId(this.props.parentEv); + header =

{ _t("In reply to this message", {}, { a: (sub) => ( @@ -386,12 +404,12 @@ export default class ReplyThread extends React.Component { const { isQuoteExpanded } = this.props; const evTiles = this.state.events.map((ev) => { const classname = classNames({ - 'mx_ReplyThread': true, - [this.getReplyThreadColorClass(ev)]: true, + 'mx_ReplyChain': true, + [this.getReplyChainColorClass(ev)]: true, // We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false - 'mx_ReplyThread--expanded': isQuoteExpanded === true, + 'mx_ReplyChain--expanded': isQuoteExpanded === true, // We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false - 'mx_ReplyThread--collapsed': isQuoteExpanded === false, + 'mx_ReplyChain--collapsed': isQuoteExpanded === false, }); return (

@@ -405,7 +423,7 @@ export default class ReplyThread extends React.Component { ); }); - return
+ return
{ header }
{ evTiles }
; diff --git a/src/components/views/elements/SyntaxHighlight.tsx b/src/components/views/elements/SyntaxHighlight.tsx index cd65cddfba0..36920af27a3 100644 --- a/src/components/views/elements/SyntaxHighlight.tsx +++ b/src/components/views/elements/SyntaxHighlight.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from 'react'; -import { highlightBlock } from 'highlight.js'; +import highlight from 'highlight.js'; import { replaceableComponent } from "../../../utils/replaceableComponent"; interface IProps { @@ -33,7 +33,7 @@ export default class SyntaxHighlight extends React.Component { // componentDidUpdate used here for reusability public componentDidUpdate(): void { - if (this.el) highlightBlock(this.el); + if (this.el) highlight.highlightElement(this.el); } // call componentDidUpdate because _ref is fired on initial render diff --git a/src/components/views/elements/TagTile.js b/src/components/views/elements/TagTile.js index 14a5ed8e6b6..1205a2050f1 100644 --- a/src/components/views/elements/TagTile.js +++ b/src/components/views/elements/TagTile.js @@ -33,6 +33,8 @@ import SettingsStore from "../../../settings/SettingsStore"; import { mediaFromMxc } from "../../../customisations/Media"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + // A class for a child of GroupFilterPanel (possibly wrapped in a DNDTagTile) that represents // a thing to click on for the user to filter the visible rooms in the RoomList to: // - Rooms that are part of the group @@ -85,7 +87,7 @@ export default class TagTile extends React.Component { if (this.unmounted) return; this.setState({ profile }); }).catch((err) => { - console.warn('Could not fetch group profile for ' + this.props.tag, err); + logger.warn('Could not fetch group profile for ' + this.props.tag, err); }); }; diff --git a/src/components/views/groups/GroupRoomInfo.js b/src/components/views/groups/GroupRoomInfo.js index d8f086700d5..b59144a716a 100644 --- a/src/components/views/groups/GroupRoomInfo.js +++ b/src/components/views/groups/GroupRoomInfo.js @@ -27,6 +27,8 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import { logger } from "matrix-js-sdk/src/logger"; + @replaceableComponent("views.groups.GroupRoomInfo") export default class GroupRoomInfo extends React.Component { static contextType = MatrixClientContext; @@ -103,7 +105,7 @@ export default class GroupRoomInfo extends React.Component { action: "view_group_room_list", }); }).catch((err) => { - console.error(`Error whilst removing ${roomId} from ${groupId}`, err); + logger.error(`Error whilst removing ${roomId} from ${groupId}`, err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, { title: _t("Failed to remove room from community"), @@ -133,7 +135,7 @@ export default class GroupRoomInfo extends React.Component { const roomId = this.props.groupRoomId; const roomName = this.state.groupRoom.displayname; GroupStore.updateGroupRoomVisibility(this.props.groupId, roomId, isPublic).catch((err) => { - console.error(`Error whilst changing visibility of ${roomId} in ${groupId} to ${isPublic}`, err); + logger.error(`Error whilst changing visibility of ${roomId} in ${groupId} to ${isPublic}`, err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, { title: _t("Something went wrong!"), diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js index 21308ac056a..cf1a5b91ce7 100644 --- a/src/components/views/groups/GroupTile.js +++ b/src/components/views/groups/GroupTile.js @@ -26,6 +26,8 @@ import { _t } from "../../../languageHandler"; import TagOrderActions from "../../../actions/TagOrderActions"; import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore"; +import { logger } from "matrix-js-sdk/src/logger"; + @replaceableComponent("views.groups.GroupTile") class GroupTile extends React.Component { static propTypes = { @@ -51,7 +53,7 @@ class GroupTile extends React.Component { FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => { this.setState({ profile }); }).catch((err) => { - console.error('Error whilst getting cached profile for GroupTile', err); + logger.error('Error whilst getting cached profile for GroupTile', err); }); } diff --git a/src/components/views/groups/GroupUserSettings.js b/src/components/views/groups/GroupUserSettings.js index efb392c54f7..85fd15e2389 100644 --- a/src/components/views/groups/GroupUserSettings.js +++ b/src/components/views/groups/GroupUserSettings.js @@ -20,6 +20,8 @@ import { _t } from '../../../languageHandler'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + @replaceableComponent("views.groups.GroupUserSettings") export default class GroupUserSettings extends React.Component { static contextType = MatrixClientContext; @@ -33,7 +35,7 @@ export default class GroupUserSettings extends React.Component { this.context.getJoinedGroups().then((result) => { this.setState({ groups: result.groups || [], error: null }); }, (err) => { - console.error(err); + logger.error(err); this.setState({ groups: null, error: err }); }); } diff --git a/src/components/views/messages/DownloadActionButton.tsx b/src/components/views/messages/DownloadActionButton.tsx index 6dc48b09363..35fd5c49bea 100644 --- a/src/components/views/messages/DownloadActionButton.tsx +++ b/src/components/views/messages/DownloadActionButton.tsx @@ -20,7 +20,7 @@ import React from "react"; import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex"; import Spinner from "../elements/Spinner"; import classNames from "classnames"; -import { _t } from "../../../languageHandler"; +import { _t, _td } from "../../../languageHandler"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { FileDownloader } from "../../../utils/FileDownloader"; @@ -36,6 +36,7 @@ interface IProps { interface IState { loading: boolean; blob?: Blob; + tooltip: string; } @replaceableComponent("views.messages.DownloadActionButton") @@ -47,12 +48,17 @@ export default class DownloadActionButton extends React.PureComponent { if (this.state.loading) return; + if (this.props.mediaEventHelperGet().media.isEncrypted) { + this.setState({ tooltip: _td("Decrypting") }); + } + this.setState({ loading: true }); if (this.state.blob) { @@ -87,7 +93,7 @@ export default class DownloadActionButton extends React.PureComponent diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 3611435e559..61604f55bda 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -27,6 +27,8 @@ import { PlaybackManager } from "../../../audio/PlaybackManager"; import { isVoiceMessage } from "../../../utils/EventUtils"; import { PlaybackQueue } from "../../../audio/PlaybackQueue"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState { error?: Error; playback?: Playback; @@ -49,12 +51,12 @@ export default class MAudioBody extends React.PureComponent buffer = await blob.arrayBuffer(); } catch (e) { this.setState({ error: e }); - console.warn("Unable to decrypt audio message", e); + logger.warn("Unable to decrypt audio message", e); return; // stop processing the audio file } } catch (e) { this.setState({ error: e }); - console.warn("Unable to decrypt/download audio message", e); + logger.warn("Unable to decrypt/download audio message", e); return; // stop processing the audio file } diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index 80c6b16f0d1..f193b7c3244 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -170,7 +170,7 @@ export default class MFileBody extends React.Component { decryptedBlob: await this.props.mediaEventHelper.sourceBlob.value, }); } catch (err) { - console.warn("Unable to decrypt attachment: ", err); + logger.warn("Unable to decrypt attachment: ", err); Modal.createTrackedDialog('Error decrypting attachment', '', ErrorDialog, { title: _t("Error"), description: _t("Error decrypting attachment"), diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 072e111c4b8..85821129a1e 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -34,6 +34,8 @@ import { IBodyProps } from "./IBodyProps"; import classNames from 'classnames'; import { CSSTransition, SwitchTransition } from 'react-transition-group'; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState { decryptedUrl?: string; decryptedThumbnailUrl?: string; @@ -275,7 +277,7 @@ export default class MImageBody extends React.Component { }); } catch (err) { if (this.unmounted) return; - console.warn("Unable to decrypt attachment: ", err); + logger.warn("Unable to decrypt attachment: ", err); // Set a placeholder image when we can't decrypt the image. this.setState({ error: err, diff --git a/src/components/views/messages/MKeyVerificationRequest.tsx b/src/components/views/messages/MKeyVerificationRequest.tsx index ce828beed04..e1b91f63d76 100644 --- a/src/components/views/messages/MKeyVerificationRequest.tsx +++ b/src/components/views/messages/MKeyVerificationRequest.tsx @@ -27,6 +27,8 @@ import EventTileBubble from "./EventTileBubble"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import AccessibleButton from '../elements/AccessibleButton'; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { mxEvent: MatrixEvent; } @@ -68,7 +70,7 @@ export default class MKeyVerificationRequest extends React.Component { this.openRequest(); await request.accept(); } catch (err) { - console.error(err.message); + logger.error(err.message); } } }; @@ -79,7 +81,7 @@ export default class MKeyVerificationRequest extends React.Component { try { await request.cancel(); } catch (err) { - console.error(err.message); + logger.error(err.message); } } }; diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index d119662f8a9..b2e587e51ab 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -180,7 +180,7 @@ export default class MVideoBody extends React.PureComponent }); } } catch (err) { - console.warn("Unable to decrypt attachment: ", err); + logger.warn("Unable to decrypt attachment: ", err); // Set a placeholder image when we can't decrypt the image. this.setState({ error: err, diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 2246d2bacce..56ae08453c2 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -41,19 +41,19 @@ import classNames from 'classnames'; import SettingsStore from '../../../settings/SettingsStore'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; -import ReplyThread from '../elements/ReplyThread'; +import ReplyChain from '../elements/ReplyChain'; interface IOptionsButtonProps { mxEvent: MatrixEvent; // TODO: Types getTile: () => any | null; - getReplyThread: () => ReplyThread; + getReplyChain: () => ReplyChain; permalinkCreator: RoomPermalinkCreator; onFocusChange: (menuDisplayed: boolean) => void; } const OptionsButton: React.FC = - ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => { + ({ mxEvent, getTile, getReplyChain, permalinkCreator, onFocusChange }) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [onFocus, isActive, ref] = useRovingTabIndex(button); useEffect(() => { @@ -63,7 +63,7 @@ const OptionsButton: React.FC = let contextMenu; if (menuDisplayed) { const tile = getTile && getTile(); - const replyThread = getReplyThread && getReplyThread(); + const replyChain = getReplyChain && getReplyChain(); const buttonRect = button.current.getBoundingClientRect(); contextMenu = = mxEvent={mxEvent} permalinkCreator={permalinkCreator} eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined} - collapseReplyThread={replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined} + collapseReplyChain={replyChain && replyChain.canCollapse() ? replyChain.collapse : undefined} onFinished={closeMenu} />; } @@ -133,7 +133,7 @@ interface IMessageActionBarProps { reactions?: Relations; // TODO: Types getTile: () => any | null; - getReplyThread: () => ReplyThread | undefined; + getReplyChain: () => ReplyChain | undefined; permalinkCreator?: RoomPermalinkCreator; onFocusChange?: (menuDisplayed: boolean) => void; toggleThreadExpanded: () => void; @@ -191,6 +191,7 @@ export default class MessageActionBar extends React.PureComponent - { SettingsStore.getValue("feature_thread") && ( + { (SettingsStore.getValue("feature_thread") + && this.context.timelineRenderingType !== TimelineRenderingType.Thread) && ( { setTimeout(() => { if (this.unmounted) return; for (let i = 0; i < codes.length; i++) { - // If the code already has the hljs class we want to skip this. - // This happens after the codeblock was edited. - if (codes[i].className.includes("hljs")) continue; this.highlightCode(codes[i]); } }, 10); @@ -212,30 +211,57 @@ export default class TextualBody extends React.Component { private addLineNumbers(pre: HTMLPreElement): void { // Calculate number of lines in pre const number = pre.innerHTML.replace(/\n(<\/code>)?$/, "").split(/\n/).length; - pre.innerHTML = '' + pre.innerHTML + ''; - const lineNumbers = pre.getElementsByClassName("mx_EventTile_lineNumbers")[0]; + const lineNumbers = document.createElement('span'); + lineNumbers.className = 'mx_EventTile_lineNumbers'; // Iterate through lines starting with 1 (number of the first line is 1) for (let i = 1; i <= number; i++) { - lineNumbers.innerHTML += '' + i + ''; + const s = document.createElement('span'); + s.textContent = i.toString(); + lineNumbers.appendChild(s); } + pre.prepend(lineNumbers); + pre.append(document.createElement('span')); } private highlightCode(code: HTMLElement): void { - // Auto-detect language only if enabled and only for codeblocks - if ( + if (code.textContent.length > MAX_HIGHLIGHT_LENGTH) { + console.log( + "Code block is bigger than highlight limit (" + + code.textContent.length + " > " + MAX_HIGHLIGHT_LENGTH + + "): not highlighting", + ); + return; + } + + let advertisedLang; + for (const cl of code.className.split(/\s+/)) { + if (cl.startsWith('language-')) { + const maybeLang = cl.split('-', 2)[1]; + if (highlight.getLanguage(maybeLang)) { + advertisedLang = maybeLang; + break; + } + } + } + + if (advertisedLang) { + // If the code says what language it is, highlight it in that language + // We don't use highlightElement here because we can't force language detection + // off. It should use the one we've found in the CSS class but we'd rather pass + // it in explicitly to make sure. + code.innerHTML = highlight.highlight(advertisedLang, code.textContent).value; + } else if ( SettingsStore.getValue("enableSyntaxHighlightLanguageDetection") && code.parentElement instanceof HTMLPreElement ) { - highlight.highlightBlock(code); - } else { - // Only syntax highlight if there's a class starting with language- - const classes = code.className.split(/\s+/).filter(function(cl) { - return cl.startsWith('language-') && !cl.startsWith('language-_'); - }); - - if (classes.length != 0) { - highlight.highlightBlock(code); - } + // User has language detection enabled and the code is within a pre + // we only auto-highlight if the code block is in a pre), so highlight + // the block with auto-highlighting enabled. + // We pass highlightjs the text to highlight rather than letting it + // work on the DOM with highlightElement because that also adds CSS + // classes to the pre/code element that we don't want (the CSS + // conflicts with our own). + code.innerHTML = highlight.highlightAuto(code.textContent).value; } } @@ -479,7 +505,7 @@ export default class TextualBody extends React.Component { const content = mxEvent.getContent(); // only strip reply if this is the original replying event, edits thereafter do not have the fallback - const stripReply = !mxEvent.replacingEvent() && !!ReplyThread.getParentEventId(mxEvent); + const stripReply = !mxEvent.replacingEvent() && !!ReplyChain.getParentEventId(mxEvent); let body = HtmlUtils.bodyToHtml(content, this.props.highlights, { disableBigEmoji: content.msgtype === MsgType.Emote || !SettingsStore.getValue('TextualBody.enableBigEmoji'), diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index c82e5a3f803..bb6ddb0f741 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -29,6 +29,8 @@ import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import PinnedEventTile from "../rooms/PinnedEventTile"; import { useRoomState } from "../../../hooks/useRoomState"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { room: Room; onClose(): void; @@ -107,8 +109,8 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => { return event; } } catch (err) { - console.error("Error looking up pinned event " + eventId + " in room " + room.roomId); - console.error(err); + logger.error("Error looking up pinned event " + eventId + " in room " + room.roomId); + logger.error(err); } return null; }); diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index e73e51bcb8d..195cf03af45 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -582,7 +582,7 @@ const RoomKickButton = ({ room, member, startUpdating, stopUpdating }: Omit = ({ member }) => { // so first yield to allow to rerender after closing the dialog. await Promise.resolve(); - console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`); + logger.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`); await Promise.all(eventsToRedact.map(async event => { try { await cli.redactEvent(roomId, event.getId()); } catch (err) { // log and swallow errors - console.error("Could not redact", event.getId()); - console.error(err); + logger.error("Could not redact", event.getId()); + logger.error(err); } })); - console.info(`Finished redacting recent ${count} messages for ${user} in ${roomId}`); + logger.info(`Finished redacting recent ${count} messages for ${user} in ${roomId}`); } }; @@ -744,7 +744,7 @@ const BanToggleButton = ({ room, member, startUpdating, stopUpdating }: Omit = ({ member, room, powerLevels, try { if (!(await warnSelfDemote(SpaceStore.spacesEnabled && room?.isSpaceRoom()))) return; } catch (e) { - console.error("Failed to warn about self demotion: ", e); + logger.error("Failed to warn about self demotion: ", e); return; } } @@ -817,7 +817,7 @@ const MuteToggleButton: React.FC = ({ member, room, powerLevels, // get out of sync if we force setState here! logger.log("Mute toggle success"); }, function(err) { - console.error("Mute error: " + err); + logger.error("Mute error: " + err); Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to mute user"), @@ -1130,7 +1130,7 @@ const PowerLevelEditor: React.FC<{ // get out of sync if we force setState here! logger.log("Power change success"); }, function(err) { - console.error("Failed to change power level " + err); + logger.error("Failed to change power level " + err); Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to change power level"), @@ -1166,7 +1166,7 @@ const PowerLevelEditor: React.FC<{ try { if (!(await warnSelfDemote(SpaceStore.spacesEnabled && room?.isSpaceRoom()))) return; } catch (e) { - console.error("Failed to warn about self demotion: ", e); + logger.error("Failed to warn about self demotion: ", e); } } @@ -1315,8 +1315,8 @@ const BasicUserInfo: React.FC<{ try { await cli.deactivateSynapseUser(member.userId); } catch (err) { - console.error("Failed to deactivate user"); - console.error(err); + logger.error("Failed to deactivate user"); + logger.error(err); Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, { title: _t('Failed to deactivate user'), diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index 8ed56bb2c3c..881dac33d40 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -35,6 +35,8 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import AccessibleButton from "../elements/AccessibleButton"; import VerificationShowSas from "../verification/VerificationShowSas"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { layout: string; request: VerificationRequest; @@ -234,7 +236,7 @@ export default class VerificationPanel extends React.PureComponent -

{ _t("Verified") }

{ description }

{ text ?

{ text }

: null } @@ -323,7 +324,6 @@ export default class VerificationPanel extends React.PureComponent : ; return
-

{ _t("Compare emoji") }

{ emojis }
; } @@ -335,7 +335,7 @@ export default class VerificationPanel extends React.PureComponent { MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias", eventContent, "").catch((err) => { - console.error(err); + logger.error(err); Modal.createTrackedDialog('Error updating main address', '', ErrorDialog, { title: _t("Error updating main address"), description: _t( @@ -204,7 +206,7 @@ export default class AliasSettings extends React.Component { MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias", eventContent, "").catch((err) => { - console.error(err); + logger.error(err); Modal.createTrackedDialog('Error updating alternative addresses', '', ErrorDialog, { title: _t("Error updating main address"), description: _t( @@ -236,7 +238,7 @@ export default class AliasSettings extends React.Component { this.changeCanonicalAlias(alias); } }).catch((err) => { - console.error(err); + logger.error(err); Modal.createTrackedDialog('Error creating address', '', ErrorDialog, { title: _t("Error creating address"), description: _t( @@ -259,7 +261,7 @@ export default class AliasSettings extends React.Component { this.changeCanonicalAlias(null); } }).catch((err) => { - console.error(err); + logger.error(err); let description; if (err.errcode === "M_FORBIDDEN") { description = _t("You don't have permission to delete the address."); diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 23b497398a3..f815cd77ccb 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -24,6 +24,8 @@ import ErrorDialog from "../dialogs/ErrorDialog"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + const GROUP_ID_REGEX = /\+\S+:\S+/; @replaceableComponent("views.room_settings.RelatedGroupSettings") @@ -53,7 +55,7 @@ export default class RelatedGroupSettings extends React.Component { this.context.sendStateEvent(this.props.roomId, 'm.room.related_groups', { groups: newGroupsList, }, '').catch((err) => { - console.error(err); + logger.error(err); Modal.createTrackedDialog('Error updating flair', '', ErrorDialog, { title: _t("Error updating flair"), description: _t( diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index edf4f515d25..4d13fab190b 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -49,6 +49,8 @@ import { ICompletion } from "../../../autocomplete/Autocompleter"; import { AutocompleteAction, getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindingsManager'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + // matches emoticons which follow the start of a line or whitespace const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s|:^$'); export const REGEX_EMOTICON = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')$'); @@ -207,7 +209,7 @@ export default class BasicMessageEditor extends React.Component try { setSelection(this.editorRef.current, this.props.model, selection); } catch (err) { - console.error(err); + logger.error(err); } // if caret selection is a range, take the end position const position = selection instanceof Range ? selection.end : selection; @@ -596,7 +598,7 @@ export default class BasicMessageEditor extends React.Component this.setState({ showVisualBell: true }); } } catch (err) { - console.error(err); + logger.error(err); } } diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index 85e416c1fab..89c710fd174 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -224,7 +224,7 @@ class EditMessageComposer extends React.Component partCreator.deserializePart(p)); return parts; } catch (e) { - console.error("Error parsing editing state: ", e); + logger.error("Error parsing editing state: ", e); } } } @@ -302,7 +302,7 @@ class EditMessageComposer extends React.Component { private isListeningForReceipts: boolean; // TODO: Types private tile = React.createRef(); - private replyThread = React.createRef(); + private replyChain = React.createRef(); public readonly ref = createRef(); @@ -475,6 +478,9 @@ export default class EventTile extends React.Component { this.props.mxEvent.once(ThreadEvent.Ready, this.updateThread); this.props.mxEvent.on(ThreadEvent.Update, this.updateThread); } + + const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + room?.on(ThreadEvent.New, this.onNewThread); } private updateThread = (thread) => { @@ -516,6 +522,9 @@ export default class EventTile extends React.Component { this.props.mxEvent.off(ThreadEvent.Ready, this.updateThread); this.props.mxEvent.off(ThreadEvent.Update, this.updateThread); } + + const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + room?.off(ThreadEvent.New, this.onNewThread); } componentDidUpdate(prevProps, prevState, snapshot) { @@ -526,21 +535,39 @@ export default class EventTile extends React.Component { } } + private onNewThread = (thread: Thread) => { + if (thread.id === this.props.mxEvent.getId()) { + this.updateThread(thread); + const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + room.off(ThreadEvent.New, this.onNewThread); + } + }; + private renderThreadInfo(): React.ReactNode { if (!SettingsStore.getValue("feature_thread")) { return null; } - const thread = this.state.thread; + /** + * Accessing the threads value through the room due to a race condition + * that will be solved when there are proper backend support for threads + * We currently have no reliable way to discover than an event is a thread + * when we are at the sync stage + */ const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + const thread = room?.threads.get(this.props.mxEvent.getId()); + + if (thread && !thread.ready) { + thread.addEvent(this.props.mxEvent, true); + } + if (!thread || this.props.showThreadInfo === false || thread.length <= 1) { return null; } - const avatars = Array.from(thread.participants).map((mxId: string) => { - const member = room.getMember(mxId); - return ; - }); + const threadMessagePreview = MessagePreviewStore.instance.generateThreadPreview(this.state.thread); + + if (!threadMessagePreview) return null; return (
{ dispatchShowThreadEvent(this.props.mxEvent); }} > - - { avatars } + + + { _t("%(count)s reply", { + count: thread.length - 1, + }) } - { thread.length - 1 } { thread.length === 2 ? 'reply' : 'replies' } + +
+ + { threadMessagePreview } + +
); } @@ -898,7 +933,7 @@ export default class EventTile extends React.Component { // TODO: Types getTile: () => any | null = () => this.tile.current; - getReplyThread = () => this.replyThread.current; + getReplyChain = () => this.replyChain.current; getReactions = () => { if ( @@ -941,7 +976,7 @@ export default class EventTile extends React.Component { // before trying to instantiate us if (!tileHandler) { const { mxEvent } = this.props; - console.warn(`Event type not supported: type:${eventType} isState:${mxEvent.isState()}`); + logger.warn(`Event type not supported: type:${eventType} isState:${mxEvent.isState()}`); return
{ _t('This event could not be displayed') } @@ -1067,7 +1102,7 @@ export default class EventTile extends React.Component { reactions={this.state.reactions} permalinkCreator={this.props.permalinkCreator} getTile={this.getTile} - getReplyThread={this.getReplyThread} + getReplyChain={this.getReplyChain} onFocusChange={this.onActionBarFocusChange} isQuoteExpanded={isQuoteExpanded} toggleThreadExpanded={() => this.setQuoteExpanded(!isQuoteExpanded)} @@ -1179,12 +1214,26 @@ export default class EventTile extends React.Component { ]); } case TileShape.Thread: { + const thread = haveTileForEvent(this.props.mxEvent) && + ReplyChain.hasReply(this.props.mxEvent) ? ( + ) : null; const room = this.context.getRoom(this.props.mxEvent.getRoomId()); return React.createElement(this.props.as || "li", { "className": classes, "aria-live": ariaLive, "aria-atomic": true, "data-scroll-tokens": scrollToken, + "data-has-reply": !!thread, }, [
@@ -1200,6 +1249,7 @@ export default class EventTile extends React.Component {
,
+ { thread } { default: { const thread = haveTileForEvent(this.props.mxEvent) && - ReplyThread.hasThreadReply(this.props.mxEvent) ? ( - { { keyRequestInfo } { actionBar } { this.props.layout === Layout.IRC && (reactionsRow) } - { this.renderThreadInfo() }
+ { this.renderThreadInfo() } { this.props.layout !== Layout.IRC && (reactionsRow) } { msgOption } ) diff --git a/src/components/views/rooms/LinkPreviewGroup.tsx b/src/components/views/rooms/LinkPreviewGroup.tsx index eed13aff0fc..fa9b601d5e0 100644 --- a/src/components/views/rooms/LinkPreviewGroup.tsx +++ b/src/components/views/rooms/LinkPreviewGroup.tsx @@ -25,6 +25,8 @@ import { _t } from "../../../languageHandler"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; +import { logger } from "matrix-js-sdk/src/logger"; + const INITIAL_NUM_PREVIEWS = 2; interface IProps { @@ -92,7 +94,7 @@ const fetchPreviews = (cli: MatrixClient, links: string[], ts: number): return [link, preview]; } } catch (error) { - console.error("Failed to get URL preview: " + error); + logger.error("Failed to get URL preview: " + error); } })).then(a => a.filter(Boolean)) as Promise<[string, IPreviewUrlResponse][]>; }; diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 139ec46e6f9..bf5411b0daa 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -48,6 +48,8 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent import { UIComponent } from "../../../settings/UIFeature"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; +import { logger } from "matrix-js-sdk/src/logger"; + const getSearchQueryLSKey = (roomId: string) => `mx_MemberList_searchQuarry_${roomId}`; const INITIAL_LOAD_NUM_MEMBERS = 30; @@ -184,7 +186,7 @@ export default class MemberList extends React.Component { try { searchQuery = window.localStorage.getItem(getSearchQueryLSKey(this.props.roomId)); } catch (error) { - console.warn("Failed to get last the MemberList search query", error); + logger.warn("Failed to get last the MemberList search query", error); } // set the state after determining showPresence to make sure it's @@ -433,7 +435,7 @@ export default class MemberList extends React.Component { try { window.localStorage.setItem(getSearchQueryLSKey(this.props.roomId), searchQuery); } catch (error) { - console.warn("Failed to set the last MemberList search query", error); + logger.warn("Failed to set the last MemberList search query", error); } this.setState({ diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 0d0b7fa441d..79f613b9965 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -55,6 +55,7 @@ import UIStore, { UI_EVENTS } from '../../../stores/UIStore'; import Modal from "../../../Modal"; import InfoDialog from "../dialogs/InfoDialog"; import { RelationType } from 'matrix-js-sdk/src/@types/event'; +import RoomContext from '../../../contexts/RoomContext'; let instanceCount = 0; const NARROW_MODE_BREAKPOINT = 500; @@ -116,7 +117,7 @@ const EmojiButton: React.FC = ({ addEmoji, menuPosition, narr className={className} onClick={openMenu} title={!narrowMode && _t('Emoji picker')} - label={narrowMode && _t("Add emoji")} + label={narrowMode ? _t("Add emoji") : null} /> { contextMenu } @@ -227,7 +228,6 @@ interface IProps { permalinkCreator: RoomPermalinkCreator; replyToEvent?: MatrixEvent; relation?: IEventRelation; - showReplyPreview?: boolean; e2eStatus?: E2EStatus; compact?: boolean; } @@ -252,8 +252,9 @@ export default class MessageComposer extends React.Component { private ref: React.RefObject = createRef(); private instanceId: number; + public static contextType = RoomContext; + static defaultProps = { - showReplyPreview: true, compact: false, }; @@ -294,7 +295,7 @@ export default class MessageComposer extends React.Component { }; private onAction = (payload: ActionPayload) => { - if (payload.action === 'reply_to_event') { + if (payload.action === 'reply_to_event' && payload.context === this.context.timelineRenderingType) { // add a timeout for the reply preview to be rendered, so // that the ScrollPanel listening to the resizeNotifier can // correctly measure it's new height and scroll down to keep @@ -485,13 +486,14 @@ export default class MessageComposer extends React.Component { className="mx_MessageComposer_button mx_MessageComposer_stickers" onClick={() => this.showStickers(!this.state.showStickers)} title={title} - label={this.state.narrowMode && _t("Send a sticker")} + label={this.state.narrowMode ? _t("Send a sticker") : null} />, ); } if (!this.state.haveRecording && !this.state.narrowMode) { buttons.push( this.voiceRecordingButton.current?.onRecordStartEndClick()} title={_t("Send voice message")} @@ -615,7 +617,9 @@ export default class MessageComposer extends React.Component { room={this.props.room} showStickers={this.state.showStickers} setShowStickers={this.showStickers} - menuPosition={menuPosition} />, + menuPosition={menuPosition} + key="stickers" + />, ); const showSendButton = !this.state.isComposerEmpty || this.state.haveRecording; @@ -630,9 +634,9 @@ export default class MessageComposer extends React.Component {
{ recordingTooltip }
- { this.props.showReplyPreview && ( - - ) } +
{ controls } { this.renderButtons(menuPosition) } diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index fbaccfd6b57..3d92a9cced5 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -106,7 +106,11 @@ const NewRoomIntro = () => { topicText = _t("Topic: %(topic)s ", { topic }); } else if (canAddTopic) { topicText = _t("Add a topic to help people know what it is about.", {}, { - a: sub => { sub }, + a: sub => { sub }, }); } diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index cfc535b23d5..f2ce7e39670 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -26,6 +26,8 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import MemberAvatar from '../avatars/MemberAvatar'; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { // the RoomMember to show the RR for member?: RoomMember; @@ -145,7 +147,7 @@ export default class ReadReceiptMarker extends React.PureComponent { - private unmounted = false; - private readonly roomStoreToken: EventSubscription; - - constructor(props) { - super(props); - - this.state = { - event: RoomViewStore.getQuotingEvent(), - }; - - this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); - } - - componentWillUnmount() { - this.unmounted = true; - - // Remove RoomStore listener - if (this.roomStoreToken) { - this.roomStoreToken.remove(); - } - } - - private onRoomViewStoreUpdate = (): void => { - if (this.unmounted) return; - - const event = RoomViewStore.getQuotingEvent(); - if (this.state.event !== event) { - this.setState({ event }); - } - }; +export default class ReplyPreview extends React.Component { + public static contextType = RoomContext; - render() { - if (!this.state.event) return null; + public render(): JSX.Element { + if (!this.props.replyToEvent) return null; return
@@ -86,13 +54,13 @@ export default class ReplyPreview extends React.Component { src={require("../../../../res/img/cancel.svg")} width="18" height="18" - onClick={cancelQuoting} + onClick={() => cancelQuoting(this.context.timelineRenderingType)} />
diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index 01a9e2f18b4..b869979d58d 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -29,6 +29,8 @@ import { getEventDisplayInfo, isVoiceMessage } from '../../../utils/EventUtils'; import MFileBody from "../messages/MFileBody"; import MVoiceMessageBody from "../messages/MVoiceMessageBody"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { mxEvent: MatrixEvent; permalinkCreator?: RoomPermalinkCreator; @@ -107,7 +109,7 @@ export default class ReplyTile extends React.PureComponent { // before trying to instantiate us if (!tileHandler) { const { mxEvent } = this.props; - console.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`); + logger.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`); return
{ _t('This event could not be displayed') }
; diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index e3b4804ae6b..a1cd079ccbe 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -162,12 +162,14 @@ export default class RoomHeader extends React.Component { className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton" onClick={() => this.props.onCallPlaced(PlaceCallType.Voice)} title={_t("Voice call")} + key="voice" />; const videoCallButton = ) => ev.shiftKey ? this.displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(PlaceCallType.Video)} title={_t("Video call")} + key="video" />; buttons.push(voiceCallButton, videoCallButton); } @@ -177,6 +179,7 @@ export default class RoomHeader extends React.Component { className="mx_RoomHeader_button mx_RoomHeader_forgetButton" onClick={this.props.onForgetClick} title={_t("Forget room")} + key="forget" />; buttons.push(forgetButton); } @@ -188,6 +191,7 @@ export default class RoomHeader extends React.Component { })} onClick={this.props.onAppsClick} title={this.props.appsShown ? _t("Hide Widgets") : _t("Show Widgets")} + key="apps" />; buttons.push(appsButton); } @@ -197,6 +201,7 @@ export default class RoomHeader extends React.Component { className="mx_RoomHeader_button mx_RoomHeader_searchButton" onClick={this.props.onSearchClick} title={_t("Search")} + key="search" />; buttons.push(searchButton); } diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index dbefcbd3331..eb13d7e96e3 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -50,6 +50,8 @@ import IconizedContextMenu, { import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { room: Room; showMessagePreview: boolean; @@ -299,7 +301,7 @@ export default class RoomTile extends React.PureComponent { 0, )); } else { - console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.roomId}`); + logger.warn(`Unexpected tag ${tagId} applied to ${this.props.room.roomId}`); } if ((ev as React.KeyboardEvent).key === Key.ENTER) { diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index c31d891dec7..031a0534ca5 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -34,7 +34,7 @@ import { } from '../../../editor/serialize'; import BasicMessageComposer, { REGEX_EMOTICON } from "./BasicMessageComposer"; import { CommandPartCreator, Part, PartCreator, SerializedPart, Type } from '../../../editor/parts'; -import ReplyThread from "../elements/ReplyThread"; +import ReplyChain from "../elements/ReplyChain"; import { findEditableEvent } from '../../../utils/EventUtils'; import SendHistoryManager from "../../../SendHistoryManager"; import { Command, CommandCategories, getCommand } from '../../../SlashCommands'; @@ -57,6 +57,7 @@ import QuestionDialog from "../dialogs/QuestionDialog"; import { ActionPayload } from "../../../dispatcher/payloads"; import { decorateStartSendingTime, sendRoundTripMetric } from "../../../sendTimePerformanceMetrics"; import RoomContext from '../../../contexts/RoomContext'; +import DocumentPosition from "../../../editor/position"; function addReplyToMessageContent( content: IContent, @@ -64,7 +65,7 @@ function addReplyToMessageContent( permalinkCreator: RoomPermalinkCreator, relation?: IEventRelation, ): void { - const replyContent = ReplyThread.makeReplyMixIn(replyToEvent); + const replyContent = ReplyChain.makeReplyMixIn(replyToEvent); Object.assign(content, replyContent); if (relation) { @@ -228,6 +229,7 @@ export class SendMessageComposer extends React.Component { scalarClient.disableWidgetAssets(WidgetType.STICKERPICKER, this.state.widgetId).then(() => { logger.log('Assets disabled'); }).catch((err) => { - console.error('Failed to disable assets'); + logger.error('Failed to disable assets'); }); } else { - console.error("Cannot disable assets: no scalar client"); + logger.error("Cannot disable assets: no scalar client"); } } else { - console.warn('No widget ID specified, not disabling assets'); + logger.warn('No widget ID specified, not disabling assets'); } this.props.setShowStickers(false); WidgetUtils.removeStickerpickerWidgets().then(() => { this.forceUpdate(); }).catch((e) => { - console.error('Failed to remove sticker picker widget', e); + logger.error('Failed to remove sticker picker widget', e); }); }; @@ -152,7 +152,7 @@ export default class Stickerpicker extends React.PureComponent { } private imError(errorMsg: string, e: Error): void { - console.error(errorMsg, e); + logger.error(errorMsg, e); this.setState({ imError: _t(errorMsg), }); @@ -230,7 +230,7 @@ export default class Stickerpicker extends React.PureComponent { const messaging = WidgetMessagingStore.instance.getMessagingForId(this.state.stickerpickerWidget.id); if (messaging && visible !== this.prevSentVisibility) { messaging.updateVisibility(visible).catch(err => { - console.error("Error updating widget visibility: ", err); + logger.error("Error updating widget visibility: ", err); }); this.prevSentVisibility = visible; } diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index 1590ce0871d..c29c558655d 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -29,6 +29,8 @@ import ErrorDialog from '../dialogs/ErrorDialog'; import AccessibleButton from '../elements/AccessibleButton'; import SpaceStore from "../../../stores/SpaceStore"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { event: MatrixEvent; } @@ -100,7 +102,7 @@ export default class ThirdPartyMemberInfo extends React.Component { MatrixClientPeg.get().sendStateEvent(this.state.roomId, "m.room.third_party_invite", {}, this.state.stateKey) .catch((err) => { - console.error(err); + logger.error(err); // Revert echo because of error this.setState({ invited: true }); diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 288d97fc501..e50672ed4a7 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -36,6 +36,8 @@ import { NotificationColor } from "../../../stores/notifications/NotificationCol import InlineSpinner from "../elements/InlineSpinner"; import { PlaybackManager } from "../../../audio/PlaybackManager"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { room: Room; } @@ -75,7 +77,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent { const content: IBridgeStateEvent = this.props.ev.getContent(); // Validate if (!content.channel?.id || !content.protocol?.id) { - console.warn(`Bridge info event ${this.props.ev.getId()} has missing content. Tile will not render`); + logger.warn(`Bridge info event ${this.props.ev.getId()} has missing content. Tile will not render`); return null; } if (!content.bridgebot) { // Bridgebot was not required previously, so in order to not break rooms we are allowing // the sender to be used in place. When the proposal is merged, this should be removed. - console.warn(`Bridge info event ${this.props.ev.getId()} does not provide a 'bridgebot' key which` + logger.warn(`Bridge info event ${this.props.ev.getId()} does not provide a 'bridgebot' key which` + "is deprecated behaviour. Using sender for now."); content.bridgebot = this.props.ev.getSender(); } diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx index 9cba55e3288..bb46e84d701 100644 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -28,6 +28,8 @@ import { MatrixEvent } from 'matrix-js-sdk/src'; import SetupEncryptionDialog from '../dialogs/security/SetupEncryptionDialog'; import { accessSecretStorage } from '../../../SecurityManager'; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState { error?: Error; crossSigningPublicKeysOnDevice?: boolean; @@ -147,7 +149,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { }); } catch (e) { this.setState({ error: e }); - console.error("Error bootstrapping cross-signing", e); + logger.error("Error bootstrapping cross-signing", e); } if (this.unmounted) return; this.getUpdatedStatus(); diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx new file mode 100644 index 00000000000..67a3e8aa75e --- /dev/null +++ b/src/components/views/settings/CryptographyPanel.tsx @@ -0,0 +1,110 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import { _t } from '../../../languageHandler'; +import Modal from '../../../Modal'; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import AccessibleButton from "../elements/AccessibleButton"; +import * as FormattingUtils from "../../../utils/FormattingUtils"; +import SettingsStore from "../../../settings/SettingsStore"; +import SettingsFlag from "../elements/SettingsFlag"; +import { SettingLevel } from "../../../settings/SettingLevel"; + +interface IProps { +} + +interface IState { +} + +@replaceableComponent("views.settings.CryptographyPanel") +export default class CryptographyPanel extends React.Component { + constructor(props: IProps) { + super(props); + } + + public render(): JSX.Element { + const client = MatrixClientPeg.get(); + const deviceId = client.deviceId; + let identityKey = client.getDeviceEd25519Key(); + if (!identityKey) { + identityKey = _t(""); + } else { + identityKey = FormattingUtils.formatCryptoKey(identityKey); + } + + let importExportButtons = null; + if (client.isCryptoEnabled()) { + importExportButtons = ( +
+ + { _t("Export E2E room keys") } + + + { _t("Import E2E room keys") } + +
+ ); + } + + let noSendUnverifiedSetting; + if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) { + noSendUnverifiedSetting = ; + } + + return ( +
+ { _t("Cryptography") } + + + + + + + + + +
{ _t("Session ID:") }{ deviceId }
{ _t("Session key:") }{ identityKey }
+ { importExportButtons } + { noSendUnverifiedSetting } +
+ ); + } + + private onExportE2eKeysClicked = (): void => { + Modal.createTrackedDialogAsync('Export E2E Keys', '', + import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), + { matrixClient: MatrixClientPeg.get() }, + ); + }; + + private onImportE2eKeysClicked = (): void => { + Modal.createTrackedDialogAsync('Import E2E Keys', '', + import('../../../async-components/views/dialogs/security/ImportE2eKeysDialog'), + { matrixClient: MatrixClientPeg.get() }, + ); + }; + + private updateBlacklistDevicesFlag = (checked): void => { + MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked); + }; +} diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx index 9a1321619e0..c2dc924694e 100644 --- a/src/components/views/settings/DevicesPanel.tsx +++ b/src/components/views/settings/DevicesPanel.tsx @@ -28,6 +28,8 @@ import DevicesPanelEntry from "./DevicesPanelEntry"; import Spinner from "../elements/Spinner"; import AccessibleButton from "../elements/AccessibleButton"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { className?: string; } @@ -72,7 +74,7 @@ export default class DevicesPanel extends React.Component { // 404 probably means the HS doesn't yet support the API. errtxt = _t("Your homeserver does not support session management."); } else { - console.error("Error loading sessions:", error); + logger.error("Error loading sessions:", error); errtxt = _t("Unable to load session list"); } this.setState({ deviceLoadError: errtxt }); @@ -159,7 +161,7 @@ export default class DevicesPanel extends React.Component { }, }); }).catch((e) => { - console.error("Error deleting sessions", e); + logger.error("Error deleting sessions", e); if (this.unmounted) { return; } }).finally(() => { this.setState({ @@ -218,17 +220,21 @@ export default class DevicesPanel extends React.Component { const classes = classNames(this.props.className, "mx_DevicesPanel"); return ( -
-
-
{ _t("ID") }
-
{ _t("Public Name") }
-
{ _t("Last seen") }
-
- { this.state.selectedDevices.length > 0 ? deleteButton : null } -
-
- { devices.map(this.renderDevice) } -
+ + + + + + + + + + + { devices.map(this.renderDevice) } + +
{ _t("ID") }{ _t("Public Name") }{ _t("Last seen") } + { this.state.selectedDevices.length > 0 ? deleteButton : null } +
); } } diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index d033bc41a9d..6d73e1fe864 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -24,6 +24,8 @@ import StyledCheckbox from '../elements/StyledCheckbox'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import EditableTextContainer from "../elements/EditableTextContainer"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { device?: IMyDevice; onDeviceToggled?: (device: IMyDevice) => void; @@ -41,7 +43,7 @@ export default class DevicesPanelEntry extends React.Component { return MatrixClientPeg.get().setDeviceDetails(device.device_id, { display_name: value, }).catch((e) => { - console.error("Error setting session display name", e); + logger.error("Error setting session display name", e); throw new Error(_t("Failed to set display name")); }); }; @@ -66,23 +68,23 @@ export default class DevicesPanelEntry extends React.Component { } return ( -
-
+ + { device.device_id } -
-
+ + -
-
+ + { lastSeen } -
-
+ + -
-
+ + ); } } diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx new file mode 100644 index 00000000000..aabfc1c9a48 --- /dev/null +++ b/src/components/views/settings/FontScalingPanel.tsx @@ -0,0 +1,163 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import EventTilePreview from '../elements/EventTilePreview'; +import Field from '../elements/Field'; +import React, { ChangeEvent } from 'react'; +import SettingsFlag from '../elements/SettingsFlag'; +import SettingsStore from "../../../settings/SettingsStore"; +import Slider from "../elements/Slider"; +import { FontWatcher } from "../../../settings/watchers/FontWatcher"; +import { IValidationResult, IFieldState } from '../elements/Validation'; +import { Layout } from "../../../settings/Layout"; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import { SettingLevel } from "../../../settings/SettingLevel"; +import { _t } from "../../../languageHandler"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +interface IProps { +} + +interface IState { + // String displaying the current selected fontSize. + // Needs to be string for things like '17.' without + // trailing 0s. + fontSize: string; + useCustomFontSize: boolean; + layout: Layout; + // User profile data for the message preview + userId?: string; + displayName: string; + avatarUrl: string; +} + +@replaceableComponent("views.settings.tabs.user.FontScalingPanel") +export default class FontScalingPanel extends React.Component { + private readonly MESSAGE_PREVIEW_TEXT = _t("Hey you. You're the best!"); + + private unmounted = false; + + constructor(props: IProps) { + super(props); + + this.state = { + fontSize: (SettingsStore.getValue("baseFontSize", null) + FontWatcher.SIZE_DIFF).toString(), + useCustomFontSize: SettingsStore.getValue("useCustomFontSize"), + layout: SettingsStore.getValue("layout"), + userId: null, + displayName: null, + avatarUrl: null, + }; + } + + async componentDidMount() { + // Fetch the current user profile for the message preview + const client = MatrixClientPeg.get(); + const userId = client.getUserId(); + const profileInfo = await client.getProfileInfo(userId); + if (this.unmounted) return; + + this.setState({ + userId, + displayName: profileInfo.displayname, + avatarUrl: profileInfo.avatar_url, + }); + } + + componentWillUnmount() { + this.unmounted = true; + } + + private onFontSizeChanged = (size: number): void => { + this.setState({ fontSize: size.toString() }); + SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, size - FontWatcher.SIZE_DIFF); + }; + + private onValidateFontSize = async ({ value }: Pick): Promise => { + const parsedSize = parseFloat(value); + const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF; + const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF; + + if (isNaN(parsedSize)) { + return { valid: false, feedback: _t("Size must be a number") }; + } + + if (!(min <= parsedSize && parsedSize <= max)) { + return { + valid: false, + feedback: _t('Custom font size can only be between %(min)s pt and %(max)s pt', { min, max }), + }; + } + + SettingsStore.setValue( + "baseFontSize", + null, + SettingLevel.DEVICE, + parseInt(value, 10) - FontWatcher.SIZE_DIFF, + ); + + return { valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', { min, max }) }; + }; + + public render() { + return
+ + { _t("Font size") } + +
+
Aa
+ ""} + disabled={this.state.useCustomFontSize} + /> +
Aa
+
+ + this.setState({ useCustomFontSize: checked })} + useCheckbox={true} + /> + + ) => + this.setState({ fontSize: value.target.value }) + } + disabled={!this.state.useCustomFontSize} + className="mx_FontScalingPanel_customFontSizeField" + /> +
; + } +} diff --git a/src/components/views/settings/Notifications.tsx b/src/components/views/settings/Notifications.tsx index 2f3f7abe4b2..133e9dda77c 100644 --- a/src/components/views/settings/Notifications.tsx +++ b/src/components/views/settings/Notifications.tsx @@ -39,6 +39,8 @@ import TagComposer from "../elements/TagComposer"; import { objectClone } from "../../../utils/objects"; import { arrayDiff } from "../../../utils/arrays"; +import { logger } from "matrix-js-sdk/src/logger"; + // TODO: this "view" component still has far too much application logic in it, // which should be factored out to other files. @@ -139,7 +141,7 @@ export default class Notifications extends React.PureComponent { phase: Phase.Ready, }); } catch (e) { - console.error("Error setting up notifications for settings: ", e); + logger.error("Error setting up notifications for settings: ", e); this.setState({ phase: Phase.Error }); } } @@ -264,7 +266,7 @@ export default class Notifications extends React.PureComponent { await this.refreshFromServer(); } catch (e) { this.setState({ phase: Phase.Error }); - console.error("Error updating master push rule:", e); + logger.error("Error updating master push rule:", e); this.showSaveError(); } }; @@ -298,7 +300,7 @@ export default class Notifications extends React.PureComponent { await this.refreshFromServer(); } catch (e) { this.setState({ phase: Phase.Error }); - console.error("Error updating email pusher:", e); + logger.error("Error updating email pusher:", e); this.showSaveError(); } }; @@ -367,7 +369,7 @@ export default class Notifications extends React.PureComponent { await this.refreshFromServer(); } catch (e) { this.setState({ phase: Phase.Error }); - console.error("Error updating push rule:", e); + logger.error("Error updating push rule:", e); this.showSaveError(); } }; @@ -427,7 +429,7 @@ export default class Notifications extends React.PureComponent { await this.refreshFromServer(); } catch (e) { this.setState({ phase: Phase.Error }); - console.error("Error updating keyword push rules:", e); + logger.error("Error updating keyword push rules:", e); this.showSaveError(); } } diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx index 44c5c444125..d69cf250a6b 100644 --- a/src/components/views/settings/SecureBackupPanel.tsx +++ b/src/components/views/settings/SecureBackupPanel.tsx @@ -210,7 +210,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { try { await accessSecretStorage(async () => { }, /* forceReset = */ true); } catch (e) { - console.error("Error resetting secret storage", e); + logger.error("Error resetting secret storage", e); if (this.unmounted) return; this.setState({ error: e }); } diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx index 1f488f1e677..3eb6041a8f1 100644 --- a/src/components/views/settings/SetIdServer.tsx +++ b/src/components/views/settings/SetIdServer.tsx @@ -32,6 +32,8 @@ import AccessibleButton from '../elements/AccessibleButton'; import Field from '../elements/Field'; import QuestionDialog from "../dialogs/QuestionDialog"; +import { logger } from "matrix-js-sdk/src/logger"; + // We'll wait up to this long when checking for 3PID bindings on the IS. const REACHABILITY_TIMEOUT = 10000; // ms @@ -206,7 +208,7 @@ export default class SetIdServer extends React.Component { this.saveIdServer(fullUrl); } } catch (e) { - console.error(e); + logger.error(e); errStr = _t("Terms of service not accepted or the identity server is invalid."); } } @@ -268,11 +270,11 @@ export default class SetIdServer extends React.Component { ); } catch (e) { currentServerReachable = false; - console.warn( + logger.warn( `Unable to reach identity server at ${currentClientIdServer} to check ` + `for 3PIDs during IS change flow`, ); - console.warn(e); + logger.warn(e); } const boundThreepids = threepids.filter(tp => tp.bound); let message; diff --git a/src/components/views/settings/SetIntegrationManager.tsx b/src/components/views/settings/SetIntegrationManager.tsx index e083efae0ef..dc33a9e20e4 100644 --- a/src/components/views/settings/SetIntegrationManager.tsx +++ b/src/components/views/settings/SetIntegrationManager.tsx @@ -18,10 +18,12 @@ import React from 'react'; import { _t } from "../../../languageHandler"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance"; -import * as sdk from '../../../index'; import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import ToggleSwitch from "../elements/ToggleSwitch"; + +import { logger } from "matrix-js-sdk/src/logger"; interface IProps { @@ -48,8 +50,8 @@ export default class SetIntegrationManager extends React.Component { const current = this.state.provisioningEnabled; SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => { - console.error("Error changing integration manager provisioning"); - console.error(err); + logger.error("Error changing integration manager provisioning"); + logger.error(err); this.setState({ provisioningEnabled: current }); }); @@ -57,8 +59,6 @@ export default class SetIntegrationManager extends React.Component { _t("Manage integrations") } { managerName } - +
{ bodyText } diff --git a/src/components/views/settings/account/EmailAddresses.tsx b/src/components/views/settings/account/EmailAddresses.tsx index df440ebde00..039d20f3e88 100644 --- a/src/components/views/settings/account/EmailAddresses.tsx +++ b/src/components/views/settings/account/EmailAddresses.tsx @@ -27,6 +27,8 @@ import { replaceableComponent } from "../../../../utils/replaceableComponent"; import ErrorDialog from "../../dialogs/ErrorDialog"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; +import { logger } from "matrix-js-sdk/src/logger"; + /* TODO: Improve the UX for everything in here. It's very much placeholder, but it gets the job done. The old way of handling @@ -78,7 +80,7 @@ export class ExistingEmailAddress extends React.Component { return this.props.onRemoved(this.props.email); }).catch((err) => { - console.error("Unable to remove contact information: " + err); + logger.error("Unable to remove contact information: " + err); Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, { title: _t("Unable to remove contact information"), description: ((err && err.message) ? err.message : _t("Operation failed")), @@ -181,7 +183,7 @@ export default class EmailAddresses extends React.Component { task.addEmailAddress(email).then(() => { this.setState({ continueDisabled: false }); }).catch((err) => { - console.error("Unable to add email address " + email + " " + err); + logger.error("Unable to add email address " + email + " " + err); this.setState({ verifying: false, continueDisabled: false, addTask: null }); Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, { title: _t("Unable to add email address"), @@ -221,7 +223,7 @@ export default class EmailAddresses extends React.Component { "and then click continue again."), }); } else { - console.error("Unable to verify email address: ", err); + logger.error("Unable to verify email address: ", err); Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, { title: _t("Unable to verify email address."), description: ((err && err.message) ? err.message : _t("Operation failed")), diff --git a/src/components/views/settings/account/PhoneNumbers.tsx b/src/components/views/settings/account/PhoneNumbers.tsx index 9105dfc3121..4a380980d83 100644 --- a/src/components/views/settings/account/PhoneNumbers.tsx +++ b/src/components/views/settings/account/PhoneNumbers.tsx @@ -28,6 +28,8 @@ import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; import ErrorDialog from "../../dialogs/ErrorDialog"; import { PhoneNumberCountryDefinition } from "../../../../phonenumber"; +import { logger } from "matrix-js-sdk/src/logger"; + /* TODO: Improve the UX for everything in here. This is a copy/paste of EmailAddresses, mostly. @@ -74,7 +76,7 @@ export class ExistingPhoneNumber extends React.Component { return this.props.onRemoved(this.props.msisdn); }).catch((err) => { - console.error("Unable to remove contact information: " + err); + logger.error("Unable to remove contact information: " + err); Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, { title: _t("Unable to remove contact information"), description: ((err && err.message) ? err.message : _t("Operation failed")), @@ -183,7 +185,7 @@ export default class PhoneNumbers extends React.Component { task.addMsisdn(phoneCountry, phoneNumber).then((response) => { this.setState({ continueDisabled: false, verifyMsisdn: response.msisdn }); }).catch((err) => { - console.error("Unable to add phone number " + phoneNumber + " " + err); + logger.error("Unable to add phone number " + phoneNumber + " " + err); this.setState({ verifying: false, continueDisabled: false, addTask: null }); Modal.createTrackedDialog('Add Phone Number Error', '', ErrorDialog, { title: _t("Error"), @@ -221,7 +223,7 @@ export default class PhoneNumbers extends React.Component { }).catch((err) => { this.setState({ continueDisabled: false }); if (err.errcode !== 'M_THREEPID_AUTH_FAILED') { - console.error("Unable to verify phone number: " + err); + logger.error("Unable to verify phone number: " + err); Modal.createTrackedDialog('Unable to verify phone number', '', ErrorDialog, { title: _t("Unable to verify phone number."), description: ((err && err.message) ? err.message : _t("Operation failed")), diff --git a/src/components/views/settings/discovery/EmailAddresses.tsx b/src/components/views/settings/discovery/EmailAddresses.tsx index e1fe1ad1fd2..48ccc13d726 100644 --- a/src/components/views/settings/discovery/EmailAddresses.tsx +++ b/src/components/views/settings/discovery/EmailAddresses.tsx @@ -26,6 +26,8 @@ import { IThreepid } from "matrix-js-sdk/src/@types/threepids"; import ErrorDialog from "../../dialogs/ErrorDialog"; import AccessibleButton from "../../elements/AccessibleButton"; +import { logger } from "matrix-js-sdk/src/logger"; + /* TODO: Improve the UX for everything in here. It's very much placeholder, but it gets the job done. The old way of handling @@ -75,7 +77,7 @@ export class EmailAddress extends React.Component { - if (!await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { + if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) { return this.changeBindingTangledAddBind({ bind, label, errorTitle }); } @@ -98,7 +100,7 @@ export class EmailAddress extends React.Component { - if (!await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) { + if (!(await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind())) { return this.changeBindingTangledAddBind({ bind, label, errorTitle }); } @@ -98,7 +100,7 @@ export class PhoneNumber extends React.Component { _t("Sounds") }
- { _t("Notification sound") }: { this.state.currentSound }
+
+ { _t("Notification sound") }: { this.state.currentSound } +
{ _t("Reset") }

{ _t("Set a new custom sound") }

-
- -
+
+
+ +
- { currentUploadedFile } + { currentUploadedFile } +
{ _t("Browse") } diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index f1179d38e55..637215682ea 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -28,6 +28,8 @@ import { compare } from "../../../../../utils/strings"; import ErrorDialog from '../../../dialogs/ErrorDialog'; import PowerSelector from "../../../elements/PowerSelector"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IEventShowOpts { isState?: boolean; hideForSpace?: boolean; @@ -72,7 +74,7 @@ interface IBannedUserProps { export class BannedUser extends React.Component { private onUnbanClick = (e) => { MatrixClientPeg.get().unban(this.props.member.roomId, this.props.member.userId).catch((err) => { - console.error("Failed to unban: " + err); + logger.error("Failed to unban: " + err); Modal.createTrackedDialog('Failed to unban', '', ErrorDialog, { title: _t('Error'), description: _t('Failed to unban'), @@ -167,7 +169,7 @@ export default class RolesRoomSettingsTab extends React.Component { } client.sendStateEvent(this.props.roomId, EventType.RoomPowerLevels, plContent).catch(e => { - console.error(e); + logger.error(e); Modal.createTrackedDialog('Power level requirement change failed', '', ErrorDialog, { title: _t('Error changing power level requirement'), @@ -193,7 +195,7 @@ export default class RolesRoomSettingsTab extends React.Component { plContent['users'][powerLevelKey] = value; client.sendStateEvent(this.props.roomId, EventType.RoomPowerLevels, plContent).catch(e => { - console.error(e); + logger.error(e); Modal.createTrackedDialog('Power level change failed', '', ErrorDialog, { title: _t('Error changing power level'), diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index d1c5bc8448b..0242ace0a61 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -36,6 +36,8 @@ import CreateRoomDialog from '../../../dialogs/CreateRoomDialog'; import JoinRuleSettings from "../../JoinRuleSettings"; import ErrorDialog from "../../../dialogs/ErrorDialog"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { roomId: string; closeSettingsFn: () => void; @@ -180,7 +182,7 @@ export default class SecurityRoomSettingsTab extends React.Component { - console.error(e); + logger.error(e); this.setState({ encrypted: beforeEncrypted }); }); }, @@ -198,7 +200,7 @@ export default class SecurityRoomSettingsTab extends React.Component { - console.error(e); + logger.error(e); this.setState({ guestAccess: beforeGuestAccess }); }); }; @@ -225,7 +227,7 @@ export default class SecurityRoomSettingsTab extends React.Component { - console.error(e); + logger.error(e); this.setState({ history: beforeHistory }); }); }; diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index bc54a8155c1..3abb90d2a9b 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -22,17 +22,13 @@ import { MatrixClientPeg } from '../../../../../MatrixClientPeg'; import SettingsStore from "../../../../../settings/SettingsStore"; import { enumerateThemes } from "../../../../../theme"; import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher"; -import Slider from "../../../elements/Slider"; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; -import { FontWatcher } from "../../../../../settings/watchers/FontWatcher"; import { RecheckThemePayload } from '../../../../../dispatcher/payloads/RecheckThemePayload'; import { Action } from '../../../../../dispatcher/actions'; -import { IValidationResult, IFieldState } from '../../../elements/Validation'; import StyledCheckbox from '../../../elements/StyledCheckbox'; import SettingsFlag from '../../../elements/SettingsFlag'; import Field from '../../../elements/Field'; -import EventTilePreview from '../../../elements/EventTilePreview'; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; @@ -41,6 +37,9 @@ import { replaceableComponent } from "../../../../../utils/replaceableComponent" import { compare } from "../../../../../utils/strings"; import LayoutSwitcher from "../../LayoutSwitcher"; +import { logger } from "matrix-js-sdk/src/logger"; +import FontScalingPanel from '../../FontScalingPanel'; + interface IProps { } @@ -55,13 +54,8 @@ export interface CustomThemeMessage { } interface IState extends IThemeState { - // String displaying the current selected fontSize. - // Needs to be string for things like '17.' without - // trailing 0s. - fontSize: string; customThemeUrl: string; customThemeMessage: CustomThemeMessage; - useCustomFontSize: boolean; useSystemFont: boolean; systemFont: string; showAdvanced: boolean; @@ -83,11 +77,9 @@ export default class AppearanceUserSettingsTab extends React.Component({ action: Action.RecheckTheme }); }; - private onFontSizeChanged = (size: number): void => { - this.setState({ fontSize: size.toString() }); - SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, size - FontWatcher.SIZE_DIFF); - }; - - private onValidateFontSize = async ({ value }: Pick): Promise => { - const parsedSize = parseFloat(value); - const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF; - const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF; - - if (isNaN(parsedSize)) { - return { valid: false, feedback: _t("Size must be a number") }; - } - - if (!(min <= parsedSize && parsedSize <= max)) { - return { - valid: false, - feedback: _t('Custom font size can only be between %(min)s pt and %(max)s pt', { min, max }), - }; - } - - SettingsStore.setValue( - "baseFontSize", - null, - SettingLevel.DEVICE, - parseInt(value, 10) - FontWatcher.SIZE_DIFF, - ); - - return { valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', { min, max }) }; - }; - private onAddCustomTheme = async (): Promise => { let currentThemes: string[] = SettingsStore.getValue("custom_themes"); if (!currentThemes) currentThemes = []; @@ -225,7 +186,7 @@ export default class AppearanceUserSettingsTab extends React.Component - - { _t("Font size") } - -
-
Aa
- ""} - disabled={this.state.useCustomFontSize} - /> -
Aa
-
- - this.setState({ useCustomFontSize: checked })} - useCheckbox={true} - /> - - this.setState({ fontSize: value.target.value })} - disabled={!this.state.useCustomFontSize} - className="mx_SettingsTab_customFontSizeField" - /> -
; - } - private renderAdvancedSection() { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; @@ -469,7 +384,7 @@ export default class AppearanceUserSettingsTab extends React.Component { this.renderThemeSection() } { layoutSection } - { this.renderFontSection() } + { this.renderAdvancedSection() }
); diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 77b3ace22b5..b29c6ed87f4 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -50,6 +50,8 @@ import InlineTermsAgreement from "../../../terms/InlineTermsAgreement"; import SetIdServer from "../../SetIdServer"; import SetIntegrationManager from "../../SetIntegrationManager"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps { closeSettingsFn: () => void; } @@ -166,11 +168,11 @@ export default class GeneralUserSettingsTab extends React.Component a.medium === 'email'), @@ -216,11 +218,11 @@ export default class GeneralUserSettingsTab extends React.Component componentDidMount(): void { PlatformPeg.get().getAppVersion().then((ver) => this.setState({ appVersion: ver })).catch((e) => { - console.error("Error getting vector version: ", e); + logger.error("Error getting vector version: ", e); }); PlatformPeg.get().canSelfUpdate().then((v) => this.setState({ canUpdate: v })).catch((e) => { - console.error("Error getting self updatability: ", e); + logger.error("Error getting self updatability: ", e); }); } diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx index 0653198aa0e..a05e2b6ee54 100644 --- a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.tsx @@ -28,6 +28,8 @@ import QuestionDialog from "../../../dialogs/QuestionDialog"; import AccessibleButton from "../../../elements/AccessibleButton"; import Field from "../../../elements/Field"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState { busy: boolean; newPersonalRule: string; @@ -69,7 +71,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> await list.banEntity(kind, this.state.newPersonalRule, _t("Ignored/Blocked")); this.setState({ newPersonalRule: "" }); // this will also cause the new rule to be rendered } catch (e) { - console.error(e); + logger.error(e); Modal.createTrackedDialog('Failed to add Mjolnir rule', '', ErrorDialog, { title: _t('Error adding ignored user/server'), @@ -90,7 +92,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> await Mjolnir.sharedInstance().subscribeToList(room.roomId); this.setState({ newList: "" }); // this will also cause the new rule to be rendered } catch (e) { - console.error(e); + logger.error(e); Modal.createTrackedDialog('Failed to subscribe to Mjolnir list', '', ErrorDialog, { title: _t('Error subscribing to list'), @@ -107,7 +109,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> const list = Mjolnir.sharedInstance().getPersonalList(); await list.unbanEntity(rule.kind, rule.entity); } catch (e) { - console.error(e); + logger.error(e); Modal.createTrackedDialog('Failed to remove Mjolnir rule', '', ErrorDialog, { title: _t('Error removing ignored user/server'), @@ -124,7 +126,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState> await Mjolnir.sharedInstance().unsubscribeFromList(list.roomId); await MatrixClientPeg.get().leave(list.roomId); } catch (e) { - console.error(e); + logger.error(e); Modal.createTrackedDialog('Failed to unsubscribe from Mjolnir list', '', ErrorDialog, { title: _t('Error unsubscribing from list'), diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx index 6aa45d05b63..b9753d9c861 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.tsx @@ -21,10 +21,8 @@ import { sleep } from "matrix-js-sdk/src/utils"; import { _t } from "../../../../../languageHandler"; import SdkConfig from "../../../../../SdkConfig"; import { MatrixClientPeg } from "../../../../../MatrixClientPeg"; -import * as FormattingUtils from "../../../../../utils/FormattingUtils"; import AccessibleButton from "../../../elements/AccessibleButton"; import Analytics from "../../../../../Analytics"; -import Modal from "../../../../../Modal"; import dis from "../../../../../dispatcher/dispatcher"; import { privateShouldBeEncrypted } from "../../../../../createRoom"; import { SettingLevel } from "../../../../../settings/SettingLevel"; @@ -37,12 +35,15 @@ import { replaceableComponent } from "../../../../../utils/replaceableComponent" import { PosthogAnalytics } from "../../../../../PosthogAnalytics"; import { ActionPayload } from "../../../../../dispatcher/payloads"; import { Room } from "matrix-js-sdk/src/models/room"; +import CryptographyPanel from "../../CryptographyPanel"; import DevicesPanel from "../../DevicesPanel"; import SettingsFlag from "../../../elements/SettingsFlag"; import CrossSigningPanel from "../../CrossSigningPanel"; import EventIndexPanel from "../../EventIndexPanel"; import InlineSpinner from "../../../elements/InlineSpinner"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IIgnoredUserProps { userId: string; onUnignored: (userId: string) => void; @@ -112,30 +113,12 @@ export default class SecurityUserSettingsTab extends React.Component { - MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked); - }; - private updateAnalytics = (checked: boolean): void => { checked ? Analytics.enable() : Analytics.disable(); CountlyAnalytics.instance.enable(/* anonymous = */ !checked); PosthogAnalytics.instance.updateAnonymityFromSettings(MatrixClientPeg.get().getUserId()); }; - private onExportE2eKeysClicked = (): void => { - Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), - { matrixClient: MatrixClientPeg.get() }, - ); - }; - - private onImportE2eKeysClicked = (): void => { - Modal.createTrackedDialogAsync('Import E2E Keys', '', - import('../../../../../async-components/views/dialogs/security/ImportE2eKeysDialog'), - { matrixClient: MatrixClientPeg.get() }, - ); - }; - private onGoToUserProfileClick = (): void => { dis.dispatch({ action: 'view_user_info', @@ -193,7 +176,7 @@ export default class SecurityUserSettingsTab extends React.Component"); - } else { - identityKey = FormattingUtils.formatCryptoKey(identityKey); - } - - let importExportButtons = null; - if (client.isCryptoEnabled()) { - importExportButtons = ( -
- - { _t("Export E2E room keys") } - - - { _t("Import E2E room keys") } - -
- ); - } - - let noSendUnverifiedSetting; - if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) { - noSendUnverifiedSetting = ; - } - - return ( -
- { _t("Cryptography") } -
    -
  • - - { deviceId } -
  • -
  • - - { identityKey } -
  • -
- { importExportButtons } - { noSendUnverifiedSetting } -
- ); - } - private renderIgnoredUsers(): JSX.Element { const { waitingUnignored, ignoredUserIds } = this.state; @@ -418,7 +349,7 @@ export default class SecurityUserSettingsTab extends React.Component
{ privacySection } { advancedSection } diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 22cbb4db656..5ec44e970b4 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -39,6 +39,8 @@ import { Action } from "../../../dispatcher/actions"; import { UserTab } from "../dialogs/UserSettingsDialog"; import { Key } from "../../../Keyboard"; +import { logger } from "matrix-js-sdk/src/logger"; + export const createSpace = async ( name: string, isPublic: boolean, @@ -255,7 +257,7 @@ const SpaceCreateMenu = ({ onFinished }) => { onFinished(); } catch (e) { - console.error(e); + logger.error(e); } }; diff --git a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx index 595bdb24487..865df03f663 100644 --- a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx +++ b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx @@ -27,6 +27,8 @@ import { IDialogProps } from "../dialogs/IDialogProps"; import { getTopic } from "../elements/RoomTopic"; import { leaveSpace } from "../../../utils/space"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IProps extends IDialogProps { matrixClient: MatrixClient; space: Room; @@ -83,7 +85,7 @@ const SpaceSettingsGeneralTab = ({ matrixClient: cli, space, onFinished }: IProp setBusy(false); const failures = results.filter(r => r.status === "rejected"); if (failures.length > 0) { - console.error("Failed to save space settings: ", failures); + logger.error("Failed to save space settings: ", failures); setError(_t("Failed to save space settings.")); } }; diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index df6c4c81498..ba2af3b8570 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -125,9 +125,11 @@ export const SpaceButton: React.FC = ({ > { children }
- { avatar } +
+ { avatar } + { notifBadge } +
{ !isNarrow && { label } } - { notifBadge } { ContextMenuComponent && text = _t("Cancellingโ€ฆ"); } confirm = ; - } else if (this.props.inDialog) { - // FIXME: stop using DialogButtons here once this component is only used in the right panel verification - confirm = ; } else { - confirm = + confirm =
{ _t("They don't match") } { _t("They match") } - ; +
; } return
diff --git a/src/components/views/voip/AudioFeed.tsx b/src/components/views/voip/AudioFeed.tsx index d6d1261343f..751ae6b96ef 100644 --- a/src/components/views/voip/AudioFeed.tsx +++ b/src/components/views/voip/AudioFeed.tsx @@ -66,7 +66,7 @@ export default class AudioFeed extends React.Component { // back to the default after the call is over - Dave element.setSinkId(audioOutput); } catch (e) { - console.error("Couldn't set requested audio output device: using default", e); + logger.error("Couldn't set requested audio output device: using default", e); logger.warn("Couldn't set requested audio output device: using default", e); } } diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index c5426e0dcdc..605df72f0dd 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -20,11 +20,11 @@ import { IRoomState } from "../components/structures/RoomView"; import { Layout } from "../settings/Layout"; export enum TimelineRenderingType { - Room, - Thread, - ThreadsList, - File, - Notification, + Room = "Room", + Thread = "Thread", + ThreadsList = "ThreadsList", + File = "File", + Notification = "Notification", } const RoomContext = createContext({ diff --git a/src/createRoom.ts b/src/createRoom.ts index daf8594e23b..6394cb6849d 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -46,6 +46,8 @@ import { Action } from "./dispatcher/actions"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import Spinner from "./components/views/elements/Spinner"; +import { logger } from "matrix-js-sdk/src/logger"; + // we define a number of interfaces which take their names from the js-sdk /* eslint-disable camelcase */ @@ -225,7 +227,7 @@ export default async function createRoom(opts: IOpts): Promise { // but the server denies them that permission (via room_list_publication_rules). // The check below responds by retrying without publishing the room. if (err.httpStatus === 403 && err.errcode === "M_UNKNOWN" && err.data.error === "Not allowed to publish room") { - console.warn("Failed to publish room, try again without publishing it"); + logger.warn("Failed to publish room, try again without publishing it"); createOpts.visibility = Visibility.Private; return client.createRoom(createOpts); } else { @@ -278,7 +280,7 @@ export default async function createRoom(opts: IOpts): Promise { action: Action.JoinRoomError, roomId, }); - console.error("Failed to create room " + roomId + " " + err); + logger.error("Failed to create room " + roomId + " " + err); let description = _t("Server may be unavailable, overloaded, or you hit a bug."); if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") { // Technically not possible with the UI as of April 2019 because there's no @@ -356,7 +358,7 @@ export async function canEncryptToAllUsers(client: MatrixClient, userIds: string Object.keys(userDevices).length > 0, ); } catch (e) { - console.error("Error determining if it's possible to encrypt to all users: ", e); + logger.error("Error determining if it's possible to encrypt to all users: ", e); return false; // assume not } } diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 2b225077d89..df98fe2349d 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -166,7 +166,7 @@ "%(roomName)s does not exist.": "%(roomName)s neexistuje.", "%(roomName)s is not accessible at this time.": "Mรญstnost %(roomName)s nenรญ v tuto chvรญli dostupnรก.", "Save": "Uloลพit", - "Send Reset Email": "Poslat resetovacรญ e-mail", + "Send Reset Email": "Odeslat obnovovacรญ e-mail", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s poslal(a) obrรกzek.", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s pozval(a) uลพivatele %(targetDisplayName)s ke vstupu do mรญstnosti.", "Server error": "Chyba serveru", @@ -1429,8 +1429,8 @@ "Upgrade private room": "Upgradovat soukromou mรญstnost", "Upgrade public room": "Upgradovat veล™ejnou mรญstnost", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgradovรกnรญ mรญstnosti je pokroฤilรก operace a je doporuฤeno jรญ provรฉst pokud je mรญstnost nestabilnรญ kvลฏli chybรกm, chybฤ›jรญcรญm funkcรญm nebo zranitelnostem.", - "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Toto bฤ›ลพnฤ› ovlivลˆuje pouze zpracovรกvรกnรญ mรญstnosti na serveru. Pokud mรกte problรฉm s %(brand)sem, nahlaste nรกm ho prosรญm.", - "You'll upgrade this room from to .": "Upgradujeme tuto mรญstnost z na .", + "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Toto obvykle ovlivลˆuje pouze zpracovรกvรกnรญ mรญstnosti na serveru. Pokud mรกte problรฉm s %(brand)sem, nahlaste nรกm ho prosรญm.", + "You'll upgrade this room from to .": "Mรญstnost bude upgradovรกna z verze na verzi .", "Upgrade": "Upgradovat", "Warning: You should only set up key backup from a trusted computer.": "Varovรกnรญ: Nastavujte zรกlohu jen z dลฏvฤ›ryhodnรฝch poฤรญtaฤลฏ.", "Notification settings": "Nastavenรญ oznรกmenรญ", @@ -2760,7 +2760,7 @@ "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "Pokud vลกe resetujete, zaฤnete bez dลฏvฤ›ryhodnรฝch relacรญ, bez dลฏvฤ›ryhodnรฝch uลพivatelลฏ a moลพnรก nebudete moci zobrazit minulรฉ zprรกvy.", "Only do this if you have no other device to complete verification with.": "Udฤ›lejte to, pouze pokud nemรกte ลพรกdnรฉ jinรฉ zaล™รญzenรญ, se kterรฝm byste mohli dokonฤit ovฤ›ล™enรญ.", "Reset everything": "Resetovat vลกe", - "Forgotten or lost all recovery methods? Reset all": "Zapomnฤ›li nebo ztratili jste vลกechny metody obnovy? Obnovit vลกe", + "Forgotten or lost all recovery methods? Reset all": "Zapomnฤ›li nebo ztratili jste vลกechny metody obnovy? Resetovat vลกe", "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few moments whilst the index is recreated": "Pokud tak uฤinรญte, nezapomeลˆte, ลพe ลพรกdnรก z vaลกich zprรกv nebude smazรกna, ale vyhledรกvรกnรญ mลฏลพe bรฝt na nฤ›kolik okamลพikลฏ zpomaleno bฤ›hem opฤ›tovnรฉho vytvoล™enรญ indexu", "View message": "Zobrazit zprรกvu", "Zoom in": "Pล™iblรญลพit", @@ -2951,7 +2951,7 @@ "Not a valid identity server (status code %(code)s)": "Toto nenรญ platnรฝ server identit (stavovรฝ kรณd %(code)s)", "Identity server URL must be HTTPS": "Adresa serveru identit musรญ bรฝt na HTTPS", "Please note upgrading will make a new version of the room. All current messages will stay in this archived room.": "Upozorลˆujeme, ลพe aktualizacรญ vznikne novรก verze mรญstnosti. Vลกechny aktuรกlnรญ zprรกvy zลฏstanou v tรฉto archivovanรฉ mรญstnosti.", - "Automatically invite members from this room to the new one": "Automaticky pozve ฤleny tรฉto mรญstnosti do novรฉ mรญstnosti", + "Automatically invite members from this room to the new one": "Automaticky pozvat ฤleny tรฉto mรญstnosti do novรฉ mรญstnosti", "These are likely ones other room admins are a part of.": "Pravdฤ›podobnฤ› se jednรก o ty, kterรฝch se รบฤastnรญ i ostatnรญ sprรกvci mรญstnostรญ.", "Other spaces or rooms you might not know": "Dalลกรญ prostory nebo mรญstnosti, kterรฉ moลพnรก neznรกte", "Spaces you know that contain this room": "Prostory, kterรฉ znรกte a kterรฉ obsahujรญ tuto mรญstnost", @@ -3204,5 +3204,53 @@ "Are you sure you want to exit during this export?": "Opravdu chcete skonฤit bฤ›hem tohoto exportu?", "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s poslal(a) nรกlepku.", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s zmฤ›nil(a) avatar mรญstnosti.", - "%(date)s at %(time)s": "%(date)s v %(time)s" + "%(date)s at %(time)s": "%(date)s v %(time)s", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "Resetovรกnรญ ovฤ›ล™ovacรญch klรญฤลฏ nelze vrรกtit zpฤ›t. Po jejich resetovรกnรญ nebudete mรญt pล™รญstup ke starรฝm zaลกifrovanรฝm zprรกvรกm a vลกem pล™รกtelลฏm, kteล™รญ vรกs dล™รญve ovฤ›ล™ili, se zobrazรญ bezpeฤnostnรญ varovรกnรญ, dokud se u nich znovu neovฤ›ล™รญte.", + "Proceed with reset": "Pokraฤovat v resetovรกnรญ", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Vypadรก to, ลพe nemรกte bezpeฤnostnรญ klรญฤ ani ลพรกdnรฉ jinรฉ zaล™รญzenรญ, kterรฉ byste mohli ovฤ›ล™it. Toto zaล™รญzenรญ nebude mรญt pล™รญstup ke starรฝm ลกifrovanรฝm zprรกvรกm. Abyste mohli na tomto zaล™รญzenรญ ovฤ›ล™it svou totoลพnost, budete muset resetovat ovฤ›ล™ovacรญ klรญฤe.", + "Really reset verification keys?": "Opravdu chcete resetovat ovฤ›ล™ovacรญ klรญฤe?", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Pokraฤujte pouze v pล™รญpadฤ›, ลพe jste si jisti, ลพe jste ztratili vลกechna ostatnรญ zaล™รญzenรญ a bezpeฤnostnรญ klรญฤ.", + "I'll verify later": "Ovฤ›ล™รญm se pozdฤ›ji", + "Verify with another login": "Ovฤ›ล™enรญ pomocรญ jinรฉho pล™ihlรกลกenรญ", + "Verify with Security Key": "Ovฤ›ล™enรญ pomocรญ bezpeฤnostnรญho klรญฤe", + "Verify with Security Key or Phrase": "Ovฤ›ล™enรญ pomocรญ bezpeฤnostnรญho klรญฤe nebo frรกze", + "Skip verification for now": "Prozatรญm pล™eskoฤit ovฤ›ล™ovรกnรญ", + "Unable to verify this login": "Nelze ovฤ›ล™it toto pล™ihlรกลกenรญ", + "To proceed, please accept the verification request on your other login.": "Pro pokraฤovรกnรญ, pล™ijmฤ›te ลพรกdost o ovฤ›ล™enรญ na svรฉm dalลกรญm zaล™รญzenรญ.", + "Waiting for you to verify on your other sessionโ€ฆ": "ฤŒekรกme, aลพ provedete ovฤ›ล™enรญ v jinรฉ relaciโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "ฤŒekรกme, aลพ provedete ovฤ›ล™enรญ v jinรฉ relaci, %(deviceName)s (%(deviceId)s)โ€ฆ", + "Creating Space...": "Vytvรกล™enรญ prostoru...", + "Fetching data...": "Naฤรญtรกnรญ dat...", + "Show:": "Zobrazit:", + "Shows all threads from current room": "Zobrazรญ vลกechna vlรกkna z aktuรกlnรญ mรญstnosti", + "All threads": "Vลกechna vlรกkna", + "Shows all threads youโ€™ve participated in": "Zobrazรญ vลกechna vlรกkna, kterรฝch jste se zรบฤastnili", + "My threads": "Moje vlรกkna", + "They won't be able to access whatever you're not an admin of.": "Nebudou mรญt pล™รญstup ke vลกemu, ฤeho nejste sprรกvcem.", + "Unban them from specific things I'm able to": "Pล™ijmout je zpฤ›t do konkrรฉtnรญch mรญst, do kterรฝch jsem schopen", + "Unban them from everything I'm able to": "Pล™ijmout zpฤ›t vลกude, kam mลฏลพu", + "Ban them from specific things I'm able to": "Vykopnout je z konkrรฉtnรญch mรญst, ze kterรฝch jsem schopen", + "Kick them from specific things I'm able to": "Vykopnout je z konkrรฉtnรญch mรญst, ze kterรฝch jsem schopen", + "Ban them from everything I'm able to": "Vykรกzat je vลกude, kam mลฏลพu", + "Ban from %(roomName)s": "Vykรกzat z %(roomName)s", + "Unban from %(roomName)s": "Pล™ijmout zpฤ›t do %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "Stรกle budou mรญt pล™รญstup ke vลกemu, ฤeho nejste sprรกvcem.", + "Kick them from everything I'm able to": "Vykopnout je ze vลกeho, co to jde", + "Kick from %(roomName)s": "Vykopnout z %(roomName)s", + "Disinvite from %(roomName)s": "Zruลกit pozvรกnku do %(roomName)s", + "Threads": "Vlรกkna", + "Create poll": "Vytvoล™it anketu", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Aktualizace prostoru...", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Aktualizace prostorลฏ... (%(progress)s z %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Odeslรกnรญ pozvรกnky...", + "Sending invites... (%(progress)s out of %(count)s)|other": "Odesรญlรกnรญ pozvรกnek... (%(progress)s z %(count)s)", + "Loading new room": "Naฤรญtรกnรญ novรฉ mรญstnosti", + "Upgrading room": "Upgrade mรญstnosti", + "Polls (under active development)": "Ankety (v aktivnรญm vรฝvoji)", + "Downloading": "Stahovรกnรญ", + "%(count)s reply|one": "%(count)s odpovฤ›ฤ", + "%(count)s reply|other": "%(count)s odpovฤ›dรญ", + "View in room": "Zobrazit v mรญstnosti", + "Enter your Security Phrase or to continue.": "Zadejte bezpeฤnostnรญ frรกzi nebo pro pokraฤovรกnรญ.", + "What projects are your team working on?": "Na jakรฝch projektech vรกลก tรฝm pracuje?" } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3e61146acb0..51fd51316a5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -952,8 +952,8 @@ "Waiting for you to verify on your other sessionโ€ฆ": "Waiting for you to verify on your other sessionโ€ฆ", "Waiting for %(displayName)s to verifyโ€ฆ": "Waiting for %(displayName)s to verifyโ€ฆ", "Cancellingโ€ฆ": "Cancellingโ€ฆ", - "They match": "They match", "They don't match": "They don't match", + "They match": "They match", "To be secure, do this in person or use a trusted way to communicate.": "To be secure, do this in person or use a trusted way to communicate.", "Dog": "Dog", "Cat": "Cat", @@ -1128,6 +1128,11 @@ "User signing private key:": "User signing private key:", "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", + "": "", + "Import E2E room keys": "Import E2E room keys", + "Cryptography": "Cryptography", + "Session ID:": "Session ID:", + "Session key:": "Session key:", "Your homeserver does not support session management.": "Your homeserver does not support session management.", "Unable to load session list": "Unable to load session list", "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Confirm deleting these sessions by using Single Sign On to prove your identity.", @@ -1153,6 +1158,10 @@ "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.", "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.", "Message search initialisation failed": "Message search initialisation failed", + "Hey you. You're the best!": "Hey you. You're the best!", + "Size must be a number": "Size must be a number", + "Custom font size can only be between %(min)s pt and %(max)s pt": "Custom font size can only be between %(min)s pt and %(max)s pt", + "Use between %(min)s pt and %(max)s pt": "Use between %(min)s pt and %(max)s pt", "Connecting to integration manager...": "Connecting to integration manager...", "Cannot connect to integration manager": "Cannot connect to integration manager", "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.", @@ -1284,10 +1293,6 @@ "Downloading update...": "Downloading update...", "New version available. Update now.": "New version available. Update now.", "Check for update": "Check for update", - "Hey you. You're the best!": "Hey you. You're the best!", - "Size must be a number": "Size must be a number", - "Custom font size can only be between %(min)s pt and %(max)s pt": "Custom font size can only be between %(min)s pt and %(max)s pt", - "Use between %(min)s pt and %(max)s pt": "Use between %(min)s pt and %(max)s pt", "Invalid theme schema.": "Invalid theme schema.", "Error downloading theme information.": "Error downloading theme information.", "Theme added!": "Theme added!", @@ -1393,11 +1398,6 @@ "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", "Unignore": "Unignore", - "": "", - "Import E2E room keys": "Import E2E room keys", - "Cryptography": "Cryptography", - "Session ID:": "Session ID:", - "Session key:": "Session key:", "You have no ignored users.": "You have no ignored users.", "Bulk options": "Bulk options", "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", @@ -1551,6 +1551,8 @@ "Send as message": "Send as message", "Edit message": "Edit message", "Mod": "Mod", + "%(count)s reply|other": "%(count)s replies", + "%(count)s reply|one": "%(count)s reply", "This event could not be displayed": "This event could not be displayed", "Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.", "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.", @@ -1929,7 +1931,6 @@ "You've successfully verified your device!": "You've successfully verified your device!", "You've successfully verified %(deviceName)s (%(deviceId)s)!": "You've successfully verified %(deviceName)s (%(deviceId)s)!", "You've successfully verified %(displayName)s!": "You've successfully verified %(displayName)s!", - "Verified": "Verified", "Got it": "Got it", "Start verification again from the notification.": "Start verification again from the notification.", "Start verification again from their profile.": "Start verification again from their profile.", @@ -1938,7 +1939,6 @@ "%(displayName)s cancelled verification.": "%(displayName)s cancelled verification.", "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", - "Compare emoji": "Compare emoji", "Call declined": "Call declined", "Call back": "Call back", "No answer": "No answer", @@ -1959,6 +1959,7 @@ "Saturday": "Saturday", "Today": "Today", "Yesterday": "Yesterday", + "Downloading": "Downloading", "Decrypting": "Decrypting", "Download": "Download", "View Source": "View Source", @@ -2655,7 +2656,7 @@ "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.", "Security Phrase": "Security Phrase", "Unable to access secret storage. Please verify that you entered the correct Security Phrase.": "Unable to access secret storage. Please verify that you entered the correct Security Phrase.", - "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", + "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", "Use your Security Key to continue.": "Use your Security Key to continue.", "Destroy cross-signing keys?": "Destroy cross-signing keys?", @@ -2699,6 +2700,7 @@ "Source URL": "Source URL", "Collapse reply thread": "Collapse reply thread", "Report": "Report", + "View in room": "View in room", "Add space": "Add space", "Manage & explore rooms": "Manage & explore rooms", "Clear status": "Clear status", @@ -2973,7 +2975,7 @@ "What are some things you want to discuss in %(spaceName)s?": "What are some things you want to discuss in %(spaceName)s?", "Let's create a room for each of them.": "Let's create a room for each of them.", "You can add more later too, including already existing ones.": "You can add more later too, including already existing ones.", - "What projects are you working on?": "What projects are you working on?", + "What projects are your team working on?": "What projects are your team working on?", "We'll create rooms for each of them. You can add more later too, including already existing ones.": "We'll create rooms for each of them. You can add more later too, including already existing ones.", "My threads": "My threads", "Shows all threads youโ€™ve participated in": "Shows all threads youโ€™ve participated in", @@ -3011,6 +3013,7 @@ "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", + "The email address doesn't appear to be valid.": "The email address doesn't appear to be valid.", "A new password must be entered.": "A new password must be entered.", "Please choose a strong password": "Please choose a strong password", "New passwords must match each other.": "New passwords must match each other.", @@ -3051,6 +3054,7 @@ "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", "That username already exists, please try another.": "That username already exists, please try another.", + "That e-mail address is already in use.": "That e-mail address is already in use.", "Continue with %(ssoButtons)s": "Continue with %(ssoButtons)s", "%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s Or %(usernamePassword)s", "Already have an account? Sign in here": "Already have an account? Sign in here", diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index a508a9e40f3..ee910cca97b 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -17,7 +17,7 @@ "Bans user with given id": "Veta al usuario con la ID dada", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "No se ha podido conectar al servidor base a travรฉs de HTTP, cuando es necesario un enlace HTTPS en la barra de direcciones de tu navegador. Ya sea usando HTTPS o activando los scripts inseguros.", "Change Password": "Cambiar la contraseรฑa", - "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ha cambiado el nivel de acceso de %(powerLevelDiffText)s.", + "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s cambiรณ el nivel de acceso de %(powerLevelDiffText)s.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s cambiรณ el nombre de la sala a %(roomName)s.", "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s cambiรณ el tema a \"%(topic)s\".", "Changes your display nickname": "Cambia tu apodo pรบblico", @@ -109,7 +109,7 @@ "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s hizo visible el historial futuro de la sala para desconocido (%(visibility)s).", "Something went wrong!": "ยกAlgo ha fallado!", "Create new room": "Crear nueva sala", - "Start chat": "Iniciar conversaciรณn", + "Start chat": "Empezar una conversaciรณn", "New Password": "Contraseรฑa nueva", "Analytics": "Analรญtica de datos", "Options": "Opciones", @@ -419,7 +419,7 @@ "Drop file here to upload": "Suelta aquรญ el archivo para enviarlo", "This event could not be displayed": "No se ha podido mostrar este evento", "Key request sent.": "Solicitud de clave enviada.", - "Disinvite this user?": "ยฟBorrar la invitaciรณn a este usuario?", + "Disinvite this user?": "ยฟDejar de invitar a este usuario?", "Kick this user?": "ยฟEchar a este usuario?", "Unban this user?": "ยฟQuitarle el veto a este usuario?", "Ban this user?": "ยฟVetar a este usuario?", @@ -985,7 +985,7 @@ "When rooms are upgraded": "Cuando las salas son actualizadas", "My Ban List": "Mi lista de baneos", "This is your list of users/servers you have blocked - don't leave the room!": "Esta es la lista de usuarios y/o servidores que has bloqueado. ยกNo te salgas de la sala!", - "Decline (%(counter)s)": "Declinar (%(counter)s)", + "Decline (%(counter)s)": "Rechazar (%(counter)s)", "Accept to continue:": ", acepta para continuar:", "ID": "ID", "Public Name": "Nombre pรบblico", @@ -1228,7 +1228,7 @@ "New login. Was this you?": "Nuevo inicio de sesiรณn. ยฟHas sido tรบ?", "%(name)s is requesting verification": "%(name)s solicita verificaciรณn", "Sign In or Create Account": "Iniciar sesiรณn o Crear una cuenta", - "Use your account or create a new one to continue.": "Usa tu cuenta existente o crea una nueva para continuar.", + "Use your account or create a new one to continue.": "Entra con tu cuenta si ya tienes una o crea una nueva para continuar.", "Create Account": "Crear cuenta", "Sign In": "Iniciar sesiรณn", "Sends a message as html, without interpreting it as markdown": "Envรญa un mensaje como HTML, sin interpretarlo en Markdown", @@ -1730,7 +1730,7 @@ "This account has been deactivated.": "Esta cuenta ha sido desactivada.", "Room name or address": "Nombre o direcciรณn de la sala", "Help us improve %(brand)s": "Ayรบdanos a mejorar %(brand)s", - "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Enviar informaciรณn anรณnima de uso nos ayuda a mejorar %(brand)s. Esto usarรก una cookie.", + "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Envรญa informaciรณn anรณnima de uso y ayรบdanos a mejorar %(brand)s. Esto usarรก una cookie.", "Ok": "Ok", "You joined the call": "Te has unido a la llamada", "%(senderName)s joined the call": "%(senderName)s se ha unido a la llamada", @@ -2290,7 +2290,7 @@ "Use Security Key": "Usar clave de seguridad", "Use Security Key or Phrase": "Usar clave de seguridad o frase", "Decide where your account is hosted": "Decide dรณnde quieres alojar tu cuenta", - "Host account on": "Alojar cuenta en", + "Host account on": "Alojar la cuenta en", "Already have an account? Sign in here": "ยฟYa tienes una cuenta? Inicia sesiรณn aquรญ", "That username already exists, please try another.": "Ese nombre de usuario ya estรก en uso, escoge otro.", "New? Create account": "ยฟPrimera vez? Crea una cuenta", @@ -2345,7 +2345,7 @@ "A call can only be transferred to a single user.": "Una llamada solo puede transferirse a un usuario.", "Invite by email": "Invitar a travรฉs de correo electrรณnico", "Send feedback": "Enviar comentarios", - "Report a bug": "Informar de un fallo", + "Report a bug": "Avรญsanos de un fallo", "There are two ways you can provide feedback and help us improve %(brand)s.": "Hay dos maneras en las que nos puedes hacer llegar tus comentarios para ayudarnos a mejorar %(brand)s.", "Comment": "Comentario", "Add comment": "Aรฑadir comentario", @@ -2356,8 +2356,8 @@ "There was an error finding this widget.": "Ha ocurrido un error al buscar este widget.", "Active Widgets": "Widgets activos", "This version of %(brand)s does not support viewing some encrypted files": "Esta versiรณn de %(brand)s no permite ver algunos archivos cifrados", - "Use the Desktop app to search encrypted messages": "Usa la aplicaciรณn de ordenador para buscar en los mensajes cifrados", - "Use the Desktop app to see all encrypted files": "Usa la aplicaciรณn de ordenador para ver todos los archivos cifrados", + "Use the Desktop app to search encrypted messages": "Usa la aplicaciรณn de escritorio para buscar en los mensajes cifrados", + "Use the Desktop app to see all encrypted files": "Usa la aplicaciรณn de escritorio para ver todos los archivos cifrados", "Video conference started by %(senderName)s": "Videoconferencia iniciada por %(senderName)s", "Video conference updated by %(senderName)s": "Videoconferencia actualizada por %(senderName)s", "You held the call Resume": "Has puesto la llamada en espera Recuperar", @@ -2449,7 +2449,7 @@ "Change the topic of your active room": "Cambiar el asunto de la sala en la que estรฉs", "See when the topic changes in this room": "Ver cuรกndo cambia el asunto de esta sala", "Change the topic of this room": "Cambiar el asunto de esta sala", - "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s ha cambiado los permisos de la sala.", + "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s cambiรณ los permisos de la sala.", "%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s ha establecido los permisos de la sala.", "Converts the DM to a room": "Convierte el mensaje directo a sala", "Converts the room to a DM": "Convierte la sala a un mensaje directo", @@ -2723,11 +2723,11 @@ "Add some details to help people recognise it.": "Aรฑade algรบn detalle para ayudar a que la gente lo reconozca.", "Check your devices": "Comprueba tus dispositivos", "You have unverified logins": "Tienes inicios de sesiรณn sin verificar", - "Verification requested": "Verificaciรณn solicitada", + "Verification requested": "Solicitud de verificaciรณn", "Avatar": "Imagen de perfil", - "Verify other login": "Verificar otro inicio de sesiรณn", + "Verify other login": "Verifica otro inicio de sesiรณn", "Consult first": "Consultar primero", - "Invited people will be able to read old messages.": "Las personas invitadas podrรกn leer mensajes antiguos.", + "Invited people will be able to read old messages.": "Las personas que invites podrรกn leer los mensajes antiguos.", "We couldn't create your DM.": "No hemos podido crear tu mensaje directo.", "Adding...": "Aรฑadiendo...", "Add existing rooms": "Aรฑadir salas que ya existan", @@ -2832,10 +2832,10 @@ "The user you called is busy.": "La persona a la que has llamado estรก ocupada.", "User Busy": "Persona ocupada", "End-to-end encryption isn't enabled": "El cifrado de extremo a extremo no estรก activado", - "If you can't see who youโ€™re looking for, send them your invite link below.": "Si no encuentras abajo a quien buscas, envรญale tu enlace de invitaciรณn.", + "If you can't see who youโ€™re looking for, send them your invite link below.": "Si no encuentras a quien estรฉs buscando, envรญale tu enlace de invitaciรณn, que encontrarรกs mรกs abajo.", "Teammates might not be able to view or join any private rooms you make.": "Las personas de tu equipo no podrรกn ver o unirse a ninguna sala privada que crees.", "Or send invite link": "O envรญa un enlace de invitaciรณn", - "Some suggestions may be hidden for privacy.": "Puede que se hayan ocultado algunas sugerencias por motivos de privacidad.", + "Some suggestions may be hidden for privacy.": "Puede que algunas sugerencias no se muestren por motivos de privacidad.", "Search for rooms or people": "Busca salas o gente", "Message preview": "Vista previa del mensaje", "Forward message": "Reenviar mensaje", @@ -2877,10 +2877,10 @@ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Esta persona estรก teniendo un comportamiento tรณxico. Por ejemplo, insultando al resto, compartiendo contenido explรญcito en una sala para todos los pรบblicos, o incumpliendo las normas de la sala en general.\nSe avisarรก a los moderadores de la sala.", "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Lo que esta persona estรก escribiendo no estรก bien.\nSe avisarรก a los moderadores de la sala.", "Please provide an address": "Por favor, elige una direcciรณn", - "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s ha cambiado los permisos del servidor", - "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s ha cambiado los permisos del servidor %(count)s veces", - "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s ha cambiado los permisos del servidor", - "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s ha cambiado los permisos del servidor %(count)s veces", + "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s cambiรณ los permisos del servidor", + "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s cambiรณ los permisos del servidor %(count)s veces", + "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s cambiรณ los permisos del servidor", + "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s cambiรณ los permisos del servidor %(count)s veces", "Message search initialisation failed, check your settings for more information": "Ha fallado el sistema de bรบsqueda de mensajes. Comprueba tus ajustes para mรกs informaciรณn", "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Elige una direcciรณn para este espacio y los usuarios de tu servidor base (%(localDomain)s) podrรกn encontrarlo a travรฉs del buscador", "To publish an address, it needs to be set as a local address first.": "Para publicar una direcciรณn, primero debe ser aรฑadida como direcciรณn local.", @@ -2915,9 +2915,9 @@ "%(targetName)s joined the room": "%(targetName)s se ha unido a la sala", "%(senderName)s made no change": "%(senderName)s no ha hecho ningรบn cambio", "%(senderName)s set a profile picture": "%(senderName)s se ha puesto una foto de perfil", - "%(senderName)s changed their profile picture": "%(senderName)s ha cambiado su foto de perfil", + "%(senderName)s changed their profile picture": "%(senderName)s cambiรณ su foto de perfil", "%(senderName)s removed their profile picture": "%(senderName)s ha eliminado su foto de perfil", - "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s ha cambiado su nombre a %(displayName)s", + "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s cambiรณ su nombre a %(displayName)s", "%(senderName)s invited %(targetName)s": "%(senderName)s ha invitado a %(targetName)s", "%(targetName)s accepted an invitation": "%(targetName)s ha aceptado una invitaciรณn", "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s ha aceptado la invitaciรณn a %(displayName)s", @@ -3084,7 +3084,7 @@ "To view Spaces, hide communities in Preferences": "Para ver espacios, oculta las comunidades en ajustes", "It's not recommended to make encrypted rooms public. It will mean anyone can find and join the room, so anyone can read messages. You'll get none of the benefits of encryption. Encrypting messages in a public room will make receiving and sending messages slower.": "No estรก recomendado activar el cifrado en salas pรบblicas. Cualquiera puede encontrar la sala y unirse, por lo que cualquiera puede leer los mensajes. No disfrutarรกs de los beneficios del cifrado. Ademรกs, activarlo en una sala pรบblica harรก que recibir y enviar mensajes tarde mรกs.", "Communities have been archived to make way for Spaces but you can convert your communities into Spaces below. Converting will ensure your conversations get the latest features.": "Las comunidades han sido archivadas para dar paso a los espacios, pero puedes convertir tus comunidades a espacios debajo. Al convertirlas, te aseguras de que tus conversaciones tienen acceso a las รบltimas funcionalidades.", - "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Si nos has avisado de un fallo a travรฉs de Github, los registros de depuraciรณn nos pueden ayudar a encontrar mรกs fรกcil el problema. Los registros incluyen datos de uso de la aplicaciรณn incluyendo tu nombre de usuario, las IDs o nombres de las salas o grupos que has visitado, los elementos de la interfaz con los que hayas interactuado recientemente, y los nombres de usuario de otras personas. No contienen mensajes.", + "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Si nos has avisado de un fallo a travรฉs de Github, los registros de depuraciรณn nos pueden ayudar a encontrar mรกs fรกcil el problema. Los registros incluyen datos de uso de la aplicaciรณn como tu nombre de usuario, las IDs o nombres de las salas o grupos que has visitado, los elementos de la interfaz con los que hayas interactuado recientemente, y los nombres de usuario de otras personas. No contienen mensajes.", "Delete avatar": "Borrar avatar", "Flair won't be available in Spaces for the foreseeable future.": "Por ahora no estรก previsto que las insignias estรฉn disponibles en los espacios.", "Ask the admins of this community to make it into a Space and keep a look out for the invite.": "Pรญdele a los admins que conviertan la comunidad en un espacio y espera a que te inviten.", @@ -3092,8 +3092,8 @@ "To create a Space from another community, just pick the community in Preferences.": "Para crear un espacio a partir de otra comunidad, escoge la comunidad en ajustes.", "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited, which UI elements you last interacted with, and the usernames of other users. They do not contain messages.": "Los registros de depuraciรณn contienen datos de uso de la aplicaciรณn como tu nombre de usuario, las IDs o los nombres de las salas o grupos que has visitado, con quรฉ elementos de la interfaz has interactuado recientemente, y nombres de usuario de otras personas. No incluyen mensajes.", "To avoid these issues, create a new public room for the conversation you plan to have.": "Para evitar estos problemas, crea una nueva sala pรบblica para la conversaciรณn que planees tener.", - "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s han cambiado los mensajes fijados de la sala %(count)s veces.", - "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s han cambiado los mensajes fijados de la sala %(count)s veces.", + "%(severalUsers)schanged the pinned messages for the room %(count)s times.|other": "%(severalUsers)s cambiaron los mensajes fijados de la sala %(count)s veces.", + "%(oneUser)schanged the pinned messages for the room %(count)s times.|other": "%(oneUser)s cambiaron los mensajes fijados de la sala %(count)s veces.", "Cross-signing is ready but keys are not backed up.": "La firma cruzada estรก lista, pero no hay copia de seguridad de las claves.", "Rooms and spaces": "Salas y espacios", "Results": "Resultados", @@ -3186,5 +3186,65 @@ "HTML": "HTML", "Are you sure you want to exit during this export?": "ยฟSeguro que quieres salir durante la exportaciรณn?", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s cambiรณ la imagen de la sala.", - "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s enviรณ una pegatina." + "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s enviรณ una pegatina.", + "Size can only be a number between %(min)s MB and %(max)s MB": "El tamaรฑo solo puede ser un nรบmero de %(min)s a %(max)s MB", + "This room is in some spaces youโ€™re not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.": "Esta sala estรก incluida en algunos espacios en los que no tienes permisos de administraciรณn. En esos espacios, la sala que aparecerรก serรก la antigua, pero se avisarรก a las personas que estรฉn en la sala y podrรกn unirse a la nueva.", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Esperando a que lo verifiques tu otra sesiรณn, %(deviceName)s (%(deviceId)s)โ€ฆ", + "Waiting for you to verify on your other sessionโ€ฆ": "Esperando a que lo verifiques en tu otra sesiรณnโ€ฆ", + "%(date)s at %(time)s": "%(date)s a la(s) %(time)s", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Parece que no tienes una clave de seguridad u otros dispositivos para la verificaciรณn. Este dispositivo no podrรก acceder los mensajes cifrados antiguos. Para verificar tu identidad en este dispositivo, tendrรกs que restablecer tus claves de verificaciรณn.", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Por favor, hazlo solo si de verdad crees que has perdido todos tus demรกs dispositivos y tu clave de seguridad.", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "Una vez restableces las claves de verificaciรณn, no lo podrรกs deshacer. Despuรฉs de restablecerlas, no podrรกs acceder a los mensajes cifrados antiguos, y cualquier persona que te haya verificado verรก avisos de seguridad hasta que vuelvas a hacer la verificaciรณn con ella.", + "I'll verify later": "La verificarรฉ en otro momento", + "Verify with another login": "Verificar usando otro inicio de sesiรณn", + "Verify with Security Key": "Verificar con una clave de seguridad", + "Verify with Security Key or Phrase": "Verificar con una clave o frase de seguridad", + "Proceed with reset": "Continuar y restablecer", + "Skip verification for now": "Saltar la verificaciรณn por ahora", + "Really reset verification keys?": "ยฟDe verdad quieres restablecer las claves de verificaciรณn?", + "Unable to verify this login": "No se ha podido verificar este inicio de sesiรณn", + "To join this Space, hide communities in your preferences": "Para unirte a este espacio, oculta las comunidades en tus ajustes", + "To view this Space, hide communities in your preferences": "Para ver este espacio, oculta las comunidades en tus ajustes", + "To view %(communityName)s, swap to communities in your preferences": "Para ver %(communityName)s, cambia a las comunidades en tus ajustes", + "To join %(communityName)s, swap to communities in your preferences": "Para unirte a %(communityName)s, cambia a las comunidades en tus ajustes", + "Select from the options below to export chats from your timeline": "Selecciona entre las opciones siguientes las conversaciones que quieres exportar", + "Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "ยฟSeguro que quieres parar la exportaciรณn de tus datos? Si quieres exportarlos mรกs adelante, tendrรกs que empezarla de nuevo.", + "Your export was successful. Find it in your Downloads folder.": "La exportaciรณn ha terminado correctamente. La puedes encontrar en tu carpeta de Descargas.", + "The export was cancelled successfully": "Has cancelado la exportaciรณn", + "Export Successful": "Exportado con รฉxito", + "Number of messages": "Nรบmero de mensajes", + "Enter a number between %(min)s and %(max)s": "Escribe un nรบmero entre %(min)s y %(max)s", + "Creating Space...": "Creando el espacioโ€ฆ", + "Fetching data...": "Pidiendo los datosโ€ฆ", + "To proceed, please accept the verification request on your other login.": "Antes de continuar, acepta la solicitud de verificaciรณn en algรบn otro sitio donde tengas tu sesiรณn iniciada, por favor.", + "Number of messages can only be a number between %(min)s and %(max)s": "El nรบmero de mensajes solo puede ser de %(min)s a %(max)s", + "They won't be able to access whatever you're not an admin of.": "No podrรกn acceder a donde no tengas permisos de administraciรณn.", + "Show:": "Mostrar:", + "Shows all threads from current room": "Muestra todos los hilos de la sala actual", + "All threads": "Todos los hilos", + "Shows all threads youโ€™ve participated in": "Muestra todos los hilos en los que has participado", + "My threads": "Mis hilos", + "Downloading": "Descargando", + "Unban them from specific things I'm able to": "Quitar veto de algunos sitios donde tenga los permisos suficientes", + "Ban them from specific things I'm able to": "Vetar de algunos sitios donde tenga los permisos suficientes", + "Ban them from everything I'm able to": "Vetar de todos los sitios donde tenga los permisos suficientes", + "Unban them from everything I'm able to": "Quitar veto de todos los sitios donde tenga los permisos suficientes", + "Ban from %(roomName)s": "Vetar de %(roomName)s", + "Unban from %(roomName)s": "Quitar veto de %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "Podrรกn seguir accediendo a donde no tengas permisos de administraciรณn.", + "Kick them from specific things I'm able to": "Echar de alguna sala en concreto donde tenga los permisos suficientes", + "Kick them from everything I'm able to": "Echar de todas las salas donde tenga los permisos suficientes", + "Kick from %(roomName)s": "Echar de %(roomName)s", + "Disinvite from %(roomName)s": "Anular la invitaciรณn a %(roomName)s", + "Threads": "Hilos", + "Create poll": "Crear una encuesta", + "%(count)s reply|one": "%(count)s respuesta", + "%(count)s reply|other": "%(count)s respuestas", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Actualizando espacioโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Actualizando espaciosโ€ฆ (%(progress)s de %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Enviando invitaciรณnโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "Enviando invitacionesโ€ฆ (%(progress)s de %(count)s)", + "Loading new room": "Cargando la nueva sala", + "Upgrading room": "Actualizar sala", + "Polls (under active development)": "Encuestas (en construcciรณn)" } diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 9fa345f435e..4145c1a2bf5 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -1856,8 +1856,8 @@ "The homeserver the user youโ€™re verifying is connected to": "Sinu poolt verifitseeritava kasutaja koduserver", "Yours, or the other usersโ€™ internet connection": "Sinu vรตi teise kasutaja internetiรผhendus", "Yours, or the other usersโ€™ session": "Sinu vรตi teise kasutaja sessioon", - "Trusted": "Usaldusvรครคrsed", - "Not trusted": "Ei ole usaldusvรครคrsed", + "Trusted": "Usaldusvรครคrne", + "Not trusted": "Ei ole usaldusvรครคrne", "%(count)s verified sessions|other": "%(count)s verifitseeritud sessiooni", "%(count)s verified sessions|one": "1 verifitseeritud sessioon", "Hide verified sessions": "Peida verifitseeritud sessioonid", @@ -2902,7 +2902,7 @@ "Collapse reply thread": "Ahenda vastuste jutulรตng", "Show preview": "Nรคita eelvaadet", "View source": "Vaata algset teavet", - "Forward": "Edasi", + "Forward": "Edasta", "Settings - %(spaceName)s": "Seadistused - %(spaceName)s", "Toxic Behaviour": "Ebasobilik kรคitumine", "Report the entire room": "Teata tervest jututoast", @@ -3200,5 +3200,53 @@ "Are you sure you want to exit during this export?": "Kas sa oled kindel, et soovid lรตpetada tegevuse selle ekspordi ajal?", "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s saatis kleepsu.", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s muutis jututoa tunnuspilti.", - "%(date)s at %(time)s": "%(date)s %(time)s" + "%(date)s at %(time)s": "%(date)s %(time)s", + "Proceed with reset": "Jรคtka kustutamisega", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Tundub, et sul ei ole ei turvavรตtit ega muid seadmeid, mida saaksid verifitseerimiseks kasutada. Siin seadmes ei saa lugeda vanu krรผptitud sรตnumeid. Enda tuvastamiseks selles seadmed pead oma vanad verifitseerimisvรตtmed kustutama.", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Palun jรคtka ainult siis, kui sa oled kaotanud ligipรครคsu kรตikidele oma seadmetele ning oma turvavรตtmele.", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "Verifitseerimisvรตtmete kustutamist ei saa hiljem tagasi vรตtta. Peale seda sul puudub ligipรครคs vanadele krรผptitud sรตnumitele ja kรตik sinu verifitseeritud sรตbrad-tuttavad nรคevad turvahoiatusi seni kuni sa uuesti nad verifitseerid.", + "I'll verify later": "Ma verifitseerin hiljem", + "Verify with another login": "Verifitseeri oma muu sessiooniga", + "Verify with Security Key": "Verifitseeri turvavรตtmega", + "Verify with Security Key or Phrase": "Verifitseeri turvavรตtme vรตi turvafraasiga", + "Skip verification for now": "Jรคta verifitseerimine praegu vahele", + "Really reset verification keys?": "Kas tรตesti kustutame kรตik verifitseerimisvรตtmed?", + "Unable to verify this login": "Selle seadme verifitseerimine ei รตnnestunud", + "To proceed, please accept the verification request on your other login.": "Jรคtkamaks palun vรตta vastu verifitseerimispalve oma teises seadmes.", + "Waiting for you to verify on your other sessionโ€ฆ": "Ootan, et sa verifitseeriksid teises sessioonisโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Ootan, et sa verifitseerid oma teises sessioonis: %(deviceName)s (%(deviceId)s)โ€ฆ", + "Creating Space...": "Loon kogukonnakeskustโ€ฆ", + "Fetching data...": "Laadin andmeid...", + "Polls (under active development)": "Kรผsitlused (aktiivses arendusjรคrgus)", + "Create poll": "Koosta รผks kรผsitlus", + "Space Autocomplete": "Kogukonnakeskuste dรผnaamiline otsing", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Uuendan kogukonnakeskust...", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Uuendan kogukonnakeskuseid... (%(progress)s / %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Saadan kutset...", + "Sending invites... (%(progress)s out of %(count)s)|other": "Saadan kutseid... (%(progress)s / %(count)s)", + "Loading new room": "Laadin uut jututuba", + "Upgrading room": "Uuendan jututoa versiooni", + "Show:": "Nรคita:", + "Shows all threads from current room": "Nรคitab kรตiki praeguse jututoa jutulรตngasid", + "All threads": "Kรตik jutulรตngad", + "Shows all threads youโ€™ve participated in": "Nรคitab kรตiki jutulรตngasid, kus sa oled osalenud", + "My threads": "Minu jutulรตngad", + "They won't be able to access whatever you're not an admin of.": "Kasutaja ei saa ligi kohtadele, kus sul pole peakasutaja รตigusi.", + "Ban them from specific things I'm able to": "Mรครคra kasutajale suhtluskeeld valitud kohtades, kust ma saan", + "Unban them from specific things I'm able to": "Eemalda kasutajalt suhtluskeeld valitud kohtadest, kust ma saan", + "Ban them from everything I'm able to": "Mรครคra kasutajale suhtluskeeld kรตikjal, kus ma saan", + "Unban them from everything I'm able to": "Eemalda kasutajalt suhtluskeeld kรตikjalt, kust ma saan", + "Ban from %(roomName)s": "Mรครคra suhtluskeeld %(roomName)s jututoas", + "Unban from %(roomName)s": "Eemalda suhtluskeeld %(roomName)s jututoas", + "They'll still be able to access whatever you're not an admin of.": "Kasutaja saab jรคtkuvalt ligi kohtadele, kus sul pole peakasutaja รตigusi.", + "Kick them from specific things I'm able to": "Mรผksa kasutaja vรคlja valitud kohtadest, kust ma saan", + "Disinvite from %(roomName)s": "Vรตta tagasi %(roomName)s jututoa kutse", + "Kick them from everything I'm able to": "Mรผksa kasutaja vรคlja kรตikjalt, kust ma saan", + "Kick from %(roomName)s": "Mรผksa %(roomName)s jututoast vรคlja", + "Threads": "Jutulรตngad", + "Downloading": "Laadin alla", + "%(count)s reply|one": "%(count)s vastus", + "%(count)s reply|other": "%(count)s vastust", + "View in room": "Vaata jututoas", + "Enter your Security Phrase or to continue.": "Jรคtkamiseks sisesta oma turvafraas vรตi ." } diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index e2ca967980e..6626796fba8 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -128,7 +128,7 @@ "Register": "Rekisterรถidy", "Reject invitation": "Hylkรครค kutsu", "Return to login screen": "Palaa kirjautumissivulle", - "%(brand)s version:": "%(brand)s-webin versio:", + "%(brand)s version:": "%(brand)s-versio:", "Rooms": "Huoneet", "Save": "Tallenna", "Search failed": "Haku epรคonnistui", @@ -203,7 +203,7 @@ "Import room keys": "Tuo huoneen avaimet", "File to import": "Tuotava tiedosto", "You must join the room to see its files": "Sinun pitรครค liittyรค huoneeseen voidaksesi nรคhdรค sen sisรคltรคmรคt tiedostot", - "Reject all %(invitedRooms)s invites": "Hylkรครค kaikki %(invitedRooms)s kutsut", + "Reject all %(invitedRooms)s invites": "Hylkรครค kaikki %(invitedRooms)s kutsua", "Failed to invite": "Kutsu epรคonnistui", "Confirm Removal": "Varmista poistaminen", "Unknown error": "Tuntematon virhe", @@ -920,7 +920,7 @@ "User %(userId)s is already in the room": "Kรคyttรคjรค %(userId)s on jo huoneessa", "The user must be unbanned before they can be invited.": "Kรคyttรคjรคn porttikielto tรคytyy poistaa ennen kutsumista.", "Upgrade to your own domain": "Pรคivitรค omaan verkkotunnukseen", - "Accept all %(invitedRooms)s invites": "Hyvรคksy kaikki %(invitedRooms)s kutsut", + "Accept all %(invitedRooms)s invites": "Hyvรคksy kaikki %(invitedRooms)s kutsua", "Change room avatar": "Vaihda huoneen kuva", "Change room name": "Vaihda huoneen nimi", "Change main address for the room": "Vaihda huoneen pรครคosoite", @@ -2591,6 +2591,301 @@ "Identity server": "Identiteettipalvelin", "Identity server (%(server)s)": "Identiteettipalvelin (%(server)s)", "Could not connect to identity server": "Identiteettipalvelimeen ei saatu yhteyttรค", - "Not a valid identity server (status code %(code)s)": "Ei kelvollinen identiteettipalvelin (tilakoodi %(code)s)", - "Identity server URL must be HTTPS": "Identiteettipalvelimen URL-osoitteen tรคytyy olla HTTPS-alkuinen" + "Not a valid identity server (status code %(code)s)": "Identiteettipalvelin ei ole kelvollinen (tilakoodi %(code)s)", + "Identity server URL must be HTTPS": "Identiteettipalvelimen URL-osoitteen tรคytyy olla HTTPS-alkuinen", + "Transfer Failed": "Siirto epรคonnistui", + "Unable to transfer call": "Puhelun siirtรคminen ei onnistu", + "Invite your teammates": "Kutsu tiimikaverit", + "Me and my teammates": "Minรค ja tiimikaverit", + "Just me": "Vain minรค", + "Go to my space": "Mene avaruuteeni", + "Go to my first room": "Mene ensimmรคiseen huoneeseeni", + "Search for rooms or spaces": "Etsi huoneita tai avaruuksia", + "Support": "Tuki", + "Rooms and spaces": "Huoneet ja avaruudet", + "No results for \"%(query)s\"": "Ei tuloksia kyselyllรค \"%(query)s\"", + "Unable to copy a link to the room to the clipboard.": "Huoneen linkin kopiointi leikepรถydรคlle ei onnistu.", + "Unable to copy room link": "Huoneen linkin kopiointi ei onnistu", + "Private community": "Yksityinen yhteisรถ", + "Public community": "Julkinen yhteisรถ", + "Play": "Toista", + "Pause": "Keskeytรค", + "Error downloading audio": "Virhe รครคntรค ladattaessa", + "Avatar": "Avatar", + "Join the beta": "Liity beetaan", + "Leave the beta": "Poistu beetasta", + "Spaces is a beta feature": "Avaruudet eli Spaces on beetaominaisuus", + "Show preview": "Nรคytรค esikatselu", + "Forward": "Vรคlitรค", + "View source": "Nรคytรค lรคhde", + "Settings - %(spaceName)s": "Asetukset - %(spaceName)s", + "Space settings": "Avaruuden asetukset", + "Report the entire room": "Raportoi koko huone", + "Search spaces": "Etsi avaruuksia", + "Unnamed Space": "Nimetรถn avaruus", + "You may contact me if you have any follow up questions": "Voitte olla yhteydessรค minuun, jos teillรค on lisรคkysymyksiรค", + "Search for rooms or people": "Etsi huoneita tai ihmisiรค", + "Message preview": "Viestin esikatselu", + "Forward message": "Vรคlitรค viesti", + "Sent": "Lรคhetetty", + "Open link": "Avaa linkki", + "Include Attachments": "Sisรคllytรค liitteet", + "Stop": "Pysรคytรค", + "Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "Pysรคytรค", + "Export Successful": "Vienti onnistui", + "Number of messages": "Viestien mรครคrรค", + "Server did not require any authentication": "Palvelin ei vaatinut mitรครคn tunnistautumista", + "Adding...": "Lisรคtรครคn...", + "Only people invited will be able to find and join this space.": "Vain kutsutut ihmiset voivat lรถytรครค tรคmรคn avaruuden ja liittyรค siihen.", + "Public space": "Julkinen avaruus", + "Failed to migrate community": "Yhteisรถn migraatio epรคonnistui", + "This community has been upgraded into a Space": "Tรคmรค yhteisรถ on pรคivitetty avaruudeksi", + "Public room": "Julkinen huone", + "Private room (invite only)": "Yksityinen huone (vain kutsulla)", + "Create a room": "Luo huone", + "Only people invited will be able to find and join this room.": "Vain kutsutut ihmiset voivat lรถytรครค tรคmรคn huoneen ja liittyรค siihen.", + "You can change this at any time from room settings.": "Voit muuttaa tรคmรคn milloin tahansa huoneen asetuksista.", + "Everyone in will be able to find and join this room.": "Kaikki avaruudessa voivat lรถytรครค tรคmรคn huoneen ja liittyรค siihen.", + "Zoom in": "Lรคhennรค", + "Zoom out": "Loitonna", + "%(count)s members including %(commaSeparatedMembers)s|other": "%(count)s jรคsentรค mukaan lukien %(commaSeparatedMembers)s", + "Including %(commaSeparatedMembers)s": "Mukaan lukien %(commaSeparatedMembers)s", + "Share content": "Jaa sisรคltรถ", + "Application window": "Sovelluksen ikkuna", + "Share entire screen": "Jaa koko nรคyttรถ", + "Image": "Kuva", + "Sticker": "Tarra", + "Expand quotes โ”‚ โ‡ง+click": "Laajenna lainaukset โ”‚ โ‡ง+napsauta", + "Collapse quotes โ”‚ โ‡ง+click": "Supista lainaukset โ”‚ โ‡ง+napsauta", + "Error processing audio message": "Virhe รครคniviestiรค kรคsiteltรคessรค", + "Decrypting": "Puretaan salausta", + "The call is in an unknown state!": "Puhelu on tuntemattomassa tilassa!", + "Missed call": "Vastaamaton puhelu", + "Unknown failure: %(reason)s": "Tuntematon virhe: %(reason)s", + "An unknown error occurred": "Tapahtui tuntematon virhe", + "Their device couldn't start the camera or microphone": "Laite ei voinut kรคyynnistรครค kameraa tai mikrofonia", + "Connection failed": "Yhteys epรคonnistui", + "No answer": "Ei vastausta", + "Message": "Viesti", + "Pinned messages": "Kiinnitetyt viestit", + "Nothing pinned, yet": "Ei mitรครคn kiinnitetty, ei vielรค", + "Stop recording": "Pysรคytรค nauhoittaminen", + "Copy Room Link": "Kopioi huoneen linkki", + "Joining space โ€ฆ": "Liitytรครคn avaruuteenโ€ฆ", + "Explore %(spaceName)s": "Tutki avaruutta %(spaceName)s", + "Invite to just this room": "Kutsu vain tรคhรคn huoneeseen", + "Send voice message": "Lรคhetรค รครคniviesti", + "Send a sticker": "Lรคhetรค tarra", + "Add emoji": "Lisรครค emoji", + "Invite to this space": "Kutsu tรคhรคn avaruuteen", + "Are you sure you want to make this encrypted room public?": "Haluatko varmasti tehdรค tรคstรค salatusta huoneesta julkisen?", + "Unknown failure": "Tuntematon virhe", + "Change description": "Vaihda kuvaus", + "Change space name": "Vaihda avaruuden nimi", + "Change space avatar": "Vaihda avaruuden avatar", + "Open Space": "Avaa avaruus", + "Message bubbles": "Viestikuplat", + "IRC": "IRC", + "Upgrade anyway": "Pรคivitรค silti", + "Before you upgrade": "Ennen kuin pรคivitรคt", + "Space members": "Avaruuden jรคsenet", + "& %(count)s more|one": "& %(count)s lisรครค", + "& %(count)s more|other": "& %(count)s lisรครค", + "Anyone can find and join.": "Kuka tahansa voi lรถytรครค ja liittyรค.", + "Only invited people can join.": "Vain kutsutut ihmiset voivat liittyรค.", + "Space options": "Avaruuden valinnat", + "Collapse": "Supista", + "Expand": "Laajenna", + "Recommended for public spaces.": "Suositeltu julkisiin avaruuksiin.", + "Guests can join a space without having an account.": "Vieraat voivat liittyรค avaruuteen ilman tiliรค.", + "Enable guest access": "Ota kรคyttรถรถn vieraiden pรครคsy", + "Failed to save space settings.": "Avaruuden asetusten tallentaminen epรคonnistui.", + "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Kiitos, ettรค kokeilet Avaruudet-ominaisuuta. Palautteesi auttaa tulevien versioiden kanssa.", + "Delete avatar": "Poista avatar", + "Mute the microphone": "Mykistรค mikrofoni", + "Unmute the microphone": "Poista mikrofonin mykistys", + "Dialpad": "Numeronรคppรคimistรถ", + "Show sidebar": "Nรคytรค sivupalkki", + "Hide sidebar": "Piilota sivupalkki", + "Start sharing your screen": "Aloita nรคyttรถsi jakaminen", + "Stop sharing your screen": "Lopeta nรคyttรถsi jakaminen", + "Stop the camera": "Pysรคytรค kamera", + "Start the camera": "Kรคynnistรค kamera", + "Your camera is still enabled": "Kamerasi on edelleen pรครคllรค", + "Your camera is turned off": "Kamerasi on pois pรครคltรค", + "%(sharerName)s is presenting": "%(sharerName)s esittรครค", + "You are presenting": "Esitรคt parhaillaan", + "Don't send read receipts": "ร„lรค lรคhetรค lukukuittauksia", + "Send pseudonymous analytics data": "Lรคhetรค pseudonymisoitua analytiikkadataa", + "Threaded messaging": "Sรคikeistetty viestittely", + "Set up Secure Backup": "Mรครคritรค turvallinen varmuuskopio", + "Error fetching file": "Virhe tiedostoa noutaessa", + "You have unverified logins": "Vahvistamattomia kirjautumisia havaittu", + "Review to ensure your account is safe": "Katselmoi varmistaaksesi, ettรค tilisi on turvassa", + "%(creatorName)s created this room.": "%(creatorName)s loi tรคmรคn huoneen.", + "Plain Text": "Raakateksti", + "JSON": "JSON", + "HTML": "HTML", + "Share your public space": "Jaa julkinen avaruutesi", + "Invite to %(spaceName)s": "Kutsu avaruuteen %(spaceName)s", + "User %(userId)s is already invited to the room": "Kรคyttรคjรค %(userId)s on jo kutsuttu huoneeseen", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s kiinnitti viestin tรคhรคn huoneeseen. Katso kaikki kiinnitetyt viestit.", + "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s lรคhetti tarran.", + "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s poisti nรคyttรถnimensรค (%(oldDisplayName)s)", + "The user you called is busy.": "Kรคyttรคjรค, jolle soitit, on varattu.", + "User Busy": "Kรคyttรคjรค varattu", + "Decide who can join %(roomName)s.": "Pรครคtรค ketkรค voivat liittyรค huoneeseen %(roomName)s.", + "Decide who can view and join %(spaceName)s.": "Pรครคtรค ketkรค voivat katsella avaruutta %(spaceName)s ja liittyรค siihen.", + "Space information": "Avaruuden tiedot", + "Allow people to preview your space before they join.": "Salli ihmisten esikatsella avaruutesi ennen liittymistรค.", + "Preview Space": "Esikatsele avaruus", + "Space visibility": "Avaruuden nรคkyvyys", + "Room visibility": "Huoneen nรคkyvyys", + "Visibility": "Nรคkyvyys", + "Are you sure you want to leave the space '%(spaceName)s'?": "Haluatko varmasti poistua avaruudesta '%(spaceName)s'?", + "Leave space": "Poistu avaruudesta", + "Would you like to leave the rooms in this space?": "Haluatko poistua tรคssรค avaruudessa olevista huoneista?", + "You are about to leave .": "Olet aikeissa poistua avaruudesta .", + "Leave %(spaceName)s": "Poistu avaruudesta %(spaceName)s", + "Leave Space": "Poistu avaruudesta", + "Edit settings relating to your space.": "Muokkaa avaruuteesi liittyviรค asetuksia.", + "Welcome to ": "Tervetuloa, tรคmรค on ", + "Filter all spaces": "Suodata kaikista avaruuksista", + "Enable encryption in settings.": "Ota salaus kรคyttรถรถn asetuksissa.", + "A private space for you and your teammates": "Yksityinen avaruus sinulle ja tiimikavereille", + "A private space to organise your rooms": "Yksityinen avaruus jossa voit jรคrjestรครค huoneesi", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites.": "Yksityiset viestisi ovat normaalisti salattu, mutta tรคmรค huone ei ole. Yleensรค tรคmรค johtuu ei tuetusta laitteesta tai kรคytetystรค tavasta, kuten sรคhkรถpostikutsuista.", + "End-to-end encryption isn't enabled": "Pรครคstรค pรครคhรคn -salaus ei ole kรคytรถssรค", + "You have no visible notifications.": "Sinulla ei ole nรคkyviรค ilmoituksia.", + "Add space": "Lisรครค avaruus", + "Want to add an existing space instead?": "Haluatko sen sijaan lisรคtรค olemassa olevan avaruuden?", + "Add a space to a space you manage.": "Lisรครค avaruus hallitsemaasi avaruuteen.", + "Anyone will be able to find and join this room, not just members of .": "Kuka tahansa voi lรถytรครค tรคmรคn huoneen ja liittyรค siihen, ei pelkรคstรครคn avaruuden jรคsenet.", + "Anyone will be able to find and join this space, not just members of .": "Kuka tahansa voi lรถytรครค tรคmรคn avaruuden ja liittyรค siihen, ei pelkรคstรครคn avaruuden jรคsenet.", + "Anyone in will be able to find and join.": "Kuka tahansa avaruudessa voi lรถytรครค ja liittyรค.", + "Anyone will be able to find and join this room.": "Kuka tahansa voi lรถytรครค tรคmรคn huoneen ja liittyรค siihen.", + "e.g. my-space": "esim. minun-avaruus", + "Private space": "Yksityinen avaruus", + "Private space (invite only)": "Yksityinen avaruus (vain kutsulla)", + "Visible to space members": "Nรคkyvissรค avaruuden jรคsenille", + "To view all keyboard shortcuts, click here.": "Katso kaikki pikanรคppรคimet napsauttamalla tรคstรค.", + "Autoplay videos": "Toista videot automaattisesti", + "Autoplay GIFs": "Toista GIF-tiedostot automaattisesti", + "Images, GIFs and videos": "Kuvat, GIF:t ja videot", + "Code blocks": "Koodilohkot", + "Keyboard shortcuts": "Pikanรคppรคimet", + "Displaying time": "Ajan nรคyttรคminen", + "Olm version:": "Olm-versio:", + "Your access token gives full access to your account. Do not share it with anyone.": "Kรคyttรถpolettisi (ns. token) antaa tรคyden pรครคsyn tilillesi. ร„lรค jaa sitรค kenenkรครคn kanssa.", + "Access Token": "Kรคyttรถpoletti", + "Select spaces": "Valitse avaruudet", + "Want to add a new space instead?": "Haluatko lisรคtรค sen sijaan uuden avaruuden?", + "Add existing space": "Lisรครค olemassa oleva avaruus", + "Space selection": "Avaruuden valinta", + "Search for rooms": "Etsi huoneita", + "Search for spaces": "Etsi avaruuksia", + "%(count)s results in all spaces|one": "%(count)s tulos kaikissa avaruuksissa", + "%(count)s results in all spaces|other": "%(count)s tulosta kaikissa avaruuksissa", + "Global": "Yleiset", + "New keyword": "Uusi avainsana", + "Keyword": "Avainsana", + "Messages containing keywords": "Viestit, jotka sisรคltรคvรคt avainsanoja", + "Enable email notifications for %(email)s": "Sรคhkรถposti-ilmoitukset osoitteeseen %(email)s", + "Enable for this account": "Ota kรคyttรถรถn tรคllรค tilillรค", + "Mentions & keywords": "Maininnat ja avainsanat", + "%(targetName)s left the room": "%(targetName)s poistui huoneesta", + "%(targetName)s left the room: %(reason)s": "%(targetName)s poistui huoneesta: %(reason)s", + "%(targetName)s rejected the invitation": "%(targetName)s hylkรคsi kutsun", + "%(targetName)s joined the room": "%(targetName)s liittyi huoneeseen", + "%(senderName)s made no change": "%(senderName)s ei tehnyt muutosta", + "%(senderName)s set a profile picture": "%(senderName)s asetti profiilikuvan", + "%(senderName)s changed their profile picture": "%(senderName)s vaihtoi profiilikuvansa", + "%(senderName)s removed their profile picture": "%(senderName)s poisti profiilikuvansa", + "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s vaihtoi nรคyttรถnimekseen %(displayName)s", + "%(senderName)s set their display name to %(displayName)s": "%(senderName)s asetti nรคyttรถnimekseen %(displayName)s", + "%(targetName)s accepted an invitation": "%(targetName)s hyvรคksyi kutsun", + "Some invites couldn't be sent": "Joitain kutsuja ei voitu lรคhettรครค", + "We couldn't log you in": "Emme voineet kirjata sinua sisรครคn", + "%(date)s at %(time)s": "%(date)s klo %(time)s", + "Collapse space panel": "Supista avaruuspaneeli", + "Expand space panel": "Laajenna avaruuspaneeli", + "Spaces": "Avaruudet", + "Show all rooms": "Nรคytรค kaikki huoneet", + "To join a space you'll need an invite.": "Liittyรคksesi avaruuteen tarvitset kutsun.", + "You can also make Spaces from communities.": "Voit myรถs luoda avaruuksia yhteisรถistรค.", + "Your private space": "Yksityinen avaruutesi", + "Your public space": "Julkinen avaruutesi", + "Invite only, best for yourself or teams": "Vain kutsulla, paras itsellesi tai tiimeille", + "Open space for anyone, best for communities": "Avoin avaruus kaikille, paras yhteisรถille", + "Give feedback.": "Anna palautetta.", + "Spaces feedback": "Palautetta avaruuksista", + "Please enter a name for the space": "Anna nimi avaruudelle", + "Address": "Osoite", + "You can change this later.": "Voit muuttaa tรคmรคn myรถhemmin.", + "Spaces are a new way to make a community, with new features coming.": "Avaruudet ovat uusi tapa luoda yhteisรถ, ja lisรครค ominaisuuksia on tulossa.", + "Spaces are a new way to group rooms and people.": "Avaruudet on uudenlainen tapa ryhmitellรค huoneita ja ihmisiรค.", + "Spaces are a new feature.": "Avaruudet ovat uusi ominaisuus.", + "Create Space from community": "Luo avaruus yhteisรถstรค", + "Space created": "Avaruus luotu", + "Create a new space": "Luo uusi avaruus", + "You do not have permissions to create new rooms in this space": "Sinulla ei ole oikeuksia luoda uusia huoneita tรคssรค avaruudessa", + "Create Space": "Luo avaruus", + "What kind of Space do you want to create?": "Minkรคlaisen avaruuden haluat luoda?", + "Create a space": "Luo avaruus", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s poisti viestin kiinnityksen tรคstรค huoneesta. Katso kaikki kiinnitetyt viestit.", + "Sends the given message as a spoiler": "Lรคhettรครค annetun viestin spoilerina", + "I'll verify later": "Vahvistan myรถhemmin", + "Skip verification for now": "Ohita vahvistus toistaiseksi", + "Unable to verify this login": "Tรคtรค kirjautumista ei voitu vahvistaa", + "What projects are you working on?": "Minkรค projektien parissa tyรถskentelet?", + "Invite by username": "Kutsu kรคyttรคjรคnimellรค", + "Failed to invite the following users to your space: %(csvUsers)s": "Seuraavien kรคyttรคjien kutsuminen avaruuteesi epรคonnistui: %(csvUsers)s", + "Who are you working with?": "Kenen kanssa tyรถskentelet?", + "Created from ": "Luotu yhteisรถstรค ", + "Results": "Tulokset", + "Suggested": "Ehdotettu", + "Communities won't receive further updates.": "Yhteisรถt eivรคt saa enรครค pรคivityksiรค.", + "You can create a Space from this community here.": "Voit luoda avaruuden tรคstรค yhteisรถstรค tรครคllรค.", + "Verify other login": "Vahvista toinen kirjautuminen", + "Illegal Content": "Laiton sisรคltรถ", + "You won't be able to rejoin unless you are re-invited.": "Et voi liittyรค uudelleen, ellei sinua kutsuta uudelleen.", + "User Directory": "Kรคyttรคjรคhakemisto", + "Size Limit": "Kokoraja", + "The export was cancelled successfully": "Vienti peruttiin onnistuneesti", + "MB": "Mt", + "Server did not return valid authentication information.": "Palvelin ei palauttanut kelvollista tunnistautumistietoa.", + "This description will be shown to people when they view your space": "Tรคmรค kuvaus nรคytetรครคn ihmisille, kun he katselevat avaruuttasi", + "Creating Space...": "Luodaan avaruutta...", + "Fetching data...": "Noudetaan tietoja...", + "Please provide an address": "Mรครคritรค osoite", + "Thread": "Ketju", + "Call back": "Soita takaisin", + "Role in ": "Rooli huoneessa ", + "Screen sharing is here!": "Nรคytรถnjakaminen on tรครคllรค!", + "Reply to threadโ€ฆ": "Vastaa ketjuunโ€ฆ", + "Reply to encrypted threadโ€ฆ": "Vastaa salattuun ketjuunโ€ฆ", + "Are you sure you want to add encryption to this public room?": "Haluatko varmasti lisรคtรค salauksen tรคhรคn julkiseen huoneeseen?", + "Show my Communities": "Nรคytรค omat yhteisรถt", + "There was an error loading your notification settings.": "Virhe ladatessa ilmoitusasetuksia.", + "An error occurred whilst saving your notification preferences.": "Ilmoitusasetuksia tallentaessa tapahtui virhe.", + "Error saving notification preferences": "Virhe tallentaessa ilmoitusasetuksia", + "Private (invite only)": "Yksityinen (vain kutsulla)", + "Workspace: ": "Tyรถtila: ", + "This may be useful for public spaces.": "Tรคmรค voi olla hyรถdyllinen julkisille avaruuksille.", + "Invite with email or username": "Kutsu sรคhkรถpostiosoitteella tai kรคyttรคjรคnimellรค", + "Waiting for you to verify on your other sessionโ€ฆ": "Odotetaan sinun vahvistavan toinen istuntosiโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Odotetaan sinun vahvistan toinen istuntosi, %(deviceName)s (%(deviceId)s)โ€ฆ", + "Display Communities instead of Spaces": "Nรคytรค yhteisรถt avaruuksien sijaan", + "Use Ctrl + F to search timeline": "Ctrl + F hakee aikajanalta", + "%(senderName)s unpinned a message from this room. See all pinned messages.": "%(senderName)s poisti viestin kiinnityksen tรคstรค huoneesta. Katso kaikki kiinnitetyt viestit.", + "%(senderName)s pinned a message to this room. See all pinned messages.": "%(senderName)s kiinnitti viestin tรคhรคn huoneeseen. Katso kaikki kiinnitetyt viestit.", + "Silence call": "Hiljennรค puhelu", + "Sound on": "ร„รคni pรครคllรค", + "Don't miss a reply": "ร„lรค jรคtรค vastauksia huomiotta", + "Current Timeline": "Nykyinen aikajana", + "Specify a number of messages": "Mรครคritรค viestien lukumรครคrรค", + "From the beginning": "Alusta lรคhtien", + "Are you sure you want to exit during this export?": "Haluatko varmasti poistua tรคmรคn viennin aikana?", + "File Attached": "Tiedosto liitetty", + "Topic: %(topic)s": "Aihe: %(topic)s" } diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 8af2e8cee16..405aa922cef 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -3169,5 +3169,83 @@ "Leave all rooms": "Quitter tous les salons", "Don't leave any rooms": "Ne quitter aucun salon", "Expand quotes โ”‚ โ‡ง+click": "Dรฉvelopper les citations โ”‚ โ‡ง+clic", - "Collapse quotes โ”‚ โ‡ง+click": "Rรฉduire les citations โ”‚ โ‡ง+clic" + "Collapse quotes โ”‚ โ‡ง+click": "Rรฉduire les citations โ”‚ โ‡ง+clic", + "Include Attachments": "Inclure les fichiers attachรฉs", + "Size Limit": "Taille maximale", + "Format": "Format", + "Select from the options below to export chats from your timeline": "Sรฉlectionner les options ci-dessous pour exporter les conversations privรฉes de votre historique", + "Export Chat": "Exporter la conversation privรฉe", + "Exporting your data": "Export de vos donnรฉes", + "Stop": "Arrรชter", + "Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "รŠtes vous sรปr de vouloir arrรชter lโ€™export de vos donnรฉesย ? Si vous le fait, vous devrez recommencer depuis le dรฉbut.", + "Your export was successful. Find it in your Downloads folder.": "Votre export est rรฉussi. Vous le trouverez dans votre dossier Tรฉlรฉchargements.", + "The export was cancelled successfully": "Cet export a รฉtรฉ annulรฉ avec succรจs", + "Export Successful": "Export rรฉussi", + "MB": "Mo", + "Number of messages": "Nombre de messages", + "Number of messages can only be a number between %(min)s and %(max)s": "Le nombre de messages ne peut รชtre quโ€™un nombre compris entre %(min)s et %(max)s", + "Size can only be a number between %(min)s MB and %(max)s MB": "La taille ne peut รชtre qu'un nombre compris entre %(min)s Mo et %(max)s Mo", + "Enter a number between %(min)s and %(max)s": "Entrez un nombre entre %(min)s et %(max)s", + "In reply to this message": "En rรฉponse ร  ce message", + "Export chat": "Exporter la conversation privรฉe", + "File Attached": "Fichier attachรฉ", + "Error fetching file": "Erreur lors de la rรฉcupรฉration du fichier", + "Topic: %(topic)s": "Sujetย : %(topic)s", + "This is the start of export of . Exported by at %(exportDate)s.": "Cโ€™est le dรฉbut de lโ€™export de . Exportรฉ par le %(exportDate)s.", + "%(creatorName)s created this room.": "%(creatorName)s a crรฉรฉ ce salon.", + "Media omitted - file size limit exceeded": "Mรฉdia ignorรฉ โ€“ taille limite de fichier dรฉpassรฉe", + "Media omitted": "Mรฉdia ignorรฉs", + "Current Timeline": "Historique actuel", + "Specify a number of messages": "Spรฉcifiez un nombre de messages", + "From the beginning": "Depuis le dรฉbut", + "Plain Text": "Text brut", + "JSON": "JSON", + "HTML": "HTML", + "Are you sure you want to exit during this export?": "รŠtes vous sรปr de vouloir quitter pendant cet exportย ?", + "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s a envoyรฉ un autocollant.", + "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s a changรฉ lโ€™avatar du salon.", + "%(date)s at %(time)s": "%(date)s ร  %(time)s", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "La rรฉinitialisation de vos clรฉs de vรฉrification ne peut pas รชtre annulรฉ. Aprรจs la rรฉinitialisation, vous nโ€™aurez plus accรจs ร  vos anciens messages chiffrรฉs, et tous les amis que vous aviez prรฉcรฉdemment vรฉrifiรฉs verront des avertissement de sรฉcuritรฉ jusqu'ร  ce vous les vรฉrifiiez ร  nouveau.", + "Downloading": "Tรฉlรฉchargement en cours", + "Polls (under active development)": "Sondages (en cours de dรฉveloppement)", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Veuillez ne continuer que si vous รชtes certain dโ€™avoir perdu tous vos autres appareils et votre clรฉ de sรฉcuritรฉ.", + "I'll verify later": "Je ferai la vรฉrification plus tard", + "Verify with another login": "Vรฉrifier avec une autre connexion", + "Verify with Security Key": "Vรฉrifiรฉ avec une clรฉ de sรฉcuritรฉ", + "Verify with Security Key or Phrase": "Vรฉrifier avec une clรฉ de sรฉcuritรฉ ou une phrase", + "Proceed with reset": "Faire la rรฉinitialisation", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Il semblerait que vous nโ€™avez pas de clรฉ de sรฉcuritรฉ ou dโ€™autres appareils pour faire la vรฉrification. Cet appareil ne pourra pas accรฉder aux anciens messages chiffrรฉs. Afin de vรฉrifier votre identitรฉ sur cet appareil, vous devrez rรฉinitialiser vos clรฉs de vรฉrifications.", + "Skip verification for now": "Ignorer la vรฉrification pour lโ€™instant", + "Really reset verification keys?": "Rรฉinitialiser les clรฉs de vรฉrification, cโ€™est certain ?", + "Unable to verify this login": "Impossible de vรฉrifier cette connexion", + "Show:": "Afficheย :", + "Shows all threads from current room": "Affiche tous les fils de discussion du salon actuel", + "All threads": "Tous les fils de discussion", + "Shows all threads youโ€™ve participated in": "Affiche tous les fils de discussion auxquels vous avez participรฉ", + "My threads": "Mes fils de discussion", + "Creating Space...": "Crรฉation de lโ€™espaceโ€ฆ", + "Fetching data...": "Rรฉcupรฉration des donnรฉesโ€ฆ", + "They won't be able to access whatever you're not an admin of.": "Ils ne pourront plus accรฉder aux endroits dans lesquels vous nโ€™รชtes pas administrateur.", + "Ban them from specific things I'm able to": "Les bannir de certains endroits oรน jโ€™ai le droit de le faire", + "Unban them from specific things I'm able to": "Annuler le bannissement de certains endroits oรน jโ€™ai le droit de le faire", + "Ban them from everything I'm able to": "Les bannir de partout oรน jโ€™ai le droit de le faire", + "Unban them from everything I'm able to": "Annuler le bannissement de partout oรน jโ€™ai le droit de le faire", + "Ban from %(roomName)s": "Bannir de %(roomName)s", + "Unban from %(roomName)s": "Annuler le bannissement de %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "Ils pourront toujours accรฉder aux endroits dans lesquels vous nโ€™รชtes pas administrateur.", + "Kick them from specific things I'm able to": "Les expulser de certains endroits oรน jโ€™ai le droit de le faire", + "Kick them from everything I'm able to": "Les expulser de partout oรน jโ€™ai le droit de le faire", + "Kick from %(roomName)s": "Expulser de %(roomName)s", + "Disinvite from %(roomName)s": "Annuler lโ€™invitation ร  %(roomName)s", + "Threads": "Fils", + "To proceed, please accept the verification request on your other login.": "Pour continuer, veuillez accepter la demande de vรฉrification sur votre autre connexion.", + "Create poll": "Crรฉer un sondage", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Mise-ร -jour de lโ€™espaceโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Mise-ร -jour des espacesโ€ฆ (%(progress)s sur %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Envoi de lโ€™invitationโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "Envoi des invitationsโ€ฆ (%(progress)s sur %(count)s)", + "Loading new room": "Chargement du nouveau salon", + "Upgrading room": "Mise-ร -jour du salon", + "Waiting for you to verify on your other sessionโ€ฆ": "En attente de votre vรฉrification sur votre autre sessionโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "En attente de votre vรฉrification sur votre autre session, %(deviceName)s (%(deviceId)s)โ€ฆ" } diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 63fc7d4804b..93a0e5d1bfc 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3166,5 +3166,87 @@ "%(reactors)s reacted with %(content)s": "%(reactors)s reaccionou con %(content)s", "Joining space โ€ฆ": "Unรญndote ao espazoโ€ฆ", "Expand quotes โ”‚ โ‡ง+click": "Despregar citas | โ‡ง+click", - "Collapse quotes โ”‚ โ‡ง+click": "Pechar citas | โ‡ง+click" + "Collapse quotes โ”‚ โ‡ง+click": "Pechar citas | โ‡ง+click", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Continรบa sรณ se estas segura de que perdeches a tรบa chave de seguridade e o acceso noutros dispositivos.", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "O restablecemento das chaves de seguridade non se pode desfacer. Tras o restablecemento, non terรกs acceso รกs antigas mensaxes cifradas, e calquera amizade que verificaras con anterioridade vai ver un aviso de seguridade ata que volvades a verificarvos mutuamente.", + "I'll verify later": "Verificarei mรกis tarde", + "Verify with another login": "Verificar con outra conexiรณn", + "Verify with Security Key": "Verificar coa Chave de Seguridade", + "Verify with Security Key or Phrase": "Verificar coa Chave ou Frase de Seguridade", + "Proceed with reset": "Procede co restablecemento", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Semella que non tes unha Chave de Seguridade ou outros dispositivos cos que verificar. Este dispositivo non poderรก acceder a mensaxes antigas cifradas. Para poder verificar a tรบa identidade neste dispositivo tes que restablecer as chaves de verificaciรณn.", + "Skip verification for now": "Omitir a verificaciรณn por agora", + "Really reset verification keys?": "Queres restablecer as chaves de verificaciรณn?", + "Unable to verify this login": "Non se puido verificar esta conexiรณn", + "Include Attachments": "Incluรญr anexos", + "Size Limit": "Lรญmite do tamaรฑo", + "Format": "Formato", + "Select from the options below to export chats from your timeline": "Elixe entre as opciรณns seguintes para exportar a tรบa cronoloxรญa", + "Export Chat": "Exportar chat", + "Exporting your data": "Exportando os teus datos", + "Stop": "Deter", + "Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "Tes a certeza de querer deter a exportaciรณn de datos? Se รฉ asรญ, terรกs que volver a iniciala.", + "Your export was successful. Find it in your Downloads folder.": "A exportaciรณn foi correcta. Atoparala no cartafol de Descargas.", + "The export was cancelled successfully": "Cancelouse con รฉxito a exportaciรณn", + "Export Successful": "Exportaciรณn correcta", + "MB": "MB", + "Number of messages": "Nรบmero de mensaxes", + "Number of messages can only be a number between %(min)s and %(max)s": "O nรบmero de mensaxes sรณ pode ser un nรบmero entre %(min)s e %(max)s", + "Size can only be a number between %(min)s MB and %(max)s MB": "O tamaรฑo sรณ pode ser un nรบmero entre %(min)s MB e %(max)s MB", + "Enter a number between %(min)s and %(max)s": "Escribe un nรบmero entre %(min)s e %(max)s", + "Creating Space...": "Creando Espazo...", + "Fetching data...": "Obtendo datos...", + "In reply to this message": "En resposta a esta mensaxe", + "Export chat": "Exportar chat", + "To proceed, please accept the verification request on your other login.": "Para continuar, acepta a solicitude de verificaciรณn na tรบa outra conexiรณn.", + "Waiting for you to verify on your other sessionโ€ฆ": "Agardando a que verifiques na tรบa outra sesiรณnโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Agardando a que verifiques na tรบa outra sesiรณn, %(deviceName)s %(deviceId)sโ€ฆ", + "File Attached": "Ficheiro anexado", + "Error fetching file": "Erro ao obter o ficheiro", + "Topic: %(topic)s": "Asunto: %(topic)s", + "This is the start of export of . Exported by at %(exportDate)s.": "Este รฉ o inicio da exportaciรณn de . Exportada por o %(exportDate)s.", + "%(creatorName)s created this room.": "%(creatorName)s creou esta sala.", + "Media omitted - file size limit exceeded": "Multimedia omitido - excedeuse o lรญmite de tamaรฑo", + "Media omitted": "Omitir multimedia", + "Current Timeline": "Cronoloxรญa actual", + "Specify a number of messages": "Indica un nรบmero de mensaxes", + "From the beginning": "Desde o comezo", + "Plain Text": "Texto plano", + "JSON": "JSON", + "HTML": "HTML", + "Are you sure you want to exit during this export?": "Tes a certeza de querer saรญr durante esta exportaciรณn?", + "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s enviou un adhesivo.", + "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s cambiou o avatar da sala.", + "%(date)s at %(time)s": "%(date)s รกs %(time)s", + "Show:": "Mostrar:", + "Shows all threads from current room": "Mostra tรณdalas conversas da sala actual", + "All threads": "Tรณdalas conversas", + "Shows all threads youโ€™ve participated in": "Mostra tรณdalas conversas nas que participaches", + "My threads": "As miรฑas conversas", + "Downloading": "Descargando", + "They won't be able to access whatever you're not an admin of.": "Non poderรกn acceder a calquera lugar no que non sexas administradora.", + "Ban them from specific things I'm able to": "Vetalos en lugares especรญficos nos que eu poida", + "Unban them from specific things I'm able to": "Retirar o veto de lugares especรญficos que eu poida", + "Ban them from everything I'm able to": "Vetalos en tรณdolos sitios nos que eu poida", + "Unban them from everything I'm able to": "Devolverlle o acceso a tรณdolos sitios onde eu poida", + "Ban from %(roomName)s": "Vetar en %(roomName)s", + "Unban from %(roomName)s": "Permitir acceso a %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "Poderรกn seguir accedendo a sitios onde ti non es administradora.", + "Kick them from specific things I'm able to": "Expulsalos de sitios especรญficos que eu poida", + "Kick them from everything I'm able to": "Expulsalos de todos os lugares que eu poida", + "Kick from %(roomName)s": "Expulsar de %(roomName)s", + "Disinvite from %(roomName)s": "Retirar o convite para %(roomName)s", + "Threads": "Conversas", + "Create poll": "Crear enquisa", + "%(count)s reply|one": "%(count)s resposta", + "%(count)s reply|other": "%(count)s respostas", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Actualizando espazo...", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Actualizando espazos... (%(progress)s de %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Enviando convite...", + "Sending invites... (%(progress)s out of %(count)s)|other": "Enviando convites... (%(progress)s de %(count)s)", + "Loading new room": "Cargando nova sala", + "Upgrading room": "Actualizando sala", + "Polls (under active development)": "Enquisas (en desenvolvemento activo)", + "View in room": "Ver na sala", + "Enter your Security Phrase or to continue.": "Escribe a tรบa Frase de Seguridade ou para continuar." } diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index c561759db9f..9468a86516b 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3194,5 +3194,60 @@ "Are you sure you want to exit during this export?": "Biztos, hogy kilรฉp az exportรกlรกs kรถzben?", "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s matricรกt kรผldรถtt.", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s megvรกltoztatta a szoba avatar kรฉpรฉt.", - "%(date)s at %(time)s": "%(date)s %(time)s" + "%(date)s at %(time)s": "%(date)s %(time)s", + "I'll verify later": "Kรฉsล‘bb ellenล‘rzรถm", + "Verify with another login": "Ellenล‘rizze egy mรกsik bejelentkezรฉssel", + "Proceed with reset": "Lecserรฉlรฉs folytatรกsa", + "Skip verification for now": "Ellenล‘rzรฉs kihagyรกsa most", + "Really reset verification keys?": "Biztosan lecserรฉli az ellenล‘rzรฉsi kulcsokat?", + "Unable to verify this login": "Ennek a bejelentkezรฉsnek az ellenล‘rzรฉse nem lehetsรฉges", + "Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "Biztos, hogy megรกllรญtja az adatai kimentรฉsรฉt? Ha igen, kรฉsล‘bb รบjra elล‘rรถl kell kezdeni.", + "Your export was successful. Find it in your Downloads folder.": "Az exportรกlรกs sikeres volt. Megtalรกlja a Letรถltรฉsek kรถnyvtรกrban.", + "Number of messages can only be a number between %(min)s and %(max)s": "Az รผzenetek szรกma csak %(min)s รฉs %(max)s kรถzรถtti szรกm lehet", + "Size can only be a number between %(min)s MB and %(max)s MB": "A mรฉret csak %(min)s MB รฉs %(max)s MB kรถzรถtti szรกm lehet", + "Enter a number between %(min)s and %(max)s": "Adjon meg egy szรกmot %(min)s รฉs %(max)s kรถzรถtt", + "Creating Space...": "Tรฉr kรฉszรญtรฉseโ€ฆ", + "Fetching data...": "Adatok letรถltรฉseโ€ฆ", + "To proceed, please accept the verification request on your other login.": "A folytatรกshoz fogadja el az ellenล‘rzรฉs kรฉrรฉst a mรกsik munkamenetben.", + "Waiting for you to verify on your other sessionโ€ฆ": "A megerล‘sรญtรฉst vรกrjuk a mรกsik munkamenetbล‘lโ€ฆ", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Csak akkor folytassa ha biztos benne, hogy elvesztett minden hozzรกfรฉrรฉst a tรถbbi eszkรถzรฉhez รฉs biztonsรกgi kulcsรกhoz.", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "Az ellenล‘rzรฉshez hasznรกlt kulcsok alaphelyzetbe รกllรญtรกsรกt nem lehet visszavonni. A visszaรกllรญtรกs utรกn nem fog hozzรกfรฉrni a rรฉgi titkosรญtott รผzenetekhez รฉs minden ismerล‘se aki eddig ellenล‘rizte a szemรฉlyazonossรกgรกt biztonsรกgi figyelmeztetรฉst fog lรกtni amรญg รบjra nem ellenล‘rzi.", + "Verify with Security Key": "Ellenล‘rzรฉs Biztonsรกgi Kulccsal", + "Verify with Security Key or Phrase": "Ellenล‘rzรฉs Biztonsรกgi Kulccsal vagy Jelmondattal", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "รšgy tลฑnik nem rendelkezik Biztonsรกgi Kulccsal vagy mรกsik eszkรถzzel amivel ellenล‘rizni lehetne. Ezzel az eszkรถzzel nem fรฉr majd hozzรก a rรฉgi titkosรญtott รผzenetekhez. Ahhoz, hogy a szemรฉlyazonossรกgรกt ezen az eszkรถzรถn ellenล‘rizni lehessen a kulcsokat alaphelyzetbe kell รกllรญtani.", + "Select from the options below to export chats from your timeline": "Az idล‘vonalon a beszรฉlgetรฉs kimentรฉsรฉhez tartozรณ beรกllรญtรกsok kivรกlasztรกsa", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Vรกrakozรกs a mรกsik munkamenetbล‘l valรณ ellenล‘rzรฉsre, %(deviceName)s (%(deviceId)s)โ€ฆ", + "This is the start of export of . Exported by at %(exportDate)s.": "Ez az exportรกlรกs kezdete ehhez a szobรกhoz: . Kimentette: , idล‘pont: %(exportDate)s.", + "Create poll": "Szavazรกs lรฉtrehozรกsa", + "Polls (under active development)": "Szavazรกsok (intenzรญv fejlesztรฉs alatt)", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Terek frissรญtรฉseโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Terek frissรญtรฉseโ€ฆ (%(progress)s / %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Meghรญvรณk kรผldรฉseโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "Meghรญvรณk kรผldรฉseโ€ฆ (%(progress)s / %(count)s)", + "Loading new room": "รšj szoba betรถltรฉse", + "Upgrading room": "Szoba fejlesztรฉse", + "Show:": "Mutat:", + "Shows all threads from current room": "Minden รผzenetszรกl megjelenรญtรฉse a szobรกbรณl", + "All threads": "Minden รผzenetszรกl", + "Shows all threads youโ€™ve participated in": "Minden รผzenetszรกl megjelenรญtรฉse ahol szerepel", + "My threads": "รœzenetszรกlak", + "Downloading": "Letรถltรฉs", + "They won't be able to access whatever you're not an admin of.": "Kรฉsล‘bb nem fรฉrhetnek hozzรก olyan helyekhez ahol รถn nem adminisztrรกtor.", + "Ban them from specific things I'm able to": "Kitiltani ล‘ket bizonyos helyekrล‘l ahonnan joga van hozzรก", + "Unban them from specific things I'm able to": "Kitiltรกsuk visszavonรกsa bizonyos helyekrล‘l ahol joga van hozzรก", + "Ban them from everything I'm able to": "Kitiltani ล‘ket mindenhonnan ahonnan joga van hozzรก", + "Unban them from everything I'm able to": "Kitiltรกsuk visszavonรกsa mindenhonnan ahol joga van hozzรก", + "Ban from %(roomName)s": "Kitiltรกs innen: %(roomName)s", + "Unban from %(roomName)s": "Kitiltรกs visszavonรกsa innen: %(roomName)s", + "Kick from %(roomName)s": "Kirรบg innen: %(roomName)s", + "Disinvite from %(roomName)s": "Meghรญvรณ visszavonรกsa innen: %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "Tovรกbbra is hozzรกfรฉrhetnek olyan helyekhez ahol รถn nem adminisztrรกtor.", + "Kick them from specific things I'm able to": "Kirรบgni ล‘ket bizonyos helyekrล‘l ahonnan joga van hozzรก", + "Kick them from everything I'm able to": "Kirรบgni ล‘ket mindenhonnan ahonnan joga van hozzรก", + "Threads": "รœzenetszรกlak", + "%(count)s reply|one": "%(count)s vรกlasz", + "%(count)s reply|other": "%(count)s vรกlasz", + "View in room": "Megjelenรญtรฉs szobรกban", + "Enter your Security Phrase or to continue.": "Add meg a Biztonsรกgi jelmondatot vagy a folytatรกshoz.", + "What projects are your team working on?": "Milyen projekteken dolgozik a csoportja?" } diff --git a/src/i18n/strings/id.json b/src/i18n/strings/id.json index c96a10ea2a5..a289d2a351d 100644 --- a/src/i18n/strings/id.json +++ b/src/i18n/strings/id.json @@ -1,21 +1,21 @@ { "Accept": "Terima", "Account": "Akun", - "Add": "Tambah", + "Add": "Tambahkan", "No Microphones detected": "Tidak ada mikrofon terdeteksi", "No media permissions": "Tidak ada izin media", "Microphone": "Mikrofon", "Camera": "Kamera", - "Are you sure?": "Anda yakin?", + "Are you sure?": "Apakah Anda yakin?", "An error has occurred.": "Telah terjadi kesalahan.", "Are you sure you want to reject the invitation?": "Anda yakin menolak undangannya?", - "Change Password": "Ubah Password", + "Change Password": "Ubah Kata Sandi", "Close": "Tutup", "Commands": "Perintah", - "Confirm password": "Konfirmasi password", + "Confirm password": "Konfirmasi kata sandi", "Continue": "Lanjut", - "Create Room": "Buat Ruang", - "Current password": "Password sekarang", + "Create Room": "Buat Ruangan", + "Current password": "Kata sandi sekarang", "Deactivate Account": "Nonaktifkan Akun", "Email": "Email", "Email address": "Alamat email", @@ -25,54 +25,54 @@ "Default": "Bawaan", "Download %(text)s": "Unduh %(text)s", "Export": "Ekspor", - "Failed to join room": "Gagal gabung ruang", + "Failed to join room": "Gagal saat bergabung ruangan", "Failed to reject invitation": "Gagal menolak undangan", "Failed to send email": "Gagal mengirim email", "Favourite": "Favorit", "Favourites": "Favorit", "Import": "Impor", "Incorrect verification code": "Kode verifikasi tidak benar", - "Invalid Email Address": "Alamat email tidak benar", + "Invalid Email Address": "Alamat Email Tidak Valid", "Invited": "Diundang", "Sign in with": "Masuk dengan", - "Leave room": "Meninggalkan ruang", + "Leave room": "Tinggalkan ruangan", "Logout": "Keluar", "Low priority": "Prioritas rendah", - "Mute": "Bisu", + "Mute": "Bisukan", "Name": "Nama", - "New passwords don't match": "Password baru tidak cocok", + "New passwords don't match": "Kata sandi baru tidak cocok", "": "", "No results": "Tidak ada hasil", "OK": "OK", "Operation failed": "Operasi gagal", - "Passwords can't be empty": "Password tidak boleh kosong", + "Passwords can't be empty": "Kata sandi tidak boleh kosong", "Permissions": "Izin", "Profile": "Profil", "Reason": "Alasan", - "Register": "Registrasi", - "%(brand)s version:": "%(brand)s versi:", + "Register": "Daftar", + "%(brand)s version:": "Versi %(brand)s:", "Return to login screen": "Kembali ke halaman masuk", - "Rooms": "Ruang", + "Rooms": "Ruangan", "Save": "Simpan", "Search": "Cari", "Search failed": "Pencarian gagal", "Send Reset Email": "Kirim Email Atur Ulang", - "Server error": "Server bermasalah", + "Server error": "Kesalahan server", "Session ID": "ID Sesi", "Settings": "Pengaturan", "Sign in": "Masuk", "Sign out": "Keluar", "Someone": "Seseorang", "Submit": "Kirim", - "Success": "Sukses", - "This email address was not found": "Alamat email ini tidak ada", - "This room": "Ruang ini", + "Success": "Berhasil", + "This email address was not found": "Alamat email ini tidak ditemukan", + "This room": "Ruangan ini", "Unable to add email address": "Tidak dapat menambahkan alamat email", "Unable to verify email address.": "Tidak dapat memverifikasi alamat email.", "unknown error code": "kode kesalahan tidak diketahui", - "Verification Pending": "Verifikasi Tertunda", - "Video call": "Panggilan Video", - "Voice call": "Panggilan Suara", + "Verification Pending": "Verifikasi Menunggu", + "Video call": "Panggilan video", + "Voice call": "Panggilan suara", "Warning!": "Peringatan!", "You cannot place a call with yourself.": "Anda tidak dapat melakukan panggilan dengan diri sendiri.", "Sun": "Min", @@ -97,111 +97,111 @@ "Admin": "Admin", "Admin Tools": "Peralatan Admin", "No Webcams detected": "Tidak ada Webcam terdeteksi", - "You may need to manually permit %(brand)s to access your microphone/webcam": "Anda mungkin perlu secara manual mengizinkan %(brand)s untuk mengakses mikrofon/webcam", + "You may need to manually permit %(brand)s to access your microphone/webcam": "Anda mungkin perlu mengizinkan %(brand)s secara manual untuk mengakses mikrofon/webcam", "Default Device": "Perangkat Bawaan", "Advanced": "Tingkat Lanjut", - "Always show message timestamps": "Selalu tampilkan cap waktu dari pesan", - "Authentication": "Autentikasi", - "Are you sure you want to leave the room '%(roomName)s'?": "Anda yakin ingin meninggalkan ruang '%(roomName)s'?", - "A new password must be entered.": "Password baru harus diisi.", + "Always show message timestamps": "Selalu tampilkan stempel waktu pesan", + "Authentication": "Otentikasi", + "Are you sure you want to leave the room '%(roomName)s'?": "Anda yakin ingin meninggalkan ruangan '%(roomName)s'?", + "A new password must be entered.": "Kata sandi baru harus dimasukkan.", "%(items)s and %(lastItem)s": "%(items)s dan %(lastItem)s", - "Banned users": "Pengguna yang diblokir", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Tidak dapat terhubung ke server Home - harap cek koneksi anda, pastikan sertifikat SSL server Home Anda terpercaya, dan ekstensi dari browser tidak memblokir permintaan.", - "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Tidak dapat terhubung ke server Home melalui HTTP ketika URL di browser berupa HTTPS. Pilih gunakan HTTPS atau aktifkan skrip yang tidak aman.", + "Banned users": "Pengguna yang dicekal", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Tidak dapat terhubung ke homeserver - harap cek koneksi anda, pastikan sertifikat SSL homeserver Anda terpercaya, dan ekstensi browser tidak memblokir permintaan.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Tidak dapat terhubung ke homeserver melalui HTTP ketika URL di browser berupa HTTPS. Gunakan HTTPS atau aktifkan skrip yang tidak aman.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s telah mengubah tingkat kekuatan dari %(powerLevelDiffText)s.", - "Changes your display nickname": "Ubah tampilan nama panggilan Anda", - "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s telah menghapus nama ruang.", + "Changes your display nickname": "Ubah tampilan nama tampilan Anda", + "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s telah menghapus nama ruangan.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s telah mengubah nama ruang menjadi %(roomName)s.", "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s telah mengubah topik menjadi \"%(topic)s\".", "Cryptography": "Kriptografi", - "Decrypt %(text)s": "Dekrip %(text)s", - "Ban": "Blokir", - "Bans user with given id": "Blokir pengguna dengan id", + "Decrypt %(text)s": "Dekripsi %(text)s", + "Ban": "Cekal", + "Bans user with given id": "Blokir pengguna dengan id yang dicantumkan", "Fetching third party location failed": "Gagal mengambil lokasi pihak ketiga", "Sunday": "Minggu", - "Guests can join": "Tamu dapat gabung", + "Guests can join": "Tamu dapat bergabung", "Messages sent by bot": "Pesan dikirim oleh bot", "Notification targets": "Target notifikasi", - "Failed to set direct chat tag": "Gagal mengatur tag obrolan langsung", + "Failed to set direct chat tag": "Gagal mengatur tanda pesan langsung", "Today": "Hari Ini", "Dismiss": "Abaikan", "Update": "Perbarui", "Notifications": "Notifikasi", "What's New": "Apa Yang Baru", "On": "Nyala", - "Changelog": "Daftar perubahan", + "Changelog": "Changelog", "Waiting for response from server": "Menunggu respon dari server", "Leave": "Tinggalkan", - "World readable": "Terpublikasi Umum", + "World readable": "Bisa dibaca oleh semua", "Warning": "Peringatan", - "This Room": "Ruang ini", + "This Room": "Ruangan ini", "Noisy": "Berisik", - "Messages containing my display name": "Pesan mengandung nama tampilan saya", + "Messages containing my display name": "Pesan yang berisi nama tampilan saya", "Messages in one-to-one chats": "Pesan di obrolan satu-ke-satu", "Unavailable": "Tidak Tersedia", - "remove %(name)s from the directory.": "hapus %(name)s dari direktori.", + "remove %(name)s from the directory.": "hapus %(name)s dari direktorinya.", "powered by Matrix": "didukung oleh Matrix", - "All Rooms": "Semua Ruang", - "Source URL": "URL sumber", - "Failed to add tag %(tagName)s to room": "Gagal menambahkan tag %(tagName)s ke ruang", + "All Rooms": "Semua Ruangan", + "Source URL": "URL Sumber", + "Failed to add tag %(tagName)s to room": "Gagal menambahkan tag %(tagName)s ke ruangan", "Members": "Anggota", - "No update available.": "Tidak ada pembaruan.", + "No update available.": "Tidak ada pembaruan yang tersedia.", "Resend": "Kirim Ulang", - "Collecting app version information": "Mengumpukan informasi versi aplikasi", + "Collecting app version information": "Mengumpulkan informasi versi aplikasi", "Room not found": "Ruang tidak ditemukan", "Tuesday": "Selasa", "Searchโ€ฆ": "Cariโ€ฆ", - "Remove %(name)s from the directory?": "Hapus %(name)s dari direktori?", + "Remove %(name)s from the directory?": "Hapus %(name)s dari direktorinya?", "Unnamed room": "Ruang tanpa nama", "Friday": "Jumat", "Saturday": "Sabtu", "The server may be unavailable or overloaded": "Server mungkin tidak tersedia atau kelebihan muatan", "Reject": "Tolak", "Monday": "Senin", - "Remove from Directory": "Hapus dari DIrektori", - "Collecting logs": "Mengumpulkan catatan", - "Failed to forget room %(errCode)s": "Gagal melupakan ruang %(errCode)s", + "Remove from Directory": "Hapus dari Direktori", + "Collecting logs": "Mengumpulkan log", + "Failed to forget room %(errCode)s": "Gagal melupakan ruangan %(errCode)s", "Wednesday": "Rabu", "You cannot delete this message. (%(code)s)": "Anda tidak dapat menghapus pesan ini. (%(code)s)", "Quote": "Kutip", "Send": "Kirim", - "Error": "Terjadi Kesalahan", - "Send logs": "Kirim catatan", + "Error": "Kesalahan", + "Send logs": "Kirim log", "All messages": "Semua pesan", "Call invitation": "Undangan panggilan", - "Downloading update...": "Unduh pembaruan...", + "Downloading update...": "Mengunduh pembaruan...", "What's new?": "Apa yang baru?", - "When I'm invited to a room": "Ketika Saya diundang ke ruang", + "When I'm invited to a room": "Ketika saya diundang ke ruangan", "Cancel": "Batal", "Unable to look up room ID from server": "Tidak dapat mencari ID ruang dari server", "Couldn't find a matching Matrix room": "Tidak dapat menemukan ruang Matrix yang sesuai", - "Invite to this room": "Undang ke ruang ini", + "Invite to this room": "Undang ke ruangan ini", "Thursday": "Kamis", "Back": "Kembali", - "Show message in desktop notification": "Tampilkan pesan pada desktop", - "Unable to join network": "Tidak dapat bergabung di jaringan", + "Show message in desktop notification": "Tampilkan pesan di notifikasi desktop", + "Unable to join network": "Tidak dapat bergabung ke jaringan", "Messages in group chats": "Pesan di obrolan grup", "Yesterday": "Kemarin", "Error encountered (%(errorDetail)s).": "Terjadi kesalahan (%(errorDetail)s).", "Low Priority": "Prioritas Rendah", "Off": "Mati", - "%(brand)s does not know how to join a room on this network": "%(brand)s tidak tau bagaimana gabung ruang di jaringan ini", - "Failed to remove tag %(tagName)s from room": "Gagal menghapus tag %(tagName)s dari ruang", + "%(brand)s does not know how to join a room on this network": "%(brand)s tidak tahu bagaimana untuk gabung ruang di jaringan ini", + "Failed to remove tag %(tagName)s from room": "Gagal menghapus tanda %(tagName)s dari ruangan", "Remove": "Hapus", - "No rooms to show": "Tidak ada ruang ditunjukkan", - "Failed to change password. Is your password correct?": "Gagal untuk mengubah password. Apakah password Anda benar?", + "No rooms to show": "Tidak ada ruang yang ditampilkan", + "Failed to change password. Is your password correct?": "Gagal untuk mengubah kata sandi. Apakah kata sandi Anda benar?", "View Source": "Tampilkan Sumber", "Thank you!": "Terima kasih!", - "Checking for an update...": "Cek pembaruan...", + "Checking for an update...": "Mengecek untuk pembaruan...", "This email address is already in use": "Alamat email ini telah terpakai", "This phone number is already in use": "Nomor telepon ini telah terpakai", "Failed to verify email address: make sure you clicked the link in the email": "Gagal memverifikasi alamat email: pastikan Anda telah menekan link di dalam email", "The version of %(brand)s": "Versi %(brand)s", - "Your language of choice": "Pilihan bahasamu", - "Your homeserver's URL": "URL Homeserver Anda", + "Your language of choice": "Pilihan bahasa Anda", + "Your homeserver's URL": "URL homeserver Anda", "e.g. %(exampleValue)s": "mis. %(exampleValue)s", "Every page you use in the app": "Setiap halaman yang digunakan di app", - "e.g. ": "e.g. ", + "e.g. ": "e.g. ", "Your device resolution": "Resolusi perangkat Anda", "Analytics": "Analitik", "The information being sent to us to help make %(brand)s better includes:": "Informasi yang dikirim membantu kami memperbaiki %(brand)s, termasuk:", @@ -209,10 +209,461 @@ "Call Failed": "Panggilan Gagal", "VoIP is unsupported": "VoIP tidak didukung", "You cannot place VoIP calls in this browser.": "Anda tidak dapat melakukan panggilan VoIP di browser ini.", - "Permission Required": "Permisi Dibutuhkan", - "You do not have permission to start a conference call in this room": "Anda tidak memiliki permisi untuk memulai panggilan massal di ruang ini", - "Explore rooms": "Jelajahi ruang", + "Permission Required": "Izin Dibutuhkan", + "You do not have permission to start a conference call in this room": "Anda tidak memiliki permisi untuk memulai panggilan konferensi di ruang ini", + "Explore rooms": "Jelajahi ruangan", "Sign In": "Masuk", "Create Account": "Buat Akun", - "Identity server": "Server identitas" + "Identity server": "Server identitas", + "%(senderName)s banned %(targetName)s": "%(senderName)s mencekal %(targetName)s", + "Prepends โ”ฌโ”€โ”€โ”ฌ ใƒŽ( ใ‚œ-ใ‚œใƒŽ) to a plain-text message": "Menambahkan โ”ฌโ”€โ”€โ”ฌ ใƒŽ( ใ‚œ-ใ‚œใƒŽ) ke pesan teks biasa", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s", + "The call was answered on another device.": "Panggilan dijawab di perangkat lainnya.", + "%(senderName)s unbanned %(targetName)s": "%(senderName)s menghilangkan cekalan %(targetName)s", + "%(targetName)s left the room": "%(targetName)s keluar dari ruangan ini", + "%(targetName)s left the room: %(reason)s": "%(targetName)s keluar dari ruangan ini: %(reason)s", + "%(targetName)s rejected the invitation": "%(targetName)s menolak undangannya", + "%(targetName)s joined the room": "%(targetName)s bergabung ke ruangan ini", + "%(senderName)s made no change": "%(senderName)s tidak membuat perubahan", + "%(senderName)s set a profile picture": "%(senderName)s mengatur foto profil", + "%(senderName)s changed their profile picture": "%(senderName)s mengubah foto profilnya", + "%(senderName)s removed their profile picture": "%(senderName)s menghilangkan foto profilnya", + "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s menghilangkan nama tampilannya (%(oldDisplayName)s)", + "%(senderName)s set their display name to %(displayName)s": "%(senderName)s mengatur nama tampilannya ke %(displayName)s", + "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s mengubah nama tampilannya ke %(displayName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s mengundang %(targetName)s", + "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s mencekal %(targetName)s: %(reason)s", + "%(targetName)s accepted an invitation": "%(targetName)s menerima sebuah undangan", + "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s menerima undangan %(displayName)s", + "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s melakukan panggilan video. (tidak didukung oleh browser ini)", + "%(senderName)s placed a video call.": "%(senderName)s melakukan panggilan video.", + "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s melakukan panggilan suara. (tidak didukung oleh browser ini)", + "%(senderName)s placed a voice call.": "%(senderName)s melakukan panggilan video.", + "Displays action": "Menampilkan aksi", + "Converts the DM to a room": "Mengubah pesan langsung ke ruangan", + "Converts the room to a DM": "Mengubah ruangan ini ke pesan langsung", + "Takes the call in the current room off hold": "Melanjutkan panggilan di ruang saat ini", + "Places the call in the current room on hold": "Menunda panggilan di ruangan saat ini", + "Sends a message to the given user": "Mengirim sebuah pesan ke pengguna yang dicantumkan", + "Opens chat with the given user": "Membuat chat dengan pengguna yang dicantumkan", + "Send a bug report with logs": "Mengirim laporan bug dengan log", + "Displays information about a user": "Menampilkan informasi tentang sebuah pengguna", + "Displays list of commands with usages and descriptions": "Menampilkan daftar perintah dengan penggunaan dan deskripsi", + "Sends the given emote coloured as a rainbow": "Mengirim emote dengan warna pelangi", + "Sends the given message coloured as a rainbow": "Mengirim pesan dengan warna pelangi", + "Forces the current outbound group session in an encrypted room to be discarded": "Memaksa sesi grup keluar saat ini di ruang terenkripsi untuk dibuang", + "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "Kunci penandatanganan yang Anda sediakan cocok dengan kunci penandatanganan yang Anda terima dari sesi %(userId)s %(deviceId)s. Sesi ditandai sebagai terverifikasi.", + "Verified key": "Kunci terverifikasi", + "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "PERINGATAN: VERIFIKASI KUNCI GAGAL! Kunci penandatanganan untuk %(userId)s dan sesi %(deviceId)s adalah \"%(fprint)s\" yang tidak cocok dengan kunci \"%(fingerprint)s\" yang disediakan. Ini bisa saja berarti komunikasi Anda sedang disadap!", + "WARNING: Session already verified, but keys do NOT MATCH!": "PERINGATAN: Sesi telah diverifikasi, tetapi kuncinya TIDAK COCOK!", + "Session already verified!": "Sesi telah diverifikasi!", + "Unknown (user, session) pair:": "Tidak diketahui (pengguna, sesi) pasangan:", + "Verifies a user, session, and pubkey tuple": "Memverifikasi sebuah pengguna, sesi, dan tupel pubkey", + "You cannot modify widgets in this room.": "Anda tidak dapat mengubah widget di ruangan ini.", + "Please supply a https:// or http:// widget URL": "Mohon masukkan sebuah URL widget https:// atau http://", + "Please supply a widget URL or embed code": "Silakan masukkan URL widget atau kode embed", + "Adds a custom widget by URL to the room": "Menambahkan widget kustom dari URL ke ruangan ini", + "Opens the Developer Tools dialog": "Membuka dialog Peralatan Pengembang", + "Deops user with given id": "De-op pengguna dengan ID yang dicantumkan", + "Could not find user in room": "Tidak dapat menemukan pengguna di ruangan", + "Command failed": "Perintah gagal", + "Define the power level of a user": "Tentukan tingkat daya pengguna", + "You are no longer ignoring %(userId)s": "Anda sekarang berhenti mengabaikan %(userId)s", + "Unignored user": "Pengguna yang berhenti diabaikan", + "Ignores a user, hiding their messages from you": "Mengabaikan pengguna, dan sembunyikan pesan mereka", + "Stops ignoring a user, showing their messages going forward": "Berhenti mengabaikan pengguna, dan tampilkan pesan mereka", + "You are now ignoring %(userId)s": "Anda sekarang mengabaikan %(userId)s", + "Ignored user": "Pengguna yang diabaikan", + "Unbans user with given ID": "Menhilangkan cekalan pengguna dengan ID yang dicantumkan", + "Kicks user with given id": "Mengeluarkan pengguna dengan ID yang dicantumkan", + "Unrecognised room address:": "Alamat ruangan yang tidak diketahui:", + "Joins room with given address": "Bergabung ke ruangan dengan alamat yang dicantumkan", + "Use an identity server to invite by email. Manage in Settings.": "Gunakan server identitas untuk mengundang melalui email. Kelola di Pengaturan.", + "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Gunakan server identitas untuk mengundang melalui email. Klik lanjutkan untuk menggunakan server identitas default (%(defaultIdentityServerName)s) atau kelola di Pengaturan.", + "Use an identity server": "Gunakan sebuah server identitias", + "Invites user with given id to current room": "Mengundang pengguna dengan ID yang dicantumkan ke ruangan saat ini", + "Sets the room name": "Mengatur nama ruangan", + "This room has no topic.": "Ruangan ini tidak ada topik.", + "Failed to set topic": "Gagal untuk mengatur topik", + "Gets or sets the room topic": "Mendapatkan atau mengatur topik ruangan", + "Changes your avatar in all rooms": "Mengubah avatar Anda di semua ruangan", + "Changes your avatar in this current room only": "Mengubah avatar Anda di ruangan saat ini saja", + "Changes the avatar of the current room": "Mengubah avatar ruangan saat ini", + "Changes your display nickname in the current room only": "Mengubah nama tampilan di ruangan saat ini saja", + "You do not have the required permissions to use this command.": "Anda tidak memiliki izin yang dibutuhkan untuk menggunakan perintah ini.", + "Upgrades a room to a new version": "Meningkatkan ruangan ke versi yang baru", + "Sends a message as html, without interpreting it as markdown": "Mengirim pesan sebagai HTML, tanpa mengubahnya ke markdown", + "Sends a message as plain text, without interpreting it as markdown": "Mengirim pesan sebagai teks biasa, tanpa mengubahnya ke markdown", + "Prepends ( อกยฐ อœส– อกยฐ) to a plain-text message": "Menambahkan ( อกยฐ อœส– อกยฐ) ke pesan teks biasa", + "Prepends (โ•ฏยฐโ–กยฐ๏ผ‰โ•ฏ๏ธต โ”ปโ”โ”ป to a plain-text message": "Menambahkan (โ•ฏยฐโ–กยฐ๏ผ‰โ•ฏ๏ธต โ”ปโ”โ”ป ke pesan teks biasa", + "Prepends ยฏ\\_(ใƒ„)_/ยฏ to a plain-text message": "Menambahkan ยฏ\\_(ใƒ„)_/ยฏ ke pesan teks biasa", + "Sends the given message as a spoiler": "Mengirim pesan sebagai spoiler", + "Usage": "Penggunaan", + "Other": "Lainnya", + "Effects": "Efek", + "Actions": "Aksi", + "Messages": "Pesan", + "Setting up keys": "Menyiapkan kunci", + "Go Back": "Kembali", + "Are you sure you want to cancel entering passphrase?": "Apakah Anda yakin untuk membatalkan pemasukkan frasa sandi?", + "Cancel entering passphrase?": "Batalkan memasukkan frasa sandi?", + "Missing user_id in request": "Kurang user_id di permintaan", + "Room %(roomId)s not visible": "Ruangan %(roomId)s tidak terlihat", + "Missing room_id in request": "Kurang room_id di permintaan", + "Missing roomId.": "Kurang ID ruangan.", + "You do not have permission to do that in this room.": "Anda tidak memiliki izin untuk melakukannya di ruangan ini.", + "You are not in this room.": "Anda tidak berada di ruangan ini.", + "Power level must be positive integer.": "Level kekuatan harus angka yang positif.", + "This room is not recognised.": "Ruangan ini tidak dikenal.", + "Failed to send request.": "Gagal untuk mengirim permintaan.", + "Unable to create widget.": "Tidak dapat membuat widget.", + "You need to be able to invite users to do that.": "Anda harus dapat mengundang pengguna untuk melakukannya.", + "You need to be logged in.": "Anda harus masuk.", + "We sent the others, but the below people couldn't be invited to ": "Kami telah mengirim yang lainnya, tetapi orang berikut ini tidak dapat diundang ke ", + "Some invites couldn't be sent": "Beberapa undangan tidak dapat dikirim", + "Failed to invite users to the room:": "Gagal untuk mengundang pengguna ke ruangan ini:", + "Failed to invite": "Gagal untuk mengundang", + "Custom (%(level)s)": "Kustom (%(level)s)", + "Moderator": "Moderator", + "Restricted": "Dibatasi", + "Sign In or Create Account": "Masuk atau Buat Akun", + "Use your account or create a new one to continue.": "Gunakan akun Anda atau buat akun baru untuk lanjut.", + "Zimbabwe": "Zimbabwe", + "Zambia": "Zambia", + "Yemen": "Yaman", + "Western Sahara": "Sahara Barat", + "Wallis & Futuna": "Wallis & Futuna", + "Vietnam": "Vietnam", + "Venezuela": "Venezuela", + "Vatican City": "Kota Vatikan", + "Vanuatu": "Vanuatu", + "Uzbekistan": "Uzbekistan", + "Uruguay": "Uruguay", + "United Arab Emirates": "Uni Emirat Arab", + "Ukraine": "Ukraina", + "Uganda": "Uganda", + "U.S. Virgin Islands": "Kepulauan Virgin AS", + "Tuvalu": "Tuvalu", + "Turks & Caicos Islands": "Kepulauan Turks & Caicos", + "Turkmenistan": "Turkmenistan", + "Turkey": "Turki", + "Tunisia": "Tunisia", + "Trinidad & Tobago": "Trinidad & Tobago", + "Tonga": "Tonga", + "Tokelau": "Tokelau", + "Togo": "Togo", + "Timor-Leste": "Timor-Leste", + "Thailand": "Thailand", + "Tanzania": "Tanzania", + "Tajikistan": "Tajikistan", + "Taiwan": "Taiwan", + "Sรฃo Tomรฉ & Prรญncipe": "Sรฃo Tomรฉ & Prรญncipe", + "Syria": "Suriah", + "Switzerland": "Swiss", + "Sweden": "Swedia", + "Swaziland": "Swaziland", + "Svalbard & Jan Mayen": "Svalbard & Jan Mayen", + "Suriname": "Suriname", + "Sudan": "Sudan", + "St. Vincent & Grenadines": "St. Vincent & Grenadines", + "St. Pierre & Miquelon": "St. Pierre & Miquelon", + "St. Martin": "St. Martin", + "St. Lucia": "St. Lucia", + "St. Kitts & Nevis": "St. Kitts & Nevis", + "St. Helena": "St. Helena", + "Sri Lanka": "Sri Lanka", + "St. Barthรฉlemy": "St. Barthรฉlemy", + "Spain": "Spanyol", + "South Sudan": "Sudan Selatan", + "South Korea": "Korea Selatan", + "South Georgia & South Sandwich Islands": "Georgia Selatan & Kepulauan Sandwich Selatan", + "South Africa": "Afrika Selatan", + "Somalia": "Somalia", + "Solomon Islands": "Pulau Solomon", + "Slovenia": "Slovenia", + "Slovakia": "Slowakia", + "Sint Maarten": "Sint Maarten", + "Singapore": "Singapura", + "Sierra Leone": "Sierra Leone", + "Seychelles": "Seychelles", + "Serbia": "Serbia", + "Senegal": "Senegal", + "Saudi Arabia": "Arab Saudi", + "San Marino": "San Marino", + "Samoa": "Samoa", + "Rรฉunion": "Reuni", + "Rwanda": "Rwanda", + "Russia": "Rusia", + "Romania": "Rumania", + "Qatar": "Qatar", + "Puerto Rico": "Puerto Riko", + "Portugal": "Portugal", + "Poland": "Polandia", + "Pitcairn Islands": "Kepulauan Pitcairn", + "Philippines": "Filipina", + "Peru": "Peru", + "Paraguay": "Paraguay", + "Papua New Guinea": "Papua Nugini", + "Panama": "Panama", + "Palestine": "Palestina", + "Pakistan": "Pakistan", + "Palau": "Palau", + "Oman": "Oman", + "Norway": "Norway", + "Northern Mariana Islands": "Kepulauan Mariana Utara", + "North Korea": "Korea Utara", + "Norfolk Island": "Pulau Norfolk", + "Niue": "Niue", + "Nigeria": "Nigeria", + "Niger": "Niger", + "Nicaragua": "Nikaragua", + "New Zealand": "Selandia Baru", + "New Caledonia": "Kaledonia Baru", + "Netherlands": "Belanda", + "Nepal": "Nepal", + "Nauru": "Nauru", + "Namibia": "Namibia", + "Myanmar": "Myanmar", + "Mozambique": "Mozambik", + "Morocco": "Maroko", + "Montserrat": "Montserrat", + "Montenegro": "Montenegro", + "Mongolia": "Mongolia", + "Monaco": "Monako", + "Moldova": "Moldova", + "Micronesia": "Mikronesia", + "Mexico": "Meksiko", + "Mayotte": "Mayotte", + "Mauritius": "Mauritius", + "Mauritania": "Mauritania", + "Martinique": "Martinik", + "Marshall Islands": "Pulau Marshall", + "Malta": "Malta", + "Mali": "Mali", + "Maldives": "Maladewa", + "Malaysia": "Malaysia", + "Malawi": "Malawi", + "Madagascar": "Madagaskar", + "Macedonia": "Makedonia", + "Macau": "Makau", + "Luxembourg": "Luksemburg", + "Lithuania": "Lithuania", + "Liechtenstein": "Liechtenstein", + "Libya": "Libya", + "Liberia": "Liberia", + "Lesotho": "Lesotho", + "Lebanon": "Libanon", + "Latvia": "Latvia", + "Laos": "Laos", + "Kyrgyzstan": "Kirgistan", + "Kuwait": "Kuwait", + "Kosovo": "Kosovo", + "Kiribati": "Kiribati", + "Kenya": "Kenya", + "Kazakhstan": "Kazakstan", + "Jordan": "Yordania", + "Jersey": "Jersey", + "Japan": "Jepang", + "Jamaica": "Jamaika", + "Italy": "Italia", + "Israel": "Israel", + "Isle of Man": "Isle of Man", + "Ireland": "Irlandia", + "Iraq": "Irak", + "Iran": "Iran", + "Indonesia": "Indonesia", + "India": "India", + "Iceland": "Islandia", + "Hungary": "Hungaria", + "Hong Kong": "Hongkong", + "Honduras": "Honduras", + "Heard & McDonald Islands": "Pulau Heard & McDonald", + "Haiti": "Haiti", + "Guyana": "Guyana", + "Guinea-Bissau": "Guinea-Bissau", + "Guinea": "Guinea", + "Guernsey": "Guernsey", + "Guatemala": "Guatemala", + "Guam": "Guam", + "Guadeloupe": "Guadeloupe", + "Grenada": "Grenada", + "Greenland": "Greenland", + "Greece": "Yunani", + "Gibraltar": "Gibraltar", + "Ghana": "Ghana", + "Germany": "Jerman", + "Georgia": "Georgia", + "Gambia": "Gambia", + "Gabon": "Gabon", + "French Southern Territories": "Wilayah Selatan Prancis", + "French Polynesia": "Polinesia Perancis", + "French Guiana": "Guyana Perancis", + "France": "Perancis", + "Finland": "Finlandia", + "Fiji": "Fiji", + "Faroe Islands": "Kepulauan Faroe", + "Falkland Islands": "Kepulauan Falkland", + "Ethiopia": "Etiopia", + "Estonia": "Estonia", + "Eritrea": "Eritrea", + "Equatorial Guinea": "Guinea Ekuator", + "El Salvador": "El Salvador", + "Egypt": "Mesir", + "Ecuador": "Ekuador", + "Dominican Republic": "Republik Dominika", + "Dominica": "Dominika", + "Djibouti": "Djibouti", + "Denmark": "Denmark", + "Cรดte dโ€™Ivoire": "Pantai Gading", + "Czech Republic": "Republik Ceko", + "Cyprus": "Siprus", + "Curaรงao": "Curacao", + "Cuba": "Kuba", + "Croatia": "Kroasia", + "Costa Rica": "Kosta Rika", + "Cook Islands": "Kepulauan Cook", + "Congo - Kinshasa": "Kongo - Kinshasa", + "Congo - Brazzaville": "Kongo - Brazzaville", + "Comoros": "Komoro", + "Colombia": "Kolumbia", + "Cocos (Keeling) Islands": "Kepulauan Cocos (Keeling)", + "Christmas Island": "Pulau Natal", + "China": "Cina", + "Chile": "Chili", + "Chad": "Chad", + "Central African Republic": "Republik Afrika Tengah", + "Cayman Islands": "Pulau Cayman", + "Caribbean Netherlands": "Karibia Belanda", + "Cape Verde": "Tanjung Verde", + "Canada": "Kanada", + "Cameroon": "Kamerun", + "Cambodia": "Kamboja", + "Burundi": "Burundi", + "Burkina Faso": "Burkina Faso", + "Bulgaria": "Bulgaria", + "Brunei": "Brunei", + "British Virgin Islands": "Kepulauan Virgin Inggris", + "British Indian Ocean Territory": "Wilayah Samudra Hindia Britania", + "Brazil": "Brazil", + "Bouvet Island": "Pulau Bouvet", + "Botswana": "Botswana", + "Bosnia": "Bosnia", + "Bolivia": "Bolivia", + "Bhutan": "Bhutan", + "Bermuda": "Bermuda", + "Benin": "Benin", + "Belize": "Belize", + "Belgium": "Belgium", + "Belarus": "Belarusia", + "Barbados": "Barbados", + "Bangladesh": "Bangladesh", + "Bahrain": "Bahrain", + "Bahamas": "Bahama", + "Azerbaijan": "Azerbaijan", + "Austria": "Austria", + "Australia": "Australia", + "Aruba": "Aruba", + "Armenia": "Armenia", + "Argentina": "Argentina", + "Antigua & Barbuda": "Antigua & Barbuda", + "Antarctica": "Antartika", + "Anguilla": "Anguila", + "Angola": "Angola", + "Andorra": "Andora", + "American Samoa": "Samoa Amerika", + "Algeria": "Aljazair", + "Albania": "Albania", + "ร…land Islands": "Pulau Aland", + "Afghanistan": "Afganistan", + "United States": "Amerika Serikat", + "United Kingdom": "Britania Raya", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Alamat email Anda tidak tertaut dengan ID Matrix di homeserver ini.", + "Unable to enable Notifications": "Tidak dapat mengaktifkan Notifikasi", + "%(brand)s was not given permission to send notifications - please try again": "%(brand)s tidak memiliki izin untuk mengirim Anda notifikasi - silakan coba lagi", + "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s tidak memiliki izin untuk mengirim Anda notifikasi - mohon periksa pengaturan browser Anda", + "%(name)s is requesting verification": "%(name)s meminta verifikasi", + "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Homeserver Anda tidak dapat diakses dan tidak dapat memasukkan Anda. Silakan coba lagi. Jika ini terus terjadi, hubungi administrator homeserver Anda.", + "Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Homeserver Anda menolak upaya masuk Anda. Ini bisa jadi karena hal-hal yang terlalu lama. Silakan coba lagi. Jika ini berlanjut, hubungi administrator homeserver Anda.", + "Try again": "Coba lagi", + "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Kami menanyakan browser ini untuk mengingat homeserver apa yang Anda gunakan untuk membantu Anda masuk, tetapi sayangnya browser ini melupakannya. Pergi ke halaman masuk dan coba lagi.", + "We couldn't log you in": "Kami tidak dapat memasukkan Anda", + "Trust": "Percayakan", + "Only continue if you trust the owner of the server.": "Hanya lanjutkan jika Anda mempercayai pemilik server ini.", + "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Aksi ini memerlukan mengakses server identitas bawaan untuk memvalidasi sebuah alamat email atau nomor telepon, tetapi server ini tidak memiliki syarat layanan apapun.", + "Identity server has no terms of service": "Identitas server ini tidak memiliki syarat layanan", + "Unnamed Room": "Ruangan Tanpa Nama", + "Failed to add the following rooms to %(groupId)s:": "Gagal untuk menambahkan ruangan berikut ini ke %(groupId)s:", + "Failed to invite users to %(groupId)s": "Gagal untuk mengundang pengguna ke %(groupId)s", + "Failed to invite users to community": "Gagal untuk mengundang pengguna ke komunitas", + "Failed to invite the following users to %(groupId)s:": "Gagal mengundang pengguna berikut ini ke %(groupId)s:", + "Add to community": "Tambahkan ke komunitas", + "Room name or address": "Nama ruangan atau alamat", + "Add rooms to the community": "Tambahkan ruangan ke komunitas ini", + "Show these rooms to non-members on the community page and room list?": "Tampilkan ruangan ini ke orang yang bukan anggota komunitas di halaman komunitas dan daftar ruangan?", + "Which rooms would you like to add to this community?": "Ruangan apa yang Anda ingin tambahkan ke komunitas ini?", + "Invite to Community": "Undang ke Komunitas", + "Name or Matrix ID": "Nama atau ID Matrix", + "Invite new community members": "Undang anggota komunitas baru", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Peringatan: setiap orang yang Anda tambahkan ke komunitas akan terlihat oleh siapa saja yang mengetahui ID komunitas", + "Who would you like to add to this community?": "Siapa yang ingin Anda tambahkan ke komunitas ini?", + "%(date)s at %(time)s": "%(date)s pada %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(time)s", + "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", + "AM": "AM", + "PM": "PM", + "Failure to create room": "Gagal untuk membuat ruangan", + "The server does not support the room version specified.": "Server ini tidak mendukung versi ruangan yang dicantumkan.", + "Server may be unavailable, overloaded, or you hit a bug.": "Server mungkin tidak tersedia, kelebihan beban, atau Anda mengalami bug.", + "Upload Failed": "Unggahan Gagal", + "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "File '%(fileName)s' melebihi batas ukuran unggahan file homeserver", + "The file '%(fileName)s' failed to upload.": "File '%(fileName)s' gagal untuk diunggah.", + "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Saat ini membalas dengan file tidak dapat dilakukan. Apakah Anda ingin menunggah file ini tanpa membalas?", + "Replying With Files": "Membalas Dengan File", + "This will end the conference for everyone. Continue?": "Ini akan mengakhiri konferensi ini untuk semuanya. Lanjut?", + "End conference": "Akhiri konferensi", + "Unable to transfer call": "Tidak dapat memindahkan panggilan", + "Failed to transfer call": "Gagal untuk memindahkan panggilan", + "Transfer Failed": "Pemindahan Gagal", + "There was an error looking up the phone number": "Sebuah kesalahan terjadi mencari nomor teleponnya", + "Unable to look up phone number": "Tidak dapat mencari nomor telepon", + "You've reached the maximum number of simultaneous calls.": "Anda telah mencapai jumlah maksimum panggilan pada waktu bersamaan.", + "Too Many Calls": "Terlalu Banyak Panggilan", + "You're already in a call with this person.": "Anda sudah ada di panggilan dengan orang itu.", + "Already in call": "Sudah ada di panggilan", + "No other application is using the webcam": "Tidak ada aplikasi lain yang menggunakan webcam", + "Permission is granted to use the webcam": "Izin diberikan untuk menggunakan webcam", + "A microphone and webcam are plugged in and set up correctly": "Mikrofon dan webcam telah dicolokkan dan diatur dengan benar", + "Call failed because webcam or microphone could not be accessed. Check that:": "Panggilan gagal karena webcam atau mikrofon tidak bisa diakses. Periksa itu:", + "Unable to access webcam / microphone": "Tidak dapat mengakses webcam/mikrofon", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Panggilan gagal karena mikrofon tidak bisa diakses. Periksa apakah mikrofon sudah dicolokkan dan diatur dengan benar.", + "Unable to load! Check your network connectivity and try again.": "Tidak dapat dimuat! Periksa koneksi jaringan Anda dan coba lagi.", + "Unable to access microphone": "Tidak dapat mengakses mikrofon", + "Try using turn.matrix.org": "Coba menggunakan turn.matrix.org", + "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatifnya, Anda bisa coba menggunakan server publik di turn.matrix.org, tetapi ini tidak terlalu andal, dan akan membagikan alamat IP Anda dengan servernya. Anda juga bisa kelola ini di Pengaturan.", + "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Mohon tanyakan ke administrator homeserver Anda (%(homeserverDomain)s) untuk mengkonfigurasikan server TURN supaya panggilan dapat bekerja dengan benar.", + "Call failed due to misconfigured server": "Panggilan gagal karena servernya tidak dikonfigurasi dengan benar", + "Answered Elsewhere": "Dijawab di Perangkat Lain", + "The call could not be established": "Panggilan tidak dapat dilakukan", + "The user you called is busy.": "Pengguna yang Anda panggil sedang sibuk.", + "User Busy": "Pengguna Sibuk", + "Your user agent": "Agen pengguna Anda", + "Whether you're using %(brand)s as an installed Progressive Web App": "Apakah Anda menggunakan %(brand)s sebagai Aplikasi Web Progresif yang terpasang", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Apakah Anda menggunakan fitur 'breadcrumbs' atau tidak (avatar di atas daftar ruangan)", + "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Apakah Anda menggunakan %(brand)s pada perangkat yang mekanisme input utamanya adalah sentuhan", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Apakah Anda menggunakan mode Richtext dari Rich Text Editor atau tidak", + "Which officially provided instance you are using, if any": "Instansi mana yang secara resmi Anda gunakan, jika ada", + "Whether or not you're logged in (we don't record your username)": "Apakah Anda masuk atau tidak (kami tidak mencatat nama pengguna Anda)", + "The platform you're on": "Platform yang Anda berada saat ini", + "Add Phone Number": "Tambahkan Nomor Telepon", + "Click the button below to confirm adding this phone number.": "Klik tombol di bawah untuk mengkonfirmasi penambahan nomor telepon ini.", + "Confirm adding phone number": "Konfirmasi penambahan nomor telepon", + "Confirm adding this phone number by using Single Sign On to prove your identity.": "Konfirmasi penambahan nomor telepon ini dengan menggunakan Single Sign On untuk membuktikan identitas Anda.", + "Add Email Address": "Tambahkan Alamat Email", + "Confirm": "Konfirmasi", + "Click the button below to confirm adding this email address.": "Klik tombol di bawah untuk mengkonfirmasi penambahan alamat email ini.", + "Confirm adding email": "Konfirmasi penambahan email", + "Single Sign On": "Single Sign On", + "Confirm adding this email address by using Single Sign On to prove your identity.": "Konfirmasi penambahan alamat email ini dengan menggunakan Single Sign On untuk membuktikan identitas Anda.", + "Use Single Sign On to continue": "Gunakan Single Sign On untuk melanjutkan" } diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index b12554e256f..c82b738aa6d 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -3168,5 +3168,88 @@ "Leave all rooms": "Esci da tutte le stanze", "Don't leave any rooms": "Non uscire da alcuna stanza", "Expand quotes โ”‚ โ‡ง+click": "Espandi le menzioni โ”‚ โ‡ง+clic", - "Collapse quotes โ”‚ โ‡ง+click": "Riduci le menzioni โ”‚ โ‡ง+clic" + "Collapse quotes โ”‚ โ‡ง+click": "Riduci le menzioni โ”‚ โ‡ง+clic", + "Current Timeline": "Linea temporale attuale", + "Specify a number of messages": "Specifica un numero di messaggi", + "From the beginning": "Dall'inizio", + "Plain Text": "Testo semplice", + "JSON": "JSON", + "HTML": "HTML", + "Are you sure you want to exit during this export?": "Vuoi davvero uscire durante l'esportazione?", + "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s ha inviato uno sticker.", + "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s ha cambiato l'avatar della stanza.", + "%(date)s at %(time)s": "%(date)s alle %(time)s", + "Displaying time": "Mostrando l'ora", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Procedi solo se sei sicuro di avere perso tutti gli altri tuoi dispositivi e la chiave di sicurezza.", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "La reimpostazione delle chiavi di verifica non puรฒ essere annullata. Dopo averlo fatto, non avrai accesso ai vecchi messaggi cifrati, e gli amici che ti avevano verificato in precedenza vedranno avvisi di sicurezza fino a quando non ti ri-verifichi con loro.", + "I'll verify later": "Verificherรฒ dopo", + "Verify with another login": "Verifica con un altro accesso", + "Verify with Security Key": "Verifica con chiave di sicurezza", + "Verify with Security Key or Phrase": "Verifica con chiave di sicurezza o frase", + "Proceed with reset": "Procedi con la reimpostazione", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Pare che tu non abbia una chiave di sicurezza o altri dispositivi con cui poterti verificare. Questo dispositivo non potrร  accedere ai vecchi messaggi cifrati. Per potere verificare la tua ideintitร  su questo dispositivo, dovrai reimpostare le chiavi di verifica.", + "Skip verification for now": "Salta la verifica per adesso", + "Really reset verification keys?": "Reimpostare le chiavi di verifica?", + "Unable to verify this login": "Impossibile verificare questo accesso", + "Include Attachments": "Includi allegati", + "Size Limit": "Limite dimensione", + "Format": "Formato", + "Select from the options below to export chats from your timeline": "Seleziona dalle opzioni sotto per esportare le chat dalla linea temporale", + "Export Chat": "Esporta conversazione", + "Exporting your data": "Esportazione dei dati", + "Stop": "Ferma", + "Are you sure you want to stop exporting your data? If you do, you'll need to start over.": "Vuoi davvero fermare l'esportazione dei dati? Se lo fai, dovrai ricominciare da capo.", + "Your export was successful. Find it in your Downloads folder.": "Esportazione riuscita. La puoi trovare nella cartella Download.", + "The export was cancelled successfully": "Esportazione annullata correttamente", + "Export Successful": "Esportazione riuscita", + "MB": "MB", + "Number of messages": "Numero di messaggi", + "Number of messages can only be a number between %(min)s and %(max)s": "Il numero di messaggi puรฒ essere solo tra %(min)s e %(max)s", + "Size can only be a number between %(min)s MB and %(max)s MB": "La dimensione puรฒ essere solo tra %(min)s MB e %(max)s MB", + "Enter a number between %(min)s and %(max)s": "Inserisci un numero tra %(min)s e %(max)s", + "Creating Space...": "Creazione spazio...", + "Fetching data...": "Recupero dei dati...", + "In reply to this message": "In risposta a questo messaggio", + "Export chat": "Esporta conversazione", + "To proceed, please accept the verification request on your other login.": "Per continuare, accetta la richiesta di verifica nell'altro tuo accesso.", + "Waiting for you to verify on your other sessionโ€ฆ": "In attesa della verifica nella tua altra sessioneโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "In attesa della verifica nella tua altra sessione, %(deviceName)s (%(deviceId)s)โ€ฆ", + "File Attached": "File allegato", + "Error fetching file": "Errore di recupero del file", + "Topic: %(topic)s": "Argomento: %(topic)s", + "This is the start of export of . Exported by at %(exportDate)s.": "Questo รจ l'inizio dell'esportazione di . Esportata da il %(exportDate)s.", + "%(creatorName)s created this room.": "%(creatorName)s ha creato questa stanza.", + "Media omitted - file size limit exceeded": "File omesso - superata dimensione massima", + "Media omitted": "File omesso", + "Create poll": "Crea sondaggio", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Aggiornamento spazio...", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Aggiornamento spazi... (%(progress)s di %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Spedizione invito...", + "Sending invites... (%(progress)s out of %(count)s)|other": "Spedizione inviti... (%(progress)s di %(count)s)", + "Loading new room": "Caricamento nuova stanza", + "Upgrading room": "Aggiornamento stanza", + "Polls (under active development)": "Sondaggi (in sviluppo)", + "Ban them from everything I'm able to": "Bandiscilo ovunque io possa farlo", + "Unban them from everything I'm able to": "Riammettilo ovunque io possa farlo", + "Ban from %(roomName)s": "Bandisci da %(roomName)s", + "Unban from %(roomName)s": "Riammetti in %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "Potrร  ancora accedere dove non sei amministratore.", + "Kick them from specific things I'm able to": "Buttalo fuori da cose specifiche dove posso farlo", + "Kick them from everything I'm able to": "Buttalo fuori da ovunque io possa farlo", + "Kick from %(roomName)s": "Butta fuori da %(roomName)s", + "Disinvite from %(roomName)s": "Annulla l'invito da %(roomName)s", + "Threads": "Conversazioni", + "%(count)s reply|one": "%(count)s risposta", + "%(count)s reply|other": "%(count)s risposte", + "Show:": "Mostra:", + "Shows all threads from current room": "Mostra tutte le conversazioni dalla stanza attuale", + "All threads": "Tutte le conversazioni", + "Shows all threads youโ€™ve participated in": "Mostra tutte le conversazioni a cui hai partecipato", + "My threads": "Le mie conversazioni", + "View in room": "Vedi nella stanza", + "Enter your Security Phrase or to continue.": "Inserisci la tua frase di sicurezza o per continuare.", + "Downloading": "Scaricamento", + "They won't be able to access whatever you're not an admin of.": "Non potrร  piรน accedere anche dove non sei amministratore.", + "Ban them from specific things I'm able to": "Bandiscilo da cose specifiche dove posso farlo", + "Unban them from specific things I'm able to": "Riammettilo in cose specifiche dove posso farlo" } diff --git a/src/i18n/strings/nb_NO.json b/src/i18n/strings/nb_NO.json index 93fe437e3d2..648b9fd7b33 100644 --- a/src/i18n/strings/nb_NO.json +++ b/src/i18n/strings/nb_NO.json @@ -1735,5 +1735,82 @@ "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Bruk en integreringsbehandler (%(serverName)s) til รฅ behandle botter, moduler, og klistremerkepakker.", "Identity server": "Identitetstjener", "Identity server (%(server)s)": "Identitetstjener (%(server)s)", - "Could not connect to identity server": "Kunne ikke koble til identitetsserveren" + "Could not connect to identity server": "Kunne ikke koble til identitetsserveren", + "Show:": "Vis:", + "Results": "Resultat", + "Retry all": "Prรธv alle igjen", + "Play": "Spill av", + "Pause": "Pause", + "Beta": "Beta", + "Move down": "Gรฅ ned", + "Move up": "Gรฅ opp", + "Report": "Rapporter", + "Forward": "Videresend", + "Forward message": "Videresend melding", + "Open link": "ร…pne lenken", + "Sent": "Sendt", + "Sending": "Sender", + "Format": "Format", + "Stop": "Stopp avspilling", + "MB": "MB", + "Zoom in": "Forstรธrr", + "Zoom out": "Forminske", + "Add reaction": "Legg til reaksjon", + "Image": "Bilde", + "Sticker": "Klistremerke", + "Thread": "Trรฅd", + "Downloading": "Laster ned", + "Connection failed": "Tilkobling mislyktes", + "Message": "Melding", + "Threads": "Trรฅder", + "Send a sticker": "Send et klistremerke", + "Keyboard shortcuts": "Tastatursnarveier", + "Global": "Globalt", + "Keyword": "Nรธkkelord", + "IRC": "IRC", + "Collapse": "Trekk sammen", + "Expand": "Utvid", + "Visibility": "Synlighet", + "Address": "Adresse", + "Delete avatar": "Slett profilbilde", + "Corn": "Mais", + "Cloud": "Sky", + "Mute the microphone": "Demp mikrofonen", + "Unmute the microphone": "Opphev demping av mikrofonen", + "Dialpad": "Nummerpanel", + "More": "Mer", + "Stop the camera": "Stopp kameraet", + "Start the camera": "Start kameraet", + "Connecting": "Kobler til", + "Plain Text": "Ren tekst", + "JSON": "JSON", + "HTML": "HTML", + "Change the name of this room": "Endre rommets navn", + "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s endret gjestetilgangen til %(rule)s", + "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s merket rommet som kun for inviterte.", + "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s endret rommets avatar.", + "%(senderName)s kicked %(targetName)s": "%(senderName)s sparket ut %(targetName)s", + "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s sparket ut %(targetName)s: %(reason)s", + "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s trakk tilbake invitasjonen til %(targetName)s: %(reason)s", + "%(senderName)s unbanned %(targetName)s": "%(senderName)s opphevde bannlysningen av %(targetName)s", + "%(targetName)s left the room": "%(targetName)s forlot rommet", + "%(targetName)s left the room: %(reason)s": "%(targetName)s forlot rommet: %(reason)s", + "%(targetName)s rejected the invitation": "%(targetName)s avslo invitasjonen", + "%(targetName)s joined the room": "%(targetName)s ble med i rommet", + "%(senderName)s made no change": "%(senderName)s gjorde ingen endringer", + "%(senderName)s set a profile picture": "%(senderName)s valgte seg et profilbilde", + "%(senderName)s changed their profile picture": "%(senderName)s endret profilbildet sitt", + "%(senderName)s removed their profile picture": "%(senderName)s fjernet profilbildet sitt", + "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s fjernet visningsnavnet sitt (%(oldDisplayName)s)", + "%(senderName)s set their display name to %(displayName)s": "%(senderName)s satte visningsnavnet sitt til %(displayName)s", + "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s endret visningsnavnet sitt til %(displayName)s", + "%(senderName)s banned %(targetName)s": "%(senderName)s bannlyste %(targetName)s", + "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s bannlyste %(targetName)s: %(reason)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s inviterte %(targetName)s", + "%(targetName)s accepted an invitation": "%(targetName)s aksepterte en invitasjon", + "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s aksepterte invitasjonen til %(displayName)s", + "St. Pierre & Miquelon": "Saint-Pierre og Miquelon", + "St. Martin": "Saint Martin", + "St. Barthรฉlemy": "Saint Barthรฉlemy", + "%(date)s at %(time)s": "%(date)s klokken %(time)s" } diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index cce71ac8f26..250e5c61761 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -3201,5 +3201,10 @@ "Plain Text": "Platte tekst", "JSON": "JSON", "HTML": "HTML", - "Are you sure you want to exit during this export?": "Weet u zeker dat u wilt afsluiten tijdens een export?" + "Are you sure you want to exit during this export?": "Weet u zeker dat u wilt afsluiten tijdens een export?", + "Creating Space...": "Ruimte aanmaken...", + "Fetching data...": "Gegevens ophalen...", + "To proceed, please accept the verification request on your other login.": "Om door te gaan, accepteer het verificatieverzoek op uw andere login.", + "Waiting for you to verify on your other sessionโ€ฆ": "Wachten op uw verificatie op uw andere sessieโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Wachten op uw verificatie op uw andere sessie, %(deviceName)s (%(deviceId)s)โ€ฆ" } diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 3a2f1aeb086..670b654f2d0 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -2959,5 +2959,29 @@ "To avoid these issues, create a new encrypted room for the conversation you plan to have.": "Para evitar esses problemas, crie uma nova sala criptografada para a conversa que vocรช planeja ter.", "Are you sure you want to add encryption to this public room?": "Tem certeza que deseja adicionar criptografia para esta sala pรบblica?", "Select the roles required to change various parts of the space": "Selecionar os cargos necessรกrios para alterar certas partes do espaรงo", - "Change main address for the space": "Mudar o endereรงo principal para o espaรงo" + "Change main address for the space": "Mudar o endereรงo principal para o espaรงo", + "Include Attachments": "Incluir Anexos", + "Size Limit": "Limite de Tamanho", + "MB": "MB", + "Number of messages": "Nรบmero de mensagens", + "Number of messages can only be a number between %(min)s and %(max)s": "Nรบmero de mensagens pode ser apenas um nรบmero entre %(min)s e %(max)s", + "Size can only be a number between %(min)s MB and %(max)s MB": "O tamanho pode ser apenas um nรบmero entre %(min)s MB e %(max)s MB", + "Enter a number between %(min)s and %(max)s": "Insira um nรบmero entre %(min)s e %(max)s", + "Creating Space...": "Criando Espaรงoโ€ฆ", + "Fetching data...": "Buscando dadosโ€ฆ", + "In reply to this message": "Em resposta a esta mensagem", + "Zoom in": "Aumentar zoom", + "Zoom out": "Diminuir zoom", + "Expand quotes โ”‚ โ‡ง+click": "Expandir citaรงรตes | โ‡ง+click", + "Export chat": "Exportar conversa", + "To proceed, please accept the verification request on your other login.": "Para prosseguir, por favor aceite o pedido de verificaรงรฃo em seu outro login.", + "Waiting for you to verify on your other sessionโ€ฆ": "Aguardando sua verificaรงรฃo em sua outra sessรฃoโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Aguardando sua verificaรงรฃo na outra sessรฃo, %(deviceName)s%(deviceId)sโ€ฆ", + "File Attached": "Arquivo Anexado", + "Topic: %(topic)s": "Tรณpico: %(topic)s", + "%(creatorName)s created this room.": "%(creatorName)s criou esta sala.", + "Plain Text": "Texto Simples", + "JSON": "JSON", + "HTML": "HTML", + "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s enviou uma figurinha." } diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index d2766949fba..1102fc7c751 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2746,7 +2746,7 @@ "Not a valid identity server (status code %(code)s)": "ะะตะฟั€ะฐะฒะธะปัŒะฝั‹ะน ะกะตั€ะฒะตั€ ะธะดะตะฝั‚ะธั„ะธะบะฐั†ะธะธ (ะบะพะด ัั‚ะฐั‚ัƒัะฐ %(code)s)", "Identity server URL must be HTTPS": "URL-ะฐะดั€ะตั ัะตั€ะฒะตั€ะฐ ะธะดะตะฝั‚ะธั„ะธะบะฐั†ะธะธ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ HTTPS", "Consulting with %(transferTarget)s. Transfer to %(transferee)s": "ะžะฑั‰ะตะฝะธะต ั %(transferTarget)s. ะŸะตั€ะตะฒะพะด ะฝะฐ %(transferee)s", - "[number]": "[ะฝะพะผะตั€]", + "[number]": "[ั†ะธั„ั€ะฐ]", "Enter your Security Phrase a second time to confirm it.": "ะ’ะฒะตะดะธั‚ะต ัะตะบั€ะตั‚ะฝัƒัŽ ั„ั€ะฐะทัƒ ะฒั‚ะพั€ะพะน ั€ะฐะท, ั‡ั‚ะพะฑั‹ ะฟะพะดั‚ะฒะตั€ะดะธั‚ัŒ ะตะต.", "Space Autocomplete": "ะะฒั‚ะพะทะฐะฟะพะปะฝะตะฝะธะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐ", "Without verifying, you wonโ€™t have access to all your messages and may appear as untrusted to others.": "ะ‘ะตะท ะฟั€ะพะฒะตั€ะบะธ ะฒั‹ ะฝะต ัะผะพะถะตั‚ะต ะฟะพะปัƒั‡ะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบะพ ะฒัะตะผ ัะฒะพะธะผ ัะพะพะฑั‰ะตะฝะธัะผ ะธ ะผะพะถะตั‚ะต ะฟะพะบะฐะทะฐั‚ัŒัั ะดั€ัƒะณะธะผ ะปัŽะดัะผ ะฝะตะดะพะฒะตั€ะตะฝะฝั‹ะผ.", @@ -2791,9 +2791,9 @@ "Move down": "ะžะฟัƒัั‚ะธั‚ัŒ", "Move up": "ะŸะพะดะฝัั‚ัŒ", "Manage & explore rooms": "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะธ ัะฟะธัะพะบ ะบะพะผะฝะฐั‚", - "Add space": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฟั€ะพัั‚ะฐะฝัั‚ะฒะพ", + "Add space": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะพ", "Report": "ะกะพะพะฑั‰ะธั‚ัŒ", - "Collapse reply thread": "ะกะฒะตั€ะฝัƒั‚ัŒ ะพั‚ะฒะตั‚ั‹", + "Collapse reply thread": "ะกะฒะตั€ะฝัƒั‚ัŒ ะพั‚ะฒะตั‚ั‹ ะฒะตั‚ะบะธ", "Show preview": "ะŸั€ะตะดะฟั€ะพัะผะพั‚ั€", "View source": "ะ˜ัั…ะพะดะฝั‹ะน ะบะพะด", "Forward": "ะŸะตั€ะตัะปะฐั‚ัŒ", @@ -3195,5 +3195,59 @@ "Are you sure you want to exit during this export?": "ะ’ั‹ ัƒะฒะตั€ะตะฝั‹, ั‡ั‚ะพ ั…ะพั‚ะธั‚ะต ะฒั‹ะนั‚ะธ ะฒะพ ะฒั€ะตะผั ัะบัะฟะพั€ั‚ะฐ?", "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s ะพั‚ะฟั€ะฐะฒะธะป(ะฐ) ัั‚ะธะบะตั€.", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s ัะผะตะฝะธะป(ะฐ) ะฐะฒะฐั‚ะฐั€ ะบะพะผะฝะฐั‚ั‹.", - "%(date)s at %(time)s": "%(date)s ะฒ %(time)s" + "%(date)s at %(time)s": "%(date)s ะฒ %(time)s", + "Select from the options below to export chats from your timeline": "ะ’ั‹ะฑะตั€ะธั‚ะต ะพะดะธะฝ ะธะท ะฟั€ะธะฒะตะดะตะฝะฝั‹ั… ะฝะธะถะต ะฒะฐั€ะธะฐะฝั‚ะพะฒ ัะบัะฟะพั€ั‚ะฐ ั‡ะฐั‚ะพะฒ ะธะท ะฒะฐัˆะตะน ะฒั€ะตะผะตะฝะฝะพะน ัˆะบะฐะปั‹", + "Current Timeline": "ะขะตะบัƒั‰ะฐั ะฒั€ะตะผะตะฝะฝะฐั ัˆะบะฐะปะฐ", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะดะตะนัั‚ะฒัƒะนั‚ะต ั‚ะพะปัŒะบะพ ะฒ ั‚ะพะผ ัะปัƒั‡ะฐะต, ะตัะปะธ ะฒั‹ ัƒะฒะตั€ะตะฝั‹, ั‡ั‚ะพ ะฟะพั‚ะตั€ัะปะธ ะฒัะต ะพัั‚ะฐะปัŒะฝั‹ะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ ะธ ะบะปัŽั‡ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ.", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "ะกะฑั€ะพั ะบะปัŽั‡ะตะน ะฟั€ะพะฒะตั€ะบะธ ะฝะตะปัŒะทั ะพั‚ะผะตะฝะธั‚ัŒ. ะŸะพัะปะต ัะฑั€ะพัะฐ ะฒั‹ ะฝะต ัะผะพะถะตั‚ะต ะฟะพะปัƒั‡ะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ัั‚ะฐั€ั‹ะผ ะทะฐัˆะธั„ั€ะพะฒะฐะฝะฝั‹ะผ ัะพะพะฑั‰ะตะฝะธัะผ, ะฐ ะดั€ัƒะทัŒั, ะบะพั‚ะพั€ั‹ะต ั€ะฐะฝะตะต ะฟั€ะพะฒะตั€ะธะปะธ ะฒะฐั, ะฑัƒะดัƒั‚ ะฒะธะดะตั‚ัŒ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธั ะพ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ, ะฟะพะบะฐ ะฒั‹ ะฝะต ะฟั€ะพะนะดะตั‚ะต ะฟะพะฒั‚ะพั€ะฝัƒัŽ ะฟั€ะพะฒะตั€ะบัƒ.", + "Skip verification for now": "ะŸะพะบะฐ ะฟั€ะพะฟัƒัั‚ะธั‚ัŒ ะฟั€ะพะฒะตั€ะบัƒ", + "I'll verify later": "ะฏ ะฟั€ะพะฒะตั€ัŽ ะฟะพะทะถะต", + "Verify with another login": "ะŸั€ะพะฒะตั€ะธั‚ัŒ ั ะฟะพะผะพั‰ัŒัŽ ะดั€ัƒะณะพะณะพ ะปะพะณะธะฝะฐ", + "Verify with Security Key": "ะŸั€ะพะฒะตั€ะธั‚ัŒ ั ะฟะพะผะพั‰ัŒัŽ ะบะปัŽั‡ะฐ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ", + "Verify with Security Key or Phrase": "ะŸั€ะพะฒะตั€ะบะฐ ั ะฟะพะผะพั‰ัŒัŽ ะบะปัŽั‡ะฐ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ ะธะปะธ ั„ั€ะฐะทั‹", + "Proceed with reset": "ะ’ั‹ะฟะพะปะฝะธั‚ัŒ ัะฑั€ะพั", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "ะŸะพั…ะพะถะต, ั‡ั‚ะพ ัƒ ะฒะฐั ะฝะตั‚ ะšะปัŽั‡ะฐ ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ ะธะปะธ ะดั€ัƒะณะธั… ัƒัั‚ั€ะพะนัั‚ะฒ, ะบะพั‚ะพั€ั‹ะต ะผะพะถะฝะพ ะฟั€ะพะฒะตั€ะธั‚ัŒ. ะญั‚ะพ ัƒัั‚ั€ะพะนัั‚ะฒะพ ะฝะต ัะผะพะถะตั‚ ะฟะพะปัƒั‡ะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ัั‚ะฐั€ั‹ะผ ะทะฐัˆะธั„ั€ะพะฒะฐะฝะฝั‹ะผ ัะพะพะฑั‰ะตะฝะธัะผ. ะงั‚ะพะฑั‹ ะฟะพะดั‚ะฒะตั€ะดะธั‚ัŒ ัะฒะพัŽ ะปะธั‡ะฝะพัั‚ัŒ ะฝะฐ ัั‚ะพะผ ัƒัั‚ั€ะพะนัั‚ะฒะต, ะฒะฐะผ ะฝะตะพะฑั…ะพะดะธะผะพ ัะฑั€ะพัะธั‚ัŒ ะบะปัŽั‡ะธ ะฟั€ะพะฒะตั€ะบะธ.", + "Really reset verification keys?": "ะ”ะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพ ัะฑั€ะพัะธั‚ัŒ ะบะปัŽั‡ะธ ะฟั€ะพะฒะตั€ะบะธ?", + "Unable to verify this login": "ะะตะฒะพะทะผะพะถะฝะพ ะฟั€ะพะฒะตั€ะธั‚ัŒ ัั‚ะพั‚ ะปะพะณะธะฝ", + "To proceed, please accept the verification request on your other login.": "ะงั‚ะพะฑั‹ ะฟั€ะพะดะพะปะถะธั‚ัŒ, ะฟะพะถะฐะปัƒะนัั‚ะฐ, ะฟั€ะธะผะธั‚ะต ะทะฐะฟั€ะพั ะฝะฐ ะฟั€ะพะฒะตั€ะบัƒ ะฝะฐ ะดั€ัƒะณะพะผ ะปะพะณะธะฝะต.", + "Waiting for you to verify on your other sessionโ€ฆ": "ะžะถะธะดะฐะตะผ ะฟั€ะพะฒะตั€ะบะธ ะฝะฐ ะดั€ัƒะณะพะผ ัะตะฐะฝัะตโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "ะžะถะธะดะฐะตะผ ะฟั€ะพะฒะตั€ะบะธ ะฝะฐ ะดั€ัƒะณะพะผ ัะตะฐะฝัะต, %(deviceName)s (%(deviceId)s)โ€ฆ", + "Creating Space...": "ะกะพะทะดะฐะฝะธะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐโ€ฆ", + "Fetching data...": "ะŸะพะปัƒั‡ะตะฝะธะต ะดะฐะฝะฝั‹ั…โ€ฆ", + "Create poll": "ะกะพะทะดะฐั‚ัŒ ะพะฟั€ะพั", + "Polls (under active development)": "ะžะฟั€ะพัั‹ (ะฒ ัั‚ะฐะดะธะธ ะฐะบั‚ะธะฒะฝะพะน ั€ะฐะทั€ะฐะฑะพั‚ะบะธ)", + "Thread": "ะ’ะตั‚ะบะฐ", + "Show threads": "ะŸะพะบะฐะทะฐั‚ัŒ ะฒะตั‚ะบะธ", + "Reply to threadโ€ฆ": "ะžั‚ะฒะตั‚ะธั‚ัŒ ะฝะฐ ะฒะตั‚ะบัƒโ€ฆ", + "Reply to encrypted threadโ€ฆ": "ะžั‚ะฒะตั‚ะธั‚ัŒ ะฝะฐ ะทะฐัˆะธั„ั€ะพะฒะฐะฝะฝัƒัŽ ะฒะตั‚ะบัƒโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|one": "ะžะฑะฝะพะฒะปะตะฝะธะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒะฐโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|other": "ะžะฑะฝะพะฒะปะตะฝะธะต ะฟั€ะพัั‚ั€ะฐะฝัั‚ะฒ... (%(progress)s ะธะท %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "ะžั‚ะฟั€ะฐะฒะบะฐ ะฟั€ะธะณะปะฐัˆะตะฝะธัโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "ะžั‚ะฟั€ะฐะฒะบะฐ ะฟั€ะธะณะปะฐัˆะตะฝะธะน... (%(progress)s ะธะท %(count)s)", + "Loading new room": "ะ—ะฐะณั€ัƒะทะบะฐ ะฝะพะฒะพะน ะบะพะผะฝะฐั‚ั‹", + "Upgrading room": "ะžะฑะฝะพะฒะปะตะฝะธะต ะบะพะผะฝะฐั‚ั‹", + "Threaded messaging": "ะกะพะพะฑั‰ะตะฝะธะต ะฒ ะฒะตั‚ะบะต", + "Ban from %(roomName)s": "ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะฒ %(roomName)s", + "Unban from %(roomName)s": "ะ ะฐะทะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะฒ %(roomName)s", + "Show:": "ะŸะพะบะฐะทะฐั‚ัŒ:", + "They won't be able to access whatever you're not an admin of.": "ะžะฝะธ ะฝะต ัะผะพะณัƒั‚ ะฟะพะปัƒั‡ะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบ ั‚ะตะผ ะผะตัั‚ะฐะผ, ะณะดะต ะฒั‹ ะฝะต ัะฒะปัะตั‚ะตััŒ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ะพะผ.", + "Ban them from specific things I'm able to": "ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะธั… ะฒ ะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ะผะตัั‚ะฐั…, ะณะดะต ั ะผะพะณัƒ ัั‚ะพ ัะดะตะปะฐั‚ัŒ", + "Unban them from specific things I'm able to": "ะ ะฐะทะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะธั… ะธะท ะพะฟั€ะตะดะตะปั‘ะฝะฝั‹ั… ะผะตัั‚, ะณะดะต ั ะผะพะณัƒ ัั‚ะพ ัะดะตะปะฐั‚ัŒ", + "Ban them from everything I'm able to": "ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะธั… ะฒะตะทะดะต, ะณะดะต ั ะผะพะณัƒ ัั‚ะพ ัะดะตะปะฐั‚ัŒ", + "Unban them from everything I'm able to": "ะ ะฐะทะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะธั… ะฒะตะทะดะต, ะณะดะต ั ะผะพะณัƒ ัั‚ะพ ัะดะตะปะฐั‚ัŒ", + "Kick them from specific things I'm able to": "ะ’ั‹ะณะฝะฐั‚ัŒ ะธั… ะธะท ะพะฟั€ะตะดะตะปะตะฝะฝั‹ั… ะผะตัั‚, ะณะดะต ั ะผะพะณัƒ ัั‚ะพ ัะดะตะปะฐั‚ัŒ", + "Kick them from everything I'm able to": "ะ’ั‹ะณะฝะฐั‚ัŒ ะธั… ะธะท ะปัŽะฑะพะณะพ ะผะตัั‚ะฐ, ะณะดะต ั ะผะพะณัƒ ัั‚ะพ ัะดะตะปะฐั‚ัŒ", + "Shows all threads from current room": "ะŸะพะบะฐะทั‹ะฒะฐะตั‚ ะฒัะต ะฒะตั‚ะบะธ ะธะท ั‚ะตะบัƒั‰ะตะน ะบะพะผะฝะฐั‚ั‹", + "All threads": "ะ’ัะต ะฒะตั‚ะบะธ", + "Shows all threads youโ€™ve participated in": "ะŸะพะบะฐะทั‹ะฒะฐะตั‚ ะฒัะต ะฒะตั‚ะบะธ, ะฒ ะบะพั‚ะพั€ั‹ั… ะฒั‹ ะฟั€ะธะฝะธะผะฐะปะธ ัƒั‡ะฐัั‚ะธะต", + "My threads": "ะœะพะธ ะฒะตั‚ะบะธ", + "Downloading": "ะ—ะฐะณั€ัƒะทะบะฐ", + "They'll still be able to access whatever you're not an admin of.": "ะžะฝะธ ะฟะพ-ะฟั€ะตะถะฝะตะผัƒ ัะผะพะณัƒั‚ ะฟะพะปัƒั‡ะธั‚ัŒ ะดะพัั‚ัƒะฟ ะบะพ ะฒัะตะผัƒ, ะณะดะต ะฒั‹ ะฝะต ัะฒะปัะตั‚ะตััŒ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ะพะผ.", + "Kick from %(roomName)s": "ะ’ั‹ะณะฝะฐั‚ัŒ ะธะท %(roomName)s", + "Disinvite from %(roomName)s": "ะžั‚ะผะตะฝะธั‚ัŒ ะฟั€ะธะณะปะฐัˆะตะฝะธะต ะธะท %(roomName)s", + "Threads": "ะ’ะตั‚ะบะธ", + "%(count)s reply|one": "%(count)s ะพั‚ะฒะตั‚", + "%(count)s reply|other": "%(count)s ะพั‚ะฒะตั‚ะพะฒ", + "View in room": "ะŸั€ะพัะผะพั‚ั€ะตั‚ัŒ ะฒ ะบะพะผะฝะฐั‚ะต", + "Enter your Security Phrase or to continue.": "ะ’ะฒะตะดะธั‚ะต ัะฒะพัŽ ัะตะบั€ะตั‚ะฝัƒัŽ ั„ั€ะฐะทัƒ ะธะปะธ ะดะปั ะฟั€ะพะดะพะปะถะตะฝะธั." } diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 9aad3d737fb..ade819129dd 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -3198,5 +3198,50 @@ "Are you sure you want to exit during this export?": "Jeni i sigurt se doni tรซ dilet gjatรซ kรซtij eksportimi?", "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s dรซrgoi njรซ ngjitรซs.", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s ndryshoi avatarin e dhomรซs.", - "%(date)s at %(time)s": "%(date)s mรซ %(time)s" + "%(date)s at %(time)s": "%(date)s mรซ %(time)s", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "Ju lutemi, ecni mรซ tej vetรซm nรซse jeni i sigurt se keni humbur krejt pajisjet tuaja tรซ tjera dhe kyรงin tuaj tรซ sigurisรซ.", + "I'll verify later": "Do ta verifikoj mรซ vonรซ", + "Verify with another login": "Verifikoje nga tjetรซr skenรซ hyrjesh", + "Verify with Security Key": "Verifikoje me Kyรง Sigurie", + "Verify with Security Key or Phrase": "Verifikojeni me Kyรง ose Frazรซ Sigurie", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "Duket sikur sโ€™keni Kyรง Sigurie ose ndonjรซ pajisje tjetรซr nga e cila mund tรซ bรซni verifikimin. Kjo pajisje sโ€™do tรซ jetรซ nรซ gjendje tรซ hyjรซ te mesazhe tรซ dikurshรซm tรซ fshehtรซzuar. Qรซ tรซ mund tรซ verifikohet identiteti juaj nรซ kรซtรซ pajisje, you'll need to reset your verification keys.", + "Skip verification for now": "Anashkaloje verifikimin hรซpรซrhรซ", + "Unable to verify this login": "Sโ€™arrihet tรซ verifikohet kjo hyrje", + "To proceed, please accept the verification request on your other login.": "Qรซ tรซ vazhdohet, ju lutemi, pranoni kรซrkesรซn pรซr verifikim nรซ skenรซn(pajisjen) tuaj tjetรซr pรซr hyrje.", + "Waiting for you to verify on your other sessionโ€ฆ": "Po pritet qรซ ju tรซ bรซni verifikimin nรซ sesionin tuaj tjetรซrโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Po pritet qรซ ju tรซ bรซni verifikimin nรซ sesionin tuaj tjetรซr, %(deviceName)s (%(deviceId)s)โ€ฆ", + "Creating Space...": "Po Krijohet Hapรซsirรซโ€ฆ", + "Fetching data...": "Po sillen tรซ dhรซnaโ€ฆ", + "Create poll": "Krijoni anketim", + "Polls (under active development)": "Anketime (nรซ zhvillim aktiv)", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Po pรซrditรซsohet hapรซsirรซโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Po pรซrditรซsohen hapรซsiraโ€ฆ (%(progress)s nga %(count)s) gjithsej", + "Sending invites... (%(progress)s out of %(count)s)|one": "Po dรซrgohen ftesaโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "Po dรซrgohen ftesaโ€ฆ (%(progress)s nga %(count)s) gjithsej", + "Loading new room": "Po ngarkohet dhomรซ e re", + "Upgrading room": "Pรซrmirรซsim dhome", + "Downloading": "Shkarkim", + "They won't be able to access whatever you're not an admin of.": "Sโ€™do tรซ jenรซ nรซ gjendje tรซ hyjnรซ kudo qoftรซ ku sโ€™jeni pรซrgjegjรซs.", + "Ban them from specific things I'm able to": "Dรซboji prej gjรซrash tรซ caktuara ku mundem ta bรซj kรซtรซ", + "Unban them from specific things I'm able to": "Hiqua dรซbimin prej gjรซrash tรซ caktuara ku mundem ta bรซj kรซtรซ", + "Ban them from everything I'm able to": "Dรซboji prej gjithรงkaje ku mundem ta bรซj kรซtรซ", + "Unban them from everything I'm able to": "Hiqua dรซbimin prej gjithรงkaje ku mundem ta bรซj kรซtรซ", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "Rikthimi te parazgjedhjet i kyรงeve tuaj tรซ verifikimit sโ€™mund tรซ zhbรซhet. Pas rikthimit te parazgjedhjet, sโ€™do tรซ mund tรซ hyni dot te mesazhe tรซ dikurshรซm tรซ fshehtรซzuar dhe, cilido shok qรซ ju ka verifikuar mรซ parรซ, do tรซ shohรซ njรซ sinjalizim sigurie deri sa tรซ ribรซni verifikimin me ta.", + "Proceed with reset": "Vazhdo me rikthimin te parazgjedhjet", + "Really reset verification keys?": "Tรซ kthehen vรซrtet te parazgjedhjet kyรงet e verifikimit?", + "Show:": "Shfaq:", + "Shows all threads from current room": "Shfaq krejt rrjedhat nรซ dhomรซn e tanishme", + "All threads": "Krejt rrjedhat", + "Shows all threads youโ€™ve participated in": "Shfaq krejt rrjedhat ku keni marrรซ pjesรซ", + "My threads": "Rrjedhat e mia", + "Ban from %(roomName)s": "Dรซboje prej %(roomName)s", + "Unban from %(roomName)s": "Hiqja dรซbimin prej %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "Do tรซ jenรซ prapรซ nรซ gjendje tรซ hyjnรซ kudo ku nuk jeni pรซrgjegjรซs.", + "Kick them from specific things I'm able to": "Pรซrzรซri prej gjรซrash specifike qรซ jam nรซ gjendje tรซ bรซj", + "Kick them from everything I'm able to": "Pรซrzรซri prej gjithรงkaje ku jam nรซ gjendje ta bรซj", + "Kick from %(roomName)s": "Pรซrzรซreni nga %(roomName)s", + "Disinvite from %(roomName)s": "Hiqja ftesรซn pรซr %(roomName)s", + "Threads": "Rrjedha", + "%(count)s reply|one": "%(count)s pรซrgjigje", + "%(count)s reply|other": "%(count)s pรซrgjigje" } diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 50ff9f5056f..cb008cc217a 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -241,7 +241,7 @@ "Noisy": "Hรถgljudd", "Room not found": "Rummet hittades inte", "Messages containing my display name": "Meddelanden som innehรฅller mitt visningsnamn", - "Messages in one-to-one chats": "Meddelanden i en-till-en chattar", + "Messages in one-to-one chats": "Meddelanden i en-till-en-chattar", "Unavailable": "Otillgรคnglig", "remove %(name)s from the directory.": "ta bort %(name)s frรฅn katalogen.", "Explore Room State": "Utforska rumsstatus", @@ -768,7 +768,7 @@ "Enable Community Filter Panel": "Aktivera gemenskapsfilterpanel", "Messages containing my username": "Meddelanden som innehรฅller mitt anvรคndarnamn", "Messages containing @room": "Meddelanden som innehรฅller @room", - "Encrypted messages in one-to-one chats": "Krypterade meddelanden i en-till-en chattar", + "Encrypted messages in one-to-one chats": "Krypterade meddelanden i en-till-en-chattar", "Encrypted messages in group chats": "Krypterade meddelanden i gruppchattar", "Dog": "Hund", "Cat": "Katt", @@ -3164,5 +3164,55 @@ "You are about to leave .": "Du kommer att lรคmna .", "Leave some rooms": "Lรคmna vissa rum", "Leave all rooms": "Lรคmna alla rum", - "Don't leave any rooms": "Lรคmna inga rum" + "Don't leave any rooms": "Lรคmna inga rum", + "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s skickade en dekal.", + "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s bytte rummets avatar.", + "%(date)s at %(time)s": "%(date)s vid %(time)s", + "Error fetching file": "Fel vid hรคmtning av fil", + "Topic: %(topic)s": "ร„mne: %(topic)s", + "This is the start of export of . Exported by at %(exportDate)s.": "Det hรคr รคr bรถrjan pรฅ exporten av . Exporterad av vid %(exportDate)s.", + "%(creatorName)s created this room.": "%(creatorName)s skapade det hรคr rummet.", + "Media omitted - file size limit exceeded": "Media uteslutet - filstorleksgrรคns รถverskriden", + "Media omitted": "Media uteslutet", + "Current Timeline": "Nuvarande tidslinje", + "Specify a number of messages": "Specificera ett antal meddelanden", + "From the beginning": "Frรฅn bรถrjan", + "Plain Text": "Vanlig text", + "JSON": "JSON", + "HTML": "HTML", + "Are you sure you want to exit during this export?": "ร„r du sรคker pรฅ att du vill avsluta under den hรคr exporten?", + "Creating Space...": "Skapar utrymmeโ€ฆ", + "Fetching data...": "Hรคmtar dataโ€ฆ", + "In reply to this message": "Som svar pรฅ detta meddelande", + "Expand quotes โ”‚ โ‡ง+click": "Expandera citat | โ‡ง+klick", + "Collapse quotes โ”‚ โ‡ง+click": "Kollapsa citat | โ‡ง+klick", + "Downloading": "Laddar ner", + "They won't be able to access whatever you're not an admin of.": "De kommer inte kunna komma รฅt saker du inte รคr admin fรถr.", + "Ban them from specific things I'm able to": "Banna dem frรฅn specifika saker jag kan", + "Unban them from specific things I'm able to": "Avbanna dem frรฅn specifika saker jag kan", + "Ban them from everything I'm able to": "Banna dem frรฅn allt jag kan", + "Unban them from everything I'm able to": "Avbanna dem frรฅn allt jag kan", + "Ban from %(roomName)s": "Banna frรฅn %(roomName)s", + "Unban from %(roomName)s": "Avbanna frรฅn %(roomName)s", + "They'll still be able to access whatever you're not an admin of.": "De kommer fortfarande kunna komma รฅt saker du inte รคr admin fรถr.", + "Kick them from specific things I'm able to": "Kicka dem frรฅn specifika saker jag kan", + "Kick them from everything I'm able to": "Kicka dem frรฅn allt jag kan", + "Kick from %(roomName)s": "Kicka frรฅn %(roomName)s", + "Disinvite from %(roomName)s": "Hรคv inbjudan frรฅn %(roomName)s", + "Export chat": "Exportera chatt", + "Threads": "Trรฅdar", + "To proceed, please accept the verification request on your other login.": "Fรถr att fortsรคtta, var god acceptera verifikationsfรถrfrรฅgan pรฅ din andra inloggning.", + "Create poll": "Skapa omrรถstning", + "%(count)s reply|one": "%(count)s svar", + "%(count)s reply|other": "%(count)s svar", + "Updating spaces... (%(progress)s out of %(count)s)|one": "Uppdaterar utrymmeโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|other": "Uppdaterar utrymmenโ€ฆ (%(progress)s av %(count)s)", + "Sending invites... (%(progress)s out of %(count)s)|one": "Skickar inbjudanโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "Skickar inbjudningarโ€ฆ (%(progress)s av %(count)s)", + "Loading new room": "Laddar nytt rum", + "Upgrading room": "Uppgraderar rum", + "Waiting for you to verify on your other sessionโ€ฆ": "Vรคntar pรฅ att du ska verifiera pรฅ din andra sessionโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "Vรคntar pรฅ att du ska verifiera pรฅ din andra session, %(deviceName)s (%(deviceId)s)โ€ฆ", + "Polls (under active development)": "Rรถstning (under aktiv utveckling)", + "File Attached": "Fil bifogad" } diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index 4fe75dd2ffa..2df149c484c 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -1724,7 +1724,7 @@ "Start a new chat": "ะŸะพั‡ะฐั‚ะธ ะฝะพะฒัƒ ะฑะตัั–ะดัƒ", "This is the start of .": "ะฆะต ะฟะพั‡ะฐั‚ะพะบ .", "Start sharing your screen": "ะŸะพั‡ะฐั‚ะธ ะฟะพะบะฐะท ะตะบั€ะฐะฝะฐ", - "Start the camera": "ะ—ะฐะฟัƒัั‚ะธั‚ะธ ะบะฐะผะตั€ัƒ", + "Start the camera": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะฐะผะตั€ัƒ", "Scan this unique code": "ะกะบะฐะฝัƒะนั‚ะต ั†ะตะน ัƒะฝั–ะบะฐะปัŒะฝะธะน ะบะพะด", "Verify this session by completing one of the following:": "ะ—ะฒั–ั€ั‚ะต ั†ะตะน ัะตะฐะฝั ะพะดะฝะธะผ ั–ะท ะทะฐะฟั€ะพะฟะพะฝะพะฒะฐะฝะธั… ัะฟะพัะพะฑั–ะฒ:", "Leave %(groupName)s?": "ะ’ะธะนั‚ะธ ะท %(groupName)s?", @@ -2136,5 +2136,13 @@ "Upload completed": "ะ’ะธะฒะฐะฝั‚ะฐะถะตะฝะฝั ะฒะธะบะพะฝะฐะฝะพ", "Search %(spaceName)s": "ะŸะพัˆัƒะบ %(spaceName)s", "Leave some rooms": "ะ’ะธะนั‚ะต ะท ะบั–ะปัŒะบะพั… ะบั–ะผะฝะฐั‚", - "Leave all rooms": "ะ’ะธะนั‚ะธ ะท ะบั–ะผะฝะฐั‚ะธ" + "Leave all rooms": "ะ’ะธะนั‚ะธ ะท ะบั–ะผะฝะฐั‚ะธ", + "More": "ะ‘ั–ะปัŒัˆะต", + "Show sidebar": "ะŸะพะบะฐะทะฐั‚ะธ ะฑั–ั‡ะฝัƒ ะฟะฐะฝะตะปัŒ", + "Hide sidebar": "ะกั…ะพะฒะฐั‚ะธ ะฑั–ั‡ะฝัƒ ะฟะฐะฝะตะปัŒ", + "Stop sharing your screen": "ะ’ะธะผะบะฝัƒั‚ะธ ะฟะพะบะฐะท ะตะบั€ะฐะฝะฐ", + "Stop the camera": "ะ’ะธะผะบะฝัƒั‚ะธ ะบะฐะผะตั€ัƒ", + "Your camera is still enabled": "ะ’ะฐัˆะฐ ะบะฐะผะตั€ะฐ ะดะพัั– ัƒะฒั–ะผะบะฝะตะฝะฐ", + "Your camera is turned off": "ะ’ะฐัˆัƒ ะบะฐะผะตั€ัƒ ะฒะธะผะบะฝะตะฝะพ", + "sends snowfall": "ะฝะฐะดัะธะปะฐั” ัะฝั–ะณะพะฟะฐะด" } diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index 7084e9f6493..c89cc953d07 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -429,7 +429,7 @@ "Flair": "ไธชๆ€งๅพฝ็ซ ", "Code": "ไปฃ็ ", "Remove from community": "ไปŽ็คพ็พคไธญ็งป้™ค", - "Disinvite this user from community?": "ๆ˜ฏๅฆไธๅ†้‚€่ฏทๆญค็”จๆˆทๅŠ ๅ…ฅๆœฌ็คพ็พค๏ผŸ", + "Disinvite this user from community?": "ๆ˜ฏๅฆๅ–ๆถˆ้‚€่ฏทๆญค็”จๆˆทๅŠ ๅ…ฅๆœฌ็คพ็พค๏ผŸ", "Remove this user from community?": "ๆ˜ฏๅฆ่ฆไปŽ็คพ็พคไธญ็งป้™คๆญค็”จๆˆท๏ผŸ", "Failed to withdraw invitation": "ๆ’คๅ›ž้‚€่ฏทๅคฑ่ดฅ", "Failed to remove user from community": "็งป้™ค็”จๆˆทๅคฑ่ดฅ", @@ -3201,5 +3201,53 @@ "Are you sure you want to exit during this export?": "ๆ‚จ็กฎๅฎš่ฆๅœจๅฏผๅ‡บๆœชๅฎŒๆˆๆ—ถ้€€ๅ‡บๅ—๏ผŸ", "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s ๅ‘้€ไบ†ไธ€ๅผ ่ดด็บธใ€‚", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s ๆ›ดๆ”นไบ†่Šๅคฉๅฎคๅคดๅƒใ€‚", - "%(date)s at %(time)s": "%(date)s ็š„ %(time)s" + "%(date)s at %(time)s": "%(date)s ็š„ %(time)s", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "้™ค้žไฝ ็กฎๅฎšไธขๅคฑไบ†ๆ‰€ๆœ‰ๅ…ถไป–่ฎพๅค‡ๅ’Œๅฎ‰ๅ…จๅฏ†้’ฅ๏ผŒๅฆๅˆ™ไธ่ฆ็ปง็ปญ้‡็ฝฎๆ“ไฝœใ€‚", + "Verify with Security Key or Phrase": "ไฝฟ็”จๅฎ‰ๅ…จๅฏ†้’ฅๆˆ–็Ÿญ่ฏญ่ฟ›่กŒ้ชŒ่ฏ", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "ๆ— ๆณ•ๆ’คๆถˆ้‡็ฝฎ้ชŒ่ฏๅฏ†้’ฅ็š„ๆ“ไฝœใ€‚้‡็ฝฎๅŽ๏ผŒไฝ ๅฐ†ๆ— ๆณ•่ฎฟ้—ฎๆ—ง็š„ๅŠ ๅฏ†ๆถˆๆฏ๏ผŒไปปไฝ•ไน‹ๅ‰้ชŒ่ฏ่ฟ‡ไฝ ็š„ๆœ‹ๅ‹ๅฐ†็œ‹ๅˆฐๅฎ‰ๅ…จ่ญฆๅ‘Š๏ผŒ็›ดๅˆฐไฝ ๅ†ๆฌกๅ’Œไป–ไปฌ่ฟ›่กŒ้ชŒ่ฏใ€‚", + "I'll verify later": "ๆˆ‘็จๅŽ่ฟ›่กŒ้ชŒ่ฏ", + "Verify with another login": "ไฝฟ็”จๅฆไธ€ไธช็™ปๅฝ•่ฟ›่กŒ้ชŒ่ฏ", + "Verify with Security Key": "ไฝฟ็”จๅฎ‰ๅ…จๅฏ†้’ฅ่ฟ›่กŒ้ชŒ่ฏ", + "Proceed with reset": "่ฟ›่กŒ้‡็ฝฎ", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "็œ‹่ตทๆฅไฝ ๆฒกๆœ‰ๅฎ‰ๅ…จๅฏ†้’ฅๆˆ–่€…ไปปไฝ•ๅ…ถไป–ๅฏไปฅ้ชŒ่ฏ็š„่ฎพๅค‡ใ€‚ ๆญค่ฎพๅค‡ๅฐ†ๆ— ๆณ•่ฎฟ้—ฎๆ—ง็š„ๅŠ ๅฏ†ไฟกๆฏใ€‚ไธบไบ†ๅœจ่ฟ™ไธช่ฎพๅค‡ไธŠ้ชŒ่ฏไฝ ็š„่บซไปฝ๏ผŒไฝ ้œ€่ฆ้‡็ฝฎไฝ ็š„้ชŒ่ฏๅฏ†้’ฅใ€‚", + "Skip verification for now": "ๆš‚ๆ—ถ่ทณ่ฟ‡้ชŒ่ฏ", + "Really reset verification keys?": "็กฎๅฎž่ฆ้‡็ฝฎ้ชŒ่ฏๅฏ†้’ฅ๏ผŸ", + "Unable to verify this login": "ๆ— ๆณ•้ชŒ่ฏๆญค็™ปๅฝ•", + "To proceed, please accept the verification request on your other login.": "็ปง็ปญ๏ผŒ่ฏทๆŽฅๅ—ๆ‚จๅ…ถไป–็™ปๅฝ•็š„้ชŒ่ฏ่ฏทๆฑ‚ใ€‚", + "Waiting for you to verify on your other sessionโ€ฆ": "็ญ‰ๅพ…ๆ‚จ้ชŒ่ฏๆ‚จ็š„ๅ…ถไป–ไผš่ฏโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "็ญ‰ๅพ…ๆ‚จ้ชŒ่ฏๆ‚จ็š„ๅ…ถไป–ไผš่ฏ๏ผŒ%(deviceName)s%(deviceId)sโ€ฆ", + "Creating Space...": "ๆญฃๅœจๅˆ›ๅปบ็ฉบ้—ดโ€ฆ", + "Fetching data...": "่Žทๅ–ๆ•ฐๆฎไธญโ€ฆ", + "Create poll": "ๅˆ›ๅปบๆŠ•็ฅจ", + "Polls (under active development)": "ๆŠ•็ฅจ (ๅœจ็งฏๆžๅผ€ๅ‘ไธญ)", + "Updating spaces... (%(progress)s out of %(count)s)|other": "ๆญฃๅœจๆ›ดๆ–ฐ่Šๅคฉๅฎคโ€ฆ ๏ผˆ%(count)s ไธญ็š„ %(progress)s๏ผ‰", + "Updating spaces... (%(progress)s out of %(count)s)|one": "ๆญฃๅœจๆ›ดๆ–ฐ็ฉบ้—ดโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|one": "ๆญฃๅœจๅ‘้€้‚€่ฏทโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "ๆญฃๅœจๅ‘้€้‚€่ฏทโ€ฆ ๏ผˆ%(count)s ไธญ็š„ %(progress)s๏ผ‰", + "Loading new room": "ๆญฃๅœจๅŠ ่ฝฝๆ–ฐ่Šๅคฉๅฎค", + "Upgrading room": "ๆญฃๅœจๅ‡็บง่Šๅคฉๅฎค", + "Kick from %(roomName)s": "ไปŽ %(roomName)s ไธญ่ธขๅ‡บๅŽป", + "Threads": "ๅธ–ๅญ", + "Disinvite from %(roomName)s": "ๅ–ๆถˆ้‚€่ฏทๅŠ ๅ…ฅ %(roomName)s", + "Show:": "ๆ˜พ็คบ๏ผš", + "Shows all threads from current room": "ๆ˜พ็คบๅฝ“ๅ‰่Šๅคฉๅฎค็š„ๆ‰€ๆœ‰ๅธ–ๅญ", + "All threads": "ๆ‰€ๆœ‰ๅธ–ๅญ", + "Shows all threads youโ€™ve participated in": "ๆ˜พ็คบๆ‰€ๆœ‰ไฝ ๅทฒๅ‚ไธŽ็š„ๅธ–ๅญ", + "My threads": "ๆˆ‘็š„ๅธ–ๅญ", + "They won't be able to access whatever you're not an admin of.": "ไป–ไปฌๅฐ†ๆ— ๆณ•่ฎฟ้—ฎไฝ ไธๆ˜ฏ็ฎก็†ๅ‘˜็š„ไธ€ๅˆ‡ใ€‚", + "Ban them from specific things I'm able to": "็ฆๆญข่ฟ™ไบ›ไบบๅšๆŸไบ›ๆˆ‘ๆœ‰ๆƒๅ†ณๅฎš็š„ไบ‹", + "Unban them from specific things I'm able to": "่งฃ้™คๆˆ‘ๆƒ้™่Œƒๅ›ดๅ†…ๅฏน่ฟ™ไบ›ไบบ็š„ๆŸไบ›็ฆไปค", + "Ban them from everything I'm able to": "็ฆๆญข่ฟ™ไบ›ไบบๅšไปปไฝ•ๆˆ‘ๆœ‰ๆƒๅ†ณๅฎš็š„ไบ‹", + "Unban them from everything I'm able to": "่งฃ้™คๆˆ‘ๆƒ้™่Œƒๅ›ดๅ†…ๅฏน่ฟ™ไบ›ไบบ็š„ๆ‰€ๆœ‰็ฆไปค", + "Ban from %(roomName)s": "็ฆๆญข่ฟ›ๅ…ฅ %(roomName)s", + "Unban from %(roomName)s": "่งฃ้™ค %(roomName)s ็ฆไปค", + "They'll still be able to access whatever you're not an admin of.": "่ฟ™ไบ›ไบบไป็„ถๅฏไปฅ่ฎฟ้—ฎไฝ ไธๆ˜ฏ็ฎก็†ๅ‘˜็š„ไธ€ๅˆ‡ไธœ่ฅฟใ€‚", + "Kick them from specific things I'm able to": "็ฆๆญข่ฟ™ไบ›ไบบๅšๆŸไบ›ๆˆ‘ๆœ‰ๆƒๅ†ณๅฎš็š„ไบ‹", + "Kick them from everything I'm able to": "็ฆๆญข่ฟ™ไบ›ไบบๅšไปปไฝ•ๆˆ‘ๆœ‰ๆƒๅ†ณๅฎš็š„ไบ‹", + "Downloading": "ไธ‹่ฝฝไธญ", + "%(count)s reply|one": "%(count)s ๆกๅ›žๅค", + "%(count)s reply|other": "%(count)s ๆกๅ›žๅค", + "View in room": "ๅœจ่Šๅคฉๅฎคๅ†…ๆŸฅ็œ‹", + "Enter your Security Phrase or to continue.": "่พ“ๅ…ฅๅฎ‰ๅ…จ็Ÿญ่ฏญๆˆ–ไปฅ็ปง็ปญใ€‚", + "What projects are your team working on?": "ไฝ ็š„ๅ›ข้˜Ÿๆญฃๅœจ่ฟ›่กŒไป€ไนˆ้กน็›ฎ?" } diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 798f7a99c2c..c2e50cce4c7 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3204,5 +3204,52 @@ "Are you sure you want to exit during this export?": "ๆ‚จ็ขบๅฎšๆ‚จ่ฆๅพžๆญคๅŒฏๅ‡บๆต็จ‹ไธญ้€€ๅ‡บๅ—Ž๏ผŸ", "%(senderDisplayName)s sent a sticker.": "%(senderDisplayName)s ๅ‚ณ้€ไบ†่ฒผๅœ–ใ€‚", "%(senderDisplayName)s changed the room avatar.": "%(senderDisplayName)s ่ฎŠๆ›ดไบ†่Šๅคฉๅฎคๅคง้ ญ็…งใ€‚", - "%(date)s at %(time)s": "%(date)s ๆ–ผ %(time)s" + "%(date)s at %(time)s": "%(date)s ๆ–ผ %(time)s", + "Please only proceed if you're sure you've lost all of your other devices and your security key.": "่ซ‹ๅƒ…ๅœจๆ‚จ็ขบๅฎšๆ‚จ้บๅคฑๆ‚จๆ‰€ๆœ‰ๅ…ถไป–่ฃ็ฝฎ่ˆ‡ๆ‚จ็š„ๅฎ‰ๅ…จ้‡‘้‘ฐๆ™‚็นผ็บŒใ€‚", + "Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.": "้‡่จญๆ‚จ็š„้ฉ—่ญ‰้‡‘้‘ฐๅฐ‡็„กๆณ•ๅพฉๅŽŸใ€‚้‡่จญๅพŒ๏ผŒๆ‚จๅฐ‡็„กๆณ•ๅญ˜ๅ–่ˆŠ็š„ๅŠ ๅฏ†่จŠๆฏ๏ผŒไน‹ๅ‰ไปปไฝ•้ฉ—่ญ‰้Žๆ‚จ็š„ๆœ‹ๅ‹ไนŸๆœƒ็œ‹ๅˆฐๅฎ‰ๅ…จ่ญฆๅ‘Š๏ผŒ็›ดๅˆฐๆ‚จ้‡ๆ–ฐ้ฉ—่ญ‰ใ€‚", + "I'll verify later": "ๆˆ‘็จๅพŒ้ฉ—่ญ‰", + "Verify with another login": "ไฝฟ็”จๅ…ถไป–็™ปๅ…ฅ้€ฒ่กŒ้ฉ—่ญ‰", + "Verify with Security Key": "ไฝฟ็”จๅฎ‰ๅ…จ้‡‘้‘ฐ้€ฒ่กŒ้ฉ—่ญ‰", + "Verify with Security Key or Phrase": "ไฝฟ็”จๅฎ‰ๅ…จ้‡‘้‘ฐๆˆ–ๅฏ†่ชž้€ฒ่กŒ้ฉ—่ญ‰", + "Proceed with reset": "็นผ็บŒ้‡่จญ", + "It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.": "ๆ‚จไผผไนŽๆฒ’ๆœ‰ๅฎ‰ๅ…จ้‡‘้‘ฐๆˆ–ๅ…ถไป–ๅฏไปฅ้ฉ—่ญ‰็š„่ฃ็ฝฎใ€‚ๆญค่ฃ็ฝฎๅฐ‡็„กๆณ•ๅญ˜ๅ–่ˆŠ็š„ๅŠ ๅฏ†่จŠๆฏใ€‚็‚บไบ†ๅœจๆญค่ฃ็ฝฎไธŠ้ฉ—่ญ‰ๆ‚จ็š„่บซไปฝ๏ผŒๆ‚จๅฟ…้ ˆ้‡่จญๆ‚จ็š„้ฉ—่ญ‰้‡‘้‘ฐใ€‚", + "Skip verification for now": "ๆšซๆ™‚็•ฅ้Ž้ฉ—่ญ‰", + "Really reset verification keys?": "็œŸ็š„่ฆ้‡่จญ้ฉ—่ญ‰้‡‘้‘ฐ๏ผŸ", + "Unable to verify this login": "็„กๆณ•้ฉ—่ญ‰ๆญค็™ปๅ…ฅ", + "To proceed, please accept the verification request on your other login.": "่ฆ็นผ็บŒ๏ผŒ่ซ‹ๅœจๆ‚จ็š„ๅ…ถไป–่ฃ็ฝฎไธŠๆŽฅๅ—้ฉ—่ญ‰่ซ‹ๆฑ‚ใ€‚", + "Waiting for you to verify on your other sessionโ€ฆ": "ๆญฃๅœจ็ญ‰ๅพ…ๆ‚จ้ฉ—่ญ‰ๆ‚จ็š„ๅ…ถไป–ๅทฅไฝœ้šŽๆฎตโ€ฆโ€ฆ", + "Waiting for you to verify on your other session, %(deviceName)s (%(deviceId)s)โ€ฆ": "ๆญฃๅœจ็ญ‰ๅพ…ๆ‚จ้ฉ—่ญ‰ๆ‚จ็š„ๅ…ถไป–ๅทฅไฝœ้šŽๆฎต๏ผŒ%(deviceName)s (%(deviceId)s)โ€ฆโ€ฆ", + "Creating Space...": "ๆญฃๅœจๅปบ็ซ‹็ฉบ้–“โ€ฆโ€ฆ", + "Fetching data...": "ๆญฃๅœจๆ“ทๅ–่ณ‡ๆ–™โ€ฆโ€ฆ", + "Create poll": "ๅปบ็ซ‹ๆŠ•็ฅจ", + "Polls (under active development)": "ๆŠ•็ฅจ๏ผˆๆญฃๅœจ็ฉๆฅต้–‹็™ผไธญ๏ผ‰", + "Show:": "้กฏ็คบ๏ผš", + "Shows all threads from current room": "้กฏ็คบๅพž็›ฎๅ‰่Šๅคฉๅฎค่€Œไพ†็š„ๆ‰€ๆœ‰่จŽ่ซ–ไธฒ", + "All threads": "ๆ‰€ๆœ‰่จŽ่ซ–ไธฒ", + "Shows all threads youโ€™ve participated in": "้กฏ็คบๆ‚จๅƒ่ˆ‡็š„ๆ‰€ๆœ‰่จŽ่ซ–ไธฒ", + "My threads": "ๆˆ‘็š„่จŽ่ซ–ไธฒ", + "They won't be able to access whatever you're not an admin of.": "ไป–ๅ€‘ๅฐ‡็„กๆณ•ๅญ˜ๅ–ๆ‚จไธๆ˜ฏ็ฎก็†ๅ“ก็š„ไปปไฝ•ๅœฐๆ–นใ€‚", + "Ban them from specific things I'm able to": "ๅพžๆˆ‘ๆœ‰ๆฌŠ้™็š„็‰นๅฎšๅœฐๆ–นๅฐ้Ž–ไป–ๅ€‘", + "Unban them from specific things I'm able to": "ๅพžๆˆ‘ๆœ‰ๆฌŠ้™็š„็‰นๅฎšๅœฐๆ–นๅ–ๆถˆๅฐ้Ž–ไป–ๅ€‘", + "Ban them from everything I'm able to": "ๅพžๆˆ‘ๆœ‰ๆฌŠ้™็š„ๆ‰€ๆœ‰ๅœฐๆ–นๅฐ้Ž–ไป–ๅ€‘", + "Unban them from everything I'm able to": "ๅพžๆˆ‘ๆœ‰ๆฌŠ้™็š„ๆ‰€ๆœ‰ๅœฐๆ–นๅ–ๆถˆๅฐ้Ž–ไป–ๅ€‘", + "Ban from %(roomName)s": "ๅพž %(roomName)s ๅฐ้Ž–", + "Unban from %(roomName)s": "ๅพž %(roomName)s ๅ–ๆถˆๅฐ้Ž–", + "They'll still be able to access whatever you're not an admin of.": "ไป–ๅ€‘ไป็„ถๅฏไปฅๅญ˜ๅ–ๆ‚จไธๆ˜ฏ็ฎก็†ๅ“ก็š„ไปปไฝ•ๅœฐๆ–นใ€‚", + "Kick them from specific things I'm able to": "ๅพžๆˆ‘ๆœ‰ๆฌŠ้™็š„็‰นๅฎšๅœฐๆ–น่ธข้™คไป–ๅ€‘", + "Kick them from everything I'm able to": "ๅพžๆˆ‘ๆœ‰ๆฌŠ้™็š„ๆ‰€ๆœ‰ๅœฐๆ–น่ธข้™คไป–ๅ€‘", + "Kick from %(roomName)s": "ๅพž %(roomName)s ่ธขๅ‡บ", + "Disinvite from %(roomName)s": "ๆ‹’็ต•ไพ†่‡ช %(roomName)s ็š„้‚€่ซ‹", + "Threads": "่จŽ่ซ–ไธฒ", + "Updating spaces... (%(progress)s out of %(count)s)|one": "ๆญฃๅœจๆ›ดๆ–ฐ็ฉบ้–“โ€ฆโ€ฆ", + "Updating spaces... (%(progress)s out of %(count)s)|other": "ๆญฃๅœจๆ›ดๆ–ฐ็ฉบ้–“โ€ฆโ€ฆ๏ผˆ%(count)s ไธญ็š„็ฌฌ %(progress)s ๅ€‹๏ผ‰", + "Sending invites... (%(progress)s out of %(count)s)|one": "ๆญฃๅœจๅ‚ณ้€้‚€่ซ‹โ€ฆโ€ฆ", + "Sending invites... (%(progress)s out of %(count)s)|other": "ๆญฃๅœจๅ‚ณ้€้‚€่ซ‹โ€ฆโ€ฆ๏ผˆ%(count)s ไธญ็š„็ฌฌ %(progress)s ๅ€‹๏ผ‰", + "Loading new room": "ๆญฃๅœจ่ผ‰ๅ…ฅๆ–ฐ็š„่Šๅคฉๅฎค", + "Upgrading room": "ๆญฃๅœจๅ‡็ดš่Šๅคฉๅฎค", + "Downloading": "ๆญฃๅœจไธ‹่ผ‰", + "%(count)s reply|one": "%(count)s ๅ›ž่ฆ†", + "%(count)s reply|other": "%(count)s ๅ›ž่ฆ†", + "View in room": "ๅœจ่Šๅคฉๅฎคไธญๆชข่ฆ–", + "Enter your Security Phrase or to continue.": "่ผธๅ…ฅๆ‚จ็š„ๅฎ‰ๅ…จๅฏ†่ชžๆˆ–ไปฅ็นผ็บŒใ€‚" } diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts index 1c182cf9129..ad715e09cc4 100644 --- a/src/indexing/EventIndex.ts +++ b/src/indexing/EventIndex.ts @@ -583,7 +583,7 @@ export default class EventIndex extends EventEmitter { if (eventId) { await indexManager.deleteEvent(eventId); } else { - console.warn("EventIndex: Redaction event doesn't contain a valid associated event id", ev); + logger.warn("EventIndex: Redaction event doesn't contain a valid associated event id", ev); } } diff --git a/src/integrations/IntegrationManagerInstance.ts b/src/integrations/IntegrationManagerInstance.ts index c3dcd41ee8d..51376ae2905 100644 --- a/src/integrations/IntegrationManagerInstance.ts +++ b/src/integrations/IntegrationManagerInstance.ts @@ -24,6 +24,8 @@ import SettingsStore from "../settings/SettingsStore"; import IntegrationManager from "../components/views/settings/IntegrationManager"; import { IntegrationManagers } from "./IntegrationManagers"; +import { logger } from "matrix-js-sdk/src/logger"; + export enum Kind { Account = "account", Config = "config", @@ -94,7 +96,7 @@ export class IntegrationManagerInstance { return; } - console.error(e); + logger.error(e); newProps["connected"] = false; } diff --git a/src/integrations/IntegrationManagers.ts b/src/integrations/IntegrationManagers.ts index a16792b1933..051b19279d2 100644 --- a/src/integrations/IntegrationManagers.ts +++ b/src/integrations/IntegrationManagers.ts @@ -224,19 +224,19 @@ export class IntegrationManagers { const result = await fetch(`https://${domainName}/.well-known/matrix/integrations`); wkConfig = await result.json(); } catch (e) { - console.error(e); - console.warn("Failed to locate integration manager"); + logger.error(e); + logger.warn("Failed to locate integration manager"); return null; } if (!wkConfig || !wkConfig["m.integrations_widget"]) { - console.warn("Missing integrations widget on .well-known response"); + logger.warn("Missing integrations widget on .well-known response"); return null; } const widget = wkConfig["m.integrations_widget"]; if (!widget["url"] || !widget["data"] || !widget["data"]["api_url"]) { - console.warn("Malformed .well-known response for integrations widget"); + logger.warn("Malformed .well-known response for integrations widget"); return null; } diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index ad3e34c3f0e..63804b2a2cf 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -89,11 +89,11 @@ function safeCounterpartTranslate(text: string, options?: object) { count = options['count']; Object.keys(options).forEach((k) => { if (options[k] === undefined) { - console.warn("safeCounterpartTranslate called with undefined interpolation name: " + k); + logger.warn("safeCounterpartTranslate called with undefined interpolation name: " + k); options[k] = 'undefined'; } if (options[k] === null) { - console.warn("safeCounterpartTranslate called with null interpolation name: " + k); + logger.warn("safeCounterpartTranslate called with null interpolation name: " + k); options[k] = 'null'; } }); @@ -353,7 +353,7 @@ export function setLanguage(preferredLangs: string | string[]) { if (!langToUse) { // Fallback to en_EN if none is found langToUse = 'en'; - console.error("Unable to find an appropriate language"); + logger.error("Unable to find an appropriate language"); } return getLanguageRetry(i18nFolder + availLangs[langToUse].fileName); @@ -521,7 +521,7 @@ function weblateToCounterpart(inTrs: object): object { async function getLanguageRetry(langPath: string, num = 3): Promise { return retry(() => getLanguage(langPath), num, e => { logger.log("Failed to load i18n", langPath); - console.error(e); + logger.error(e); return true; // always retry }); } diff --git a/src/notifications/VectorPushRulesDefinitions.ts b/src/notifications/VectorPushRulesDefinitions.ts index a8c617e786b..772a7a62ab0 100644 --- a/src/notifications/VectorPushRulesDefinitions.ts +++ b/src/notifications/VectorPushRulesDefinitions.ts @@ -20,6 +20,8 @@ import { PushRuleVectorState, VectorState } from "./PushRuleVectorState"; import { NotificationUtils } from "./NotificationUtils"; import { PushRuleAction, PushRuleKind } from "matrix-js-sdk/src/@types/PushRules"; +import { logger } from "matrix-js-sdk/src/logger"; + type StateToActionsMap = { [state in VectorState]?: PushRuleAction[]; }; @@ -70,7 +72,7 @@ class VectorPushRuleDefinition { } } - console.error(`Cannot translate rule actions into Vector rule state. ` + + logger.error(`Cannot translate rule actions into Vector rule state. ` + `Rule: ${JSON.stringify(rule)}, ` + `Expected: ${JSON.stringify(this.vectorStateToActions)}`); return undefined; diff --git a/src/performance/index.ts b/src/performance/index.ts index cb808f91735..d2330dfb75f 100644 --- a/src/performance/index.ts +++ b/src/performance/index.ts @@ -16,6 +16,8 @@ limitations under the License. import { PerformanceEntryNames } from "./entry-names"; +import { logger } from "matrix-js-sdk/src/logger"; + interface GetEntriesOptions { name?: string; type?: string; @@ -57,7 +59,7 @@ export default class PerformanceMonitor { const key = this.buildKey(name, id); if (performance.getEntriesByName(this.START_PREFIX + key).length > 0) { - console.warn(`Recording already started for: ${name}`); + logger.warn(`Recording already started for: ${name}`); return; } @@ -77,7 +79,7 @@ export default class PerformanceMonitor { } const key = this.buildKey(name, id); if (performance.getEntriesByName(this.START_PREFIX + key).length === 0) { - console.warn(`No recording started for: ${name}`); + logger.warn(`No recording started for: ${name}`); return; } diff --git a/src/rageshake/rageshake.ts b/src/rageshake/rageshake.ts index acf77c31c0b..f275b15a411 100644 --- a/src/rageshake/rageshake.ts +++ b/src/rageshake/rageshake.ts @@ -162,7 +162,7 @@ export class IndexedDBLogStore { // @ts-ignore "Failed to open log database: " + event.target.error.name ); - console.error(err); + logger.error(err); reject(new Error(err)); }; @@ -245,7 +245,7 @@ export class IndexedDBLogStore { resolve(); }; txn.onerror = (event) => { - console.error( + logger.error( "Failed to flush logs : ", event, ); reject( @@ -384,7 +384,7 @@ export class IndexedDBLogStore { Promise.all(removeLogIds.map((id) => deleteLogs(id))).then(() => { logger.log(`Removed ${removeLogIds.length} old logs.`); }, (err) => { - console.error(err); + logger.error(err); }); } return logs; diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index d2f55689888..1222ca9231e 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -182,7 +182,7 @@ export default class SettingsStore { */ public static unwatchSetting(watcherReference: string) { if (!SettingsStore.watchers.has(watcherReference)) { - console.warn(`Ending non-existent watcher ID ${watcherReference}`); + logger.warn(`Ending non-existent watcher ID ${watcherReference}`); return; } @@ -550,7 +550,7 @@ export default class SettingsStore { logger.log(`--- ${handlerName}@${roomId || ''} = ${JSON.stringify(value)}`); } catch (e) { logger.log(`--- ${handler}@${roomId || ''} THREW ERROR: ${e.message}`); - console.error(e); + logger.error(e); } if (roomId) { @@ -559,7 +559,7 @@ export default class SettingsStore { logger.log(`--- ${handlerName}@ = ${JSON.stringify(value)}`); } catch (e) { logger.log(`--- ${handler}@ THREW ERROR: ${e.message}`); - console.error(e); + logger.error(e); } } } @@ -572,7 +572,7 @@ export default class SettingsStore { logger.log(`--- SettingsStore#generic@${roomId || ''} = ${JSON.stringify(value)}`); } catch (e) { logger.log(`--- SettingsStore#generic@${roomId || ''} THREW ERROR: ${e.message}`); - console.error(e); + logger.error(e); } if (roomId) { @@ -581,7 +581,7 @@ export default class SettingsStore { logger.log(`--- SettingsStore#generic@ = ${JSON.stringify(value)}`); } catch (e) { logger.log(`--- SettingsStore#generic@$ THREW ERROR: ${e.message}`); - console.error(e); + logger.error(e); } } @@ -591,7 +591,7 @@ export default class SettingsStore { logger.log(`--- SettingsStore#${level}@${roomId || ''} = ${JSON.stringify(value)}`); } catch (e) { logger.log(`--- SettingsStore#${level}@${roomId || ''} THREW ERROR: ${e.message}`); - console.error(e); + logger.error(e); } if (roomId) { @@ -600,7 +600,7 @@ export default class SettingsStore { logger.log(`--- SettingsStore#${level}@ = ${JSON.stringify(value)}`); } catch (e) { logger.log(`--- SettingsStore#${level}@$ THREW ERROR: ${e.message}`); - console.error(e); + logger.error(e); } } } diff --git a/src/settings/controllers/NotificationControllers.ts b/src/settings/controllers/NotificationControllers.ts index 09e4e1dd1aa..a7114325ae0 100644 --- a/src/settings/controllers/NotificationControllers.ts +++ b/src/settings/controllers/NotificationControllers.ts @@ -23,6 +23,8 @@ import { SettingLevel } from "../SettingLevel"; import { PushProcessor } from "matrix-js-sdk/src/pushprocessor"; import { PushRuleActionName } from "matrix-js-sdk/src/@types/PushRules"; +import { logger } from "matrix-js-sdk/src/logger"; + // .m.rule.master being enabled means all events match that push rule // default action on this rule is dont_notify, but it could be something else export function isPushNotifyDisabled(): boolean { @@ -31,7 +33,7 @@ export function isPushNotifyDisabled(): boolean { const masterRule = processor.getPushRuleById(".m.rule.master"); if (!masterRule) { - console.warn("No master push rule! Notifications are disabled for this user."); + logger.warn("No master push rule! Notifications are disabled for this user."); return true; } diff --git a/src/settings/handlers/MatrixClientBackedSettingsHandler.ts b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts index 76825d13358..a475787f335 100644 --- a/src/settings/handlers/MatrixClientBackedSettingsHandler.ts +++ b/src/settings/handlers/MatrixClientBackedSettingsHandler.ts @@ -17,6 +17,8 @@ limitations under the License. import SettingsHandler from "./SettingsHandler"; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { logger } from "matrix-js-sdk/src/logger"; + // Dev note: This whole class exists in the event someone logs out and back in - we want // to make sure the right MatrixClient is listening for changes. @@ -48,6 +50,6 @@ export default abstract class MatrixClientBackedSettingsHandler extends Settings } protected initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) { - console.warn("initMatrixClient not overridden"); + logger.warn("initMatrixClient not overridden"); } } diff --git a/src/stores/CommunityPrototypeStore.ts b/src/stores/CommunityPrototypeStore.ts index 0c364430ea3..6c5ab22c5f4 100644 --- a/src/stores/CommunityPrototypeStore.ts +++ b/src/stores/CommunityPrototypeStore.ts @@ -28,6 +28,8 @@ import GroupStore from "./GroupStore"; import dis from "../dispatcher/dispatcher"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState { // nothing of value - we use account data } @@ -134,7 +136,7 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient { // we use global account data because per-room account data on invites is unreliable await this.matrixClient.setAccountData("im.vector.group_info." + room.roomId, profile); } catch (e) { - console.warn("Non-fatal error getting group information for invite:", e); + logger.warn("Non-fatal error getting group information for invite:", e); } } } else if (payload.action === "MatrixActions.accountData") { diff --git a/src/stores/FlairStore.js b/src/stores/FlairStore.js index 155d9beaf47..971efd6cf60 100644 --- a/src/stores/FlairStore.js +++ b/src/stores/FlairStore.js @@ -98,12 +98,12 @@ class FlairStore extends EventEmitter { }).catch((err) => { // Indicate whether the homeserver supports groups if (err.errcode === 'M_UNRECOGNIZED') { - console.warn('Cannot display flair, server does not support groups'); + logger.warn('Cannot display flair, server does not support groups'); groupSupport = false; // Return silently to avoid spamming for non-supporting servers return; } - console.error('Could not get groups for user', userId, err); + logger.error('Could not get groups for user', userId, err); throw err; }).finally(() => { delete this._usersInFlight[userId]; diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js index 63972b31fb7..0934cd9f90d 100644 --- a/src/stores/GroupStore.js +++ b/src/stores/GroupStore.js @@ -20,6 +20,8 @@ import FlairStore from './FlairStore'; import { MatrixClientPeg } from '../MatrixClientPeg'; import dis from '../dispatcher/dispatcher'; +import { logger } from "matrix-js-sdk/src/logger"; + export function parseMembersResponse(response) { return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember)); } @@ -144,7 +146,7 @@ class GroupStore extends EventEmitter { return; } - console.error(`Failed to get resource ${stateKey} for ${groupId}`, err); + logger.error(`Failed to get resource ${stateKey} for ${groupId}`, err); this.emit('error', err, groupId, stateKey); }).finally(() => { // Indicate finished request, allow for future fetches diff --git a/src/stores/ModalWidgetStore.ts b/src/stores/ModalWidgetStore.ts index 851cff3549b..abbf87d827f 100644 --- a/src/stores/ModalWidgetStore.ts +++ b/src/stores/ModalWidgetStore.ts @@ -22,6 +22,8 @@ import ModalWidgetDialog from "../components/views/dialogs/ModalWidgetDialog"; import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore"; import { IModalWidgetOpenRequestData, IModalWidgetReturnData, Widget } from "matrix-widget-api"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState { modal?: IModal; openedFromId?: string; @@ -81,7 +83,7 @@ export class ModalWidgetStore extends AsyncStoreWithClient { const sourceMessaging = WidgetMessagingStore.instance.getMessaging(sourceWidget); if (!sourceMessaging) { - console.error("No source widget messaging for modal widget"); + logger.error("No source widget messaging for modal widget"); return; } sourceMessaging.notifyModalWidgetClose(data); diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts index b6f91bf8357..f9e0a578eb3 100644 --- a/src/stores/RightPanelStore.ts +++ b/src/stores/RightPanelStore.ts @@ -23,6 +23,8 @@ import { ActionPayload } from "../dispatcher/payloads"; import { Action } from '../dispatcher/actions'; import { SettingLevel } from "../settings/SettingLevel"; +import { logger } from "matrix-js-sdk/src/logger"; + interface RightPanelStoreState { // Whether or not to show the right panel at all. We split out rooms and groups // because they're different flows for the user to follow. @@ -180,7 +182,7 @@ export default class RightPanelStore extends Store { } } if (!RightPanelPhases[targetPhase]) { - console.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); + logger.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); return; } diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 0415044d3a3..1a44b4fb326 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -32,6 +32,7 @@ import { retry } from "../utils/promise"; import CountlyAnalytics from "../CountlyAnalytics"; import { logger } from "matrix-js-sdk/src/logger"; +import { TimelineRenderingType } from "../contexts/RoomContext"; const NUM_JOIN_RETRY = 5; @@ -153,16 +154,19 @@ class RoomViewStore extends Store { case 'reply_to_event': // If currently viewed room does not match the room in which we wish to reply then change rooms // this can happen when performing a search across all rooms - if (payload.event && payload.event.getRoomId() !== this.state.roomId) { - dis.dispatch({ - action: 'view_room', - room_id: payload.event.getRoomId(), - replyingToEvent: payload.event, - }); - } else { - this.setState({ - replyingToEvent: payload.event, - }); + if (payload.context === TimelineRenderingType.Room) { + if (payload.event + && payload.event.getRoomId() !== this.state.roomId) { + dis.dispatch({ + action: 'view_room', + room_id: payload.event.getRoomId(), + replyingToEvent: payload.event, + }); + } else { + this.setState({ + replyingToEvent: payload.event, + }); + } } break; case 'open_room_settings': { @@ -235,7 +239,7 @@ class RoomViewStore extends Store { storeRoomAliasInCache(payload.room_alias, result.room_id); roomId = result.room_id; } catch (err) { - console.error("RVS failed to get room id for alias: ", err); + logger.error("RVS failed to get room id for alias: ", err); dis.dispatch({ action: 'view_room_error', room_id: null, diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts index e3d4d425914..eefe98c7a27 100644 --- a/src/stores/SetupEncryptionStore.ts +++ b/src/stores/SetupEncryptionStore.ts @@ -248,7 +248,7 @@ export class SetupEncryptionStore extends EventEmitter { this.phase = Phase.Finished; }, true); } catch (e) { - console.error("Error resetting cross-signing", e); + logger.error("Error resetting cross-signing", e); this.phase = Phase.Intro; } this.emit("update"); diff --git a/src/stores/SpaceStore.ts b/src/stores/SpaceStore.ts index b4a1889d3e0..4ba10813253 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/SpaceStore.ts @@ -41,6 +41,8 @@ import { reorderLexicographically } from "../utils/stringOrderField"; import { TAG_ORDER } from "../components/views/rooms/RoomList"; import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload"; +import { logger } from "matrix-js-sdk/src/logger"; + type SpaceKey = string | symbol; interface IState {} @@ -261,7 +263,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { viaServers: Array.from(viaMap.get(roomInfo.room_id) || []), })); } catch (e) { - console.error(e); + logger.error(e); } return []; }; @@ -865,7 +867,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { try { await this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order }); } catch (e) { - console.warn("Failed to set root space order", e); + logger.warn("Failed to set root space order", e); if (this.spaceOrderLocalEchoMap.get(space.roomId) === order) { this.spaceOrderLocalEchoMap.delete(space.roomId); } diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 44c8327c047..ebe761aa80a 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -126,7 +126,7 @@ export default class WidgetStore extends AsyncStoreWithClient { // Sanity check for https://github.com/vector-im/element-web/issues/15705 const existingApp = this.widgetMap.get(widgetUid(app)); if (existingApp) { - console.warn( + logger.warn( `Possible widget ID conflict for ${app.id} - wants to store in room ${app.roomId} ` + `but is currently stored as ${existingApp.roomId} - letting the want win`, ); diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 44ec173e083..ab22baf5d1a 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -27,6 +27,7 @@ import { CallHangupEvent } from "./previews/CallHangupEvent"; import { StickerEventPreview } from "./previews/StickerEventPreview"; import { ReactionEventPreview } from "./previews/ReactionEventPreview"; import { UPDATE_EVENT } from "../AsyncStore"; +import { Thread } from "matrix-js-sdk/src/models/thread"; // Emitted event for when a room's preview has changed. First argument will the room for which // the change happened. @@ -108,6 +109,15 @@ export class MessagePreviewStore extends AsyncStoreWithClient { return previews.get(inTagId); } + public generateThreadPreview(thread: Thread): string { + const lastEvent = thread.replyToEvent; + const previewDef = PREVIEWS[lastEvent.getType()]; + // TODO: Handle case where we don't have + if (!previewDef) return ''; + const previewText = previewDef.previewer.getTextFor(lastEvent, null, true); + return previewText ?? ''; + } + private async generatePreview(room: Room, tagId?: TagID) { const events = room.timeline; if (!events) return; // should only happen in tests diff --git a/src/stores/room-list/RoomListLayoutStore.ts b/src/stores/room-list/RoomListLayoutStore.ts index 1443448a64d..d5b31fed935 100644 --- a/src/stores/room-list/RoomListLayoutStore.ts +++ b/src/stores/room-list/RoomListLayoutStore.ts @@ -20,6 +20,8 @@ import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { ActionPayload } from "../../dispatcher/payloads"; +import { logger } from "matrix-js-sdk/src/logger"; + interface IState {} export default class RoomListLayoutStore extends AsyncStoreWithClient { @@ -53,7 +55,7 @@ export default class RoomListLayoutStore extends AsyncStoreWithClient { // Note: this primarily exists for debugging, and isn't really intended to be used by anything. public async resetLayouts() { - console.warn("Resetting layouts for room list"); + logger.warn("Resetting layouts for room list"); for (const layout of this.layoutMap.values()) { layout.reset(); } diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index ff90cb8caac..c4b1f012b1b 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -162,7 +162,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } else if (activeRoomId) { const activeRoom = this.matrixClient.getRoom(activeRoomId); if (!activeRoom) { - console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`); + logger.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`); this.algorithm.setStickyRoom(null); } else if (activeRoom !== this.algorithm.stickyRoom) { this.algorithm.setStickyRoom(activeRoom); @@ -226,7 +226,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { if (readReceiptChangeIsFor(payload.event, this.matrixClient)) { const room = payload.room; if (!room) { - console.warn(`Own read receipt was in unknown room ${room.roomId}`); + logger.warn(`Own read receipt was in unknown room ${room.roomId}`); return; } await this.handleRoomUpdate(room, RoomUpdateCause.ReadReceipt); @@ -258,8 +258,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient { this.updateFn.trigger(); }; if (!room) { - console.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`); - console.warn(`Queuing failed room update for retry as a result.`); + logger.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`); + logger.warn(`Queuing failed room update for retry as a result.`); setTimeout(async () => { const updatedRoom = this.matrixClient.getRoom(roomId); await tryUpdate(updatedRoom); @@ -276,7 +276,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } const room = this.matrixClient.getRoom(roomId); if (!room) { - console.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`); + logger.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`); return; } await this.handleRoomUpdate(room, RoomUpdateCause.Timeline); @@ -289,7 +289,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { for (const roomId of roomIds) { const room = this.matrixClient.getRoom(roomId); if (!room) { - console.warn(`${roomId} was found in DMs but the room is not in the store`); + logger.warn(`${roomId} was found in DMs but the room is not in the store`); continue; } @@ -550,7 +550,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient { * be used if the calling code will manually trigger the update. */ public regenerateAllLists({ trigger = true }) { - console.warn("Regenerating all room lists"); + logger.warn("Regenerating all room lists"); const rooms = this.getPlausibleRooms(); diff --git a/src/stores/room-list/TagWatcher.ts b/src/stores/room-list/TagWatcher.ts index 7e809283e9c..09f6ec13cd4 100644 --- a/src/stores/room-list/TagWatcher.ts +++ b/src/stores/room-list/TagWatcher.ts @@ -19,6 +19,8 @@ import GroupFilterOrderStore from "../GroupFilterOrderStore"; import { CommunityFilterCondition } from "./filters/CommunityFilterCondition"; import { arrayDiff, arrayHasDiff } from "../../utils/arrays"; +import { logger } from "matrix-js-sdk/src/logger"; + /** * Watches for changes in groups to manage filters on the provided RoomListStore */ @@ -37,7 +39,7 @@ export class TagWatcher { // Selected tags changed, do some filtering if (!this.store.matrixClient) { - console.warn("Tag update without an associated matrix client - ignoring"); + logger.warn("Tag update without an associated matrix client - ignoring"); return; } @@ -47,7 +49,7 @@ export class TagWatcher { for (const tag of filterableTags) { const group = this.store.matrixClient.getGroup(tag); if (!group) { - console.warn(`Group selected with no group object available: ${tag}`); + logger.warn(`Group selected with no group object available: ${tag}`); continue; } diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 1e2606686d0..5e98ef37ffb 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -36,6 +36,8 @@ import { getListAlgorithmInstance } from "./list-ordering"; import { VisibilityProvider } from "../filters/VisibilityProvider"; import SpaceStore from "../../SpaceStore"; +import { logger } from "matrix-js-sdk/src/logger"; + /** * Fired when the Algorithm has determined a list has been updated. */ @@ -126,7 +128,7 @@ export class Algorithm extends EventEmitter { try { this.updateStickyRoom(val); } catch (e) { - console.warn("Failed to update sticky room", e); + logger.warn("Failed to update sticky room", e); } } @@ -241,7 +243,7 @@ export class Algorithm extends EventEmitter { // to force the position to zero (top) to ensure we can properly handle it. const wasSticky = this._lastStickyRoom.room ? this._lastStickyRoom.room.roomId === val.roomId : false; if (this._lastStickyRoom.tag && tag !== this._lastStickyRoom.tag && wasSticky && position < 0) { - console.warn(`Sticky room ${val.roomId} changed tags during sticky room handling`); + logger.warn(`Sticky room ${val.roomId} changed tags during sticky room handling`); position = 0; } @@ -278,13 +280,13 @@ export class Algorithm extends EventEmitter { if (this._stickyRoom.room !== val) { // Check the room IDs just in case if (this._stickyRoom.room.roomId === val.roomId) { - console.warn("Sticky room changed references"); + logger.warn("Sticky room changed references"); } else { throw new Error("Sticky room changed while the sticky room was changing"); } } - console.warn(`Sticky room changed tag & position from ${tag} / ${position} ` + logger.warn(`Sticky room changed tag & position from ${tag} / ${position} ` + `to ${this._stickyRoom.tag} / ${this._stickyRoom.position}`); tag = this._stickyRoom.tag; @@ -322,7 +324,7 @@ export class Algorithm extends EventEmitter { return; } - console.warn("Recalculating filtered room list"); + logger.warn("Recalculating filtered room list"); const filters = Array.from(this.allowedByFilter.keys()); const newMap: ITagMap = {}; for (const tagId of Object.keys(this.cachedRooms)) { @@ -491,7 +493,7 @@ export class Algorithm extends EventEmitter { // We only log this if we're expecting to be publishing updates, which means that // this could be an unexpected invocation. If we're inhibited, then this is probably // an intentional invocation. - console.warn("Resetting known rooms, initiating regeneration"); + logger.warn("Resetting known rooms, initiating regeneration"); } // Before we go any further we need to clear (but remember) the sticky room to @@ -657,18 +659,18 @@ export class Algorithm extends EventEmitter { // pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus // lose the room. if (hasTags && !isForLastSticky) { - console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); + logger.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); cause = RoomUpdateCause.PossibleTagChange; } // Check to see if the room is known first let knownRoomRef = this.rooms.includes(room); if (hasTags && !knownRoomRef) { - console.warn(`${room.roomId} might be a reference change - attempting to update reference`); + logger.warn(`${room.roomId} might be a reference change - attempting to update reference`); this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r); knownRoomRef = this.rooms.includes(room); if (!knownRoomRef) { - console.warn(`${room.roomId} is still not referenced. It may be sticky.`); + logger.warn(`${room.roomId} is still not referenced. It may be sticky.`); } } @@ -766,7 +768,7 @@ export class Algorithm extends EventEmitter { const tags = this.roomIdsToTags[room.roomId]; if (!tags) { - console.warn(`No tags known for "${room.name}" (${room.roomId})`); + logger.warn(`No tags known for "${room.name}" (${room.roomId})`); return false; } diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts index 3cc393305eb..ef1f4629be5 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -23,6 +23,8 @@ import { OrderingAlgorithm } from "./OrderingAlgorithm"; import { NotificationColor } from "../../../notifications/NotificationColor"; import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore"; +import { logger } from "matrix-js-sdk/src/logger"; + interface ICategorizedRoomMap { // @ts-ignore - TS wants this to be a string, but we know better [category: NotificationColor]: Room[]; @@ -130,7 +132,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } else if (cause === RoomUpdateCause.RoomRemoved) { const roomIdx = this.getRoomIndex(room); if (roomIdx === -1) { - console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`); + logger.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`); return false; // no change } const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices); @@ -263,7 +265,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { if (indices[lastCat] > indices[thisCat]) { // "should never happen" disclaimer goes here - console.warn( + logger.warn( `!! Room list index corruption: ${lastCat} (i:${indices[lastCat]}) is greater ` + `than ${thisCat} (i:${indices[thisCat]}) - category indices are likely desynced from reality`); diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts index 91182dee167..726987e7343 100644 --- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts @@ -20,6 +20,8 @@ import { OrderingAlgorithm } from "./OrderingAlgorithm"; import { RoomUpdateCause, TagID } from "../../models"; import { Room } from "matrix-js-sdk/src/models/room"; +import { logger } from "matrix-js-sdk/src/logger"; + /** * Uses the natural tag sorting algorithm order to determine tag ordering. No * additional behavioural changes are present. @@ -47,7 +49,7 @@ export class NaturalAlgorithm extends OrderingAlgorithm { if (idx >= 0) { this.cachedOrderedRooms.splice(idx, 1); } else { - console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`); + logger.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`); } } diff --git a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts index 9d7b5f9ddb5..3afcd4f9d59 100644 --- a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts @@ -18,6 +18,8 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { RoomUpdateCause, TagID } from "../../models"; import { SortAlgorithm } from "../models"; +import { logger } from "matrix-js-sdk/src/logger"; + /** * Represents a list ordering algorithm. Subclasses should populate the * `cachedOrderedRooms` field. @@ -71,7 +73,7 @@ export abstract class OrderingAlgorithm { protected getRoomIndex(room: Room): number { let roomIdx = this.cachedOrderedRooms.indexOf(room); if (roomIdx === -1) { // can only happen if the js-sdk's store goes sideways. - console.warn(`Degrading performance to find missing room in "${this.tagId}": ${room.roomId}`); + logger.warn(`Degrading performance to find missing room in "${this.tagId}": ${room.roomId}`); roomIdx = this.cachedOrderedRooms.findIndex(r => r.roomId === room.roomId); } return roomIdx; diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts index 961f27fda1d..b8e3119ad4f 100644 --- a/src/stores/room-list/previews/MessageEventPreview.ts +++ b/src/stores/room-list/previews/MessageEventPreview.ts @@ -19,11 +19,11 @@ import { TagID } from "../models"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t, sanitizeForTranslation } from "../../../languageHandler"; import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; -import ReplyThread from "../../../components/views/elements/ReplyThread"; +import ReplyChain from "../../../components/views/elements/ReplyChain"; import { getHtmlText } from "../../../HtmlUtils"; export class MessageEventPreview implements IPreview { - public getTextFor(event: MatrixEvent, tagId?: TagID): string { + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { let eventContent = event.getContent(); if (event.isRelation("m.replace")) { @@ -47,9 +47,9 @@ export class MessageEventPreview implements IPreview { if (mRelatesTo && mRelatesTo['m.in_reply_to']) { // If this is a reply, get the real reply and use that if (hasHtml) { - body = (ReplyThread.stripHTMLReply(body) || '').trim(); + body = (ReplyChain.stripHTMLReply(body) || '').trim(); } else { - body = (ReplyThread.stripPlainReply(body) || '').trim(); + body = (ReplyChain.stripPlainReply(body) || '').trim(); } if (!body) return null; // invalid event, no preview } @@ -64,7 +64,7 @@ export class MessageEventPreview implements IPreview { return _t("* %(senderName)s %(emote)s", { senderName: getSenderName(event), emote: body }); } - if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { return body; } else { return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: body }); diff --git a/src/stores/room-list/previews/ReactionEventPreview.ts b/src/stores/room-list/previews/ReactionEventPreview.ts index 25f8e0b61aa..4e2c175055a 100644 --- a/src/stores/room-list/previews/ReactionEventPreview.ts +++ b/src/stores/room-list/previews/ReactionEventPreview.ts @@ -23,7 +23,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import DMRoomMap from "../../../utils/DMRoomMap"; export class ReactionEventPreview implements IPreview { - public getTextFor(event: MatrixEvent, tagId?: TagID): string { + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms"); const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all"); @@ -41,7 +41,7 @@ export class ReactionEventPreview implements IPreview { const reaction = relation.key; if (!reaction) return null; // invalid reaction (unknown format) - if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { return reaction; } else { return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction }); diff --git a/src/stores/room-list/previews/StickerEventPreview.ts b/src/stores/room-list/previews/StickerEventPreview.ts index 56746568af2..6ad43ef3e13 100644 --- a/src/stores/room-list/previews/StickerEventPreview.ts +++ b/src/stores/room-list/previews/StickerEventPreview.ts @@ -21,11 +21,11 @@ import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; import { _t } from "../../../languageHandler"; export class StickerEventPreview implements IPreview { - public getTextFor(event: MatrixEvent, tagId?: TagID): string { + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string { const stickerName = event.getContent()['body']; if (!stickerName) return null; - if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { return stickerName; } else { return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName }); diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index e00c4c6c0b5..1293fe05483 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -401,7 +401,7 @@ export class StopGapWidget extends EventEmitter { } } catch (e) { // All errors are non-fatal - console.error("Error preparing widget communications: ", e); + logger.error("Error preparing widget communications: ", e); } } @@ -473,7 +473,7 @@ export class StopGapWidget extends EventEmitter { const raw = ev.getEffectiveEvent(); this.messaging.feedEvent(raw, this.eventListenerRoomId).catch(e => { - console.error("Error sending event to widget: ", e); + logger.error("Error sending event to widget: ", e); }); } } diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index 058a605380d..e8f550b92c4 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -46,6 +46,8 @@ import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permali import { IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from "matrix-js-sdk/src/models/room"; +import { logger } from "matrix-js-sdk/src/logger"; + // TODO: Purge this from the universe function getRememberedCapabilitiesForWidget(widget: Widget): Capability[] { @@ -122,7 +124,7 @@ export class StopGapWidgetDriver extends WidgetDriver { (result.approved || []).forEach(cap => allowedSoFar.add(cap)); rememberApproved = result.remember; } catch (e) { - console.error("Non-fatal error getting capabilities: ", e); + logger.error("Non-fatal error getting capabilities: ", e); } } diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index bad87db2b93..ae804381cf3 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -20,6 +20,8 @@ import { _t, _td, newTranslatableError } from "../languageHandler"; import { makeType } from "./TypeUtils"; import SdkConfig from '../SdkConfig'; +import { logger } from "matrix-js-sdk/src/logger"; + const LIVELINESS_DISCOVERY_ERRORS: string[] = [ AutoDiscovery.ERROR_INVALID_HOMESERVER, AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER, @@ -198,7 +200,7 @@ export default class AutoDiscoveryUtils { if (!discoveryResult || !discoveryResult["m.homeserver"]) { // This shouldn't happen without major misconfiguration, so we'll log a bit of information // in the log so we can find this bit of codee but otherwise tell teh user "it broke". - console.error("Ended up in a state of not knowing which homeserver to connect to."); + logger.error("Ended up in a state of not knowing which homeserver to connect to."); throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); } @@ -218,7 +220,7 @@ export default class AutoDiscoveryUtils { if (isResult && isResult.state === AutoDiscovery.SUCCESS) { preferredIdentityUrl = isResult["base_url"]; } else if (isResult && isResult.state !== AutoDiscovery.PROMPT) { - console.error("Error determining preferred identity server URL:", isResult); + logger.error("Error determining preferred identity server URL:", isResult); if (isResult.state === AutoDiscovery.FAIL_ERROR) { if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error) !== -1) { throw newTranslatableError(isResult.error); @@ -234,7 +236,7 @@ export default class AutoDiscoveryUtils { } if (hsResult.state !== AutoDiscovery.SUCCESS) { - console.error("Error processing homeserver config:", hsResult); + logger.error("Error processing homeserver config:", hsResult); if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(hsResult.error)) { if (AutoDiscovery.ALL_ERRORS.indexOf(hsResult.error) !== -1) { throw newTranslatableError(hsResult.error); @@ -251,7 +253,7 @@ export default class AutoDiscoveryUtils { // It should have been set by now, so check it if (!preferredHomeserverName) { - console.error("Failed to parse homeserver name from homeserver URL"); + logger.error("Failed to parse homeserver name from homeserver URL"); throw newTranslatableError(_td("Unexpected error resolving homeserver configuration")); } diff --git a/src/utils/DMRoomMap.ts b/src/utils/DMRoomMap.ts index 50f849b2ba0..089279cdcc0 100644 --- a/src/utils/DMRoomMap.ts +++ b/src/utils/DMRoomMap.ts @@ -20,6 +20,8 @@ import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClientPeg } from '../MatrixClientPeg'; +import { logger } from "matrix-js-sdk/src/logger"; + /** * Class that takes a Matrix Client and flips the m.direct map * so the operation of mapping a room ID to which user it's a DM @@ -196,7 +198,7 @@ export default class DMRoomMap { // to avoid multiple devices fighting to correct // the account data, only try to send the corrected // version once. - console.warn(`Invalid m.direct account data detected ` + + logger.warn(`Invalid m.direct account data detected ` + `(self-chats that shouldn't be), patching it up.`); if (neededPatching && !this.hasSentOutPatchDirectAccountDataPatch) { this.hasSentOutPatchDirectAccountDataPatch = true; diff --git a/src/utils/FontManager.ts b/src/utils/FontManager.ts index b0a017be97c..197e4eda00e 100644 --- a/src/utils/FontManager.ts +++ b/src/utils/FontManager.ts @@ -39,9 +39,9 @@ function safariVersionCheck(ua: string): boolean { return colrFontSupported; } } catch (err) { - console.error("Error in Safari COLR version check", err); + logger.error("Error in Safari COLR version check", err); } - console.warn("Couldn't determine Safari version to check COLR font support, assuming no."); + logger.warn("Couldn't determine Safari version to check COLR font support, assuming no."); return false; } @@ -96,7 +96,7 @@ async function isColrFontSupported(): Promise { logger.log("Canvas check revealed COLR is supported? " + colrFontSupported); return colrFontSupported; } catch (e) { - console.error("Couldn't load COLR font", e); + logger.error("Couldn't load COLR font", e); return false; } } diff --git a/src/utils/IdentityServerUtils.ts b/src/utils/IdentityServerUtils.ts index 47226a17274..ef432f71df4 100644 --- a/src/utils/IdentityServerUtils.ts +++ b/src/utils/IdentityServerUtils.ts @@ -19,6 +19,8 @@ import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types'; import SdkConfig from '../SdkConfig'; import { MatrixClientPeg } from '../MatrixClientPeg'; +import { logger } from "matrix-js-sdk/src/logger"; + export function getDefaultIdentityServerUrl(): string { return SdkConfig.get()['validated_server_config']['isUrl']; } @@ -36,7 +38,7 @@ export async function doesIdentityServerHaveTerms(fullUrl: string): Promise = (w: Whenable) => void; /** @@ -73,7 +75,7 @@ export abstract class Whenable implements IDestroyable { try { listener.fn(this); } catch (e) { - console.error(`Error calling whenable listener for ${condition}:`, e); + logger.error(`Error calling whenable listener for ${condition}:`, e); } } } diff --git a/src/utils/WidgetUtils.ts b/src/utils/WidgetUtils.ts index ea56f2a563c..106ae4bfda1 100644 --- a/src/utils/WidgetUtils.ts +++ b/src/utils/WidgetUtils.ts @@ -31,6 +31,8 @@ import { objectClone } from "./objects"; import { _t } from "../languageHandler"; import { IApp } from "../stores/WidgetStore"; +import { logger } from "matrix-js-sdk/src/logger"; + // How long we wait for the state event echo to come back from the server // before waitFor[Room/User]Widget rejects its promise const WIDGET_WAIT_TIME = 20000; @@ -53,30 +55,30 @@ export default class WidgetUtils { */ static canUserModifyWidgets(roomId: string): boolean { if (!roomId) { - console.warn('No room ID specified'); + logger.warn('No room ID specified'); return false; } const client = MatrixClientPeg.get(); if (!client) { - console.warn('User must be be logged in'); + logger.warn('User must be be logged in'); return false; } const room = client.getRoom(roomId); if (!room) { - console.warn(`Room ID ${roomId} is not recognised`); + logger.warn(`Room ID ${roomId} is not recognised`); return false; } const me = client.credentials.userId; if (!me) { - console.warn('Failed to get user ID'); + logger.warn('Failed to get user ID'); return false; } if (room.getMyMembership() !== "join") { - console.warn(`User ${me} is not in room ${roomId}`); + logger.warn(`User ${me} is not in room ${roomId}`); return false; } @@ -92,7 +94,7 @@ export default class WidgetUtils { */ static isScalarUrl(testUrlString: string): boolean { if (!testUrlString) { - console.error('Scalar URL check failed. No URL specified'); + logger.error('Scalar URL check failed. No URL specified'); return false; } @@ -246,7 +248,7 @@ export default class WidgetUtils { try { delete userWidgets[widgetId]; } catch (e) { - console.error(`$widgetId is non-configurable`); + logger.error(`$widgetId is non-configurable`); } const addingWidget = Boolean(widgetUrl); diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts index db28508c683..b23c1153e04 100644 --- a/src/utils/exportUtils/Exporter.ts +++ b/src/utils/exportUtils/Exporter.ts @@ -29,6 +29,8 @@ import { saveAs } from "file-saver"; import { _t } from "../../languageHandler"; import SdkConfig from "../../SdkConfig"; +import { logger } from "matrix-js-sdk/src/logger"; + type BlobFile = { name: string; blob: Blob; @@ -61,7 +63,7 @@ export default abstract class Exporter { } protected updateProgress(progress: string, log = true, show = true): void { - if (log) console.log(progress); + if (log) logger.log(progress); if (show) this.setProgressText(progress); } @@ -91,13 +93,13 @@ export default abstract class Exporter { } protected cleanUp(): string { - console.log("Cleaning up..."); + logger.log("Cleaning up..."); window.removeEventListener("beforeunload", this.onBeforeUnload); return ""; } public async cancelExport(): Promise { - console.log("Cancelling export..."); + logger.log("Cancelling export..."); this.cancelled = true; } @@ -212,7 +214,7 @@ export default abstract class Exporter { blob = await image.blob(); } } catch (err) { - console.log("Error decrypting media"); + logger.log("Error decrypting media"); } return blob; } diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx index 5f8413fad89..f7d3bb5e09a 100644 --- a/src/utils/exportUtils/HtmlExport.tsx +++ b/src/utils/exportUtils/HtmlExport.tsx @@ -38,6 +38,8 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import getExportCSS from "./exportCSS"; import { textForEvent } from "../../TextForEvent"; +import { logger } from "matrix-js-sdk/src/logger"; + export default class HTMLExporter extends Exporter { protected avatars: Map; protected permalinkCreator: RoomPermalinkCreator; @@ -70,7 +72,7 @@ export default class HTMLExporter extends Exporter { this.totalSize += blob.size; this.addFile(avatarPath, blob); } catch (err) { - console.log("Failed to fetch room's avatar" + err); + logger.log("Failed to fetch room's avatar" + err); } } const avatar = ( @@ -238,7 +240,7 @@ export default class HTMLExporter extends Exporter { const blob = await image.blob(); this.addFile(`users/${member.userId.replace(/:/g, '-')}.png`, blob); } catch (err) { - console.log("Failed to fetch user's avatar" + err); + logger.log("Failed to fetch user's avatar" + err); } } } @@ -362,7 +364,7 @@ export default class HTMLExporter extends Exporter { this.addFile(filePath, blob); } } catch (e) { - console.log("Error while fetching file" + e); + logger.log("Error while fetching file" + e); eventTile = await this.getEventTileMarkup( this.createModifiedEvent(_t("Error fetching file"), mxEv), joined, @@ -377,7 +379,7 @@ export default class HTMLExporter extends Exporter { } else eventTile = await this.getEventTileMarkup(mxEv, joined); } catch (e) { // TODO: Handle callEvent errors - console.error(e); + logger.error(e); eventTile = await this.getEventTileMarkup( this.createModifiedEvent(textForEvent(mxEv), mxEv, false), joined, @@ -430,7 +432,7 @@ export default class HTMLExporter extends Exporter { const exportEnd = performance.now(); if (this.cancelled) { - console.info("Export cancelled successfully"); + logger.info("Export cancelled successfully"); } else { this.updateProgress("Export successful!"); this.updateProgress(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`); diff --git a/src/utils/exportUtils/JSONExport.ts b/src/utils/exportUtils/JSONExport.ts index 4d862e69e31..6061f33cf61 100644 --- a/src/utils/exportUtils/JSONExport.ts +++ b/src/utils/exportUtils/JSONExport.ts @@ -23,6 +23,8 @@ import { ExportType } from "./exportUtils"; import { IExportOptions } from "./exportUtils"; import { EventType } from "matrix-js-sdk/src/@types/event"; +import { logger } from "matrix-js-sdk/src/logger"; + export default class JSONExporter extends Exporter { protected totalSize = 0; protected messages: Record[] = []; @@ -67,7 +69,7 @@ export default class JSONExporter extends Exporter { this.addFile(filePath, blob); } } catch (err) { - console.log("Error fetching file: " + err); + logger.log("Error fetching file: " + err); } } const jsonEvent: any = mxEv.toJSON(); @@ -87,16 +89,16 @@ export default class JSONExporter extends Exporter { } public async export() { - console.info("Starting export process..."); - console.info("Fetching events..."); + logger.info("Starting export process..."); + logger.info("Fetching events..."); const fetchStart = performance.now(); const res = await this.getRequiredEvents(); const fetchEnd = performance.now(); - console.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart)/1000}s`); + logger.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart)/1000}s`); - console.info("Creating output..."); + logger.info("Creating output..."); const text = await this.createOutput(res); if (this.files.length) { @@ -110,10 +112,10 @@ export default class JSONExporter extends Exporter { const exportEnd = performance.now(); if (this.cancelled) { - console.info("Export cancelled successfully"); + logger.info("Export cancelled successfully"); } else { - console.info("Export successful!"); - console.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`); + logger.info("Export successful!"); + logger.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`); } this.cleanUp(); diff --git a/src/utils/exportUtils/PlainTextExport.ts b/src/utils/exportUtils/PlainTextExport.ts index 8e3ba81c4e0..e3201314f95 100644 --- a/src/utils/exportUtils/PlainTextExport.ts +++ b/src/utils/exportUtils/PlainTextExport.ts @@ -24,6 +24,8 @@ import { ExportType } from "./exportUtils"; import { IExportOptions } from "./exportUtils"; import { textForEvent } from "../../TextForEvent"; +import { logger } from "matrix-js-sdk/src/logger"; + export default class PlainTextExporter extends Exporter { protected totalSize: number; protected mediaOmitText: string; @@ -94,7 +96,7 @@ export default class PlainTextExporter extends Exporter { } } catch (error) { mediaText = " (" + _t("Error fetching file") + ")"; - console.log("Error fetching file " + error); + logger.log("Error fetching file " + error); } } else mediaText = ` (${this.mediaOmitText})`; } @@ -123,7 +125,7 @@ export default class PlainTextExporter extends Exporter { const res = await this.getRequiredEvents(); const fetchEnd = performance.now(); - console.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart)/1000}s`); + logger.log(`Fetched ${res.length} events in ${(fetchEnd - fetchStart)/1000}s`); this.updateProgress("Creating output..."); const text = await this.createOutput(res); @@ -139,10 +141,10 @@ export default class PlainTextExporter extends Exporter { const exportEnd = performance.now(); if (this.cancelled) { - console.info("Export cancelled successfully"); + logger.info("Export cancelled successfully"); } else { - console.info("Export successful!"); - console.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`); + logger.info("Export successful!"); + logger.log(`Exported ${res.length} events in ${(exportEnd - fetchStart)/1000} seconds`); } this.cleanUp(); diff --git a/src/utils/exportUtils/exportCustomCSS.css b/src/utils/exportUtils/exportCustomCSS.css index 284f54ac085..aa403be5e81 100644 --- a/src/utils/exportUtils/exportCustomCSS.css +++ b/src/utils/exportUtils/exportCustomCSS.css @@ -119,7 +119,7 @@ a.mx_reply_anchor:hover { } } -.mx_ReplyThread_Export { +.mx_ReplyChain_Export { margin-top: 0px; margin-bottom: 5px; } diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index cff7ebc08b3..4c05f42a0da 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -28,6 +28,8 @@ import ElementPermalinkConstructor from "./ElementPermalinkConstructor"; import matrixLinkify from "../../linkify-matrix"; import SdkConfig from "../../SdkConfig"; +import { logger } from "matrix-js-sdk/src/logger"; + // The maximum number of servers to pick when working out which servers // to add to permalinks. The servers are appended as ?via=example.org const MAX_SERVER_CANDIDATES = 3; @@ -109,7 +111,7 @@ export class RoomPermalinkCreator { // currentState, at least potentially at the early stages of joining a room. // To avoid breaking everything, we'll just warn rather than throw as well as // not bother updating the various aspects of the share link. - console.warn("Tried to load a permalink creator with no room state"); + logger.warn("Tried to load a permalink creator with no room state"); return; } this.updateAllowedServers(); diff --git a/src/utils/strings.ts b/src/utils/strings.ts index 6d12c889403..1ca7aee6301 100644 --- a/src/utils/strings.ts +++ b/src/utils/strings.ts @@ -13,7 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - /** * Copy plaintext to user's clipboard * It will overwrite user's selection range @@ -21,6 +20,8 @@ limitations under the License. * Tries to use new async clipboard API if available * @param text the plaintext to put in the user's clipboard */ +import { logger } from "matrix-js-sdk/src/logger"; + export async function copyPlaintext(text: string): Promise { try { if (navigator && navigator.clipboard && navigator.clipboard.writeText) { @@ -49,7 +50,7 @@ export async function copyPlaintext(text: string): Promise { return successful; } } catch (e) { - console.error("copyPlaintext failed", e); + logger.error("copyPlaintext failed", e); } return false; } diff --git a/src/widgets/ManagedHybrid.ts b/src/widgets/ManagedHybrid.ts index 2adb11da622..f623becbd8c 100644 --- a/src/widgets/ManagedHybrid.ts +++ b/src/widgets/ManagedHybrid.ts @@ -23,6 +23,8 @@ import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetStore from "../stores/WidgetStore"; import SdkConfig from "../SdkConfig"; +import { logger } from "matrix-js-sdk/src/logger"; + /* eslint-disable camelcase */ interface IManagedHybridWidgetData { widget_id: string; @@ -52,7 +54,7 @@ export async function addManagedHybridWidget(roomId: string) { // Check for permission if (!WidgetUtils.canUserModifyWidgets(roomId)) { - console.error(`User not allowed to modify widgets in ${roomId}`); + logger.error(`User not allowed to modify widgets in ${roomId}`); return; } @@ -67,7 +69,7 @@ export async function addManagedHybridWidget(roomId: string) { const response = await fetch(`${widgetBuildUrl}?roomId=${roomId}`); widgetData = await response.json(); } catch (e) { - console.error(`Managed hybrid widget builder failed for room ${roomId}`, e); + logger.error(`Managed hybrid widget builder failed for room ${roomId}`, e); return; } if (!widgetData) { @@ -82,7 +84,7 @@ export async function addManagedHybridWidget(roomId: string) { WidgetEchoStore.roomHasPendingWidgets(roomId, []) ); if (existing) { - console.error(`Managed hybrid widget already present in room ${roomId}`); + logger.error(`Managed hybrid widget already present in room ${roomId}`); return; } @@ -90,7 +92,7 @@ export async function addManagedHybridWidget(roomId: string) { try { await WidgetUtils.setRoomWidgetContent(roomId, widgetId, widgetContent); } catch (e) { - console.error(`Unable to add managed hybrid widget in room ${roomId}`, e); + logger.error(`Unable to add managed hybrid widget in room ${roomId}`, e); return; } diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index a5efbb570cd..d46cfa903b4 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -142,7 +142,7 @@ describe("ForwardDialog", () => { }); const wrapper = await mountForwardDialog(replyMessage); - expect(wrapper.find("ReplyThread")).toBeTruthy(); + expect(wrapper.find("ReplyChain")).toBeTruthy(); }); it("disables buttons for rooms without send permissions", async () => { diff --git a/test/components/views/elements/ReplyThread-test.js b/test/components/views/elements/ReplyChain-test.js similarity index 93% rename from test/components/views/elements/ReplyThread-test.js rename to test/components/views/elements/ReplyChain-test.js index ee81c2f210c..35344929e47 100644 --- a/test/components/views/elements/ReplyThread-test.js +++ b/test/components/views/elements/ReplyChain-test.js @@ -1,8 +1,8 @@ import "../../../skinned-sdk"; import * as testUtils from '../../../test-utils'; -import ReplyThread from '../../../../src/components/views/elements/ReplyThread'; +import ReplyChain from '../../../../src/components/views/elements/ReplyChain'; -describe("ReplyThread", () => { +describe("ReplyChain", () => { describe('getParentEventId', () => { it('retrieves relation reply from unedited event', () => { const originalEventWithRelation = testUtils.mkEvent({ @@ -21,7 +21,7 @@ describe("ReplyThread", () => { room: "room_id", }); - expect(ReplyThread.getParentEventId(originalEventWithRelation)) + expect(ReplyChain.getParentEventId(originalEventWithRelation)) .toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og'); }); @@ -65,7 +65,7 @@ describe("ReplyThread", () => { originalEventWithRelation.makeReplaced(editEvent); // The relation should be pulled from the original event - expect(ReplyThread.getParentEventId(originalEventWithRelation)) + expect(ReplyChain.getParentEventId(originalEventWithRelation)) .toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og'); }); @@ -109,7 +109,7 @@ describe("ReplyThread", () => { originalEvent.makeReplaced(editEvent); // The relation should be pulled from the edit event - expect(ReplyThread.getParentEventId(originalEvent)) + expect(ReplyChain.getParentEventId(originalEvent)) .toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og'); }); @@ -158,7 +158,7 @@ describe("ReplyThread", () => { originalEventWithRelation.makeReplaced(editEvent); // The relation should be pulled from the edit event - expect(ReplyThread.getParentEventId(originalEventWithRelation)).toStrictEqual('$999'); + expect(ReplyChain.getParentEventId(originalEventWithRelation)).toStrictEqual('$999'); }); it('able to clear relation reply from original event by providing empty relation field', () => { @@ -203,7 +203,7 @@ describe("ReplyThread", () => { originalEventWithRelation.makeReplaced(editEvent); // The relation should be pulled from the edit event - expect(ReplyThread.getParentEventId(originalEventWithRelation)).toStrictEqual(undefined); + expect(ReplyChain.getParentEventId(originalEventWithRelation)).toStrictEqual(undefined); }); }); }); diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx index e6bac2b96ef..ec4894719e5 100644 --- a/test/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/components/views/rooms/SendMessageComposer-test.tsx @@ -216,6 +216,7 @@ describe('', () => { expect(spyDispatcher).toHaveBeenCalledWith({ action: "reply_to_event", event: mockEvent, + context: TimelineRenderingType.Room, }); // now try with localStorage wiped out @@ -277,6 +278,7 @@ describe('', () => { expect(spyDispatcher).toHaveBeenCalledWith({ action: "reply_to_event", event: null, + context: TimelineRenderingType.Room, }); expect(wrapper.text()).toBe(""); diff --git a/test/components/views/settings/CryptographyPanel-test.tsx b/test/components/views/settings/CryptographyPanel-test.tsx new file mode 100644 index 00000000000..56056b53ec1 --- /dev/null +++ b/test/components/views/settings/CryptographyPanel-test.tsx @@ -0,0 +1,38 @@ +import '../../../skinned-sdk'; +import * as TestUtils from '../../../test-utils'; + +import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; +import React, { ReactElement } from 'react'; +import ReactDOM from 'react-dom'; + +import { MatrixClient } from 'matrix-js-sdk'; +import CryptographyPanel from '../../../../src/components/views/settings/CryptographyPanel'; + +describe('CryptographyPanel', () => { + it('shows the session ID and key', () => { + const sessionId = "ABCDEFGHIJ"; + const sessionKey = "AbCDeFghIJK7L/m4nOPqRSTUVW4xyzaBCDef6gHIJkl"; + const sessionKeyFormatted = "AbCD eFgh IJK7 L/m4 nOPq RSTU VW4x yzaB CDef 6gHI Jkl"; + + TestUtils.stubClient(); + const client: MatrixClient = MatrixClientPeg.get(); + client.deviceId = sessionId; + client.getDeviceEd25519Key = () => sessionKey; + + // When we render the CryptographyPanel + const rendered = render(); + + // Then it displays info about the user's session + const codes = rendered.querySelectorAll("code"); + expect(codes.length).toEqual(2); + expect(codes[0].innerHTML).toEqual(sessionId); + expect(codes[1].innerHTML).toEqual(sessionKeyFormatted); + }); +}); + +function render(component: ReactElement): HTMLDivElement { + const parentDiv = document.createElement('div'); + document.body.appendChild(parentDiv); + ReactDOM.render(component, parentDiv); + return parentDiv; +} diff --git a/test/components/views/settings/FontScalingPanel-test.tsx b/test/components/views/settings/FontScalingPanel-test.tsx new file mode 100644 index 00000000000..3c3a68e2746 --- /dev/null +++ b/test/components/views/settings/FontScalingPanel-test.tsx @@ -0,0 +1,44 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { mount } from "enzyme"; + +import '../../../skinned-sdk'; +import * as TestUtils from "../../../test-utils"; +import _FontScalingPanel from '../../../../src/components/views/settings/FontScalingPanel'; + +const FontScalingPanel = TestUtils.wrapInMatrixClientContext(_FontScalingPanel); + +// Fake random strings to give a predictable snapshot +jest.mock( + 'matrix-js-sdk/src/randomstring', + () => { + return { + randomString: () => "abdefghi", + }; + }, +); + +describe('FontScalingPanel', () => { + it('renders the font scaling UI', () => { + TestUtils.stubClient(); + const wrapper = mount( + , + ); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap b/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap new file mode 100644 index 00000000000..2174b4f2893 --- /dev/null +++ b/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap @@ -0,0 +1,307 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FontScalingPanel renders the font scaling UI 1`] = ` + + +
+ + Font size + + +
+ +
+
+
+ +
+ +
+
+ Aa +
+ +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+ + +
+
+
+ +
+ Aa +
+
+ + + + + + + + + +
+ + +
+
+
+ + +`; diff --git a/test/end-to-end-tests/src/usecases/settings.js b/test/end-to-end-tests/src/usecases/settings.js index 509e0b4a07b..372bdead104 100644 --- a/test/end-to-end-tests/src/usecases/settings.js +++ b/test/end-to-end-tests/src/usecases/settings.js @@ -44,7 +44,7 @@ module.exports.enableLazyLoading = async function(session) { module.exports.getE2EDeviceFromSettings = async function(session) { session.log.step(`gets e2e device/key from settings`); await openSettings(session, "security"); - const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_SecurityUserSettingsTab_deviceInfo code"); + const deviceAndKey = await session.queryAll(".mx_SettingsTab_section .mx_CryptographyPanel code"); assert.equal(deviceAndKey.length, 2); const id = await (await deviceAndKey[0].getProperty("innerText")).jsonValue(); const key = await (await deviceAndKey[1].getProperty("innerText")).jsonValue(); diff --git a/yarn.lock b/yarn.lock index 0b325d66d92..7603c3448e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,7 +54,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== -"@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.7.5": +"@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.7.5": version "7.15.8" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.8.tgz#195b9f2bffe995d2c6c159e72fe525b4114e8c10" integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og== @@ -307,7 +307,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.13.16", "@babel/parser@^7.15.4", "@babel/parser@^7.15.8": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.15.4", "@babel/parser@^7.15.8": version "7.15.8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.8.tgz#7bacdcbe71bdc3ff936d510c15dcea7cf0b99016" integrity sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA== @@ -1308,6 +1308,7 @@ "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz": version "3.2.3" + uid cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4 resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": @@ -1757,14 +1758,14 @@ integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA== "@types/node@*": - version "16.10.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5" - integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ== + version "16.11.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.1.tgz#2e50a649a50fc403433a14f829eface1a3443e97" + integrity sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA== "@types/node@^14.14.22": - version "14.17.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.21.tgz#6359d8cf73481e312a43886fa50afc70ce5592c6" - integrity sha512-zv8ukKci1mrILYiQOwGSV4FpkZhyxQtuFWGya2GujWg+zVAeRQ4qbaMmWp9vb9889CFA8JECH7lkwCL6Ygg8kA== + version "14.17.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.27.tgz#5054610d37bb5f6e21342d0e6d24c494231f3b85" + integrity sha512-94+Ahf9IcaDuJTle/2b+wzvjmutxXAEXU6O81JHblYXUg2BDG+dnBy7VxIPHKAyEEDHzCMQydTJuWvrE+Aanzw== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2052,10 +2053,10 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" -allchange@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.3.tgz#f8814ddfbcfe39a01bf4570778ee7e6d9ff0ebb3" - integrity sha512-UZkfz5SkNEMFQFLr8vZcXHaph2EbJxmkVNF5Nt6D9RIa5pmAar7oAMfNdda714jg7IQijvaFty5PYazXLgd5WA== +allchange@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.4.tgz#c540d9344e7e5bf4187be823ad5d858bed365d82" + integrity sha512-9V2PPRj2UtkqFIx1DFPS5ubadTpu59fMrIteA3JUVpv6UrVCFeFRELWrOwUBHlQmsG4ud82ru34k8yJ3VKeqHg== dependencies: "@actions/core" "^1.4.0" "@actions/github" "^5.0.0" @@ -2083,11 +2084,6 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" -ansi-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -2309,14 +2305,14 @@ babel-plugin-dynamic-import-node@^2.3.3: object.assign "^4.1.0" babel-plugin-istanbul@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" - integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@istanbuljs/load-nyc-config" "^1.0.0" "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" babel-plugin-jest-hoist@^26.6.2: @@ -2503,15 +2499,15 @@ browser-request@^0.3.3: integrity sha1-ns5bWsqJopkyJC4Yv5M975h2zBc= browserslist@^4.12.0, browserslist@^4.16.6, browserslist@^4.17.3: - version "4.17.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.3.tgz#2844cd6eebe14d12384b0122d217550160d2d624" - integrity sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ== + version "4.17.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.4.tgz#72e2508af2a403aec0a49847ef31bd823c57ead4" + integrity sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ== dependencies: - caniuse-lite "^1.0.30001264" - electron-to-chromium "^1.3.857" + caniuse-lite "^1.0.30001265" + electron-to-chromium "^1.3.867" escalade "^3.1.1" - node-releases "^1.1.77" - picocolors "^0.2.1" + node-releases "^2.0.0" + picocolors "^1.0.0" bs58@^4.0.1: version "4.0.1" @@ -2605,10 +2601,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001264: - version "1.0.30001265" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3" - integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw== +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001265: + version "1.0.30001269" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001269.tgz#3a71bee03df627364418f9fd31adfc7aa1cc2d56" + integrity sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w== capture-exit@^2.0.0: version "2.0.0" @@ -2732,15 +2728,14 @@ classnames@*, classnames@^2.2.6: integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== cli-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" - integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A== + version "2.0.1" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.1.tgz#93e3491308691f1e46beb78b63d0fb2585e42ba6" + integrity sha512-eBbxZF6fqPUNnf7CLAFOersUnyYzv83tHFLSlts+OAHsNendaqv2tHCq+/MO+b3Y+9JeoUlIvobyxG/Z8GNeOg== dependencies: - ansi-regex "^2.1.1" d "^1.0.1" - es5-ext "^0.10.51" + es5-ext "^0.10.53" es6-iterator "^2.0.3" - memoizee "^0.4.14" + memoizee "^0.4.15" timers-ext "^0.1.7" cliui@^5.0.0: @@ -2903,9 +2898,9 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js-compat@^3.16.0, core-js-compat@^3.16.2: - version "3.18.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.18.2.tgz#e40c266fbd613948dd8d2d2156345da8ac03c142" - integrity sha512-25VJYCJtGjZwLguj7d66oiHfmnVw3TMOZ0zV8DyMJp/aeQ3OjR519iOOeck08HMyVVRAqXxafc2Hl+5QstJrsQ== + version "3.18.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.18.3.tgz#e0e7e87abc55efb547e7fa19169e45fa9df27a67" + integrity sha512-4zP6/y0a2RTHN5bRGT7PTq9lVt3WzvffTNjqnTKsXhkAYNDTkdCLOIfAdOLcQ/7TDdyRj3c+NeHe1NmF1eDScw== dependencies: browserslist "^4.17.3" semver "7.0.0" @@ -3165,9 +3160,9 @@ detect-node-es@^1.1.0: integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== diff-dom@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-4.2.2.tgz#49a121fe1614675b4f28ed50df9547bb0c3c1d52" - integrity sha512-qO0WsnnMF6Wc5MEm0j4LkwvJWs4Mpkj6pyXWMtcjGJPHWDNcnQY8ijYoSrsZuArBmlHLN9Dqr4de/3ZlbjpRVw== + version "4.2.3" + resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-4.2.3.tgz#c6234b49c1b49e41601d2f08dbb26cd57842de45" + integrity sha512-8OZPIbTWVhkQVlUlsb+VuMEMpTpKKhO5FTwds2bYVIaBiPNJCG1YW5qXUyLWMux5gC2UGyYXjtX05SPivnGMCw== diff-match-patch@^1.0.5: version "1.0.5" @@ -3291,10 +3286,10 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.857: - version "1.3.866" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.866.tgz#d446338f5ad6948b27a50739760e7b0b5cc5032f" - integrity sha512-iYze6TpDXWxk+sfcpUUdTs6Pv/3kG45Pnjer2DxEeFw0N08bZeNLuz97s2lMgy8yObon48o0WHY2Bkg3xuAPOA== +electron-to-chromium@^1.3.867: + version "1.3.872" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.872.tgz#2311a82f344d828bab6904818adc4afb57b35369" + integrity sha512-qG96atLFY0agKyEETiBFNhpRLSXGSXOBuhXWpbkYqrLKKASpRyRBUtfkn0ZjIf/yXfA7FA4nScVOMpXSHFlUCQ== emittery@^0.7.1: version "0.7.2" @@ -3468,7 +3463,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== @@ -4350,10 +4345,10 @@ has@^1.0.0, has@^1.0.1, has@^1.0.3: dependencies: function-bind "^1.1.1" -highlight.js@^10.5.0: - version "10.7.3" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" - integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== +highlight.js@^11.3.1: + version "11.3.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.3.1.tgz#813078ef3aa519c61700f84fe9047231c5dc3291" + integrity sha512-PUhCRnPjLtiLHZAQ5A/Dt5F8cWZeMyj9KRsACsWT+OD6OP0x6dp5OmT5jdx0JgEyPxPZZIPQpRN2TciUT7occw== hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" @@ -4661,9 +4656,9 @@ is-ci@^2.0.0: ci-info "^2.0.0" is-core-module@^2.2.0, is-core-module@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" - integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ== + version "2.8.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== dependencies: has "^1.0.3" @@ -5012,11 +5007,11 @@ isstream@~0.1.2: integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= istanbul-lib-coverage@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.2.tgz#36786d4d82aad2ea5911007e255e2da6b5f80d86" - integrity sha512-o5+eTUYzCJ11/+JhW5/FUCdfsdoYVdQ/8I/OveE2XsjehYn5DdeSnNQAbjYaO8gQ6hvGTN6GM6ddQqpTVG5j8g== + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: +istanbul-lib-instrument@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== @@ -5026,6 +5021,17 @@ istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" +istanbul-lib-instrument@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.0.4.tgz#e976f2aa66ebc6737f236d3ab05b76e36f885c80" + integrity sha512-W6jJF9rLGEISGoCyXRqa/JCGQGmmxPO10TMu7izaUTynxvBvTjqzAIIGCK9USBmIbQAaSWD6XJPrM9Pv5INknw== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + istanbul-lib-report@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" @@ -5045,9 +5051,9 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.4.tgz#5c38ce8136edf484c0fcfbf7514aafb0363ed1db" - integrity sha512-bFjUnc95rHjdCR63WMHUS7yfJJh8T9IPSWavvR02hhjVwezWALZ5axF9EqjmwZHpXqkzbgAMP8DmAtiyNxrdrQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" + integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -5866,7 +5872,7 @@ mathml-tag-names@^2.1.3: "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "14.0.1" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e7b41fadd0d6eda7423a369c99ec7b94afd48d5d" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/94ff0ea4cd9dfd37400b079c8da08b8ecd0f7c6f" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" @@ -5946,7 +5952,7 @@ memoize-one@^5.1.1: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== -memoizee@^0.4.14: +memoizee@^0.4.15: version "0.4.15" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== @@ -6097,9 +6103,9 @@ ms@2.1.2: integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== nanoid@^3.1.28: - version "3.1.29" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.29.tgz#214fb2d7a33e1a5bef4757b779dfaeb6a4e5aeb4" - integrity sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg== + version "3.1.30" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" + integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== nanomatch@^1.2.9: version "1.2.13" @@ -6190,10 +6196,10 @@ node-notifier@^8.0.0: uuid "^8.3.0" which "^2.0.2" -node-releases@^1.1.77: - version "1.1.77" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e" - integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== +node-releases@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.0.tgz#67dc74903100a7deb044037b8a2e5f453bb05400" + integrity sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA== normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" @@ -6413,9 +6419,9 @@ optionator@^0.9.1: word-wrap "^1.2.3" opus-recorder@^8.0.3: - version "8.0.4" - resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.4.tgz#c4cdbb8bb94d17aa406934b58dcf9caab6c79b09" - integrity sha512-nWwLH5BySgNDHdpkOMV+igl9iyS99g60ap/0LycIgbSXykZvUpweuWCgAl3mTKSL0773yvKohlO5dOv5RQqG/Q== + version "8.0.5" + resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.5.tgz#06d3e32e15da57ebc3f57e41b93033475fcb4e3e" + integrity sha512-tBRXc9Btds7i3bVfA7d5rekAlyOcfsivt5vSIXHxRV1Oa+s6iXFW8omZ0Lm3ABWotVcEyKt96iIIUcgbV07YOw== p-each-series@^2.1.0: version "2.2.0" @@ -6575,6 +6581,11 @@ picocolors@^0.2.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -7370,9 +7381,9 @@ sane@^4.0.3: walker "~1.0.5" sanitize-html@^2.3.2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.1.tgz#f49998dc54c8180153940440d3a7294b09e4258a" - integrity sha512-hUITPitQk+eFNLtr4dEkaaiAJndG2YE87IOpcfBSL1XdklWgwcNDJdr9Ppe8QKL/C3jFt1xH/Mbj20e0GZQOfg== + version "2.5.2" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.2.tgz#fd7892340e7fc9afd5722200929258808c578784" + integrity sha512-sJ1rO2YixFIqs2kIcEUb6PTrCjvz8DMq1XqWWuy0kjgjrn58GNLK1DKSIRybFZDO1WNgsEgD+WiEzTEYS8xEug== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0"