display search results correct; support HTML markup

This commit is contained in:
Matthew Hodgson 2015-10-30 04:10:37 +00:00
parent d315e4afcd
commit fdfa0cbd0e
7 changed files with 116 additions and 7 deletions

View File

@ -31,7 +31,8 @@
"matrix-react-sdk": "^0.0.1", "matrix-react-sdk": "^0.0.1",
"q": "^1.4.1", "q": "^1.4.1",
"react": "^0.13.3", "react": "^0.13.3",
"react-loader": "^1.4.0" "react-loader": "^1.4.0",
"sanitize-html": "^1.11.1"
}, },
"devDependencies": { "devDependencies": {
"babel": "^5.8.23", "babel": "^5.8.23",

View File

@ -66,6 +66,13 @@ limitations under the License.
margin-right: 100px; margin-right: 100px;
} }
.mx_MessageTile_searchHighlight {
background-color: #76cfa6;
color: #fff;
border-radius: 5px;
padding: 4px;
}
.mx_EventTile_sending { .mx_EventTile_sending {
color: #ddd; color: #ddd;
} }
@ -78,6 +85,10 @@ limitations under the License.
color: #FF0064; color: #FF0064;
} }
.mx_EventTile_contextual {
opacity: 0.4;
}
.mx_EventTile_msgOption { .mx_EventTile_msgOption {
float: right; float: right;
} }

View File

@ -17,4 +17,3 @@ limitations under the License.
.mx_MTextTile { .mx_MTextTile {
white-space: pre-wrap; white-space: pre-wrap;
} }

View File

@ -91,7 +91,8 @@ module.exports = React.createClass({
mx_EventTile_highlight: this.shouldHighlight(), mx_EventTile_highlight: this.shouldHighlight(),
mx_EventTile_continuation: this.props.continuation, mx_EventTile_continuation: this.props.continuation,
mx_EventTile_last: this.props.last, mx_EventTile_last: this.props.last,
menu: this.state.menu mx_EventTile_contextual: this.props.contextual,
menu: this.state.menu,
}); });
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} /> var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
var editButton = ( var editButton = (
@ -126,7 +127,7 @@ module.exports = React.createClass({
<div className="mx_EventTile_line"> <div className="mx_EventTile_line">
{ timestamp } { timestamp }
{ editButton } { editButton }
<EventTileType mxEvent={this.props.mxEvent} /> <EventTileType mxEvent={this.props.mxEvent} searchTerm={this.props.searchTerm} />
</div> </div>
</div> </div>
); );

View File

@ -17,18 +17,67 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); var React = require('react');
var sanitizeHtml = require('sanitize-html');
var MNoticeTileController = require('matrix-react-sdk/lib/controllers/molecules/MNoticeTile') var MNoticeTileController = require('matrix-react-sdk/lib/controllers/molecules/MNoticeTile')
var allowedAttributes = sanitizeHtml.defaults.allowedAttributes;
allowedAttributes['font'] = ['color'];
var sanitizeHtmlParams = {
allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'font' ]),
allowedAttributes: allowedAttributes,
};
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MNoticeTile', displayName: 'MNoticeTile',
mixins: [MNoticeTileController], mixins: [MNoticeTileController],
// FIXME: this entire class is copy-pasted from MTextTile :(
render: function() { render: function() {
var content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
var body = content.body;
if (content.format === "org.matrix.custom.html") {
body = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
}
if (this.props.searchTerm) {
var lastOffset = 0;
var bodyList = [];
var k = 0;
var offset;
// XXX: this probably doesn't handle stemming very well.
while ((offset = body.indexOf(this.props.searchTerm, lastOffset)) >= 0) {
if (content.format === "org.matrix.custom.html") {
// FIXME: we need to apply the search highlighting to only the text elements of HTML, which means
// hooking into the sanitizer parser rather than treating it as a string. Otherwise
// the act of highlighting a <b/> or whatever will break the HTML badly.
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset, offset) }} />);
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: this.props.searchTerm }} className="mx_MessageTile_searchHighlight" />);
}
else {
bodyList.push(<span key={ k++ } >{ body.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
}
lastOffset = offset + this.props.searchTerm.length;
}
if (content.format === "org.matrix.custom.html") {
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset) }} />);
}
else {
bodyList.push(<span key={ k++ }>{ body.substring(lastOffset) }</span>);
}
body = bodyList;
}
else {
if (content.format === "org.matrix.custom.html") {
body = <span dangerouslySetInnerHTML={{ __html: body }} />;
}
}
return ( return (
<span ref="content" className="mx_MNoticeTile mx_MessageTile_content"> <span ref="content" className="mx_MNoticeTile mx_MessageTile_content">
{content.body} { body }
</span> </span>
); );
}, },

View File

@ -17,18 +17,66 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); var React = require('react');
var sanitizeHtml = require('sanitize-html');
var MTextTileController = require('matrix-react-sdk/lib/controllers/molecules/MTextTile') var MTextTileController = require('matrix-react-sdk/lib/controllers/molecules/MTextTile')
var allowedAttributes = sanitizeHtml.defaults.allowedAttributes;
allowedAttributes['font'] = ['color'];
var sanitizeHtmlParams = {
allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'font' ]),
allowedAttributes: allowedAttributes,
};
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MTextTile', displayName: 'MTextTile',
mixins: [MTextTileController], mixins: [MTextTileController],
render: function() { render: function() {
var content = this.props.mxEvent.getContent(); var content = this.props.mxEvent.getContent();
var body = content.body;
if (content.format === "org.matrix.custom.html") {
body = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
}
if (this.props.searchTerm) {
var lastOffset = 0;
var bodyList = [];
var k = 0;
var offset;
// XXX: this probably doesn't handle stemming very well.
while ((offset = body.indexOf(this.props.searchTerm, lastOffset)) >= 0) {
if (content.format === "org.matrix.custom.html") {
// FIXME: we need to apply the search highlighting to only the text elements of HTML, which means
// hooking into the sanitizer parser rather than treating it as a string. Otherwise
// the act of highlighting a <b/> or whatever will break the HTML badly.
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset, offset) }} />);
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: this.props.searchTerm }} className="mx_MessageTile_searchHighlight" />);
}
else {
bodyList.push(<span key={ k++ } >{ body.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
}
lastOffset = offset + this.props.searchTerm.length;
}
if (content.format === "org.matrix.custom.html") {
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: body.substring(lastOffset) }} />);
}
else {
bodyList.push(<span key={ k++ }>{ body.substring(lastOffset) }</span>);
}
body = bodyList;
}
else {
if (content.format === "org.matrix.custom.html") {
body = <span dangerouslySetInnerHTML={{ __html: body }} />;
}
}
return ( return (
<span ref="content" className="mx_MTextTile mx_MessageTile_content"> <span ref="content" className="mx_MTextTile mx_MessageTile_content">
{content.body} { body }
</span> </span>
); );
}, },

View File

@ -52,6 +52,6 @@ module.exports = React.createClass({
TileType = tileTypes[msgtype]; TileType = tileTypes[msgtype];
} }
return <TileType mxEvent={this.props.mxEvent} />; return <TileType mxEvent={this.props.mxEvent} searchTerm={this.props.searchTerm} />;
}, },
}); });