mirror of
https://github.com/element-hq/element-web
synced 2024-11-24 18:25:49 +03:00
Merge branch 'develop' into rav/update_status_bar
This commit is contained in:
commit
b087157855
21 changed files with 229 additions and 182 deletions
|
@ -92,6 +92,7 @@ class ContentMessages {
|
||||||
this.inprogress.push(upload);
|
this.inprogress.push(upload);
|
||||||
dis.dispatch({action: 'upload_started'});
|
dis.dispatch({action: 'upload_started'});
|
||||||
|
|
||||||
|
var error;
|
||||||
var self = this;
|
var self = this;
|
||||||
return def.promise.then(function() {
|
return def.promise.then(function() {
|
||||||
upload.promise = matrixClient.uploadContent(file);
|
upload.promise = matrixClient.uploadContent(file);
|
||||||
|
@ -103,11 +104,10 @@ class ContentMessages {
|
||||||
dis.dispatch({action: 'upload_progress', upload: upload});
|
dis.dispatch({action: 'upload_progress', upload: upload});
|
||||||
}
|
}
|
||||||
}).then(function(url) {
|
}).then(function(url) {
|
||||||
dis.dispatch({action: 'upload_finished', upload: upload});
|
|
||||||
content.url = url;
|
content.url = url;
|
||||||
return matrixClient.sendMessage(roomId, content);
|
return matrixClient.sendMessage(roomId, content);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
dis.dispatch({action: 'upload_failed', upload: upload});
|
error = err;
|
||||||
if (!upload.canceled) {
|
if (!upload.canceled) {
|
||||||
var desc = "The file '"+upload.fileName+"' failed to upload.";
|
var desc = "The file '"+upload.fileName+"' failed to upload.";
|
||||||
if (err.http_status == 413) {
|
if (err.http_status == 413) {
|
||||||
|
@ -128,6 +128,12 @@ class ContentMessages {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (error) {
|
||||||
|
dis.dispatch({action: 'upload_failed', upload: upload});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dis.dispatch({action: 'upload_finished', upload: upload});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,15 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
var ReactDOMServer = require('react-dom/server')
|
||||||
var sanitizeHtml = require('sanitize-html');
|
var sanitizeHtml = require('sanitize-html');
|
||||||
var highlight = require('highlight.js');
|
var highlight = require('highlight.js');
|
||||||
|
|
||||||
var sanitizeHtmlParams = {
|
var sanitizeHtmlParams = {
|
||||||
allowedTags: [
|
allowedTags: [
|
||||||
'font', // custom to matrix. deliberately no h1/h2 to stop people shouting.
|
'font', // custom to matrix for IRC-style font coloring
|
||||||
'del', // for markdown
|
'del', // for markdown
|
||||||
|
// deliberately no h1/h2 to stop people shouting.
|
||||||
'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
||||||
'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre'
|
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre'
|
||||||
|
@ -56,24 +58,17 @@ class Highlighter {
|
||||||
this._key = 0;
|
this._key = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyHighlights(safeSnippet, highlights) {
|
applyHighlights(safeSnippet, safeHighlights) {
|
||||||
var lastOffset = 0;
|
var lastOffset = 0;
|
||||||
var offset;
|
var offset;
|
||||||
var nodes = [];
|
var nodes = [];
|
||||||
|
|
||||||
// XXX: when highlighting HTML, synapse performs the search on the plaintext body,
|
var safeHighlight = safeHighlights[0];
|
||||||
// but we're attempting to apply the highlights here to the HTML body. This is
|
|
||||||
// never going to end well - we really should be hooking into the sanitzer HTML
|
|
||||||
// parser to only attempt to highlight text nodes to avoid corrupting tags.
|
|
||||||
// If and when this happens, we'll probably have to split his method in two between
|
|
||||||
// HTML and plain-text highlighting.
|
|
||||||
|
|
||||||
var safeHighlight = this.html ? sanitizeHtml(highlights[0], sanitizeHtmlParams) : highlights[0];
|
|
||||||
while ((offset = safeSnippet.toLowerCase().indexOf(safeHighlight.toLowerCase(), lastOffset)) >= 0) {
|
while ((offset = safeSnippet.toLowerCase().indexOf(safeHighlight.toLowerCase(), lastOffset)) >= 0) {
|
||||||
// handle preamble
|
// handle preamble
|
||||||
if (offset > lastOffset) {
|
if (offset > lastOffset) {
|
||||||
var subSnippet = safeSnippet.substring(lastOffset, offset);
|
var subSnippet = safeSnippet.substring(lastOffset, offset);
|
||||||
nodes = nodes.concat(this._applySubHighlights(subSnippet, highlights));
|
nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights));
|
||||||
}
|
}
|
||||||
|
|
||||||
// do highlight
|
// do highlight
|
||||||
|
@ -85,15 +80,15 @@ class Highlighter {
|
||||||
// handle postamble
|
// handle postamble
|
||||||
if (lastOffset != safeSnippet.length) {
|
if (lastOffset != safeSnippet.length) {
|
||||||
var subSnippet = safeSnippet.substring(lastOffset, undefined);
|
var subSnippet = safeSnippet.substring(lastOffset, undefined);
|
||||||
nodes = nodes.concat(this._applySubHighlights(subSnippet, highlights));
|
nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights));
|
||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
_applySubHighlights(safeSnippet, highlights) {
|
_applySubHighlights(safeSnippet, safeHighlights) {
|
||||||
if (highlights[1]) {
|
if (safeHighlights[1]) {
|
||||||
// recurse into this range to check for the next set of highlight matches
|
// recurse into this range to check for the next set of highlight matches
|
||||||
return this.applyHighlights(safeSnippet, highlights.slice(1));
|
return this.applyHighlights(safeSnippet, safeHighlights.slice(1));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// no more highlights to be found, just return the unhighlighted string
|
// no more highlights to be found, just return the unhighlighted string
|
||||||
|
@ -131,7 +126,7 @@ module.exports = {
|
||||||
*
|
*
|
||||||
* content: 'content' of the MatrixEvent
|
* content: 'content' of the MatrixEvent
|
||||||
*
|
*
|
||||||
* highlights: optional list of words to highlight
|
* highlights: optional list of words to highlight, ordered by longest word first
|
||||||
*
|
*
|
||||||
* opts.onHighlightClick: optional callback function to be called when a
|
* opts.onHighlightClick: optional callback function to be called when a
|
||||||
* highlighted word is clicked
|
* highlighted word is clicked
|
||||||
|
@ -143,26 +138,42 @@ module.exports = {
|
||||||
|
|
||||||
var safeBody;
|
var safeBody;
|
||||||
if (isHtml) {
|
if (isHtml) {
|
||||||
safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
|
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
|
||||||
|
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
|
||||||
|
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
|
||||||
|
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
|
||||||
|
try {
|
||||||
|
if (highlights && highlights.length > 0) {
|
||||||
|
var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick);
|
||||||
|
var safeHighlights = highlights.map(function(highlight) {
|
||||||
|
return sanitizeHtml(highlight, sanitizeHtmlParams);
|
||||||
|
});
|
||||||
|
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure.
|
||||||
|
sanitizeHtmlParams.textFilter = function(safeText) {
|
||||||
|
return highlighter.applyHighlights(safeText, safeHighlights).map(function(span) {
|
||||||
|
// XXX: rather clunky conversion from the react nodes returned by applyHighlights
|
||||||
|
// (which need to be nodes for the non-html highlighting case), to convert them
|
||||||
|
// back into raw HTML given that's what sanitize-html works in terms of.
|
||||||
|
return ReactDOMServer.renderToString(span);
|
||||||
|
}).join('');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
delete sanitizeHtmlParams.textFilter;
|
||||||
|
}
|
||||||
|
return <span className="markdown-body" dangerouslySetInnerHTML={{ __html: safeBody }} />;
|
||||||
} else {
|
} else {
|
||||||
safeBody = content.body;
|
safeBody = content.body;
|
||||||
}
|
if (highlights && highlights.length > 0) {
|
||||||
|
var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick);
|
||||||
var body;
|
return highlighter.applyHighlights(safeBody, highlights);
|
||||||
if (highlights && highlights.length > 0) {
|
|
||||||
var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick);
|
|
||||||
body = highlighter.applyHighlights(safeBody, highlights);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (isHtml) {
|
|
||||||
body = <span className="markdown-body" dangerouslySetInnerHTML={{ __html: safeBody }} />;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
body = safeBody;
|
return safeBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return body;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
highlightDom: function(element) {
|
highlightDom: function(element) {
|
||||||
|
|
|
@ -182,6 +182,9 @@ var Notifier = {
|
||||||
if (state === "PREPARED" || state === "SYNCING") {
|
if (state === "PREPARED" || state === "SYNCING") {
|
||||||
this.isPrepared = true;
|
this.isPrepared = true;
|
||||||
}
|
}
|
||||||
|
else if (state === "STOPPED" || state === "ERROR") {
|
||||||
|
this.isPrepared = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||||
|
|
|
@ -352,11 +352,12 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getCommandList: function() {
|
getCommandList: function() {
|
||||||
// Return all the commands plus /me which isn't handled like normal commands
|
// Return all the commands plus /me and /markdown which aren't handled like normal commands
|
||||||
var cmds = Object.keys(commands).sort().map(function(cmdKey) {
|
var cmds = Object.keys(commands).sort().map(function(cmdKey) {
|
||||||
return commands[cmdKey];
|
return commands[cmdKey];
|
||||||
})
|
})
|
||||||
cmds.push(new Command("me", "<action>", function(){}));
|
cmds.push(new Command("me", "<action>", function(){}));
|
||||||
|
cmds.push(new Command("markdown", "<on|off>", function(){}));
|
||||||
|
|
||||||
return cmds;
|
return cmds;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ var cached = false;
|
||||||
function calcCssFixups() {
|
function calcCssFixups() {
|
||||||
for (var i = 0; i < document.styleSheets.length; i++) {
|
for (var i = 0; i < document.styleSheets.length; i++) {
|
||||||
var ss = document.styleSheets[i];
|
var ss = document.styleSheets[i];
|
||||||
|
if (!ss) continue; // well done safari >:(
|
||||||
// Chromium apparently sometimes returns null here; unsure why.
|
// Chromium apparently sometimes returns null here; unsure why.
|
||||||
// see $14534907369972FRXBx:matrix.org in HQ
|
// see $14534907369972FRXBx:matrix.org in HQ
|
||||||
// ...ah, it's because there's a third party extension like
|
// ...ah, it's because there's a third party extension like
|
||||||
|
|
|
@ -30,6 +30,7 @@ class UserActivity {
|
||||||
* Start listening to user activity
|
* Start listening to user activity
|
||||||
*/
|
*/
|
||||||
start() {
|
start() {
|
||||||
|
document.onmousedown = this._onUserActivity.bind(this);
|
||||||
document.onmousemove = this._onUserActivity.bind(this);
|
document.onmousemove = this._onUserActivity.bind(this);
|
||||||
document.onkeypress = this._onUserActivity.bind(this);
|
document.onkeypress = this._onUserActivity.bind(this);
|
||||||
// can't use document.scroll here because that's only the document
|
// can't use document.scroll here because that's only the document
|
||||||
|
@ -46,6 +47,7 @@ class UserActivity {
|
||||||
* Stop tracking user activity
|
* Stop tracking user activity
|
||||||
*/
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
|
document.onmousedown = undefined;
|
||||||
document.onmousemove = undefined;
|
document.onmousemove = undefined;
|
||||||
document.onkeypress = undefined;
|
document.onkeypress = undefined;
|
||||||
window.removeEventListener('wheel', this._onUserActivity.bind(this), true);
|
window.removeEventListener('wheel', this._onUserActivity.bind(this), true);
|
||||||
|
|
|
@ -175,7 +175,7 @@ module.exports = React.createClass({
|
||||||
guest: true
|
guest: true
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.error(err.data);
|
console.error("Failed to register as guest: " + err + " " + err.data);
|
||||||
self._setAutoRegisterAsGuest(false);
|
self._setAutoRegisterAsGuest(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -316,9 +316,6 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'view_room':
|
case 'view_room':
|
||||||
// by default we autoPeek rooms, unless we were called explicitly with
|
|
||||||
// autoPeek=false by something like RoomDirectory who has already peeked
|
|
||||||
this.setState({ autoPeek : payload.auto_peek === false ? false : true });
|
|
||||||
this._viewRoom(payload.room_id, payload.show_settings, payload.event_id);
|
this._viewRoom(payload.room_id, payload.show_settings, payload.event_id);
|
||||||
break;
|
break;
|
||||||
case 'view_prev_room':
|
case 'view_prev_room':
|
||||||
|
@ -880,7 +877,6 @@ module.exports = React.createClass({
|
||||||
eventId={this.state.initialEventId}
|
eventId={this.state.initialEventId}
|
||||||
highlightedEventId={this.state.highlightedEventId}
|
highlightedEventId={this.state.highlightedEventId}
|
||||||
eventPixelOffset={this.state.initialEventPixelOffset}
|
eventPixelOffset={this.state.initialEventPixelOffset}
|
||||||
autoPeek={this.state.autoPeek}
|
|
||||||
key={this.state.currentRoom}
|
key={this.state.currentRoom}
|
||||||
ConferenceHandler={this.props.ConferenceHandler} />
|
ConferenceHandler={this.props.ConferenceHandler} />
|
||||||
);
|
);
|
||||||
|
@ -974,7 +970,9 @@ module.exports = React.createClass({
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
homeserverUrl={this.props.config.default_hs_url}
|
homeserverUrl={this.props.config.default_hs_url}
|
||||||
identityServerUrl={this.props.config.default_is_url}
|
identityServerUrl={this.props.config.default_is_url}
|
||||||
onForgotPasswordClick={this.onForgotPasswordClick} />
|
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||||
|
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest: undefined}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,10 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
|
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
|
||||||
|
if (MatrixClientPeg.get()) {
|
||||||
|
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onSyncStateChange: function(state, prevState) {
|
onSyncStateChange: function(state, prevState) {
|
||||||
|
|
|
@ -60,12 +60,11 @@ module.exports = React.createClass({
|
||||||
displayName: 'RoomView',
|
displayName: 'RoomView',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: React.PropTypes.any,
|
||||||
roomId: React.PropTypes.string,
|
|
||||||
autoPeek: React.PropTypes.bool, // Now unused, left here temporarily to avoid merge conflicts with @richvdh's branch.
|
|
||||||
|
|
||||||
roomId: React.PropTypes.string.isRequired,
|
roomId: React.PropTypes.string.isRequired,
|
||||||
|
|
||||||
// id of an event to jump to. If not given, will use the read-up-to-marker.
|
// id of an event to jump to. If not given, will go to the end of the
|
||||||
|
// live timeline.
|
||||||
eventId: React.PropTypes.string,
|
eventId: React.PropTypes.string,
|
||||||
|
|
||||||
// where to position the event given by eventId, in pixels from the
|
// where to position the event given by eventId, in pixels from the
|
||||||
|
@ -76,14 +75,6 @@ module.exports = React.createClass({
|
||||||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
// ID of an event to highlight. If undefined, no event will be highlighted.
|
||||||
// Typically this will either be the same as 'eventId', or undefined.
|
// Typically this will either be the same as 'eventId', or undefined.
|
||||||
highlightedEventId: React.PropTypes.string,
|
highlightedEventId: React.PropTypes.string,
|
||||||
|
|
||||||
autoPeek: React.PropTypes.bool, // should we try to peek the room on mount, or has whoever invoked us already initiated a peek?
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
autoPeek: true,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* properties in RoomView objects include:
|
/* properties in RoomView objects include:
|
||||||
|
@ -155,11 +146,6 @@ module.exports = React.createClass({
|
||||||
// We can /peek though. If it fails then we present the join UI. If it
|
// We can /peek though. If it fails then we present the join UI. If it
|
||||||
// succeeds then great, show the preview (but we still may be able to /join!).
|
// succeeds then great, show the preview (but we still may be able to /join!).
|
||||||
if (!this.state.room) {
|
if (!this.state.room) {
|
||||||
if (!this.props.autoPeek) {
|
|
||||||
console.log("No room loaded, and autopeek disabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Attempting to peek into room %s", this.props.roomId);
|
console.log("Attempting to peek into room %s", this.props.roomId);
|
||||||
|
|
||||||
roomProm = MatrixClientPeg.get().peekInRoom(this.props.roomId).then((room) => {
|
roomProm = MatrixClientPeg.get().peekInRoom(this.props.roomId).then((room) => {
|
||||||
|
@ -193,11 +179,6 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_initTimeline: function(props) {
|
_initTimeline: function(props) {
|
||||||
var initialEvent = props.eventId;
|
var initialEvent = props.eventId;
|
||||||
if (!initialEvent) {
|
|
||||||
// go to the 'read-up-to' mark if no explicit event given
|
|
||||||
initialEvent = this.state.readMarkerEventId;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pixelOffset = props.eventPixelOffset;
|
var pixelOffset = props.eventPixelOffset;
|
||||||
return this._loadTimeline(initialEvent, pixelOffset);
|
return this._loadTimeline(initialEvent, pixelOffset);
|
||||||
},
|
},
|
||||||
|
@ -486,20 +467,6 @@ module.exports = React.createClass({
|
||||||
readMarkerEventId: readMarkerEventId,
|
readMarkerEventId: readMarkerEventId,
|
||||||
readMarkerGhostEventId: readMarkerGhostEventId,
|
readMarkerGhostEventId: readMarkerGhostEventId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// if the scrollpanel is following the timeline, attempt to scroll
|
|
||||||
// it to bring the read message up to the middle of the panel. This
|
|
||||||
// will have no immediate effect (since we are already at the
|
|
||||||
// bottom), but will ensure that if there is no further user
|
|
||||||
// activity, but room activity continues, the read message will
|
|
||||||
// scroll up to the middle of the window, but no further.
|
|
||||||
//
|
|
||||||
// we do this here as well as in sendReadReceipt to deal with
|
|
||||||
// people using two clients at once.
|
|
||||||
if (this.refs.messagePanel && this.state.atEndOfLiveTimeline) {
|
|
||||||
this.refs.messagePanel.scrollToToken(readMarkerEventId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -585,14 +552,6 @@ module.exports = React.createClass({
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
|
|
||||||
if (this.refs.roomView) {
|
|
||||||
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
|
|
||||||
roomView.addEventListener('drop', this.onDrop);
|
|
||||||
roomView.addEventListener('dragover', this.onDragOver);
|
|
||||||
roomView.addEventListener('dragleave', this.onDragLeaveOrEnd);
|
|
||||||
roomView.addEventListener('dragend', this.onDragLeaveOrEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._updateTabCompleteList();
|
this._updateTabCompleteList();
|
||||||
|
|
||||||
// XXX: EVIL HACK to autofocus inviting on empty rooms.
|
// XXX: EVIL HACK to autofocus inviting on empty rooms.
|
||||||
|
@ -630,6 +589,16 @@ module.exports = React.createClass({
|
||||||
// separate component to avoid this ridiculous dance.
|
// separate component to avoid this ridiculous dance.
|
||||||
if (!this.refs.messagePanel) return;
|
if (!this.refs.messagePanel) return;
|
||||||
|
|
||||||
|
if (this.refs.roomView) {
|
||||||
|
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
|
||||||
|
if (!roomView.ondrop) {
|
||||||
|
roomView.addEventListener('drop', this.onDrop);
|
||||||
|
roomView.addEventListener('dragover', this.onDragOver);
|
||||||
|
roomView.addEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||||
|
roomView.addEventListener('dragend', this.onDragLeaveOrEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.refs.messagePanel.initialised) {
|
if (!this.refs.messagePanel.initialised) {
|
||||||
this._initialiseMessagePanel();
|
this._initialiseMessagePanel();
|
||||||
}
|
}
|
||||||
|
@ -1159,19 +1128,6 @@ module.exports = React.createClass({
|
||||||
// it failed, so allow retries next time the user is active
|
// it failed, so allow retries next time the user is active
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.last_rr_sent_event_id = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
// if the scrollpanel is following the timeline, attempt to scroll
|
|
||||||
// it to bring the read message up to the middle of the panel. This
|
|
||||||
// will have no immediate effect (since we are already at the
|
|
||||||
// bottom), but will ensure that if there is no further user
|
|
||||||
// activity, but room activity continues, the read message will
|
|
||||||
// scroll up to the middle of the window, but no further.
|
|
||||||
//
|
|
||||||
// we do this here as well as in onRoomReceipt to cater for guest users
|
|
||||||
// (which do not send out read receipts).
|
|
||||||
if (this.state.atEndOfLiveTimeline) {
|
|
||||||
this.refs.messagePanel.scrollToToken(lastReadEvent.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1275,11 +1231,19 @@ module.exports = React.createClass({
|
||||||
self.setState({
|
self.setState({
|
||||||
rejecting: false
|
rejecting: false
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(error) {
|
||||||
console.error("Failed to reject invite: %s", err);
|
console.error("Failed to reject invite: %s", error);
|
||||||
|
|
||||||
|
var msg = error.message ? error.message : JSON.stringify(error);
|
||||||
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Failed to reject invite",
|
||||||
|
description: msg
|
||||||
|
});
|
||||||
|
|
||||||
self.setState({
|
self.setState({
|
||||||
rejecting: false,
|
rejecting: false,
|
||||||
rejectError: err
|
rejectError: error
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1473,14 +1437,14 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
<RoomHeader ref="header" room={this.state.room} simpleHeader="Join room"/>
|
<RoomHeader ref="header" room={this.state.room} simpleHeader="Join room"/>
|
||||||
<div className="mx_RoomView_auxPanel">
|
<div className="mx_RoomView_auxPanel">
|
||||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||||
canJoin={ true } canPreview={ false }/>
|
canJoin={ true } canPreview={ false }
|
||||||
<div className="error">{joinErrorText}</div>
|
spinner={this.state.joining}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomView_messagePanel"></div>
|
<div className="mx_RoomView_messagePanel"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1506,10 +1470,6 @@ module.exports = React.createClass({
|
||||||
} else {
|
} else {
|
||||||
var inviteEvent = myMember.events.member;
|
var inviteEvent = myMember.events.member;
|
||||||
var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender();
|
var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender();
|
||||||
// XXX: Leaving this intentionally basic for now because invites are about to change totally
|
|
||||||
// FIXME: This comment is now outdated - what do we need to fix? ^
|
|
||||||
var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
|
|
||||||
var rejectErrorText = this.state.rejectError ? "Failed to reject invite!" : "";
|
|
||||||
|
|
||||||
// We deliberately don't try to peek into invites, even if we have permission to peek
|
// We deliberately don't try to peek into invites, even if we have permission to peek
|
||||||
// as they could be a spam vector.
|
// as they could be a spam vector.
|
||||||
|
@ -1522,9 +1482,9 @@ module.exports = React.createClass({
|
||||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||||
onRejectClick={ this.onRejectButtonClicked }
|
onRejectClick={ this.onRejectButtonClicked }
|
||||||
inviterName={ inviterName }
|
inviterName={ inviterName }
|
||||||
canJoin={ true } canPreview={ false }/>
|
canJoin={ true } canPreview={ false }
|
||||||
<div className="error">{joinErrorText}</div>
|
spinner={this.state.joining}
|
||||||
<div className="error">{rejectErrorText}</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomView_messagePanel"></div>
|
<div className="mx_RoomView_messagePanel"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1588,13 +1548,17 @@ module.exports = React.createClass({
|
||||||
else if (this.state.guestsCanJoin && MatrixClientPeg.get().isGuest() &&
|
else if (this.state.guestsCanJoin && MatrixClientPeg.get().isGuest() &&
|
||||||
(!myMember || myMember.membership !== "join")) {
|
(!myMember || myMember.membership !== "join")) {
|
||||||
aux = (
|
aux = (
|
||||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true} />
|
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true}
|
||||||
|
spinner={this.state.joining}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (this.state.canPeek &&
|
else if (this.state.canPeek &&
|
||||||
(!myMember || myMember.membership !== "join")) {
|
(!myMember || myMember.membership !== "join")) {
|
||||||
aux = (
|
aux = (
|
||||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true} />
|
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true}
|
||||||
|
spinner={this.state.joining}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1714,15 +1678,22 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// it's important that stickyBottom = false on this, otherwise if somebody hits the
|
// give the messagepanel a stickybottom if we're at the end of the
|
||||||
// bottom of the loaded events when viewing historical messages, we get stuck in a
|
// live timeline, so that the arrival of new events triggers a
|
||||||
// loop of paginating our way through the entire history of the room.
|
// scroll.
|
||||||
|
//
|
||||||
|
// Make sure that stickyBottom is *false* if we can paginate
|
||||||
|
// forwards, otherwise if somebody hits the bottom of the loaded
|
||||||
|
// events when viewing historical messages, we get stuck in a loop
|
||||||
|
// of paginating our way through the entire history of the room.
|
||||||
|
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
|
||||||
|
|
||||||
messagePanel = (
|
messagePanel = (
|
||||||
<ScrollPanel ref="messagePanel" className="mx_RoomView_messagePanel"
|
<ScrollPanel ref="messagePanel" className="mx_RoomView_messagePanel"
|
||||||
onScroll={ this.onMessageListScroll }
|
onScroll={ this.onMessageListScroll }
|
||||||
onFillRequest={ this.onMessageListFillRequest }
|
onFillRequest={ this.onMessageListFillRequest }
|
||||||
style={ hideMessagePanel ? { display: 'none' } : {} }
|
style={ hideMessagePanel ? { display: 'none' } : {} }
|
||||||
stickyBottom={ false }>
|
stickyBottom={ stickyBottom }>
|
||||||
<li className={scrollheader_classes}></li>
|
<li className={scrollheader_classes}></li>
|
||||||
{this.getEventTiles()}
|
{this.getEventTiles()}
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
|
|
|
@ -315,6 +315,16 @@ module.exports = React.createClass({
|
||||||
onFinished={this.onPasswordChanged} />
|
onFinished={this.onPasswordChanged} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
var notification_area;
|
||||||
|
if (!MatrixClientPeg.get().isGuest()) {
|
||||||
|
notification_area = (<div>
|
||||||
|
<h2>Notifications</h2>
|
||||||
|
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
<Notifications/>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_UserSettings">
|
<div className="mx_UserSettings">
|
||||||
|
@ -364,11 +374,7 @@ module.exports = React.createClass({
|
||||||
{accountJsx}
|
{accountJsx}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Notifications</h2>
|
{notification_area}
|
||||||
|
|
||||||
<div className="mx_UserSettings_section">
|
|
||||||
<Notifications/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Advanced</h2>
|
<h2>Advanced</h2>
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
// login shouldn't know or care how registration is done.
|
// login shouldn't know or care how registration is done.
|
||||||
onRegisterClick: React.PropTypes.func.isRequired,
|
onRegisterClick: React.PropTypes.func.isRequired,
|
||||||
// login shouldn't care how password recovery is done.
|
// login shouldn't care how password recovery is done.
|
||||||
onForgotPasswordClick: React.PropTypes.func
|
onForgotPasswordClick: React.PropTypes.func,
|
||||||
|
onLoginAsGuestClick: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -167,6 +168,13 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
var LoginFooter = sdk.getComponent("login.LoginFooter");
|
var LoginFooter = sdk.getComponent("login.LoginFooter");
|
||||||
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
|
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
|
||||||
|
|
||||||
|
var loginAsGuestJsx;
|
||||||
|
if (this.props.onLoginAsGuestClick) {
|
||||||
|
loginAsGuestJsx =
|
||||||
|
<a className="mx_Login_create" onClick={this.props.onLoginAsGuestClick} href="#">
|
||||||
|
Login as guest
|
||||||
|
</a>
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx_Login">
|
<div className="mx_Login">
|
||||||
<div className="mx_Login_box">
|
<div className="mx_Login_box">
|
||||||
|
@ -188,6 +196,7 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||||
Create a new account
|
Create a new account
|
||||||
</a>
|
</a>
|
||||||
|
{ loginAsGuestJsx }
|
||||||
<br/>
|
<br/>
|
||||||
<LoginFooter />
|
<LoginFooter />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -115,6 +115,9 @@ module.exports = React.createClass({
|
||||||
onProcessingRegistration: function(promise) {
|
onProcessingRegistration: function(promise) {
|
||||||
var self = this;
|
var self = this;
|
||||||
promise.done(function(response) {
|
promise.done(function(response) {
|
||||||
|
self.setState({
|
||||||
|
busy: false
|
||||||
|
});
|
||||||
if (!response || !response.access_token) {
|
if (!response || !response.access_token) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"FIXME: Register fulfilled without a final response, " +
|
"FIXME: Register fulfilled without a final response, " +
|
||||||
|
@ -126,7 +129,7 @@ module.exports = React.createClass({
|
||||||
if (!response || !response.user_id || !response.access_token) {
|
if (!response || !response.user_id || !response.access_token) {
|
||||||
console.error("Final response is missing keys.");
|
console.error("Final response is missing keys.");
|
||||||
self.setState({
|
self.setState({
|
||||||
errorText: "There was a problem processing the response."
|
errorText: "Registration failed on server"
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -136,9 +139,6 @@ module.exports = React.createClass({
|
||||||
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
|
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
|
||||||
accessToken: response.access_token
|
accessToken: response.access_token
|
||||||
});
|
});
|
||||||
self.setState({
|
|
||||||
busy: false
|
|
||||||
});
|
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.message) {
|
if (err.message) {
|
||||||
self.setState({
|
self.setState({
|
||||||
|
|
|
@ -31,14 +31,22 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onKeyDown: function(e) {
|
||||||
|
if (e.keyCode === 27) { // escape
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
this.cancelPrompt();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
Sign out?
|
Sign out?
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons" onKeyDown={ this.onKeyDown }>
|
||||||
<button onClick={this.logOut}>Sign Out</button>
|
<button autoFocus onClick={this.logOut}>Sign Out</button>
|
||||||
<button onClick={this.cancelPrompt}>Cancel</button>
|
<button onClick={this.cancelPrompt}>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,9 +26,20 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
if (this.props.currentDisplayName) {
|
||||||
value: this.props.currentDisplayName || "Guest "+MatrixClientPeg.get().getUserIdLocalpart(),
|
return { value: this.props.currentDisplayName };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
return { value : "Guest " + MatrixClientPeg.get().getUserIdLocalpart() };
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return { value : MatrixClientPeg.get().getUserIdLocalpart() };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.refs.input_value.select();
|
||||||
},
|
},
|
||||||
|
|
||||||
getValue: function() {
|
getValue: function() {
|
||||||
|
@ -54,11 +65,12 @@ module.exports = React.createClass({
|
||||||
Set a Display Name
|
Set a Display Name
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
Your display name is how you'll appear to others when you speak in rooms. What would you like it to be?
|
Your display name is how you'll appear to others when you speak in rooms.<br/>
|
||||||
|
What would you like it to be?
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={this.onFormSubmit}>
|
<form onSubmit={this.onFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<input type="text" value={this.state.value}
|
<input type="text" ref="input_value" value={this.state.value}
|
||||||
autoFocus={true} onChange={this.onValueChange} size="30"
|
autoFocus={true} onChange={this.onValueChange} size="30"
|
||||||
className="mx_SetDisplayNameDialog_input"
|
className="mx_SetDisplayNameDialog_input"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -51,7 +51,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.truncateAt >= 0) {
|
if (this.props.truncateAt >= 0) {
|
||||||
var overflowCount = childCount - this.props.truncateAt;
|
var overflowCount = childCount - this.props.truncateAt;
|
||||||
|
|
||||||
if (overflowCount > 0) {
|
if (overflowCount > 1) {
|
||||||
overflowJsx = this.props.createOverflowElement(
|
overflowJsx = this.props.createOverflowElement(
|
||||||
overflowCount, childCount
|
overflowCount, childCount
|
||||||
);
|
);
|
||||||
|
|
|
@ -42,9 +42,11 @@ module.exports = React.createClass({
|
||||||
// TODO: Keep this list bleeding-edge up-to-date. Practically speaking,
|
// TODO: Keep this list bleeding-edge up-to-date. Practically speaking,
|
||||||
// it will do for now not being updated as random new users join different
|
// it will do for now not being updated as random new users join different
|
||||||
// rooms as this list will be reloaded every room swap.
|
// rooms as this list will be reloaded every room swap.
|
||||||
this._userList = MatrixClientPeg.get().getUsers().filter((u) => {
|
if (this._room) {
|
||||||
return !this._room.hasMembershipState(u.userId, "join");
|
this._userList = MatrixClientPeg.get().getUsers().filter((u) => {
|
||||||
});
|
return !this._room.hasMembershipState(u.userId, "join");
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onInvite: function(ev) {
|
onInvite: function(ev) {
|
||||||
|
@ -87,7 +89,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchableEntityList searchPlaceholderText={"Invite / Search"}
|
<SearchableEntityList searchPlaceholderText={"Invite/search by name, email, id"}
|
||||||
onSubmit={this.props.onInvite}
|
onSubmit={this.props.onInvite}
|
||||||
onQueryChanged={this.onSearchQueryChanged}
|
onQueryChanged={this.onSearchQueryChanged}
|
||||||
entities={entities}
|
entities={entities}
|
||||||
|
|
|
@ -35,21 +35,23 @@ var invite_defer = q.defer();
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberList',
|
displayName: 'MemberList',
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
if (!this.props.roomId) return { members: [] };
|
var state = {
|
||||||
var cli = MatrixClientPeg.get();
|
members: [],
|
||||||
var room = cli.getRoom(this.props.roomId);
|
|
||||||
if (!room) return { members: [] };
|
|
||||||
|
|
||||||
this.memberDict = this.getMemberDict();
|
|
||||||
|
|
||||||
var members = this.roomMembers(INITIAL_LOAD_NUM_MEMBERS);
|
|
||||||
return {
|
|
||||||
members: members,
|
|
||||||
// ideally we'd size this to the page height, but
|
// ideally we'd size this to the page height, but
|
||||||
// in practice I find that a little constraining
|
// in practice I find that a little constraining
|
||||||
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
|
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
|
||||||
};
|
};
|
||||||
|
if (!this.props.roomId) return state;
|
||||||
|
var cli = MatrixClientPeg.get();
|
||||||
|
var room = cli.getRoom(this.props.roomId);
|
||||||
|
if (!room) return state;
|
||||||
|
|
||||||
|
this.memberDict = this.getMemberDict();
|
||||||
|
|
||||||
|
state.members = this.roomMembers(INITIAL_LOAD_NUM_MEMBERS);
|
||||||
|
return state;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -325,7 +327,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var memberList = self.state.members.filter(function(userId) {
|
var memberList = self.state.members.filter(function(userId) {
|
||||||
var m = self.memberDict[userId];
|
var m = self.memberDict[userId];
|
||||||
if (query && m.name.toLowerCase().indexOf(query) !== 0) {
|
if (query && m.name.toLowerCase().indexOf(query) === -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return m.membership == membership;
|
return m.membership == membership;
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
var sdk = require('../../../index');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomPreviewBar',
|
displayName: 'RoomPreviewBar',
|
||||||
|
@ -27,6 +28,7 @@ module.exports = React.createClass({
|
||||||
inviterName: React.PropTypes.string,
|
inviterName: React.PropTypes.string,
|
||||||
canJoin: React.PropTypes.bool,
|
canJoin: React.PropTypes.bool,
|
||||||
canPreview: React.PropTypes.bool,
|
canPreview: React.PropTypes.bool,
|
||||||
|
spinner: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -40,6 +42,13 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var joinBlock, previewBlock;
|
var joinBlock, previewBlock;
|
||||||
|
|
||||||
|
if (this.props.spinner) {
|
||||||
|
var Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
return (<div className="mx_RoomPreviewBar">
|
||||||
|
<Spinner />
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.inviterName) {
|
if (this.props.inviterName) {
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -37,10 +37,12 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
var areNotifsMuted = false;
|
var areNotifsMuted = false;
|
||||||
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
|
if (!MatrixClientPeg.get().isGuest()) {
|
||||||
if (roomPushRule) {
|
var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId);
|
||||||
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
|
if (roomPushRule) {
|
||||||
areNotifsMuted = true;
|
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
|
||||||
|
areNotifsMuted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,34 +140,37 @@ var SearchableEntityList = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
var list;
|
var list;
|
||||||
if (this.props.truncateAt) { // caller wants list truncated
|
if (this.state.results.length) {
|
||||||
var TruncatedList = sdk.getComponent("elements.TruncatedList");
|
if (this.props.truncateAt) { // caller wants list truncated
|
||||||
list = (
|
var TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||||
<TruncatedList className="mx_SearchableEntityList_list"
|
list = (
|
||||||
truncateAt={this.state.truncateAt} // use state truncation as it may be expanded
|
<TruncatedList className="mx_SearchableEntityList_list"
|
||||||
createOverflowElement={this._createOverflowEntity}>
|
truncateAt={this.state.truncateAt} // use state truncation as it may be expanded
|
||||||
{this.state.results.map((entity) => {
|
createOverflowElement={this._createOverflowEntity}>
|
||||||
return entity.getJsx();
|
{this.state.results.map((entity) => {
|
||||||
})}
|
return entity.getJsx();
|
||||||
</TruncatedList>
|
})}
|
||||||
);
|
</TruncatedList>
|
||||||
}
|
);
|
||||||
else {
|
}
|
||||||
list = (
|
else {
|
||||||
<div className="mx_SearchableEntityList_list">
|
list = (
|
||||||
{this.state.results.map((entity) => {
|
<div className="mx_SearchableEntityList_list">
|
||||||
return entity.getJsx();
|
{this.state.results.map((entity) => {
|
||||||
})}
|
return entity.getJsx();
|
||||||
</div>
|
})}
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
list = <GeminiScrollbar autoshow={true} className="mx_SearchableEntityList_listWrapper">
|
||||||
|
{ list }
|
||||||
|
</GeminiScrollbar>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ "mx_SearchableEntityList " + (this.state.query.length ? "mx_SearchableEntityList_expanded" : "") }>
|
<div className={ "mx_SearchableEntityList " + (this.state.query.length ? "mx_SearchableEntityList_expanded" : "") }>
|
||||||
{inputBox}
|
{ inputBox }
|
||||||
<GeminiScrollbar autoshow={true} className="mx_SearchableEntityList_listWrapper">
|
{ list }
|
||||||
{ list }
|
|
||||||
</GeminiScrollbar>
|
|
||||||
{ this.state.query.length ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' }
|
{ this.state.query.length ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -110,19 +110,17 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
|
||||||
var avatarImg;
|
var avatarImg;
|
||||||
// Having just set an avatar we just display that since it will take a little
|
// Having just set an avatar we just display that since it will take a little
|
||||||
// time to propagate through to the RoomAvatar.
|
// time to propagate through to the RoomAvatar.
|
||||||
if (this.props.room && !this.avatarSet) {
|
if (this.props.room && !this.avatarSet) {
|
||||||
|
var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
||||||
avatarImg = <RoomAvatar room={this.props.room} width={ this.props.width } height={ this.props.height } resizeMethod='crop' />;
|
avatarImg = <RoomAvatar room={this.props.room} width={ this.props.width } height={ this.props.height } resizeMethod='crop' />;
|
||||||
} else {
|
} else {
|
||||||
var style = {
|
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
width: this.props.width,
|
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
|
||||||
height: this.props.height,
|
avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop'
|
||||||
objectFit: 'cover',
|
name='?' idName={ MatrixClientPeg.get().getUserIdLocalpart() } url={this.state.avatarUrl} />
|
||||||
};
|
|
||||||
avatarImg = <img className="mx_BaseAvatar_image" src={this.state.avatarUrl} style={style} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploadSection;
|
var uploadSection;
|
||||||
|
|
Loading…
Reference in a new issue