voluntary 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/CHANGELOG.md +88 -0
  2. data/README.rdoc +2 -2
  3. data/app/assets/javascripts/voluntary/application.js +3 -0
  4. data/app/assets/javascripts/voluntary/base.js.coffee +47 -28
  5. data/app/assets/javascripts/voluntary/lib/jquery.multisortable.js +254 -0
  6. data/app/assets/stylesheets/voluntary/application.css +2 -0
  7. data/app/assets/stylesheets/voluntary/base.css.sass +7 -1
  8. data/app/assets/stylesheets/voluntary/bootstrap_and_overrides.css.sass +5 -1
  9. data/app/controllers/concerns/voluntary/v1/base_controller.rb +6 -1
  10. data/app/controllers/voluntary/api/v1/base_controller.rb +11 -0
  11. data/app/helpers/voluntary/show_helper.rb +2 -2
  12. data/app/models/concerns/likeable.rb +11 -0
  13. data/app/models/user.rb +4 -0
  14. data/app/views/layouts/application.html.erb +7 -1
  15. data/app/views/shared/_modal_javascript_response.js.erb +31 -0
  16. data/app/views/shared/layouts/twitter_bootstrap/_control_group.html.erb +11 -0
  17. data/app/views/shared/layouts/twitter_bootstrap/_modal.html.erb +12 -0
  18. data/app/views/shared/layouts/twitter_bootstrap/control_group/_boolean.html.erb +17 -0
  19. data/app/views/shared/resource/_actions.html.erb +4 -2
  20. data/config/locales/general/en.yml +14 -2
  21. data/config/routes/api.rb +6 -0
  22. data/config/routes.rb +3 -1
  23. data/lib/generators/voluntary/install/templates/app/views/layouts/application.html.erb +7 -1
  24. data/lib/generators/voluntary/product_dummy/product_dummy_generator.rb +0 -1
  25. data/lib/generators/voluntary/product_dummy/templates/app/views/layouts/application.html.erb +7 -1
  26. data/lib/voluntary/version.rb +1 -1
  27. data/lib/voluntary.rb +3 -1
  28. metadata +90 -84
  29. data/app/controllers/voluntary/api/v1/api_controller.rb +0 -9
