mirror of
https://github.com/element-hq/element-web
synced 2024-11-28 20:38:55 +03:00
Merge branch 'develop' into rav/new_search_api
Conflicts: src/components/structures/RoomView.js
This commit is contained in:
commit
583d35e39f
16 changed files with 215 additions and 81 deletions
|
@ -35,10 +35,10 @@ module.exports = {
|
|||
return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
else if (now.getFullYear() === date.getFullYear()) {
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
else {
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ class Highlighter {
|
|||
// HTML and plain-text highlighting.
|
||||
|
||||
var safeHighlight = this.html ? sanitizeHtml(highlights[0], sanitizeHtmlParams) : highlights[0];
|
||||
while ((offset = safeSnippet.indexOf(safeHighlight, lastOffset)) >= 0) {
|
||||
while ((offset = safeSnippet.toLowerCase().indexOf(safeHighlight.toLowerCase(), lastOffset)) >= 0) {
|
||||
// handle preamble
|
||||
if (offset > lastOffset) {
|
||||
var subSnippet = safeSnippet.substring(lastOffset, offset);
|
||||
|
@ -150,7 +150,7 @@ module.exports = {
|
|||
|
||||
var body;
|
||||
if (highlights && highlights.length > 0) {
|
||||
var highlighter = new Highlighter(isHtml, "mx_MessageTile_searchHighlight", opts.onHighlightClick);
|
||||
var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick);
|
||||
body = highlighter.applyHighlights(safeBody, highlights);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -99,19 +99,24 @@ var commands = {
|
|||
}
|
||||
|
||||
// Try to find a room with this alias
|
||||
// XXX: do we need to do this? Doesn't the JS SDK suppress duplicate attempts to join the same room?
|
||||
var foundRoom = MatrixTools.getRoomForAlias(
|
||||
MatrixClientPeg.get().getRooms(),
|
||||
room_alias
|
||||
);
|
||||
if (foundRoom) { // we've already joined this room, view it.
|
||||
|
||||
if (foundRoom) { // we've already joined this room, view it if it's not archived.
|
||||
var me = foundRoom.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
if (me && me.membership !== "leave") {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: foundRoom.roomId
|
||||
});
|
||||
return success();
|
||||
}
|
||||
else {
|
||||
// attempt to join this alias.
|
||||
}
|
||||
|
||||
// otherwise attempt to join this alias.
|
||||
return success(
|
||||
MatrixClientPeg.get().joinRoom(room_alias).then(
|
||||
function(room) {
|
||||
|
@ -123,7 +128,6 @@ var commands = {
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reject("Usage: /join <room_alias>");
|
||||
},
|
||||
|
||||
|
|
|
@ -251,13 +251,15 @@ module.exports = React.createClass({
|
|||
var UserSelector = sdk.getComponent("elements.UserSelector");
|
||||
var RoomHeader = sdk.getComponent("rooms.RoomHeader");
|
||||
|
||||
var domain = MatrixClientPeg.get().credentials.userId.replace(/^.*:/, '');
|
||||
|
||||
return (
|
||||
<div className="mx_CreateRoom">
|
||||
<RoomHeader simpleHeader="Create room" />
|
||||
<div className="mx_CreateRoom_body">
|
||||
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder="Name"/> <br />
|
||||
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder="Topic"/> <br />
|
||||
<RoomAlias ref="alias" alias={this.state.alias} onChange={this.onAliasChanged}/> <br />
|
||||
<RoomAlias ref="alias" alias={this.state.alias} homeserver={ domain } onChange={this.onAliasChanged}/> <br />
|
||||
<UserSelector ref="user_selector" selected_users={this.state.invited_users} onChange={this.onInviteChanged}/> <br />
|
||||
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
|
||||
<div>
|
||||
|
|
|
@ -43,6 +43,13 @@ var INITIAL_SIZE = 20;
|
|||
|
||||
var DEBUG_SCROLL = false;
|
||||
|
||||
if (DEBUG_SCROLL) {
|
||||
// using bind means that we get to keep useful line numbers in the console
|
||||
var debuglog = console.log.bind(console);
|
||||
} else {
|
||||
var debuglog = function () {};
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomView',
|
||||
propTypes: {
|
||||
|
@ -330,8 +337,6 @@ module.exports = React.createClass({
|
|||
|
||||
this.scrollToBottom();
|
||||
this.sendReadReceipt();
|
||||
|
||||
this.refs.messagePanel.checkFillState();
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
|
@ -346,50 +351,47 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_paginateCompleted: function() {
|
||||
if (DEBUG_SCROLL) console.log("paginate complete");
|
||||
debuglog("paginate complete");
|
||||
|
||||
this.setState({
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
||||
});
|
||||
|
||||
this.setState({paginating: false});
|
||||
|
||||
// we might not have got enough (or, indeed, any) results from the
|
||||
// pagination request, so give the messagePanel a chance to set off
|
||||
// another.
|
||||
|
||||
this.refs.messagePanel.checkFillState();
|
||||
},
|
||||
|
||||
onSearchResultsFillRequest: function(backwards) {
|
||||
if (!backwards || this.state.searchInProgress)
|
||||
return;
|
||||
if (!backwards)
|
||||
return q(false);
|
||||
|
||||
if (this.state.searchResults.next_batch) {
|
||||
if (DEBUG_SCROLL) console.log("requesting more search results");
|
||||
this._backPaginateSearch();
|
||||
debuglog("requesting more search results");
|
||||
return this._backPaginateSearch();
|
||||
} else {
|
||||
if (DEBUG_SCROLL) console.log("no more search results");
|
||||
debuglog("no more search results");
|
||||
return q(false);
|
||||
}
|
||||
},
|
||||
|
||||
// set off a pagination request.
|
||||
onMessageListFillRequest: function(backwards) {
|
||||
if (!backwards || this.state.paginating)
|
||||
return;
|
||||
if (!backwards)
|
||||
return q(false);
|
||||
|
||||
// Either wind back the message cap (if there are enough events in the
|
||||
// timeline to do so), or fire off a pagination request.
|
||||
|
||||
if (this.state.messageCap < this.state.room.timeline.length) {
|
||||
var cap = Math.min(this.state.messageCap + PAGINATE_SIZE, this.state.room.timeline.length);
|
||||
if (DEBUG_SCROLL) console.log("winding back message cap to", cap);
|
||||
debuglog("winding back message cap to", cap);
|
||||
this.setState({messageCap: cap});
|
||||
return q(true);
|
||||
} else if(this.state.room.oldState.paginationToken) {
|
||||
var cap = this.state.messageCap + PAGINATE_SIZE;
|
||||
if (DEBUG_SCROLL) console.log("starting paginate to cap", cap);
|
||||
debuglog("starting paginate to cap", cap);
|
||||
this.setState({messageCap: cap, paginating: true});
|
||||
MatrixClientPeg.get().scrollback(this.state.room, PAGINATE_SIZE).finally(this._paginateCompleted).done();
|
||||
return MatrixClientPeg.get().scrollback(this.state.room, PAGINATE_SIZE).
|
||||
finally(this._paginateCompleted).then(true);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -523,7 +525,7 @@ module.exports = React.createClass({
|
|||
|
||||
var searchPromise = MatrixClientPeg.get().backPaginateRoomEventsSearch(
|
||||
this.state.searchResults);
|
||||
this._handleSearchResult(searchPromise).done();
|
||||
return this._handleSearchResult(searchPromise);
|
||||
},
|
||||
|
||||
_handleSearchResult: function(searchPromise) {
|
||||
|
@ -538,7 +540,7 @@ module.exports = React.createClass({
|
|||
});
|
||||
|
||||
return searchPromise.then(function(results) {
|
||||
if (DEBUG_SCROLL) console.log("search complete");
|
||||
debuglog("search complete");
|
||||
if (!self.state.searching || self.searchId != localSearchId) {
|
||||
console.error("Discarding stale search results");
|
||||
return;
|
||||
|
|
|
@ -17,9 +17,17 @@ limitations under the License.
|
|||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
var q = require("q");
|
||||
|
||||
var DEBUG_SCROLL = false;
|
||||
|
||||
if (DEBUG_SCROLL) {
|
||||
// using bind means that we get to keep useful line numbers in the console
|
||||
var debuglog = console.log.bind(console);
|
||||
} else {
|
||||
var debuglog = function () {};
|
||||
}
|
||||
|
||||
/* This component implements an intelligent scrolling list.
|
||||
*
|
||||
* It wraps a list of <li> children; when items are added to the start or end
|
||||
|
@ -51,7 +59,16 @@ module.exports = React.createClass({
|
|||
|
||||
/* onFillRequest(backwards): a callback which is called on scroll when
|
||||
* the user nears the start (backwards = true) or end (backwards =
|
||||
* false) of the list
|
||||
* 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,
|
||||
|
||||
|
@ -71,25 +88,33 @@ module.exports = React.createClass({
|
|||
getDefaultProps: function() {
|
||||
return {
|
||||
stickyBottom: true,
|
||||
onFillRequest: function(backwards) {},
|
||||
onFillRequest: function(backwards) { return q(false); },
|
||||
onScroll: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._pendingFillRequests = {b: null, f: null};
|
||||
this.resetScrollState();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.checkFillState();
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
// after adding event tiles, we may need to tweak the scroll (either to
|
||||
// keep at the bottom of the timeline, or to maintain the view after
|
||||
// adding events to the top).
|
||||
this._restoreSavedScrollState();
|
||||
|
||||
// we also re-check the fill state, in case the paginate was inadequate
|
||||
this.checkFillState();
|
||||
},
|
||||
|
||||
onScroll: function(ev) {
|
||||
var sn = this._getScrollNode();
|
||||
if (DEBUG_SCROLL) console.log("Scroll event: offset now:", sn.scrollTop, "recentEventScroll:", this.recentEventScroll);
|
||||
debuglog("Scroll event: offset now:", sn.scrollTop, "recentEventScroll:", this.recentEventScroll);
|
||||
|
||||
// Sometimes we see attempts to write to scrollTop essentially being
|
||||
// ignored. (Or rather, it is successfully written, but on the next
|
||||
|
@ -113,26 +138,96 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
this.scrollState = this._calculateScrollState();
|
||||
if (DEBUG_SCROLL) console.log("Saved scroll state", this.scrollState);
|
||||
debuglog("Saved scroll state", this.scrollState);
|
||||
|
||||
this.props.onScroll(ev);
|
||||
|
||||
this.checkFillState();
|
||||
},
|
||||
|
||||
// return true if the content is fully scrolled down right now; else false.
|
||||
//
|
||||
// Note that if the content hasn't yet been fully populated, this may
|
||||
// spuriously return true even if the user wanted to be looking at earlier
|
||||
// content. So don't call it in render() cycles.
|
||||
isAtBottom: function() {
|
||||
return this.scrollState && this.scrollState.atBottom;
|
||||
var sn = this._getScrollNode();
|
||||
// + 1 here to avoid fractional pixel rounding errors
|
||||
return sn.scrollHeight - sn.scrollTop <= sn.clientHeight + 1;
|
||||
},
|
||||
|
||||
// check the scroll state and send out backfill requests if necessary.
|
||||
checkFillState: function() {
|
||||
var sn = this._getScrollNode();
|
||||
|
||||
// if there is less than a screenful of messages above or below the
|
||||
// viewport, try to get some more messages.
|
||||
//
|
||||
// scrollTop is the number of pixels between the top of the content and
|
||||
// the top of the viewport.
|
||||
//
|
||||
// scrollHeight is the total height of the content.
|
||||
//
|
||||
// clientHeight is the height of the viewport (excluding borders,
|
||||
// margins, and scrollbars).
|
||||
//
|
||||
//
|
||||
// .---------. - -
|
||||
// | | | scrollTop |
|
||||
// .-+---------+-. - - |
|
||||
// | | | | | |
|
||||
// | | | | | clientHeight | scrollHeight
|
||||
// | | | | | |
|
||||
// `-+---------+-' - |
|
||||
// | | |
|
||||
// | | |
|
||||
// `---------' -
|
||||
//
|
||||
|
||||
if (sn.scrollTop < sn.clientHeight) {
|
||||
// there's less than a screenful of messages left - try to get some
|
||||
// more messages.
|
||||
this.props.onFillRequest(true);
|
||||
// need to back-fill
|
||||
this._maybeFill(true);
|
||||
}
|
||||
if (sn.scrollTop > sn.scrollHeight - sn.clientHeight * 2) {
|
||||
// need to forward-fill
|
||||
this._maybeFill(false);
|
||||
}
|
||||
},
|
||||
|
||||
// check if there is already a pending fill request. If not, set one off.
|
||||
_maybeFill: function(backwards) {
|
||||
var dir = backwards ? 'b' : 'f';
|
||||
if (this._pendingFillRequests[dir]) {
|
||||
debuglog("ScrollPanel: Already a "+dir+" fill in progress - not starting another");
|
||||
return;
|
||||
}
|
||||
|
||||
debuglog("ScrollPanel: starting "+dir+" fill");
|
||||
|
||||
// onFillRequest can end up calling us recursively (via onScroll
|
||||
// events) so make sure we set this before firing off the call. That
|
||||
// does present the risk that we might not ever actually fire off the
|
||||
// fill request, so wrap it in a try/catch.
|
||||
this._pendingFillRequests[dir] = true;
|
||||
var fillPromise;
|
||||
try {
|
||||
fillPromise = this.props.onFillRequest(backwards);
|
||||
} catch (e) {
|
||||
this._pendingFillRequests[dir] = false;
|
||||
throw e;
|
||||
}
|
||||
|
||||
q.finally(fillPromise, () => {
|
||||
debuglog("ScrollPanel: "+dir+" fill complete");
|
||||
this._pendingFillRequests[dir] = false;
|
||||
}).then((hasMoreResults) => {
|
||||
if (hasMoreResults) {
|
||||
// further pagination requests have been disabled until now, so
|
||||
// it's time to check the fill state again in case the pagination
|
||||
// was insufficient.
|
||||
this.checkFillState();
|
||||
}
|
||||
}).done();
|
||||
},
|
||||
|
||||
// get the current scroll position of the room, so that it can be
|
||||
|
@ -156,13 +251,13 @@ module.exports = React.createClass({
|
|||
|
||||
scrollToTop: function() {
|
||||
this._getScrollNode().scrollTop = 0;
|
||||
if (DEBUG_SCROLL) console.log("Scrolled to top");
|
||||
debuglog("Scrolled to top");
|
||||
},
|
||||
|
||||
scrollToBottom: function() {
|
||||
var scrollNode = this._getScrollNode();
|
||||
scrollNode.scrollTop = scrollNode.scrollHeight;
|
||||
if (DEBUG_SCROLL) console.log("Scrolled to bottom; offset now", scrollNode.scrollTop);
|
||||
debuglog("Scrolled to bottom; offset now", scrollNode.scrollTop);
|
||||
},
|
||||
|
||||
// scroll the message list to the node with the given scrollToken. See
|
||||
|
@ -199,10 +294,10 @@ module.exports = React.createClass({
|
|||
this.recentEventScroll = scrollNode.scrollTop;
|
||||
}
|
||||
|
||||
if (DEBUG_SCROLL) {
|
||||
console.log("Scrolled to token", node.dataset.scrollToken, "+", pixelOffset+":", scrollNode.scrollTop, "(delta: "+scrollDelta+")");
|
||||
console.log("recentEventScroll now "+this.recentEventScroll);
|
||||
}
|
||||
debuglog("Scrolled to token", node.dataset.scrollToken, "+",
|
||||
pixelOffset+":", scrollNode.scrollTop,
|
||||
"(delta: "+scrollDelta+")");
|
||||
debuglog("recentEventScroll now "+this.recentEventScroll);
|
||||
},
|
||||
|
||||
_calculateScrollState: function() {
|
||||
|
@ -213,9 +308,7 @@ module.exports = React.createClass({
|
|||
// attribute. It is this token which is stored as the
|
||||
// 'lastDisplayedScrollToken'.
|
||||
|
||||
var sn = this._getScrollNode();
|
||||
// + 1 here to avoid fractional pixel rounding errors
|
||||
var atBottom = sn.scrollHeight - sn.scrollTop <= sn.clientHeight + 1;
|
||||
var atBottom = this.isAtBottom();
|
||||
|
||||
var itemlist = this.refs.itemlist;
|
||||
var wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
|
||||
|
|
|
@ -56,8 +56,9 @@ module.exports = React.createClass({
|
|||
|
||||
if (this.props.homeserver) {
|
||||
if (curr_val == "") {
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
target.value = "#:" + this.props.homeserver;
|
||||
target.value = "#:" + self.props.homeserver;
|
||||
target.setSelectionRange(1, 1);
|
||||
}, 0);
|
||||
} else {
|
||||
|
|
|
@ -54,8 +54,8 @@ module.exports = React.createClass({
|
|||
|
||||
if (httpUrl) {
|
||||
return (
|
||||
<span className="mx_MFileTile">
|
||||
<div className="mx_MImageTile_download">
|
||||
<span className="mx_MFileBody">
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
||||
<img src="img/download.png" width="10" height="12"/>
|
||||
Download {text}
|
||||
|
@ -65,7 +65,7 @@ module.exports = React.createClass({
|
|||
);
|
||||
} else {
|
||||
var extra = text ? ': '+text : '';
|
||||
return <span className="mx_MFileTile">
|
||||
return <span className="mx_MFileBody">
|
||||
Invalid file{extra}
|
||||
</span>
|
||||
}
|
||||
|
|
|
@ -109,14 +109,14 @@ module.exports = React.createClass({
|
|||
var thumbUrl = this._getThumbUrl();
|
||||
if (thumbUrl) {
|
||||
return (
|
||||
<span className="mx_MImageTile">
|
||||
<span className="mx_MImageBody">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} onClick={ this.onClick }>
|
||||
<img className="mx_MImageTile_thumbnail" src={thumbUrl}
|
||||
<img className="mx_MImageBody_thumbnail" src={thumbUrl}
|
||||
alt={content.body} style={imgStyle}
|
||||
onMouseEnter={this.onImageEnter}
|
||||
onMouseLeave={this.onImageLeave} />
|
||||
</a>
|
||||
<div className="mx_MImageTile_download">
|
||||
<div className="mx_MImageBody_download">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
||||
<img src="img/download.png" width="10" height="12"/>
|
||||
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
|
||||
|
@ -126,13 +126,13 @@ module.exports = React.createClass({
|
|||
);
|
||||
} else if (content.body) {
|
||||
return (
|
||||
<span className="mx_MImageTile">
|
||||
<span className="mx_MImageBody">
|
||||
Image '{content.body}' cannot be displayed.
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span className="mx_MImageTile">
|
||||
<span className="mx_MImageBody">
|
||||
This image cannot be displayed.
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -70,8 +70,8 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<span className="mx_MVideoTile">
|
||||
<video className="mx_MVideoTile" src={cli.mxcUrlToHttp(content.url)} alt={content.body}
|
||||
<span className="mx_MVideoBody">
|
||||
<video className="mx_MVideoBody" src={cli.mxcUrlToHttp(content.url)} alt={content.body}
|
||||
controls preload={preload} autoPlay="0"
|
||||
height={height} width={width} poster={poster}>
|
||||
</video>
|
||||
|
|
|
@ -47,6 +47,7 @@ module.exports = React.createClass({
|
|||
TileType = tileTypes[msgtype];
|
||||
}
|
||||
|
||||
return <TileType mxEvent={this.props.mxEvent} highlights={this.props.highlights} />;
|
||||
return <TileType mxEvent={this.props.mxEvent} highlights={this.props.highlights}
|
||||
onHighlightClick={this.props.onHighlightClick} />;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -49,25 +49,26 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
var mxEvent = this.props.mxEvent;
|
||||
var content = mxEvent.getContent();
|
||||
var body = HtmlUtils.bodyToHtml(content, this.props.highlights);
|
||||
var body = HtmlUtils.bodyToHtml(content, this.props.highlights,
|
||||
{onHighlightClick: this.props.onHighlightClick});
|
||||
|
||||
switch (content.msgtype) {
|
||||
case "m.emote":
|
||||
var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
|
||||
return (
|
||||
<span ref="content" className="mx_MEmoteTile mx_MessageTile_content">
|
||||
<span ref="content" className="mx_MEmoteBody mx_EventTile_content">
|
||||
* { name } { body }
|
||||
</span>
|
||||
);
|
||||
case "m.notice":
|
||||
return (
|
||||
<span ref="content" className="mx_MNoticeTile mx_MessageTile_content">
|
||||
<span ref="content" className="mx_MNoticeBody mx_EventTile_content">
|
||||
{ body }
|
||||
</span>
|
||||
);
|
||||
default: // including "m.text"
|
||||
return (
|
||||
<span ref="content" className="mx_MTextTile mx_MessageTile_content">
|
||||
<span ref="content" className="mx_MTextBody mx_EventTile_content">
|
||||
{ body }
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -34,7 +34,7 @@ module.exports = React.createClass({
|
|||
if (text == null || text.length == 0) return null;
|
||||
|
||||
return (
|
||||
<div className="mx_EventAsTextTile">
|
||||
<div className="mx_TextualEvent">
|
||||
{TextForEvent.textForEvent(this.props.mxEvent)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -24,7 +24,7 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
return (
|
||||
<span className="mx_UnknownMessageTile">
|
||||
<span className="mx_UnknownBody">
|
||||
{content.body}
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -74,6 +74,32 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
/* the MatrixEvent to show */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
|
||||
/* true if this is a continuation of the previous event (which has the
|
||||
* effect of not showing another avatar/displayname
|
||||
*/
|
||||
continuation: React.PropTypes.bool,
|
||||
|
||||
/* true if this is the last event in the timeline (which has the effect
|
||||
* of always showing the timestamp)
|
||||
*/
|
||||
last: React.PropTypes.bool,
|
||||
|
||||
/* true if this is search context (which has the effect of greying out
|
||||
* the text
|
||||
*/
|
||||
contextual: React.PropTypes.bool,
|
||||
|
||||
/* a list of words to highlight */
|
||||
highlights: React.PropTypes.array,
|
||||
|
||||
/* a function to be called when the highlight is clicked */
|
||||
onHighlightClick: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {menu: false, allReadAvatars: false};
|
||||
},
|
||||
|
@ -134,6 +160,9 @@ module.exports = React.createClass({
|
|||
|
||||
for (var i = 0; i < receipts.length; ++i) {
|
||||
var member = room.getMember(receipts[i].userId);
|
||||
if (!member) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Using react refs here would mean both getting Velociraptor to expose
|
||||
// them and making them scoped to the whole RoomView. Not impossible, but
|
||||
|
@ -280,7 +309,8 @@ module.exports = React.createClass({
|
|||
{ avatar }
|
||||
{ sender }
|
||||
<div className="mx_EventTile_line">
|
||||
<EventTileType mxEvent={this.props.mxEvent} highlights={this.props.highlights} />
|
||||
<EventTileType mxEvent={this.props.mxEvent} highlights={this.props.highlights}
|
||||
onHighlightClick={this.props.onHighlightClick} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -254,7 +254,7 @@ module.exports = React.createClass({
|
|||
} else {
|
||||
return (
|
||||
<form onSubmit={this.onPopulateInvite}>
|
||||
<input className="mx_MemberList_invite" ref="invite" placeholder="Invite another user"/>
|
||||
<input className="mx_MemberList_invite" ref="invite" placeholder="Invite user (email)"/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue