From 038599bf8f22a79ed98155acadf88fae0e73d8d0 Mon Sep 17 00:00:00 2001
From: Florian Staudacher <florian_staudacher@yahoo.de>
Date: Sun, 8 Jul 2012 21:32:04 +0200
Subject: [PATCH] update jquery.mentionsInput to latest version + make some
 tests a little nicer

---
 app/assets/javascripts/mentions.js            |  23 +++-
 app/views/people/show.html.haml               |   2 +-
 features/step_definitions/custom_web_steps.rb |   5 +-
 features/step_definitions/debug_steps.rb      |  17 ++-
 .../javascripts/jquery.mentionsInput.js       | 116 +++++++++++-------
 5 files changed, 113 insertions(+), 50 deletions(-)

diff --git a/app/assets/javascripts/mentions.js b/app/assets/javascripts/mentions.js
index b6e2304373..851337e389 100644
--- a/app/assets/javascripts/mentions.js
+++ b/app/assets/javascripts/mentions.js
@@ -3,12 +3,31 @@ var Mentions = {
     return mentionsInput.mentionsInput(Mentions.options);
   },
 
+  // pre-fetch the list of contacts for the current user.
+  // called by the initializer of the publisher, for faster ('offline')
+  // execution of the filtering for mentions
   fetchContacts : function(){
     Mentions.contacts || $.getJSON("/contacts", function(data) {
-      Mentions.contacts = data;
+      Mentions.contacts = Mentions.createList(data);
     });
   },
 
+  // creates a list of mentions out of a list of contacts
+  // @see _contactToMention
+  createList: function(contacts) {
+    return _.map(contacts, Mentions._contactToMention);
+  },
+
+  // takes a given contact object and modifies to fit the format
+  // expected by the jQuery.mentionsInput plugin.
+  // @see http://podio.github.com/jquery-mentions-input/
+  _contactToMention: function(contact) {
+    contact.value = contact.name;
+    return contact;
+  },
+
+  // default options for jQuery.mentionsInput
+  // @see http://podio.github.com/jquery-mentions-input/
   options: {
     elastic: false,
     minChars: 1,
@@ -20,7 +39,7 @@ var Mentions = {
     },
 
     templates: {
-      mentionItemSyntax: _.template("@{<%= mention.name %> ; <%= mention.handle %>}")
+      mentionItemSyntax: _.template("@{<%= name %> ; <%= handle %>}")
     }
   }
 };
diff --git a/app/views/people/show.html.haml b/app/views/people/show.html.haml
index 206cfad3cb..6ee3f982ac 100644
--- a/app/views/people/show.html.haml
+++ b/app/views/people/show.html.haml
@@ -6,7 +6,7 @@
 - content_for :head do
   = javascript_include_tag :people
   :javascript
-    Mentions.options.prefillMention = #{@person.to_json};
+    Mentions.options.prefillMention = Mentions._contactToMention(#{@person.to_json});
 
 - content_for :page_title do
   = @person.name
diff --git a/features/step_definitions/custom_web_steps.rb b/features/step_definitions/custom_web_steps.rb
index 298ba941cd..11cc641ed9 100644
--- a/features/step_definitions/custom_web_steps.rb
+++ b/features/step_definitions/custom_web_steps.rb
@@ -38,8 +38,9 @@ Then /^the publisher should be expanded$/ do
 end
 
 When /^I append "([^"]*)" to the publisher$/ do |stuff|
