diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index cf579627b..1f825ffed 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,6 +14,7 @@ //= require jquery_ujs //= require jquery-ui/widgets/datepicker //= require jquery-ui/i18n/datepicker-es +//= require jquery-ui/widgets/autocomplete //= require jquery-fileupload/basic //= require foundation //= require turbolinks @@ -64,6 +65,7 @@ //= require documentable //= require tree_navigator //= require custom +//= require tag_autocomplete var initialize_modules = function() { App.Comments.initialize(); @@ -98,6 +100,7 @@ var initialize_modules = function() { App.WatchFormChanges.initialize(); App.TreeNavigator.initialize(); App.Documentable.initialize(); + App.TagAutocomplete.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/tag_autocomplete.js.coffee b/app/assets/javascripts/tag_autocomplete.js.coffee new file mode 100644 index 000000000..be27cd81c --- /dev/null +++ b/app/assets/javascripts/tag_autocomplete.js.coffee @@ -0,0 +1,34 @@ +App.TagAutocomplete = + + split: ( val ) -> + return (val.split( /,\s*/ )) + + extractLast: ( term ) -> + return (App.TagAutocomplete.split( term ).pop()) + + init_autocomplete: -> + $('.tag-autocomplete').autocomplete + source: (request, response) -> + $.ajax + url: $('.tag-autocomplete').data('js-url'), + data: {search: App.TagAutocomplete.extractLast( request.term )}, + type: 'GET', + dataType: 'json' + success: ( data ) -> + response( data ); + + minLength: 0, + search: -> + App.TagAutocomplete.extractLast( this.value ); + focus: -> + return false; + select: ( event, ui ) -> ( + terms = App.TagAutocomplete.split( this.value ); + terms.pop(); + terms.push( ui.item.value ); + terms.push( "" ); + this.value = terms.join( ", " ); + return false;); + + initialize: -> + App.TagAutocomplete.init_autocomplete(); \ No newline at end of file diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 19c73de32..e4dd4ea1c 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -16,4 +16,6 @@ @import 'annotator_overrides'; @import 'jquery-ui/datepicker'; @import 'datepicker_overrides'; +@import 'jquery-ui/autocomplete'; +@import 'autocomplete_overrides'; @import 'documentable'; diff --git a/app/assets/stylesheets/autocomplete_overrides.scss b/app/assets/stylesheets/autocomplete_overrides.scss new file mode 100644 index 000000000..dd2b939ad --- /dev/null +++ b/app/assets/stylesheets/autocomplete_overrides.scss @@ -0,0 +1,40 @@ +// Overrides styles of jquery-ui/autocomplete +// + +/* Autocomplete +----------------------------------*/ +.ui-autocomplete { + position: absolute; + cursor: default; +} + +/* workarounds */ +* html .ui-autocomplete { + width: 1px; +} /* without this, the menu expands to 100% in IE6 */ + +/* Menu +----------------------------------*/ +.ui-menu { + list-style: none; + padding: $line-height / 4 $line-height / 3; + display: block; + background: #fff; + border: 1px solid $border; + font-size: $small-font-size; + + .ui-menu-item { + + .ui-menu-item-wrapper { + padding: $line-height / 4 $line-height / 3; + position: relative; + } + + .ui-state-hover, + .ui-state-active { + background: #ececec; + border-radius: rem-calc(6); + } + } + +} diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb new file mode 100644 index 000000000..c58067048 --- /dev/null +++ b/app/controllers/tags_controller.rb @@ -0,0 +1,11 @@ +class TagsController < ApplicationController + + load_and_authorize_resource class: ActsAsTaggableOn::Tag + respond_to :json + + def suggest + @tags = ActsAsTaggableOn::Tag.search(params[:search]).map(&:name) + respond_with @tags + end + +end diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 620cfb212..0df92616d 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -24,6 +24,7 @@ module Abilities can :suggest, Debate can :suggest, Proposal + can :suggest, ActsAsTaggableOn::Tag can [:flag, :unflag], Comment cannot [:flag, :unflag], Comment, user_id: user.id diff --git a/app/views/budgets/investments/_form.html.erb b/app/views/budgets/investments/_form.html.erb index 7cf2bb1e2..8f98b9d5c 100644 --- a/app/views/budgets/investments/_form.html.erb +++ b/app/views/budgets/investments/_form.html.erb @@ -49,7 +49,8 @@ label: false, placeholder: t("budgets.investments.form.tags_placeholder"), aria: {describedby: "tags-list-help-text"}, - class: 'js-tag-list' %> + class: 'js-tag-list tag-autocomplete', + data: {js_url: suggest_tags_path} %> diff --git a/app/views/debates/_form.html.erb b/app/views/debates/_form.html.erb index 96a5337ca..a98611011 100644 --- a/app/views/debates/_form.html.erb +++ b/app/views/debates/_form.html.erb @@ -22,7 +22,9 @@ <%= f.text_field :tag_list, value: @debate.tag_list.to_s, label: false, placeholder: t("debates.form.tags_placeholder"), - aria: {describedby: "tag-list-help-text"} %> + aria: {describedby: "tag-list-help-text"}, + data: {js_url: suggest_tags_path}, + class: 'tag-autocomplete'%>
<% if @debate.new_record? %> diff --git a/app/views/proposals/_form.html.erb b/app/views/proposals/_form.html.erb index 73b39dfdb..b46dbc69e 100644 --- a/app/views/proposals/_form.html.erb +++ b/app/views/proposals/_form.html.erb @@ -70,8 +70,9 @@ <%= f.text_field :tag_list, value: @proposal.tag_list.to_s, label: false, placeholder: t("proposals.form.tags_placeholder"), - class: 'js-tag-list', - aria: {describedby: "tag-list-help-text"} %> + class: 'js-tag-list tag-autocomplete', + aria: {describedby: "tag-list-help-text"}, + data: {js_url: suggest_tags_path} %>
<% if current_user.unverified? %> diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb index 57766c8c9..018319442 100644 --- a/config/initializers/acts_as_taggable_on.rb +++ b/config/initializers/acts_as_taggable_on.rb @@ -43,6 +43,18 @@ module ActsAsTaggableOn Tagging.public_for_api.pluck('DISTINCT taggings.tag_id')) end + include PgSearch + + pg_search_scope :pg_search, against: :name, + using: { + tsearch: {prefix: true} + }, + ignoring: :accents + + def self.search(term) + pg_search(term) + end + def increment_custom_counter_for(taggable_type) Tag.increment_counter(custom_counter_field_name_for(taggable_type), id) end @@ -78,6 +90,7 @@ module ActsAsTaggableOn end private + def custom_counter_field_name_for(taggable_type) "#{taggable_type.underscore.pluralize}_count" end diff --git a/config/routes.rb b/config/routes.rb index a02384ced..7b1c1cac0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,17 +6,17 @@ Rails.application.routes.draw do end devise_for :users, controllers: { - registrations: 'users/registrations', - sessions: 'users/sessions', - confirmations: 'users/confirmations', - omniauth_callbacks: 'users/omniauth_callbacks' - } + registrations: 'users/registrations', + sessions: 'users/sessions', + confirmations: 'users/confirmations', + omniauth_callbacks: 'users/omniauth_callbacks' + } devise_for :organizations, class_name: 'User', - controllers: { - registrations: 'organizations/registrations', - sessions: 'devise/sessions', - }, - skip: [:omniauth_callbacks] + controllers: { + registrations: 'organizations/registrations', + sessions: 'devise/sessions' + }, + skip: [:omniauth_callbacks] devise_scope :organization do get 'organizations/sign_up/success', to: 'organizations/registrations#success' @@ -168,6 +168,11 @@ Rails.application.routes.draw do resource :letter, controller: "letter", only: [:new, :create, :show, :edit, :update] end + resources :tags do + collection do + get :suggest + end + end namespace :admin do root to: "dashboard#index" @@ -431,9 +436,7 @@ Rails.application.routes.draw do get '/graphql', to: 'graphql#query' post '/graphql', to: 'graphql#query' - if Rails.env.development? - mount LetterOpenerWeb::Engine, at: "/letter_opener" - end + mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql' diff --git a/spec/lib/acts_as_taggable_on_spec.rb b/spec/lib/acts_as_taggable_on_spec.rb index 761e90553..de53b35e7 100644 --- a/spec/lib/acts_as_taggable_on_spec.rb +++ b/spec/lib/acts_as_taggable_on_spec.rb @@ -133,6 +133,21 @@ describe 'ActsAsTaggableOn' do expect(ActsAsTaggableOn::Tag.public_for_api).to be_empty end end + + describe "search" do + it "containing the word in the name" do + create(:tag, name: 'Familia') + create(:tag, name: 'Cultura') + create(:tag, name: 'Salud') + create(:tag, name: 'Famosos') + + expect(ActsAsTaggableOn::Tag.pg_search('f').length).to eq(2) + expect(ActsAsTaggableOn::Tag.search('cultura').first.name).to eq('Cultura') + expect(ActsAsTaggableOn::Tag.search('sal').first.name).to eq('Salud') + expect(ActsAsTaggableOn::Tag.search('fami').first.name).to eq('Familia') + end + end + end end