Commit bd707c0d authored by Jonne Haß's avatar Jonne Haß
Browse files

Merge pull request #6293 from svbergerem/typeahead

Replace jquery.autocomplete with typeahead.js
parents 25be9ecf e8acaa08
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ With the port to Bootstrap 3, app/views/terms/default.haml has a new structure.
* Improve mobile drawer transition [#6233](https://github.com/diaspora/diaspora/pull/6233)
* Remove unused header icons and an unused favicon  [#6283](https://github.com/diaspora/diaspora/pull/6283)
* Replace mobile icons for post interactions with Entypo icons [#6291](https://github.com/diaspora/diaspora/pull/6291)
* Replace jquery.autocomplete with typeahead.js [#6293](https://github.com/diaspora/diaspora/pull/6293)

## Bug fixes
* Destroy Participation when removing interactions with a post [#5852](https://github.com/diaspora/diaspora/pull/5852)
+1 −0
Original line number Diff line number Diff line
@@ -104,6 +104,7 @@ source "https://rails-assets.org" do
  gem "rails-assets-markdown-it-sub",                     "1.0.0"
  gem "rails-assets-markdown-it-sup",                     "1.0.0"
  gem "rails-assets-highlightjs",                         "8.6.0"
  gem "rails-assets-typeahead.js",                        "0.11.1"

  # jQuery plugins

+3 −0
Original line number Diff line number Diff line
@@ -569,6 +569,8 @@ GEM
    rails-assets-markdown-it-sub (1.0.0)
    rails-assets-markdown-it-sup (1.0.0)
    rails-assets-perfect-scrollbar (0.6.4)
    rails-assets-typeahead.js (0.11.1)
      rails-assets-jquery (>= 1.7)
    rails-deprecated_sanitizer (1.0.3)
      activesupport (>= 4.2.0.alpha)
    rails-dom-testing (1.0.6)
@@ -860,6 +862,7 @@ DEPENDENCIES
  rails-assets-markdown-it-sub (= 1.0.0)!
  rails-assets-markdown-it-sup (= 1.0.0)!
  rails-assets-perfect-scrollbar (= 0.6.4)!
  rails-assets-typeahead.js (= 0.11.1)!
  rails-i18n (= 4.0.4)
  rails-timeago (= 2.11.0)
  rails_admin (= 0.6.8)
+0 −13
Original line number Diff line number Diff line
@@ -6,11 +6,6 @@ app.views.Header = app.views.Base.extend({

  className: "dark-header",

  events: {
    "focusin #q": "toggleSearchActive",
    "focusout #q": "toggleSearchActive"
  },

  presenter: function() {
    return _.extend({}, this.defaultPresenter(), {
      podname: gon.appConfig.settings.podname
@@ -24,13 +19,5 @@ app.views.Header = app.views.Base.extend({
  },

  menuElement: function(){ return this.$("ul.dropdown"); },

  toggleSearchActive: function(evt){
    // jQuery produces two events for focus/blur (for bubbling)
    // don't rely on which event arrives first, by allowing for both variants
    var isActive = (_.indexOf(["focus","focusin"], evt.type) !== -1);
    $(evt.target).toggleClass("active", isActive);
    return false;
  }
});
// @license-end
+76 −52
Original line number Diff line number Diff line
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.Search = app.views.Base.extend({
  events: {
    "focusin #q": "toggleSearchActive",
    "focusout #q": "toggleSearchActive",
    "keypress #q": "inputKeypress",
  },

  initialize: function(){
    this.searchFormAction = this.$el.attr('action');
    this.searchInput = this.$('input[type="search"]');
    this.searchInputName = this.$('input[type="search"]').attr('name');
    this.searchInputHandle = this.$('input[type="search"]').attr('handle');
    this.options = {
      cacheLength: 15,
      delay: 800,
      extraParams: {limit: 4},
      formatItem: this.formatItem,
      formatResult: this.formatResult,
      max: 5,
      minChars: 2,
      onSelect: this.selectItemCallback,
      parse: this.parse,
      scroll: false,
      context: this
    };
    this.searchFormAction = this.$el.attr("action");
    this.searchInput = this.$("#q");

    var self = this;
    this.searchInput.autocomplete(self.searchFormAction + '.json',
        $.extend(self.options, { element: self.searchInput }));
    // constructs the suggestion engine
    this.setupBloodhound();
    this.setupTypeahead();
    this.searchInput.on("typeahead:select", this.suggestionSelected);
  },

  formatItem: function(row){
    if(typeof row.search !== 'undefined') { return Diaspora.I18n.t('search_for', row); }
    else {
      var item = '';
      if (row.avatar) { item += '<img src="' + row.avatar + '" class="avatar"/>'; }
      item += row.name;
      if (row.handle) { item += '<div class="search_handle">' + row.handle + '</div>'; }
      return item;
    }
  setupBloodhound: function() {
    this.bloodhound = new Bloodhound({
      datumTokenizer: function(datum) {
        var nameTokens = Bloodhound.tokenizers.nonword(datum.name);
        var handleTokens = datum.handle ? Bloodhound.tokenizers.nonword(datum.name) : [];
        return nameTokens.concat(handleTokens);
      },
      queryTokenizer: Bloodhound.tokenizers.whitespace,
      remote: {
        url: this.searchFormAction + ".json?q=%QUERY",
        wildcard: "%QUERY",
        transform: this.transformBloodhoundResponse
      },
      prefetch: {
        url: "/contacts.json",
        transform: this.transformBloodhoundResponse,
        cache: false
      },
      sufficient: 5
    });
  },

  formatResult: function(row){ return Handlebars.Utils.escapeExpression(row.name); },
  setupTypeahead: function() {
    this.searchInput.typeahead({
      hint: false,
      highlight: true,
      minLength: 2
    },
    {
      name: "search",
      display: "name",
      limit: 5,
      source: this.bloodhound,
      templates: {
        /* jshint camelcase: false */
        suggestion: HandlebarsTemplates.search_suggestion_tpl
        /* jshint camelcase: true */
      }
    });
  },

  parse: function(data) {
    var self = this.context;
  transformBloodhoundResponse: function(response) {
    var result = response.map(function(data) {
      // person
      if(data.handle) {
        data.person = true;
        return data;
      }

    var results =  data.map(function(person){
      person.name = self.formatResult(person);
      return {data : person, value : person.name};
      // hashtag
      return {
        hashtag: true,
        name: data.name,
        url: Routes.tag(data.name.substring(1))
      };
    });

    results.push({
      data: {
        name: self.searchInput.val(),
        url: self.searchFormAction + '?' + self.searchInputName + '=' + self.searchInput.val(),
        search: true
    return result;
  },
      value: self.searchInput.val()
    });

    return results;
  toggleSearchActive: function(evt) {
    // jQuery produces two events for focus/blur (for bubbling)
    // don't rely on which event arrives first, by allowing for both variants
    var isActive = (_.indexOf(["focus","focusin"], evt.type) !== -1);
    $(evt.target).toggleClass("active", isActive);
  },

  selectItemCallback: function(evt, data, formatted){
    var self = this.context;
  suggestionSelected: function(evt, datum) {
    window.location = datum.url;
  },

    if(data.search === true){
      window.location = self.searchFormAction + '?' + self.searchInputName + '=' + data.name;
    }
    else{ // The actual result
      self.options.element.val(formatted);
      window.location = data.url ? data.url : '/tags/' + data.name.substring(1);
  inputKeypress: function(evt) {
    if(evt.which === 13 && $(".tt-suggestion.tt-cursor").length === 0) {
      $(evt.target).closest("form").submit();
    }
  }
});
Loading