data/CHANGELOG.md ADDED
@@ -0,0 +1,88 @@
1
+ ## unreleased ##
2
+
3
+ ## 0.2.3 (March 15, 2015) ##
4
+
5
+ * #16 Implements scope liked_by for likeables.
6
+ * #45 Rewrite of API base controller and initialization of API V1 routes.
7
+ * Introduces Likeable.likes_or_dislikes_for.
8
+ * Introduces new window function jquery_ui_tabs_initialization.
9
+ * #37 Integrates selectize plugin. Used at new tournament form of competition product.
10
+ * Make .remote_modal_link click handler work with other elements than links through data attribute url.
11
+ * Disables closing of modal on pressing escape key.
12
+ * Introduces modal JavaScript response partial. See protip https://coderwall.com/p/eu9sqa/multi-step-form-wizard-responses-for-ajax-modals-and-html-requests
13
+ * Option to yield sidebar in application layout.
14
+ * Introduces User#best_available_name.
15
+ * #26 Add event handler for toggleable checkboxes.
16
+ * Sets GitHub ribbon to voluntary repository in application layout which can be overridden in each product through controller method voluntary_application_repository_path.
17
+ * #20 #23 #32 #33 #34 Adds JavaScript Plugin CompetitiveList used at music metadata enrichment music year in review top albums and songs.
18
+ * Adds jquery.multisortable.js used at music metadata enrichment music year in review top albums and songs.
19
+ * #19 Adds Twitter Bootstrap datetime picker.
20
+ * Implements shared twitter bootstrap layout 'boolean control group'.
21
+ * Sets width of #bootstrap_modal to 800px.
22
+ * Implements shared twitter bootstrap layout partials 'control_group' ...
23
+ * Implements new option :namespace for Voluntary::ShowHelper#show_actions.
24
+
25
+ ## 0.2.2 (February 5, 2015) ##
26
+
27
+ * #17 Show progress spinner gif for Ajax tabs.
28
+ * Implements option to render custom breadcrumbs.
29
+ * Makes stylesheets and javascripts of application layout variable.
30
+ * Adds new view helper name_with_apostrophe.
31
+ * Yield page title and search in application layout.
32
+ * Disable scrolling in modal windows because of autocomplete lists cut off before.
33
+ * Sets global font size from 10px to 12px.
34
+ * Fixes ui tab paging links. Implements remote links and remote modal link handler.
35
+ * #14 last.fm authentication.
36
+ * #13 Facebook authentication.
37
+ * #12 Google authentication.
38
+
39
+ ## 0.2.1 (January 6, 2015) ##
40
+
41
+ * Small changes not worth mentioning.
42
+
43
+ ## 0.2.0 (March 22, 2014) ##
44
+
45
+ * Rails 4 upgrade.
46
+ * Adds new resource organization.
47
+ * after initialize callback for authorization ability.rb
48
+ * after_resource_has_many callback for main navigation
49
+ * Several bugfixes and small features.
50
+
51
+ ## 0.1.0 (October 13, 2013) ##
52
+
53
+ * voluntary_translation support with new model column.
54
+
55
+ ## 0.1.0.rc4 (September 9, 2013) ##
56
+
57
+ * Select product in /workflow/user/products/:product_id/areas/:id
58
+
59
+ ## 0.1.0.rc3 (September 6, 2013) ##
60
+
61
+ * Fixes invalid user update form by initializing presenter.
62
+
63
+ ## 0.1.0.rc2 (July 31, 2013) ##
64
+
65
+ * Support nesting of areas. #1
66
+
67
+ ## 0.1.0.rc1 (June 7, 2013) ##
68
+
69
+ * Updates twitter bootstrap and jquery ui.
70
+ * Removes rails_info dependency.
71
+ * Paints navigation black and more responsive.
72
+ * Allow mixin of cancan authorization abilities in products and client applications.
73
+ * Several small bug fixes and cleanup.
74
+ * Removes "twitter" (auth) dependencies
75
+
76
+ ## 0.0.3 (November 19, 2012) ##
77
+
78
+ * Adds vendor_extensions to gem specification's directories.
79
+
80
+ ## 0.0.2 (November 19, 2012) ##
81
+
82
+ * Fixes some bugs regarding integration of product engines.
83
+
84
+ * Adds product dummy generator.
85
+
86
+ ## 0.0.1 (November 11, 2012) ##
87
+
88
+ * Initial version.
data/README.rdoc CHANGED
@@ -12,7 +12,7 @@ Run this in your console:
12
12
 
13
13
  rvm --create use 1.9.3@your_crowdsourcing_platform_name
14
14
  gem update bundler
15
- gem install rails -v 3.2.14 --no-rdoc --no-ri
15
+ gem install rails -v 4.0.13 --no-rdoc --no-ri
16
16
  rails new your_crowdsourcing_platform_name
17
17
  cd your_crowdsourcing_platform_name
18
18
 
@@ -74,9 +74,9 @@ Run this in your console:
74
74
  bundle install
75
75
  # change config/boot.rb to require bundler like here: https://github.com/volontariat/voluntary_scholarship/blob/master/dummy/config/boot.rb
76
76
  # change database names to #{product_name}_#{environment} and customize user credentials in config/database.yml
77
- # add gitignore file from voluntary: https://github.com/volontariat/voluntary/blob/master/.gitignore
78
77
  bundle exec rake db:create:all && bundle exec rails g voluntary:product_dummy # confirm all overwrite questions except of Gemfile
79
78
  cd ..
79
+ # add gitignore file from voluntary: https://github.com/volontariat/voluntary/blob/master/.gitignore
80
80
  rails g migration add_product_name_product
81
81
  # fill migration file with template: https://github.com/volontariat/voluntary_scholarship/blob/master/db/migrate/20140306201232_add_scholarship_product.rb
82
82
  cd dummy
@@ -2,5 +2,8 @@
2
2
  //= require jquery_ujs
3
3
  //= require jquery-ui-bootstrap
4
4
  //= require twitter/bootstrap
5
+ //= require bootstrap-datetimepicker
5
6
  //= require jquery.tokeninput
7
+ //= require selectize
8
+ //= require competitive_list
6
9
  //= require_tree .
@@ -5,33 +5,37 @@ $(document).ready ->
5
5
 
6
6
  $( '.accordions' ).each (k, v) ->
7
7
  $(v).accordion({ autoHeight: false });