-  previous_value = page.find("#status_message_fake_text").value
-  fill_in "status_message_fake_text", :with => previous_value +  " " + stuff
+  elem = find('#status_message_fake_text')
+  elem.native.send_keys ' ' + stuff
+
   wait_until do
     page.find("#status_message_text").value.match(/#{stuff}/)
   end
diff --git a/features/step_definitions/debug_steps.rb b/features/step_definitions/debug_steps.rb
index 2d5e17d433..58a36d5333 100644
--- a/features/step_definitions/debug_steps.rb
+++ b/features/step_definitions/debug_steps.rb
@@ -1,12 +1,21 @@
+module DebuggingCukeHelpers
+  def start_debugging
+    require 'ruby-debug'
+    debugger
+    true
+  end
+end
+
+World(DebuggingCukeHelpers)
+
+
 When 'I debug' do
-  require 'ruby-debug'
-  debugger
-  true
+  start_debugging
 end
 
 When /^I wait for (\d+) seconds?$/ do |seconds|
   sleep seconds.to_i
-  warn "DELETEME - this step is for debugging only\n"
+  warn "\nDELETEME - this step is for debugging, only!\n"
 end
 
 When /^I open the error console$/ do
diff --git a/vendor/assets/javascripts/jquery.mentionsInput.js b/vendor/assets/javascripts/jquery.mentionsInput.js
index 274f24404e..40efd9db0f 100644
--- a/vendor/assets/javascripts/jquery.mentionsInput.js
+++ b/vendor/assets/javascripts/jquery.mentionsInput.js
@@ -1,6 +1,6 @@
 /*
  * Mentions Input
- * Version 1.0
+ * Version 1.0.2
  * Written by: Kenneth Auchenberg (Podio)
  *
  * Using underscore.js
@@ -28,8 +28,8 @@
       autocompleteListItemAvatar : _.template('<img  src="<%= avatar %>" />'),
       autocompleteListItemIcon   : _.template('<div class="icon <%= icon %>"></div>'),
       mentionsOverlay            : _.template('<div class="mentions"><div></div></div>'),
-      mentionItemSyntax          : _.template('@[<%= name %>](<%= type %>:<%= id %>)'),
-      mentionItemHighlight       : _.template('<strong><span><%= name %></span></strong>')
+      mentionItemSyntax          : _.template('@[<%= value %>](<%= type %>:<%= id %>)'),
+      mentionItemHighlight       : _.template('<strong><span><%= value %></span></strong>')
     }
   };
 
@@ -56,18 +56,24 @@
           domNode.focus();
         }
       }
+    },
+    rtrim: function(string) {
+      return string.replace(/\s+$/,"");
     }
   };
 
-  var MentionsInput = function (input) {
-    var settings;
-    var elmInputBox, elmInputWrapper, elmAutocompleteList, elmWrapperBox, elmMentionsOverlay, elmActiveAutoCompleteItem;
+  var MentionsInput = function (settings) {
+
+    var domInput, elmInputBox, elmInputWrapper, elmAutocompleteList, elmWrapperBox, elmMentionsOverlay, elmActiveAutoCompleteItem;
     var mentionsCollection = [];
+    var autocompleteItemCollection = {};
     var inputBuffer = [];
     var currentDataQuery = '';
 
+    settings = $.extend(true, {}, defaultSettings, settings );
+
     function initTextarea() {
-      elmInputBox = $(input);
+      elmInputBox = $(domInput);
 
       if (elmInputBox.attr('data-mentions-input') == 'true') {
         return;
@@ -83,16 +89,19 @@
       elmInputBox.bind('keypress', onInputBoxKeyPress);
       elmInputBox.bind('input', onInputBoxInput);
       elmInputBox.bind('click', onInputBoxClick);
+      elmInputBox.bind('blur', onInputBoxBlur);
 
-      if (settings.elastic) {
+      // Elastic textareas, internal setting for the Dispora guys
+      if( settings.elastic ) {
         elmInputBox.elastic();
       }
+
     }
 
     function initAutocomplete() {
       elmAutocompleteList = $(settings.templates.autocompleteList());
       elmAutocompleteList.appendTo(elmWrapperBox);
-      elmAutocompleteList.delegate('li', 'click', onAutoCompleteItemClick);
+      elmAutocompleteList.delegate('li', 'mousedown', onAutoCompleteItemClick);
     }
 
     function initMentionsOverlay() {
@@ -100,20 +109,20 @@
       elmMentionsOverlay.prependTo(elmWrapperBox);
     }
 
-    function updateNames() {
+    function updateValues() {
       var syntaxMessage = getInputBoxValue();
 
       _.each(mentionsCollection, function (mention) {
-        var textSyntax = settings.templates.mentionItemSyntax({ name : mention.name, type : 'contact', id : mention.id, mention: mention });
-
-        syntaxMessage = syntaxMessage.replace(mention.name, textSyntax);
+        var textSyntax = settings.templates.mentionItemSyntax(mention);
+        syntaxMessage = syntaxMessage.replace(mention.value, textSyntax);
       });
 
       var mentionText = utils.htmlEncode(syntaxMessage);
 
       _.each(mentionsCollection, function (mention) {
-        var textSyntax = settings.templates.mentionItemSyntax({ name : utils.htmlEncode(mention.name), type : 'contact', id : mention.id, mention : mention });
-        var textHighlight = settings.templates.mentionItemHighlight({ name : utils.htmlEncode(mention.name), mention : mention });
+        var formattedMention = _.extend({}, mention, {value: utils.htmlEncode(mention.value)});
+        var textSyntax = settings.templates.mentionItemSyntax(formattedMention);
+        var textHighlight = settings.templates.mentionItemHighlight(formattedMention);
 
         mentionText = mentionText.replace(textSyntax, textHighlight);
       });
@@ -133,12 +142,13 @@
       var inputText = getInputBoxValue();
 
       mentionsCollection = _.reject(mentionsCollection, function (mention, index) {
-        return !mention.name || inputText.indexOf(mention.name) == -1;
+        return !mention.value || inputText.indexOf(mention.value) == -1;
       });
       mentionsCollection = _.compact(mentionsCollection);
     }
 
     function addMention(mention) {
+
       var currentMessage = getInputBoxValue();
 
       // Using a regex to figure out positions
@@ -150,9 +160,7 @@
 
       var start = currentMessage.substr(0, startCaretPosition);
       var end = currentMessage.substr(currentCaretPosition, currentMessage.length);
-      var startEndIndex = (start + mention.name).length;
-
-      var updatedMessageText = start + mention.name + end;
+      var startEndIndex = (start + mention.value).length + 1;
 
       mentionsCollection.push(mention);
 
@@ -162,8 +170,9 @@
       hideAutoComplete();
 
       // Mentions & syntax message
+      var updatedMessageText = start + mention.value + ' ' + end;
       elmInputBox.val(updatedMessageText);
-      updateNames();
+      updateValues();
 
       // Set correct focus and selection
       elmInputBox.focus();
@@ -175,7 +184,8 @@
     }
 
     function onAutoCompleteItemClick(e) {
-      var mention = $(this).data("mention");
+      var elmTarget = $(this);
+      var mention = autocompleteItemCollection[elmTarget.attr('data-uid')];
 
       addMention(mention);
 
@@ -186,21 +196,26 @@
       resetBuffer();
     }
 
+    function onInputBoxBlur(e) {
+      hideAutoComplete();
+    }
+
     function onInputBoxInput(e) {
-      updateNames();
+      updateValues();
       updateMentionsCollection();
       hideAutoComplete();
 
       var triggerCharIndex = _.lastIndexOf(inputBuffer, settings.triggerChar);
       if (triggerCharIndex > -1) {
         currentDataQuery = inputBuffer.slice(triggerCharIndex + 1).join('');
+        currentDataQuery = utils.rtrim(currentDataQuery);
 
         _.defer(_.bind(doSearch, this, currentDataQuery));
       }
     }
 
     function onInputBoxKeyPress(e) {
-      if(e.keyCode != KEY.BACKSPACE) {
+      if(e.keyCode !== KEY.BACKSPACE) {
         var typedValue = String.fromCharCode(e.which || e.keyCode);
         inputBuffer.push(typedValue);
       }
@@ -212,6 +227,14 @@
       if (e.keyCode == KEY.LEFT || e.keyCode == KEY.RIGHT || e.keyCode == KEY.HOME || e.keyCode == KEY.END) {
         // Defer execution to ensure carat pos has changed after HOME/END keys
         _.defer(resetBuffer);
+
+        // IE9 doesn't fire the oninput event when backspace or delete is pressed. This causes the highlighting
+        // to stay on the screen whenever backspace is pressed after a highlighed word. This is simply a hack
+        // to force updateValues() to fire when backspace/delete is pressed in IE9.
+        if (navigator.userAgent.indexOf("MSIE 9") > -1) {
+          _.defer(updateValues);
+        }
+
         return;
       }
 
@@ -247,7 +270,7 @@
         case KEY.RETURN:
         case KEY.TAB:
           if (elmActiveAutoCompleteItem && elmActiveAutoCompleteItem.length) {
-            elmActiveAutoCompleteItem.click();
+            elmActiveAutoCompleteItem.trigger('mousedown');
             return false;
           }
 
@@ -273,9 +296,9 @@
       elmAutocompleteList.show();
 
       // Filter items that has already been mentioned
-      var mentionedNames = _.pluck(mentionsCollection, 'name');
+      var mentionValues = _.pluck(mentionsCollection, 'value');
       results = _.reject(results, function (item) {
-        return _.include(mentionedNames, item.name);
+        return _.include(mentionValues, item.name);
       });
 
       if (!results.length) {
@@ -287,15 +310,19 @@
       var elmDropDownList = $("<ul>").appendTo(elmAutocompleteList).hide();
 
       _.each(results, function (item, index) {
+        var itemUid = _.uniqueId('mention_');
+
+        autocompleteItemCollection[itemUid] = _.extend({}, item, {value: item.name});
+
         var elmListItem = $(settings.templates.autocompleteListItem({
           'id'      : utils.htmlEncode(item.id),
           'display' : utils.htmlEncode(item.name),
           'type'    : utils.htmlEncode(item.type),
           'content' : utils.highlightTerm(utils.htmlEncode((item.name)), query)
-        })).data('mention', item);
+        })).attr('data-uid', itemUid);
 
-        if (index === 0) { 
-          selectAutoCompleteItem(elmListItem); 
+        if (index === 0) {
+          selectAutoCompleteItem(elmListItem);
         }
 
         if (settings.showAvatars) {
@@ -323,18 +350,27 @@
       }
     }
 
+    function resetInput() {
+      elmInputBox.val('');
+      mentionsCollection = [];
+      updateValues();
+    }
+
     // Public methods
     return {
-      init : function (options) {
-        settings = options;
+      init : function (domTarget) {
+
+        domInput = domTarget;
 
         initTextarea();
         initAutocomplete();
         initMentionsOverlay();
+        resetInput();
 
-        if(options.prefillMention) {
-          addMention(options.prefillMention);
+        if( settings.prefillMention ) {
+          addMention( settings.prefillMention );
         }
+
       },
 
       val : function (callback) {
@@ -347,9 +383,7 @@
       },
 
       reset : function () {
-        elmInputBox.val('');
-        mentionsCollection = [];
-        updateNames();
+        resetInput();
       },
 
       getMentions : function (callback) {
@@ -364,20 +398,20 @@
 
   $.fn.mentionsInput = function (method, settings) {
 
+    var outerArguments = arguments;
+
     if (typeof method === 'object' || !method) {
-      settings = $.extend(true, {}, defaultSettings, method);
+      settings = method;
     }
 
-    var outerArguments = arguments;
-
     return this.each(function () {
-      var instance = $.data(this, 'mentionsInput') || $.data(this, 'mentionsInput', new MentionsInput(this));
+      var instance = $.data(this, 'mentionsInput') || $.data(this, 'mentionsInput', new MentionsInput(settings));
 
       if (_.isFunction(instance[method])) {
         return instance[method].apply(this, Array.prototype.slice.call(outerArguments, 1));
 
       } else if (typeof method === 'object' || !method) {
-        return instance.init.call(this, settings);
+        return instance.init.call(this, this);
 
       } else {
         $.error('Method ' + method + ' does not exist');
-- 
GitLab