Merge remote-tracking branch 'upstream/develop' into show-username
This commit is contained in:
commit
bf77a4a2ab
24 changed files with 685 additions and 172 deletions
|
@ -76,6 +76,7 @@
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
|
@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_FeedbackDialog.scss";
|
@import "./views/dialogs/_FeedbackDialog.scss";
|
||||||
|
@import "./views/dialogs/_ForwardDialog.scss";
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
@import "./views/dialogs/_HostSignupDialog.scss";
|
@import "./views/dialogs/_HostSignupDialog.scss";
|
||||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
|
|
|
@ -365,6 +365,45 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_betaWarning {
|
||||||
|
padding: 12px 12px 12px 54px;
|
||||||
|
position: relative;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
width: 432px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $info-plinth-bg-color;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> p {
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
content: '';
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
left: 14px;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_inviteTeammates {
|
.mx_SpaceRoomView_inviteTeammates {
|
||||||
// XXX remove this when spaces leaves Beta
|
// XXX remove this when spaces leaves Beta
|
||||||
.mx_SpaceRoomView_inviteTeammates_betaDisclaimer {
|
.mx_SpaceRoomView_inviteTeammates_betaDisclaimer {
|
||||||
|
|
159
res/css/views/dialogs/_ForwardDialog.scss
Normal file
159
res/css/views/dialogs/_ForwardDialog.scss
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Robin Townsend <robin@robin.town>
|
||||||
|
|
||||||
|
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_ForwardDialog {
|
||||||
|
width: 520px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: 0;
|
||||||
|
height: 80vh;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
line-height: $font-15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_ForwardDialog_preview {
|
||||||
|
max-height: 30%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
div {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_msgOption {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When forwarding messages from encrypted rooms, EventTile will complain
|
||||||
|
// that our preview is unencrypted, which doesn't actually matter
|
||||||
|
.mx_EventTile_e2eIcon_unencrypted {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We also hide download links to not encourage users to try interacting
|
||||||
|
.mx_MFileBody_download {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> hr {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid $input-border-color;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_ForwardList {
|
||||||
|
display: contents;
|
||||||
|
|
||||||
|
.mx_SearchBox {
|
||||||
|
// To match the space around the title
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_noResults {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_results {
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 32px;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $groupFilterPanel-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_roomButton {
|
||||||
|
display: flex;
|
||||||
|
margin-right: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.mx_DecoratedRoomAvatar {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_entry_name {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_sendButton {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:not(.mx_ForwardList_canSend) .mx_ForwardList_sendLabel {
|
||||||
|
// Hide the "Send" label while preserving button size
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ForwardList_sendIcon, .mx_NotificationBadge {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NotificationBadge {
|
||||||
|
// Match the failed to send indicator's color with the disabled button
|
||||||
|
background-color: $button-danger-disabled-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_ForwardList_sending .mx_ForwardList_sendIcon {
|
||||||
|
background-color: $button-primary-bg-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 14px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_ForwardList_sent .mx_ForwardList_sendIcon {
|
||||||
|
background-color: $button-primary-bg-color;
|
||||||
|
mask-image: url('$(res)/img/element-icons/circle-sent.svg');
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 14px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,7 +59,6 @@ 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 ForwardMessage from "../views/rooms/ForwardMessage";
|
|
||||||
import SearchBar from "../views/rooms/SearchBar";
|
import SearchBar 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";
|
||||||
|
@ -136,7 +135,6 @@ export interface IState {
|
||||||
// Whether to highlight the event scrolled to
|
// Whether to highlight the event scrolled to
|
||||||
isInitialEventHighlighted?: boolean;
|
isInitialEventHighlighted?: boolean;
|
||||||
replyToEvent?: MatrixEvent;
|
replyToEvent?: MatrixEvent;
|
||||||
forwardingEvent?: MatrixEvent;
|
|
||||||
numUnreadMessages: number;
|
numUnreadMessages: number;
|
||||||
draggingFile: boolean;
|
draggingFile: boolean;
|
||||||
searching: boolean;
|
searching: boolean;
|
||||||
|
@ -323,7 +321,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
initialEventId: RoomViewStore.getInitialEventId(),
|
initialEventId: RoomViewStore.getInitialEventId(),
|
||||||
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||||
replyToEvent: RoomViewStore.getQuotingEvent(),
|
replyToEvent: RoomViewStore.getQuotingEvent(),
|
||||||
forwardingEvent: RoomViewStore.getForwardingEvent(),
|
|
||||||
// we should only peek once we have a ready client
|
// we should only peek once we have a ready client
|
||||||
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
|
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
|
||||||
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
||||||
|
@ -1410,18 +1407,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
dis.dispatch({ action: "open_room_settings" });
|
dis.dispatch({ action: "open_room_settings" });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCancelClick = () => {
|
|
||||||
console.log("updateTint from onCancelClick");
|
|
||||||
this.updateTint();
|
|
||||||
if (this.state.forwardingEvent) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'forward_event',
|
|
||||||
event: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dis.fire(Action.FocusComposer);
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAppsClick = () => {
|
private onAppsClick = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "appsDrawer",
|
action: "appsDrawer",
|
||||||
|
@ -1837,11 +1822,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
let aux = null;
|
let aux = null;
|
||||||
let previewBar;
|
let previewBar;
|
||||||
let hideCancel = false;
|
if (this.state.searching) {
|
||||||
if (this.state.forwardingEvent) {
|
|
||||||
aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
|
|
||||||
} else if (this.state.searching) {
|
|
||||||
hideCancel = true; // has own cancel
|
|
||||||
aux = <SearchBar
|
aux = <SearchBar
|
||||||
searchInProgress={this.state.searchInProgress}
|
searchInProgress={this.state.searchInProgress}
|
||||||
onCancelClick={this.onCancelSearchClick}
|
onCancelClick={this.onCancelSearchClick}
|
||||||
|
@ -1850,7 +1831,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
/>;
|
/>;
|
||||||
} else if (showRoomUpgradeBar) {
|
} else if (showRoomUpgradeBar) {
|
||||||
aux = <RoomUpgradeWarningBar room={this.state.room} recommendation={roomVersionRecommendation} />;
|
aux = <RoomUpgradeWarningBar room={this.state.room} recommendation={roomVersionRecommendation} />;
|
||||||
hideCancel = true;
|
|
||||||
} else if (myMembership !== "join") {
|
} else if (myMembership !== "join") {
|
||||||
// We do have a room object for this room, but we're not currently in it.
|
// We do have a room object for this room, but we're not currently in it.
|
||||||
// We may have a 3rd party invite to it.
|
// We may have a 3rd party invite to it.
|
||||||
|
@ -1859,7 +1839,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
inviterName = this.props.oobData.inviterName;
|
inviterName = this.props.oobData.inviterName;
|
||||||
}
|
}
|
||||||
const invitedEmail = this.props.threepidInvite?.toEmail;
|
const invitedEmail = this.props.threepidInvite?.toEmail;
|
||||||
hideCancel = true;
|
|
||||||
previewBar = (
|
previewBar = (
|
||||||
<RoomPreviewBar
|
<RoomPreviewBar
|
||||||
onJoinClick={this.onJoinButtonClicked}
|
onJoinClick={this.onJoinButtonClicked}
|
||||||
|
@ -1977,11 +1956,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
hideMessagePanel = true;
|
hideMessagePanel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldHighlight = this.state.isInitialEventHighlighted;
|
|
||||||
let highlightedEventId = null;
|
let highlightedEventId = null;
|
||||||
if (this.state.forwardingEvent) {
|
if (this.state.isInitialEventHighlighted) {
|
||||||
highlightedEventId = this.state.forwardingEvent.getId();
|
|
||||||
} else if (shouldHighlight) {
|
|
||||||
highlightedEventId = this.state.initialEventId;
|
highlightedEventId = this.state.initialEventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2070,7 +2046,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
inRoom={myMembership === 'join'}
|
inRoom={myMembership === 'join'}
|
||||||
onSearchClick={this.onSearchClick}
|
onSearchClick={this.onSearchClick}
|
||||||
onSettingsClick={this.onSettingsClick}
|
onSettingsClick={this.onSettingsClick}
|
||||||
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
|
||||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||||
e2eStatus={this.state.e2eStatus}
|
e2eStatus={this.state.e2eStatus}
|
||||||
|
|
|
@ -587,6 +587,10 @@ const SpaceSetupPrivateScope = ({ space, justCreatedOpts, onFinished }) => {
|
||||||
<h3>{ _t("Me and my teammates") }</h3>
|
<h3>{ _t("Me and my teammates") }</h3>
|
||||||
<div>{ _t("A private space for you and your teammates") }</div>
|
<div>{ _t("A private space for you and your teammates") }</div>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
<div className="mx_SpaceRoomView_betaWarning">
|
||||||
|
<h3>{ _t("Teammates might not be able to view or join any private rooms you make.") }</h3>
|
||||||
|
<p>{ _t("We're working on this as part of the beta, but just want to let you know.") }</p>
|
||||||
|
</div>
|
||||||
<SpaceFeedbackPrompt />
|
<SpaceFeedbackPrompt />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { MenuItem } from "../../structures/ContextMenu";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
||||||
|
import ForwardDialog from "../dialogs/ForwardDialog";
|
||||||
|
|
||||||
export function canCancel(eventStatus) {
|
export function canCancel(eventStatus) {
|
||||||
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
||||||
|
@ -157,10 +158,10 @@ export default class MessageContextMenu extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
onForwardClick = () => {
|
onForwardClick = () => {
|
||||||
if (this.props.onCloseDialog) this.props.onCloseDialog();
|
Modal.createTrackedDialog('Forward Message', '', ForwardDialog, {
|
||||||
dis.dispatch({
|
matrixClient: MatrixClientPeg.get(),
|
||||||
action: 'forward_event',
|
|
||||||
event: this.props.mxEvent,
|
event: this.props.mxEvent,
|
||||||
|
permalinkCreator: this.props.permalinkCreator,
|
||||||
});
|
});
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,6 +40,8 @@ interface IProps extends React.ComponentProps<typeof IconizedContextMenu> {
|
||||||
showUnpin?: boolean;
|
showUnpin?: boolean;
|
||||||
// override delete handler
|
// override delete handler
|
||||||
onDeleteClick?(): void;
|
onDeleteClick?(): void;
|
||||||
|
// override edit handler
|
||||||
|
onEditClick?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WidgetContextMenu: React.FC<IProps> = ({
|
const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
|
@ -47,6 +49,7 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
app,
|
app,
|
||||||
userWidget,
|
userWidget,
|
||||||
onDeleteClick,
|
onDeleteClick,
|
||||||
|
onEditClick,
|
||||||
showUnpin,
|
showUnpin,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -89,12 +92,16 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
|
|
||||||
let editButton;
|
let editButton;
|
||||||
if (canModify && WidgetUtils.isManagedByManager(app)) {
|
if (canModify && WidgetUtils.isManagedByManager(app)) {
|
||||||
const onEditClick = () => {
|
const _onEditClick = () => {
|
||||||
WidgetUtils.editWidget(room, app);
|
if (onEditClick) {
|
||||||
|
onEditClick();
|
||||||
|
} else {
|
||||||
|
WidgetUtils.editWidget(room, app);
|
||||||
|
}
|
||||||
onFinished();
|
onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
editButton = <IconizedContextMenuOption onClick={onEditClick} label={_t("Edit")} />;
|
editButton = <IconizedContextMenuOption onClick={_onEditClick} label={_t("Edit")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let snapshotButton;
|
let snapshotButton;
|
||||||
|
@ -116,24 +123,29 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
|
|
||||||
let deleteButton;
|
let deleteButton;
|
||||||
if (onDeleteClick || canModify) {
|
if (onDeleteClick || canModify) {
|
||||||
const onDeleteClickDefault = () => {
|
const _onDeleteClick = () => {
|
||||||
// Show delete confirmation dialog
|
if (onDeleteClick) {
|
||||||
Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
|
onDeleteClick();
|
||||||
title: _t("Delete Widget"),
|
} else {
|
||||||
description: _t(
|
// Show delete confirmation dialog
|
||||||
"Deleting a widget removes it for all users in this room." +
|
Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
|
||||||
" Are you sure you want to delete this widget?"),
|
title: _t("Delete Widget"),
|
||||||
button: _t("Delete widget"),
|
description: _t(
|
||||||
onFinished: (confirmed) => {
|
"Deleting a widget removes it for all users in this room." +
|
||||||
if (!confirmed) return;
|
" Are you sure you want to delete this widget?"),
|
||||||
WidgetUtils.setRoomWidget(roomId, app.id);
|
button: _t("Delete widget"),
|
||||||
},
|
onFinished: (confirmed) => {
|
||||||
});
|
if (!confirmed) return;
|
||||||
|
WidgetUtils.setRoomWidget(roomId, app.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onFinished();
|
onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteButton = <IconizedContextMenuOption
|
deleteButton = <IconizedContextMenuOption
|
||||||
onClick={onDeleteClick || onDeleteClickDefault}
|
onClick={_onDeleteClick}
|
||||||
label={userWidget ? _t("Remove") : _t("Remove for everyone")}
|
label={userWidget ? _t("Remove") : _t("Remove for everyone")}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
247
src/components/views/dialogs/ForwardDialog.tsx
Normal file
247
src/components/views/dialogs/ForwardDialog.tsx
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Robin Townsend <robin@robin.town>
|
||||||
|
|
||||||
|
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, {useMemo, useState, useEffect} from "react";
|
||||||
|
import classnames from "classnames";
|
||||||
|
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||||
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
|
import {_t} from "../../../languageHandler";
|
||||||
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
import {useSettingValue, useFeatureEnabled} from "../../../hooks/useSettings";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
import {Layout} from "../../../settings/Layout";
|
||||||
|
import {IDialogProps} from "./IDialogProps";
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import {avatarUrlForUser} from "../../../Avatar";
|
||||||
|
import EventTile from "../rooms/EventTile";
|
||||||
|
import SearchBox from "../../structures/SearchBox";
|
||||||
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
|
import {Alignment} from '../elements/Tooltip';
|
||||||
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
|
||||||
|
import NotificationBadge from "../rooms/NotificationBadge";
|
||||||
|
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
||||||
|
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||||
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
|
|
||||||
|
const AVATAR_SIZE = 30;
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
matrixClient: MatrixClient;
|
||||||
|
// The event to forward
|
||||||
|
event: MatrixEvent;
|
||||||
|
// We need a permalink creator for the source room to pass through to EventTile
|
||||||
|
// in case the event is a reply (even though the user can't get at the link)
|
||||||
|
permalinkCreator: RoomPermalinkCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IEntryProps {
|
||||||
|
room: Room;
|
||||||
|
event: MatrixEvent;
|
||||||
|
matrixClient: MatrixClient;
|
||||||
|
onFinished(success: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SendState {
|
||||||
|
CanSend,
|
||||||
|
Sending,
|
||||||
|
Sent,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Entry: React.FC<IEntryProps> = ({ room, event, matrixClient: cli, onFinished }) => {
|
||||||
|
const [sendState, setSendState] = useState<SendState>(SendState.CanSend);
|
||||||
|
|
||||||
|
const jumpToRoom = () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: room.roomId,
|
||||||
|
});
|
||||||
|
onFinished(true);
|
||||||
|
};
|
||||||
|
const send = async () => {
|
||||||
|
setSendState(SendState.Sending);
|
||||||
|
try {
|
||||||
|
await cli.sendEvent(room.roomId, event.getType(), event.getContent());
|
||||||
|
setSendState(SendState.Sent);
|
||||||
|
} catch (e) {
|
||||||
|
setSendState(SendState.Failed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let className;
|
||||||
|
let disabled = false;
|
||||||
|
let title;
|
||||||
|
let icon;
|
||||||
|
if (sendState === SendState.CanSend) {
|
||||||
|
className = "mx_ForwardList_canSend";
|
||||||
|
if (room.maySendMessage()) {
|
||||||
|
title = _t("Send");
|
||||||
|
} else {
|
||||||
|
disabled = true;
|
||||||
|
title = _t("You don't have permission to do this");
|
||||||
|
}
|
||||||
|
} else if (sendState === SendState.Sending) {
|
||||||
|
className = "mx_ForwardList_sending";
|
||||||
|
disabled = true;
|
||||||
|
title = _t("Sending");
|
||||||
|
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>;
|
||||||
|
} else if (sendState === SendState.Sent) {
|
||||||
|
className = "mx_ForwardList_sent";
|
||||||
|
disabled = true;
|
||||||
|
title = _t("Sent");
|
||||||
|
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>;
|
||||||
|
} else {
|
||||||
|
className = "mx_ForwardList_sendFailed";
|
||||||
|
disabled = true;
|
||||||
|
title = _t("Failed to send");
|
||||||
|
icon = <NotificationBadge
|
||||||
|
notification={StaticNotificationState.RED_EXCLAMATION}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_ForwardList_entry">
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
className="mx_ForwardList_roomButton"
|
||||||
|
onClick={jumpToRoom}
|
||||||
|
title={_t("Open link")}
|
||||||
|
yOffset={-20}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
>
|
||||||
|
<DecoratedRoomAvatar room={room} avatarSize={32} />
|
||||||
|
<span className="mx_ForwardList_entry_name">{ room.name }</span>
|
||||||
|
</AccessibleTooltipButton>
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
kind={sendState === SendState.Failed ? "danger_outline" : "primary_outline"}
|
||||||
|
className={`mx_ForwardList_sendButton ${className}`}
|
||||||
|
onClick={send}
|
||||||
|
disabled={disabled}
|
||||||
|
title={title}
|
||||||
|
yOffset={-20}
|
||||||
|
alignment={Alignment.Top}
|
||||||
|
>
|
||||||
|
<div className="mx_ForwardList_sendLabel">{ _t("Send") }</div>
|
||||||
|
{ icon }
|
||||||
|
</AccessibleTooltipButton>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => {
|
||||||
|
const userId = cli.getUserId();
|
||||||
|
const [profileInfo, setProfileInfo] = useState<any>({});
|
||||||
|
useEffect(() => {
|
||||||
|
cli.getProfileInfo(userId).then(info => setProfileInfo(info));
|
||||||
|
}, [cli, userId]);
|
||||||
|
|
||||||
|
// For the message preview we fake the sender as ourselves
|
||||||
|
const mockEvent = new MatrixEvent({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: userId,
|
||||||
|
content: event.getContent(),
|
||||||
|
unsigned: {
|
||||||
|
age: 97,
|
||||||
|
},
|
||||||
|
event_id: "$9999999999999999999999999999999999999999999",
|
||||||
|
room_id: event.getRoomId(),
|
||||||
|
});
|
||||||
|
mockEvent.sender = {
|
||||||
|
name: profileInfo.displayname || userId,
|
||||||
|
userId,
|
||||||
|
getAvatarUrl: (..._) => {
|
||||||
|
return avatarUrlForUser(
|
||||||
|
{ avatarUrl: profileInfo.avatar_url },
|
||||||
|
AVATAR_SIZE, AVATAR_SIZE, "crop",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getMxcAvatarUrl: () => profileInfo.avatar_url,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const lcQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
const spacesEnabled = useFeatureEnabled("feature_spaces");
|
||||||
|
const flairEnabled = useFeatureEnabled(UIFeature.Flair);
|
||||||
|
const previewLayout = useSettingValue<Layout>("layout");
|
||||||
|
|
||||||
|
let rooms = useMemo(() => sortRooms(
|
||||||
|
cli.getVisibleRooms().filter(
|
||||||
|
room => room.getMyMembership() === "join" &&
|
||||||
|
!(spacesEnabled && room.isSpaceRoom()),
|
||||||
|
),
|
||||||
|
), [cli, spacesEnabled]);
|
||||||
|
|
||||||
|
if (lcQuery) {
|
||||||
|
rooms = new QueryMatcher<Room>(rooms, {
|
||||||
|
keys: ["name"],
|
||||||
|
funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
|
||||||
|
shouldMatchWordsOnly: false,
|
||||||
|
}).match(lcQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={_t("Forward message")}
|
||||||
|
className="mx_ForwardDialog"
|
||||||
|
contentId="mx_ForwardList"
|
||||||
|
onFinished={onFinished}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<h3>{ _t("Message preview") }</h3>
|
||||||
|
<div className={classnames("mx_ForwardDialog_preview", {
|
||||||
|
"mx_IRCLayout": previewLayout == Layout.IRC,
|
||||||
|
"mx_GroupLayout": previewLayout == Layout.Group,
|
||||||
|
})}>
|
||||||
|
<EventTile
|
||||||
|
mxEvent={mockEvent}
|
||||||
|
layout={previewLayout}
|
||||||
|
enableFlair={flairEnabled}
|
||||||
|
permalinkCreator={permalinkCreator}
|
||||||
|
as="div"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div className="mx_ForwardList" id="mx_ForwardList">
|
||||||
|
<SearchBox
|
||||||
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
|
placeholder={_t("Search for rooms or people")}
|
||||||
|
onSearch={setQuery}
|
||||||
|
autoComplete={true}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
<AutoHideScrollbar className="mx_ForwardList_content">
|
||||||
|
{ rooms.length > 0 ? (
|
||||||
|
<div className="mx_ForwardList_results">
|
||||||
|
{ rooms.map(room =>
|
||||||
|
<Entry
|
||||||
|
key={room.roomId}
|
||||||
|
room={room}
|
||||||
|
event={event}
|
||||||
|
matrixClient={cli}
|
||||||
|
onFinished={onFinished}
|
||||||
|
/>,
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
) : <span className="mx_ForwardList_noResults">
|
||||||
|
{ _t("No results") }
|
||||||
|
</span> }
|
||||||
|
</AutoHideScrollbar>
|
||||||
|
</div>
|
||||||
|
</BaseDialog>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ForwardDialog;
|
|
@ -19,7 +19,7 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip, {Alignment} from './Tooltip';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
|
@ -28,6 +28,7 @@ interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
forceHide?: boolean;
|
forceHide?: boolean;
|
||||||
yOffset?: number;
|
yOffset?: number;
|
||||||
|
alignment?: Alignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -66,13 +67,14 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const {title, tooltip, children, tooltipClassName, forceHide, yOffset, ...props} = this.props;
|
const {title, tooltip, children, tooltipClassName, forceHide, yOffset, alignment, ...props} = this.props;
|
||||||
|
|
||||||
const tip = this.state.hover ? <Tooltip
|
const tip = this.state.hover ? <Tooltip
|
||||||
className="mx_AccessibleTooltipButton_container"
|
className="mx_AccessibleTooltipButton_container"
|
||||||
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
|
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
|
||||||
label={tooltip || title}
|
label={tooltip || title}
|
||||||
yOffset={yOffset}
|
yOffset={yOffset}
|
||||||
|
alignment={alignment}
|
||||||
/> : null;
|
/> : null;
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
|
|
@ -417,6 +417,8 @@ export default class AppTile extends React.Component {
|
||||||
onFinished={this._closeContextMenu}
|
onFinished={this._closeContextMenu}
|
||||||
showUnpin={!this.props.userWidget}
|
showUnpin={!this.props.userWidget}
|
||||||
userWidget={this.props.userWidget}
|
userWidget={this.props.userWidget}
|
||||||
|
onEditClick={this.props.onEditClick}
|
||||||
|
onDeleteClick={this.props.onDeleteClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
Copyright 2017 Michael Telatynski
|
|
||||||
|
|
||||||
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 PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import {Key} from '../../../Keyboard';
|
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
|
||||||
|
|
||||||
@replaceableComponent("views.rooms.ForwardMessage")
|
|
||||||
export default class ForwardMessage extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
onCancelClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
document.addEventListener('keydown', this._onKeyDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
document.removeEventListener('keydown', this._onKeyDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onKeyDown = ev => {
|
|
||||||
switch (ev.key) {
|
|
||||||
case Key.ESCAPE:
|
|
||||||
this.props.onCancelClick();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="mx_ForwardMessage">
|
|
||||||
<h1>{ _t('Please select the destination room for this message') }</h1>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,7 +22,6 @@ import { _t } from '../../../languageHandler';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||||
|
|
||||||
import { CancelButton } from './SimpleRoomHeader';
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||||
import E2EIcon from './E2EIcon';
|
import E2EIcon from './E2EIcon';
|
||||||
|
@ -42,7 +41,6 @@ export default class RoomHeader extends React.Component {
|
||||||
onSettingsClick: PropTypes.func,
|
onSettingsClick: PropTypes.func,
|
||||||
onSearchClick: PropTypes.func,
|
onSearchClick: PropTypes.func,
|
||||||
onLeaveClick: PropTypes.func,
|
onLeaveClick: PropTypes.func,
|
||||||
onCancelClick: PropTypes.func,
|
|
||||||
e2eStatus: PropTypes.string,
|
e2eStatus: PropTypes.string,
|
||||||
onAppsClick: PropTypes.func,
|
onAppsClick: PropTypes.func,
|
||||||
appsShown: PropTypes.bool,
|
appsShown: PropTypes.bool,
|
||||||
|
@ -52,7 +50,6 @@ export default class RoomHeader extends React.Component {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
editing: false,
|
editing: false,
|
||||||
inRoom: false,
|
inRoom: false,
|
||||||
onCancelClick: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -83,11 +80,6 @@ export default class RoomHeader extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let searchStatus = null;
|
let searchStatus = null;
|
||||||
let cancelButton = null;
|
|
||||||
|
|
||||||
if (this.props.onCancelClick) {
|
|
||||||
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't display the search count until the search completes and
|
// don't display the search count until the search completes and
|
||||||
// gives us a valid (possibly zero) searchCount.
|
// gives us a valid (possibly zero) searchCount.
|
||||||
|
@ -207,7 +199,6 @@ export default class RoomHeader extends React.Component {
|
||||||
<div className="mx_RoomHeader_e2eIcon">{ e2eIcon }</div>
|
<div className="mx_RoomHeader_e2eIcon">{ e2eIcon }</div>
|
||||||
{ name }
|
{ name }
|
||||||
{ topicElement }
|
{ topicElement }
|
||||||
{ cancelButton }
|
|
||||||
{ rightRow }
|
{ rightRow }
|
||||||
<RoomHeaderButtons room={this.props.room} />
|
<RoomHeaderButtons room={this.props.room} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,23 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
// cancel button which is shared between room header and simple room header
|
|
||||||
export function CancelButton(props) {
|
|
||||||
const {onClick} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AccessibleButton className='mx_RoomHeader_cancelButton' onClick={onClick}>
|
|
||||||
<img src={require("../../../../res/img/cancel.svg")} className='mx_filterFlipColor'
|
|
||||||
width="18" height="18" alt={_t("Cancel")} />
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A stripped-down room header used for things like the user settings
|
* A stripped-down room header used for things like the user settings
|
||||||
* and room directory.
|
* and room directory.
|
||||||
|
@ -41,18 +27,13 @@ export function CancelButton(props) {
|
||||||
export default class SimpleRoomHeader extends React.Component {
|
export default class SimpleRoomHeader extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
onCancelClick: PropTypes.func,
|
|
||||||
|
|
||||||
// `src` to a TintableSvg. Optional.
|
// `src` to a TintableSvg. Optional.
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let cancelButton;
|
|
||||||
let icon;
|
let icon;
|
||||||
if (this.props.onCancelClick) {
|
|
||||||
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
|
||||||
}
|
|
||||||
if (this.props.icon) {
|
if (this.props.icon) {
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
icon = <TintableSvg
|
icon = <TintableSvg
|
||||||
|
@ -66,7 +47,6 @@ export default class SimpleRoomHeader extends React.Component {
|
||||||
<div className="mx_RoomHeader_simpleHeader">
|
<div className="mx_RoomHeader_simpleHeader">
|
||||||
{ icon }
|
{ icon }
|
||||||
{ this.props.title }
|
{ this.props.title }
|
||||||
{ cancelButton }
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -367,7 +367,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||||
/**
|
/**
|
||||||
* Launch the integration manager on the stickers integration page
|
* Launch the integration manager on the stickers integration page
|
||||||
*/
|
*/
|
||||||
_launchManageIntegrations() {
|
_launchManageIntegrations = () => {
|
||||||
// TODO: Open the right integration manager for the widget
|
// TODO: Open the right integration manager for the widget
|
||||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||||
IntegrationManagers.sharedInstance().openAll(
|
IntegrationManagers.sharedInstance().openAll(
|
||||||
|
@ -382,7 +382,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||||
this.state.widgetId,
|
this.state.widgetId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let stickerPicker;
|
let stickerPicker;
|
||||||
|
@ -401,7 +401,7 @@ export default class Stickerpicker extends React.PureComponent {
|
||||||
key="controls_hide_stickers"
|
key="controls_hide_stickers"
|
||||||
className={className}
|
className={className}
|
||||||
onClick={this._onHideStickersClick}
|
onClick={this._onHideStickersClick}
|
||||||
active={this.state.showStickers}
|
active={this.state.showStickers.toString()}
|
||||||
title={_t("Hide Stickers")}
|
title={_t("Hide Stickers")}
|
||||||
>
|
>
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
|
|
|
@ -35,8 +35,8 @@ export const useSettingValue = <T>(settingName: string, roomId: string = null, e
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook to fetch whether a feature is enabled and dynamically update when that changes
|
// Hook to fetch whether a feature is enabled and dynamically update when that changes
|
||||||
export const useFeatureEnabled = (featureName: string, roomId: string = null) => {
|
export const useFeatureEnabled = (featureName: string, roomId: string = null): boolean => {
|
||||||
const [enabled, setEnabled] = useState(SettingsStore.getValue(featureName, roomId));
|
const [enabled, setEnabled] = useState(SettingsStore.getValue<boolean>(featureName, roomId));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ref = SettingsStore.watchSetting(featureName, roomId, () => {
|
const ref = SettingsStore.watchSetting(featureName, roomId, () => {
|
||||||
|
|
|
@ -1473,7 +1473,6 @@
|
||||||
"Encrypting your message...": "Encrypting your message...",
|
"Encrypting your message...": "Encrypting your message...",
|
||||||
"Your message was sent": "Your message was sent",
|
"Your message was sent": "Your message was sent",
|
||||||
"Failed to send": "Failed to send",
|
"Failed to send": "Failed to send",
|
||||||
"Please select the destination room for this message": "Please select the destination room for this message",
|
|
||||||
"Scroll to most recent messages": "Scroll to most recent messages",
|
"Scroll to most recent messages": "Scroll to most recent messages",
|
||||||
"Close preview": "Close preview",
|
"Close preview": "Close preview",
|
||||||
"and %(count)s others...|other": "and %(count)s others...",
|
"and %(count)s others...|other": "and %(count)s others...",
|
||||||
|
@ -2204,6 +2203,13 @@
|
||||||
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.",
|
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.",
|
||||||
"Report a bug": "Report a bug",
|
"Report a bug": "Report a bug",
|
||||||
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.",
|
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.",
|
||||||
|
"You don't have permission to do this": "You don't have permission to do this",
|
||||||
|
"Sending": "Sending",
|
||||||
|
"Sent": "Sent",
|
||||||
|
"Open link": "Open link",
|
||||||
|
"Forward message": "Forward message",
|
||||||
|
"Message preview": "Message preview",
|
||||||
|
"Search for rooms or people": "Search for rooms or people",
|
||||||
"Confirm abort of host creation": "Confirm abort of host creation",
|
"Confirm abort of host creation": "Confirm abort of host creation",
|
||||||
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
|
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
|
||||||
"Abort": "Abort",
|
"Abort": "Abort",
|
||||||
|
@ -2662,7 +2668,6 @@
|
||||||
"Some of your messages have not been sent": "Some of your messages have not been sent",
|
"Some of your messages have not been sent": "Some of your messages have not been sent",
|
||||||
"Delete all": "Delete all",
|
"Delete all": "Delete all",
|
||||||
"Retry all": "Retry all",
|
"Retry all": "Retry all",
|
||||||
"Sending": "Sending",
|
|
||||||
"You can select all or individual messages to retry or delete": "You can select all or individual messages to retry or delete",
|
"You can select all or individual messages to retry or delete": "You can select all or individual messages to retry or delete",
|
||||||
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
|
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
|
||||||
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
|
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
|
||||||
|
@ -2723,6 +2728,8 @@
|
||||||
"A private space to organise your rooms": "A private space to organise your rooms",
|
"A private space to organise your rooms": "A private space to organise your rooms",
|
||||||
"Me and my teammates": "Me and my teammates",
|
"Me and my teammates": "Me and my teammates",
|
||||||
"A private space for you and your teammates": "A private space for you and your teammates",
|
"A private space for you and your teammates": "A private space for you and your teammates",
|
||||||
|
"Teammates might not be able to view or join any private rooms you make.": "Teammates might not be able to view or join any private rooms you make.",
|
||||||
|
"We're working on this as part of the beta, but just want to let you know.": "We're working on this as part of the beta, but just want to let you know.",
|
||||||
"Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s",
|
"Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s",
|
||||||
"Inviting...": "Inviting...",
|
"Inviting...": "Inviting...",
|
||||||
"Invite your teammates": "Invite your teammates",
|
"Invite your teammates": "Invite your teammates",
|
||||||
|
|
|
@ -63,8 +63,7 @@ export class WatchManager {
|
||||||
|
|
||||||
if (!inRoomId) {
|
if (!inRoomId) {
|
||||||
// Fire updates to all the individual room watchers too, as they probably care about the change higher up.
|
// Fire updates to all the individual room watchers too, as they probably care about the change higher up.
|
||||||
const callbacks = Array.from(roomWatchers.values()).flat(1);
|
callbacks.push(...Array.from(roomWatchers.values()).flat(1));
|
||||||
callbacks.push(...callbacks);
|
|
||||||
} else if (roomWatchers.has(IRRELEVANT_ROOM)) {
|
} else if (roomWatchers.has(IRRELEVANT_ROOM)) {
|
||||||
callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM));
|
callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM));
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,6 @@ const INITIAL_STATE = {
|
||||||
// Any error that has occurred during loading
|
// Any error that has occurred during loading
|
||||||
roomLoadError: null,
|
roomLoadError: null,
|
||||||
|
|
||||||
forwardingEvent: null,
|
|
||||||
|
|
||||||
quotingEvent: null,
|
quotingEvent: null,
|
||||||
|
|
||||||
replyingToEvent: null,
|
replyingToEvent: null,
|
||||||
|
@ -150,11 +148,6 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
case 'on_logged_out':
|
case 'on_logged_out':
|
||||||
this.reset();
|
this.reset();
|
||||||
break;
|
break;
|
||||||
case 'forward_event':
|
|
||||||
this.setState({
|
|
||||||
forwardingEvent: payload.event,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'reply_to_event':
|
case 'reply_to_event':
|
||||||
// If currently viewed room does not match the room in which we wish to reply then change rooms
|
// If currently viewed room does not match the room in which we wish to reply then change rooms
|
||||||
// this can happen when performing a search across all rooms
|
// this can happen when performing a search across all rooms
|
||||||
|
@ -187,7 +180,6 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
roomAlias: payload.room_alias,
|
roomAlias: payload.room_alias,
|
||||||
initialEventId: payload.event_id,
|
initialEventId: payload.event_id,
|
||||||
isInitialEventHighlighted: payload.highlighted,
|
isInitialEventHighlighted: payload.highlighted,
|
||||||
forwardingEvent: null,
|
|
||||||
roomLoading: false,
|
roomLoading: false,
|
||||||
roomLoadError: null,
|
roomLoadError: null,
|
||||||
// should peek by default
|
// should peek by default
|
||||||
|
@ -207,14 +199,6 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
newState.replyingToEvent = payload.replyingToEvent;
|
newState.replyingToEvent = payload.replyingToEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.forwardingEvent) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'send_event',
|
|
||||||
room_id: newState.roomId,
|
|
||||||
event: this.state.forwardingEvent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
|
|
||||||
if (payload.auto_join) {
|
if (payload.auto_join) {
|
||||||
|
@ -428,11 +412,6 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
return this.state.joinError;
|
return this.state.joinError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The mxEvent if one is about to be forwarded
|
|
||||||
public getForwardingEvent() {
|
|
||||||
return this.state.forwardingEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The mxEvent if one is currently being replied to/quoted
|
// The mxEvent if one is currently being replied to/quoted
|
||||||
public getQuotingEvent() {
|
public getQuotingEvent() {
|
||||||
return this.state.replyingToEvent;
|
return this.state.replyingToEvent;
|
||||||
|
|
|
@ -332,7 +332,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContainerWidgets(room: Room, container: Container): IApp[] {
|
public getContainerWidgets(room: Room, container: Container): IApp[] {
|
||||||
return this.byRoom[room.roomId]?.[container]?.ordered || [];
|
return this.byRoom[room?.roomId]?.[container]?.ordered || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public isInContainer(room: Room, widget: IApp, container: Container): boolean {
|
public isInContainer(room: Room, widget: IApp, container: Container): boolean {
|
||||||
|
|
|
@ -19,11 +19,11 @@ limitations under the License.
|
||||||
* TODO: Convert this to a real TypeScript interface
|
* TODO: Convert this to a real TypeScript interface
|
||||||
*/
|
*/
|
||||||
export default class PermalinkConstructor {
|
export default class PermalinkConstructor {
|
||||||
forEvent(roomId: string, eventId: string, serverCandidates: string[]): string {
|
forEvent(roomId: string, eventId: string, serverCandidates: string[] = []): string {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
forRoom(roomIdOrAlias: string, serverCandidates: string[]): string {
|
forRoom(roomIdOrAlias: string, serverCandidates: string[] = []): string {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,12 +73,12 @@ export class PermalinkParts {
|
||||||
return new PermalinkParts(null, null, null, groupId, null);
|
return new PermalinkParts(null, null, null, groupId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static forRoom(roomIdOrAlias: string, viaServers: string[]): PermalinkParts {
|
static forRoom(roomIdOrAlias: string, viaServers: string[] = []): PermalinkParts {
|
||||||
return new PermalinkParts(roomIdOrAlias, null, null, null, viaServers || []);
|
return new PermalinkParts(roomIdOrAlias, null, null, null, viaServers);
|
||||||
}
|
}
|
||||||
|
|
||||||
static forEvent(roomId: string, eventId: string, viaServers: string[]): PermalinkParts {
|
static forEvent(roomId: string, eventId: string, viaServers: string[] = []): PermalinkParts {
|
||||||
return new PermalinkParts(roomId, eventId, null, null, viaServers || []);
|
return new PermalinkParts(roomId, eventId, null, null, viaServers);
|
||||||
}
|
}
|
||||||
|
|
||||||
get primaryEntityId(): string {
|
get primaryEntityId(): string {
|
||||||
|
|
|
@ -149,7 +149,7 @@ export class RoomPermalinkCreator {
|
||||||
// Prefer to use canonical alias for permalink if possible
|
// Prefer to use canonical alias for permalink if possible
|
||||||
const alias = this.room.getCanonicalAlias();
|
const alias = this.room.getCanonicalAlias();
|
||||||
if (alias) {
|
if (alias) {
|
||||||
return getPermalinkConstructor().forRoom(alias, this._serverCandidates);
|
return getPermalinkConstructor().forRoom(alias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return getPermalinkConstructor().forRoom(this.roomId, this._serverCandidates);
|
return getPermalinkConstructor().forRoom(this.roomId, this._serverCandidates);
|
||||||
|
@ -302,7 +302,7 @@ export function makeRoomPermalink(roomId: string): string {
|
||||||
}
|
}
|
||||||
const permalinkCreator = new RoomPermalinkCreator(room);
|
const permalinkCreator = new RoomPermalinkCreator(room);
|
||||||
permalinkCreator.load();
|
permalinkCreator.load();
|
||||||
return permalinkCreator.forRoom();
|
return permalinkCreator.forShareableRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeGroupPermalink(groupId: string): string {
|
export function makeGroupPermalink(groupId: string): string {
|
||||||
|
|
163
test/components/views/dialogs/ForwardDialog-test.js
Normal file
163
test/components/views/dialogs/ForwardDialog-test.js
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Robin Townsend <robin@robin.town>
|
||||||
|
|
||||||
|
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 "../../../skinned-sdk";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {configure, mount} from "enzyme";
|
||||||
|
import Adapter from "enzyme-adapter-react-16";
|
||||||
|
import {act} from "react-dom/test-utils";
|
||||||
|
|
||||||
|
import * as TestUtils from "../../../test-utils";
|
||||||
|
import {MatrixClientPeg} from "../../../../src/MatrixClientPeg";
|
||||||
|
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||||
|
import {RoomPermalinkCreator} from "../../../../src/utils/permalinks/Permalinks";
|
||||||
|
import ForwardDialog from "../../../../src/components/views/dialogs/ForwardDialog";
|
||||||
|
|
||||||
|
configure({ adapter: new Adapter() });
|
||||||
|
|
||||||
|
describe("ForwardDialog", () => {
|
||||||
|
const sourceRoom = "!111111111111111111:example.org";
|
||||||
|
const defaultMessage = TestUtils.mkMessage({
|
||||||
|
room: sourceRoom,
|
||||||
|
user: "@alice:example.org",
|
||||||
|
msg: "Hello world!",
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
const defaultRooms = ["a", "A", "b"].map(name => TestUtils.mkStubRoom(name, name));
|
||||||
|
|
||||||
|
const mountForwardDialog = async (message = defaultMessage, rooms = defaultRooms) => {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
client.getVisibleRooms = jest.fn().mockReturnValue(rooms);
|
||||||
|
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mount(
|
||||||
|
<ForwardDialog
|
||||||
|
matrixClient={client}
|
||||||
|
event={message}
|
||||||
|
permalinkCreator={new RoomPermalinkCreator(undefined, sourceRoom)}
|
||||||
|
onFinished={jest.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
// Wait one tick for our profile data to load so the state update happens within act
|
||||||
|
await new Promise(resolve => setImmediate(resolve));
|
||||||
|
});
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestUtils.stubClient();
|
||||||
|
DMRoomMap.makeShared();
|
||||||
|
MatrixClientPeg.get().getUserId = jest.fn().mockReturnValue("@bob:example.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows a preview with us as the sender", async () => {
|
||||||
|
const wrapper = await mountForwardDialog();
|
||||||
|
|
||||||
|
const previewBody = wrapper.find(".mx_EventTile_body");
|
||||||
|
expect(previewBody.text()).toBe("Hello world!");
|
||||||
|
|
||||||
|
// We would just test SenderProfile for the user ID, but it's stubbed
|
||||||
|
const previewAvatar = wrapper.find(".mx_EventTile_avatar .mx_BaseAvatar_image");
|
||||||
|
expect(previewAvatar.prop("title")).toBe("@bob:example.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters the rooms", async () => {
|
||||||
|
const wrapper = await mountForwardDialog();
|
||||||
|
|
||||||
|
expect(wrapper.find("Entry")).toHaveLength(3);
|
||||||
|
|
||||||
|
const searchInput = wrapper.find("SearchBox input");
|
||||||
|
searchInput.instance().value = "a";
|
||||||
|
searchInput.simulate("change");
|
||||||
|
|
||||||
|
expect(wrapper.find("Entry")).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tracks message sending progress across multiple rooms", async () => {
|
||||||
|
const wrapper = await mountForwardDialog();
|
||||||
|
|
||||||
|
// Make sendEvent require manual resolution so we can see the sending state
|
||||||
|
let finishSend;
|
||||||
|
let cancelSend;
|
||||||
|
MatrixClientPeg.get().sendEvent = jest.fn(() => new Promise((resolve, reject) => {
|
||||||
|
finishSend = resolve;
|
||||||
|
cancelSend = reject;
|
||||||
|
}));
|
||||||
|
|
||||||
|
const firstButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first();
|
||||||
|
expect(firstButton.render().is(".mx_ForwardList_canSend")).toBe(true);
|
||||||
|
|
||||||
|
act(() => { firstButton.simulate("click"); });
|
||||||
|
expect(firstButton.render().is(".mx_ForwardList_sending")).toBe(true);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
cancelSend();
|
||||||
|
// Wait one tick for the button to realize the send failed
|
||||||
|
await new Promise(resolve => setImmediate(resolve));
|
||||||
|
});
|
||||||
|
expect(firstButton.render().is(".mx_ForwardList_sendFailed")).toBe(true);
|
||||||
|
|
||||||
|
const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").at(1);
|
||||||
|
expect(secondButton.render().is(".mx_ForwardList_canSend")).toBe(true);
|
||||||
|
|
||||||
|
act(() => { secondButton.simulate("click"); });
|
||||||
|
expect(secondButton.render().is(".mx_ForwardList_sending")).toBe(true);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
finishSend();
|
||||||
|
// Wait one tick for the button to realize the send succeeded
|
||||||
|
await new Promise(resolve => setImmediate(resolve));
|
||||||
|
});
|
||||||
|
expect(secondButton.render().is(".mx_ForwardList_sent")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can render replies", async () => {
|
||||||
|
const replyMessage = TestUtils.mkEvent({
|
||||||
|
type: "m.room.message",
|
||||||
|
room: "!111111111111111111:example.org",
|
||||||
|
user: "@alice:example.org",
|
||||||
|
content: {
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "> <@bob:example.org> Hi Alice!\n\nHi Bob!",
|
||||||
|
"m.relates_to": {
|
||||||
|
"m.in_reply_to": {
|
||||||
|
event_id: "$2222222222222222222222222222222222222222222",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = await mountForwardDialog(replyMessage);
|
||||||
|
expect(wrapper.find("ReplyThread")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables buttons for rooms without send permissions", async () => {
|
||||||
|
const readOnlyRoom = TestUtils.mkStubRoom("a", "a");
|
||||||
|
readOnlyRoom.maySendMessage = jest.fn().mockReturnValue(false);
|
||||||
|
const rooms = [readOnlyRoom, TestUtils.mkStubRoom("b", "b")];
|
||||||
|
|
||||||
|
const wrapper = await mountForwardDialog(undefined, rooms);
|
||||||
|
|
||||||
|
const firstButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first();
|
||||||
|
expect(firstButton.prop("disabled")).toBe(true);
|
||||||
|
const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").last();
|
||||||
|
expect(secondButton.prop("disabled")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -219,7 +219,7 @@ export function mkMessage(opts) {
|
||||||
return mkEvent(opts);
|
return mkEvent(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mkStubRoom(roomId = null) {
|
export function mkStubRoom(roomId = null, name) {
|
||||||
const stubTimeline = { getEvents: () => [] };
|
const stubTimeline = { getEvents: () => [] };
|
||||||
return {
|
return {
|
||||||
roomId,
|
roomId,
|
||||||
|
@ -238,6 +238,7 @@ export function mkStubRoom(roomId = null) {
|
||||||
getPendingEvents: () => [],
|
getPendingEvents: () => [],
|
||||||
getLiveTimeline: () => stubTimeline,
|
getLiveTimeline: () => stubTimeline,
|
||||||
getUnfilteredTimelineSet: () => null,
|
getUnfilteredTimelineSet: () => null,
|
||||||
|
findEventById: () => null,
|
||||||
getAccountData: () => null,
|
getAccountData: () => null,
|
||||||
hasMembershipState: () => null,
|
hasMembershipState: () => null,
|
||||||
getVersion: () => '1',
|
getVersion: () => '1',
|
||||||
|
@ -255,13 +256,17 @@ export function mkStubRoom(roomId = null) {
|
||||||
tags: {},
|
tags: {},
|
||||||
setBlacklistUnverifiedDevices: jest.fn(),
|
setBlacklistUnverifiedDevices: jest.fn(),
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
|
off: jest.fn(),
|
||||||
removeListener: jest.fn(),
|
removeListener: jest.fn(),
|
||||||
getDMInviter: jest.fn(),
|
getDMInviter: jest.fn(),
|
||||||
|
name,
|
||||||
getAvatarUrl: () => 'mxc://avatar.url/room.png',
|
getAvatarUrl: () => 'mxc://avatar.url/room.png',
|
||||||
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
|
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
|
||||||
isSpaceRoom: jest.fn(() => false),
|
isSpaceRoom: jest.fn(() => false),
|
||||||
getUnreadNotificationCount: jest.fn(() => 0),
|
getUnreadNotificationCount: jest.fn(() => 0),
|
||||||
getEventReadUpTo: jest.fn(() => null),
|
getEventReadUpTo: jest.fn(() => null),
|
||||||
|
getCanonicalAlias: jest.fn(),
|
||||||
|
getAltAliases: jest.fn().mockReturnValue([]),
|
||||||
timeline: [],
|
timeline: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ function mockRoom(roomId, members, serverACL) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roomId,
|
roomId,
|
||||||
getCanonicalAlias: () => roomId,
|
getCanonicalAlias: () => null,
|
||||||
getJoinedMembers: () => members,
|
getJoinedMembers: () => members,
|
||||||
getMember: (userId) => members.find(m => m.userId === userId),
|
getMember: (userId) => members.find(m => m.userId === userId),
|
||||||
currentState: {
|
currentState: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue