From 3efc31c68f511a3a4a9b4bb8a27fa333873e7663 Mon Sep 17 00:00:00 2001
From: danielvincent <danielgrippi@gmail.com>
Date: Fri, 4 Feb 2011 17:26:21 -0800
Subject: [PATCH] mention js working minus tracking keypresses with the hidden
 message field.

---
 app/models/person.rb               |   2 +-
 app/models/status_message.rb       |   2 +-
 public/javascripts/publisher.js    |  79 +++++++++++++++---
 spec/javascripts/publisher-spec.js | 128 +++++++++++++++++++----------
 4 files changed, 152 insertions(+), 59 deletions(-)

diff --git a/app/models/person.rb b/app/models/person.rb
index 82f0c81d98..4f360e81ef 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -160,7 +160,7 @@ class Person < ActiveRecord::Base
   end
 
   def as_json(opts={})
-   {:id => self.guid, :name => self.name, :avatar => self.profile.image_url(:thumb_small), :url => "/people/#{self.id}"}
+   {:id => self.guid, :name => self.name, :avatar => self.profile.image_url(:thumb_small), :handle => self.diaspora_handle, :url => "/people/#{self.id}"}
   end
 
   protected
diff --git a/app/models/status_message.rb b/app/models/status_message.rb
index 3a8b6935e0..d229602a41 100644
--- a/app/models/status_message.rb
+++ b/app/models/status_message.rb
@@ -37,7 +37,7 @@ class StatusMessage < Post
     identifiers = self.message.scan(regex).map do |match|
       match.last
     end
-    Person.where(:diaspora_handle => identifiers)
+    self.person.owner.contact_people.where(:diaspora_handle => identifiers)
   end
 
   def to_activity
diff --git a/public/javascripts/publisher.js b/public/javascripts/publisher.js
index 527b356b8f..0e79c3a3cc 100644
--- a/public/javascripts/publisher.js
+++ b/public/javascripts/publisher.js
@@ -36,11 +36,6 @@ var Publisher = {
     return Publisher.cachedHiddenInput;
   },
 
-  appendToHiddenField: function(evt){
-   Publisher.hiddenInput().val(
-    Publisher.input().val());
-  },
-
   autocompletion: {
     options : function(){return {
       minChars : 1,
@@ -58,13 +53,71 @@ var Publisher = {
           return row.name;
       }
     };},
+    hiddenMentionFromPerson : function(personData){
+      return "@{" + personData.name + "; " + personData.handle + "}";
+    },
 
-    onSelect :  function(input, data, formatted) {
-      addMentionToVisibleInput(input, formatted);
+    onSelect :  function(visibleInput, data, formatted) {
+      var visibleCursorIndex = visibleInput[0].selectionStart;
+      var visibleLoc = Publisher.autocompletion.addMentionToInput(visibleInput, visibleCursorIndex, formatted);
+
+
+      var hiddenCursorIndex = visibleCursorIndex + Publisher.autocompletion.mentionList.offsetFrom(visibleCursorIndex);
+      var hiddenLoc = Publisher.autocompletion.addMentionToInput(Publisher.hiddenInput(), hiddenCursorIndex, Publisher.autocompletion.hiddenMentionFromPerson(data));
+      var mention = { visibleStart: visibleLoc[0],
+                      visibleEnd  : visibleLoc[1],
+                      hiddenStart : hiddenLoc[0],
+                      hiddenEnd   : hiddenLoc[1]
+                    };
+    },
+
+    mentionList : {
+      mentions : [],
+      push : function(mention){
+        mention.offset = mention.hiddenEnd - mention.visibleEnd;
+        this.mentions.push(mention);
+      },
+      keypressAt : function(visibleCursorIndex){
+        var mentionIndex = this.mentionAt(visibleCursorIndex);
+        var mention = this.mentions[mentionIndex];
+        if(!mention){return;}
+        var visibleMentionString = Publisher.input().val().slice(mention.visibleStart, mention.visibleEnd);
+        var hiddenContent = Publisher.hiddenInput().val();
+        hiddenContent = hiddenContent.slice(0,mention.hiddenStart) +
+                        visibleMentionString +
+                        hiddenContent.slice(mention.hiddenEnd);
+        Publisher.hiddenInput().val(hiddenContent);
+
+        this.mentions.splice(mentionIndex, 1);
+      },
+      mentionAt : function(visibleCursorIndex){
+        for(i in this.mentions){
+          var mention = this.mentions[i];
+          if(visibleCursorIndex >= mention.visibleStart && visibleCursorIndex < mention.visibleEnd){
+            return i;
+          }
+          return false;
+        }
+      },
+      offsetFrom: function(visibleCursorIndex){
+        var mention = {visibleStart : -1, fake: true};
+        var currentMention;
+        for(i in this.mentions){
+          currentMention = this.mentions[i];
+          if(visibleCursorIndex >= currentMention.visibleStart &&
+             currentMention.visibleStart > mention.visibleStart){
+             mention = currentMention;
+          }
+        }
+        if(mention && !mention.fake){
+          return mention.offset;
+        }else{
+          return 0;
+        }
+      }
     },
 
-    addMentionToVisibleInput: function(input, formatted){
-      var cursorIndex = input[0].selectionStart;
+    addMentionToInput: function(input, cursorIndex, formatted){
       var inputContent = input.val();
 
       var stringLoc = Publisher.autocompletion.findStringToReplace(input.val(), cursorIndex);
@@ -73,12 +126,13 @@ var Publisher = {
       var stringEnd = inputContent.slice(stringLoc[1]);
 
       input.val(stringStart + formatted + stringEnd);
+      return [stringStart.length, stringStart.length + stringLoc[1]]
     },
 
     findStringToReplace: function(value, cursorIndex){
       var atLocation = value.lastIndexOf('@', cursorIndex);
       if(atLocation == -1){return [0,0];}
-      var nextAt = value.indexOf('@', cursorIndex+1);
+      var nextAt = value.indexOf(' @', cursorIndex+1);
 
       if(nextAt == -1){nextAt = value.length;}
       return [atLocation, nextAt];
@@ -115,6 +169,7 @@ var Publisher = {
   initialize: function() {
     Publisher.cachedForm = false;
     Publisher.cachedInput = false;
+    Publisher.cachedHiddenInput = false;
     $("div.public_toggle input").live("click", function(evt) {
       $("#publisher_service_icons").toggleClass("dim");
       if ($(this).attr('checked') == true) {
@@ -127,9 +182,7 @@ var Publisher = {
     };
 
     Publisher.autocompletion.initialize();
-    Publisher.updateHiddenField();
-    Publisher.form().find('#status_message_fake_message').bind('keydown',
-        Publisher.updateHiddenField);
+    Publisher.hiddenInput().val(Publisher.input().val());
     Publisher.form().find("textarea").bind("focus", function(evt) {
       Publisher.open();
       $(this).css('min-height', '42px');
diff --git a/spec/javascripts/publisher-spec.js b/spec/javascripts/publisher-spec.js
index e48aa9da59..4ba86e7e98 100644
--- a/spec/javascripts/publisher-spec.js
+++ b/spec/javascripts/publisher-spec.js
@@ -6,21 +6,6 @@
 describe("Publisher", function() {
 
     describe("initialize", function(){
-      it("calls updateHiddenField", function(){
-        spec.loadFixture('aspects_index_prefill');
-        spyOn(Publisher, 'updateHiddenField');
-        Publisher.initialize();
-        expect(Publisher.updateHiddenField).toHaveBeenCalled();
-      });
-
-      it("attaches updateHiddenField to the keydown handler on fake_message", function(){
-        spec.loadFixture('aspects_index_prefill');
-        spyOn(Publisher, 'updateHiddenField');
-        Publisher.initialize();
-        Publisher.form().find('#status_message_fake_message').keydown();
-        expect(Publisher.updateHiddenField.mostRecentCall.args[0].type).toBe('keydown');
-      });
-
       it("calls close when it does not have text", function(){
         spec.loadFixture('aspects_index');
         spyOn(Publisher, 'close');
@@ -67,17 +52,6 @@ describe("Publisher", function() {
         expect(Publisher.form().find(".options_and_submit:visible").length).toBe(0);
         });
     });
-    describe("updateHiddenField", function(){
-      beforeEach(function(){
-        spec.loadFixture('aspects_index_prefill');
-      });
-
-      it("copies the value of fake_message to message",function(){
-        Publisher.updateHiddenField();
-        expect(Publisher.form().find('#status_message_message').val()).toBe(
-          Publisher.form().find('#status_message_fake_message').val());
-      });
-    });
     describe("input", function(){
       beforeEach(function(){
         spec.loadFixture('aspects_index_prefill');
@@ -88,6 +62,8 @@ describe("Publisher", function() {
       });
     });
     describe("autocompletion", function(){
+      describe("onKeypress", function(){
+      });,
       describe("searchTermFromValue", function(){
         var func;
         beforeEach(function(){func = Publisher.autocompletion.searchTermFromValue;});
@@ -124,56 +100,120 @@ describe("Publisher", function() {
       describe("onSelect", function(){
 
       });
-      describe("addMentionToHiddenInput", function(){
-        var func;
-        var input;
 
+      describe("mentionList", function(){
+        var visibleInput, visibleVal,
+            hiddenInput, hiddenVal,
+            list,
+            func,
+            mention;
         beforeEach(function(){
           spec.loadFixture('aspects_index');
-          func = Publisher.autocompletion.addMentionToHiddenInput;
-          input = Publisher.input();
+          list = Publisher.autocompletion.mentionList;
+          func = list.keypressAt;
+          visibleInput = Publisher.input();
+          hiddenInput = Publisher.hiddenInput();
+          mention = { visibleStart : 0,
+                      visibleEnd   : 5,
+                      hiddenStart  : 0,
+                      hiddenEnd    : 21
+                    };
+          list.mentions = [];
+          list.push(mention);
+          visibleVal = "Danny loves testing javascript";
+          visibleInput.val(visibleVal);
+          hiddenVal = "@{Danny; dan@pod.org} loves testing javascript";
+          hiddenInput.val(hiddenVal);
+        });
+        describe("push", function(){
+          it("adds mention to mentions array", function(){
+            expect(list.mentions.length).toBe(1);
+            expect(list.mentions[0]).toBe(mention)
+          });
+        });
+        describe("mentionAt", function(){
+          it("returns the location of the mention at that location in the mentions array", function(){
+            expect(list.mentions[list.mentionAt(3)]).toBe(mention);
+          });
+          it("returns null if there is no mention", function(){
+            expect(list.mentionAt(8)).toBeFalsy();
+          });
+        });
+        describe("keypressAt", function(){
+          it("does nothing if there is no visible mention at that index", function(){
+            list.keypressAt(8);
+            expect(visibleInput.val()).toBe(visibleVal)
+            expect(hiddenInput.val()).toBe(hiddenVal)
+          });
+          it("deletes the mention from the hidden field if there is a mention", function(){
+            list.keypressAt(3);
+            expect(visibleInput.val()).toBe(visibleVal)
+            expect(hiddenInput.val()).toBe(visibleVal)
+          });
+          it("deletes the mention from the list", function(){
+            list.keypressAt(3);
+            expect(list.mentionAt(3)).toBeFalsy();
+          });
+          it("updates the offsets of the remaining mentions in the list");
+        });
+        describe("offsetFrom", function(){
+          var func;
+          beforeEach(function(){
+            func = list.offsetFrom;
+          });
+          it("returns the offset of the mention at that location", function(){
+            expect(list.offsetFrom(3)).toBe(mention.offset);
+          });
+          it("returns the offset of the previous mention if there is no mention there", function(){
+            expect(list.offsetFrom(10)).toBe(mention.offset);
+          });
+          it("returns 0 if there are no mentions", function(){
+            list.mentions = [];
+            expect(list.offsetFrom(8)).toBe(0);
+          });
         });
-
       });
 
-      describe("addMentionToVisibleInput", function(){
+
+
+      describe("addMentionToInput", function(){
         var func;
         var input;
         var replaceWith;
         beforeEach(function(){
           spec.loadFixture('aspects_index');
-          func = Publisher.autocompletion.addMentionToVisibleInput;
+          func = Publisher.autocompletion.addMentionToInput;
           input = Publisher.input();
           replaceWith = "Replace with this.";
         });
         it("replaces everything after an @ if the cursor is a word after that @", function(){
           input.val('not @dan grip');
-          func(input,  replaceWith);
-          input[0].selectionStart = 13;
+          var cursorIndex = 13;
+          func(input, cursorIndex, replaceWith);
           expect(input.val()).toBe('not ' + replaceWith);
         });
         it("replaces everything after an @ if the cursor is after that @", function(){
           input.val('not @dan grip');
-          input[0].selectionStart = 7;
-          func(input,  replaceWith);
+          var cursorIndex = 7;
+          func(input, cursorIndex, replaceWith);
           expect(input.val()).toBe('not ' + replaceWith);
         });
         it("replaces everything after an @ at the start of the line", function(){
           input.val('@dan grip');
-          input[0].selectionStart = 9;
-          func(input,  replaceWith);
+          var cursorIndex = 9;
+          func(input, cursorIndex, replaceWith);
           expect(input.val()).toBe(replaceWith);
         });
         it("replaces everything between @s if there are 2 @s and the cursor is between them", function(){
           input.val('@asdpo  aoisdj @asodk');
-          input[0].selectionStart = 8;
-          func(input,  replaceWith);
+          var cursorIndex = 8;
+          func(input, cursorIndex, replaceWith);
           expect(input.val()).toBe(replaceWith + ' @asodk');
         });
         it("replaces everything after the 2nd @ if there are 2 @s and the cursor after them", function(){
           input.val('@asod asdo @asd asok');
-          input[0].selectionStart = 15;
-          func(input,  replaceWith);
+          var cursorIndex = 15;
+          func(input, cursorIndex, replaceWith);
           expect(input.val()).toBe('@asod asdo ' + replaceWith);
         });
       });
-- 
GitLab