diff --git a/src/ContentMessages.js b/src/ContentMessages.js
index 82c295756b..bbd714fa57 100644
--- a/src/ContentMessages.js
+++ b/src/ContentMessages.js
@@ -92,6 +92,7 @@ class ContentMessages {
this.inprogress.push(upload);
dis.dispatch({action: 'upload_started'});
+ var error;
var self = this;
return def.promise.then(function() {
upload.promise = matrixClient.uploadContent(file);
@@ -103,11 +104,10 @@ class ContentMessages {
dis.dispatch({action: 'upload_progress', upload: upload});
}
}).then(function(url) {
- dis.dispatch({action: 'upload_finished', upload: upload});
content.url = url;
return matrixClient.sendMessage(roomId, content);
}, function(err) {
- dis.dispatch({action: 'upload_failed', upload: upload});
+ error = err;
if (!upload.canceled) {
var desc = "The file '"+upload.fileName+"' failed to upload.";
if (err.http_status == 413) {
@@ -128,6 +128,12 @@ class ContentMessages {
break;
}
}
+ if (error) {
+ dis.dispatch({action: 'upload_failed', upload: upload});
+ }
+ else {
+ dis.dispatch({action: 'upload_finished', upload: upload});
+ }
});
}
diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js
index 0b7f17b2b2..fe97d7b84f 100644
--- a/src/HtmlUtils.js
+++ b/src/HtmlUtils.js
@@ -17,7 +17,6 @@ limitations under the License.
'use strict';
var React = require('react');
-var ReactDOMServer = require('react-dom/server')
var sanitizeHtml = require('sanitize-html');
var highlight = require('highlight.js');
@@ -50,14 +49,23 @@ var sanitizeHtmlParams = {
},
};
-class Highlighter {
- constructor(html, highlightClass, onHighlightClick) {
- this.html = html;
+class BaseHighlighter {
+ constructor(highlightClass, highlightLink) {
this.highlightClass = highlightClass;
- this.onHighlightClick = onHighlightClick;
- this._key = 0;
+ this.highlightLink = highlightLink;
}
+ /**
+ * apply the highlights to a section of text
+ *
+ * @param {string} safeSnippet The snippet of text to apply the highlights
+ * to.
+ * @param {string[]} safeHighlights A list of substrings to highlight,
+ * sorted by descending length.
+ *
+ * returns a list of results (strings for HtmlHighligher, react nodes for
+ * TextHighlighter).
+ */
applyHighlights(safeSnippet, safeHighlights) {
var lastOffset = 0;
var offset;
@@ -71,10 +79,12 @@ class Highlighter {
nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights));
}
- // do highlight
- nodes.push(this._createSpan(safeHighlight, true));
+ // do highlight. use the original string rather than safeHighlight
+ // to preserve the original casing.
+ var endOffset = offset + safeHighlight.length;
+ nodes.push(this._processSnippet(safeSnippet.substring(offset, endOffset), true));
- lastOffset = offset + safeHighlight.length;
+ lastOffset = endOffset;
}
// handle postamble
@@ -92,31 +102,64 @@ class Highlighter {
}
else {
// no more highlights to be found, just return the unhighlighted string
- return [this._createSpan(safeSnippet, false)];
+ return [this._processSnippet(safeSnippet, false)];
}
}
+}
+
+class HtmlHighlighter extends BaseHighlighter {
+ /* highlight the given snippet if required
+ *
+ * snippet: content of the span; must have been sanitised
+ * highlight: true to highlight as a search match
+ *
+ * returns an HTML string
+ */
+ _processSnippet(snippet, highlight) {
+ if (!highlight) {
+ // nothing required here
+ return snippet;
+ }
+
+ var span = ""
+ + snippet + "";
+
+ if (this.highlightLink) {
+ span = ""
+ +span+"";
+ }
+ return span;
+ }
+}
+
+class TextHighlighter extends BaseHighlighter {
+ constructor(highlightClass, highlightLink) {
+ super(highlightClass, highlightLink);
+ this._key = 0;
+ }
/* create a node to hold the given content
*
- * spanBody: content of the span. If html, must have been sanitised
+ * snippet: content of the span
* highlight: true to highlight as a search match
+ *
+ * returns a React node
*/
- _createSpan(spanBody, highlight) {
+ _processSnippet(snippet, highlight) {
var spanProps = {
key: this._key++,
};
if (highlight) {
- spanProps.onClick = this.onHighlightClick;
spanProps.className = this.highlightClass;
}
- if (this.html) {
- return ();
- }
- else {
- return ({ spanBody });
+ var node = { snippet };
+
+ if (highlight && this.highlightLink) {
+ node = {node}
}
+ return node;
}
}
@@ -128,8 +171,7 @@ module.exports = {
*
* highlights: optional list of words to highlight, ordered by longest word first
*
- * opts.onHighlightClick: optional callback function to be called when a
- * highlighted word is clicked
+ * opts.highlightLink: optional href to add to highlights
*/
bodyToHtml: function(content, highlights, opts) {
opts = opts || {};
@@ -144,18 +186,13 @@ module.exports = {
// 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 highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
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('');
+ return highlighter.applyHighlights(safeText, safeHighlights).join('');
};
}
safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
@@ -167,7 +204,7 @@ module.exports = {
} else {
safeBody = content.body;
if (highlights && highlights.length > 0) {
- var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick);
+ var highlighter = new TextHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
return highlighter.applyHighlights(safeBody, highlights);
}
else {
diff --git a/src/Notifier.js b/src/Notifier.js
index e52fd252fe..b64a001a5f 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -182,6 +182,9 @@ var Notifier = {
if (state === "PREPARED" || state === "SYNCING") {
this.isPrepared = true;
}
+ else if (state === "STOPPED" || state === "ERROR") {
+ this.isPrepared = false;
+ }
},
onRoomTimeline: function(ev, room, toStartOfTimeline) {
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 9ff3925b10..a4ac219b95 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -64,7 +64,10 @@ module.exports = React.createClass({
},
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) {
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index be3ac1adfe..d3aabf366f 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -693,15 +693,6 @@ module.exports = React.createClass({
});
},
- _onSearchResultSelected: function(result) {
- var event = result.context.getEvent();
- dis.dispatch({
- action: 'view_room',
- room_id: event.getRoomId(),
- event_id: event.getId(),
- });
- },
-
getSearchResultTiles: function() {
var EventTile = sdk.getComponent('rooms.EventTile');
var SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
@@ -762,10 +753,12 @@ module.exports = React.createClass({
}
}
+ var resultLink = "#/room/"+this.props.roomId+"/"+mxEv.getId();
+
ret.push();
+ resultLink={resultLink}/>);
}
return ret;
},
diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js
index 5666318368..9ec379a814 100644
--- a/src/components/structures/login/Registration.js
+++ b/src/components/structures/login/Registration.js
@@ -115,6 +115,9 @@ module.exports = React.createClass({
onProcessingRegistration: function(promise) {
var self = this;
promise.done(function(response) {
+ self.setState({
+ busy: false
+ });
if (!response || !response.access_token) {
console.warn(
"FIXME: Register fulfilled without a final response, " +
@@ -126,7 +129,7 @@ module.exports = React.createClass({
if (!response || !response.user_id || !response.access_token) {
console.error("Final response is missing keys.");
self.setState({
- errorText: "There was a problem processing the response."
+ errorText: "Registration failed on server"
});
return;
}
@@ -136,9 +139,6 @@ module.exports = React.createClass({
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
accessToken: response.access_token
});
- self.setState({
- busy: false
- });
}, function(err) {
if (err.message) {
self.setState({
diff --git a/src/components/views/dialogs/LogoutPrompt.js b/src/components/views/dialogs/LogoutPrompt.js
index 824924e999..67fedfe840 100644
--- a/src/components/views/dialogs/LogoutPrompt.js
+++ b/src/components/views/dialogs/LogoutPrompt.js
@@ -31,14 +31,22 @@ module.exports = React.createClass({
}
},
+ onKeyDown: function(e) {
+ if (e.keyCode === 27) { // escape
+ e.stopPropagation();
+ e.preventDefault();
+ this.cancelPrompt();
+ }
+ },
+
render: function() {
return (
Sign out?
-
-
+
+
diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js
index 2490d9be8b..9cc0e22c59 100644
--- a/src/components/views/messages/MessageEvent.js
+++ b/src/components/views/messages/MessageEvent.js
@@ -28,6 +28,18 @@ module.exports = React.createClass({
}
},
+ propTypes: {
+ /* the MatrixEvent to show */
+ mxEvent: React.PropTypes.object.isRequired,
+
+ /* a list of words to highlight */
+ highlights: React.PropTypes.array,
+
+ /* link URL for the highlights */
+ highlightLink: React.PropTypes.string,
+ },
+
+
render: function() {
var UnknownMessageTile = sdk.getComponent('messages.UnknownBody');
@@ -48,6 +60,6 @@ module.exports = React.createClass({
}
return ;
+ highlightLink={this.props.highlightLink} />;
},
});
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index e3613ef9a3..92447dd1da 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -28,6 +28,17 @@ linkifyMatrix(linkify);
module.exports = React.createClass({
displayName: 'TextualBody',
+ propTypes: {
+ /* the MatrixEvent to show */
+ mxEvent: React.PropTypes.object.isRequired,
+
+ /* a list of words to highlight */
+ highlights: React.PropTypes.array,
+
+ /* link URL for the highlights */
+ highlightLink: React.PropTypes.string,
+ },
+
componentDidMount: function() {
linkifyElement(this.refs.content, linkifyMatrix.options);
@@ -46,14 +57,15 @@ module.exports = React.createClass({
shouldComponentUpdate: function(nextProps) {
// exploit that events are immutable :)
return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
- nextProps.highlights !== this.props.highlights);
+ nextProps.highlights !== this.props.highlights ||
+ nextProps.highlightLink !== this.props.highlightLink);
},
render: function() {
var mxEvent = this.props.mxEvent;
var content = mxEvent.getContent();
var body = HtmlUtils.bodyToHtml(content, this.props.highlights,
- {onHighlightClick: this.props.onHighlightClick});
+ {highlightLink: this.props.highlightLink});
switch (content.msgtype) {
case "m.emote":
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js
index f580686128..36ec85e91b 100644
--- a/src/components/views/rooms/EventTile.js
+++ b/src/components/views/rooms/EventTile.js
@@ -96,8 +96,8 @@ module.exports = React.createClass({
/* a list of words to highlight */
highlights: React.PropTypes.array,
- /* a function to be called when the highlight is clicked */
- onHighlightClick: React.PropTypes.func,
+ /* link URL for the highlights */
+ highlightLink: React.PropTypes.string,
/* is this the focussed event */
isSelectedEvent: React.PropTypes.bool,
@@ -313,8 +313,8 @@ module.exports = React.createClass({
{ avatar }
{ sender }
-
+
);
diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js
index 4e8f38b035..eba09ca9da 100644
--- a/src/components/views/rooms/MemberList.js
+++ b/src/components/views/rooms/MemberList.js
@@ -327,7 +327,7 @@ module.exports = React.createClass({
var memberList = self.state.members.filter(function(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 m.membership == membership;
diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js
index 9d3af16ee7..9c793e8705 100644
--- a/src/components/views/rooms/SearchResultTile.js
+++ b/src/components/views/rooms/SearchResultTile.js
@@ -29,8 +29,8 @@ module.exports = React.createClass({
// a list of strings to be highlighted in the results
searchHighlights: React.PropTypes.array,
- // callback to be called when the user selects this result
- onSelect: React.PropTypes.func,
+ // href for the highlights in this result
+ resultLink: React.PropTypes.string,
},
render: function() {
@@ -53,7 +53,7 @@ module.exports = React.createClass({
}
if (EventTile.haveTileForEvent(ev)) {
ret.push()
+ highlightLink={this.props.resultLink}/>);
}
}
return (
diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js
index 89303856b2..9b03aba1a3 100644
--- a/src/components/views/settings/ChangeAvatar.js
+++ b/src/components/views/settings/ChangeAvatar.js
@@ -110,19 +110,17 @@ module.exports = React.createClass({
},
render: function() {
- var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
var avatarImg;
// Having just set an avatar we just display that since it will take a little
// time to propagate through to the RoomAvatar.
if (this.props.room && !this.avatarSet) {
+ var RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
avatarImg = ;
} else {
- var style = {
- width: this.props.width,
- height: this.props.height,
- objectFit: 'cover',
- };
- avatarImg = ;
+ var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
+ // XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
+ avatarImg =
}
var uploadSection;