From ae16cc36aaeb2bc96d236b88c5ff7f225acbde8d Mon Sep 17 00:00:00 2001
From: David Baker <dave@matrix.org>
Date: Fri, 8 Feb 2019 14:57:36 +0000
Subject: [PATCH] Change SAS to decimal / emoji

Requires https://github.com/matrix-org/matrix-js-sdk/pull/837
---
 .../verification/_VerificationShowSas.scss    |  28 +++-
 src/HtmlUtils.js                              |  18 ++-
 src/components/views/elements/EmojiText.js    |   5 +-
 .../views/verification/VerificationShowSas.js | 124 ++++++++++++++++--
 src/i18n/strings/en_EN.json                   |  67 +++++++++-
 5 files changed, 224 insertions(+), 18 deletions(-)

diff --git a/res/css/views/verification/_VerificationShowSas.scss b/res/css/views/verification/_VerificationShowSas.scss
index 32ccf6b0bb..a0da7e2539 100644
--- a/res/css/views/verification/_VerificationShowSas.scss
+++ b/res/css/views/verification/_VerificationShowSas.scss
@@ -14,9 +14,35 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-.mx_VerificationShowSas_sas {
+.mx_VerificationShowSas_decimalSas {
     text-align: center;
     font-weight: bold;
     padding-left: 3px;
     padding-right: 3px;
 }
+
+.mx_VerificationShowSas_decimalSas span {
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.mx_VerificationShowSas_emojiSas {
+    text-align: center;
+}
+
+.mx_VerificationShowSas_emojiSas_block {
+    display: inline-block;
+    margin-left: 15px;
+    margin-right: 15px;
+    text-align: center;
+    margin-bottom: 20px;
+}
+
+.mx_VerificationShowSas_emojiSas_emoji {
+    font-size: 48px;
+}
+
+.mx_VerificationShowSas_emojiSas_label {
+    text-align: center;
+    font-weight: bold;
+}
diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js
index 371804725d..2e08c059eb 100644
--- a/src/HtmlUtils.js
+++ b/src/HtmlUtils.js
@@ -68,8 +68,10 @@ export function containsEmoji(str) {
 /* modified from https://github.com/Ranks/emojione/blob/master/lib/js/emojione.js
  * because we want to include emoji shortnames in title text
  */
-function unicodeToImage(str) {
-    let replaceWith; let unicode; let alt; let short; let fname;
+function unicodeToImage(str, addAlt) {
+    if (addAlt === undefined) addAlt = true;
+
+    let replaceWith; let unicode; let short; let fname;
     const mappedUnicode = emojione.mapUnicodeToShort();
 
     str = str.replace(emojione.regUnicode, function(unicodeChar) {
@@ -84,10 +86,14 @@ function unicodeToImage(str) {
             fname = emojione.emojioneList[short].fname;
 
             // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname
-            alt = (emojione.unicodeAlt) ? emojione.convert(unicode.toUpperCase()) : mappedUnicode[unicode];
             const title = mappedUnicode[unicode];
 
-            replaceWith = `<img class="mx_emojione" title="${title}" alt="${alt}" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
+            if (addAlt) {
+                const alt = (emojione.unicodeAlt) ? emojione.convert(unicode.toUpperCase()) : mappedUnicode[unicode];
+                replaceWith = `<img class="mx_emojione" title="${title}" alt="${alt}" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
+            } else {
+                replaceWith = `<img class="mx_emojione" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
+            }
             return replaceWith;
         }
     });
@@ -508,9 +514,9 @@ export function bodyToHtml(content, highlights, opts={}) {
         <span className={className} dir="auto">{ strippedBody }</span>;
 }
 
-export function emojifyText(text) {
+export function emojifyText(text, addAlt) {
     return {
-        __html: unicodeToImage(escape(text)),
+        __html: unicodeToImage(escape(text), addAlt),
     };
 }
 
diff --git a/src/components/views/elements/EmojiText.js b/src/components/views/elements/EmojiText.js
index 9fb650b2c3..b7f3e45321 100644
--- a/src/components/views/elements/EmojiText.js
+++ b/src/components/views/elements/EmojiText.js
@@ -20,12 +20,12 @@ import PropTypes from 'prop-types';
 import {emojifyText, containsEmoji} from '../../../HtmlUtils';
 
 export default function EmojiText(props) {
-    const {element, children, ...restProps} = props;
+    const {element, children, addAlt, ...restProps} = props;
 
     // fast path: simple regex to detect strings that don't contain
     // emoji and just return them
     if (containsEmoji(children)) {
-        restProps.dangerouslySetInnerHTML = emojifyText(children);
+        restProps.dangerouslySetInnerHTML = emojifyText(children, addAlt);
         return React.createElement(element, restProps);
     } else {
         return React.createElement(element, restProps, children);
@@ -39,4 +39,5 @@ EmojiText.propTypes = {
 
 EmojiText.defaultProps = {
     element: 'span',
+    addAlt: true,
 };
diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js
index 0224571d9e..bca68e92d3 100644
--- a/src/components/views/verification/VerificationShowSas.js
+++ b/src/components/views/verification/VerificationShowSas.js
@@ -17,13 +17,17 @@ limitations under the License.
 import React from 'react';
 import PropTypes from 'prop-types';
 import sdk from '../../../index';
-import { _t } from '../../../languageHandler';
+import { _t, _td } from '../../../languageHandler';
+
+function capFirst(s) {
+    return s.charAt(0).toUpperCase() + s.slice(1);
+}
 
 export default class VerificationShowSas extends React.Component {
     static propTypes = {
         onDone: PropTypes.func.isRequired,
         onCancel: PropTypes.func.isRequired,
-        sas: PropTypes.string.isRequired,
+        sas: PropTypes.object.isRequired,
     }
 
     constructor() {
@@ -32,17 +36,55 @@ export default class VerificationShowSas extends React.Component {
 
     render() {
         const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
-        return <div className="mx_VerificationShowSas">
-            <p>{_t(
+        const EmojiText = sdk.getComponent('views.elements.EmojiText');
+
+        let sasDisplay;
+        let sasCaption;
+        if (this.props.sas.emoji) {
+            const emojiBlocks = this.props.sas.emoji.map(
+                (emoji, i) => <div className="mx_VerificationShowSas_emojiSas_block" key={i}>
+                    <div className="mx_VerificationShowSas_emojiSas_emoji">
+                        <EmojiText addAlt={false}>{emoji[0]}</EmojiText>
+                    </div>
+                    <div className="mx_VerificationShowSas_emojiSas_label">
+                        {_t(capFirst(emoji[1]))}
+                    </div>
+                </div>,
+            );
+            sasDisplay = <div className="mx_VerificationShowSas_emojiSas">
+                {emojiBlocks}
+            </div>;
+            sasCaption = _t(
+                "Verify this user by confirming the following emoji appear on their screen.",
+            );
+        } else if (this.props.sas.decimal) {
+            const numberBlocks = this.props.sas.decimal.map((num, i) => <span key={i}>
+                {num}
+            </span>);
+            sasDisplay = <div className="mx_VerificationShowSas_decimalSas">
+                {numberBlocks}
+            </div>;
+            sasCaption = _t(
                 "Verify this user by confirming the following number appears on their screen.",
-            )}</p>
+            );
+        } else {
+            return <div>
+                {_t("Unable to find a supported verification method.")}
+                <DialogButtons
+                    primaryButton={_t('Cancel')}
+                    hasCancel={false}
+                    onPrimaryButtonClick={this.props.onCancel}
+                />
+            </div>;
+        }
+
+        return <div className="mx_VerificationShowSas">
+            <p>{sasCaption}</p>
             <p>{_t(
                 "For maximum security, we recommend you do this in person or use another " +
                 "trusted means of communication.",
             )}</p>
-            <div className="mx_VerificationShowSas_sas">
-                {this.props.sas}
-            </div>
+            {sasDisplay}
             <DialogButtons onPrimaryButtonClick={this.props.onDone}
                 primaryButton={_t("Continue")}
                 hasCancel={true}
@@ -51,3 +93,69 @@ export default class VerificationShowSas extends React.Component {
         </div>;
     }
 }
+
+// List of Emoji strings from the js-sdk, for i18n
+_td("Dog");
+_td("Cat");
+_td("Lion");
+_td("Horse");
+_td("Unicorn");
+_td("Pig");
+_td("Elephant");
+_td("Rabbit");
+_td("Panda");
+_td("Rooster");
+_td("Penguin");
+_td("Turtle");
+_td("Fish");
+_td("Octopus");
+_td("Butterfly");
+_td("Flower");
+_td("Tree");
+_td("Cactus");
+_td("Mushroom");
+_td("Globe");
+_td("Moon");
+_td("Cloud");
+_td("Fire");
+_td("Banana");
+_td("Apple");
+_td("Strawberry");
+_td("Corn");
+_td("Pizza");
+_td("Cake");
+_td("Heart");
+_td("Smiley");
+_td("Robot");
+_td("Hat");
+_td("Glasses");
+_td("Spanner");
+_td("Santa");
+_td("Thumbs up");
+_td("Umbrella");
+_td("Hourglass");
+_td("Clock");
+_td("Gift");
+_td("Light bulb");
+_td("Book");
+_td("Pencil");
+_td("Paperclip");
+_td("Scisors");
+_td("Padlock");
+_td("Key");
+_td("Hammer");
+_td("Telephone");
+_td("Flag");
+_td("Train");
+_td("Bicycle");
+_td("Aeroplane");
+_td("Rocket");
+_td("Trophy");
+_td("Ball");
+_td("Guitar");
+_td("Trumpet");
+_td("Bell");
+_td("Anchor");
+_td("Headphones");
+_td("Folder");
+_td("Pin");
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 6fd9b4d190..0ff2df46ad 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -326,10 +326,75 @@
     "You've successfully verified this user.": "You've successfully verified this user.",
     "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.",
     "Got It": "Got It",
+    "Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.",
     "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.",
+    "Unable to find a supported verification method.": "Unable to find a supported verification method.",
     "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.",
-    "To continue, click on each pair to confirm it's correct.": "To continue, click on each pair to confirm it's correct.",
     "Continue": "Continue",
+    "Dog": "Dog",
+    "Cat": "Cat",
+    "Lion": "Lion",
+    "Horse": "Horse",
+    "Unicorn": "Unicorn",
+    "Pig": "Pig",
+    "Elephant": "Elephant",
+    "Rabbit": "Rabbit",
+    "Panda": "Panda",
+    "Rooster": "Rooster",
+    "Penguin": "Penguin",
+    "Turtle": "Turtle",
+    "Fish": "Fish",
+    "Octopus": "Octopus",
+    "Butterfly": "Butterfly",
+    "Flower": "Flower",
+    "Tree": "Tree",
+    "Cactus": "Cactus",
+    "Mushroom": "Mushroom",
+    "Globe": "Globe",
+    "Moon": "Moon",
+    "Cloud": "Cloud",
+    "Fire": "Fire",
+    "Banana": "Banana",
+    "Apple": "Apple",
+    "Strawberry": "Strawberry",
+    "Corn": "Corn",
+    "Pizza": "Pizza",
+    "Cake": "Cake",
+    "Heart": "Heart",
+    "Smiley": "Smiley",
+    "Robot": "Robot",
+    "Hat": "Hat",
+    "Glasses": "Glasses",
+    "Wrench": "Wrench",
+    "Santa": "Santa",
+    "Thumbs up": "Thumbs up",
+    "Umbrella": "Umbrella",
+    "Hourglass": "Hourglass",
+    "Clock": "Clock",
+    "Gift": "Gift",
+    "Light bulb": "Light bulb",
+    "Book": "Book",
+    "Pencil": "Pencil",
+    "Paperclip": "Paperclip",
+    "Scisors": "Scisors",
+    "Padlock": "Padlock",
+    "Key": "Key",
+    "Hammer": "Hammer",
+    "Telephone": "Telephone",
+    "Flag": "Flag",
+    "Train": "Train",
+    "Bicycle": "Bicycle",
+    "Aeroplane": "Aeroplane",
+    "Rocket": "Rocket",
+    "Trophy": "Trophy",
+    "Ball": "Ball",
+    "Guitar": "Guitar",
+    "Trumpet": "Trumpet",
+    "Bell": "Bell",
+    "Anchor": "Anchor",
+    "Headphones": "Headphones",
+    "Folder": "Folder",
+    "Pin": "Pin",
     "Failed to upload profile picture!": "Failed to upload profile picture!",
     "Upload new:": "Upload new:",
     "No display name": "No display name",