8
-
9
- $('.tabs').each (k, v) ->
10
- $(v).tabs
11
- autoHeight: false
12
- beforeActivate: (event, ui) ->
13
- unless ui.newTab.find('a').attr('href').indexOf('#') == 0
14
- ui.newTab.find('.ajax_spinner').show()
15
- ui.newPanel.empty()
16
-
17
- beforeLoad: (event, ui) ->
18
- ui.jqXHR.error ->
8
+
9
+ window.jquery_ui_tabs_initialization = ->
10
+ $('.tabs').each (k, v) ->
11
+ $(v).tabs
12
+ autoHeight: false
13
+ beforeActivate: (event, ui) ->
14
+ unless ui.newTab.find('a').attr('href').indexOf('#') == 0
15
+ ui.newTab.find('.ajax_spinner').show()
16
+ ui.newPanel.empty()
17
+
18
+ beforeLoad: (event, ui) ->
19
+ ui.jqXHR.error ->
20
+ unless ui.tab.find('a').attr('href').indexOf('#') == 0
21
+ ui.tab.find('.ajax_spinner').hide()
22
+
23
+ json = null
24
+
25
+ try
26
+ json = jQuery.parseJSON(ui.jqXHR.responseText)
27
+ catch e
28
+
29
+ error = if json && json['error'] then json['error'] else 'Something went wrong'
30
+
31
+ ui.panel.html error
32
+
33
+ load: (event, ui) ->
19
34
  unless ui.tab.find('a').attr('href').indexOf('#') == 0
20
35
  ui.tab.find('.ajax_spinner').hide()
21
-
22
- json = null
23
-
24
- try
25
- json = jQuery.parseJSON(ui.jqXHR.responseText)
26
- catch e
27
-
28
- error = if json && json['error'] then json['error'] else 'Something went wrong'
29
-
30
- ui.panel.html error
31
-
32
- load: (event, ui) ->
33
- unless ui.tab.find('a').attr('href').indexOf('#') == 0
34
- ui.tab.find('.ajax_spinner').hide()
36
+ window.jquery_ui_tabs_initialization()
37
+
38
+ window.jquery_ui_tabs_initialization()
35
39
 
36
40
  $(document).on "click", ".ui-tabs-panel .pagination a", (event) ->
37
41
  event.preventDefault()
@@ -82,6 +86,8 @@ $(document).ready ->
82
86
  $( ".datepicker" ).each (k, v) ->
83
87
  $(v).datepicker({ dateFormat: "yy-mm-dd", changeYear: true, yearRange: "c-100:c+10" });
84
88
 
89
+ $('.datetime_picker').datetimepicker()
90
+
85
91
  $(document.body).on "click", ".remote_links", (event) ->
86
92
  $this = $(this)
87
93
 
@@ -93,13 +99,26 @@ $(document).ready ->
93
99
  $(document.body).on "click", ".remote_modal_link", (event) ->
94
100
  $this = $(this)
95
101
 
96
- $.ajax(url: $this.attr('href'), type: "GET", dataType: "html").success (data) ->
102
+ url = if $this.data('url') then $this.data('url') else $this.attr('href')
103
+
104
+ $.ajax(url: url, type: "GET", dataType: "html").success (data) ->
97
105
  $('#bootstrap_modal').html(data)
98
- $('#bootstrap_modal').modal('show')
106
+ $('#bootstrap_modal').modal(show: true, keyboard: false)
99
107
 
100
108
  event.preventDefault()
101
109
 
102
110
  $(document.body).on "click", "#close_bootstrap_modal_button", (event) ->
103
111
  $('#bootstrap_modal').modal('hide')
104
112
  event.preventDefault()
