Bug 1409901 - nicks inside messages should be detected and colored, r=clokep.
authorFlorian Quèze <florian@queze.net>
Fri, 27 Oct 2017 11:58:09 +0200
changeset 29267 2dc8f2e4802db4d61212b928e6d38628dc300baa
parent 29266 83f249160f6cc1239c3234a7b9802b58133c1f30
child 29268 98161291e20d4549da37d2a80608c4f70f3f8506
push id2068
push userclokep@gmail.com
push dateMon, 13 Nov 2017 19:02:14 +0000
treeherdercomm-beta@9c7e7ce8672b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersclokep
bugs1409901
Bug 1409901 - nicks inside messages should be detected and colored, r=clokep.
mail/components/im/all-im.js
mail/components/im/content/imconversation.xml
--- a/mail/components/im/all-im.js
+++ b/mail/components/im/all-im.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 pref("messenger.options.messagesStyle.theme", "mail");
 pref("messenger.options.emoticonsTheme", "messenger-emoticons");
 pref("messenger.conversations.textbox.autoResize", true);
 pref("messenger.conversations.doubleClickToReply", true);
+pref("messenger.conversations.showNicks", true);
 pref("purple.debug.loglevel", 3);
 pref("chat.twitter.consumerKey", "7Gzq35FbZLYJQ9n3sJmJw");
 pref("chat.twitter.consumerSecret", "fz0dXr3yul1uncHNf0vITTiIOtSKAVCvopKDrqR1Mo");
 
 // Limit the number of gloda IM results
 pref("mailnews.database.global.search.im.limit", 1000);
