diff --git a/CHANGELOG.md b/CHANGELOG.md index 73b383d76d..4d65a524d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,122 @@ +Changes in [3.27.0](https://github.com/vector-im/element-desktop/releases/tag/v3.27.0) (2021-07-02) +=================================================================================================== + +## 🔒 SECURITY FIXES + * Sanitize untrusted variables from message previews before translation + Fixes vector-im/element-web#18314 + +## ✨ Features + * Fix editing of `` & ` & `` + [\#6469](https://github.com/matrix-org/matrix-react-sdk/pull/6469) + Fixes vector-im/element-web#18211 + * Zoom images in lightbox to where the cursor points + [\#6418](https://github.com/matrix-org/matrix-react-sdk/pull/6418) + Fixes vector-im/element-web#17870 + * Avoid hitting the settings store from TextForEvent + [\#6205](https://github.com/matrix-org/matrix-react-sdk/pull/6205) + Fixes vector-im/element-web#17650 + * Initial MSC3083 + MSC3244 support + [\#6212](https://github.com/matrix-org/matrix-react-sdk/pull/6212) + Fixes vector-im/element-web#17686 and vector-im/element-web#17661 + * Navigate to the first room with notifications when clicked on space notification dot + [\#5974](https://github.com/matrix-org/matrix-react-sdk/pull/5974) + * Add matrix: to the list of permitted URL schemes + [\#6388](https://github.com/matrix-org/matrix-react-sdk/pull/6388) + * Add "Copy Link" to room context menu + [\#6374](https://github.com/matrix-org/matrix-react-sdk/pull/6374) + * 💭 Message bubble layout + [\#6291](https://github.com/matrix-org/matrix-react-sdk/pull/6291) + Fixes vector-im/element-web#4635, vector-im/element-web#17773 vector-im/element-web#16220 and vector-im/element-web#7687 + * Play only one audio file at a time + [\#6417](https://github.com/matrix-org/matrix-react-sdk/pull/6417) + Fixes vector-im/element-web#17439 + * Move download button for media to the action bar + [\#6386](https://github.com/matrix-org/matrix-react-sdk/pull/6386) + Fixes vector-im/element-web#17943 + * Improved display of one-to-one call history with summary boxes for each call + [\#6121](https://github.com/matrix-org/matrix-react-sdk/pull/6121) + Fixes vector-im/element-web#16409 + * Notification settings UI refresh + [\#6352](https://github.com/matrix-org/matrix-react-sdk/pull/6352) + Fixes vector-im/element-web#17782 + * Fix EventIndex double handling events and erroring + [\#6385](https://github.com/matrix-org/matrix-react-sdk/pull/6385) + Fixes vector-im/element-web#18008 + * Improve reply rendering + [\#3553](https://github.com/matrix-org/matrix-react-sdk/pull/3553) + Fixes vector-im/riot-web#9217, vector-im/riot-web#7633, vector-im/riot-web#7530, vector-im/riot-web#7169, vector-im/riot-web#7151, vector-im/riot-web#6692 vector-im/riot-web#6579 and vector-im/element-web#17440 + +## 🐛 Bug Fixes + * Fix CreateRoomDialog exploding when making public room outside of a space + [\#6493](https://github.com/matrix-org/matrix-react-sdk/pull/6493) + * Fix regression where registration would soft-crash on captcha + [\#6505](https://github.com/matrix-org/matrix-react-sdk/pull/6505) + Fixes vector-im/element-web#18284 + * only send join rule event if we have a join rule to put in it + [\#6517](https://github.com/matrix-org/matrix-react-sdk/pull/6517) + * Improve the new download button's discoverability and interactions. + [\#6510](https://github.com/matrix-org/matrix-react-sdk/pull/6510) + * Fix voice recording UI looking broken while microphone permissions are being requested. + [\#6479](https://github.com/matrix-org/matrix-react-sdk/pull/6479) + Fixes vector-im/element-web#18223 + * Match colors of room and user avatars in DMs + [\#6393](https://github.com/matrix-org/matrix-react-sdk/pull/6393) + Fixes vector-im/element-web#2449 + * Fix onPaste handler to work with copying files from Finder + [\#5389](https://github.com/matrix-org/matrix-react-sdk/pull/5389) + Fixes vector-im/element-web#15536 and vector-im/element-web#16255 + * Fix infinite pagination loop when offline + [\#6478](https://github.com/matrix-org/matrix-react-sdk/pull/6478) + Fixes vector-im/element-web#18242 + * Fix blurhash rounded corners missing regression + [\#6467](https://github.com/matrix-org/matrix-react-sdk/pull/6467) + Fixes vector-im/element-web#18110 + * Fix position of the space hierarchy spinner + [\#6462](https://github.com/matrix-org/matrix-react-sdk/pull/6462) + Fixes vector-im/element-web#18182 + * Fix display of image messages that lack thumbnails + [\#6456](https://github.com/matrix-org/matrix-react-sdk/pull/6456) + Fixes vector-im/element-web#18175 + * Fix crash with large audio files. + [\#6436](https://github.com/matrix-org/matrix-react-sdk/pull/6436) + Fixes vector-im/element-web#18149 + * Make diff colors in codeblocks more pleasant + [\#6355](https://github.com/matrix-org/matrix-react-sdk/pull/6355) + Fixes vector-im/element-web#17939 + * Show the correct audio file duration while loading the file. + [\#6435](https://github.com/matrix-org/matrix-react-sdk/pull/6435) + Fixes vector-im/element-web#18160 + * Fix various timeline settings not applying immediately. + [\#6261](https://github.com/matrix-org/matrix-react-sdk/pull/6261) + Fixes vector-im/element-web#17748 + * Fix issues with room list duplication + [\#6391](https://github.com/matrix-org/matrix-react-sdk/pull/6391) + Fixes vector-im/element-web#14508 + * Fix grecaptcha throwing useless error sometimes + [\#6401](https://github.com/matrix-org/matrix-react-sdk/pull/6401) + Fixes vector-im/element-web#15142 + * Update Emojibase and Twemoji and switch to IamCal (Slack-style) shortcodes + [\#6347](https://github.com/matrix-org/matrix-react-sdk/pull/6347) + Fixes vector-im/element-web#13857 and vector-im/element-web#13334 + * Respect compound emojis in default avatar initial generation + [\#6397](https://github.com/matrix-org/matrix-react-sdk/pull/6397) + Fixes vector-im/element-web#18040 + * Fix bug where the 'other homeserver' field in the server selection dialog would become briefly focus and then unfocus when clicked. + [\#6394](https://github.com/matrix-org/matrix-react-sdk/pull/6394) + Fixes vector-im/element-web#18031 + * Standardise spelling and casing of homeserver, identity server, and integration manager + [\#6365](https://github.com/matrix-org/matrix-react-sdk/pull/6365) + * Fix widgets not receiving decrypted events when they have permission. + [\#6371](https://github.com/matrix-org/matrix-react-sdk/pull/6371) + Fixes vector-im/element-web#17615 + * Prevent client hangs when calculating blurhashes + [\#6366](https://github.com/matrix-org/matrix-react-sdk/pull/6366) + Fixes vector-im/element-web#17945 + * Exclude state events from widgets reading room events + [\#6378](https://github.com/matrix-org/matrix-react-sdk/pull/6378) + * Cache feature_spaces\* flags to improve performance + [\#6381](https://github.com/matrix-org/matrix-react-sdk/pull/6381) + Changes in [3.26.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.26.0) (2021-07-19) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.26.0-rc.1...v3.26.0) diff --git a/README.md b/README.md index b3e96ef001..67e5e12f59 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ All code lands on the `develop` branch - `master` is only used for stable releas **Please file PRs against `develop`!!** Please follow the standard Matrix contributor's guide: -https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.rst +https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.md Please follow the Matrix JS/React code style as per: https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md diff --git a/package.json b/package.json index b73462d188..9744aa7685 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.26.0", + "version": "3.27.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "12.1.0", + "matrix-js-sdk": "12.2.0", "matrix-widget-api": "^0.1.0-beta.15", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index e64057d16c..1dea6332f5 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -297,7 +297,7 @@ $activeBorderColor: $secondary-fg-color; .mx_SpaceButton:hover, .mx_SpaceButton:focus-within, .mx_SpaceButton_hasMenuOpen { - &:not(.mx_SpaceButton_home):not(.mx_SpaceButton_invite) { + &:not(.mx_SpaceButton_invite) { // Hide the badge container on hover because it'll be a menu button .mx_SpacePanel_badgeContainer { width: 0; @@ -368,6 +368,14 @@ $activeBorderColor: $secondary-fg-color; .mx_SpacePanel_iconExplore::before { mask-image: url('$(res)/img/element-icons/roomlist/browse.svg'); } + + .mx_SpacePanel_noIcon { + display: none; + + & + .mx_IconizedContextMenu_label { + padding-left: 5px !important; // override default iconized label style to align with header + } + } } diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index 2aa43283b5..ff176eef7e 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -149,12 +149,17 @@ limitations under the License. } } - .mx_IconizedContextMenu_checked { + .mx_IconizedContextMenu_checked, + .mx_IconizedContextMenu_unchecked { margin-left: 16px; margin-right: -5px; + } - &::before { - mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); - } + .mx_IconizedContextMenu_checked::before { + mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + } + + .mx_IconizedContextMenu_unchecked::before { + content: unset; } } diff --git a/res/css/views/messages/_CallEvent.scss b/res/css/views/messages/_CallEvent.scss index f2ceb086c4..0c1b41ca38 100644 --- a/res/css/views/messages/_CallEvent.scss +++ b/res/css/views/messages/_CallEvent.scss @@ -23,7 +23,7 @@ limitations under the License. background-color: $dark-panel-bg-color; border-radius: 8px; margin: 10px auto; - max-width: 75%; + width: 75%; box-sizing: border-box; height: 60px; diff --git a/res/css/views/messages/_MFileBody.scss b/res/css/views/messages/_MFileBody.scss index 403f671673..d941a8132f 100644 --- a/res/css/views/messages/_MFileBody.scss +++ b/res/css/views/messages/_MFileBody.scss @@ -1,5 +1,5 @@ /* -Copyright 2015, 2016, 2021 The Matrix.org Foundation C.I.C. +Copyright 2015 - 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. @@ -60,6 +60,8 @@ limitations under the License. } .mx_MFileBody_info { + cursor: pointer; + .mx_MFileBody_info_icon { background-color: $message-body-panel-icon-bg-color; border-radius: 20px; diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index e495c2c596..1e25deba26 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -280,6 +280,11 @@ limitations under the License. margin-right: 5px; } + .mx_EventTile_line, + .mx_EventTile_info { + min-width: 100%; + } + .mx_EventTile_e2eIcon { margin-left: 9px; } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 5de9a9f9d1..6e207d674b 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -459,8 +459,14 @@ $hover-select-border: 4px; /* Various markdown overrides */ -.mx_EventTile_body pre { - border: 1px solid transparent; +.mx_EventTile_body { + a:hover { + text-decoration: underline; + } + + pre { + border: 1px solid transparent; + } } .mx_EventTile_content .markdown-body { diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 4cbcb8e708..63a5fa7edf 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -16,6 +16,7 @@ limitations under the License. .mx_ProfileSettings_controls_topic { & > textarea { + font-family: inherit; resize: vertical; } } diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 8e6b99871c..9f40372690 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -72,6 +72,13 @@ limitations under the License. padding-right: 10px; } +.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_microcopy { + margin-top: 4px; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; +} + .mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch { float: right; } diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 104e2993d8..eff865f20c 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -76,16 +76,22 @@ limitations under the License. &.mx_VideoFeed_voice { // We don't want to collide with the call controls that have 52px of height - padding-bottom: 52px; + margin-bottom: 52px; background-color: $inverted-bg-color; display: flex; justify-content: center; align-items: center; } - &.mx_VideoFeed_video { + .mx_VideoFeed_video { + height: 100%; background-color: #000; } + + .mx_VideoFeed_mic { + left: 10px; + bottom: 10px; + } } } diff --git a/res/css/views/voip/_CallViewSidebar.scss b/res/css/views/voip/_CallViewSidebar.scss index 79bf3cbf09..892a137a32 100644 --- a/res/css/views/voip/_CallViewSidebar.scss +++ b/res/css/views/voip/_CallViewSidebar.scss @@ -35,12 +35,23 @@ limitations under the License. width: 100%; &.mx_VideoFeed_voice { + border-radius: 4px; + display: flex; align-items: center; justify-content: center; aspect-ratio: 16 / 9; } + + .mx_VideoFeed_video { + border-radius: 4px; + } + + .mx_VideoFeed_mic { + left: 6px; + bottom: 6px; + } } &.mx_CallViewSidebar_pipMode { diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss index 0019994e72..527d223ffc 100644 --- a/res/css/views/voip/_DialPadContextMenu.scss +++ b/res/css/views/voip/_DialPadContextMenu.scss @@ -69,7 +69,6 @@ limitations under the License. overflow: hidden; max-width: 185px; text-align: left; - direction: rtl; padding: 8px 0px; background-color: rgb(0, 0, 0, 0); } diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 07a4a0e530..3a0f62636e 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -15,18 +15,52 @@ limitations under the License. */ .mx_VideoFeed { - border-radius: 4px; - + overflow: hidden; + position: relative; &.mx_VideoFeed_voice { background-color: $inverted-bg-color; } - &.mx_VideoFeed_video { + .mx_VideoFeed_video { + width: 100%; background-color: transparent; + + &.mx_VideoFeed_video_mirror { + transform: scale(-1, 1); + } + } + + .mx_VideoFeed_mic { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + + width: 24px; + height: 24px; + + background-color: rgba(0, 0, 0, 0.5); // Same on both themes + border-radius: 100%; + + &::before { + position: absolute; + content: ""; + width: 16px; + height: 16px; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + background-color: white; // Same on both themes + border-radius: 7px; + } + + &.mx_VideoFeed_mic_muted::before { + mask-image: url('$(res)/img/voip/mic-muted.svg'); + } + + &.mx_VideoFeed_mic_unmuted::before { + mask-image: url('$(res)/img/voip/mic-unmuted.svg'); + } } } - -.mx_VideoFeed_mirror { - transform: scale(-1, 1); -} diff --git a/res/img/voip/mic-muted.svg b/res/img/voip/mic-muted.svg new file mode 100644 index 0000000000..0cb7ad1c9e --- /dev/null +++ b/res/img/voip/mic-muted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/voip/mic-unmuted.svg b/res/img/voip/mic-unmuted.svg new file mode 100644 index 0000000000..8334cafa0a --- /dev/null +++ b/res/img/voip/mic-unmuted.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx index 7bad8eb50e..b9295be3ed 100644 --- a/src/TextForEvent.tsx +++ b/src/TextForEvent.tsx @@ -25,11 +25,44 @@ import { Action } from './dispatcher/actions'; import defaultDispatcher from './dispatcher/dispatcher'; import { SetRightPanelPhasePayload } from './dispatcher/payloads/SetRightPanelPhasePayload'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { MatrixClientPeg } from "./MatrixClientPeg"; // 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. +function textForCallInviteEvent(event: MatrixEvent): () => string | null { + const getSenderName = () => event.sender ? event.sender.name : _t('Someone'); + // FIXME: Find a better way to determine this from the event? + let isVoice = true; + if (event.getContent().offer && event.getContent().offer.sdp && + event.getContent().offer.sdp.indexOf('m=video') !== -1) { + isVoice = false; + } + const isSupported = MatrixClientPeg.get().supportsVoip(); + + // This ladder could be reduced down to a couple string variables, however other languages + // can have a hard time translating those strings. In an effort to make translations easier + // and more accurate, we break out the string-based variables to a couple booleans. + if (isVoice && isSupported) { + return () => _t("%(senderName)s placed a voice call.", { + senderName: getSenderName(), + }); + } else if (isVoice && !isSupported) { + return () => _t("%(senderName)s placed a voice call. (not supported by this browser)", { + senderName: getSenderName(), + }); + } else if (!isVoice && isSupported) { + return () => _t("%(senderName)s placed a video call.", { + senderName: getSenderName(), + }); + } else if (!isVoice && !isSupported) { + return () => _t("%(senderName)s placed a video call. (not supported by this browser)", { + senderName: getSenderName(), + }); + } +} + function textForMemberEvent(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean): () => string | null { // XXX: SYJS-16 "sender is sometimes null for join messages" const senderName = ev.sender ? ev.sender.name : ev.getSender(); @@ -567,6 +600,7 @@ interface IHandlers { const handlers: IHandlers = { 'm.room.message': textForMessageEvent, + 'm.call.invite': textForCallInviteEvent, }; const stateHandlers: IHandlers = { diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 01c086b73c..332b6cd318 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { CSSProperties, RefObject, useRef, useState } from "react"; +import React, { CSSProperties, RefObject, SyntheticEvent, useRef, useState } from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; @@ -471,10 +471,14 @@ type ContextMenuTuple = [boolean, RefObject, () => void, () => void, (val: export const useContextMenu = (): ContextMenuTuple => { const button = useRef(null); const [isOpen, setIsOpen] = useState(false); - const open = () => { + const open = (ev?: SyntheticEvent) => { + ev?.preventDefault(); + ev?.stopPropagation(); setIsOpen(true); }; - const close = () => { + const close = (ev?: SyntheticEvent) => { + ev?.preventDefault(); + ev?.stopPropagation(); setIsOpen(false); }; diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx index aead3a266e..0bb96f9397 100644 --- a/src/components/views/context_menus/DialpadContextMenu.tsx +++ b/src/components/views/context_menus/DialpadContextMenu.tsx @@ -49,6 +49,13 @@ export default class DialpadContextMenu extends React.Component this.props.onFinished(); }; + onKeyDown = (ev) => { + // Prevent Backspace and Delete keys from functioning in the entry field + if (ev.code === "Backspace" || ev.code === "Delete") { + ev.preventDefault(); + } + }; + onChange = (ev) => { this.setState({ value: ev.target.value }); }; @@ -64,6 +71,7 @@ export default class DialpadContextMenu extends React.Component className="mx_DialPadContextMenu_dialled" value={this.state.value} autoFocus={true} + onKeyDown={this.onKeyDown} onChange={this.onChange} /> diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index c9506d7c98..571b0b39bf 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -86,7 +86,10 @@ export const IconizedContextMenuCheckbox: React.FC = ({ > { label } - { active && } + ; }; diff --git a/src/components/views/context_menus/SpaceContextMenu.tsx b/src/components/views/context_menus/SpaceContextMenu.tsx new file mode 100644 index 0000000000..3da00e71aa --- /dev/null +++ b/src/components/views/context_menus/SpaceContextMenu.tsx @@ -0,0 +1,216 @@ +/* +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, { useContext } from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { EventType } from "matrix-js-sdk/src/@types/event"; + +import { + IProps as IContextMenuProps, +} from "../../structures/ContextMenu"; +import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu"; +import { _t } from "../../../languageHandler"; +import { + leaveSpace, + shouldShowSpaceSettings, + showAddExistingRooms, + showCreateNewRoom, + showCreateNewSubspace, + showSpaceInvite, + showSpaceSettings, +} from "../../../utils/space"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { ButtonEvent } from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import RoomViewStore from "../../../stores/RoomViewStore"; +import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import { Action } from "../../../dispatcher/actions"; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; +import { BetaPill } from "../beta/BetaCard"; + +interface IProps extends IContextMenuProps { + space: Room; +} + +const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => { + const cli = useContext(MatrixClientContext); + const userId = cli.getUserId(); + + let inviteOption; + if (space.getJoinRule() === "public" || space.canInvite(userId)) { + const onInviteClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showSpaceInvite(space); + onFinished(); + }; + + inviteOption = ( + + ); + } + + let settingsOption; + let leaveSection; + if (shouldShowSpaceSettings(space)) { + const onSettingsClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showSpaceSettings(space); + onFinished(); + }; + + settingsOption = ( + + ); + } else { + const onLeaveClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + leaveSpace(space); + onFinished(); + }; + + leaveSection = + + ; + } + + const canAddRooms = space.currentState.maySendStateEvent(EventType.SpaceChild, userId); + + let newRoomSection; + if (space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) { + const onNewRoomClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showCreateNewRoom(space); + onFinished(); + }; + + const onAddExistingRoomClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showAddExistingRooms(space); + onFinished(); + }; + + const onNewSubspaceClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showCreateNewSubspace(space); + onFinished(); + }; + + newRoomSection = + + + + + + ; + } + + const onMembersClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + if (!RoomViewStore.getRoomId()) { + defaultDispatcher.dispatch({ + action: "view_room", + room_id: space.roomId, + }, true); + } + + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.SpaceMemberList, + refireParams: { space: space }, + }); + onFinished(); + }; + + const onExploreRoomsClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + defaultDispatcher.dispatch({ + action: "view_room", + room_id: space.roomId, + }); + onFinished(); + }; + + return +
+ { space.name } +
+ + { inviteOption } + + { settingsOption } + + + { newRoomSection } + { leaveSection } +
; +}; + +export default SpaceContextMenu; + diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.tsx similarity index 82% rename from src/components/views/dialogs/LogoutDialog.js rename to src/components/views/dialogs/LogoutDialog.tsx index 469cd48093..8c035dcbba 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.tsx @@ -16,6 +16,7 @@ limitations under the License. */ import React from 'react'; +import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; import Modal from '../../../Modal'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; @@ -24,19 +25,25 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg'; import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + onFinished: (success: boolean) => void; +} + +interface IState { + shouldLoadBackupStatus: boolean; + loading: boolean; + backupInfo: IKeyBackupInfo; + error?: string; +} + @replaceableComponent("views.dialogs.LogoutDialog") -export default class LogoutDialog extends React.Component { - defaultProps = { +export default class LogoutDialog extends React.Component { + static defaultProps = { onFinished: function() {}, }; - constructor() { - super(); - this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this); - this._onExportE2eKeysClicked = this._onExportE2eKeysClicked.bind(this); - this._onFinished = this._onFinished.bind(this); - this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this); - this._onLogoutConfirm = this._onLogoutConfirm.bind(this); + constructor(props) { + super(props); const cli = MatrixClientPeg.get(); const shouldLoadBackupStatus = cli.isCryptoEnabled() && !cli.getKeyBackupEnabled(); @@ -49,11 +56,11 @@ export default class LogoutDialog extends React.Component { }; if (shouldLoadBackupStatus) { - this._loadBackupStatus(); + this.loadBackupStatus(); } } - async _loadBackupStatus() { + private async loadBackupStatus() { try { const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); this.setState({ @@ -69,29 +76,29 @@ export default class LogoutDialog extends React.Component { } } - _onSettingsLinkClick() { + private onSettingsLinkClick = (): void => { // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; - _onExportE2eKeysClicked() { + private onExportE2eKeysClicked = (): void => { Modal.createTrackedDialogAsync('Export E2E Keys', '', import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, ); - } + }; - _onFinished(confirmed) { + private onFinished = (confirmed: boolean): void => { if (confirmed) { dis.dispatch({ action: 'logout' }); } // close dialog - this.props.onFinished(); - } + this.props.onFinished(confirmed); + }; - _onSetRecoveryMethodClick() { + private onSetRecoveryMethodClick = (): void => { if (this.state.backupInfo) { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and @@ -108,15 +115,15 @@ export default class LogoutDialog extends React.Component { } // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; - _onLogoutConfirm() { + private onLogoutConfirm = (): void => { dis.dispatch({ action: 'logout' }); // close dialog - this.props.onFinished(); - } + this.props.onFinished(true); + }; render() { if (this.state.shouldLoadBackupStatus) { @@ -152,16 +159,16 @@ export default class LogoutDialog extends React.Component { -
{ _t("Advanced") } -

