decidim-posts 1.0.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 (117) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +40 -0
  4. data/Rakefile +9 -0
  5. data/app/cells/decidim/posts/comments/add_comment.erb +15 -0
  6. data/app/cells/decidim/posts/comments/comments_loading.erb +1 -0
  7. data/app/cells/decidim/posts/comments/order_control.erb +13 -0
  8. data/app/cells/decidim/posts/comments/show.erb +31 -0
  9. data/app/cells/decidim/posts/comments_cell.rb +158 -0
  10. data/app/cells/decidim/posts/content_blocks/posts/show.erb +3 -0
  11. data/app/cells/decidim/posts/content_blocks/posts_cell.rb +50 -0
  12. data/app/cells/decidim/posts/content_blocks/posts_settings_form/show.erb +3 -0
  13. data/app/cells/decidim/posts/content_blocks/posts_settings_form_cell.rb +23 -0
  14. data/app/cells/decidim/posts/feed_dropdown_metadata_cell.rb +19 -0
  15. data/app/cells/decidim/posts/meeting/show.erb +65 -0
  16. data/app/cells/decidim/posts/meeting_cell.rb +44 -0
  17. data/app/cells/decidim/posts/post/show.erb +30 -0
  18. data/app/cells/decidim/posts/post/survey.erb +68 -0
  19. data/app/cells/decidim/posts/post_attachments/show.erb +40 -0
  20. data/app/cells/decidim/posts/post_attachments_cell.rb +17 -0
  21. data/app/cells/decidim/posts/post_cell.rb +44 -0
  22. data/app/cells/decidim/posts/post_comment/show.erb +17 -0
  23. data/app/cells/decidim/posts/post_comment_cell.rb +49 -0
  24. data/app/cells/decidim/posts/post_header/show.erb +53 -0
  25. data/app/cells/decidim/posts/post_header_cell.rb +79 -0
  26. data/app/cells/decidim/posts/post_host/show.erb +74 -0
  27. data/app/cells/decidim/posts/post_host_cell.rb +73 -0
  28. data/app/cells/decidim/posts/post_metadata/show.erb +9 -0
  29. data/app/cells/decidim/posts/post_metadata_cell.rb +67 -0
  30. data/app/cells/decidim/posts/reaction_menu/show.erb +18 -0
  31. data/app/cells/decidim/posts/reaction_menu/styles.erb +7 -0
  32. data/app/cells/decidim/posts/reaction_menu_cell.rb +26 -0
  33. data/app/cells/decidim/posts/reactions/show.erb +8 -0
  34. data/app/cells/decidim/posts/reactions_cell.rb +22 -0
  35. data/app/commands/decidim/posts/add_reaction_to_resource.rb +90 -0
  36. data/app/commands/decidim/posts/create_post.rb +112 -0
  37. data/app/commands/decidim/posts/destroy_post.rb +20 -0
  38. data/app/commands/decidim/posts/remove_reaction_from_resource.rb +41 -0
  39. data/app/commands/decidim/posts/update_post.rb +72 -0
  40. data/app/controllers/concerns/decidim/posts/admin/filterable.rb +46 -0
  41. data/app/controllers/concerns/decidim/posts/reactionable.rb +31 -0
  42. data/app/controllers/decidim/posts/admin/application_controller.rb +26 -0
  43. data/app/controllers/decidim/posts/admin/posts_controller.rb +93 -0
  44. data/app/controllers/decidim/posts/application_controller.rb +18 -0
  45. data/app/controllers/decidim/posts/meetings_controller.rb +176 -0
  46. data/app/controllers/decidim/posts/posts_controller.rb +202 -0
  47. data/app/controllers/decidim/posts/reactions_controller.rb +54 -0
  48. data/app/controllers/decidim/posts/user_answers_controller.rb +36 -0
  49. data/app/events/decidim/posts/resource_reacted_event.rb +38 -0
  50. data/app/forms/decidim/posts/answer_form.rb +13 -0
  51. data/app/forms/decidim/posts/post_form.rb +121 -0
  52. data/app/forms/decidim/posts/question_form.rb +20 -0
  53. data/app/helpers/decidim/posts/admin/posts_helper.rb +8 -0
  54. data/app/helpers/decidim/posts/application_helper.rb +20 -0
  55. data/app/helpers/decidim/posts/post_cells_helper.rb +35 -0
  56. data/app/helpers/decidim/posts/posts_helper.rb +34 -0
  57. data/app/helpers/decidim/posts/reaction_helper.rb +22 -0
  58. data/app/models/decidim/posts/answer.rb +13 -0
  59. data/app/models/decidim/posts/application_record.rb +10 -0
  60. data/app/models/decidim/posts/post.rb +116 -0
  61. data/app/models/decidim/posts/question.rb +17 -0
  62. data/app/models/decidim/posts/reaction.rb +22 -0
  63. data/app/models/decidim/posts/reaction_type.rb +13 -0
  64. data/app/models/decidim/posts/user_answer.rb +11 -0
  65. data/app/packs/entrypoints/decidim_posts.js +5 -0
  66. data/app/packs/entrypoints/decidim_posts.scss +1 -0
  67. data/app/packs/images/decidim/posts/icon.svg +1 -0
  68. data/app/packs/src/decidim/posts/carousel.js +112 -0
  69. data/app/packs/src/decidim/posts/host_status.js +75 -0
  70. data/app/packs/src/decidim/posts/newFeeds.js +98 -0
  71. data/app/packs/src/decidim/posts/posts.js +272 -0
  72. data/app/packs/src/decidim/posts/submenu.js +46 -0
  73. data/app/packs/src/decidim/posts/survey.js +94 -0
  74. data/app/packs/stylesheets/decidim/posts/_variables.scss +10 -0
  75. data/app/packs/stylesheets/decidim/posts/posts.scss +415 -0
  76. data/app/permissions/decidim/posts/admin/permissions.rb +23 -0
  77. data/app/permissions/decidim/posts/permissions.rb +101 -0
  78. data/app/presenters/decidim/posts/post_presenter.rb +45 -0
  79. data/app/views/decidim/posts/admin/posts/_post-tr.html.erb +45 -0
  80. data/app/views/decidim/posts/admin/posts/index.html.erb +54 -0
  81. data/app/views/decidim/posts/meetings/edit.html.erb +24 -0
  82. data/app/views/decidim/posts/posts/_admin_options.html.erb +30 -0
  83. data/app/views/decidim/posts/posts/_attachment.html.erb +24 -0
  84. data/app/views/decidim/posts/posts/_edit_form.html.erb +16 -0
  85. data/app/views/decidim/posts/posts/_feed.html.erb +8 -0
  86. data/app/views/decidim/posts/posts/_form.html.erb +104 -0
  87. data/app/views/decidim/posts/posts/_index.html.erb +49 -0
  88. data/app/views/decidim/posts/posts/_meeting_form.erb +18 -0
  89. data/app/views/decidim/posts/posts/_new.html.erb +33 -0
  90. data/app/views/decidim/posts/posts/_new_survey.html.erb +20 -0
  91. data/app/views/decidim/posts/posts/_new_survey_answer.html.erb +11 -0
  92. data/app/views/decidim/posts/posts/_new_survey_question.html.erb +17 -0
  93. data/app/views/decidim/posts/posts/_post.html.erb +11 -0
  94. data/app/views/decidim/posts/posts/_post_form.html.erb +5 -0
  95. data/app/views/decidim/posts/posts/_sidebar.html.erb +36 -0
  96. data/app/views/decidim/posts/posts/edit.html.erb +34 -0
  97. data/app/views/decidim/posts/posts/index.html.erb +1 -0
  98. data/app/views/decidim/posts/posts/show.html.erb +12 -0
  99. data/app/views/decidim/posts/reactions/_update_buttons_and_counters.html.erb +1 -0
  100. data/config/assets.rb +9 -0
  101. data/config/i18n-tasks.yml +10 -0
  102. data/config/locales/bs.yml +215 -0
  103. data/config/locales/de.yml +217 -0
  104. data/config/locales/en.yml +216 -0
  105. data/config/locales/hr.yml +219 -0
  106. data/config/locales/it.yml +215 -0
  107. data/config/locales/sr.yml +220 -0
  108. data/config/locales/tr.yml +219 -0
  109. data/lib/decidim/posts/admin.rb +10 -0
  110. data/lib/decidim/posts/admin_engine.rb +25 -0
  111. data/lib/decidim/posts/component.rb +59 -0
  112. data/lib/decidim/posts/content_blocks/content_blocks_homepage.rb +19 -0
  113. data/lib/decidim/posts/engine.rb +84 -0
  114. data/lib/decidim/posts/test/factories.rb +14 -0
  115. data/lib/decidim/posts/version.rb +9 -0
  116. data/lib/decidim/posts.rb +13 -0
  117. metadata +183 -0
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require_dependency "decidim/posts/application_controller"
4
+
5
+ module Decidim
6
+ module Posts
7
+ class PostsController < Decidim::Posts::ApplicationController
8
+ include Decidim::ApplicationHelper
9
+ include FormFactory
10
+ include Flaggable
11
+ include Decidim::AttachmentsHelper
12
+
13
+ helper_method :post_presenter, :form_presenter
14
+
15
+ def index
16
+ enforce_permission_to :read, :post
17
+
18
+ @posts = posts(filter_post_category).where(fixed: [false, nil])
19
+ @fixed_posts = posts.where(fixed: true).order(created_at: :desc)
20
+
21
+ @meetings = meetings(filter_post_category)
22
+
23
+ @non_fixed_objects = (@posts + @meetings).sort_by(&:created_at).reverse
24
+ @non_fixed_objects = Kaminari.paginate_array(@non_fixed_objects).page(params[:page]).per(20)
25
+ end
26
+
27
+ # action to load more posts and meetings on the index page via ajax
28
+ def load_more
29
+ enforce_permission_to :read, :post
30
+
31
+ @posts = posts(filter_post_category).where(fixed: false)
32
+ @meetings = meetings(filter_post_category)
33
+
34
+ @non_fixed_objects = (@posts + @meetings).sort_by(&:created_at).reverse
35
+ @non_fixed_objects = Kaminari.paginate_array(@non_fixed_objects).page(params[:page]).per(20)
36
+
37
+ render partial: "decidim/posts/posts/feed", locals: { objects: @non_fixed_objects }
38
+ end
39
+
40
+ def show
41
+ @post = Post.find(params[:id])
42
+
43
+ enforce_permission_to :read, :post
44
+
45
+ raise ActionController::RoutingError, "Not Found" unless @post
46
+ end
47
+
48
+ def new
49
+ enforce_permission_to :create, :post
50
+ @form = form(PostForm).from_params(post_creation_params)
51
+ end
52
+
53
+ def create
54
+ enforce_permission_to :create, :post
55
+
56
+ @form = form(PostForm).from_params(post_creation_params)
57
+
58
+ CreatePost.call(@form) do
59
+ on(:ok) do |post|
60
+ flash[:notice] = I18n.t("posts.create.success", scope: "decidim.posts")
61
+ # TODO: implement javascript to create a new post without reloading the page
62
+ # redirect_to decidim.root_path
63
+ # redirect_to current_component_path
64
+ redirect_to posts_path
65
+ end
66
+
67
+ on(:invalid) do
68
+ flash.now[:alert] = I18n.t("posts.create.invalid", scope: "decidim.posts")
69
+ redirect_to posts_path
70
+ end
71
+ end
72
+ end
73
+
74
+ def edit
75
+ @post = Post.find(params[:id])
76
+ enforce_permission_to :edit, :post, post: @post
77
+ @form = Decidim::Posts::PostForm.from_model(@post).with_context(context)
78
+ end
79
+
80
+ def update
81
+ @post = Post.find(params[:id])
82
+ enforce_permission_to :edit, :post, post: @post
83
+
84
+ @form = form(PostForm).from_params(params)
85
+ UpdatePost.call(@form, current_user, @post) do
86
+ on(:ok) do |post|
87
+ flash[:notice] = I18n.t("posts.update.success", scope: "decidim.posts")
88
+ redirect_to posts_path
89
+ # redirect_to Decidim::ResourceLocatorPresenter.new(@post).path
90
+ end
91
+
92
+ on(:invalid) do
93
+ flash.now[:alert] = I18n.t("posts.update.invalid", scope: "decidim.posts")
94
+ render :edit
95
+ end
96
+ end
97
+ end
98
+
99
+ def destroy
100
+ @post = Post.find(params[:id])
101
+ enforce_permission_to :delete, :post, post: @post
102
+
103
+ DestroyPost.call(@post, current_user) do
104
+ on(:ok) do
105
+ flash[:notice] = I18n.t("posts.destroy.success", scope: "decidim.posts")
106
+ redirect_to posts_path
107
+ end
108
+
109
+ on(:invalid) do
110
+ flash.now[:alert] = I18n.t("posts.destroy.invalid", scope: "decidim.posts")
111
+ redirect_to posts_path
112
+ end
113
+ end
114
+ end
115
+
116
+ def change_status
117
+ @post = Decidim::Posts::Post.find(params[:id])
118
+ # enable_comments: params[:status] ? true : false
119
+ if @post.update(status: params[:status], enable_comments: false)
120
+ render json: { message: 'Status updated successfully', new_content: cell("decidim/posts/post_host", @post).call(:show) }, status: :ok
121
+ else
122
+ render json: { error: 'Failed to update status' }, status: :unprocessable_entity
123
+ end
124
+
125
+ end
126
+
127
+ private
128
+
129
+ def post_categories
130
+ ["post", "host", "calendar", "survey", "sharecare"]
131
+ end
132
+
133
+ def allowed_post_categories
134
+ ["all"] + post_categories
135
+ end
136
+
137
+ def default_filter_post_category
138
+ return "host" if current_user.admin?
139
+
140
+ "all"
141
+ end
142
+
143
+ def filter_post_category
144
+ allowed_post_categories.include?(params[:filter_post_category]) ? params[:filter_post_category] : default_filter_post_category
145
+ end
146
+
147
+ def posts(filter_category=nil)
148
+ category = filter_category == "all" ? nil : filter_category
149
+
150
+ Decidim::Posts::Post
151
+ .where(decidim_component_id: current_component.id)
152
+ .filter_category(category)
153
+ .order(created_at: :desc)
154
+ .includes(:attachments)
155
+ end
156
+
157
+ def meetings(filter_category=nil)
158
+ category = filter_category == "all" ? nil : filter_category
159
+
160
+ if category.blank? || category == 'calendar'
161
+ meetings_component.blank? ? [] : Decidim::Meetings::Meeting.where(component: meetings_component).published.except_withdrawn
162
+ else
163
+ []
164
+ end
165
+ end
166
+
167
+ def post_presenter
168
+ @post_presenter ||= present(@post)
169
+ end
170
+
171
+ def form_presenter
172
+ @form_presenter ||= present(@form, presenter_class: Decidim::Posts::PostPresenter)
173
+ end
174
+
175
+ def post_creation_params
176
+ params[:post]
177
+ end
178
+
179
+ def participatory_space
180
+ @participatory_space ||= current_component.participatory_space
181
+ end
182
+
183
+ def meetings_component
184
+ @meetings_component ||= participatory_space.components.find_by(manifest_name: "meetings")
185
+ end
186
+
187
+ # def meeting_form
188
+ # form(Decidim::Meetings::MeetingForm).from_params(params)
189
+ # end
190
+
191
+ def context
192
+ {
193
+ current_component: current_component,
194
+ current_organization: current_component.organization,
195
+ current_user:,
196
+ current_participatory_space: participatory_space
197
+ }
198
+ end
199
+
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
5
+ class ReactionsController < Decidim::Components::BaseController
6
+ before_action :authenticate_user!
7
+
8
+ def create
9
+ enforce_permission_to(:create, :reaction, resource:)
10
+
11
+ reaction_type_id = params[:reaction_type_id]
12
+ user_group_id = params[:user_group_id]
13
+
14
+ AddReactionToResource.call(resource, current_user, user_group_id, reaction_type_id) do
15
+ on(:ok) do
16
+ resource.reload
17
+ render partial: "update_buttons_and_counters", locals: { resource: resource }
18
+ #render json: { success: true, reaction_type: @reaction.reaction_type }, status: :ok
19
+ end
20
+
21
+ on(:invalid) do
22
+ render json: { error: I18n.t("resource_reactions.create.error", scope: "decidim") }, status: :unprocessable_entity
23
+ #render json: { success: false, errors: @reaction.errors.full_messages }, status: :unprocessable_entity
24
+ end
25
+ end
26
+ end
27
+
28
+ def destroy
29
+ enforce_permission_to(:withdraw, :reaction, resource:)
30
+
31
+ user_group_id = params[:user_group_id]
32
+ user_group = user_groups.find(user_group_id) if user_group_id
33
+
34
+ RemoveReactionFromResource.call(resource, current_user, user_group) do
35
+ on(:ok) do
36
+ resource.reload
37
+ render :update_buttons_and_counters
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def resource
45
+ gid_param = params[:id] || params[:resource_id]
46
+ @resource ||= GlobalID::Locator.locate(GlobalID.parse(gid_param))
47
+ end
48
+
49
+ def user_groups
50
+ Decidim::UserGroups::ManageableUserGroups.for(current_user).verified
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ # app/controllers/decidim/posts/user_answers_controller.rb
2
+ module Decidim
3
+ module Posts
4
+ class UserAnswersController < ApplicationController
5
+
6
+ def create
7
+ answer_id = params[:answer_id].to_i
8
+ checked = params[:checked].to_s.downcase == "true" ? true : false
9
+
10
+ answer = Answer.find(answer_id)
11
+
12
+ if checked
13
+ if answer.question.single_choice?
14
+ UserAnswer.where(decidim_user_id: current_user.id, decidim_posts_answer_id: answer.question.answers.pluck(:id)).destroy_all
15
+ end
16
+ UserAnswer.find_or_create_by(decidim_user_id: current_user.id, decidim_posts_answer_id: answer_id)
17
+ else
18
+ UserAnswer.where(decidim_user_id: current_user.id, decidim_posts_answer_id: answer_id).destroy_all
19
+ end
20
+
21
+ # get count of user_answers for all answers of the question
22
+ user_answers_counts = {}
23
+ answer.question.answers.each do |a|
24
+ user_answers_counts[a.id] = UserAnswer.where(decidim_posts_answer_id: a.id).count
25
+ end
26
+ result = {
27
+ user_answers: user_answers_counts,
28
+ survey_responses_count: answer.question.post.survey_responses_count
29
+ }
30
+
31
+ render json: result, status: :ok
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
5
+ class ResourceReactedEvent < Decidim::Events::SimpleEvent
6
+ i18n_attributes :reactor_nickname, :reactor_name, :reactor_path, :nickname, :resource_type
7
+
8
+ delegate :nickname, :name, to: :reactor, prefix: true
9
+
10
+ def nickname
11
+ reactor_nickname
12
+ end
13
+
14
+ def reactor_path
15
+ reactor.profile_path
16
+ end
17
+
18
+ def resource_text
19
+ return resource.body if resource.respond_to? :body
20
+ return resource.description if resource.respond_to? :description
21
+ end
22
+
23
+ def resource_type
24
+ resource.class.model_name.human
25
+ end
26
+
27
+ private
28
+
29
+ def reactor
30
+ @reactor ||= Decidim::UserPresenter.new(reactor_user)
31
+ end
32
+
33
+ def reactor_user
34
+ @reactor_user ||= Decidim::User.find_by(id: extra[:reactor_id])
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ module Decidim
2
+ module Posts
3
+ class AnswerForm < Decidim::Form
4
+ include Decidim::TranslatableAttributes
5
+
6
+ mimic :answer
7
+
8
+ attribute :title, Decidim::Attributes::CleanString
9
+
10
+ validates :title, presence: true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
5
+ class PostForm < Decidim::Form
6
+ include Decidim::TranslatableAttributes
7
+ include Decidim::AttachmentAttributes
8
+ # include Decidim::HasUploadValidations # should be included by AttachmentForm
9
+
10
+ mimic :post
11
+
12
+ #attribute :body, Decidim::Attributes::CleanString
13
+ # attribute :body, String
14
+ attribute :body, Decidim::Attributes::CleanString
15
+ attribute :category, String
16
+ attribute :enable_comments, Boolean, default: true
17
+ #attribute :body_template, String
18
+ #attribute :user_group_id, Integer
19
+ #attribute :category_id, Integer
20
+ #attribute :scope_id, Integer
21
+ attribute :attachment, AttachmentForm
22
+ #attribute :suggested_hashtags, Array[String]
23
+ attribute :questions, Array[QuestionForm]
24
+ attribute :highlighted, Boolean
25
+ attribute :fixed, Boolean
26
+
27
+ attachments_attribute :documents
28
+
29
+ validates :body, presence: true#, etiquette: true
30
+ # validates :body, length: { in: 15..150 }
31
+ validates :category, presence: true
32
+ validate :questions_validator
33
+
34
+ # validate :body_is_not_bare_template
35
+ validate :notify_missing_attachment_if_errored
36
+
37
+ alias component current_component
38
+ # delegate :categories, to: :current_component
39
+
40
+ def map_model(model)
41
+ self.body = translated_attribute(model.body)
42
+ self.highlighted = model.highlighted
43
+ self.fixed = model.fixed
44
+ self.category = model.category
45
+ self.enable_comments = model.enable_comments
46
+
47
+ self.questions = model.questions.map do |question|
48
+ QuestionForm.from_model(question)
49
+ end
50
+
51
+ # @suggested_hashtags = Decidim::ContentRenderers::HashtagRenderer.new(body).extra_hashtags.map(&:name).map(&:downcase)
52
+
53
+ # presenter = PostPresenter.new(model)
54
+ # self.body = presenter.editor_body(all_locales: body.is_a?(Hash))
55
+
56
+ self.documents = model.attachments
57
+ end
58
+
59
+ # Finds the Category from the category_id.
60
+ #
61
+ # Returns a Decidim::Category
62
+ # def category
63
+ # @category ||= categories.find_by(id: category_id)
64
+ # end
65
+
66
+ # def extra_hashtags
67
+ # @extra_hashtags ||= (component_automatic_hashtags + suggested_hashtags).uniq
68
+ # end
69
+
70
+ # def suggested_hashtags
71
+ # downcased_suggested_hashtags = super.to_set(&:downcase)
72
+ # component_suggested_hashtags.select { |hashtag| downcased_suggested_hashtags.member?(hashtag.downcase) }
73
+ # end
74
+
75
+ # def suggested_hashtag_checked?(hashtag)
76
+ # suggested_hashtags.member?(hashtag)
77
+ # end
78
+
79
+ # def component_automatic_hashtags
80
+ # @component_automatic_hashtags ||= ordered_hashtag_list(current_component.current_settings.automatic_hashtags)
81
+ # end
82
+
83
+ # def component_suggested_hashtags
84
+ # @component_suggested_hashtags ||= ordered_hashtag_list(current_component.current_settings.suggested_hashtags)
85
+ # end
86
+
87
+ def has_attachments?
88
+ post.has_attachments? && errors[:documents].empty? && documents.present?
89
+ end
90
+
91
+ def has_error_in_attachments?
92
+ errors[:documents].present?
93
+ end
94
+
95
+ private
96
+
97
+ # def body_is_not_bare_template
98
+ # return if body_template.blank?
99
+
100
+ # errors.add(:body, :cant_be_equal_to_template) if body.presence == body_template.presence
101
+ # end
102
+
103
+ # This method will add an error to the "documents" field only if there is any error
104
+ # in any other field. This is needed because when the form has an error, the attachment
105
+ # is lost, so we need a way to inform the user of this problem.
106
+ def notify_missing_attachment_if_errored
107
+ errors.add(:documents, :needs_to_be_reattached) if errors.any? && documents.present?
108
+ end
109
+
110
+ def questions_validator
111
+ @questions_validator ||= questions.reject(&:deleted).each do |question|
112
+ errors.add(:questions, I18n.t("decidim.posts.posts.form.question_too_short")) if question.title.length <= 3
113
+ end.map(&:title).uniq
114
+ end
115
+
116
+ # def ordered_hashtag_list(string)
117
+ # string.to_s.split.compact_blank.uniq.sort_by(&:parameterize)
118
+ # end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,20 @@
1
+ module Decidim
2
+ module Posts
3
+ class QuestionForm < Decidim::Form
4
+ include Decidim::TranslatableAttributes
5
+
6
+ mimic :question
7
+
8
+ attribute :title, Decidim::Attributes::CleanString
9
+ attribute :answers, Array[AnswerForm]
10
+ attribute :deleted, Boolean, default: false
11
+ attribute :question_type, String
12
+
13
+ validates :title, presence: true
14
+
15
+ def map_model(model)
16
+ self.title = translated_attribute(model.title)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ module Decidim
2
+ module Posts
3
+ module Admin
4
+ module PostsHelper
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
5
+ # Custom helpers, scoped to the feeds engine.
6
+ #
7
+ module ApplicationHelper
8
+ include PaginateHelper
9
+ include SanitizeHelper
10
+ # include Decidim::Posts::PostsHelper
11
+ include ::Decidim::EndorsableHelper
12
+ include ::Decidim::FollowableHelper
13
+ include Decidim::Comments::CommentsHelper
14
+
15
+ def posts_component_for_meeting(meeting)
16
+ meeting.component.participatory_space.components.find_by(manifest_name: "posts")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
5
+ module PostCellsHelper
6
+ include Decidim::Posts::ApplicationHelper
7
+ include Decidim::Posts::Engine.routes.url_helpers
8
+ include Decidim::LayoutHelper
9
+ include Decidim::ApplicationHelper
10
+ include Decidim::TranslationsHelper
11
+ include Decidim::ResourceReferenceHelper
12
+ include Decidim::TranslatableAttributes
13
+ include Decidim::CardHelper
14
+
15
+ delegate :body, :category, to: :model
16
+
17
+ def current_settings
18
+ model.component.current_settings
19
+ end
20
+
21
+ def component_settings
22
+ model.component.settings
23
+ end
24
+
25
+ def current_component
26
+ model.component
27
+ end
28
+
29
+ def participatory_space
30
+ @participatory_space ||= current_component.participatory_space
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
5
+ # Custom helpers used in posts views
6
+ module PostsHelper
7
+ include Decidim::ApplicationHelper
8
+ include Decidim::TranslationsHelper
9
+ include Decidim::ResourceHelper
10
+ include Decidim::Meetings::ApplicationHelper
11
+
12
+ def calendar_months
13
+ [Date.current, Date.current.next_month]
14
+ end
15
+
16
+ def collection
17
+ @collection ||= meetings.limit(limit)
18
+ end
19
+
20
+ def meetings
21
+ @meetings ||= show_upcoming_meetings? ? upcoming_meetings : past_meetings
22
+ end
23
+
24
+ def limit
25
+ 3
26
+ end
27
+
28
+ def participatory_space
29
+ @participatory_space ||= current_component.participatory_space
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
5
+ module ReactionHelper
6
+ def reaction_icon(icon_name)
7
+ emojis = {
8
+ "heart": "❤️",
9
+ "laugh": "😂",
10
+ "surprise": "😮",
11
+ "sad": "😢",
12
+ "angry": "😡",
13
+ "like": "👍"
14
+ }
15
+
16
+ return "?" unless emojis.key?(icon_name.to_sym)
17
+
18
+ emojis[icon_name.to_sym]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Decidim
2
+ module Posts
3
+ class Answer < Feeds::ApplicationRecord
4
+ include Decidim::TranslatableResource
5
+ include Decidim::TranslatableAttributes
6
+
7
+ belongs_to :question, class_name: "Decidim::Posts::Question", foreign_key: "decidim_posts_question_id"
8
+ has_many :user_answers, class_name: "Decidim::Posts::UserAnswer", dependent: :destroy, foreign_key: "decidim_posts_answer_id"
9
+
10
+ translatable_fields :title
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Posts
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