/* Copyright 2016 OpenMarket Ltd 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. */ var React = require('react'); var sdk = require('../../index'); /* stateless UI component which builds the event tiles in the room timeline. */ module.exports = React.createClass({ displayName: 'MessagePanel', propTypes: { // true to give the component a 'display: none' style. hidden: React.PropTypes.bool, // the list of MatrixEvents to display events: React.PropTypes.array.isRequired, // ID of an event to highlight. If undefined, no event will be highlighted. highlightedEventId: React.PropTypes.string, // event after which we should show a read marker readMarkerEventId: React.PropTypes.string, // event after which we should show an animating disappearance of a // read marker readMarkerGhostEventId: React.PropTypes.string, // the userid of our user. This is used to suppress the read marker // for pending messages. ourUserId: React.PropTypes.string, // true to suppress the date at the start of the timeline suppressFirstDateSeparator: React.PropTypes.bool, // true if updates to the event list should cause the scroll panel to // scroll down when we are at the bottom of the window. See ScrollPanel // for more details. stickyBottom: React.PropTypes.bool, // callback to determine if a user is the magic freeswitch conference // user. Takes one parameter, which is a user id. Should return true if // the user is the conference user. isConferenceUser: React.PropTypes.func, // callback which is called when the panel is scrolled. onScroll: React.PropTypes.func, // callback which is called when more content is needed. onFillRequest: React.PropTypes.func, }, /* get the DOM node representing the given event */ getNodeForEventId: function(eventId) { if (!this.eventNodes) { return undefined; } return this.eventNodes[eventId]; }, /* return true if the content is fully scrolled down right now; else false. */ isAtBottom: function() { return this.refs.scrollPanel && this.refs.scrollPanel.isAtBottom(); }, /* get the current scroll state. See ScrollPanel.getScrollState for * details. * * returns null if we are not mounted. */ getScrollState: function() { if (!this.refs.scrollPanel) { return null; } return this.refs.scrollPanel.getScrollState(); }, /* jump to the bottom of the content. */ scrollToBottom: function() { if (this.refs.scrollPanel) { this.refs.scrollPanel.scrollToBottom(); } }, /* jump to the given event id. * * pixelOffset gives the number of pixels between the bottom of the node * and the bottom of the container. If undefined, it will put the node * in the middle of the container. */ scrollToEvent: function(eventId, pixelOffset) { if (this.refs.scrollPanel) { this.refs.scrollPanel.scrollToToken(eventId, pixelOffset); } }, /* check the scroll state and send out pagination requests if necessary. */ checkFillState: function() { if (this.refs.scrollPanel) { this.refs.scrollPanel.checkFillState(); } }, _getEventTiles: function() { var DateSeparator = sdk.getComponent('messages.DateSeparator'); var EventTile = sdk.getComponent('rooms.EventTile'); var ret = []; var prevEvent = null; // the last event we showed var ghostIndex; var readMarkerIndex; for (var i = 0; i < this.props.events.length; i++) { var mxEv = this.props.events[i]; if (!EventTile.haveTileForEvent(mxEv)) { continue; } if (this.props.isConferenceUser && mxEv.getType() === "m.room.member") { if (this.props.isConferenceUser(mxEv.getSender()) || this.props.isConferenceUser(mxEv.getStateKey())) { continue; // suppress conf user join/parts } } // now we've decided whether or not to show this message, // add the read up to marker if appropriate // doing this here means we implicitly do not show the marker // if it's at the bottom // NB. it would be better to decide where the read marker was going // when the state changed rather than here in the render method, but // this is where we decide what messages we show so it's the only // place we know whether we're at the bottom or not. var mxEvSender = mxEv.sender ? mxEv.sender.userId : null; if (prevEvent && prevEvent.getId() == this.props.readMarkerEventId) { // suppress the read marker if the next event is sent by us; this // is a nonsensical and temporary situation caused by the delay between // us sending a message and receiving the synthesized receipt. if (mxEvSender != this.props.ourUserId) { var hr; hr = (