From e34960392cf93d5f71888980c6a04ee28d9ea9b2 Mon Sep 17 00:00:00 2001 From: Augier <contact@c-henry.fr> Date: Sat, 20 Feb 2016 17:13:22 +0100 Subject: [PATCH] Code simplifications and typo --- .../app/views/publisher/mention_view.js | 119 ++++++++++++------ .../javascripts/app/views/search_base_view.js | 34 ++--- .../javascripts/app/views/search_view.js | 2 +- app/assets/javascripts/main.js | 1 - app/assets/javascripts/mentions.js | 48 ------- .../app/views/publisher_mention_view_spec.js | 11 +- .../app/views/search_base_view_spec.js | 22 ++-- .../javascripts/app/views/search_view_spec.js | 7 +- 8 files changed, 118 insertions(+), 126 deletions(-) delete mode 100644 app/assets/javascripts/mentions.js diff --git a/app/assets/javascripts/app/views/publisher/mention_view.js b/app/assets/javascripts/app/views/publisher/mention_view.js index 4383da759c..27bb929905 100644 --- a/app/assets/javascripts/app/views/publisher/mention_view.js +++ b/app/assets/javascripts/app/views/publisher/mention_view.js @@ -32,6 +32,27 @@ app.views.PublisherMention = app.views.SearchBase.extend({ "paste #status_message_fake_text": "onInputBoxPaste" }, + /** + * Performs setup of the setup of the plugin. + * + * this.mentionsCollection: used to keep track of the people mentionned in the post + * this.inputBuffer: buffer to keep track of the text currently being typed. It is cleared + * each time a mention has been processed. + * See this#onInputBoxKeyPress + * this.currentDataQuery: contains the query for the search engine + * + * The plugin initilizes two different elements that will contain the text of the post: + * + * this.elmInputBox: hidden element which keeps track of typed text formatted following + * the mentioning syntax given by this.settings.templates#mentionItemSyntax + * For instance, if the user writes the text "Hello @user1", the resulting hidden + * text will be: "Hello @{user1 ; user1@pod.tld}. This is the text that is submitted + * to the pod when the user posts. + * this.elmMentionsOverlay: contains the text that will be displayed to the user + * + * this.mentionChar is a invisible caracter used to mark the name of the mentionned person + * during the process. See this#processMention + */ initialize: function(){ this.mentionsCollection = []; this.inputBuffer = []; @@ -39,28 +60,32 @@ app.views.PublisherMention = app.views.SearchBase.extend({ this.mentionChar = "\u200B"; this.elmInputBox = this.$el.find("#status_message_fake_text"); - this.elmInputWrapper = this.elmInputBox.parent(); - this.elmWrapperBox = $(this.settings.templates.wrapper()); - this.elmInputBox.wrapAll(this.elmWrapperBox); - this.elmWrapperBox = this.elmInputWrapper.find("> div").first(); + var elmInputWrapper = this.elmInputBox.parent(); + this.elmInputBox.wrapAll($(this.settings.templates.wrapper())); + var elmWrapperBox = elmInputWrapper.find("> div").first(); this.elmMentionsOverlay = $(this.settings.templates.mentionsOverlay()); - this.elmMentionsOverlay.prependTo(this.elmWrapperBox); + this.elmMentionsOverlay.prependTo(elmWrapperBox); - this.bindMentionningEvents(); - this.completeSetup(this.getTypeaheadInput()); + this.bindMentioningEvents(); + app.views.SearchBase.prototype.initialize.call(this, {typeaheadElement: this.getTypeaheadInput()}); this.$el.find(".twitter-typeahead").css({position: "absolute", left: "-1px"}); this.$el.find(".twitter-typeahead .tt-menu").css("margin-top", 0); }, - bindMentionningEvents: function(){ + /** + * Attach events to Typeahead. + */ + bindMentioningEvents: function(){ var self = this; + // Process mention when the user selects a result. this.getTypeaheadInput().on("typeahead:select", function(evt, datum){ self.processMention(datum); self.resetMentionBox(); self.addToFilteredResults(datum); }); + // Highlight the first result when the results dropdown opens this.getTypeaheadInput().on("typeahead:render", function(){ self.select(self.$(".tt-menu .tt-suggestion").first()); }); @@ -70,6 +95,10 @@ app.views.PublisherMention = app.views.SearchBase.extend({ this.inputBuffer.length = 0; }, + /** + * Cleans the collection of mentionned people. Rejects every item who's name + * is not present in the post an falsy values (false, null, "", etc.) + */ updateMentionsCollection: function(){ var inputText = this.getInputBoxValue(); @@ -79,6 +108,11 @@ app.views.PublisherMention = app.views.SearchBase.extend({ this.mentionsCollection = _.compact(this.mentionsCollection); }, + /** + * Adds mention to the mention collection + * @param person Mentionned person. + * JSON object of form { handle: <diaspora handle>, name: <name>, ... } + */ addMention: function(person){ if(!person || !person.name || !person.handle){ return; @@ -90,12 +124,25 @@ app.views.PublisherMention = app.views.SearchBase.extend({ this.mentionsCollection.push(person); }, + /** + * Process the text to add mention to the post. Every @mention in the text + * will be replaced by this.mentionChar + mention.name. This temporary text + * will then be replaced by final syntax in this#updateValues + * + * For instance if the user types text "Hello @use" and selects result user1, + * The text will be transformed to "Hello \u200Buser1" before calling this#updateValues + * + * @param mention Mentionned person. + * JSON object of form { handle: <diaspora handle>, name: <name>, ... } + */ processMention: function(mention){ var currentMessage = this.getInputBoxValue(); var currentCaretPosition = this.getCaretPosition(); var startCaretPosition = currentCaretPosition - (this.currentDataQuery.length + 1); + // Extracts the text before the mention and the text after it. + // startEndIndex is the position where to place the caret at the en of the process var start = currentMessage.substr(0, startCaretPosition); var end = currentMessage.substr(currentCaretPosition, currentMessage.length); var startEndIndex = (start + mention.name).length + 1; @@ -107,16 +154,24 @@ app.views.PublisherMention = app.views.SearchBase.extend({ this.currentDataQuery = ""; this.resetMentionBox(); - // Mentions & syntax message + // Autocompletes mention and updates message text var updatedMessageText = start + this.mentionChar + mention.name + end; this.elmInputBox.val(updatedMessageText); this.updateValues(); - // Set correct focus and selection + // Set correct focus and caret position this.elmInputBox.focus(); this.setCaretPosition(startEndIndex); }, + /** + * Replaces every combination of this.mentionChar + mention.name by the + * correct syntax for both hidden text and visible one. + * + * For instance, the text "Hello \u200Buser1" will be tranformed to + * "Hello @{user1 ; user1@pod.tld}" in the hidden element and + * "Hello <strong><span>user1</span></strong>" in the element visible to the user. + */ updateValues: function(){ var syntaxMessage = this.getInputBoxValue(); var mentionText = this.getInputBoxValue(); @@ -162,34 +217,21 @@ app.views.PublisherMention = app.views.SearchBase.extend({ }); }, - selectNextResult: function(evt){ - if(this.isVisible()){ - evt.preventDefault(); - evt.stopPropagation(); - } - - if(this.getSelected().size() !== 1 || this.getSelected().next().size() !== 1){ - this.getSelected().removeClass("tt-cursor"); - this.$el.find(".tt-suggestion").first().addClass("tt-cursor"); - } - else{ - this.getSelected().removeClass("tt-cursor").next().addClass("tt-cursor"); + /** + * Selects next or previous result when result dropdown is open and + * user press up and down arrows. + */ + onArrowKeysPress: function(e){ + if(!this.isVisible() || (e.keyCode !== this.KEYS.UP && e.keyCode !== this.KEYS.DOWN)){ + return; } - }, - selectPreviousResult: function(evt){ - if(this.isVisible()){ - evt.preventDefault(); - evt.stopPropagation(); - } + e.preventDefault(); + e.stopPropagation(); - if(this.getSelected().size() !== 1 || this.getSelected().prev().size() !== 1){ - this.getSelected().removeClass("tt-cursor"); - this.$el.find(".tt-suggestion").last().addClass("tt-cursor"); - } - else{ - this.getSelected().removeClass("tt-cursor").prev().addClass("tt-cursor"); - } + this.getTypeaheadInput().typeahead("activate"); + this.getTypeaheadInput().typeahead("open"); + this.getTypeaheadInput().trigger($.Event("keydown", {keyCode: e.keyCode})); }, onInputBoxKeyPress: function(e){ @@ -200,6 +242,9 @@ app.views.PublisherMention = app.views.SearchBase.extend({ } }, + /** + * Listens for user input and opens results dropdown when input contains the trigger char + */ onInputBoxInput: function(){ this.updateValues(); this.updateMentionsCollection(); @@ -244,10 +289,8 @@ app.views.PublisherMention = app.views.SearchBase.extend({ this.resetMentionBox(); break; case this.KEYS.UP: - this.selectPreviousResult(e); - break; case this.KEYS.DOWN: - this.selectNextResult(e); + this.onArrowKeysPress(e); break; case this.KEYS.RETURN: case this.KEYS.TAB: diff --git a/app/assets/javascripts/app/views/search_base_view.js b/app/assets/javascripts/app/views/search_base_view.js index 65bf83fcd7..6b2dfae297 100644 --- a/app/assets/javascripts/app/views/search_base_view.js +++ b/app/assets/javascripts/app/views/search_base_view.js @@ -1,6 +1,6 @@ app.views.SearchBase = app.views.Base.extend({ - completeSetup: function(typeaheadElement){ - this.typeaheadElement = $(typeaheadElement); + initialize: function(options){ + this.typeaheadElement = $(options.typeaheadElement); this.setupBloodhound(); this.setupTypeahead(); this.bindSelectionEvents(); @@ -59,21 +59,21 @@ app.views.SearchBase = app.views.Base.extend({ setupTypeahead: function() { this.typeaheadElement.typeahead({ - hint: false, - highlight: true, - minLength: 2 - }, - { - name: "search", - display: "name", - limit: 5, - source: this.bloodhound.customSearch, - templates: { - /* jshint camelcase: false */ - suggestion: HandlebarsTemplates.search_suggestion_tpl - /* jshint camelcase: true */ - } - }); + hint: false, + highlight: true, + minLength: 2 + }, + { + name: "search", + display: "name", + limit: 5, + source: this.searchFormAction !== undefined ? this.bloodhound : this.bloodhound.customSearch, + templates: { + /* jshint camelcase: false */ + suggestion: HandlebarsTemplates.search_suggestion_tpl + /* jshint camelcase: true */ + } + }); }, transformBloodhoundResponse: function(response) { diff --git a/app/assets/javascripts/app/views/search_view.js b/app/assets/javascripts/app/views/search_view.js index 65ed775c0d..ec183b0945 100644 --- a/app/assets/javascripts/app/views/search_view.js +++ b/app/assets/javascripts/app/views/search_view.js @@ -8,7 +8,7 @@ app.views.Search = app.views.SearchBase.extend({ initialize: function(){ this.searchFormAction = this.$el.attr("action"); - this.completeSetup(this.getTypeaheadElement()); + app.views.SearchBase.prototype.initialize.call(this, {typeaheadElement: this.getTypeaheadElement()}); this.bindMoreSelectionEvents(); this.getTypeaheadElement().on("typeahead:select", this.suggestionSelected); }, diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 458824bd1e..2a2ef7c1db 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -38,7 +38,6 @@ //= require_tree ./helpers //= require_tree ./pages //= require_tree ./widgets -//= require mentions //= require bootstrap //= require osmlocator //= require bootstrap-switch diff --git a/app/assets/javascripts/mentions.js b/app/assets/javascripts/mentions.js deleted file mode 100644 index f805b97fae..0000000000 --- a/app/assets/javascripts/mentions.js +++ /dev/null @@ -1,48 +0,0 @@ -// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - -var Mentions = { - initialize: function(mentionsInput) { - 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 = 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, - - onDataRequest: function(mode, query, callback) { - var filteredResults = _.filter(Mentions.contacts, function(item) { return item.name.toLowerCase().indexOf(query.toLowerCase()) > -1 }); - - callback.call(this, filteredResults.slice(0,5)); - }, - - templates: { - mentionItemSyntax: _.template("@{<%= name %> ; <%= handle %>}") - } - } -}; -// @license-end diff --git a/spec/javascripts/app/views/publisher_mention_view_spec.js b/spec/javascripts/app/views/publisher_mention_view_spec.js index f104704942..f67aad2159 100644 --- a/spec/javascripts/app/views/publisher_mention_view_spec.js +++ b/spec/javascripts/app/views/publisher_mention_view_spec.js @@ -8,8 +8,8 @@ describe("app.views.PublisherMention", function(){ describe("initialize", function(){ beforeEach(function(){ - spyOn(app.views.PublisherMention.prototype, "completeSetup").and.callThrough(); - spyOn(app.views.PublisherMention.prototype, "bindMentionningEvents").and.callThrough(); + spyOn(app.views.SearchBase.prototype, "initialize").and.callThrough(); + spyOn(app.views.PublisherMention.prototype, "bindMentioningEvents").and.callThrough(); this.view = new app.views.PublisherMention({ el: "#publisher" }); }); @@ -21,8 +21,9 @@ describe("app.views.PublisherMention", function(){ }); it("calls completeSetup", function(){ - expect(app.views.PublisherMention.prototype.completeSetup).toHaveBeenCalledWith(this.view.getTypeaheadInput()); - expect(app.views.PublisherMention.prototype.bindMentionningEvents).toHaveBeenCalled(); + expect(app.views.SearchBase.prototype.initialize) + .toHaveBeenCalledWith({typeaheadElement: this.view.getTypeaheadInput()}); + expect(app.views.PublisherMention.prototype.bindMentioningEvents).toHaveBeenCalled(); }); it("initializes html elements", function(){ @@ -33,7 +34,7 @@ describe("app.views.PublisherMention", function(){ }); }); - describe("bindMentionningEvents", function(){ + describe("bindMentioningEvents", function(){ beforeEach(function(){ spyOn(app.views.PublisherMention.prototype, "processMention"); spyOn(app.views.PublisherMention.prototype, "resetMentionBox"); diff --git a/spec/javascripts/app/views/search_base_view_spec.js b/spec/javascripts/app/views/search_base_view_spec.js index 36bd65912b..d0e633305b 100644 --- a/spec/javascripts/app/views/search_base_view_spec.js +++ b/spec/javascripts/app/views/search_base_view_spec.js @@ -1,36 +1,32 @@ describe("app.views.SearchBase", function() { beforeEach(function(){ spec.content().html( - "<form action='/search' id='search_people_form'><input id='q' name='q' type='search'></input></form>" + "<form action='/search' id='search_people_form'><input id='q' name='q' type='search'/></form>" ); }); - describe("completeSetup", function(){ + describe("initialize", function(){ it("calls setupBloodhound", function(){ spyOn(app.views.SearchBase.prototype, "setupBloodhound").and.callThrough(); - var view = new app.views.SearchBase({el: "#search_people_form"}); - view.completeSetup(); + new app.views.SearchBase({el: "#search_people_form"}); expect(app.views.SearchBase.prototype.setupBloodhound).toHaveBeenCalled(); }); it("calls setupTypeahead", function(){ spyOn(app.views.SearchBase.prototype, "setupTypeahead"); - var view = new app.views.SearchBase({el: "#search_people_form"}); - view.completeSetup(); + new app.views.SearchBase({el: "#search_people_form"}); expect(app.views.SearchBase.prototype.setupTypeahead).toHaveBeenCalled(); }); it("calls bindSelectionEvents", function(){ spyOn(app.views.SearchBase.prototype, "bindSelectionEvents"); - var view = new app.views.SearchBase({el: "#search_people_form"}); - view.completeSetup(); + new app.views.SearchBase({el: "#search_people_form"}); expect(app.views.SearchBase.prototype.bindSelectionEvents).toHaveBeenCalled(); }); it("initializes the results to filter", function(){ spyOn(app.views.SearchBase.prototype, "bindSelectionEvents"); var view = new app.views.SearchBase({el: "#search_people_form"}); - view.completeSetup(); expect(view.resultsTofilter.length).toBe(0); }); }); @@ -44,7 +40,7 @@ describe("app.views.SearchBase", function() { context("when performing a local search with 1 filtered result", function(){ beforeEach(function(){ - this.view.completeSetup(this.view.$("#q")); + this.view.initialize({typeaheadElement: this.view.$("#q")}); this.view.bloodhound.add([ {"id":1,"guid":"1","name":"user1","handle":"user1@pod.tld","url":"/people/1"}, {"id":2,"guid":"2","name":"user2","handle":"user2@pod.tld","url":"/people/2"} @@ -106,7 +102,7 @@ describe("app.views.SearchBase", function() { describe("bindSelectionEvents", function(){ beforeEach(function() { this.view = new app.views.SearchBase({ el: "#search_people_form" }); - this.view.completeSetup(this.view.$("#q")); + this.view.initialize({typeaheadElement: this.view.$("#q")}); this.view.bloodhound.add([ {"person": true, "name":"user1", "handle":"user1@pod.tld"}, {"person": true, "name":"user2", "handle":"user2@pod.tld"} @@ -147,7 +143,7 @@ describe("app.views.SearchBase", function() { describe("addToFilteredResults", function(){ beforeEach(function() { this.view = new app.views.SearchBase({ el: "#search_people_form" }); - this.view.completeSetup(this.view.$("#q")); + this.view.initialize({typeaheadElement: this.view.$("#q")}); }); context("when item is a person", function(){ @@ -168,7 +164,7 @@ describe("app.views.SearchBase", function() { describe("clearFilteredResults", function(){ beforeEach(function() { this.view = new app.views.SearchBase({ el: "#search_people_form" }); - this.view.completeSetup(this.view.$("#q")); + this.view.initialize({typeaheadElement: this.view.$("#q")}); }); context("clear filtered results", function(){ diff --git a/spec/javascripts/app/views/search_view_spec.js b/spec/javascripts/app/views/search_view_spec.js index 70525ae3bb..001fabddde 100644 --- a/spec/javascripts/app/views/search_view_spec.js +++ b/spec/javascripts/app/views/search_view_spec.js @@ -6,10 +6,11 @@ describe("app.views.Search", function(){ }); describe("initialize", function(){ - it("calls completeSetup", function(){ - spyOn(app.views.Search.prototype, "completeSetup").and.callThrough(); + it("calls app.views.SearchBase.prototype.initialize", function(){ + spyOn(app.views.SearchBase.prototype, "initialize").and.callThrough(); var view = new app.views.Search({el: "#search_people_form"}); - expect(app.views.Search.prototype.completeSetup).toHaveBeenCalledWith(view.getTypeaheadElement()); + expect(app.views.SearchBase.prototype.initialize) + .toHaveBeenCalledWith({typeaheadElement: view.getTypeaheadElement()}); }); it("calls bindMoreSelectionEvents", function(){ -- GitLab