diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 7a3cdd277b..196b87a972 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -48,8 +48,15 @@ var sanitizeHtmlParams = { }, }; -module.exports = { - _applyHighlights: function(safeSnippet, highlights, html, k) { +class Highlighter { + constructor(html, highlightClass, onHighlightClick) { + this.html = html; + this.highlightClass = highlightClass; + this.onHighlightClick = onHighlightClick; + this._key = 0; + } + + applyHighlights(safeSnippet, highlights) { var lastOffset = 0; var offset; var nodes = []; @@ -61,77 +68,97 @@ module.exports = { // If and when this happens, we'll probably have to split his method in two between // HTML and plain-text highlighting. - var safeHighlight = html ? sanitizeHtml(highlights[0], sanitizeHtmlParams) : highlights[0]; + var safeHighlight = this.html ? sanitizeHtml(highlights[0], sanitizeHtmlParams) : highlights[0]; while ((offset = safeSnippet.indexOf(safeHighlight, lastOffset)) >= 0) { // handle preamble if (offset > lastOffset) { - nodes = nodes.concat(this._applySubHighlightsInRange(safeSnippet, lastOffset, offset, highlights, html, k)); - k += nodes.length; + var subSnippet = safeSnippet.substring(lastOffset, offset); + nodes = nodes.concat(this._applySubHighlights(subSnippet, highlights)); } // do highlight - if (html) { - nodes.push(); - } - else { - nodes.push({ safeHighlight }); - } + nodes.push(this._createSpan(safeHighlight, true)); lastOffset = offset + safeHighlight.length; } // handle postamble if (lastOffset != safeSnippet.length) { - nodes = nodes.concat(this._applySubHighlightsInRange(safeSnippet, lastOffset, undefined, highlights, html, k)); - k += nodes.length; + var subSnippet = safeSnippet.substring(lastOffset, undefined); + nodes = nodes.concat(this._applySubHighlights(subSnippet, highlights)); } return nodes; - }, + } - _applySubHighlightsInRange: function(safeSnippet, lastOffset, offset, highlights, html, k) { - var nodes = []; + _applySubHighlights(safeSnippet, highlights) { if (highlights[1]) { // recurse into this range to check for the next set of highlight matches - var subnodes = this._applyHighlights( safeSnippet.substring(lastOffset, offset), highlights.slice(1), html, k ); - nodes = nodes.concat(subnodes); - k += subnodes.length; + return this.applyHighlights(safeSnippet, highlights.slice(1)); } else { // no more highlights to be found, just return the unhighlighted string - if (html) { - nodes.push(); - } - else { - nodes.push({ safeSnippet.substring(lastOffset, offset) }); - } + return [this._createSpan(safeSnippet, false)]; } - return nodes; - }, + } - bodyToHtml: function(content, highlights) { - var originalBody = content.body; - var body; - var k = 0; + /* create a node to hold the given content + * + * spanBody: content of the span. If html, must have been sanitised + * highlight: true to highlight as a search match + */ + _createSpan(spanBody, highlight) { + var spanProps = { + key: this._key++, + }; - if (highlights && highlights.length > 0) { - var bodyList = []; + if (highlight) { + spanProps.onClick = this.onHighlightClick; + spanProps.className = this.highlightClass; + } - if (content.format === "org.matrix.custom.html") { - var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); - bodyList = this._applyHighlights(safeBody, highlights, true, k); - } - else { - bodyList = this._applyHighlights(originalBody, highlights, true, k); - } - body = bodyList; + if (this.html) { + return (); } else { - if (content.format === "org.matrix.custom.html") { - var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); + return ({ spanBody }); + } + } +} + + +module.exports = { + /* turn a matrix event body into html + * + * content: 'content' of the MatrixEvent + * + * highlights: optional list of words to highlight + * + * opts.onHighlightClick: optional callback function to be called when a + * highlighted word is clicked + */ + bodyToHtml: function(content, highlights, opts) { + opts = opts || {}; + + var isHtml = (content.format === "org.matrix.custom.html"); + + var safeBody; + if (isHtml) { + safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); + } else { + safeBody = content.body; + } + + var body; + if (highlights && highlights.length > 0) { + var highlighter = new Highlighter(isHtml, "mx_MessageTile_searchHighlight", opts.onHighlightClick); + body = highlighter.applyHighlights(safeBody, highlights); + } + else { + if (isHtml) { body = ; } else { - body = originalBody; + body = safeBody; } }