From bed0a7c7feabfdc9299faeabeb60d9a1a371d42a Mon Sep 17 00:00:00 2001 From: Aviral Dasgupta Date: Fri, 1 Apr 2016 06:18:34 +0530 Subject: [PATCH 1/3] Autocomplete and render unicode emoji as images fixes vector-im/vector-web#1031, fixes vector-im/vector-web#883, fixes vector-im/vector-web#117 --- package.json | 8 +++++--- src/HtmlUtils.js | 22 +++++++++++++++------ src/TabCompleteEntries.js | 28 +++++++++++++++++++++++++++ src/components/structures/RoomView.js | 3 ++- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index c259700c927..cc4da1f7315 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,14 @@ }, "dependencies": { "classnames": "^2.1.2", + "emojione": "^2.1.3", "favico.js": "^0.3.10", "filesize": "^3.1.2", "flux": "^2.0.3", "glob": "^5.0.14", "highlight.js": "^8.9.1", "linkifyjs": "^2.0.0-beta.4", + "lodash": "^4.7.0", "marked": "^0.3.5", "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "optimist": "^0.6.1", @@ -41,9 +43,9 @@ "velocity-ui-pack": "^1.2.2" }, "//babelversion": [ - "brief experiments with babel6 seems to show that it generates source ", - "maps which confuse chrome and make setting breakpoints tricky. So ", - "let's stick with v5 for now." + "brief experiments with babel6 seems to show that it generates source ", + "maps which confuse chrome and make setting breakpoints tricky. So ", + "let's stick with v5 for now." ], "devDependencies": { "babel": "^5.8.23", diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 82aea0bb764..ca4e7a3bdb3 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -20,6 +20,8 @@ var React = require('react'); var sanitizeHtml = require('sanitize-html'); var highlight = require('highlight.js'); var linkifyMatrix = require('./linkify-matrix'); +var emojione = require('emojione'); +var _ = require('lodash'); var sanitizeHtmlParams = { allowedTags: [ @@ -208,17 +210,25 @@ module.exports = { finally { delete sanitizeHtmlParams.textFilter; } - return ; } else { - safeBody = content.body; + safeBody = _.escape(content.body); + if (highlights && highlights.length > 0) { var highlighter = new TextHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); - return highlighter.applyHighlights(safeBody, highlights); - } - else { - return safeBody; + safeBody = highlighter.applyHighlights(safeBody, highlights); } } + + // We do this here to avoid having to whitelist + + try { + safeBody = emojione.toImage(safeBody); + } catch (e) { + console.log(e); + } + + safeBody = ; + return safeBody; }, highlightDom: function(element) { diff --git a/src/TabCompleteEntries.js b/src/TabCompleteEntries.js index a23050063f4..0d633bafdcd 100644 --- a/src/TabCompleteEntries.js +++ b/src/TabCompleteEntries.js @@ -15,6 +15,7 @@ limitations under the License. */ var React = require("react"); var sdk = require("./index"); +var emojione = require('emojione'); class Entry { constructor(text) { @@ -91,6 +92,32 @@ CommandEntry.fromCommands = function(commandArray) { }); } +class EmojiEntry extends Entry { + constructor(shortname) { + super(shortname); + this.shortname = shortname; + } + + getFillText() { + return emojione.shortnameToUnicode(this.shortname); + } + + getKey() { + return this.shortname; + } + + getImageJsx() { + var image = emojione.shortnameToImage(this.shortname); + return ; + } + + getSuffix(isFirstWord) { + return " "; // force a space after the command. + } +} + +EmojiEntry.entries = Object.keys(emojione.emojioneList).map((shortname) => new EmojiEntry(shortname)); + class MemberEntry extends Entry { constructor(member) { super(member.name || member.userId); @@ -139,3 +166,4 @@ MemberEntry.fromMemberList = function(members) { module.exports.Entry = Entry; module.exports.MemberEntry = MemberEntry; module.exports.CommandEntry = CommandEntry; +module.exports.EmojiEntry = EmojiEntry; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index b5c34de20da..190b60847da 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -33,6 +33,7 @@ var CallHandler = require('../../CallHandler'); var TabComplete = require("../../TabComplete"); var MemberEntry = require("../../TabCompleteEntries").MemberEntry; var CommandEntry = require("../../TabCompleteEntries").CommandEntry; +var EmojiEntry = require("../../TabCompleteEntries").EmojiEntry; var Resend = require("../../Resend"); var SlashCommands = require("../../SlashCommands"); var dis = require("../../dispatcher"); @@ -469,7 +470,7 @@ module.exports = React.createClass({ this.tabComplete.setCompletionList( MemberEntry.fromMemberList(members).concat( CommandEntry.fromCommands(SlashCommands.getCommandList()) - ) + ).concat(EmojiEntry.entries) ); }, 500), From 8f9d35a5d09a56601b6b4576d8cb874ee1796a52 Mon Sep 17 00:00:00 2001 From: Aviral Dasgupta Date: Sun, 10 Apr 2016 02:57:01 +0530 Subject: [PATCH 2/3] Fix emoji conversion's interaction with highlights --- src/HtmlUtils.js | 61 ++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 17fd33e75f0..449cfc1fdb1 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -173,6 +173,15 @@ class TextHighlighter extends BaseHighlighter { } } +function emojiToImage(body: String) { + try { + return emojione.toImage(body); + } catch (e) { + console.log(e); + } + + return body; +} module.exports = { /* turn a matrix event body into html @@ -188,43 +197,29 @@ module.exports = { var isHtml = (content.format === "org.matrix.custom.html"); - var safeBody; - if (isHtml) { - // 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. foobar 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 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).join(''); - }; - } - safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); - } - finally { - delete sanitizeHtmlParams.textFilter; - } - } else { - safeBody = _.escape(content.body); + let safeBody; + const body = isHtml ? content.formatted_body : _.escape(content.body); + // 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. foobar 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 TextHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); - safeBody = highlighter.applyHighlights(safeBody, highlights); + 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).join(''); + }; } + safeBody = sanitizeHtml(body, sanitizeHtmlParams); + safeBody = emojiToImage(safeBody); } - - // We do this here to avoid having to whitelist - - try { - safeBody = emojione.toImage(safeBody); - } catch (e) { - console.log(e); + finally { + delete sanitizeHtmlParams.textFilter; } safeBody = ; From 94f10093fd917f05dfc8998323ba8c5a564e7379 Mon Sep 17 00:00:00 2001 From: Aviral Dasgupta Date: Sun, 10 Apr 2016 04:05:23 +0530 Subject: [PATCH 3/3] Remove emoji to image code from HtmlUtils --- src/HtmlUtils.js | 67 ++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 449cfc1fdb1..82aea0bb764 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -20,8 +20,6 @@ var React = require('react'); var sanitizeHtml = require('sanitize-html'); var highlight = require('highlight.js'); var linkifyMatrix = require('./linkify-matrix'); -var emojione = require('emojione'); -var _ = require('lodash'); var sanitizeHtmlParams = { allowedTags: [ @@ -29,7 +27,7 @@ var sanitizeHtmlParams = { 'del', // for markdown // deliberately no h1/h2 to stop people shouting. 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', - 'nl', 'li', 'b', 'i', 'u', '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' ], allowedAttributes: { @@ -173,15 +171,6 @@ class TextHighlighter extends BaseHighlighter { } } -function emojiToImage(body: String) { - try { - return emojione.toImage(body); - } catch (e) { - console.log(e); - } - - return body; -} module.exports = { /* turn a matrix event body into html @@ -197,33 +186,39 @@ module.exports = { var isHtml = (content.format === "org.matrix.custom.html"); - let safeBody; - const body = isHtml ? content.formatted_body : _.escape(content.body); - - // 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. foobar won't get highlighted - // by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either - try { + var safeBody; + if (isHtml) { + // 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. foobar 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 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).join(''); + }; + } + safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); + } + finally { + delete sanitizeHtmlParams.textFilter; + } + return ; + } else { + safeBody = content.body; if (highlights && highlights.length > 0) { - 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).join(''); - }; + var highlighter = new TextHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); + return highlighter.applyHighlights(safeBody, highlights); + } + else { + return safeBody; } - safeBody = sanitizeHtml(body, sanitizeHtmlParams); - safeBody = emojiToImage(safeBody); - } - finally { - delete sanitizeHtmlParams.textFilter; } - - safeBody = ; - return safeBody; }, highlightDom: function(element) {