decidim-debates 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +15 -0
  3. data/Rakefile +3 -0
  4. data/app/assets/images/decidim/debates/icon.svg +1 -0
  5. data/app/commands/decidim/debates/admin/create_debate.rb +57 -0
  6. data/app/commands/decidim/debates/admin/update_debate.rb +46 -0
  7. data/app/commands/decidim/debates/create_debate.rb +68 -0
  8. data/app/controllers/decidim/debates/admin/application_controller.rb +15 -0
  9. data/app/controllers/decidim/debates/admin/debates_controller.rb +73 -0
  10. data/app/controllers/decidim/debates/application_controller.rb +13 -0
  11. data/app/controllers/decidim/debates/debates_controller.rb +78 -0
  12. data/app/events/decidim/debates/create_debate_event.rb +42 -0
  13. data/app/forms/decidim/debates/admin/debate_form.rb +33 -0
  14. data/app/forms/decidim/debates/debate_form.rb +25 -0
  15. data/app/helpers/decidim/debates/application_helper.rb +12 -0
  16. data/app/models/decidim/debates/abilities/admin/admin_ability.rb +21 -0
  17. data/app/models/decidim/debates/abilities/admin/participatory_process_admin_ability.rb +23 -0
  18. data/app/models/decidim/debates/abilities/admin/participatory_process_moderator_ability.rb +21 -0
  19. data/app/models/decidim/debates/abilities/current_user_ability.rb +49 -0
  20. data/app/models/decidim/debates/application_record.rb +10 -0
  21. data/app/models/decidim/debates/debate.rb +103 -0
  22. data/app/presenters/decidim/debates/debate_presenter.rb +20 -0
  23. data/app/presenters/decidim/debates/official_author_presenter.rb +34 -0
  24. data/app/services/decidim/debates/debate_search.rb +38 -0
  25. data/app/views/decidim/debates/admin/debates/_form.html.erb +36 -0
  26. data/app/views/decidim/debates/admin/debates/edit.html.erb +7 -0
  27. data/app/views/decidim/debates/admin/debates/index.html.erb +52 -0
  28. data/app/views/decidim/debates/admin/debates/new.html.erb +7 -0
  29. data/app/views/decidim/debates/debates/_count.html.erb +1 -0
  30. data/app/views/decidim/debates/debates/_debate.html.erb +42 -0
  31. data/app/views/decidim/debates/debates/_debates.html.erb +4 -0
  32. data/app/views/decidim/debates/debates/_filters.html.erb +20 -0
  33. data/app/views/decidim/debates/debates/_filters_small_view.html.erb +18 -0
  34. data/app/views/decidim/debates/debates/_share.html.erb +33 -0
  35. data/app/views/decidim/debates/debates/index.html.erb +27 -0
  36. data/app/views/decidim/debates/debates/index.js.erb +9 -0
  37. data/app/views/decidim/debates/debates/new.html.erb +43 -0
  38. data/app/views/decidim/debates/debates/show.html.erb +80 -0
  39. data/config/locales/ca.yml +114 -0
  40. data/config/locales/en.yml +117 -0
  41. data/config/locales/es.yml +114 -0
  42. data/config/locales/eu.yml +114 -0
  43. data/config/locales/fi.yml +114 -0
  44. data/config/locales/fr.yml +114 -0
  45. data/config/locales/gl.yml +114 -0
  46. data/config/locales/it.yml +114 -0
  47. data/config/locales/nl.yml +114 -0
  48. data/config/locales/pl.yml +116 -0
  49. data/config/locales/pt-BR.yml +114 -0
  50. data/config/locales/pt.yml +114 -0
  51. data/config/locales/ru.yml +5 -0
  52. data/config/locales/sv.yml +114 -0
  53. data/config/locales/uk.yml +5 -0
  54. data/db/migrate/20170118141619_create_debates.rb +18 -0
  55. data/db/migrate/20180117100413_add_debate_information_updates.rb +7 -0
  56. data/db/migrate/20180118132243_add_author_to_debates.rb +7 -0
  57. data/db/migrate/20180119150434_add_reference_to_debates.rb +12 -0
  58. data/db/migrate/20180122090505_add_user_group_author_to_debates.rb +7 -0
  59. data/lib/decidim/debates/admin.rb +10 -0
  60. data/lib/decidim/debates/admin_engine.rb +33 -0
  61. data/lib/decidim/debates/engine.rb +26 -0
  62. data/lib/decidim/debates/feature.rb +55 -0
  63. data/lib/decidim/debates/test/factories.rb +52 -0
  64. data/lib/decidim/debates/version.rb +10 -0
  65. data/lib/decidim/debates.rb +12 -0
  66. metadata +197 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # This class holds a Form to create/update debates from Decidim's admin panel.
