{ _t("Drop file here to upload") }
@@ -170,7 +180,6 @@ export default class AuxPanel extends React.Component {
userId={this.props.userId}
maxHeight={this.props.maxHeight}
showApps={this.props.showApps}
- hide={this.props.hideAppsDrawer}
resizeNotifier={this.props.resizeNotifier}
/>;
}
@@ -211,7 +220,7 @@ export default class AuxPanel extends React.Component {
─
+ > ─ ,
);
});
@@ -229,7 +238,7 @@ export default class AuxPanel extends React.Component {
"mx_RoomView_auxPanel": true,
"mx_RoomView_auxPanel_fullHeight": this.props.fullHeight,
});
- const style = {};
+ const style: React.CSSProperties = {};
if (!this.props.fullHeight) {
style.maxHeight = this.props.maxHeight;
}
diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js
index 78c7de887d..6fc1d3e1ad 100644
--- a/src/components/views/rooms/EditMessageComposer.js
+++ b/src/components/views/rooms/EditMessageComposer.js
@@ -32,6 +32,7 @@ import BasicMessageComposer from "./BasicMessageComposer";
import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {Action} from "../../../dispatcher/actions";
+import CountlyAnalytics from "../../../CountlyAnalytics";
function _isReply(mxEvent) {
const relatesTo = mxEvent.getContent()["m.relates_to"];
@@ -182,6 +183,7 @@ export default class EditMessageComposer extends React.Component {
}
_sendEdit = () => {
+ const startTime = CountlyAnalytics.getTimestamp();
const editedEvent = this.props.editState.getEvent();
const editContent = createEditContent(this.model, editedEvent);
const newContent = editContent["m.new_content"];
@@ -190,8 +192,9 @@ export default class EditMessageComposer extends React.Component {
if (this._isContentModified(newContent)) {
const roomId = editedEvent.getRoomId();
this._cancelPreviousPendingEdit();
- this.context.sendMessage(roomId, editContent);
+ const prom = this.context.sendMessage(roomId, editContent);
dis.dispatch({action: "message_sent"});
+ CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent);
}
// close the event editing and focus composer
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js
index c2b1af2ddc..c358ef610d 100644
--- a/src/components/views/rooms/EventTile.js
+++ b/src/components/views/rooms/EventTile.js
@@ -21,6 +21,7 @@ import ReplyThread from "../elements/ReplyThread";
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from "classnames";
+import {EventType} from "matrix-js-sdk/src/@types/event";
import { _t, _td } from '../../../languageHandler';
import * as TextForEvent from "../../../TextForEvent";
import * as sdk from "../../../index";
@@ -46,6 +47,7 @@ const eventTileTypes = {
'm.call.invite': 'messages.TextualEvent',
'm.call.answer': 'messages.TextualEvent',
'm.call.hangup': 'messages.TextualEvent',
+ 'm.call.reject': 'messages.TextualEvent',
};
const stateEventTileTypes = {
@@ -645,20 +647,20 @@ export default class EventTile extends React.Component {
// Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") ||
- (eventType === "m.room.message" && msgtype && msgtype.startsWith("m.key.verification")) ||
- (eventType === "m.room.encryption") ||
+ (eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
+ (eventType === EventType.RoomCreate) ||
+ (eventType === EventType.RoomEncryption) ||
(tileHandler === "messages.MJitsiWidgetEvent");
let isInfoMessage = (
- !isBubbleMessage && eventType !== 'm.room.message' &&
- eventType !== 'm.sticker' && eventType !== 'm.room.create'
+ !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).
- const useSource = !tileHandler || this.props.mxEvent.isRelation("m.replace");
- if (useSource && SettingsStore.getValue("showHiddenEventsInTimeline")) {
+ if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.props.mxEvent)) {
tileHandler = "messages.ViewSourceEvent";
// Reuse info message avatar and sender profile styling
isInfoMessage = true;
diff --git a/src/components/views/rooms/ForwardMessage.js b/src/components/views/rooms/ForwardMessage.js
index 8daea8dc17..b85dd2c8df 100644
--- a/src/components/views/rooms/ForwardMessage.js
+++ b/src/components/views/rooms/ForwardMessage.js
@@ -18,7 +18,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
-import dis from '../../../dispatcher/dispatcher';
import {Key} from '../../../Keyboard';
@@ -28,19 +27,10 @@ export default class ForwardMessage extends React.Component {
};
componentDidMount() {
- dis.dispatch({
- action: 'panel_disable',
- middleDisabled: true,
- });
-
document.addEventListener('keydown', this._onKeyDown);
}
componentWillUnmount() {
- dis.dispatch({
- action: 'panel_disable',
- middleDisabled: false,
- });
document.removeEventListener('keydown', this._onKeyDown);
}
diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js
index a3d59e5137..2a053bf467 100644
--- a/src/components/views/rooms/LinkPreviewWidget.js
+++ b/src/components/views/rooms/LinkPreviewWidget.js
@@ -114,7 +114,10 @@ export default class LinkPreviewWidget extends React.Component {
let thumbHeight = imageMaxHeight;
if (p["og:image:width"] && p["og:image:height"]) {
- thumbHeight = ImageUtils.thumbHeight(p["og:image:width"], p["og:image:height"], imageMaxWidth, imageMaxHeight);
+ thumbHeight = ImageUtils.thumbHeight(
+ p["og:image:width"], p["og:image:height"],
+ imageMaxWidth, imageMaxHeight,
+ );
}
let img;
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js
index cac746f203..4ddff8f4b0 100644
--- a/src/components/views/rooms/MessageComposer.js
+++ b/src/components/views/rooms/MessageComposer.js
@@ -23,7 +23,6 @@ import CallHandler from '../../../CallHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
-import RoomViewStore from '../../../stores/RoomViewStore';
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
@@ -38,6 +37,7 @@ import WidgetUtils from "../../../utils/WidgetUtils";
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
import ActiveWidgetStore from "../../../stores/ActiveWidgetStore";
import { PlaceCallType } from "../../../CallHandler";
+import { CallState } from 'matrix-js-sdk/src/webrtc/call';
function ComposerAvatar(props) {
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
@@ -104,8 +104,11 @@ function HangupButton(props) {
if (!call) {
return;
}
+
+ const action = call.state === CallState.Ringing ? 'reject' : 'hangup';
+
dis.dispatch({
- action: 'hangup',
+ action,
// hangup the call for this room, which may not be the room in props
// (e.g. conferences which will hangup the 1:1 room instead)
room_id: call.roomId,
@@ -250,7 +253,6 @@ export default class MessageComposer extends React.Component {
super(props);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
- this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
this._onTombstoneClick = this._onTombstoneClick.bind(this);
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
WidgetStore.instance.on(UPDATE_EVENT, this._onWidgetUpdate);
@@ -258,7 +260,6 @@ export default class MessageComposer extends React.Component {
this._dispatcherRef = null;
this.state = {
- replyToEvent: RoomViewStore.getQuotingEvent(),
tombstone: this._getRoomTombstone(),
canSendMessages: this.props.room.maySendMessage(),
showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"),
@@ -290,7 +291,6 @@ export default class MessageComposer extends React.Component {
componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
- this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._waitForOwnMember();
}
@@ -314,9 +314,6 @@ export default class MessageComposer extends React.Component {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
}
- if (this._roomStoreToken) {
- this._roomStoreToken.remove();
- }
WidgetStore.instance.removeListener(UPDATE_EVENT, this._onWidgetUpdate);
ActiveWidgetStore.removeListener('update', this._onActiveWidgetUpdate);
dis.unregister(this.dispatcherRef);
@@ -337,12 +334,6 @@ export default class MessageComposer extends React.Component {
return this.props.room.currentState.getStateEvents('m.room.tombstone', '');
}
- _onRoomViewStoreUpdate() {
- const replyToEvent = RoomViewStore.getQuotingEvent();
- if (this.state.replyToEvent === replyToEvent) return;
- this.setState({ replyToEvent });
- }
-
onInputStateChanged(inputState) {
// Merge the new input state with old to support partial updates
inputState = Object.assign({}, this.state.inputState, inputState);
@@ -367,6 +358,7 @@ export default class MessageComposer extends React.Component {
event_id: createEventId,
room_id: replacementRoomId,
auto_join: true,
+ _type: "tombstone", // instrumentation
// Try to join via the server that sent the event. This converts @something:example.org
// into a server domain by splitting on colons and ignoring the first entry ("@something").
@@ -379,7 +371,7 @@ export default class MessageComposer extends React.Component {
}
renderPlaceholderText() {
- if (this.state.replyToEvent) {
+ if (this.props.replyToEvent) {
if (this.props.e2eStatus) {
return _t('Send an encrypted reply…');
} else {
@@ -425,7 +417,7 @@ export default class MessageComposer extends React.Component {
placeholder={this.renderPlaceholderText()}
resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.props.permalinkCreator}
- replyToEvent={this.state.replyToEvent}
+ replyToEvent={this.props.replyToEvent}
/>,
,
,
diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx
new file mode 100644
index 0000000000..be4ecaffb3
--- /dev/null
+++ b/src/components/views/rooms/NewRoomIntro.tsx
@@ -0,0 +1,135 @@
+/*
+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, {useContext} from "react";
+import {EventType} from "matrix-js-sdk/src/@types/event";
+
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import RoomContext from "../../../contexts/RoomContext";
+import DMRoomMap from "../../../utils/DMRoomMap";
+import {_t} from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import MiniAvatarUploader, {AVATAR_SIZE} from "../elements/MiniAvatarUploader";
+import RoomAvatar from "../avatars/RoomAvatar";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload";
+import {Action} from "../../../dispatcher/actions";
+import dis from "../../../dispatcher/dispatcher";
+
+const NewRoomIntro = () => {
+ const cli = useContext(MatrixClientContext);
+ const {room, roomId} = useContext(RoomContext);
+
+ const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
+ let body;
+ if (dmPartner) {
+ let caption;
+ if ((room.getJoinedMemberCount() + room.getInvitedMemberCount()) === 2) {
+ caption = _t("Only the two of you are in this conversation, unless either of you invites anyone to join.");
+ }
+
+ const member = room?.getMember(dmPartner);
+ const displayName = member?.rawDisplayName || dmPartner;
+ body =
+ {
+ defaultDispatcher.dispatch({
+ action: Action.ViewUser,
+ // XXX: We should be using a real member object and not assuming what the receiver wants.
+ member: member || {userId: dmPartner},
+ });
+ }} />
+
+ { room.name }
+
+ {_t("This is the beginning of your direct message history with .", {}, {
+ displayName: () => { displayName },
+ })}
+ { caption && { caption }
}
+ ;
+ } else {
+ const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic;
+ const canAddTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId());
+
+ const onTopicClick = () => {
+ dis.dispatch({
+ action: "open_room_settings",
+ room_id: roomId,
+ }, true);
+ // focus the topic field to help the user find it as it'll gain an outline
+ setImmediate(() => {
+ window.document.getElementById("profileTopic").focus();
+ });
+ };
+
+ let topicText;
+ if (canAddTopic && topic) {
+ topicText = _t("Topic: %(topic)s (
edit)", { topic }, {
+ a: sub =>
{ sub },
+ });
+ } else if (topic) {
+ topicText = _t("Topic: %(topic)s ", { topic });
+ } else if (canAddTopic) {
+ topicText = _t("
Add a topic to help people know what it is about.", {}, {
+ a: sub =>
{ sub },
+ });
+ }
+
+ const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
+ const creatorName = room?.getMember(creator)?.rawDisplayName || creator;
+
+ let createdText;
+ if (creator === cli.getUserId()) {
+ createdText = _t("You created this room.");
+ } else {
+ createdText = _t("%(displayName)s created this room.", {
+ displayName: creatorName,
+ });
+ }
+
+ const onInviteClick = () => {
+ dis.dispatch({ action: "view_invite", roomId });
+ };
+
+ const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url;
+ body =
+ cli.sendStateEvent(roomId, EventType.RoomAvatar, { url }, '')}
+ >
+
+
+
+ { room.name }
+
+ {createdText} {_t("This is the start of .", {}, {
+ roomName: () => { room.name },
+ })}
+ {topicText}
+
+
+ {_t("Invite to this room")}
+
+
+ ;
+ }
+
+ return
+ { body }
+
;
+};
+
+export default NewRoomIntro;
diff --git a/src/components/views/rooms/RoomBreadcrumbs.tsx b/src/components/views/rooms/RoomBreadcrumbs.tsx
index 92e911067c..7725ce456e 100644
--- a/src/components/views/rooms/RoomBreadcrumbs.tsx
+++ b/src/components/views/rooms/RoomBreadcrumbs.tsx
@@ -76,7 +76,7 @@ export default class RoomBreadcrumbs extends React.PureComponent
};
private viewRoom = (room: Room, index: number) => {
- Analytics.trackEvent("Breadcrumbs", "click_node", index);
+ Analytics.trackEvent("Breadcrumbs", "click_node", String(index));
defaultDispatcher.dispatch({action: "view_room", room_id: room.roomId});
};
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js
index 1a116838ac..8eb8276630 100644
--- a/src/components/views/rooms/RoomHeader.js
+++ b/src/components/views/rooms/RoomHeader.js
@@ -42,6 +42,8 @@ export default class RoomHeader extends React.Component {
onLeaveClick: PropTypes.func,
onCancelClick: PropTypes.func,
e2eStatus: PropTypes.string,
+ onAppsClick: PropTypes.func,
+ appsShown: PropTypes.bool,
};
static defaultProps = {
@@ -230,6 +232,17 @@ export default class RoomHeader extends React.Component {
title={_t("Forget room")} />;
}
+ let appsButton;
+ if (this.props.onAppsClick) {
+ appsButton =
+ ;
+ }
+
let searchButton;
if (this.props.onSearchClick && this.props.inRoom) {
searchButton =
@@ -243,6 +256,7 @@ export default class RoomHeader extends React.Component {
{ pinnedEventsButton }
{ forgetButton }
+ { appsButton }
{ searchButton }
;
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index 92bbdfeacb..6e677f2b01 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -53,12 +53,12 @@ interface IProps {
onBlur: (ev: React.FocusEvent) => void;
onResize: () => void;
resizeNotifier: ResizeNotifier;
- collapsed: boolean;
isMinimized: boolean;
}
interface IState {
sublists: ITagMap;
+ isNameFiltering: boolean;
}
const TAG_ORDER: TagID[] = [
@@ -184,6 +184,7 @@ export default class RoomList extends React.PureComponent {
this.state = {
sublists: {},
+ isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(),
};
this.dispatcherRef = defaultDispatcher.register(this.onAction);
@@ -254,7 +255,8 @@ export default class RoomList extends React.PureComponent {
return CustomRoomTagStore.getTags()[t];
});
- let doUpdate = arrayHasDiff(previousListIds, newListIds);
+ const isNameFiltering = !!RoomListStore.instance.getFirstNameFilterCondition();
+ let doUpdate = this.state.isNameFiltering !== isNameFiltering || arrayHasDiff(previousListIds, newListIds);
if (!doUpdate) {
// so we didn't have the visible sublists change, but did the contents of those
// sublists change significantly enough to break the sticky headers? Probably, so
@@ -276,14 +278,20 @@ export default class RoomList extends React.PureComponent {
const newSublists = objectWithOnly(newLists, newListIds);
const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v));
- this.setState({sublists}, () => {
+ this.setState({sublists, isNameFiltering}, () => {
this.props.onResize();
});
}
};
+ private onStartChat = () => {
+ const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
+ dis.dispatch({ action: "view_create_chat", initialText });
+ };
+
private onExplore = () => {
- dis.fire(Action.ViewRoomDirectory);
+ const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
+ dis.dispatch({ action: Action.ViewRoomDirectory, initialText });
};
private renderCommunityInvites(): TemporaryTile[] {
@@ -333,6 +341,10 @@ export default class RoomList extends React.PureComponent {
return p;
}, [] as TagID[]);
+ // show a skeleton UI if the user is in no rooms and they are not filtering
+ const showSkeleton = !this.state.isNameFiltering &&
+ Object.values(RoomListStore.instance.unfilteredLists).every(list => !list?.length);
+
for (const orderedTagId of tagOrder) {
const orderedRooms = this.state.sublists[orderedTagId] || [];
const extraTiles = orderedTagId === DefaultTagID.Invite ? this.renderCommunityInvites() : null;
@@ -357,6 +369,7 @@ export default class RoomList extends React.PureComponent {
addRoomContextMenu={aesthetics.addRoomContextMenu}
isMinimized={this.props.isMinimized}
onResize={this.props.onResize}
+ showSkeleton={showSkeleton}
extraBadTilesThatShouldntExist={extraTiles}
/>);
}
@@ -366,13 +379,50 @@ export default class RoomList extends React.PureComponent {
public render() {
let explorePrompt: JSX.Element;
- if (RoomListStore.instance.getFirstNameFilterCondition()) {
- explorePrompt =
-
{_t("Can't see what you’re looking for?")}
-
- {_t("Explore all public rooms")}
-
-
;
+ if (!this.props.isMinimized) {
+ if (this.state.isNameFiltering) {
+ explorePrompt =
+
{_t("Can't see what you’re looking for?")}
+
+ {_t("Start a new chat")}
+
+
+ {_t("Explore all public rooms")}
+
+
;
+ } else if (Object.values(this.state.sublists).some(list => list.length > 0)) {
+ const unfilteredLists = RoomListStore.instance.unfilteredLists
+ const unfilteredRooms = unfilteredLists[DefaultTagID.Untagged] || [];
+ const unfilteredHistorical = unfilteredLists[DefaultTagID.Archived] || [];
+ // show a prompt to join/create rooms if the user is in 0 rooms and no historical
+ if (unfilteredRooms.length < 1 && unfilteredHistorical < 1) {
+ explorePrompt =
+
{_t("Use the + to make a new room or explore existing ones below")}
+
+ {_t("Start a new chat")}
+
+
+ {_t("Explore all public rooms")}
+
+
;
+ }
+ }
}
const sublists = this.renderSublists();
diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index f42e18372a..dc68068157 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -284,7 +284,7 @@ export default class RoomPreviewBar extends React.Component {
room_name: this.props.oobData ? this.props.oobData.room_name : null,
room_avatar_url: this.props.oobData ? this.props.oobData.avatarUrl : null,
inviter_name: this.props.oobData ? this.props.oobData.inviterName : null,
- }
+ },
};
}
@@ -337,7 +337,7 @@ export default class RoomPreviewBar extends React.Component {
if (this.props.previewLoading) {
footer = (
-
+
{_t("Loading room preview")}
);
diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx
index 4056f2fbd4..b5ae3285b9 100644
--- a/src/components/views/rooms/RoomSublist.tsx
+++ b/src/components/views/rooms/RoomSublist.tsx
@@ -71,6 +71,7 @@ interface IProps {
isMinimized: boolean;
tagId: TagID;
onResize: () => void;
+ showSkeleton?: boolean;
// TODO: Don't use this. It's for community invites, and community invites shouldn't be here.
// You should feel bad if you use this.
@@ -399,6 +400,7 @@ export default class RoomSublist extends React.Component {
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);
+ this.forceUpdate(); // because if the sublist doesn't have any changes then we will miss the list order change
};
private onTagSortChanged = async (sort: SortAlgorithm) => {
@@ -876,6 +878,8 @@ export default class RoomSublist extends React.Component {
);
+ } else if (this.props.showSkeleton && this.state.isExpanded) {
+ content = ;
}
return (
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index 4828277d8a..78b1dd85db 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -42,6 +42,7 @@ import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RateLimitedFunc from '../../../ratelimitedfunc';
import {Action} from "../../../dispatcher/actions";
+import CountlyAnalytics from "../../../CountlyAnalytics";
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
@@ -304,9 +305,13 @@ export default class SendMessageComposer extends React.Component {
const replyToEvent = this.props.replyToEvent;
if (shouldSend) {
+ const startTime = CountlyAnalytics.getTimestamp();
const {roomId} = this.props.room;
const content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent);
- this.context.sendMessage(roomId, content);
+ // don't bother sending an empty message
+ if (!content.body.trim()) return;
+
+ const prom = this.context.sendMessage(roomId, content);
if (replyToEvent) {
// Clear reply_to_event as we put the message into the queue
// if the send fails, retry will handle resending.
@@ -316,6 +321,7 @@ export default class SendMessageComposer extends React.Component {
});
}
dis.dispatch({action: "message_sent"});
+ CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, false, !!replyToEvent, content);
}
this.sendHistoryManager.save(this.model, replyToEvent);
diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js
index 2faa0fea27..ae7ed48898 100644
--- a/src/components/views/rooms/Stickerpicker.js
+++ b/src/components/views/rooms/Stickerpicker.js
@@ -272,13 +272,10 @@ export default class Stickerpicker extends React.Component {
userId={MatrixClientPeg.get().credentials.userId}
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
waitForIframeLoad={true}
- show={true}
showMenubar={true}
onEditClick={this._launchManageIntegrations}
onDeleteClick={this._removeStickerpickerWidgets}
showTitle={false}
- showMinimise={true}
- showDelete={false}
showCancel={false}
showPopout={false}
onMinimiseClick={this._onHideStickersClick}
diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js
index a6fc60ceb9..7ab2936584 100644
--- a/src/components/views/settings/ChangeAvatar.js
+++ b/src/components/views/settings/ChangeAvatar.js
@@ -19,6 +19,7 @@ import PropTypes from 'prop-types';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
+import Spinner from '../elements/Spinner';
export default class ChangeAvatar extends React.Component {
static propTypes = {
@@ -58,7 +59,7 @@ export default class ChangeAvatar extends React.Component {
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
- UNSAFE_componentWillReceiveProps(newProps) {
+ UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
if (this.avatarSet) {
// don't clobber what the user has just set
return;
@@ -143,7 +144,9 @@ export default class ChangeAvatar extends React.Component {
// time to propagate through to the RoomAvatar.
if (this.props.room && !this.avatarSet) {
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
- avatarImg = ;
+ avatarImg = ;
} else {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
@@ -174,9 +177,8 @@ export default class ChangeAvatar extends React.Component {
);
case ChangeAvatar.Phases.Uploading:
- var Loader = sdk.getComponent("elements.Spinner");
return (
-
);
}
}
diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.js
index fe4a4abfdc..dc3ce9e03d 100644
--- a/src/components/views/settings/DevicesPanel.js
+++ b/src/components/views/settings/DevicesPanel.js
@@ -74,7 +74,7 @@ export default class DevicesPanel extends React.Component {
}
- /**
+ /*
* compare two devices, sorting from most-recently-seen to least-recently-seen
* (and then, for stability, by device id)
*/
diff --git a/src/components/views/settings/EventIndexPanel.js b/src/components/views/settings/EventIndexPanel.js
index 8598a2a966..ec6ccacc9a 100644
--- a/src/components/views/settings/EventIndexPanel.js
+++ b/src/components/views/settings/EventIndexPanel.js
@@ -129,11 +129,16 @@ export default class EventIndexPanel extends React.Component {
eventIndexingSettings = (