Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/ts/5

This commit is contained in:
Michael Telatynski 2021-06-22 17:28:19 +01:00
commit 59258585b3
24 changed files with 295 additions and 276 deletions

3
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,3 @@
<!-- Please read https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.rst before submitting your pull request -->
<!-- Include a Sign-Off as described in https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.rst#sign-off -->

View file

@ -123,7 +123,6 @@
@import "./views/elements/_EventListSummary.scss"; @import "./views/elements/_EventListSummary.scss";
@import "./views/elements/_FacePile.scss"; @import "./views/elements/_FacePile.scss";
@import "./views/elements/_Field.scss"; @import "./views/elements/_Field.scss";
@import "./views/elements/_FormButton.scss";
@import "./views/elements/_ImageView.scss"; @import "./views/elements/_ImageView.scss";
@import "./views/elements/_InfoTooltip.scss"; @import "./views/elements/_InfoTooltip.scss";
@import "./views/elements/_InlineSpinner.scss"; @import "./views/elements/_InlineSpinner.scss";

View file

@ -134,8 +134,9 @@ limitations under the License.
.mx_Toast_buttons { .mx_Toast_buttons {
float: right; float: right;
display: flex; display: flex;
gap: 5px;
.mx_FormButton { .mx_AccessibleButton {
min-width: 96px; min-width: 96px;
box-sizing: border-box; box-sizing: border-box;
} }

View file

@ -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;
}
}

View file

@ -259,16 +259,6 @@ limitations under the License.
.mx_AccessibleButton.mx_AccessibleButton_hasKind { .mx_AccessibleButton.mx_AccessibleButton_hasKind {
padding: 8px 18px; 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, .mx_VerificationShowSas .mx_AccessibleButton,

View file

@ -58,7 +58,7 @@ limitations under the License.
} }
.mx_VerificationPanel_reciprocate_section { .mx_VerificationPanel_reciprocate_section {
.mx_FormButton { .mx_AccessibleButton {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; padding: 10px;

View file

@ -73,7 +73,7 @@ limitations under the License.
} }
} }
.mx_FormButton { .mx_AccessibleButton {
padding: 8px 22px; padding: 8px 22px;
margin-left: auto; margin-left: auto;
display: block; display: block;

View file

@ -24,10 +24,10 @@ clone() {
# Try the PR author's branch in case it exists on the deps as well. # Try the PR author's branch in case it exists on the deps as well.
# First we check if GITHUB_HEAD_REF is defined, # First we check if GITHUB_HEAD_REF is defined,
# Then we check if BUILDKITE_BRANCH is defined, # Then we check if BUILDKITE_BRANCH is defined,
# if it isn't we can assume this is a Netlify build # if they aren't we can assume this is a Netlify build
if [ -n ${GITHUB_HEAD_REF+x} ]; then if [ -n "$GITHUB_HEAD_REF" ]; then
head=$GITHUB_HEAD_REF head=$GITHUB_HEAD_REF
elif [ -n ${BUILDKITE_BRANCH+x} ]; then elif [ -n "$BUILDKITE_BRANCH" ]; then
head=$BUILDKITE_BRANCH head=$BUILDKITE_BRANCH
else else
# Netlify doesn't give us info about the fork so we have to get it from GitHub API # Netlify doesn't give us info about the fork so we have to get it from GitHub API
@ -36,7 +36,7 @@ else
head=$(curl $apiEndpoint | jq -r '.head.label') head=$(curl $apiEndpoint | jq -r '.head.label')
fi 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 # * "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 # * "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. # 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 # to determine whether the branch is from a fork or not
BRANCH_ARRAY=(${head//:/ }) BRANCH_ARRAY=(${head//:/ })
if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then 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 else
clone $GITHUB_ACTOR $defrepo $head REPO_ARRAY=(${GITHUB_REPOSITORY//\// })
clone $REPO_ARRAY[0] $defrepo $GITHUB_HEAD_REF
fi fi
else
clone $deforg $defrepo $BUILDKITE_BRANCH
fi
elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then
clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]} clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]}
fi fi
# Try the target branch of the push or PR. # 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 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 clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
fi fi

View file

@ -385,7 +385,7 @@ export class ModalManager {
</div> </div>
); );
ReactDOM.render(dialog, ModalManager.getOrCreateContainer()); setImmediate(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()));
} else { } else {
// This is safe to call repeatedly if we happen to do that // This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer()); ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());

View file

@ -57,6 +57,8 @@ export enum Modifiers {
// Meta-modifier: isMac ? CMD : CONTROL // Meta-modifier: isMac ? CMD : CONTROL
export const CMD_OR_CTRL = isMac ? Modifiers.COMMAND : Modifiers.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 { interface IKeybind {
modifiers?: Modifiers[]; modifiers?: Modifiers[];
@ -319,6 +321,7 @@ const alternateKeyName: Record<string, string> = {
[Key.SPACE]: _td("Space"), [Key.SPACE]: _td("Space"),
[Key.HOME]: _td("Home"), [Key.HOME]: _td("Home"),
[Key.END]: _td("End"), [Key.END]: _td("End"),
[DIGITS]: _td("[number]"),
}; };
const keyIcon: Record<string, string> = { const keyIcon: Record<string, string> = {
[Key.ARROW_UP]: "↑", [Key.ARROW_UP]: "↑",

View file

@ -32,13 +32,9 @@ import SettingsStore from "../settings/SettingsStore";
const ROOM_REGEX = /\B#\S*/g; const ROOM_REGEX = /\B#\S*/g;
function score(query: string, space: string) { // Prefer canonical aliases over non-canonical ones
const index = space.indexOf(query); function canonicalScore(displayedAlias: string, room: Room): number {
if (index === -1) { return displayedAlias === room.getCanonicalAlias() ? 0 : 1;
return Infinity;
} else {
return index;
}
} }
function matcherObject(room: Room, displayedAlias: string, matchName = "") { function matcherObject(room: Room, displayedAlias: string, matchName = "") {
@ -106,7 +102,7 @@ export default class RoomProvider extends AutocompleteProvider {
const matchedString = command[0]; const matchedString = command[0];
completions = this.matcher.match(matchedString, limit); completions = this.matcher.match(matchedString, limit);
completions = sortBy(completions, [ completions = sortBy(completions, [
(c) => score(matchedString, c.displayedAlias), (c) => canonicalScore(c.displayedAlias, c.room),
(c) => c.displayedAlias.length, (c) => c.displayedAlias.length,
]); ]);
completions = uniqBy(completions, (match) => match.room); completions = uniqBy(completions, (match) => match.room);

View file

@ -60,7 +60,7 @@ import ScrollPanel from "./ScrollPanel";
import TimelinePanel from "./TimelinePanel"; import TimelinePanel from "./TimelinePanel";
import ErrorBoundary from "../views/elements/ErrorBoundary"; import ErrorBoundary from "../views/elements/ErrorBoundary";
import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; 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 RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
import AuxPanel from "../views/rooms/AuxPanel"; import AuxPanel from "../views/rooms/AuxPanel";
import RoomHeader from "../views/rooms/RoomHeader"; import RoomHeader from "../views/rooms/RoomHeader";
@ -139,7 +139,7 @@ export interface IState {
draggingFile: boolean; draggingFile: boolean;
searching: boolean; searching: boolean;
searchTerm?: string; searchTerm?: string;
searchScope?: "All" | "Room"; searchScope?: SearchScope;
searchResults?: XOR<{}, { searchResults?: XOR<{}, {
count: number; count: number;
highlights: string[]; highlights: string[];
@ -1263,7 +1263,7 @@ export default class RoomView extends React.Component<IProps, IState> {
}); });
} }
private onSearch = (term: string, scope) => { private onSearch = (term: string, scope: SearchScope) => {
this.setState({ this.setState({
searchTerm: term, searchTerm: term,
searchScope: scope, searchScope: scope,
@ -1284,7 +1284,7 @@ export default class RoomView extends React.Component<IProps, IState> {
this.searchId = new Date().getTime(); this.searchId = new Date().getTime();
let roomId; let roomId;
if (scope === "Room") roomId = this.state.room.roomId; if (scope === SearchScope.Room) roomId = this.state.room.roomId;
debuglog("sending search request"); debuglog("sending search request");
const searchPromise = eventSearch(term, roomId); const searchPromise = eventSearch(term, roomId);

View file

@ -14,10 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react";
import EventIndexPeg from "../../../indexing/EventIndexPeg"; import EventIndexPeg from "../../../indexing/EventIndexPeg";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig"; 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 { export enum WarningKind {
Files, Files,
@ -33,6 +37,22 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) {
if (!isRoomEncrypted) return null; if (!isRoomEncrypted) return null;
if (EventIndexPeg.get()) return null; if (EventIndexPeg.get()) return null;
if (EventIndexPeg.error) {
return <>
{_t("Message search initialisation failed, check <a>your settings</a> for more information", {}, {
a: sub => (<a onClick={(evt) => {
evt.preventDefault();
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Security,
});
}}>
{sub}
</a>),
})}
</>;
}
const {desktopBuilds, brand} = SdkConfig.get(); const {desktopBuilds, brand} = SdkConfig.get();
let text = null; let text = null;

View file

@ -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;

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { MatrixEvent } from 'matrix-js-sdk/src';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -27,28 +27,27 @@ import {Action} from "../../../dispatcher/actions";
import EventTileBubble from "./EventTileBubble"; import EventTileBubble from "./EventTileBubble";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
mxEvent: MatrixEvent
}
@replaceableComponent("views.messages.MKeyVerificationRequest") @replaceableComponent("views.messages.MKeyVerificationRequest")
export default class MKeyVerificationRequest extends React.Component { export default class MKeyVerificationRequest extends React.Component<IProps> {
constructor(props) { public componentDidMount() {
super(props);
this.state = {};
}
componentDidMount() {
const request = this.props.mxEvent.verificationRequest; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
request.on("change", this._onRequestChanged); request.on("change", this.onRequestChanged);
} }
} }
componentWillUnmount() { public componentWillUnmount() {
const request = this.props.mxEvent.verificationRequest; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
request.off("change", this._onRequestChanged); request.off("change", this.onRequestChanged);
} }
} }
_openRequest = () => { private openRequest = () => {
const { verificationRequest } = this.props.mxEvent; const { verificationRequest } = this.props.mxEvent;
const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId);
dis.dispatch({ dis.dispatch({
@ -58,15 +57,15 @@ export default class MKeyVerificationRequest extends React.Component {
}); });
}; };
_onRequestChanged = () => { private onRequestChanged = () => {
this.forceUpdate(); this.forceUpdate();
}; };
_onAcceptClicked = async () => { private onAcceptClicked = async () => {
const request = this.props.mxEvent.verificationRequest; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
try { try {
this._openRequest(); this.openRequest();
await request.accept(); await request.accept();
} catch (err) { } catch (err) {
console.error(err.message); 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; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
try { try {
@ -85,7 +84,7 @@ export default class MKeyVerificationRequest extends React.Component {
} }
}; };
_acceptedLabel(userId) { private acceptedLabel(userId: string) {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const myUserId = client.getUserId(); const myUserId = client.getUserId();
if (userId === myUserId) { 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 client = MatrixClientPeg.get();
const myUserId = client.getUserId(); const myUserId = client.getUserId();
const {cancellationCode} = this.props.mxEvent.verificationRequest; 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 AccessibleButton = sdk.getComponent("elements.AccessibleButton");
const FormButton = sdk.getComponent("elements.FormButton");
const {mxEvent} = this.props; const {mxEvent} = this.props;
const request = mxEvent.verificationRequest; const request = mxEvent.verificationRequest;
@ -134,11 +132,11 @@ export default class MKeyVerificationRequest extends React.Component {
let stateLabel; let stateLabel;
const accepted = request.ready || request.started || request.done; const accepted = request.ready || request.started || request.done;
if (accepted) { if (accepted) {
stateLabel = (<AccessibleButton onClick={this._openRequest}> stateLabel = (<AccessibleButton onClick={this.openRequest}>
{this._acceptedLabel(request.receivingUserId)} {this.acceptedLabel(request.receivingUserId)}
</AccessibleButton>); </AccessibleButton>);
} else if (request.cancelled) { } else if (request.cancelled) {
stateLabel = this._cancelledLabel(request.cancellingUserId); stateLabel = this.cancelledLabel(request.cancellingUserId);
} else if (request.accepting) { } else if (request.accepting) {
stateLabel = _t("Accepting …"); stateLabel = _t("Accepting …");
} else if (request.declining) { } else if (request.declining) {
@ -153,8 +151,12 @@ export default class MKeyVerificationRequest extends React.Component {
subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId()); subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId());
if (request.canAccept) { if (request.canAccept) {
stateNode = (<div className="mx_cryptoEvent_buttons"> stateNode = (<div className="mx_cryptoEvent_buttons">
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} /> <AccessibleButton kind="danger" onClick={this.onRejectClicked}>
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} /> {_t("Decline")}
</AccessibleButton>
<AccessibleButton onClick={this.onAcceptClicked}>
{_t("Accept")}
</AccessibleButton>
</div>); </div>);
} }
} else { // request sent by us } else { // request sent by us
@ -174,8 +176,3 @@ export default class MKeyVerificationRequest extends React.Component {
return null; return null;
} }
} }
MKeyVerificationRequest.propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
};

View file

@ -195,14 +195,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
private renderQRReciprocatePhase() { private renderQRReciprocatePhase() {
const {member, request} = this.props; const {member, request} = this.props;
let Button; const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
// a bit of a hack, but the FormButton should only be used in the right panel
// they should probably just be the same component with a css class applied to it?
if (this.props.inDialog) {
Button = sdk.getComponent("elements.AccessibleButton");
} else {
Button = sdk.getComponent("elements.FormButton");
}
const description = request.isSelfVerification ? const description = request.isSelfVerification ?
_t("Almost there! Is your other session showing the same shield?") : _t("Almost there! Is your other session showing the same shield?") :
_t("Almost there! Is %(displayName)s showing the same shield?", { _t("Almost there! Is %(displayName)s showing the same shield?", {
@ -211,21 +204,24 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
let body: JSX.Element; let body: JSX.Element;
if (this.state.reciprocateQREvent) { if (this.state.reciprocateQREvent) {
// Element Web doesn't support scanning yet, so assume here we're the client being scanned. // Element Web doesn't support scanning yet, so assume here we're the client being scanned.
//
// we're passing both a label and a child string to Button as
// FormButton and AccessibleButton expect this differently
body = <React.Fragment> body = <React.Fragment>
<p>{description}</p> <p>{description}</p>
<E2EIcon isUser={true} status="verified" size={128} hideTooltip={true} /> <E2EIcon isUser={true} status="verified" size={128} hideTooltip={true} />
<div className="mx_VerificationPanel_reciprocateButtons"> <div className="mx_VerificationPanel_reciprocateButtons">
<Button <AccessibleButton
label={_t("No")} kind="danger" kind="danger"
disabled={this.state.reciprocateButtonClicked} disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateNoClick}>{_t("No")}</Button> onClick={this.onReciprocateNoClick}
<Button >
label={_t("Yes")} kind="primary" { _t("No") }
</AccessibleButton>
<AccessibleButton
kind="primary"
disabled={this.state.reciprocateButtonClicked} disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateYesClick}>{_t("Yes")}</Button> onClick={this.onReciprocateYesClick}
>
{ _t("Yes") }
</AccessibleButton>
</div> </div>
</React.Fragment>; </React.Fragment>;
} else { } else {

View file

@ -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 (
<>
<div className="mx_SearchBar">
<div className="mx_SearchBar_buttons" role="radiogroup">
<AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick} aria-checked={this.state.scope === 'Room'} role="radio">
{_t("This Room")}
</AccessibleButton>
<AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick} aria-checked={this.state.scope === 'All'} role="radio">
{_t("All Rooms")}
</AccessibleButton>
</div>
<div className="mx_SearchBar_input mx_textinput">
<input ref={this._search_term} type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch} />
</div>
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
</div>
<DesktopBuildsNotice isRoomEncrypted={this.props.isRoomEncrypted} kind={WarningKind.Search} />
</>
);
}
}

View file

@ -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<IProps, IState> {
private searchTerm: RefObject<HTMLInputElement> = 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 (
<>
<div className="mx_SearchBar">
<div className="mx_SearchBar_buttons" role="radiogroup">
<AccessibleButton
className={thisRoomClasses}
onClick={this.onThisRoomClick}
aria-checked={this.state.scope === SearchScope.Room}
role="radio"
>
{_t("This Room")}
</AccessibleButton>
<AccessibleButton
className={allRoomsClasses}
onClick={this.onAllRoomsClick}
aria-checked={this.state.scope === SearchScope.All}
role="radio"
>
{_t("All Rooms")}
</AccessibleButton>
</div>
<div className="mx_SearchBar_input mx_textinput">
<input
ref={this.searchTerm}
type="text"
autoFocus={true}
placeholder={_t("Search…")}
onKeyDown={this.onSearchChange}
/>
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch} />
</div>
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
</div>
<DesktopBuildsNotice isRoomEncrypted={this.props.isRoomEncrypted} kind={WarningKind.Search} />
</>
);
}
}

View file

@ -15,8 +15,8 @@ limitations under the License.
*/ */
import React, {ReactNode} from "react"; import React, {ReactNode} from "react";
import AccessibleButton from "../elements/AccessibleButton";
import FormButton from "../elements/FormButton";
import {XOR} from "../../../@types/common"; import {XOR} from "../../../@types/common";
export interface IProps { export interface IProps {
@ -50,8 +50,12 @@ const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({
{detailContent} {detailContent}
</div> </div>
<div className="mx_Toast_buttons" aria-live="off"> <div className="mx_Toast_buttons" aria-live="off">
{onReject && rejectLabel && <FormButton label={rejectLabel} kind="danger" onClick={onReject} /> } {onReject && rejectLabel && <AccessibleButton kind="danger" onClick={onReject}>
<FormButton label={acceptLabel} onClick={onAccept} /> { rejectLabel }
</AccessibleButton> }
<AccessibleButton onClick={onAccept} kind="primary">
{ acceptLabel }
</AccessibleButton>
</div> </div>
</div>; </div>;
}; };

View file

@ -23,7 +23,7 @@ import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads'; import { ActionPayload } from '../../../dispatcher/payloads';
import CallHandler, { AudioID } from '../../../CallHandler'; import CallHandler, { AudioID } from '../../../CallHandler';
import RoomAvatar from '../avatars/RoomAvatar'; 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 { CallState } from 'matrix-js-sdk/src/webrtc/call';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
@ -143,21 +143,22 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
/> />
</div> </div>
<div className="mx_IncomingCallBox_buttons"> <div className="mx_IncomingCallBox_buttons">
<FormButton <AccessibleButton
className={"mx_IncomingCallBox_decline"} className={"mx_IncomingCallBox_decline"}
onClick={this.onRejectClick} onClick={this.onRejectClick}
kind="danger" kind="danger"
label={_t("Decline")} >
/> { _t("Decline") }
</AccessibleButton>
<div className="mx_IncomingCallBox_spacer" /> <div className="mx_IncomingCallBox_spacer" />
<FormButton <AccessibleButton
className={"mx_IncomingCallBox_accept"} className={"mx_IncomingCallBox_accept"}
onClick={this.onAnswerClick} onClick={this.onAnswerClick}
kind="primary" kind="primary"
label={_t("Accept")} >
/> { _t("Accept") }
</AccessibleButton>
</div> </div>
</div>; </div>;
} }
} }

View file

@ -164,4 +164,9 @@ export enum Action {
* Inserts content into the active composer. Should be used with ComposerInsertPayload * Inserts content into the active composer. Should be used with ComposerInsertPayload
*/ */
ComposerInsert = "composer_insert", ComposerInsert = "composer_insert",
/**
* Switches space. Should be used with SwitchSpacePayload.
*/
SwitchSpace = "switch_space",
} }

View file

@ -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;
}

View file

@ -1933,6 +1933,7 @@
"Error loading Widget": "Error loading Widget", "Error loading Widget": "Error loading Widget",
"Error - Mixed content": "Error - Mixed content", "Error - Mixed content": "Error - Mixed content",
"Popout widget": "Popout widget", "Popout widget": "Popout widget",
"Message search initialisation failed, check <a>your settings</a> for more information": "Message search initialisation failed, check <a>your settings</a> for more information",
"Use the <a>Desktop app</a> to see all encrypted files": "Use the <a>Desktop app</a> to see all encrypted files", "Use the <a>Desktop app</a> to see all encrypted files": "Use the <a>Desktop app</a> to see all encrypted files",
"Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> to search encrypted messages", "Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> 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", "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", "Esc": "Esc",
"Enter": "Enter", "Enter": "Enter",
"Space": "Space", "Space": "Space",
"End": "End" "End": "End",
"[number]": "[number]"
} }

View file

@ -33,6 +33,7 @@ import {EnhancedMap, mapDiff} from "../utils/maps";
import {setHasDiff} from "../utils/sets"; import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory"; import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore"; import RoomViewStore from "./RoomViewStore";
import {Action} from "../dispatcher/actions";
interface IState {} interface IState {}
@ -571,6 +572,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.setActiveSpace(null, false); this.setActiveSpace(null, false);
} }
break; 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]);
}
} }
} }