6
+ class DebateForm < Decidim::Form
7
+ include TranslatableAttributes
8
+
9
+ attribute :title, String
10
+ attribute :description, String
11
+ attribute :category_id, Integer
12
+ attribute :user_group_id, Integer
13
+
14
+ validates :title, presence: true
15
+ validates :description, presence: true
16
+
17
+ validates :category, presence: true, if: ->(form) { form.category_id.present? }
18
+
19
+ def category
20
+ return unless current_feature
21
+ @category ||= current_feature.categories.where(id: category_id).first
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # Custom helpers, scoped to the debates engine.
6
+ #
7
+ module ApplicationHelper
8
+ include PaginateHelper
9
+ include Decidim::Comments::CommentsHelper
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ module Admin
7
+ # Defines the abilities related to debates for an admin user.
8
+ # Intended to be used with `cancancan`.
9
+ class AdminAbility < Decidim::Abilities::AdminAbility
10
+ def define_abilities
11
+ can :manage, Debate do |debate|
12
+ debate.author.blank?
13
+ end
14
+ can :unreport, Debate
15
+ can :hide, Debate
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ module Admin
7
+ # Defines the abilities related to debates for a logged in process admin user.
8
+ # Intended to be used with `cancancan`.
9
+ class ParticipatoryProcessAdminAbility < Decidim::Abilities::ParticipatoryProcessAdminAbility
10
+ def define_participatory_process_abilities
11
+ super
12
+
13
+ can :manage, Debate do |debate|
14
+ debate.author.blank?
15
+ end
16
+ can :unreport, Debate
17
+ can :hide, Debate
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ module Admin
7
+ # Defines the abilities related to Debate for a logged in process admin user.
8
+ # Intended to be used with `cancancan`.
9
+ class ParticipatoryProcessModeratorAbility < Decidim::Abilities::ParticipatoryProcessModeratorAbility
10
+ def define_participatory_process_abilities
11
+ super
12
+
13
+ can [:unreport, :hide], Debate do |debate|
14
+ can_manage_process?(debate.feature.participatory_space)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ # Defines the abilities related to debates for a logged in user.
7
+ # Intended to be used with `cancancan`.
8
+ class CurrentUserAbility
9
+ include CanCan::Ability
10
+
11
+ attr_reader :user, :context
12
+
13
+ def initialize(user, context)
14
+ return unless user
15
+
16
+ @user = user
17
+ @context = context
18
+
19
+ can :create, Debate if authorized?(:create) && creation_enabled?
20
+ can :report, Debate
21
+ end
22
+
23
+ private
24
+
25
+ def authorized?(action)
26
+ return unless feature
27
+
28
+ ActionAuthorizer.new(user, feature, action).authorize.ok?
29
+ end
30
+
31
+ def creation_enabled?
32
+ return unless current_settings
33
+ current_settings.creation_enabled?
34
+ end
35
+
36
+ def current_settings
37
+ context.fetch(:current_settings, nil)
38
+ end
39
+
40
+ def feature
41
+ feature = context.fetch(:current_feature, nil)
42
+ return nil unless feature && feature.manifest.name == :debates
43
+
44
+ feature
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # Abstract class from which all models in this engine inherit.
6
+ class ApplicationRecord < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # The data store for a Debate in the Decidim::Debates component. It stores a
6
+ # title, description and any other useful information to render a custom
7
+ # debate.
8
+ class Debate < Debates::ApplicationRecord
9
+ include Decidim::HasFeature
10
+ include Decidim::HasCategory
11
+ include Decidim::Resourceable
12
+ include Decidim::Followable
13
+ include Decidim::Comments::Commentable
14
+ include Decidim::HasScope
15
+ include Decidim::Authorable
16
+ include Decidim::Reportable
17
+ include Decidim::HasReference
18
+
19
+ feature_manifest_name "debates"
20
+
21
+ validates :title, presence: true
22
+
23
+ # Public: Checks whether the debate is official or not.
24
+ #
25
+ # Returns a boolean.
26
+ def official?
27
+ author.blank?
28
+ end
29
+
30
+ # Public: Overrides the `reported_content_url` Reportable concern method.
31
+ def reported_content_url
32
+ ResourceLocatorPresenter.new(self).url
33
+ end
34
+
35
+ # Public: Calculates whether the current debate is an AMA-styled one or not.
36
+ # AMA-styled debates are those that have a start and end time set, and comments
37
+ # are only open during that timelapse. AMA stands for Ask Me Anything, a type
38
+ # of debate inspired by Reddit.
39
+ #
40
+ # Returns a Boolean.
41
+ def ama?
42
+ start_time.present? && end_time.present?
43
+ end
44
+
45
+ # Public: Checks whether the debate is an AMA-styled one and is open.
46
+ #
47
+ # Returns a boolean.
48
+ def open_ama?
49
+ ama? && Time.current.between?(start_time, end_time)
50
+ end
51
+
52
+ # Public: Checks if the debate is open or not.
53
+ #
54
+ # Returns a boolean.
55
+ def open?
56
+ (ama? && open_ama?) || !ama?
57
+ end
58
+
59
+ # Public: Overrides the `commentable?` Commentable concern method.
60
+ def commentable?
61
+ feature.settings.comments_enabled?
62
+ end
63
+
64
+ # Public: Overrides the `accepts_new_comments?` Commentable concern method.
65
+ def accepts_new_comments?
66
+ return false unless open?
67
+ commentable? && !comments_blocked?
68
+ end
69
+
70
+ # Public: Overrides the `comments_have_alignment?` Commentable concern method.
71
+ def comments_have_alignment?
72
+ true
73
+ end
74
+
75
+ # Public: Overrides the `comments_have_votes?` Commentable concern method.
76
+ def comments_have_votes?
77
+ true
78
+ end
79
+
80
+ # Public: Identifies the commentable type in the API.
81
+ def commentable_type
82
+ self.class.name
83
+ end
84
+
85
+ # Public: Overrides the `notifiable?` Notifiable concern method.
86
+ def notifiable?(_context)
87
+ false
88
+ end
89
+
90
+ # Public: Override Commentable concern method `users_to_notify_on_comment_created`
91
+ def users_to_notify_on_comment_created
92
+ return (followers | feature.participatory_space.admins).uniq if official?
93
+ followers
94
+ end
95
+
96
+ private
97
+
98
+ def comments_blocked?
99
+ feature.current_settings.comments_blocked
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ #
6
+ # Decorator for debates
7
+ #
8
+ class DebatePresenter < SimpleDelegator
9
+ def author
10
+ @author ||= if official?
11
+ Decidim::Debates::OfficialAuthorPresenter.new
12
+ elsif user_group
13
+ Decidim::UserGroupPresenter.new(user_group)
14
+ else
15
+ Decidim::UserPresenter.new(super)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ #
6
+ # A dummy presenter to abstract out the author of an official debate.
7
+ #
8
+ class OfficialAuthorPresenter
9
+ def name
10
+ I18n.t("decidim.debates.models.debate.fields.official_debate")
11
+ end
12
+
13
+ def nickname
14
+ ""
15
+ end
16
+
17
+ def badge
18
+ ""
19
+ end
20
+
21
+ def profile_path
22
+ ""
23
+ end
24
+
25
+ def avatar_url
26
+ ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
27
+ end
28
+
29
+ def deleted?
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # This class handles search and filtering of debates. Needs a
6
+ # `current_feature` param with a `Decidim::Feature` in order to
7
+ # find the debates.
8
+ class DebateSearch < ResourceSearch
9
+ # Public: Initializes the service.
10
+ # feature - A Decidim::Feature to get the debates from.
11
+ # page - The page number to paginate the results.
12
+ # per_page - The number of proposals to return per page.
13
+ def initialize(options = {})
14
+ super(Debate.not_hidden, options)
15
+ end
16
+
17
+ # Handle the search_text filter. We have to cast the JSONB columns
18
+ # into a `text` type so that we can search.
19
+ def search_search_text
20
+ query
21
+ .where("title::text ILIKE ?", "%#{search_text}%")
22
+ .or(query.where("description::text ILIKE ?", "%#{search_text}%"))
23
+ end
24
+
25
+ # Handle the origin filter
26
+ # The 'official' debates don't have an author ID
27
+ def search_origin
28
+ if origin == "official"
29
+ query.where(decidim_author_id: nil)
30
+ elsif origin == "citizens"
31
+ query.where.not(decidim_author_id: nil)
32
+ else # Assume 'all'
33
+ query
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,36 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title"><%= title %></h2>
4
+ </div>
5
+
6
+ <div class="card-section">
7
+ <div class="row column" >
8
+ <%= form.translated :text_field, :title, autofocus: true %>
9
+ </div>
10
+
11
+ <div class="row column" >
12
+ <%= form.translated :editor, :description %>
13
+ </div>
14
+
15
+ <div class="row column" >
16
+ <%= form.translated :editor, :instructions %>
17
+ </div>
18
+
19
+ <div class="row column" >
20
+ <%= form.translated :editor, :information_updates %>
21
+ </div>
22
+
23
+ <div class="row" >
24
+ <div class="column xlarge-6">
25
+ <%= form.datetime_field :start_time %>
26
+ </div>
27
+ <div class="column xlarge-6">
28
+ <%= form.datetime_field :end_time %>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="row column" >
33
+ <%= form.categories_select :decidim_category_id, current_participatory_space.categories, prompt: "", disable_parents: false %>
34
+ </div>
35
+ </div>
36
+ </div>
@@ -0,0 +1,7 @@
1
+ <%= decidim_form_for(@form, html: { class: "form edit_debate" }) do |f| %>
2
+ <%= render partial: 'form', object: f, locals: { title: t('.title') } %>
3
+
4
+ <div class="button--double form-general-submit">
5
+ <%= f.submit t(".update") %>
6
+ </div>
7
+ <% end %>
@@ -0,0 +1,52 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title">
4
+ <%= t(".title") %>
5
+
6
+ <%= link_to t("actions.new", scope: "decidim.debates", name: t("models.debate.name", scope: "decidim.debates.admin")), new_debate_path, class: 'button tiny button--title' if can? :manage, current_feature %>
7
+ </h2>
8
+ </div>
9
+
10
+ <div class="card-section">
11
+ <div class="table-scroll">
12
+ <table class="table-list">
13
+ <thead>
14
+ <tr>
15
+ <th><%= t("models.debate.fields.title", scope: "decidim.debates") %></th>
16
+ <th><%= t("models.debate.fields.start_time", scope: "decidim.debates") %></th>
17
+ <th><%= t("models.debate.fields.end_time", scope: "decidim.debates") %></th>
18
+ <th class="actions"><%= t("actions.title", scope: "decidim.debates") %></th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <% debates.each do |debate| %>
23
+ <tr data-id="<%= debate.id %>">
24
+ <td>
25
+ <%= link_to translated_attribute(debate.title), resource_locator(debate).path, target: :blank %><br />
26
+ </td>
27
+ <td>
28
+ <% if debate.start_time %>
29
+ <%= l debate.start_time, format: :long %>
30
+ <% end%>
31
+ </td>
32
+ <td>
33
+ <% if debate.end_time %>
34
+ <%= l debate.end_time, format: :long %>
35
+ <% end%>
36
+ </td>
37
+ <td class="table-list__actions">
38
+ <% if can? :update, debate %>
39
+ <%= icon_link_to "pencil", edit_debate_path(debate), t("actions.edit", scope: "decidim.debates"), class: "action-icon--edit" %>
40
+ <% end %>
41
+
42
+ <% if can? :destroy, debate %>
43
+ <%= icon_link_to "circle-x", debate_path(debate), t("actions.destroy", scope: "decidim.debates"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.debates") } %>
44
+ <% end %>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
50
+ </div>
51
+ </div>
52
+ </div>