Merge branch 'develop' into travis/notifications-2
This commit is contained in:
commit
942cadc8a3
170 changed files with 3577 additions and 1332 deletions
|
@ -55,7 +55,7 @@ interface IState {
|
|||
export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||
autocompleter: Autocompleter;
|
||||
queryRequested: string;
|
||||
debounceCompletionsRequest: NodeJS.Timeout;
|
||||
debounceCompletionsRequest: number;
|
||||
private containerRef = createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props) {
|
||||
|
|
|
@ -27,7 +27,6 @@ import { _t } from '../../../languageHandler';
|
|||
import { hasText } from "../../../TextForEvent";
|
||||
import * as sdk from "../../../index";
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { Layout } from "../../../settings/Layout";
|
||||
import { formatTime } from "../../../DateUtils";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
|
@ -54,6 +53,7 @@ import TooltipButton from '../elements/TooltipButton';
|
|||
import ReadReceiptMarker from "./ReadReceiptMarker";
|
||||
import MessageActionBar from "../messages/MessageActionBar";
|
||||
import ReactionsRow from '../messages/ReactionsRow';
|
||||
import { getEventDisplayInfo } from '../../../utils/EventUtils';
|
||||
|
||||
const eventTileTypes = {
|
||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||
|
@ -192,8 +192,7 @@ export interface IReadReceiptProps {
|
|||
export enum TileShape {
|
||||
Notif = "notif",
|
||||
FileGrid = "file_grid",
|
||||
Reply = "reply",
|
||||
ReplyPreview = "reply_preview",
|
||||
Pinned = "pinned",
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
|
@ -847,35 +846,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
render() {
|
||||
//console.info("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
|
||||
const msgtype = this.props.mxEvent.getContent().msgtype;
|
||||
const { tileHandler, isBubbleMessage, isInfoMessage } = getEventDisplayInfo(this.props.mxEvent);
|
||||
|
||||
const content = this.props.mxEvent.getContent();
|
||||
const msgtype = content.msgtype;
|
||||
const eventType = this.props.mxEvent.getType();
|
||||
|
||||
let tileHandler = getHandlerTile(this.props.mxEvent);
|
||||
|
||||
// Info messages are basically information about commands processed on a room
|
||||
let isBubbleMessage = eventType.startsWith("m.key.verification") ||
|
||||
(eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
|
||||
(eventType === EventType.RoomCreate) ||
|
||||
(eventType === EventType.RoomEncryption) ||
|
||||
(tileHandler === "messages.MJitsiWidgetEvent");
|
||||
let isInfoMessage = (
|
||||
!isBubbleMessage && eventType !== EventType.RoomMessage &&
|
||||
eventType !== EventType.Sticker && eventType !== EventType.RoomCreate
|
||||
);
|
||||
|
||||
// If we're showing hidden events in the timeline, we should use the
|
||||
// source tile when there's no regular tile for an event and also for
|
||||
// replace relations (which otherwise would display as a confusing
|
||||
// duplicate of the thing they are replacing).
|
||||
if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.props.mxEvent)) {
|
||||
tileHandler = "messages.ViewSourceEvent";
|
||||
isBubbleMessage = false;
|
||||
// Reuse info message avatar and sender profile styling
|
||||
isInfoMessage = true;
|
||||
}
|
||||
// This shouldn't happen: the caller should check we support this type
|
||||
// before trying to instantiate us
|
||||
if (!tileHandler) {
|
||||
|
@ -902,7 +875,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
mx_EventTile_12hr: this.props.isTwelveHour,
|
||||
// Note: we keep the `sending` state class for tests, not for our styles
|
||||
mx_EventTile_sending: !isEditing && isSending,
|
||||
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(),
|
||||
mx_EventTile_highlight: this.props.tileShape === TileShape.Notif ? false : this.shouldHighlight(),
|
||||
mx_EventTile_selected: this.props.isSelectedEvent,
|
||||
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
|
||||
mx_EventTile_last: this.props.last,
|
||||
|
@ -935,7 +908,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
let avatarSize;
|
||||
let needsSenderProfile;
|
||||
|
||||
if (this.props.tileShape === "notif") {
|
||||
if (this.props.tileShape === TileShape.Notif) {
|
||||
avatarSize = 24;
|
||||
needsSenderProfile = true;
|
||||
} else if (tileHandler === 'messages.RoomCreate' || isBubbleMessage) {
|
||||
|
@ -949,7 +922,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
} else if (this.props.layout == Layout.IRC) {
|
||||
avatarSize = 14;
|
||||
needsSenderProfile = true;
|
||||
} else if (this.props.continuation && this.props.tileShape !== "file_grid") {
|
||||
} else if (this.props.continuation && this.props.tileShape !== TileShape.FileGrid) {
|
||||
// no avatar or sender profile for continuation messages
|
||||
avatarSize = 0;
|
||||
needsSenderProfile = false;
|
||||
|
@ -979,7 +952,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
if (needsSenderProfile) {
|
||||
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') {
|
||||
if (!this.props.tileShape) {
|
||||
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
||||
mxEvent={this.props.mxEvent}
|
||||
enableFlair={this.props.enableFlair}
|
||||
|
@ -1065,7 +1038,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
switch (this.props.tileShape) {
|
||||
case 'notif': {
|
||||
case TileShape.Notif: {
|
||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
return React.createElement(this.props.as || "li", {
|
||||
"className": classes,
|
||||
|
@ -1093,11 +1066,12 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
tileShape={this.props.tileShape}
|
||||
/>
|
||||
</div>,
|
||||
]);
|
||||
}
|
||||
case 'file_grid': {
|
||||
case TileShape.FileGrid: {
|
||||
return React.createElement(this.props.as || "li", {
|
||||
"className": classes,
|
||||
"aria-live": ariaLive,
|
||||
|
@ -1128,44 +1102,6 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
]);
|
||||
}
|
||||
|
||||
case 'reply':
|
||||
case 'reply_preview': {
|
||||
let thread;
|
||||
if (this.props.tileShape === 'reply_preview') {
|
||||
thread = ReplyThread.makeThread(
|
||||
this.props.mxEvent,
|
||||
this.props.onHeightChanged,
|
||||
this.props.permalinkCreator,
|
||||
this.replyThread,
|
||||
null,
|
||||
this.props.alwaysShowTimestamps || this.state.hover,
|
||||
);
|
||||
}
|
||||
return React.createElement(this.props.as || "li", {
|
||||
"className": classes,
|
||||
"aria-live": ariaLive,
|
||||
"aria-atomic": true,
|
||||
"data-scroll-tokens": scrollToken,
|
||||
}, [
|
||||
ircTimestamp,
|
||||
avatar,
|
||||
sender,
|
||||
ircPadlock,
|
||||
<div className="mx_EventTile_reply" key="mx_EventTile_reply">
|
||||
{ groupTimestamp }
|
||||
{ groupPadlock }
|
||||
{ thread }
|
||||
<EventTileType ref={this.tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
showUrlPreview={false}
|
||||
/>
|
||||
</div>,
|
||||
]);
|
||||
}
|
||||
default: {
|
||||
const thread = ReplyThread.makeThread(
|
||||
this.props.mxEvent,
|
||||
|
@ -1187,10 +1123,10 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
"data-scroll-tokens": scrollToken,
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
}, [
|
||||
ircTimestamp,
|
||||
sender,
|
||||
ircPadlock,
|
||||
}, <>
|
||||
{ ircTimestamp }
|
||||
{ sender }
|
||||
{ ircPadlock }
|
||||
<div className="mx_EventTile_line" key="mx_EventTile_line">
|
||||
{ groupTimestamp }
|
||||
{ groupPadlock }
|
||||
|
@ -1208,11 +1144,10 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
{ keyRequestInfo }
|
||||
{ reactionsRow }
|
||||
{ actionBar }
|
||||
</div>,
|
||||
msgOption,
|
||||
avatar,
|
||||
|
||||
])
|
||||
</div>
|
||||
{ msgOption }
|
||||
{ avatar }
|
||||
</>)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,10 +40,12 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
|
|||
|
||||
const ts = mxEvent.getTs();
|
||||
const previews = useAsyncMemo<[string, IPreviewUrlResponse][]>(async () => {
|
||||
return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(link => {
|
||||
return cli.getUrlPreview(link, ts).then(preview => [link, preview], error => {
|
||||
return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(async link => {
|
||||
try {
|
||||
return [link, await cli.getUrlPreview(link, ts)];
|
||||
} catch (error) {
|
||||
console.error("Failed to get URL preview: " + error);
|
||||
});
|
||||
}
|
||||
})).then(a => a.filter(Boolean)) as Promise<[string, IPreviewUrlResponse][]>;
|
||||
}, [links, ts], []);
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import EntityTile from "./EntityTile";
|
|||
import MemberTile from "./MemberTile";
|
||||
import BaseAvatar from '../avatars/BaseAvatar';
|
||||
import { throttle } from 'lodash';
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
|
||||
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||
const INITIAL_LOAD_NUM_INVITED = 5;
|
||||
|
@ -509,7 +510,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
|||
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
|
||||
if (chat && chat.roomId === this.props.roomId) {
|
||||
inviteButtonText = _t("Invite to this community");
|
||||
} else if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) {
|
||||
} else if (SpaceStore.spacesEnabled && room.isSpaceRoom()) {
|
||||
inviteButtonText = _t("Invite to this space");
|
||||
}
|
||||
|
||||
|
@ -549,7 +550,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
|||
let previousPhase = RightPanelPhases.RoomSummary;
|
||||
// We have no previousPhase for when viewing a MemberList from a Space
|
||||
let scopeHeader;
|
||||
if (SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom()) {
|
||||
if (SpaceStore.spacesEnabled && room?.isSpaceRoom()) {
|
||||
previousPhase = undefined;
|
||||
scopeHeader = <div className="mx_RightPanel_scopeHeader">
|
||||
<RoomAvatar room={room} height={32} width={32} />
|
||||
|
|
|
@ -29,6 +29,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
|
|||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { TileShape } from "./EventTile";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -87,6 +88,7 @@ export default class PinnedEventTile extends React.Component<IProps> {
|
|||
className="mx_PinnedEventTile_body"
|
||||
maxImageHeight={150}
|
||||
onHeightChanged={() => {}} // we need to give this, apparently
|
||||
tileShape={TileShape.Pinned}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 New Vector Ltd
|
||||
Copyright 2017 - 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.
|
||||
|
@ -16,14 +16,12 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import PropTypes from "prop-types";
|
||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ReplyTile from './ReplyTile';
|
||||
|
||||
function cancelQuoting() {
|
||||
dis.dispatch({
|
||||
|
@ -71,8 +69,6 @@ export default class ReplyPreview extends React.Component {
|
|||
render() {
|
||||
if (!this.state.event) return null;
|
||||
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
|
||||
return <div className="mx_ReplyPreview">
|
||||
<div className="mx_ReplyPreview_section">
|
||||
<div className="mx_ReplyPreview_header mx_ReplyPreview_title">
|
||||
|
@ -88,15 +84,12 @@ export default class ReplyPreview extends React.Component {
|
|||
/>
|
||||
</div>
|
||||
<div className="mx_ReplyPreview_clear" />
|
||||
<EventTile
|
||||
alwaysShowTimestamps={true}
|
||||
tileShape="reply_preview"
|
||||
mxEvent={this.state.event}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
|
||||
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||
as="div"
|
||||
/>
|
||||
<div className="mx_ReplyPreview_tile">
|
||||
<ReplyTile
|
||||
mxEvent={this.state.event}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
|
155
src/components/views/rooms/ReplyTile.tsx
Normal file
155
src/components/views/rooms/ReplyTile.tsx
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
Copyright 2020-2021 Tulir Asokan <tulir@maunium.net>
|
||||
|
||||
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 classNames from 'classnames';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||
import SenderProfile from "../messages/SenderProfile";
|
||||
import MImageReplyBody from "../messages/MImageReplyBody";
|
||||
import * as sdk from '../../../index';
|
||||
import { EventType, MsgType } from 'matrix-js-sdk/src/@types/event';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
import { getEventDisplayInfo } from '../../../utils/EventUtils';
|
||||
import MFileBody from "../messages/MFileBody";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
highlights?: string[];
|
||||
highlightLink?: string;
|
||||
onHeightChanged?(): void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.rooms.ReplyTile")
|
||||
export default class ReplyTile extends React.PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
onHeightChanged: () => {},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.mxEvent.on("Event.decrypted", this.onDecrypted);
|
||||
this.props.mxEvent.on("Event.beforeRedaction", this.onEventRequiresUpdate);
|
||||
this.props.mxEvent.on("Event.replaced", this.onEventRequiresUpdate);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.mxEvent.removeListener("Event.decrypted", this.onDecrypted);
|
||||
this.props.mxEvent.removeListener("Event.beforeRedaction", this.onEventRequiresUpdate);
|
||||
this.props.mxEvent.removeListener("Event.replaced", this.onEventRequiresUpdate);
|
||||
}
|
||||
|
||||
private onDecrypted = (): void => {
|
||||
this.forceUpdate();
|
||||
if (this.props.onHeightChanged) {
|
||||
this.props.onHeightChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private onEventRequiresUpdate = (): void => {
|
||||
// Force update when necessary - redactions and edits
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
private onClick = (e: React.MouseEvent): void => {
|
||||
// This allows the permalink to be opened in a new tab/window or copied as
|
||||
// matrix.to, but also for it to enable routing within Riot when clicked.
|
||||
e.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
event_id: this.props.mxEvent.getId(),
|
||||
highlighted: true,
|
||||
room_id: this.props.mxEvent.getRoomId(),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const mxEvent = this.props.mxEvent;
|
||||
const msgType = mxEvent.getContent().msgtype;
|
||||
const evType = mxEvent.getType() as EventType;
|
||||
|
||||
const { tileHandler, isInfoMessage } = getEventDisplayInfo(this.props.mxEvent);
|
||||
// This shouldn't happen: the caller should check we support this type
|
||||
// before trying to instantiate us
|
||||
if (!tileHandler) {
|
||||
const { mxEvent } = this.props;
|
||||
console.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`);
|
||||
return <div className="mx_ReplyTile mx_ReplyTile_info mx_MNoticeBody">
|
||||
{ _t('This event could not be displayed') }
|
||||
</div>;
|
||||
}
|
||||
|
||||
const EventTileType = sdk.getComponent(tileHandler);
|
||||
|
||||
const classes = classNames("mx_ReplyTile", {
|
||||
mx_ReplyTile_info: isInfoMessage && !this.props.mxEvent.isRedacted(),
|
||||
mx_ReplyTile_audio: msgType === MsgType.Audio,
|
||||
mx_ReplyTile_video: msgType === MsgType.Video,
|
||||
});
|
||||
|
||||
let permalink = "#";
|
||||
if (this.props.permalinkCreator) {
|
||||
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||
}
|
||||
|
||||
let sender;
|
||||
const needsSenderProfile = (
|
||||
!isInfoMessage &&
|
||||
msgType !== MsgType.Image &&
|
||||
tileHandler !== EventType.RoomCreate &&
|
||||
evType !== EventType.Sticker
|
||||
);
|
||||
|
||||
if (needsSenderProfile) {
|
||||
sender = <SenderProfile
|
||||
mxEvent={this.props.mxEvent}
|
||||
enableFlair={false}
|
||||
/>;
|
||||
}
|
||||
|
||||
const msgtypeOverrides = {
|
||||
[MsgType.Image]: MImageReplyBody,
|
||||
// Override audio and video body with file body. We also hide the download/decrypt button using CSS
|
||||
[MsgType.Audio]: MFileBody,
|
||||
[MsgType.Video]: MFileBody,
|
||||
};
|
||||
const evOverrides = {
|
||||
// Use MImageReplyBody so that the sticker isn't taking up a lot of space
|
||||
[EventType.Sticker]: MImageReplyBody,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<a href={permalink} onClick={this.onClick}>
|
||||
{ sender }
|
||||
<EventTileType
|
||||
ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
showUrlPreview={false}
|
||||
overrideBodyTypes={msgtypeOverrides}
|
||||
overrideEventTypes={evOverrides}
|
||||
replacingEventId={this.props.mxEvent.replacingEventId()}
|
||||
maxImageHeight={96} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -417,7 +417,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
private renderCommunityInvites(): ReactComponentElement<typeof ExtraTile>[] {
|
||||
if (SettingsStore.getValue("feature_spaces")) return [];
|
||||
if (SpaceStore.spacesEnabled) return [];
|
||||
// TODO: Put community invites in a more sensible place (not in the room list)
|
||||
// See https://github.com/vector-im/element-web/issues/14456
|
||||
return MatrixClientPeg.get().getGroups().filter(g => {
|
||||
|
|
|
@ -408,10 +408,10 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
this.setState({ addRoomContextMenuPosition: null });
|
||||
};
|
||||
|
||||
private onUnreadFirstChanged = async () => {
|
||||
private onUnreadFirstChanged = () => {
|
||||
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
||||
const newAlgorithm = isUnreadFirst ? ListAlgorithm.Natural : ListAlgorithm.Importance;
|
||||
await RoomListStore.instance.setListOrder(this.props.tagId, newAlgorithm);
|
||||
RoomListStore.instance.setListOrder(this.props.tagId, newAlgorithm);
|
||||
this.forceUpdate(); // because if the sublist doesn't have any changes then we will miss the list order change
|
||||
};
|
||||
|
||||
|
|
|
@ -358,6 +358,17 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
};
|
||||
|
||||
private onCopyRoomClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
dis.dispatch({
|
||||
action: 'copy_room',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
};
|
||||
|
||||
private onInviteClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
@ -517,6 +528,11 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
iconClassName="mx_RoomTile_iconInvite"
|
||||
/>
|
||||
) : null}
|
||||
<IconizedContextMenuOption
|
||||
onClick={this.onCopyRoomClick}
|
||||
label={_t("Copy Link")}
|
||||
iconClassName="mx_RoomTile_iconCopyLink"
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
onClick={this.onOpenRoomSettings}
|
||||
label={_t("Settings")}
|
||||
|
|
|
@ -224,7 +224,7 @@ export default class Stickerpicker extends React.PureComponent {
|
|||
}
|
||||
|
||||
_getStickerpickerContent() {
|
||||
// Handle Integration Manager errors
|
||||
// Handle integration manager errors
|
||||
if (this.state._imError) {
|
||||
return this._errorStickerpickerContent();
|
||||
}
|
||||
|
|
|
@ -25,9 +25,9 @@ import { isValid3pidInvite } from "../../../RoomInvite";
|
|||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import RoomName from "../elements/RoomName";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import ErrorDialog from '../dialogs/ErrorDialog';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
|
||||
interface IProps {
|
||||
event: MatrixEvent;
|
||||
|
@ -134,7 +134,7 @@ export default class ThirdPartyMemberInfo extends React.Component<IProps, IState
|
|||
}
|
||||
|
||||
let scopeHeader;
|
||||
if (SettingsStore.getValue("feature_spaces") && this.room.isSpaceRoom()) {
|
||||
if (SpaceStore.spacesEnabled && this.room.isSpaceRoom()) {
|
||||
scopeHeader = <div className="mx_RightPanel_scopeHeader">
|
||||
<RoomAvatar room={this.room} height={32} width={32} />
|
||||
<RoomName room={this.room} />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue