;
},
+ _getMembershipSection: function() {
+ const Spinner = sdk.getComponent("elements.Spinner");
+
+ const group = MatrixClientPeg.get().getGroup(this.props.groupId);
+ if (!group) return null;
+
+ if (group.myMembership === 'invite') {
+ if (this.state.membershipBusy) {
+ return
+
+
;
+ }
+
+ return
+
+ { _t("%(inviter)s has invited you to join this group", {inviter: group.inviter.userId}) }
+
+
+
+ { _t("Accept") }
+
+
+ { _t("Decline") }
+
+
+
;
+ } else if (group.myMembership === 'join') {
+ let youAreAMemberText = _t("You are a member of this group");
+ if (this.state.summary.user && this.state.summary.user.is_privileged) {
+ youAreAMemberText = _t("You are an administrator of this group");
+ }
+
+ let publicisedButton;
+ if (this.state.publicityBusy) {
+ publicisedButton = ;
+ }
+
+ let publicisedSection;
+ if (this.state.summary.user && this.state.summary.user.is_publicised) {
+ if (!this.state.publicityBusy) {
+ publicisedButton =
+ { _t("Unpublish") }
+ ;
+ }
+ publicisedSection =
+ { _t("This group is published on your profile") }
+
diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index aad4e1957a..5ce36b4b82 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -61,6 +61,9 @@ module.exports = React.createClass({
// for pending messages.
ourUserId: React.PropTypes.string,
+ // true to suppress the date at the start of the timeline
+ suppressFirstDateSeparator: React.PropTypes.bool,
+
// whether to show read receipts
showReadReceipts: React.PropTypes.bool,
@@ -151,15 +154,15 @@ module.exports = React.createClass({
// 0: read marker is within the window
// +1: read marker is below the window
getReadMarkerPosition: function() {
- var readMarker = this.refs.readMarkerNode;
- var messageWrapper = this.refs.scrollPanel;
+ const readMarker = this.refs.readMarkerNode;
+ const messageWrapper = this.refs.scrollPanel;
if (!readMarker || !messageWrapper) {
return null;
}
- var wrapperRect = ReactDOM.findDOMNode(messageWrapper).getBoundingClientRect();
- var readMarkerRect = readMarker.getBoundingClientRect();
+ const wrapperRect = ReactDOM.findDOMNode(messageWrapper).getBoundingClientRect();
+ const readMarkerRect = readMarker.getBoundingClientRect();
// the read-marker pretends to have zero height when it is actually
// two pixels high; +2 here to account for that.
@@ -238,6 +241,10 @@ module.exports = React.createClass({
// TODO: Implement granular (per-room) hide options
_shouldShowEvent: function(mxEv) {
+ if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
+ return false; // ignored = no show (only happens if the ignore happens after an event was received)
+ }
+
const EventTile = sdk.getComponent('rooms.EventTile');
if (!EventTile.haveTileForEvent(mxEv)) {
return false; // no tile = no show
@@ -255,7 +262,7 @@ module.exports = React.createClass({
this.eventNodes = {};
- var i;
+ let i;
// first figure out which is the last event in the list which we're
// actually going to show; this allows us to behave slightly
@@ -265,9 +272,9 @@ module.exports = React.createClass({
// a local echo, to manage the read-marker.
let lastShownEvent;
- var lastShownNonLocalEchoIndex = -1;
+ let lastShownNonLocalEchoIndex = -1;
for (i = this.props.events.length-1; i >= 0; i--) {
- var mxEv = this.props.events[i];
+ const mxEv = this.props.events[i];
if (!this._shouldShowEvent(mxEv)) {
continue;
}
@@ -285,12 +292,12 @@ module.exports = React.createClass({
break;
}
- var ret = [];
+ const ret = [];
- var prevEvent = null; // the last event we showed
+ let prevEvent = null; // the last event we showed
// assume there is no read marker until proven otherwise
- var readMarkerVisible = false;
+ let readMarkerVisible = false;
// if the readmarker has moved, cancel any active ghost.
if (this.currentReadMarkerEventId && this.props.readMarkerEventId &&
@@ -302,16 +309,16 @@ module.exports = React.createClass({
const isMembershipChange = (e) => e.getType() === 'm.room.member';
for (i = 0; i < this.props.events.length; i++) {
- let mxEv = this.props.events[i];
- let eventId = mxEv.getId();
- let last = (mxEv === lastShownEvent);
+ const mxEv = this.props.events[i];
+ const eventId = mxEv.getId();
+ const last = (mxEv === lastShownEvent);
const wantTile = this._shouldShowEvent(mxEv);
// Wrap consecutive member events in a ListSummary, ignore if redacted
if (isMembershipChange(mxEv) && wantTile) {
let readMarkerInMels = false;
- let ts1 = mxEv.getTs();
+ const ts1 = mxEv.getTs();
// Ensure that the key of the MemberEventListSummary does not change with new
// member events. This will prevent it from being re-created unnecessarily, and
// instead will allow new props to be provided. In turn, the shouldComponentUpdate
@@ -323,7 +330,7 @@ module.exports = React.createClass({
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
- let dateSeparator =
;
+ const dateSeparator =
;
ret.push(dateSeparator);
}
@@ -332,7 +339,7 @@ module.exports = React.createClass({
readMarkerInMels = true;
}
- let summarisedEvents = [mxEv];
+ const summarisedEvents = [mxEv];
for (;i + 1 < this.props.events.length; i++) {
const collapsedMxEv = this.props.events[i + 1];
@@ -358,8 +365,13 @@ module.exports = React.createClass({
summarisedEvents.push(collapsedMxEv);
}
+ let highlightInMels = false;
+
// At this point, i = the index of the last event in the summary sequence
let eventTiles = summarisedEvents.map((e) => {
+ if (e.getId() === this.props.highlightedEventId) {
+ highlightInMels = true;
+ }
// In order to prevent DateSeparators from appearing in the expanded form
// of MemberEventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
@@ -373,15 +385,13 @@ module.exports = React.createClass({
eventTiles = null;
}
- ret.push(
-
- {eventTiles}
-
- );
+ ret.push(
+ { eventTiles }
+ );
if (readMarkerInMels) {
ret.push(this._getReadMarkerTile(visible));
@@ -398,7 +408,7 @@ module.exports = React.createClass({
prevEvent = mxEv;
}
- var isVisibleReadMarker = false;
+ let isVisibleReadMarker = false;
if (eventId == this.props.readMarkerEventId) {
var visible = this.props.readMarkerVisible;
@@ -438,10 +448,10 @@ module.exports = React.createClass({
_getTilesForEvent: function(prevEvent, mxEv, last) {
const EventTile = sdk.getComponent('rooms.EventTile');
const DateSeparator = sdk.getComponent('messages.DateSeparator');
- var ret = [];
+ const ret = [];
// is this a continuation of the previous message?
- var continuation = false;
+ let continuation = false;
if (prevEvent !== null
&& prevEvent.sender && mxEv.sender
@@ -466,8 +476,8 @@ module.exports = React.createClass({
// local echoes have a fake date, which could even be yesterday. Treat them
// as 'today' for the date separators.
- var ts1 = mxEv.getTs();
- var eventDate = mxEv.getDate();
+ let ts1 = mxEv.getTs();
+ let eventDate = mxEv.getDate();
if (mxEv.status) {
eventDate = new Date();
ts1 = eventDate.getTime();
@@ -475,19 +485,19 @@ module.exports = React.createClass({
// do we need a date separator since the last event?
if (this._wantsDateSeparator(prevEvent, eventDate)) {
- var dateSeparator =
;
+ const dateSeparator =
;
ret.push(dateSeparator);
continuation = false;
}
- var eventId = mxEv.getId();
- var highlight = (eventId == this.props.highlightedEventId);
+ const eventId = mxEv.getId();
+ const highlight = (eventId == this.props.highlightedEventId);
// we can't use local echoes as scroll tokens, because their event IDs change.
// Local echos have a send "status".
- var scrollToken = mxEv.status ? undefined : eventId;
+ const scrollToken = mxEv.status ? undefined : eventId;
- var readReceipts;
+ let readReceipts;
if (this.props.showReadReceipts) {
readReceipts = this._getReadReceiptsForEvent(mxEv);
}
@@ -505,8 +515,8 @@ module.exports = React.createClass({
eventSendStatus={mxEv.status}
tileShape={this.props.tileShape}
isTwelveHour={this.props.isTwelveHour}
- last={last} isSelectedEvent={highlight}/>
-
+ last={last} isSelectedEvent={highlight} />
+ ,
);
return ret;
@@ -514,10 +524,10 @@ module.exports = React.createClass({
_wantsDateSeparator: function(prevEvent, nextEventDate) {
if (prevEvent == null) {
- // First event in the panel always wants a DateSeparator
- return true;
+ // first event in the panel: depends if we could back-paginate from
+ // here.
+ return !this.props.suppressFirstDateSeparator;
}
-
const prevEventDate = prevEvent.getDate();
if (!nextEventDate || !prevEventDate) {
return false;
@@ -541,12 +551,15 @@ module.exports = React.createClass({
if (!room) {
return null;
}
- let receipts = [];
+ const receipts = [];
room.getReceiptsForEvent(event).forEach((r) => {
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
return; // ignore non-read receipts and receipts from self.
}
- let member = room.getMember(r.userId);
+ if (MatrixClientPeg.get().isUserIgnored(r.userId)) {
+ return; // ignore ignored users
+ }
+ const member = room.getMember(r.userId);
if (!member) {
return; // ignore unknown user IDs
}
@@ -562,7 +575,7 @@ module.exports = React.createClass({
},
_getReadMarkerTile: function(visible) {
- var hr;
+ let hr;
if (visible) {
hr =
- {hr}
+ { hr }
);
},
@@ -591,7 +604,7 @@ module.exports = React.createClass({
},
_getReadMarkerGhostTile: function() {
- var hr = ;
@@ -602,7 +615,7 @@ module.exports = React.createClass({
return (
- {hr}
+ { hr }
);
},
@@ -614,7 +627,7 @@ module.exports = React.createClass({
// once dynamic content in the events load, make the scrollPanel check the
// scroll offsets.
_onWidgetLoad: function() {
- var scrollPanel = this.refs.scrollPanel;
+ const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
scrollPanel.forceUpdate();
}
@@ -625,9 +638,9 @@ module.exports = React.createClass({
},
render: function() {
- var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
- var Spinner = sdk.getComponent("elements.Spinner");
- var topSpinner, bottomSpinner;
+ const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
+ const Spinner = sdk.getComponent("elements.Spinner");
+ let topSpinner, bottomSpinner;
if (this.props.backPaginating) {
topSpinner =
- {_t('Error whilst fetching joined groups')}
+ { _t('Error whilst fetching joined groups') }
;
} else {
content = ;
}
return
-
+
- {_t('Create a new group')}
+ { _t('Create a new group') }
- {_t(
+ { _t(
'Create a group to represent your community! '+
'Define a set of rooms and your own custom homepage '+
'to mark out your space in the Matrix universe.',
- )}
+ ) }
- {_t('Join an existing group')}
+ { _t('Join an existing group') }
- {_tJsx(
+ { _tJsx(
'To join an existing group you\'ll have to '+
'know its group identifier; this will look '+
'something like +example:matrix.org.',
/(.*)<\/i>/,
- (sub) => {sub},
- )}
+ (sub) => { sub },
+ ) }
- {content}
+ { content }
;
},
diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.js
index 21eccbdff6..3c8f34fb86 100644
--- a/src/components/structures/NotificationPanel.js
+++ b/src/components/structures/NotificationPanel.js
@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-var React = require('react');
-var ReactDOM = require("react-dom");
+const React = require('react');
+const ReactDOM = require("react-dom");
import { _t } from '../../languageHandler';
-var Matrix = require("matrix-js-sdk");
-var sdk = require('../../index');
-var MatrixClientPeg = require("../../MatrixClientPeg");
-var dis = require("../../dispatcher");
+const Matrix = require("matrix-js-sdk");
+const sdk = require('../../index');
+const MatrixClientPeg = require("../../MatrixClientPeg");
+const dis = require("../../dispatcher");
/*
* Component which shows the global notification list using a TimelinePanel
*/
-var NotificationPanel = React.createClass({
+const NotificationPanel = React.createClass({
displayName: 'NotificationPanel',
propTypes: {
@@ -33,10 +33,10 @@ var NotificationPanel = React.createClass({
render: function() {
// wrap a TimelinePanel with the jump-to-event bits turned off.
- var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
- var Loader = sdk.getComponent("elements.Spinner");
+ const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
+ const Loader = sdk.getComponent("elements.Spinner");
- var timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
+ const timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
if (timelineSet) {
return (
);
- }
- else {
+ } else {
console.error("No notifTimelineSet available!");
return (
-
+
);
}
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 2a81605a78..cad55351d1 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -43,6 +43,10 @@ module.exports = React.createClass({
// the end of the live timeline.
atEndOfLiveTimeline: React.PropTypes.bool,
+ // This is true when the user is alone in the room, but has also sent a message.
+ // Used to suggest to the user to invite someone
+ sentMessageAndIsAlone: React.PropTypes.bool,
+
// true if there is an active call in this room (means we show
// the 'Active Call' text in the status bar if there is nothing
// more interesting)
@@ -60,6 +64,14 @@ module.exports = React.createClass({
// 'unsent messages' bar
onCancelAllClick: React.PropTypes.func,
+ // callback for when the user clicks on the 'invite others' button in the
+ // 'you are alone' bar
+ onInviteClick: React.PropTypes.func,
+
+ // callback for when the user clicks on the 'stop warning me' button in the
+ // 'you are alone' bar
+ onStopWarningClick: React.PropTypes.func,
+
// callback for when the user clicks on the 'scroll to bottom' button
onScrollToBottomClick: React.PropTypes.func,
@@ -103,7 +115,7 @@ module.exports = React.createClass({
componentWillUnmount: function() {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
- var client = MatrixClientPeg.get();
+ const client = MatrixClientPeg.get();
if (client) {
client.removeListener("sync", this.onSyncStateChange);
client.removeListener("RoomMember.typing", this.onRoomMemberTyping);
@@ -115,18 +127,18 @@ module.exports = React.createClass({
return;
}
this.setState({
- syncState: state
+ syncState: state,
});
},
onRoomMemberTyping: function(ev, member) {
this.setState({
- usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
+ usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
});
},
// Check whether current size is greater than 0, if yes call props.onVisible
- _checkSize: function () {
+ _checkSize: function() {
if (this.props.onVisible && this._getSize()) {
this.props.onVisible();
}
@@ -140,7 +152,8 @@ module.exports = React.createClass({
(this.state.usersTyping.length > 0) ||
this.props.numUnreadMessages ||
!this.props.atEndOfLiveTimeline ||
- this.props.hasActiveCall
+ this.props.hasActiveCall ||
+ this.props.sentMessageAndIsAlone
) {
return STATUS_BAR_EXPANDED;
} else if (this.props.unsentMessageError) {
@@ -157,9 +170,9 @@ module.exports = React.createClass({
if (this.props.numUnreadMessages) {
return (
- {_t('Connectivity to the server has been lost.')}
+ { _t('Connectivity to the server has been lost.') }
- {_t('Sent messages will be stored until your connection has returned.')}
+ { _t('Sent messages will be stored until your connection has returned.') }
);
@@ -275,24 +288,24 @@ module.exports = React.createClass({
// set when you've scrolled up
if (this.props.numUnreadMessages) {
// MUST use var name "count" for pluralization to kick in
- var unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
+ const unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
return (
);
},
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 87bed1ed08..db40380636 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -22,33 +22,34 @@ limitations under the License.
import shouldHideEvent from "../../shouldHideEvent";
-var React = require("react");
-var ReactDOM = require("react-dom");
+const React = require("react");
+const ReactDOM = require("react-dom");
import Promise from 'bluebird';
-var classNames = require("classnames");
-var Matrix = require("matrix-js-sdk");
+const classNames = require("classnames");
+const Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler';
-var UserSettingsStore = require('../../UserSettingsStore');
-var MatrixClientPeg = require("../../MatrixClientPeg");
-var ContentMessages = require("../../ContentMessages");
-var Modal = require("../../Modal");
-var sdk = require('../../index');
-var CallHandler = require('../../CallHandler');
-var Resend = require("../../Resend");
-var dis = require("../../dispatcher");
-var Tinter = require("../../Tinter");
-var rate_limited_func = require('../../ratelimitedfunc');
-var ObjectUtils = require('../../ObjectUtils');
-var Rooms = require('../../Rooms');
+const UserSettingsStore = require('../../UserSettingsStore');
+const MatrixClientPeg = require("../../MatrixClientPeg");
+const ContentMessages = require("../../ContentMessages");
+const Modal = require("../../Modal");
+const sdk = require('../../index');
+const CallHandler = require('../../CallHandler');
+const Resend = require("../../Resend");
+const dis = require("../../dispatcher");
+const Tinter = require("../../Tinter");
+const rate_limited_func = require('../../ratelimitedfunc');
+const ObjectUtils = require('../../ObjectUtils');
+const Rooms = require('../../Rooms');
import KeyCode from '../../KeyCode';
import UserProvider from '../../autocomplete/UserProvider';
import RoomViewStore from '../../stores/RoomViewStore';
+import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
-let DEBUG = false;
+const DEBUG = false;
let debuglog = function() {};
const BROWSER_SUPPORTS_SANDBOX = 'sandbox' in document.createElement('iframe');
@@ -116,12 +117,17 @@ module.exports = React.createClass({
guestsCanJoin: false,
canPeek: false,
showApps: false,
+ isAlone: false,
+ isPeeking: false,
// error object, as from the matrix client/server API
// If we failed to load information about the room,
// store the error here.
roomLoadError: null,
+ // Have we sent a request to join the room that we're waiting to complete?
+ joining: false,
+
// this is true if we are fully scrolled-down, and are looking at
// the end of the live timeline. It has the effect of hiding the
// 'scroll to bottom' knob, among a couple of other things.
@@ -156,6 +162,22 @@ module.exports = React.createClass({
if (this.unmounted) {
return;
}
+
+ if (!initial && this.state.roomId !== RoomViewStore.getRoomId()) {
+ // RoomView explicitly does not support changing what room
+ // is being viewed: instead it should just be re-mounted when
+ // switching rooms. Therefore, if the room ID changes, we
+ // ignore this. We either need to do this or add code to handle
+ // saving the scroll position (otherwise we end up saving the
+ // scroll position against the wrong room).
+
+ // Given that doing the setState here would cause a bunch of
+ // unnecessary work, we just ignore the change since we know
+ // that if the current room ID has changed from what we thought
+ // it was, it means we're about to be unmounted.
+ return;
+ }
+
const newState = {
roomId: RoomViewStore.getRoomId(),
roomAlias: RoomViewStore.getRoomAlias(),
@@ -163,16 +185,11 @@ module.exports = React.createClass({
roomLoadError: RoomViewStore.getRoomLoadError(),
joining: RoomViewStore.isJoining(),
initialEventId: RoomViewStore.getInitialEventId(),
- initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
forwardingEvent: RoomViewStore.getForwardingEvent(),
shouldPeek: RoomViewStore.shouldPeek(),
};
- // finished joining, start waiting for a room and show a spinner. See onRoom.
- newState.waitingForRoom = this.state.joining && !newState.joining &&
- !RoomViewStore.getJoinError();
-
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
console.log(
'RVS update:',
@@ -181,7 +198,6 @@ module.exports = React.createClass({
'loading?', newState.roomLoading,
'joining?', newState.joining,
'initial?', initial,
- 'waiting?', newState.waitingForRoom,
'shouldPeek?', newState.shouldPeek,
);
@@ -189,6 +205,25 @@ module.exports = React.createClass({
// the RoomView instance
if (initial) {
newState.room = MatrixClientPeg.get().getRoom(newState.roomId);
+ if (newState.room) {
+ newState.unsentMessageError = this._getUnsentMessageError(newState.room);
+ newState.showApps = this._shouldShowApps(newState.room);
+ this._onRoomLoaded(newState.room);
+ }
+ }
+
+ if (this.state.roomId === null && newState.roomId !== null) {
+ // Get the scroll state for the new room
+
+ // If an event ID wasn't specified, default to the one saved for this room
+ // in the scroll state store. Assume initialEventPixelOffset should be set.
+ if (!newState.initialEventId) {
+ const roomScrollState = RoomScrollStateStore.getScrollState(newState.roomId);
+ if (roomScrollState) {
+ newState.initialEventId = roomScrollState.focussedEvent;
+ newState.initialEventPixelOffset = roomScrollState.pixelOffset;
+ }
+ }
}
// Clear the search results when clicking a search result (which changes the
@@ -197,22 +232,20 @@ module.exports = React.createClass({
newState.searchResults = null;
}
- // Store the scroll state for the previous room so that we can return to this
- // position when viewing this room in future.
- if (this.state.roomId !== newState.roomId) {
- this._updateScrollMap(this.state.roomId);
- }
+ this.setState(newState);
+ // At this point, newState.roomId could be null (e.g. the alias might not
+ // have been resolved yet) so anything called here must handle this case.
- this.setState(newState, () => {
- // At this point, this.state.roomId could be null (e.g. the alias might not
- // have been resolved yet) so anything called here must handle this case.
- if (initial) {
- this._onHaveRoom();
- }
- });
+ // We pass the new state into this function for it to read: it needs to
+ // observe the new state but we don't want to put it in the setState
+ // callback because this would prevent the setStates from being batched,
+ // ie. cause it to render RoomView twice rather than the once that is necessary.
+ if (initial) {
+ this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek);
+ }
},
- _onHaveRoom: function() {
+ _setupRoom: function(room, roomId, joining, shouldPeek) {
// if this is an unknown room then we're in one of three states:
// - This is a room we can peek into (search engine) (we can /peek)
// - This is a room we can publicly join or were invited to. (we can /join)
@@ -228,29 +261,27 @@ module.exports = React.createClass({
// about it). We don't peek in the historical case where we were joined but are
// now not joined because the js-sdk peeking API will clobber our historical room,
// making it impossible to indicate a newly joined room.
- const room = this.state.room;
- if (room) {
- this.setState({
- unsentMessageError: this._getUnsentMessageError(room),
- showApps: this._shouldShowApps(room),
- });
- this._onRoomLoaded(room);
- }
- if (!this.state.joining && this.state.roomId) {
+ if (!joining && roomId) {
if (this.props.autoJoin) {
this.onJoinButtonClicked();
- } else if (!room && this.state.shouldPeek) {
- console.log("Attempting to peek into room %s", this.state.roomId);
+ } else if (!room && shouldPeek) {
+ console.log("Attempting to peek into room %s", roomId);
this.setState({
peekLoading: true,
+ isPeeking: true, // this will change to false if peeking fails
});
- MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
+ MatrixClientPeg.get().peekInRoom(roomId).then((room) => {
this.setState({
room: room,
peekLoading: false,
});
this._onRoomLoaded(room);
}, (err) => {
+ // Stop peeking if anything went wrong
+ this.setState({
+ isPeeking: false,
+ });
+
// This won't necessarily be a MatrixError, but we duck-type
// here and say if it's got an 'errcode' key with the right value,
// it means we can't peek.
@@ -267,6 +298,7 @@ module.exports = React.createClass({
} else if (room) {
// Stop peeking because we have joined this room previously
MatrixClientPeg.get().stopPeeking();
+ this.setState({isPeeking: false});
}
},
@@ -284,10 +316,10 @@ module.exports = React.createClass({
},
componentDidMount: function() {
- var call = this._getCallForRoom();
- var callState = call ? call.call_state : "ended";
+ const call = this._getCallForRoom();
+ const callState = call ? call.call_state : "ended";
this.setState({
- callState: callState
+ callState: callState,
});
this._updateConfCallNotification();
@@ -304,9 +336,8 @@ module.exports = React.createClass({
this.state.room.getJoinedMembers().length == 1 &&
this.state.room.getLiveTimeline() &&
this.state.room.getLiveTimeline().getEvents() &&
- this.state.room.getLiveTimeline().getEvents().length <= 6)
- {
- var inviteBox = document.getElementById("mx_SearchableEntityList_query");
+ this.state.room.getLiveTimeline().getEvents().length <= 6) {
+ const inviteBox = document.getElementById("mx_SearchableEntityList_query");
setTimeout(function() {
if (inviteBox) {
inviteBox.focus();
@@ -322,7 +353,7 @@ module.exports = React.createClass({
componentDidUpdate: function() {
if (this.refs.roomView) {
- var roomView = ReactDOM.findDOMNode(this.refs.roomView);
+ const roomView = ReactDOM.findDOMNode(this.refs.roomView);
if (!roomView.ondrop) {
roomView.addEventListener('drop', this.onDrop);
roomView.addEventListener('dragover', this.onDragOver);
@@ -340,14 +371,16 @@ module.exports = React.createClass({
this.unmounted = true;
// update the scroll map before we get unmounted
- this._updateScrollMap(this.state.roomId);
+ if (this.state.roomId) {
+ RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState());
+ }
if (this.refs.roomView) {
// disconnect the D&D event listeners from the room view. This
// is really just for hygiene - we're going to be
// deleted anyway, so it doesn't matter if the event listeners
// don't get cleaned up.
- var roomView = ReactDOM.findDOMNode(this.refs.roomView);
+ const roomView = ReactDOM.findDOMNode(this.refs.roomView);
roomView.removeEventListener('drop', this.onDrop);
roomView.removeEventListener('dragover', this.onDragOver);
roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
@@ -429,6 +462,8 @@ module.exports = React.createClass({
switch (payload.action) {
case 'message_send_failed':
case 'message_sent':
+ this._checkIfAlone(this.state.room);
+ // no break; to intentionally fall through
case 'message_send_cancelled':
this.setState({
unsentMessageError: this._getUnsentMessageError(this.state.room),
@@ -453,8 +488,7 @@ module.exports = React.createClass({
if (call) {
callState = call.call_state;
- }
- else {
+ } else {
callState = "ended";
}
@@ -566,17 +600,17 @@ module.exports = React.createClass({
},
_calculatePeekRules: function(room) {
- var guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
+ const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
this.setState({
- guestsCanJoin: true
+ guestsCanJoin: true,
});
}
- var historyVisibility = room.currentState.getStateEvents("m.room.history_visibility", "");
+ const historyVisibility = room.currentState.getStateEvents("m.room.history_visibility", "");
if (historyVisibility && historyVisibility.getContent().history_visibility === "world_readable") {
this.setState({
- canPeek: true
+ canPeek: true,
});
}
},
@@ -585,47 +619,35 @@ module.exports = React.createClass({
// console.log("_updatePreviewUrlVisibility");
// check our per-room overrides
- var roomPreviewUrls = room.getAccountData("org.matrix.room.preview_urls");
+ const roomPreviewUrls = room.getAccountData("org.matrix.room.preview_urls");
if (roomPreviewUrls && roomPreviewUrls.getContent().disable !== undefined) {
this.setState({
- showUrlPreview: !roomPreviewUrls.getContent().disable
+ showUrlPreview: !roomPreviewUrls.getContent().disable,
});
return;
}
// check our global disable override
- var userRoomPreviewUrls = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
+ const userRoomPreviewUrls = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
if (userRoomPreviewUrls && userRoomPreviewUrls.getContent().disable) {
this.setState({
- showUrlPreview: false
+ showUrlPreview: false,
});
return;
}
// check the room state event
- var roomStatePreviewUrls = room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
+ const roomStatePreviewUrls = room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
if (roomStatePreviewUrls && roomStatePreviewUrls.getContent().disable) {
this.setState({
- showUrlPreview: false
+ showUrlPreview: false,
});
return;
}
// otherwise, we assume they're on.
this.setState({
- showUrlPreview: true
- });
- },
-
- _updateScrollMap(roomId) {
- // No point updating scroll state if the room ID hasn't been resolved yet
- if (!roomId) {
- return;
- }
- dis.dispatch({
- action: 'update_scroll_state',
- room_id: roomId,
- scroll_state: this._getScrollState(),
+ showUrlPreview: true,
});
},
@@ -635,18 +657,17 @@ module.exports = React.createClass({
}
this.setState({
room: room,
- waitingForRoom: false,
}, () => {
this._onRoomLoaded(room);
});
},
updateTint: function() {
- var room = this.state.room;
+ const room = this.state.room;
if (!room) return;
- var color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
- var color_scheme = {};
+ const color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
+ let color_scheme = {};
if (color_scheme_event) {
color_scheme = color_scheme_event.getContent();
// XXX: we should validate the event
@@ -664,12 +685,11 @@ module.exports = React.createClass({
onRoomAccountData: function(event, room) {
if (room.roomId == this.state.roomId) {
if (event.getType() === "org.matrix.room.color_scheme") {
- var color_scheme = event.getContent();
+ const color_scheme = event.getContent();
// XXX: we should validate the event
console.log("Tinter.tint from onRoomAccountData");
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
- }
- else if (event.getType() === "org.matrix.room.preview_urls") {
+ } else if (event.getType() === "org.matrix.room.preview_urls") {
this._updatePreviewUrlVisibility(room);
}
}
@@ -691,14 +711,7 @@ module.exports = React.createClass({
onRoomMemberMembership: function(ev, member, oldMembership) {
if (member.userId == MatrixClientPeg.get().credentials.userId) {
-
- if (member.membership === 'join') {
- this.setState({
- waitingForRoom: false,
- });
- } else {
- this.forceUpdate();
- }
+ this.forceUpdate();
}
},
@@ -715,7 +728,7 @@ module.exports = React.createClass({
// if we are now a member of the room, where we were not before, that
// means we have finished joining a room we were previously peeking
// into.
- var me = MatrixClientPeg.get().credentials.userId;
+ const me = MatrixClientPeg.get().credentials.userId;
if (this.state.joining && this.state.room.hasMembershipState(me, "join")) {
// Having just joined a room, check to see if it looks like a DM room, and if so,
// mark it as one. This is to work around the fact that some clients don't support
@@ -730,9 +743,34 @@ module.exports = React.createClass({
}
}, 500),
+ _checkIfAlone: function(room) {
+ let warnedAboutLonelyRoom = false;
+ if (localStorage) {
+ warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId);
+ }
+ if (warnedAboutLonelyRoom) {
+ if (this.state.isAlone) this.setState({isAlone: false});
+ return;
+ }
+
+ const joinedMembers = room.currentState.getMembers().filter(m => m.membership === "join" || m.membership === "invite");
+ this.setState({isAlone: joinedMembers.length === 1});
+ },
+
_getUnsentMessageError: function(room) {
const unsentMessages = this._getUnsentMessages(room);
if (!unsentMessages.length) return "";
+
+ if (
+ unsentMessages.length === 1 &&
+ unsentMessages[0].error &&
+ unsentMessages[0].error.data &&
+ unsentMessages[0].error.data.error &&
+ unsentMessages[0].error.name !== "UnknownDeviceError"
+ ) {
+ return unsentMessages[0].error.data.error;
+ }
+
for (const event of unsentMessages) {
if (!event.error || event.error.name !== "UnknownDeviceError") {
return _t("Some of your messages have not been sent.");
@@ -749,18 +787,18 @@ module.exports = React.createClass({
},
_updateConfCallNotification: function() {
- var room = this.state.room;
+ const room = this.state.room;
if (!room || !this.props.ConferenceHandler) {
return;
}
- var confMember = room.getMember(
- this.props.ConferenceHandler.getConferenceUserIdForRoom(room.roomId)
+ const confMember = room.getMember(
+ this.props.ConferenceHandler.getConferenceUserIdForRoom(room.roomId),
);
if (!confMember) {
return;
}
- var confCall = this.props.ConferenceHandler.getConferenceCallForRoom(confMember.roomId);
+ const confCall = this.props.ConferenceHandler.getConferenceCallForRoom(confMember.roomId);
// A conf call notification should be displayed if there is an ongoing
// conf call but this cilent isn't a part of it.
@@ -768,7 +806,7 @@ module.exports = React.createClass({
displayConfCallNotification: (
(!confCall || confCall.call_state === "ended") &&
confMember.membership === "join"
- )
+ ),
});
},
@@ -783,7 +821,7 @@ module.exports = React.createClass({
if (this.state.searchResults.next_batch) {
debuglog("requesting more search results");
- var searchPromise = MatrixClientPeg.get().backPaginateRoomEventsSearch(
+ const searchPromise = MatrixClientPeg.get().backPaginateRoomEventsSearch(
this.state.searchResults);
return this._handleSearchResult(searchPromise);
} else {
@@ -800,6 +838,22 @@ module.exports = React.createClass({
Resend.cancelUnsentEvents(this.state.room);
},
+ onInviteButtonClick: function() {
+ // call AddressPickerDialog
+ dis.dispatch({
+ action: 'view_invite',
+ roomId: this.state.room.roomId,
+ });
+ this.setState({isAlone: false}); // there's a good chance they'll invite someone
+ },
+
+ onStopAloneWarningClick: function() {
+ if (localStorage) {
+ localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true);
+ }
+ this.setState({isAlone: false});
+ },
+
onJoinButtonClicked: function(ev) {
const cli = MatrixClientPeg.get();
@@ -878,8 +932,7 @@ module.exports = React.createClass({
numUnreadMessages: 0,
atEndOfLiveTimeline: true,
});
- }
- else {
+ } else {
this.setState({
atEndOfLiveTimeline: false,
});
@@ -893,10 +946,10 @@ module.exports = React.createClass({
ev.dataTransfer.dropEffect = 'none';
- var items = ev.dataTransfer.items;
+ const items = ev.dataTransfer.items;
if (items.length == 1) {
if (items[0].kind == 'file') {
- this.setState({ draggingFile : true });
+ this.setState({ draggingFile: true });
ev.dataTransfer.dropEffect = 'copy';
}
}
@@ -905,8 +958,8 @@ module.exports = React.createClass({
onDrop: function(ev) {
ev.stopPropagation();
ev.preventDefault();
- this.setState({ draggingFile : false });
- var files = ev.dataTransfer.files;
+ this.setState({ draggingFile: false });
+ const files = ev.dataTransfer.files;
if (files.length == 1) {
this.uploadFile(files[0]);
}
@@ -915,7 +968,7 @@ module.exports = React.createClass({
onDragLeaveOrEnd: function(ev) {
ev.stopPropagation();
ev.preventDefault();
- this.setState({ draggingFile : false });
+ this.setState({ draggingFile: false });
},
uploadFile: function(file) {
@@ -925,7 +978,7 @@ module.exports = React.createClass({
}
ContentMessages.sendContentToRoom(
- file, this.state.room.roomId, MatrixClientPeg.get()
+ file, this.state.room.roomId, MatrixClientPeg.get(),
).done(undefined, (error) => {
if (error.name === "UnknownDeviceError") {
dis.dispatch({
@@ -964,19 +1017,19 @@ module.exports = React.createClass({
// todo: should cancel any previous search requests.
this.searchId = new Date().getTime();
- var filter;
+ let filter;
if (scope === "Room") {
filter = {
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
rooms: [
- this.state.room.roomId
- ]
+ this.state.room.roomId,
+ ],
};
}
debuglog("sending search request");
- var searchPromise = MatrixClientPeg.get().searchRoomEvents({
+ const searchPromise = MatrixClientPeg.get().searchRoomEvents({
filter: filter,
term: term,
});
@@ -984,11 +1037,11 @@ module.exports = React.createClass({
},
_handleSearchResult: function(searchPromise) {
- var self = this;
+ const self = this;
// keep a record of the current search id, so that if the search terms
// change before we get a response, we can ignore the results.
- var localSearchId = this.searchId;
+ const localSearchId = this.searchId;
this.setState({
searchInProgress: true,
@@ -1007,7 +1060,7 @@ module.exports = React.createClass({
// In either case, we want to highlight the literal search term
// whether it was used by the search engine or not.
- var highlights = results.highlights;
+ let highlights = results.highlights;
if (highlights.indexOf(self.state.searchTerm) < 0) {
highlights = highlights.concat(self.state.searchTerm);
}
@@ -1015,14 +1068,15 @@ module.exports = React.createClass({
// For overlapping highlights,
// favour longer (more specific) terms first
highlights = highlights.sort(function(a, b) {
- return b.length - a.length; });
+ return b.length - a.length;
+});
self.setState({
searchHighlights: highlights,
searchResults: results,
});
}, function(error) {
- var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Search failed: " + error);
Modal.createTrackedDialog('Search failed', '', ErrorDialog, {
title: _t("Search failed"),
@@ -1030,17 +1084,17 @@ module.exports = React.createClass({
});
}).finally(function() {
self.setState({
- searchInProgress: false
+ searchInProgress: false,
});
});
},
getSearchResultTiles: function() {
- var EventTile = sdk.getComponent('rooms.EventTile');
- var SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
- var Spinner = sdk.getComponent("elements.Spinner");
+ const EventTile = sdk.getComponent('rooms.EventTile');
+ const SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
+ const Spinner = sdk.getComponent("elements.Spinner");
- var cli = MatrixClientPeg.get();
+ const cli = MatrixClientPeg.get();
// XXX: todo: merge overlapping results somehow?
// XXX: why doesn't searching on name work?
@@ -1050,7 +1104,7 @@ module.exports = React.createClass({
return [];
}
- var ret = [];
+ const ret = [];
if (this.state.searchInProgress) {
ret.push(
+ ,
);
}
}
// once dynamic content in the search results load, make the scrollPanel check
// the scroll offsets.
- var onWidgetLoad = () => {
- var scrollPanel = this.refs.searchResultsPanel;
+ const onWidgetLoad = () => {
+ const scrollPanel = this.refs.searchResultsPanel;
if (scrollPanel) {
scrollPanel.checkScroll();
}
};
- var lastRoomId;
+ let lastRoomId;
- for (var i = this.state.searchResults.results.length - 1; i >= 0; i--) {
- var result = this.state.searchResults.results[i];
+ for (let i = this.state.searchResults.results.length - 1; i >= 0; i--) {
+ const result = this.state.searchResults.results[i];
- var mxEv = result.context.getEvent();
- var roomId = mxEv.getRoomId();
+ const mxEv = result.context.getEvent();
+ const roomId = mxEv.getRoomId();
if (!EventTile.haveTileForEvent(mxEv)) {
// XXX: can this ever happen? It will make the result count
@@ -1097,13 +1151,13 @@ module.exports = React.createClass({
if (this.state.searchScope === 'All') {
if(roomId != lastRoomId) {
- var room = cli.getRoom(roomId);
+ const room = cli.getRoom(roomId);
// XXX: if we've left the room, we might not know about
// it. We should tell the js sdk to go and find out about
// it. But that's not an issue currently, as synapse only
// returns results for rooms we're joined to.
- var roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
+ const roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
ret.push(
{ _t("Room") }: { roomName }
@@ -1112,13 +1166,13 @@ module.exports = React.createClass({
}
}
- var resultLink = "#/room/"+roomId+"/"+mxEv.getId();
+ const resultLink = "#/room/"+roomId+"/"+mxEv.getId();
ret.push();
+ onWidgetLoad={onWidgetLoad} />);
}
return ret;
},
@@ -1134,38 +1188,37 @@ module.exports = React.createClass({
uploadingRoomSettings: true,
});
- var newName = this.refs.header.getEditedName();
+ const newName = this.refs.header.getEditedName();
if (newName !== undefined) {
this.refs.room_settings.setName(newName);
}
- var newTopic = this.refs.header.getEditedTopic();
+ const newTopic = this.refs.header.getEditedTopic();
if (newTopic !== undefined) {
this.refs.room_settings.setTopic(newTopic);
}
this.refs.room_settings.save().then((results) => {
- var fails = results.filter(function(result) { return result.state !== "fulfilled"; });
+ const fails = results.filter(function(result) { return result.state !== "fulfilled"; });
console.log("Settings saved with %s errors", fails.length);
if (fails.length) {
fails.forEach(function(result) {
console.error(result.reason);
});
- var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to save room settings', '', ErrorDialog, {
title: _t("Failed to save settings"),
description: fails.map(function(result) { return result.reason; }).join("\n"),
});
// still editing room settings
- }
- else {
+ } else {
this.setState({
- editingRoomSettings: false
+ editingRoomSettings: false,
});
}
}).finally(() => {
this.setState({
uploadingRoomSettings: false,
- editingRoomSettings: false
+ editingRoomSettings: false,
});
}).done();
},
@@ -1196,8 +1249,8 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
- var errCode = err.errcode || _t("unknown error code");
- var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const errCode = err.errcode || _t("unknown error code");
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, {
title: _t("Error"),
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
@@ -1206,20 +1259,20 @@ module.exports = React.createClass({
},
onRejectButtonClicked: function(ev) {
- var self = this;
+ const self = this;
this.setState({
- rejecting: true
+ rejecting: true,
});
MatrixClientPeg.get().leave(this.state.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
self.setState({
- rejecting: false
+ rejecting: false,
});
}, function(error) {
console.error("Failed to reject invite: %s", error);
- var msg = error.message ? error.message : JSON.stringify(error);
- var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const msg = error.message ? error.message : JSON.stringify(error);
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, {
title: _t("Failed to reject invite"),
description: msg,
@@ -1227,7 +1280,7 @@ module.exports = React.createClass({
self.setState({
rejecting: false,
- rejectError: error
+ rejectError: error,
});
});
},
@@ -1287,7 +1340,7 @@ module.exports = React.createClass({
// restored when we switch back to it.
//
_getScrollState: function() {
- var messagePanel = this.refs.messagePanel;
+ const messagePanel = this.refs.messagePanel;
if (!messagePanel) return null;
// if we're following the live timeline, we want to return null; that
@@ -1302,7 +1355,7 @@ module.exports = React.createClass({
return null;
}
- var scrollState = messagePanel.getScrollState();
+ const scrollState = messagePanel.getScrollState();
if (scrollState.stuckAtBottom) {
// we don't really expect to be in this state, but it will
@@ -1329,7 +1382,7 @@ module.exports = React.createClass({
// a maxHeight on the underlying remote video tag.
// header + footer + status + give us at least 120px of scrollback at all times.
- var auxPanelMaxHeight = window.innerHeight -
+ let auxPanelMaxHeight = window.innerHeight -
(83 + // height of RoomHeader
36 + // height of the status area
72 + // minimum height of the message compmoser
@@ -1348,26 +1401,26 @@ module.exports = React.createClass({
onFullscreenClick: function() {
dis.dispatch({
action: 'video_fullscreen',
- fullscreen: true
+ fullscreen: true,
}, true);
},
onMuteAudioClick: function() {
- var call = this._getCallForRoom();
+ const call = this._getCallForRoom();
if (!call) {
return;
}
- var newState = !call.isMicrophoneMuted();
+ const newState = !call.isMicrophoneMuted();
call.setMicrophoneMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons
},
onMuteVideoClick: function() {
- var call = this._getCallForRoom();
+ const call = this._getCallForRoom();
if (!call) {
return;
}
- var newState = !call.isLocalVideoMuted();
+ const newState = !call.isLocalVideoMuted();
call.setLocalVideoMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons
},
@@ -1403,7 +1456,7 @@ module.exports = React.createClass({
* We pass it down to the scroll panel.
*/
handleScrollKey: function(ev) {
- var panel;
+ let panel;
if(this.refs.searchResultsPanel) {
panel = this.refs.searchResultsPanel;
} else if(this.refs.messagePanel) {
@@ -1448,10 +1501,6 @@ module.exports = React.createClass({
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
- // Whether the preview bar spinner should be shown. We do this when joining or
- // when waiting for a room to be returned by js-sdk when joining
- const previewBarSpinner = this.state.joining || this.state.waitingForRoom;
-
if (!this.state.room) {
if (this.state.roomLoading || this.state.peekLoading) {
return (
@@ -1477,15 +1526,15 @@ module.exports = React.createClass({
-
);
} else {
- var inviteEvent = myMember.events.member;
+ const inviteEvent = myMember.events.member;
var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender();
// We deliberately don't try to peek into invites, even if we have permission to peek
@@ -1520,15 +1569,15 @@ module.exports = React.createClass({
-
@@ -1541,33 +1590,36 @@ module.exports = React.createClass({
// We have successfully loaded this room, and are not previewing.
// Display the "normal" room view.
- var call = this._getCallForRoom();
- var inCall = false;
+ const call = this._getCallForRoom();
+ let inCall = false;
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
inCall = true;
}
- var scrollheader_classes = classNames({
+ const scrollheader_classes = classNames({
mx_RoomView_scrollheader: true,
});
- var statusBar;
+ let statusBar;
let isStatusAreaExpanded = true;
if (ContentMessages.getCurrentUploads().length > 0) {
- var UploadBar = sdk.getComponent('structures.UploadBar');
+ const UploadBar = sdk.getComponent('structures.UploadBar');
statusBar = ;
} else if (!this.state.searchResults) {
- var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
+ const RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
isStatusAreaExpanded = this.state.statusBarVisible;
statusBar = ;
} else if (this.state.uploadingRoomSettings) {
- aux = ;
+ aux = ;
} else if (this.state.forwardingEvent !== null) {
aux = ;
} else if (this.state.searching) {
hideCancel = true; // has own cancel
- aux = ;
+ aux = ;
} else if (!myMember || myMember.membership !== "join") {
// 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.
@@ -1601,9 +1653,9 @@ module.exports = React.createClass({
hideCancel = true;
aux = (
);
- var messageComposer, searchInfo;
- var canSpeak = (
+ let messageComposer, searchInfo;
+ const canSpeak = (
// joined and not showing search results
myMember && (myMember.membership == 'join') && !this.state.searchResults
);
@@ -1637,8 +1689,8 @@ module.exports = React.createClass({
onResize={this.onChildResize}
uploadFile={this.uploadFile}
callState={this.state.callState}
- opacity={ this.props.opacity }
- showApps={ this.state.showApps }
+ opacity={this.props.opacity}
+ showApps={this.state.showApps}
/>;
}
@@ -1646,19 +1698,19 @@ module.exports = React.createClass({
// in this.state if this is what RoomHeader desires?
if (this.state.searchResults) {
searchInfo = {
- searchTerm : this.state.searchTerm,
- searchScope : this.state.searchScope,
- searchCount : this.state.searchResults.count,
+ searchTerm: this.state.searchTerm,
+ searchScope: this.state.searchScope,
+ searchCount: this.state.searchResults.count,
};
}
if (inCall) {
- var zoomButton, voiceMuteButton, videoMuteButton;
+ let zoomButton, voiceMuteButton, videoMuteButton;
if (call.type === "video") {
zoomButton = (
-
;
// wrap the existing status bar into a 'callStatusBar' which adds more knobs.
@@ -1683,25 +1735,25 @@ module.exports = React.createClass({
{ videoMuteButton }
{ zoomButton }
{ statusBar }
-
+
;
}
// if we have search results, we keep the messagepanel (so that it preserves its
// scroll state), but hide it.
- var searchResultsPanel;
- var hideMessagePanel = false;
+ let searchResultsPanel;
+ let hideMessagePanel = false;
if (this.state.searchResults) {
searchResultsPanel = (
- {this.getSearchResultTiles()}
+ { this.getSearchResultTiles() }
);
hideMessagePanel = true;
@@ -1716,26 +1768,26 @@ module.exports = React.createClass({
}
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
- var messagePanel = (
+ const messagePanel = (
);
- var topUnreadMessagesBar = null;
+ let topUnreadMessagesBar = null;
if (this.state.showTopUnreadMessagesBar) {
- var TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
+ const TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
topUnreadMessagesBar = (
+
` children to be sticky, namely DateSeparators.
- this.node = sn;
- // Update subscribers - arbitrarily nested `` children
- this.notifySubscribers(ev);
-
// Sometimes we see attempts to write to scrollTop essentially being
// ignored. (Or rather, it is successfully written, but on the next
// scroll event, it's been reset again).
@@ -159,28 +217,28 @@ export default class ScrollPanel extends StickyContainer {
this.props.onScroll(ev);
this.checkFillState();
- }
+ },
- onResize() {
+ onResize: function() {
this.props.onResize();
this.checkScroll();
this.refs.geminiPanel.forceUpdate();
- }
+ },
// after an update to the contents of the panel, check that the scroll is
// where it ought to be, and set off pagination requests if necessary.
- checkScroll() {
+ checkScroll: function() {
this._restoreSavedScrollState();
this.checkFillState();
- }
+ },
// return true if the content is fully scrolled down right now; else false.
//
// note that this is independent of the 'stuckAtBottom' state - it is simply
// about whether the the content is scrolled down right now, irrespective of
// whether it will stay that way when the children update.
- isAtBottom() {
- var sn = this._getScrollNode();
+ isAtBottom: function() {
+ const sn = this._getScrollNode();
// there seems to be some bug with flexbox/gemini/chrome/richvdh's
// understanding of the box model, wherein the scrollNode ends up 2
@@ -189,7 +247,7 @@ export default class ScrollPanel extends StickyContainer {
// that we're at the bottom when we're still a few pixels off.
return sn.scrollHeight - Math.ceil(sn.scrollTop) <= sn.clientHeight + 3;
- }
+ },
// returns the vertical height in the given direction that can be removed from
// the content box (which has a height of scrollHeight, see checkFillState) without
@@ -222,22 +280,22 @@ export default class ScrollPanel extends StickyContainer {
// |#########| - |
// |#########| |
// `---------' -
- _getExcessHeight(backwards) {
- var sn = this._getScrollNode();
+ _getExcessHeight: function(backwards) {
+ const sn = this._getScrollNode();
if (backwards) {
return sn.scrollTop - sn.clientHeight - UNPAGINATION_PADDING;
} else {
return sn.scrollHeight - (sn.scrollTop + 2*sn.clientHeight) - UNPAGINATION_PADDING;
}
- }
+ },
// check the scroll state and send out backfill requests if necessary.
- checkFillState() {
+ checkFillState: function() {
if (this.unmounted) {
return;
}
- var sn = this._getScrollNode();
+ const sn = this._getScrollNode();
// if there is less than a screenful of messages above or below the
// viewport, try to get some more messages.
@@ -271,10 +329,10 @@ export default class ScrollPanel extends StickyContainer {
// need to forward-fill
this._maybeFill(false);
}
- }
+ },
// check if unfilling is possible and send an unfill request if necessary
- _checkUnfillState(backwards) {
+ _checkUnfillState: function(backwards) {
let excessHeight = this._getExcessHeight(backwards);
if (excessHeight <= 0) {
return;
@@ -315,11 +373,11 @@ export default class ScrollPanel extends StickyContainer {
this.props.onUnfillRequest(backwards, markerScrollToken);
}, UNFILL_REQUEST_DEBOUNCE_MS);
}
- }
+ },
// check if there is already a pending fill request. If not, set one off.
- _maybeFill(backwards) {
- var dir = backwards ? 'b' : 'f';
+ _maybeFill: function(backwards) {
+ const dir = backwards ? 'b' : 'f';
if (this._pendingFillRequests[dir]) {
debuglog("ScrollPanel: Already a "+dir+" fill in progress - not starting another");
return;
@@ -350,7 +408,7 @@ export default class ScrollPanel extends StickyContainer {
this.checkFillState();
}
}).done();
- }
+ },
/* get the current scroll state. This returns an object with the following
* properties:
@@ -366,9 +424,9 @@ export default class ScrollPanel extends StickyContainer {
* the number of pixels the bottom of the tracked child is above the
* bottom of the scroll panel.
*/
- getScrollState() {
+ getScrollState: function() {
return this.scrollState;
- }
+ },
/* reset the saved scroll state.
*
@@ -382,46 +440,46 @@ export default class ScrollPanel extends StickyContainer {
* no use if no children exist yet, or if you are about to replace the
* child list.)
*/
- resetScrollState() {
+ resetScrollState: function() {
this.scrollState = {stuckAtBottom: this.props.startAtBottom};
- }
+ },
/**
* jump to the top of the content.
*/
- scrollToTop() {
+ scrollToTop: function() {
this._setScrollTop(0);
this._saveScrollState();
- }
+ },
/**
* jump to the bottom of the content.
*/
- scrollToBottom() {
+ scrollToBottom: function() {
// the easiest way to make sure that the scroll state is correctly
// saved is to do the scroll, then save the updated state. (Calculating
// it ourselves is hard, and we can't rely on an onScroll callback
// happening, since there may be no user-visible change here).
this._setScrollTop(Number.MAX_VALUE);
this._saveScrollState();
- }
+ },
/**
* Page up/down.
*
* mult: -1 to page up, +1 to page down
*/
- scrollRelative(mult) {
- var scrollNode = this._getScrollNode();
- var delta = mult * scrollNode.clientHeight * 0.5;
+ scrollRelative: function(mult) {
+ const scrollNode = this._getScrollNode();
+ const delta = mult * scrollNode.clientHeight * 0.5;
this._setScrollTop(scrollNode.scrollTop + delta);
this._saveScrollState();
- }
+ },
/**
* Scroll up/down in response to a scroll key
*/
- handleScrollKey(ev) {
+ handleScrollKey: function(ev) {
switch (ev.keyCode) {
case KeyCode.PAGE_UP:
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
@@ -447,7 +505,7 @@ export default class ScrollPanel extends StickyContainer {
}
break;
}
- }
+ },
/* Scroll the panel to bring the DOM node with the scroll token
* `scrollToken` into view.
@@ -460,7 +518,7 @@ export default class ScrollPanel extends StickyContainer {
* node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0.
*/
- scrollToToken(scrollToken, pixelOffset, offsetBase) {
+ scrollToToken: function(scrollToken, pixelOffset, offsetBase) {
pixelOffset = pixelOffset || 0;
offsetBase = offsetBase || 0;
@@ -477,21 +535,21 @@ export default class ScrollPanel extends StickyContainer {
this.scrollState = {
stuckAtBottom: false,
trackedScrollToken: scrollToken,
- pixelOffset: pixelOffset
+ pixelOffset: pixelOffset,
};
// ... then make it so.
this._restoreSavedScrollState();
- }
+ },
// set the scrollTop attribute appropriately to position the given child at the
// given offset in the window. A helper for _restoreSavedScrollState.
- _scrollToToken(scrollToken, pixelOffset) {
+ _scrollToToken: function(scrollToken, pixelOffset) {
/* find the dom node with the right scrolltoken */
- var node;
- var messages = this.refs.itemlist.children;
- for (var i = messages.length-1; i >= 0; --i) {
- var m = messages[i];
+ let node;
+ const messages = this.refs.itemlist.children;
+ for (let i = messages.length-1; i >= 0; --i) {
+ const m = messages[i];
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
// There might only be one scroll token
if (m.dataset.scrollTokens &&
@@ -506,10 +564,10 @@ export default class ScrollPanel extends StickyContainer {
return;
}
- var scrollNode = this._getScrollNode();
- var wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
- var boundingRect = node.getBoundingClientRect();
- var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
+ const scrollNode = this._getScrollNode();
+ const wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
+ const boundingRect = node.getBoundingClientRect();
+ const scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
pixelOffset + " (delta: "+scrollDelta+")");
@@ -517,26 +575,25 @@ export default class ScrollPanel extends StickyContainer {
if(scrollDelta != 0) {
this._setScrollTop(scrollNode.scrollTop + scrollDelta);
}
+ },
- }
-
- _saveScrollState() {
+ _saveScrollState: function() {
if (this.props.stickyBottom && this.isAtBottom()) {
this.scrollState = { stuckAtBottom: true };
debuglog("ScrollPanel: Saved scroll state", this.scrollState);
return;
}
- var itemlist = this.refs.itemlist;
- var wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
- var messages = itemlist.children;
+ const itemlist = this.refs.itemlist;
+ const wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
+ const messages = itemlist.children;
let newScrollState = null;
- for (var i = messages.length-1; i >= 0; --i) {
- var node = messages[i];
+ for (let i = messages.length-1; i >= 0; --i) {
+ const node = messages[i];
if (!node.dataset.scrollTokens) continue;
- var boundingRect = node.getBoundingClientRect();
+ const boundingRect = node.getBoundingClientRect();
newScrollState = {
stuckAtBottom: false,
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
@@ -558,11 +615,11 @@ export default class ScrollPanel extends StickyContainer {
} else {
debuglog("ScrollPanel: unable to save scroll state: found no children in the viewport");
}
- }
+ },
- _restoreSavedScrollState() {
- var scrollState = this.scrollState;
- var scrollNode = this._getScrollNode();
+ _restoreSavedScrollState: function() {
+ const scrollState = this.scrollState;
+ const scrollNode = this._getScrollNode();
if (scrollState.stuckAtBottom) {
this._setScrollTop(Number.MAX_VALUE);
@@ -570,12 +627,12 @@ export default class ScrollPanel extends StickyContainer {
this._scrollToToken(scrollState.trackedScrollToken,
scrollState.pixelOffset);
}
- }
+ },
- _setScrollTop(scrollTop) {
- var scrollNode = this._getScrollNode();
+ _setScrollTop: function(scrollTop) {
+ const scrollNode = this._getScrollNode();
- var prevScroll = scrollNode.scrollTop;
+ const prevScroll = scrollNode.scrollTop;
// FF ignores attempts to set scrollTop to very large numbers
scrollNode.scrollTop = Math.min(scrollTop, scrollNode.scrollHeight);
@@ -594,12 +651,12 @@ export default class ScrollPanel extends StickyContainer {
debuglog("ScrollPanel: set scrollTop:", scrollNode.scrollTop,
"requested:", scrollTop,
"_lastSetScroll:", this._lastSetScroll);
- }
+ },
/* get the DOM node which has the scrollTop property we care about for our
* message panel.
*/
- _getScrollNode() {
+ _getScrollNode: function() {
if (this.unmounted) {
// this shouldn't happen, but when it does, turn the NPE into
// something more meaningful.
@@ -607,91 +664,21 @@ export default class ScrollPanel extends StickyContainer {
}
return this.refs.geminiPanel.scrollbar.getViewElement();
- }
+ },
- render() {
+ render: function() {
// TODO: the classnames on the div and ol could do with being updated to
// reflect the fact that we don't necessarily contain a list of messages.
// it's not obvious why we have a separate div and ol anyway.
- return (
-
-
-
- {this.props.children}
-
-
-
- );
- }
-}
-
-ScrollPanel.propTypes = {
- /* stickyBottom: if set to true, then once the user hits the bottom of
- * the list, any new children added to the list will cause the list to
- * scroll down to show the new element, rather than preserving the
- * existing view.
- */
- stickyBottom: React.PropTypes.bool,
-
- /* startAtBottom: if set to true, the view is assumed to start
- * scrolled to the bottom.
- * XXX: It's likley this is unecessary and can be derived from
- * stickyBottom, but I'm adding an extra parameter to ensure
- * behaviour stays the same for other uses of ScrollPanel.
- * If so, let's remove this parameter down the line.
- */
- startAtBottom: React.PropTypes.bool,
-
- /* onFillRequest(backwards): a callback which is called on scroll when
- * the user nears the start (backwards = true) or end (backwards =
- * false) of the list.
- *
- * This should return a promise; no more calls will be made until the
- * promise completes.
- *
- * The promise should resolve to true if there is more data to be
- * retrieved in this direction (in which case onFillRequest may be
- * called again immediately), or false if there is no more data in this
- * directon (at this time) - which will stop the pagination cycle until
- * the user scrolls again.
- */
- onFillRequest: React.PropTypes.func,
-
- /* onUnfillRequest(backwards): a callback which is called on scroll when
- * there are children elements that are far out of view and could be removed
- * without causing pagination to occur.
- *
- * This function should accept a boolean, which is true to indicate the back/top
- * of the panel and false otherwise, and a scroll token, which refers to the
- * first element to remove if removing from the front/bottom, and last element
- * to remove if removing from the back/top.
- */
- onUnfillRequest: React.PropTypes.func,
-
- /* onScroll: a callback which is called whenever any scroll happens.
- */
- onScroll: React.PropTypes.func,
-
- /* onResize: a callback which is called whenever the Gemini scroll
- * panel is resized
- */
- onResize: React.PropTypes.func,
-
- /* className: classnames to add to the top-level div
- */
- className: React.PropTypes.string,
-
- /* style: styles to add to the top-level div
- */
- style: React.PropTypes.object,
-};
-
-ScrollPanel.defaultProps = {
- stickyBottom: true,
- startAtBottom: true,
- onFillRequest: function(backwards) { return Promise.resolve(false); },
- onUnfillRequest: function(backwards, scrollToken) {},
- onScroll: function() {},
-};
+ return (
+
+
+ { this.props.children }
+
+
+
+ );
+ },
+});
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index ebd9784b6f..e3b3b66f97 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -15,27 +15,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-var React = require('react');
-var ReactDOM = require("react-dom");
+const React = require('react');
+const ReactDOM = require("react-dom");
import Promise from 'bluebird';
-var Matrix = require("matrix-js-sdk");
-var EventTimeline = Matrix.EventTimeline;
+const Matrix = require("matrix-js-sdk");
+const EventTimeline = Matrix.EventTimeline;
-var sdk = require('../../index');
+const sdk = require('../../index');
import { _t } from '../../languageHandler';
-var MatrixClientPeg = require("../../MatrixClientPeg");
-var dis = require("../../dispatcher");
-var ObjectUtils = require('../../ObjectUtils');
-var Modal = require("../../Modal");
-var UserActivity = require("../../UserActivity");
-var KeyCode = require('../../KeyCode');
+const MatrixClientPeg = require("../../MatrixClientPeg");
+const dis = require("../../dispatcher");
+const ObjectUtils = require('../../ObjectUtils');
+const Modal = require("../../Modal");
+const UserActivity = require("../../UserActivity");
+const KeyCode = require('../../KeyCode');
import UserSettingsStore from '../../UserSettingsStore';
-var PAGINATE_SIZE = 20;
-var INITIAL_SIZE = 20;
+const PAGINATE_SIZE = 20;
+const INITIAL_SIZE = 20;
-var DEBUG = false;
+const DEBUG = false;
if (DEBUG) {
// using bind means that we get to keep useful line numbers in the console
@@ -260,7 +260,7 @@ var TimelinePanel = React.createClass({
dis.unregister(this.dispatcherRef);
- var client = MatrixClientPeg.get();
+ const client = MatrixClientPeg.get();
if (client) {
client.removeListener("Room.timeline", this.onRoomTimeline);
client.removeListener("Room.timelineReset", this.onRoomTimelineReset);
@@ -275,20 +275,20 @@ var TimelinePanel = React.createClass({
onMessageListUnfillRequest: function(backwards, scrollToken) {
// If backwards, unpaginate from the back (i.e. the start of the timeline)
- let dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
+ const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
debuglog("TimelinePanel: unpaginating events in direction", dir);
// All tiles are inserted by MessagePanel to have a scrollToken === eventId, and
// this particular event should be the first or last to be unpaginated.
- let eventId = scrollToken;
+ const eventId = scrollToken;
- let marker = this.state.events.findIndex(
+ const marker = this.state.events.findIndex(
(ev) => {
return ev.getId() === eventId;
- }
+ },
);
- let count = backwards ? marker + 1 : this.state.events.length - marker;
+ const count = backwards ? marker + 1 : this.state.events.length - marker;
if (count > 0) {
debuglog("TimelinePanel: Unpaginating", count, "in direction", dir);
@@ -305,9 +305,9 @@ var TimelinePanel = React.createClass({
// set off a pagination request.
onMessageListFillRequest: function(backwards) {
- var dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
- var canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
- var paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
+ const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
+ const canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
+ const paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
if (!this.state[canPaginateKey]) {
debuglog("TimelinePanel: have given up", dir, "paginating this timeline");
@@ -328,7 +328,7 @@ var TimelinePanel = React.createClass({
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
- var newState = {
+ const newState = {
[paginatingKey]: false,
[canPaginateKey]: r,
events: this._getEvents(),
@@ -336,8 +336,8 @@ var TimelinePanel = React.createClass({
// moving the window in this direction may mean that we can now
// paginate in the other where we previously could not.
- var otherDirection = backwards ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;
- var canPaginateOtherWayKey = backwards ? 'canForwardPaginate' : 'canBackPaginate';
+ const otherDirection = backwards ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;
+ const canPaginateOtherWayKey = backwards ? 'canForwardPaginate' : 'canBackPaginate';
if (!this.state[canPaginateOtherWayKey] &&
this._timelineWindow.canPaginate(otherDirection)) {
debuglog('TimelinePanel: can now', otherDirection, 'paginate again');
@@ -384,6 +384,9 @@ var TimelinePanel = React.createClass({
this.sendReadReceipt();
this.updateReadMarker();
break;
+ case 'ignore_state_changed':
+ this.forceUpdate();
+ break;
}
},
@@ -417,15 +420,15 @@ var TimelinePanel = React.createClass({
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
if (this.unmounted) { return; }
- var events = this._timelineWindow.getEvents();
- var lastEv = events[events.length-1];
+ const events = this._timelineWindow.getEvents();
+ const lastEv = events[events.length-1];
// if we're at the end of the live timeline, append the pending events
if (this.props.timelineSet.room && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
events.push(...this.props.timelineSet.room.getPendingEvents());
}
- var updatedState = {events: events};
+ const updatedState = {events: events};
if (this.props.manageReadMarkers) {
// when a new event arrives when the user is not watching the
@@ -436,8 +439,8 @@ var TimelinePanel = React.createClass({
// read-marker when a remote echo of an event we have just sent takes
// more than the timeout on userCurrentlyActive.
//
- var myUserId = MatrixClientPeg.get().credentials.userId;
- var sender = ev.sender ? ev.sender.userId : null;
+ const myUserId = MatrixClientPeg.get().credentials.userId;
+ const sender = ev.sender ? ev.sender.userId : null;
var callback = null;
if (sender != myUserId && !UserActivity.userCurrentlyActive()) {
updatedState.readMarkerVisible = true;
@@ -643,7 +646,7 @@ var TimelinePanel = React.createClass({
// and we'll get confused when their ID changes and we can't figure out
// where the RM is pointing to. The read marker will be invisible for
// now anyway, so this doesn't really matter.
- var lastDisplayedIndex = this._getLastDisplayedEventIndex({
+ const lastDisplayedIndex = this._getLastDisplayedEventIndex({
allowPartial: true,
ignoreEchoes: true,
});
@@ -652,7 +655,7 @@ var TimelinePanel = React.createClass({
return;
}
- var lastDisplayedEvent = this.state.events[lastDisplayedIndex];
+ const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
this._setReadMarker(lastDisplayedEvent.getId(),
lastDisplayedEvent.getTs());
@@ -673,7 +676,7 @@ var TimelinePanel = React.createClass({
// we call _timelineWindow.getEvents() rather than using
// this.state.events, because react batches the update to the latter, so it
// may not have been updated yet.
- var events = this._timelineWindow.getEvents();
+ const events = this._timelineWindow.getEvents();
// first find where the current RM is
for (var i = 0; i < events.length; i++) {
@@ -686,7 +689,7 @@ var TimelinePanel = React.createClass({
}
// now think about advancing it
- var myUserId = MatrixClientPeg.get().credentials.userId;
+ const myUserId = MatrixClientPeg.get().credentials.userId;
for (i++; i < events.length; i++) {
var ev = events[i];
if (!ev.sender || ev.sender.userId != myUserId) {
@@ -731,7 +734,7 @@ var TimelinePanel = React.createClass({
//
// a quick way to figure out if we've loaded the relevant event is
// simply to check if the messagepanel knows where the read-marker is.
- var ret = this.refs.messagePanel.getReadMarkerPosition();
+ const ret = this.refs.messagePanel.getReadMarkerPosition();
if (ret !== null) {
// The messagepanel knows where the RM is, so we must have loaded
// the relevant event.
@@ -752,13 +755,13 @@ var TimelinePanel = React.createClass({
forgetReadMarker: function() {
if (!this.props.manageReadMarkers) return;
- var rmId = this._getCurrentReadReceipt();
+ const rmId = this._getCurrentReadReceipt();
// see if we know the timestamp for the rr event
- var tl = this.props.timelineSet.getTimelineForEvent(rmId);
- var rmTs;
+ const tl = this.props.timelineSet.getTimelineForEvent(rmId);
+ let rmTs;
if (tl) {
- var event = tl.getEvents().find((e) => { return e.getId() == rmId; });
+ const event = tl.getEvents().find((e) => { return e.getId() == rmId; });
if (event) {
rmTs = event.getTs();
}
@@ -798,7 +801,7 @@ var TimelinePanel = React.createClass({
if (!this.props.manageReadMarkers) return null;
if (!this.refs.messagePanel) return null;
- var ret = this.refs.messagePanel.getReadMarkerPosition();
+ const ret = this.refs.messagePanel.getReadMarkerPosition();
if (ret !== null) {
return ret;
}
@@ -841,8 +844,7 @@ var TimelinePanel = React.createClass({
// jump to the live timeline on ctrl-end, rather than the end of the
// timeline window.
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey &&
- ev.keyCode == KeyCode.END)
- {
+ ev.keyCode == KeyCode.END) {
this.jumpToLiveTimeline();
} else {
this.refs.messagePanel.handleScrollKey(ev);
@@ -850,12 +852,12 @@ var TimelinePanel = React.createClass({
},
_initTimeline: function(props) {
- var initialEvent = props.eventId;
- var pixelOffset = props.eventPixelOffset;
+ const initialEvent = props.eventId;
+ const pixelOffset = props.eventPixelOffset;
// if a pixelOffset is given, it is relative to the bottom of the
// container. If not, put the event in the middle of the container.
- var offsetBase = 1;
+ let offsetBase = 1;
if (pixelOffset == null) {
offsetBase = 0.5;
}
@@ -884,7 +886,7 @@ var TimelinePanel = React.createClass({
MatrixClientPeg.get(), this.props.timelineSet,
{windowLimit: this.props.timelineCap});
- var onLoaded = () => {
+ const onLoaded = () => {
this._reloadEvents();
// If we switched away from the room while there were pending
@@ -919,15 +921,15 @@ var TimelinePanel = React.createClass({
});
};
- var onError = (error) => {
+ const onError = (error) => {
this.setState({timelineLoading: false});
console.error(
`Error loading timeline panel at ${eventId}: ${error}`,
);
- var msg = error.message ? error.message : JSON.stringify(error);
- var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ const msg = error.message ? error.message : JSON.stringify(error);
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- var onFinished;
+ let onFinished;
// if we were given an event ID, then when the user closes the
// dialog, let's jump to the end of the timeline. If we weren't,
@@ -942,7 +944,7 @@ var TimelinePanel = React.createClass({
});
};
}
- var message = (error.errcode == 'M_FORBIDDEN')
+ const message = (error.errcode == 'M_FORBIDDEN')
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.")
: _t("Tried to load a specific point in this room's timeline, but was unable to find it.");
Modal.createTrackedDialog('Failed to load timeline position', '', ErrorDialog, {
@@ -952,7 +954,7 @@ var TimelinePanel = React.createClass({
});
};
- var prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
+ let prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
// if we already have the event in question, TimelineWindow.load
// returns a resolved promise.
@@ -993,7 +995,7 @@ var TimelinePanel = React.createClass({
// get the list of events from the timeline window and the pending event list
_getEvents: function() {
- var events = this._timelineWindow.getEvents();
+ const events = this._timelineWindow.getEvents();
// if we're at the end of the live timeline, append the pending events
if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
@@ -1004,7 +1006,7 @@ var TimelinePanel = React.createClass({
},
_indexForEventId: function(evId) {
- for (var i = 0; i < this.state.events.length; ++i) {
+ for (let i = 0; i < this.state.events.length; ++i) {
if (evId == this.state.events[i].getId()) {
return i;
}
@@ -1014,18 +1016,18 @@ var TimelinePanel = React.createClass({
_getLastDisplayedEventIndex: function(opts) {
opts = opts || {};
- var ignoreOwn = opts.ignoreOwn || false;
- var ignoreEchoes = opts.ignoreEchoes || false;
- var allowPartial = opts.allowPartial || false;
+ const ignoreOwn = opts.ignoreOwn || false;
+ const ignoreEchoes = opts.ignoreEchoes || false;
+ const allowPartial = opts.allowPartial || false;
- var messagePanel = this.refs.messagePanel;
+ const messagePanel = this.refs.messagePanel;
if (messagePanel === undefined) return null;
- var wrapperRect = ReactDOM.findDOMNode(messagePanel).getBoundingClientRect();
- var myUserId = MatrixClientPeg.get().credentials.userId;
+ const wrapperRect = ReactDOM.findDOMNode(messagePanel).getBoundingClientRect();
+ const myUserId = MatrixClientPeg.get().credentials.userId;
- for (var i = this.state.events.length-1; i >= 0; --i) {
- var ev = this.state.events[i];
+ for (let i = this.state.events.length-1; i >= 0; --i) {
+ const ev = this.state.events[i];
if (ignoreOwn && ev.sender && ev.sender.userId == myUserId) {
continue;
@@ -1036,10 +1038,10 @@ var TimelinePanel = React.createClass({
continue;
}
- var node = messagePanel.getNodeForEventId(ev.getId());
+ const node = messagePanel.getNodeForEventId(ev.getId());
if (!node) continue;
- var boundingRect = node.getBoundingClientRect();
+ const boundingRect = node.getBoundingClientRect();
if ((allowPartial && boundingRect.top < wrapperRect.bottom) ||
(!allowPartial && boundingRect.bottom < wrapperRect.bottom)) {
return i;
@@ -1057,18 +1059,18 @@ var TimelinePanel = React.createClass({
* SDK.
*/
_getCurrentReadReceipt: function(ignoreSynthesized) {
- var client = MatrixClientPeg.get();
+ const client = MatrixClientPeg.get();
// the client can be null on logout
if (client == null) {
return null;
}
- var myUserId = client.credentials.userId;
+ const myUserId = client.credentials.userId;
return this.props.timelineSet.room.getEventReadUpTo(myUserId, ignoreSynthesized);
},
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
- var roomId = this.props.timelineSet.room.roomId;
+ const roomId = this.props.timelineSet.room.roomId;
// don't update the state (and cause a re-render) if there is
// no change to the RM.
@@ -1093,8 +1095,8 @@ var TimelinePanel = React.createClass({
},
render: function() {
- var MessagePanel = sdk.getComponent("structures.MessagePanel");
- var Loader = sdk.getComponent("elements.Spinner");
+ const MessagePanel = sdk.getComponent("structures.MessagePanel");
+ const Loader = sdk.getComponent("elements.Spinner");
// just show a spinner while the timeline loads.
//
@@ -1109,7 +1111,7 @@ var TimelinePanel = React.createClass({
// exist.
if (this.state.timelineLoading) {
return (
-