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.
- checksums.yaml +7 -0
- data/README.md +15 -0
- data/Rakefile +3 -0
- data/app/assets/images/decidim/debates/icon.svg +1 -0
- data/app/commands/decidim/debates/admin/create_debate.rb +57 -0
- data/app/commands/decidim/debates/admin/update_debate.rb +46 -0
- data/app/commands/decidim/debates/create_debate.rb +68 -0
- data/app/controllers/decidim/debates/admin/application_controller.rb +15 -0
- data/app/controllers/decidim/debates/admin/debates_controller.rb +73 -0
- data/app/controllers/decidim/debates/application_controller.rb +13 -0
- data/app/controllers/decidim/debates/debates_controller.rb +78 -0
- data/app/events/decidim/debates/create_debate_event.rb +42 -0
- data/app/forms/decidim/debates/admin/debate_form.rb +33 -0
- data/app/forms/decidim/debates/debate_form.rb +25 -0
- data/app/helpers/decidim/debates/application_helper.rb +12 -0
- data/app/models/decidim/debates/abilities/admin/admin_ability.rb +21 -0
- data/app/models/decidim/debates/abilities/admin/participatory_process_admin_ability.rb +23 -0
- data/app/models/decidim/debates/abilities/admin/participatory_process_moderator_ability.rb +21 -0
- data/app/models/decidim/debates/abilities/current_user_ability.rb +49 -0
- data/app/models/decidim/debates/application_record.rb +10 -0
- data/app/models/decidim/debates/debate.rb +103 -0
- data/app/presenters/decidim/debates/debate_presenter.rb +20 -0
- data/app/presenters/decidim/debates/official_author_presenter.rb +34 -0
- data/app/services/decidim/debates/debate_search.rb +38 -0
- data/app/views/decidim/debates/admin/debates/_form.html.erb +36 -0
- data/app/views/decidim/debates/admin/debates/edit.html.erb +7 -0
- data/app/views/decidim/debates/admin/debates/index.html.erb +52 -0
- data/app/views/decidim/debates/admin/debates/new.html.erb +7 -0
- data/app/views/decidim/debates/debates/_count.html.erb +1 -0
- data/app/views/decidim/debates/debates/_debate.html.erb +42 -0
- data/app/views/decidim/debates/debates/_debates.html.erb +4 -0
- data/app/views/decidim/debates/debates/_filters.html.erb +20 -0
- data/app/views/decidim/debates/debates/_filters_small_view.html.erb +18 -0
- data/app/views/decidim/debates/debates/_share.html.erb +33 -0
- data/app/views/decidim/debates/debates/index.html.erb +27 -0
- data/app/views/decidim/debates/debates/index.js.erb +9 -0
- data/app/views/decidim/debates/debates/new.html.erb +43 -0
- data/app/views/decidim/debates/debates/show.html.erb +80 -0
- data/config/locales/ca.yml +114 -0
- data/config/locales/en.yml +117 -0
- data/config/locales/es.yml +114 -0
- data/config/locales/eu.yml +114 -0
- data/config/locales/fi.yml +114 -0
- data/config/locales/fr.yml +114 -0
- data/config/locales/gl.yml +114 -0
- data/config/locales/it.yml +114 -0
- data/config/locales/nl.yml +114 -0
- data/config/locales/pl.yml +116 -0
- data/config/locales/pt-BR.yml +114 -0
- data/config/locales/pt.yml +114 -0
- data/config/locales/ru.yml +5 -0
- data/config/locales/sv.yml +114 -0
- data/config/locales/uk.yml +5 -0
- data/db/migrate/20170118141619_create_debates.rb +18 -0
- data/db/migrate/20180117100413_add_debate_information_updates.rb +7 -0
- data/db/migrate/20180118132243_add_author_to_debates.rb +7 -0
- data/db/migrate/20180119150434_add_reference_to_debates.rb +12 -0
- data/db/migrate/20180122090505_add_user_group_author_to_debates.rb +7 -0
- data/lib/decidim/debates/admin.rb +10 -0
- data/lib/decidim/debates/admin_engine.rb +33 -0
- data/lib/decidim/debates/engine.rb +26 -0
- data/lib/decidim/debates/feature.rb +55 -0
- data/lib/decidim/debates/test/factories.rb +52 -0
- data/lib/decidim/debates/version.rb +10 -0
- data/lib/decidim/debates.rb +12 -0
- 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,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,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,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>
|