105
-
113
+
114
+ $(document.body).on "click", "#toggle_checkboxes_checkbox", (event) ->
115
+ if $('.toggleable_checkbox:checked').length == 0
116
+ $('.toggleable_checkbox').prop('checked', true)
117
+ else
118
+ $('.toggleable_checkbox').prop('checked', false)
119
+
120
+ $('.bootstrap_tooltip').tooltip()
121
+
122
+ $('.selectize_select').selectize
123
+ create: true,
124
+ sortField: 'text'
@@ -0,0 +1,254 @@
1
+ /**
2
+ * jquery.multisortable.js - v0.2
3
+ * https://github.com/shvetsgroup/jquery.multisortable
4
+ *
5
+ * Author: Ethan Atlakson, Jay Hayes, Gabriel Such, Alexander Shvets
6
+ * Last Revision 3/16/2012
7
+ * multi-selectable, multi-sortable jQuery plugin
8
+ */
9
+
10
+ !function($) {
11
+
12
+ $.fn.multiselectable = function(options) {
13
+ if (!options) {
14
+ options = {}
15
+ }
16
+ options = $.extend({}, $.fn.multiselectable.defaults, options);
17
+
18
+ function mouseDown(e) {
19
+ var item = $(this),
20
+ parent = item.parent(),
21
+ myIndex = item.index();
22
+
23
+ var prev = parent.find('.multiselectable-previous');
24
+ // If no previous selection found, start selecting from first selected item.
25
+ prev = prev.length ? prev : $(parent.find('.' + options.selectedClass)[0]).addClass('multiselectable-previous');
26
+ var prevIndex = prev.index();
27
+
28
+ if (e.ctrlKey || e.metaKey) {
29
+ if (item.hasClass(options.selectedClass)) {
30
+ item.removeClass(options.selectedClass).removeClass('multiselectable-previous')
31
+ if (item.not('.child').length) {
32
+ item.nextUntil(':not(.child)').removeClass(options.selectedClass);
33
+ }
34
+ }
35
+ else {
36
+ parent.find('.multiselectable-previous').removeClass('multiselectable-previous');
37
+ item.addClass(options.selectedClass).addClass('multiselectable-previous')
38
+ if (item.not('.child').length) {
39
+ item.nextUntil(':not(.child)').addClass(options.selectedClass);
40
+ }
41
+ }
42
+ }
43
+
44
+ if (e.shiftKey) {
45
+ var last_shift_range = parent.find('.multiselectable-shift');
46
+ last_shift_range.removeClass(options.selectedClass).removeClass('multiselectable-shift');
47
+
48
+ var shift_range;
49
+ if (prevIndex < myIndex) {
50
+ shift_range = item.prevUntil('.multiselectable-previous').add(prev).add(item);
51
+ }
52
+ else if (prevIndex > myIndex) {
53
+ shift_range = item.nextUntil('.multiselectable-previous').add(prev).add(item);
54
+ }
55
+ shift_range.addClass(options.selectedClass).addClass('multiselectable-shift');
56
+ }
57
+ else {
58
+ parent.find('.multiselectable-shift').removeClass('multiselectable-shift');
59
+ }
60
+
61
+ if (!e.ctrlKey && !e.metaKey && !e.shiftKey) {
62
+ parent.find('.multiselectable-previous').removeClass('multiselectable-previous');
63
+ if (!item.hasClass(options.selectedClass)) {
64
+ parent.find('.' + options.selectedClass).removeClass(options.selectedClass);
65
+ item.addClass(options.selectedClass).addClass('multiselectable-previous');
66
+ if (item.not('.child').length) {
67
+ item.nextUntil(':not(.child)').addClass(options.selectedClass);
68
+ }
69
+ }
70
+ }
71
+
72
+ options.mousedown(e, item);
73
+ }
74
+
75
+ function click(e) {
76
+ if ( $(this).is('.ui-draggable-dragging') ) {
77
+ return;
78
+ }
79
+
80
+ var item = $(this), parent = item.parent();
81
+
82
+ // If item wasn't draged and is not multiselected, it should reset selection for other items.
83
+ if (!e.ctrlKey && !e.metaKey && !e.shiftKey) {
84
+ parent.find('.multiselectable-previous').removeClass('multiselectable-previous');
85
+ parent.find('.' + options.selectedClass).removeClass(options.selectedClass);
86
+ item.addClass(options.selectedClass).addClass('multiselectable-previous');
87
+ if (item.not('.child').length) {
88
+ item.nextUntil(':not(.child)').addClass(options.selectedClass);
89
+ }
90
+ }
91
+
92
+ options.click(e, item);
93
+ }
94
+
95
+ return this.each(function() {
96
+ var list = $(this);
97
+
98
+ if (!list.data('multiselectable')) {
99
+ list.data('multiselectable', true)
100
+ .delegate(options.items, 'mousedown', mouseDown)
101
+ .delegate(options.items, 'click', click)
102
+ .disableSelection();
103
+ }
104
+ })
105
+ };
106
+
107
+ $.fn.multiselectable.defaults = {
108
+ click: function(event, elem) {},
109
+ mousedown: function(event, elem) {},
110
+ selectedClass: 'selected',
111
+ items: 'li'
112
+ };
113
+
114
+
115
+ $.fn.multisortable = function(options) {
116
+ if (!options) {
117
+ options = {}
118
+ }
119
+ var settings = $.extend({}, $.fn.multisortable.defaults, options);
120
+
121
+ function regroup(item, list) {
122
+ if (list.find('.' + settings.selectedClass).length > 0) {
123
+ var myIndex = item.data('i');
124
+
125
+ var itemsBefore = list.find('.' + settings.selectedClass).filter(function() {
126
+ return $(this).data('i') < myIndex
127
+ }).css({
128
+ position: '',
129
+ width: '',
130
+ left: '',
131
+ top: '',
132
+ zIndex: ''
133
+ });
134
+
135
+ item.before(itemsBefore);
136
+
137
+ var itemsAfter = list.find('.' + settings.selectedClass).filter(function() {
138
+ return $(this).data('i') > myIndex
139
+ }).css({
140
+ position: '',
141
+ width: '',
142
+ left: '',
143
+ top: '',
144
+ zIndex: ''
145
+ });
146
+
147
+ item.after(itemsAfter);
148
+
149
+ setTimeout(function() {
150
+ itemsAfter.add(itemsBefore).addClass(settings.selectedClass);
151
+ }, 0);
152
+ }
153
+ }
154
+
155
+ return this.each(function() {
156
+ var list = $(this);
157
+
158
+ //enable multi-selection
159
+ list.multiselectable({
160
+ selectedClass: settings.selectedClass,
161
+ click: settings.click,
162
+ items: settings.items,
163
+ mousedown: settings.mousedown
164
+ });
165
+
166
+ //enable sorting
167
+ options.cancel = settings.items + ':not(.' + settings.selectedClass + ')';
168
+ options.placeholder = settings.placeholder;
169
+ options.start = function(event, ui) {
170
+ if (ui.item.hasClass(settings.selectedClass)) {
171
+ var parent = ui.item.parent();
172
+
173
+ //assign indexes to all selected items
174
+ parent.find('.' + settings.selectedClass).each(function(i) {
175
+ $(this).data('i', i);
176
+ });
177
+
178
+ // adjust placeholder size to be size of items
179
+ var height = parent.find('.' + settings.selectedClass).length * ui.item.outerHeight();
180
+ ui.placeholder.height(height);
181
+ }
182
+
183
+ settings.start(event, ui);
184
+ };
185
+
186
+ options.stop = function(event, ui) {
187
+ regroup(ui.item, ui.item.parent());
188
+ settings.stop(event, ui);
189
+ };
190
+
191
+ options.sort = function(event, ui) {
192
+ var parent = ui.item.parent(),
193
+ myIndex = ui.item.data('i'),
194
+ top = parseInt(ui.item.css('top').replace('px', '')),
195
+ left = parseInt(ui.item.css('left').replace('px', ''));
196
+
197
+ // fix to keep compatibility using prototype.js and jquery together
198
+ $.fn.reverse = Array.prototype._reverse || Array.prototype.reverse
199
+
200
+ var height = 0;
201
+ $('.' + settings.selectedClass, parent).filter(function() {
202
+ return $(this).data('i') < myIndex;
203
+ }).reverse().each(function() {
204
+ height += $(this).outerHeight();
205
+ $(this).css({
206
+ left: left,
207
+ top: top - height,
208
+ position: 'absolute',
209
+ zIndex: 1000,
210
+ width: ui.item.width()
211
+ })
212
+ });
213
+
214
+ height = ui.item.outerHeight();
215
+ $('.' + settings.selectedClass, parent).filter(function() {
216
+ return $(this).data('i') > myIndex;
217
+ }).each(function() {
218
+ var item = $(this);
219
+ item.css({
220
+ left: left,
221
+ top: top + height,
222
+ position: 'absolute',
223
+ zIndex: 1000,
224
+ width: ui.item.width()
225
+ });
226
+
227
+ height += item.outerHeight();
228
+ });
229
+
230
+ settings.sort(event, ui);
231
+ };
232
+
233
+ options.receive = function(event, ui) {
234
+ regroup(ui.item, ui.sender);
235
+ settings.receive(event, ui);
236
+ };
237
+
238
+ list.sortable(options).disableSelection();
239
+ })
240
+ };
241
+
242
+ $.fn.multisortable.defaults = {
243
+ start: function(event, ui) {},
244
+ stop: function(event, ui) {},
245
+ sort: function(event, ui) {},
246
+ receive: function(event, ui) {},
247
+ click: function(event, elem) {},
248
+ mousedown: function(event, elem) {},
249
+ selectedClass: 'selected',
250
+ placeholder: 'placeholder',
251
+ items: 'li'
252
+ };
253
+
254
+ }(jQuery);
@@ -13,6 +13,8 @@
13
13
  *= require token-input-facebook