--- a/mail/components/im/content/imconversation.xml
+++ b/mail/components/im/content/imconversation.xml
@@ -1116,26 +1116,32 @@
      </method>
 
      <!-- Add a buddy in the visible list of participants -->
      <method name="addBuddy">
        <parameter name="aBuddy"/>
        <body>
        <![CDATA[
          var name = aBuddy.name;
+         if (!name)
+           throw "The empty string isn't a valid nick.";
          if (this.buddies.has(name))
-           throw "Adding a chat buddy twice?!";
+           throw "Adding chat buddy " + name + " twice?!";
+
+         this.trackNick(name);
+
          var item = document.createElement("listitem");
          item.chatBuddy = aBuddy;
          item.setAttribute("class", "listitem-iconic");
          item.setAttribute("label", name);
          this.setBuddyAttributes(item);
 
          var color = this._computeColor(name);
          var style = "color: hsl(" + color + ", 100%, 40%);";
+         item.colorStyle = style;
          item.setAttribute("style", style);
          if (!this._isBuddyActive(name))
            item.setAttribute("inactive", "true");
          item.color = color;
          this.buddies.set(name, item);
 
          // Insert item at the right position
          this.addNick(item);
@@ -1173,29 +1179,34 @@
 
      <!-- Update a buddy in the visible list of participants -->
      <method name="updateBuddy">
        <parameter name="aBuddy"/>
        <parameter name="aOldName"/>
        <body>
        <![CDATA[
          var name = aBuddy.name;
+         if (!name)
+           throw "The empty string isn't a valid nick.";
+
          if (!aOldName) {
            // If aOldName is null, we are changing the flags of the buddy
            let item = this.buddies.get(name);
            item.chatBuddy = aBuddy;
            this.setBuddyAttributes(item);
            return;
          }
 
          if (this._isBuddyActive(aOldName)) {
            delete this._activeBuddies[aOldName];
            this._activeBuddies[aBuddy.name] = true;
          }
 
+         this.trackNick(name);
+
          if (!this._isConversationSelected)
            return;
 
          // Is aOldName is not null, then we are renaming the buddy
          if (!this.buddies.has(aOldName))
            throw "Updating a chat buddy that does not exist: " + aOldName;
 
          if (this.buddies.has(name))
@@ -1223,16 +1234,91 @@
          this.buddies.get(aName).remove();
          this.buddies.delete(aName);
          if (this._isBuddyActive(aName))
            delete this._activeBuddies[aName];
        ]]>
        </body>
      </method>
 
+     <field name="_nickEscape">/[[\]{}()*+?.\\^$|]/g</field>
+     <method name="trackNick">
+       <parameter name="aNick"/>
+       <body>
+       <![CDATA[
+         if ("_showNickList" in this) {
+           this._showNickList[aNick.replace(this._nickEscape, "\\$&")] = true;
+           delete this._showNickRegExp;
+         }
+       ]]>
+       </body>
+     </method>
+
+     <method name="getShowNickModifier">
+       <body>
+       <![CDATA[
+         return (function (aNode) {
+           if (!("_showNickRegExp" in this)) {
+             if (!("_showNickList" in this)) {
+               this._showNickList = {};
+               for (let n of this.buddies.keys())
+                 this._showNickList[n.replace(this._nickEscape, "\\$&")] = true;
+             }
+
+             // The reverse sort ensures that if we have "foo" and "foobar",
+             // "foobar" will be matched first by the regexp.
+             let nicks = Object.keys(this._showNickList).sort().reverse().join("|");
+             if (nicks) {
+               // We use \W to match for word-boundaries, as \b will not match the
+               // nick if it starts/ends with \W characters.
+               // XXX Ideally we would use unicode word boundaries:
+               // http://www.unicode.org/reports/tr29/#Word_Boundaries
+               this._showNickRegExp = new RegExp("\\W(?:" + nicks + ")\\W");
+             }
+             else {
+               // nobody, disable...
+               this._showNickRegExp = {exec: () => null};
+               return 0;
+             }
+           }
+           let exp = this._showNickRegExp;
+           let result = 0;
+           let match;
+           // Add leading/trailing spaces to match at beginning and end of
+           // the string as well. (If we used regex ^ and $, match.index would
+           // not be reliable.)
+           while ((match = exp.exec(" " + aNode.data + " "))) {
+             // \W is not zero-length, but this is cancelled by the
+             // extra leading space here.
+             let nickNode = aNode.splitText(match.index);
+             // subtract the 2 \W's to get the length of the nick.
+             aNode = nickNode.splitText(match[0].length - 2);
+             // at this point, nickNode is a text node with only the text
+             // of the nick and aNode is a text node with the text after
+             // the nick. The text in aNode hasn't been processed yet.
+             let nick = nickNode.data;
+             let elt = aNode.ownerDocument.createElement("span");
+             elt.setAttribute("class", "ib-nick");
+             if (this.buddies.has(nick)) {
+               let buddy = this.buddies.get(nick);
+               elt.setAttribute("style", buddy.colorStyle);
+               elt.setAttribute("nickColor", buddy.color);
+             }
+             else
+               elt.setAttribute("left", "true");
+             nickNode.parentNode.replaceChild(elt, nickNode);
+             elt.textContent = nick;
+             result += 2;
+           }
+           return result;
+         }).bind(this);
+       ]]>
+       </body>
+     </method>
+
      <method name="updateTopic">
        <body>
        <![CDATA[
           let cti = document.getElementById("conv-top-info");
           let statusMessage = this.getElt("statusMessage");
           if (this._conv.topicSettable)
             cti.setAttribute("topicEditable", "true");
           else
@@ -1422,16 +1508,19 @@
            this.updateTopic();
            this.setAttribute("chat", "true");
            let cti = document.getElementById("conv-top-info");
            cti.setAttribute("displayName", this._conv.title);
            cti.setAttribute("status", "chat");
 
            this._activeBuddies = {};
            this.showParticipants();
+
+           if (Services.prefs.getBoolPref("messenger.conversations.showNicks"))
+             this.browser.addTextModifier(this.getShowNickModifier());
          }
 
          if (this.tab)
            this.tab.setAttribute("label", this._conv.title);
 
          this.findbar.browser = this.browser;
 
          if (!("Status" in window))