@@ -174,7 +181,7 @@ export default class LogoutDialog extends React.Component { title={_t("You'll lose access to your encrypted messages")} contentId='mx_Dialog_content' hasCancel={true} - onFinished={this._onFinished} + onFinished={this.onFinished} > { dialogContent } ); @@ -187,7 +194,7 @@ export default class LogoutDialog extends React.Component { "Are you sure you want to sign out?", )} button={_t("Sign out")} - onFinished={this._onFinished} + onFinished={this.onFinished} />); } } diff --git a/src/components/views/messages/DownloadActionButton.tsx b/src/components/views/messages/DownloadActionButton.tsx index 2bdae04eda..6dc48b0936 100644 --- a/src/components/views/messages/DownloadActionButton.tsx +++ b/src/components/views/messages/DownloadActionButton.tsx @@ -16,12 +16,13 @@ limitations under the License. import { MatrixEvent } from "matrix-js-sdk/src"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; -import React, { createRef } from "react"; +import React from "react"; import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex"; import Spinner from "../elements/Spinner"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { FileDownloader } from "../../../utils/FileDownloader"; interface IProps { mxEvent: MatrixEvent; @@ -39,7 +40,7 @@ interface IState { @replaceableComponent("views.messages.DownloadActionButton") export default class DownloadActionButton extends React.PureComponent { - private iframe: React.RefObject = createRef(); + private downloader = new FileDownloader(); public constructor(props: IProps) { super(props); @@ -56,27 +57,21 @@ export default class DownloadActionButton extends React.PureComponent { - this.setState({ loading: false }); - - // we aren't showing the iframe, so we can send over the bare minimum styles and such. - this.iframe.current.contentWindow.postMessage({ - imgSrc: "", // no image - imgStyle: null, - style: "", + private async doDownload() { + await this.downloader.download({ blob: this.state.blob, - download: this.props.mediaEventHelperGet().fileName, - textContent: "", - auto: true, // autodownload - }, '*'); - }; + name: this.props.mediaEventHelperGet().fileName, + }); + this.setState({ loading: false }); + } public render() { let spinner: JSX.Element; @@ -92,18 +87,11 @@ export default class DownloadActionButton extends React.PureComponent { spinner } - { this.state.blob &&