forked from matrix/element-web
display search results correct; support HTML markup
This commit is contained in:
parent
d315e4afcd
commit
fdfa0cbd0e
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,3 @@ limitations under the License.
|
||||||
.mx_MTextTile {
|
.mx_MTextTile {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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} />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue