decidim-posts 1.0.0

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