14
14
  *= require twitter/bootstrap
15
15
  *= require jquery-ui-bootstrap
16
+ *= require selectize
17
+ *= require selectize.bootstrap2
16
18
  *= require 'voluntary/bootstrap_and_overrides'
17
19
  *= require 'voluntary/base'
18
20
  */
@@ -63,4 +63,10 @@ html, body, div
63
63
 
64
64
  .likes_and_dislikes_table td
65
65
  background-color: transparent !important
66
- border-top: 0px !important
66
+ border-top: 0px !important
67
+
68
+ .multisortable li.selected
69
+ outline: 1px solid red
70
+
71
+ .selectize-input
72
+ width: 216px
@@ -1,4 +1,5 @@
1
1
  @import "twitter/bootstrap/responsive"
2
+ @import 'bootstrap-datetimepicker'
2
3
 
3
4
  .list-striped
4
5
  li:nth-child(odd)
@@ -11,4 +12,7 @@
11
12
  overflow-y: visible
12
13
 
13
14
  .scrollable-modal-body
14
- overflow-y: auto
15
+ overflow-y: auto
16
+
17
+ #bootstrap_modal
18
+ width: 800px !important
@@ -8,7 +8,8 @@ module Voluntary
8
8
  rescue_from ActiveRecord::RecordNotFound, with: :not_found
