diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..c9d11f02c8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ + + + diff --git a/res/css/_components.scss b/res/css/_components.scss index 56403ea190..ec3af8655e 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -123,7 +123,6 @@ @import "./views/elements/_EventListSummary.scss"; @import "./views/elements/_FacePile.scss"; @import "./views/elements/_Field.scss"; -@import "./views/elements/_FormButton.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InfoTooltip.scss"; @import "./views/elements/_InlineSpinner.scss"; diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 09f834a6e3..14e4c01389 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -134,8 +134,9 @@ limitations under the License. .mx_Toast_buttons { float: right; display: flex; + gap: 5px; - .mx_FormButton { + .mx_AccessibleButton { min-width: 96px; box-sizing: border-box; } diff --git a/res/css/views/elements/_FormButton.scss b/res/css/views/elements/_FormButton.scss deleted file mode 100644 index eda201ff03..0000000000 --- a/res/css/views/elements/_FormButton.scss +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2019 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_FormButton { - line-height: $font-16px; - padding: 5px 15px; - font-size: $font-12px; - height: min-content; - - &:not(:last-child) { - margin-right: 8px; - } - - &.mx_AccessibleButton_kind_primary { - color: $accent-color; - background-color: $accent-bg-color; - } - - &.mx_AccessibleButton_kind_danger { - color: $notice-primary-color; - background-color: $notice-primary-bg-color; - } - - &.mx_AccessibleButton_kind_secondary { - color: $secondary-fg-color; - border: 1px solid $secondary-fg-color; - background-color: unset; - } -} diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 87420ae4e7..6632ccddf9 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -259,16 +259,6 @@ limitations under the License. .mx_AccessibleButton.mx_AccessibleButton_hasKind { padding: 8px 18px; - - &.mx_AccessibleButton_kind_primary { - color: $accent-color; - background-color: $accent-bg-color; - } - - &.mx_AccessibleButton_kind_danger { - color: $notice-primary-color; - background-color: $notice-primary-bg-color; - } } .mx_VerificationShowSas .mx_AccessibleButton, diff --git a/res/css/views/right_panel/_VerificationPanel.scss b/res/css/views/right_panel/_VerificationPanel.scss index a8466a1626..12148b09de 100644 --- a/res/css/views/right_panel/_VerificationPanel.scss +++ b/res/css/views/right_panel/_VerificationPanel.scss @@ -58,7 +58,7 @@ limitations under the License. } .mx_VerificationPanel_reciprocate_section { - .mx_FormButton { + .mx_AccessibleButton { width: 100%; box-sizing: border-box; padding: 10px; diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss index 204ccab2b7..32454b9530 100644 --- a/res/css/views/spaces/_SpaceBasicSettings.scss +++ b/res/css/views/spaces/_SpaceBasicSettings.scss @@ -73,7 +73,7 @@ limitations under the License. } } - .mx_FormButton { + .mx_AccessibleButton { padding: 8px 22px; margin-left: auto; display: block; diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh index 02af402951..0990af70ce 100755 --- a/scripts/fetchdep.sh +++ b/scripts/fetchdep.sh @@ -24,19 +24,19 @@ clone() { # Try the PR author's branch in case it exists on the deps as well. # First we check if GITHUB_HEAD_REF is defined, # Then we check if BUILDKITE_BRANCH is defined, -# if it isn't we can assume this is a Netlify build -if [ -n ${GITHUB_HEAD_REF+x} ]; then - head=$GITHUB_HEAD_REF -elif [ -n ${BUILDKITE_BRANCH+x} ]; then +# if they aren't we can assume this is a Netlify build +if [ -n "$GITHUB_HEAD_REF" ]; then + head=$GITHUB_HEAD_REF +elif [ -n "$BUILDKITE_BRANCH" ]; then head=$BUILDKITE_BRANCH else - # Netlify doesn't give us info about the fork so we have to get it from GitHub API - apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/" - apiEndpoint+=$REVIEW_ID - head=$(curl $apiEndpoint | jq -r '.head.label') + # Netlify doesn't give us info about the fork so we have to get it from GitHub API + apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/" + apiEndpoint+=$REVIEW_ID + head=$(curl $apiEndpoint | jq -r '.head.label') fi -# If head is set, it will contain on BuilKite either: +# If head is set, it will contain on Buildkite either: # * "branch" when the author's branch and target branch are in the same repo # * "fork:branch" when the author's branch is in their fork or if this is a Netlify build # We can split on `:` into an array to check. @@ -44,19 +44,26 @@ fi # to determine whether the branch is from a fork or not BRANCH_ARRAY=(${head//:/ }) if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then - if [[ "$GITHUB_REPOSITORY" = "$deforg/$defrepo" ]]; then - clone $deforg $defrepo $head + + if [ -n "$GITHUB_HEAD_REF" ]; then + if [[ "$GITHUB_REPOSITORY" == "$deforg"* ]]; then + clone $deforg $defrepo $GITHUB_HEAD_REF + else + REPO_ARRAY=(${GITHUB_REPOSITORY//\// }) + clone $REPO_ARRAY[0] $defrepo $GITHUB_HEAD_REF + fi else - clone $GITHUB_ACTOR $defrepo $head + clone $deforg $defrepo $BUILDKITE_BRANCH fi + elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]} fi # Try the target branch of the push or PR. -if [ -n ${GITHUB_BASE_REF+x} ]; then +if [ -n $GITHUB_BASE_REF ]; then clone $deforg $defrepo $GITHUB_BASE_REF -elif [ -n ${BUILDKITE_PULL_REQUEST_BASE_BRANCH+x} ]; then +elif [ -n $BUILDKITE_PULL_REQUEST_BASE_BRANCH ]; then clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH fi diff --git a/src/Modal.tsx b/src/Modal.tsx index ce11c571b6..2f2d5a2d52 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -385,7 +385,7 @@ export class ModalManager { ); - ReactDOM.render(dialog, ModalManager.getOrCreateContainer()); + setImmediate(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer())); } else { // This is safe to call repeatedly if we happen to do that ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer()); diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx index 2a3e576e31..1cd5408210 100644 --- a/src/accessibility/KeyboardShortcuts.tsx +++ b/src/accessibility/KeyboardShortcuts.tsx @@ -57,6 +57,8 @@ export enum Modifiers { // Meta-modifier: isMac ? CMD : CONTROL export const CMD_OR_CTRL = isMac ? Modifiers.COMMAND : Modifiers.CONTROL; +// Meta-key representing the digits [0-9] often found at the top of standard keyboard layouts +export const DIGITS = "digits"; interface IKeybind { modifiers?: Modifiers[]; @@ -319,6 +321,7 @@ const alternateKeyName: Record = { [Key.SPACE]: _td("Space"), [Key.HOME]: _td("Home"), [Key.END]: _td("End"), + [DIGITS]: _td("[number]"), }; const keyIcon: Record = { [Key.ARROW_UP]: "↑", diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index a82a757a78..5240db6955 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -32,13 +32,9 @@ import SettingsStore from "../settings/SettingsStore"; const ROOM_REGEX = /\B#\S*/g; -function score(query: string, space: string) { - const index = space.indexOf(query); - if (index === -1) { - return Infinity; - } else { - return index; - } +// Prefer canonical aliases over non-canonical ones +function canonicalScore(displayedAlias: string, room: Room): number { + return displayedAlias === room.getCanonicalAlias() ? 0 : 1; } function matcherObject(room: Room, displayedAlias: string, matchName = "") { @@ -106,7 +102,7 @@ export default class RoomProvider extends AutocompleteProvider { const matchedString = command[0]; completions = this.matcher.match(matchedString, limit); completions = sortBy(completions, [ - (c) => score(matchedString, c.displayedAlias), + (c) => canonicalScore(c.displayedAlias, c.room), (c) => c.displayedAlias.length, ]); completions = uniqBy(completions, (match) => match.room); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index bfc7a1972d..885851e8e6 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -60,7 +60,7 @@ import ScrollPanel from "./ScrollPanel"; import TimelinePanel from "./TimelinePanel"; import ErrorBoundary from "../views/elements/ErrorBoundary"; import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; -import SearchBar from "../views/rooms/SearchBar"; +import SearchBar, { SearchScope } from "../views/rooms/SearchBar"; import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader from "../views/rooms/RoomHeader"; @@ -139,7 +139,7 @@ export interface IState { draggingFile: boolean; searching: boolean; searchTerm?: string; - searchScope?: "All" | "Room"; + searchScope?: SearchScope; searchResults?: XOR<{}, { count: number; highlights: string[]; @@ -1263,7 +1263,7 @@ export default class RoomView extends React.Component { }); } - private onSearch = (term: string, scope) => { + private onSearch = (term: string, scope: SearchScope) => { this.setState({ searchTerm: term, searchScope: scope, @@ -1284,7 +1284,7 @@ export default class RoomView extends React.Component { this.searchId = new Date().getTime(); let roomId; - if (scope === "Room") roomId = this.state.room.roomId; + if (scope === SearchScope.Room) roomId = this.state.room.roomId; debuglog("sending search request"); const searchPromise = eventSearch(term, roomId); diff --git a/src/components/views/elements/DesktopBuildsNotice.tsx b/src/components/views/elements/DesktopBuildsNotice.tsx index fd1c7848aa..426554f31e 100644 --- a/src/components/views/elements/DesktopBuildsNotice.tsx +++ b/src/components/views/elements/DesktopBuildsNotice.tsx @@ -14,10 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from "react"; import EventIndexPeg from "../../../indexing/EventIndexPeg"; import { _t } from "../../../languageHandler"; import SdkConfig from "../../../SdkConfig"; -import React from "react"; +import dis from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import { UserTab } from "../dialogs/UserSettingsDialog"; + export enum WarningKind { Files, @@ -33,6 +37,22 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) { if (!isRoomEncrypted) return null; if (EventIndexPeg.get()) return null; + if (EventIndexPeg.error) { + return <> + {_t("Message search initialisation failed, check your settings for more information", {}, { + a: sub => ( { + evt.preventDefault(); + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Security, + }); + }}> + {sub} + ), + })} + ; + } + const {desktopBuilds, brand} = SdkConfig.get(); let text = null; diff --git a/src/components/views/elements/FormButton.js b/src/components/views/elements/FormButton.js deleted file mode 100644 index f6b4c986f5..0000000000 --- a/src/components/views/elements/FormButton.js +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2019 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 AccessibleButton from "./AccessibleButton"; - -export default function FormButton(props) { - const {className, label, kind, ...restProps} = props; - const newClassName = (className || "") + " mx_FormButton"; - const allProps = Object.assign({}, restProps, - {className: newClassName, kind: kind || "primary", children: [label]}); - return React.createElement(AccessibleButton, allProps); -} - -FormButton.propTypes = AccessibleButton.propTypes; diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.tsx similarity index 76% rename from src/components/views/messages/MKeyVerificationRequest.js rename to src/components/views/messages/MKeyVerificationRequest.tsx index 988606a766..69467cfa50 100644 --- a/src/components/views/messages/MKeyVerificationRequest.js +++ b/src/components/views/messages/MKeyVerificationRequest.tsx @@ -15,41 +15,40 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixEvent } from 'matrix-js-sdk/src'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; -import {getNameForEventRoom, userLabelForEventRoom} +import { getNameForEventRoom, userLabelForEventRoom } from '../../../utils/KeyVerificationStateObserver'; import dis from "../../../dispatcher/dispatcher"; -import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; -import {Action} from "../../../dispatcher/actions"; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; +import { Action } from "../../../dispatcher/actions"; import EventTileBubble from "./EventTileBubble"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +interface IProps { + mxEvent: MatrixEvent +} @replaceableComponent("views.messages.MKeyVerificationRequest") -export default class MKeyVerificationRequest extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } - - componentDidMount() { +export default class MKeyVerificationRequest extends React.Component { + public componentDidMount() { const request = this.props.mxEvent.verificationRequest; if (request) { - request.on("change", this._onRequestChanged); + request.on("change", this.onRequestChanged); } } - componentWillUnmount() { + public componentWillUnmount() { const request = this.props.mxEvent.verificationRequest; if (request) { - request.off("change", this._onRequestChanged); + request.off("change", this.onRequestChanged); } } - _openRequest = () => { - const {verificationRequest} = this.props.mxEvent; + private openRequest = () => { + const { verificationRequest } = this.props.mxEvent; const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); dis.dispatch({ action: Action.SetRightPanelPhase, @@ -58,15 +57,15 @@ export default class MKeyVerificationRequest extends React.Component { }); }; - _onRequestChanged = () => { + private onRequestChanged = () => { this.forceUpdate(); }; - _onAcceptClicked = async () => { + private onAcceptClicked = async () => { const request = this.props.mxEvent.verificationRequest; if (request) { try { - this._openRequest(); + this.openRequest(); await request.accept(); } catch (err) { console.error(err.message); @@ -74,7 +73,7 @@ export default class MKeyVerificationRequest extends React.Component { } }; - _onRejectClicked = async () => { + private onRejectClicked = async () => { const request = this.props.mxEvent.verificationRequest; if (request) { try { @@ -85,7 +84,7 @@ export default class MKeyVerificationRequest extends React.Component { } }; - _acceptedLabel(userId) { + private acceptedLabel(userId: string) { const client = MatrixClientPeg.get(); const myUserId = client.getUserId(); if (userId === myUserId) { @@ -95,7 +94,7 @@ export default class MKeyVerificationRequest extends React.Component { } } - _cancelledLabel(userId) { + private cancelledLabel(userId: string) { const client = MatrixClientPeg.get(); const myUserId = client.getUserId(); const {cancellationCode} = this.props.mxEvent.verificationRequest; @@ -115,9 +114,8 @@ export default class MKeyVerificationRequest extends React.Component { } } - render() { + public render() { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const FormButton = sdk.getComponent("elements.FormButton"); const {mxEvent} = this.props; const request = mxEvent.verificationRequest; @@ -134,11 +132,11 @@ export default class MKeyVerificationRequest extends React.Component { let stateLabel; const accepted = request.ready || request.started || request.done; if (accepted) { - stateLabel = ( - {this._acceptedLabel(request.receivingUserId)} + stateLabel = ( + {this.acceptedLabel(request.receivingUserId)} ); } else if (request.cancelled) { - stateLabel = this._cancelledLabel(request.cancellingUserId); + stateLabel = this.cancelledLabel(request.cancellingUserId); } else if (request.accepting) { stateLabel = _t("Accepting …"); } else if (request.declining) { @@ -153,8 +151,12 @@ export default class MKeyVerificationRequest extends React.Component { subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId()); if (request.canAccept) { stateNode = (
- - + + {_t("Decline")} + + + {_t("Accept")} +
); } } else { // request sent by us @@ -174,8 +176,3 @@ export default class MKeyVerificationRequest extends React.Component { return null; } } - -MKeyVerificationRequest.propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, -}; diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index edfe0e3483..d3f2ba8cbf 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -195,14 +195,7 @@ export default class VerificationPanel extends React.PureComponent

{description}

- - + onClick={this.onReciprocateYesClick} + > + { _t("Yes") } +
; } else { diff --git a/src/components/views/rooms/SearchBar.js b/src/components/views/rooms/SearchBar.js deleted file mode 100644 index 029516c932..0000000000 --- a/src/components/views/rooms/SearchBar.js +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2020 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, {createRef} from 'react'; -import AccessibleButton from "../elements/AccessibleButton"; -import classNames from "classnames"; -import { _t } from '../../../languageHandler'; -import {Key} from "../../../Keyboard"; -import DesktopBuildsNotice, {WarningKind} from "../elements/DesktopBuildsNotice"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; - -@replaceableComponent("views.rooms.SearchBar") -export default class SearchBar extends React.Component { - constructor(props) { - super(props); - - this._search_term = createRef(); - - this.state = { - scope: 'Room', - }; - } - - onThisRoomClick = () => { - this.setState({ scope: 'Room' }, () => this._searchIfQuery()); - }; - - onAllRoomsClick = () => { - this.setState({ scope: 'All' }, () => this._searchIfQuery()); - }; - - onSearchChange = (e) => { - switch (e.key) { - case Key.ENTER: - this.onSearch(); - break; - case Key.ESCAPE: - this.props.onCancelClick(); - break; - } - }; - - _searchIfQuery() { - if (this._search_term.current.value) { - this.onSearch(); - } - } - - onSearch = () => { - this.props.onSearch(this._search_term.current.value, this.state.scope); - }; - - render() { - const searchButtonClasses = classNames("mx_SearchBar_searchButton", { - mx_SearchBar_searching: this.props.searchInProgress, - }); - const thisRoomClasses = classNames("mx_SearchBar_button", { - mx_SearchBar_unselected: this.state.scope !== 'Room', - }); - const allRoomsClasses = classNames("mx_SearchBar_button", { - mx_SearchBar_unselected: this.state.scope !== 'All', - }); - - return ( - <> -
-
- - {_t("This Room")} - - - {_t("All Rooms")} - -
-
- - -
- -
- - - ); - } -} diff --git a/src/components/views/rooms/SearchBar.tsx b/src/components/views/rooms/SearchBar.tsx new file mode 100644 index 0000000000..d71bb8da73 --- /dev/null +++ b/src/components/views/rooms/SearchBar.tsx @@ -0,0 +1,130 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2020 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, { createRef, RefObject } from 'react'; +import AccessibleButton from "../elements/AccessibleButton"; +import classNames from "classnames"; +import { _t } from '../../../languageHandler'; +import { Key } from "../../../Keyboard"; +import DesktopBuildsNotice, { WarningKind } from "../elements/DesktopBuildsNotice"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +interface IProps { + onCancelClick: () => void; + onSearch: (query: string, scope: string) => void; + searchInProgress?: boolean; + isRoomEncrypted?: boolean; +} + +interface IState { + scope: SearchScope; +} + +export enum SearchScope { + Room = "Room", + All = "All", +} + +@replaceableComponent("views.rooms.SearchBar") +export default class SearchBar extends React.Component { + private searchTerm: RefObject = createRef(); + + constructor(props: IProps) { + super(props); + this.state = { + scope: SearchScope.Room, + }; + } + + private onThisRoomClick = () => { + this.setState({ scope: SearchScope.Room }, () => this.searchIfQuery()); + }; + + private onAllRoomsClick = () => { + this.setState({ scope: SearchScope.All }, () => this.searchIfQuery()); + }; + + private onSearchChange = (e: React.KeyboardEvent) => { + switch (e.key) { + case Key.ENTER: + this.onSearch(); + break; + case Key.ESCAPE: + this.props.onCancelClick(); + break; + } + }; + + private searchIfQuery(): void { + if (this.searchTerm.current.value) { + this.onSearch(); + } + } + + private onSearch = (): void => { + this.props.onSearch(this.searchTerm.current.value, this.state.scope); + }; + + public render() { + const searchButtonClasses = classNames("mx_SearchBar_searchButton", { + mx_SearchBar_searching: this.props.searchInProgress, + }); + const thisRoomClasses = classNames("mx_SearchBar_button", { + mx_SearchBar_unselected: this.state.scope !== SearchScope.Room, + }); + const allRoomsClasses = classNames("mx_SearchBar_button", { + mx_SearchBar_unselected: this.state.scope !== SearchScope.All, + }); + + return ( + <> +
+
+ + {_t("This Room")} + + + {_t("All Rooms")} + +
+
+ + +
+ +
+ + + ); + } +} diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx index 209babbf9d..45b65ae1fb 100644 --- a/src/components/views/toasts/GenericToast.tsx +++ b/src/components/views/toasts/GenericToast.tsx @@ -15,8 +15,8 @@ limitations under the License. */ import React, {ReactNode} from "react"; +import AccessibleButton from "../elements/AccessibleButton"; -import FormButton from "../elements/FormButton"; import {XOR} from "../../../@types/common"; export interface IProps { @@ -50,8 +50,12 @@ const GenericToast: React.FC> = ({ {detailContent}
- {onReject && rejectLabel && } - + {onReject && rejectLabel && + { rejectLabel } + } + + { acceptLabel } +
; }; diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index a0660318bc..c09043da24 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -23,7 +23,7 @@ import { _t } from '../../../languageHandler'; import { ActionPayload } from '../../../dispatcher/payloads'; import CallHandler, { AudioID } from '../../../CallHandler'; import RoomAvatar from '../avatars/RoomAvatar'; -import FormButton from '../elements/FormButton'; +import AccessibleButton from '../elements/AccessibleButton'; import { CallState } from 'matrix-js-sdk/src/webrtc/call'; import {replaceableComponent} from "../../../utils/replaceableComponent"; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; @@ -143,21 +143,22 @@ export default class IncomingCallBox extends React.Component { />
- + > + { _t("Decline") } +
- + > + { _t("Accept") } +
; } } - diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 30cb753975..6438ecc0f2 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -164,4 +164,9 @@ export enum Action { * Inserts content into the active composer. Should be used with ComposerInsertPayload */ ComposerInsert = "composer_insert", + + /** + * Switches space. Should be used with SwitchSpacePayload. + */ + SwitchSpace = "switch_space", } diff --git a/src/dispatcher/payloads/SwitchSpacePayload.ts b/src/dispatcher/payloads/SwitchSpacePayload.ts new file mode 100644 index 0000000000..04eb744334 --- /dev/null +++ b/src/dispatcher/payloads/SwitchSpacePayload.ts @@ -0,0 +1,27 @@ +/* +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 { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface SwitchSpacePayload extends ActionPayload { + action: Action.SwitchSpace; + + /** + * The number of the space to switch to, 1-indexed, 0 is Home. + */ + num: number; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b88dc79da5..0c2bf1ca57 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1933,6 +1933,7 @@ "Error loading Widget": "Error loading Widget", "Error - Mixed content": "Error - Mixed content", "Popout widget": "Popout widget", + "Message search initialisation failed, check your settings for more information": "Message search initialisation failed, check your settings for more information", "Use the Desktop app to see all encrypted files": "Use the Desktop app to see all encrypted files", "Use the Desktop app to search encrypted messages": "Use the Desktop app to search encrypted messages", "This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files", @@ -3007,5 +3008,6 @@ "Esc": "Esc", "Enter": "Enter", "Space": "Space", - "End": "End" + "End": "End", + "[number]": "[number]" } diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 9463949aff..ed1a515ea6 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -33,6 +33,7 @@ import {EnhancedMap, mapDiff} from "../utils/maps"; import {setHasDiff} from "../utils/sets"; import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory"; import RoomViewStore from "./RoomViewStore"; +import {Action} from "../dispatcher/actions"; interface IState {} @@ -571,6 +572,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.setActiveSpace(null, false); } break; + case Action.SwitchSpace: + if (payload.num === 0) { + this.setActiveSpace(null); + } else if (this.spacePanelSpaces.length >= payload.num) { + this.setActiveSpace(this.spacePanelSpaces[payload.num - 1]); + } } }