decidim-debates 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|