9
9
  rescue_from Mongoid::Errors::DocumentNotFound, with: :not_found
10
10
 
11
- helper_method :parent, :application_navigation, :navigation_product_path, :navigation_product_name, :voluntary_application_stylesheets, :voluntary_application_javascripts
11
+ helper_method :parent, :application_navigation, :navigation_product_path, :navigation_product_name, :voluntary_application_stylesheets
12
+ helper_method :voluntary_application_javascripts, :voluntary_application_repository_path
12
13
  end
13
14
 
14
15
  def voluntary_application_stylesheets
@@ -18,6 +19,10 @@ module Voluntary
18
19
  def voluntary_application_javascripts
19
20
  ['voluntary/application', 'application']
20
21
  end
22
+
23
+ def voluntary_application_repository_path
24
+ 'volontariat/voluntary'
25
+ end
21
26
 
22
27
  def parent
23
28
  @parent
@@ -0,0 +1,11 @@
1
+ module Voluntary
2
+ module Api
3
+ module V1
4
+ class BaseController < ActionController::Base #ActionController::Metal
5
+ #include ActionController::Rendering # enables rendering
6
+ #include ActionController::MimeResponds # enables serving different content types like :xml or :json
7
+ #include AbstractController::Callbacks # callbacks for your authentication logic
8
+ end
9
+ end
10
+ end
11
+ end
@@ -53,11 +53,11 @@ module Voluntary
53
53
  end
54
54
  end
55
55
 
56
- def show_actions
56
+ def show_actions(options = {})
57
57
  result = content_tag :dt, raw('&nbsp')
58
58
  result += content_tag :dd, render(
59
59
  partial: 'shared/resource/actions', locals: {
60
- type: root_model_class_name(resource).tableize, resource: resource
60
+ type: root_model_class_name(resource).tableize, resource: resource, namespace: options[:namespace]
61
61
  }
62
62
  )
63
63
 
@@ -8,9 +8,20 @@ module Likeable
8
8
  has_many :likers, class_name: 'User', through: :likes, source: :user
9
9
  has_many :dislikes, -> { where(positive: false) }, class_name: 'Like', dependent: :delete_all, as: :target
10
10
  has_many :dislikers, class_name: 'User', through: :dislikes, source: :user
11
+
12
+ scope :liked_by, ->(user_id) do
13
+ select('music_videos.*, likes.created_at AS liked_at').joins('RIGHT JOIN likes ON likes.positive = 1 AND likes.target_type = "MusicVideo" AND likes.target_id = music_videos.id').
14
+ where('likes.user_id = ? AND music_videos.id IS NOT NULL', user_id)
15
+ end
11
16
  end
12
17
 
13
18
  def update_likes_counter
14
19
  self.class.where(id: self.id).update_all likes_count: self.likes.count, dislikes_count: self.dislikes.count
15
20
  end
21
+
22
+ module ClassMethods
23
+ def likes_or_dislikes_for(user, ids)
24
+ user.likes_or_dislikes.for_targets(name, ids).index_by(&:target_id)
25
+ end
26
+ end
16
27
  end
data/app/models/user.rb CHANGED
@@ -101,6 +101,10 @@ class User < ActiveRecord::Base
101
101
  [first_name, last_name].join(' ')
102
102
  end
103
103
 
104
+ def best_available_name
105
+ lastfm_user_name || full_name
106
+ end
107
+
104
108
  private
105
109
 
106
110
  def set_main_role
@@ -13,6 +13,10 @@
13
13
 
14
14
  <%= render 'layouts/shared/navigation' %>
15
15
 
16
+ <% if voluntary_application_repository_path.present? %>
17
+ <a href="https://github.com/<%= voluntary_application_repository_path %>"><img style="position: absolute; top: 41px; right: 0; border: 0; z-index: 100;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
18
+ <% end %>
19
+
16
20
  <section id="dialog">
17
21
  <img alt="Ajax-loader-small" class="hide " id="dialog_body_spinner" src="<%=image_path('voluntary/spinner.gif')%>"/>
18
22
  <div id="dialog_body"/>
@@ -27,7 +31,7 @@
27
31
  </div>
28
32
  <% end %>
29
33
  <div class="row-fluid">
30
- <% if sidenav(@sidenav_links_count).present? || content_for?(:search) %>
34
+ <% if sidenav(@sidenav_links_count).present? || content_for?(:search) || content_for?(:sidebar) %>
31
35
  <div class="span9">
32
36
  <% if content_for?(:breadcrumbs) %>
33
37
  <div class="nav">
@@ -43,6 +47,8 @@
43
47
  <%= yield :search %>
44
48
 
45
49
  <%= sidenav(@sidenav_links_count) %>
50
+
51
+ <%= yield :sidebar %>
46
52
  </div>
47
53
  <% else %>
48
54
  <div class="span12">
@@ -0,0 +1,31 @@
1
+ <% namespace ||= nil %>
2
+ <% message = flash[:notice].present? ? flash[:notice].clone : nil %>
3
+ <% flash.delete(:notice) %>
4
+ <% message = flash[:alert].present? ? flash[:alert].clone : message %>
5
+ <% flash.delete(:alert) %>
6
+ <% ajax_data ||= {} %>
7
+ <% ajax_method ||= :get %>
8
+
9
+ <% if @path.present? %>
10
+ $.ajax({ url: "<%= @path %>", data: <%= raw ajax_data.to_json %>, type: "<%= ajax_method.to_s.upcase %>", dataType: "script"}).done(function(data) {
11
+ eval(data);
12
+ <%= message.present? ? raw('alert("' + message + '");') : '' %>
13
+ })
14
+ .fail(function(data) {
15
+ <%= message.present? ? raw('alert("' + message + '");') : '' %>
16
+ alert("Failed to load <%= @path %>!");
17
+ });
18
+ <% elsif @template.present? %>
19
+ $("#bootstrap_modal").html("<%= escape_javascript(
20
+ render(
21
+ partial: 'shared/layouts/twitter_bootstrap/modal',
22
+ locals: {
23
+ title: title,
24
+ body: render(template: "#{namespace}#{@template}.html")
25
+ }
26
+ )
27
+ ) %>");
28
+ <%= message.present? ? raw('alert("' + message + '");') : '' %>
29
+ <% elsif message.present? %>
30
+ <%= message.present? ? raw('alert("' + message + '");') : '' %>
31
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% id ||= field.match(/id="([^"]*)"/)[1] %>
2
+ <% required ||= false %>
3
+ <% required_class = required ? ' required' : ' optional' %>
4
+ <div class="control-group string<%= required_class %> <%= id %>">
5
+ <label for="<%= id %>" class="string<%= required_class %> control-label">
6
+ <% if required %><abbr title="required">*</abbr><% end %> <%= label %>
7
+ </label>
8
+ <div class="controls">
9
+ <%= field %>
10
+ </div>
11
+ </div>