decidim-challenges 0.5.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 (231) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +49 -0
  4. data/Rakefile +9 -0
  5. data/app/cells/decidim/challenges/challenge_cell.rb +21 -0
  6. data/app/cells/decidim/challenges/challenge_g/show.erb +47 -0
  7. data/app/cells/decidim/challenges/challenge_g_cell.rb +69 -0
  8. data/app/cells/decidim/problems/problem_cell.rb +21 -0
  9. data/app/cells/decidim/problems/problem_g/show.erb +35 -0
  10. data/app/cells/decidim/problems/problem_g_cell.rb +61 -0
  11. data/app/cells/decidim/solutions/solution_cell.rb +21 -0
  12. data/app/cells/decidim/solutions/solution_g/show.erb +30 -0
  13. data/app/cells/decidim/solutions/solution_g_cell.rb +84 -0
  14. data/app/commands/decidim/challenges/admin/create_challenge.rb +67 -0
  15. data/app/commands/decidim/challenges/admin/destroy_challenge.rb +49 -0
  16. data/app/commands/decidim/challenges/admin/export_challenge_surveys.rb +45 -0
  17. data/app/commands/decidim/challenges/admin/publish_challenge.rb +40 -0
  18. data/app/commands/decidim/challenges/admin/unpublish_challenge.rb +40 -0
  19. data/app/commands/decidim/challenges/admin/update_challenge.rb +72 -0
  20. data/app/commands/decidim/challenges/admin/update_surveys.rb +43 -0
  21. data/app/commands/decidim/challenges/survey_challenge.rb +67 -0
  22. data/app/commands/decidim/command_utils.rb +20 -0
  23. data/app/commands/decidim/problems/admin/create_problem.rb +66 -0
  24. data/app/commands/decidim/problems/admin/destroy_problem.rb +49 -0
  25. data/app/commands/decidim/problems/admin/publish_problem.rb +40 -0
  26. data/app/commands/decidim/problems/admin/unpublish_problem.rb +40 -0
  27. data/app/commands/decidim/problems/admin/update_problem.rb +69 -0
  28. data/app/commands/decidim/solutions/admin/create_solution.rb +65 -0
  29. data/app/commands/decidim/solutions/admin/destroy_solution.rb +43 -0
  30. data/app/commands/decidim/solutions/admin/publish_solution.rb +40 -0
  31. data/app/commands/decidim/solutions/admin/unpublish_solution.rb +40 -0
  32. data/app/commands/decidim/solutions/admin/update_solution.rb +67 -0
  33. data/app/commands/decidim/solutions/create_solution.rb +67 -0
  34. data/app/controllers/concerns/decidim/challenges/admin/filterable.rb +27 -0
  35. data/app/controllers/concerns/decidim/challenges/orderable_challenges.rb +37 -0
  36. data/app/controllers/concerns/decidim/problems/admin/filterable.rb +27 -0
  37. data/app/controllers/concerns/decidim/problems/orderable_problems.rb +37 -0
  38. data/app/controllers/concerns/decidim/solutions/admin/filterable.rb +27 -0
  39. data/app/controllers/concerns/decidim/solutions/orderable_solutions.rb +37 -0
  40. data/app/controllers/concerns/decidim/with_default_filters.rb +23 -0
  41. data/app/controllers/concerns/decidim/with_sdgs.rb +19 -0
  42. data/app/controllers/decidim/challenges/admin/application_controller.rb +15 -0
  43. data/app/controllers/decidim/challenges/admin/challenge_publications_controller.rb +53 -0
  44. data/app/controllers/decidim/challenges/admin/challenges_controller.rb +108 -0
  45. data/app/controllers/decidim/challenges/admin/survey_form_controller.rb +43 -0
  46. data/app/controllers/decidim/challenges/admin/surveys_controller.rb +104 -0
  47. data/app/controllers/decidim/challenges/application_controller.rb +13 -0
  48. data/app/controllers/decidim/challenges/challenges_controller.rb +70 -0
  49. data/app/controllers/decidim/challenges/surveys_controller.rb +61 -0
  50. data/app/controllers/decidim/problems/admin/application_controller.rb +15 -0
  51. data/app/controllers/decidim/problems/admin/problem_publications_controller.rb +53 -0
  52. data/app/controllers/decidim/problems/admin/problems_controller.rb +102 -0
  53. data/app/controllers/decidim/problems/application_controller.rb +13 -0
  54. data/app/controllers/decidim/problems/problems_controller.rb +62 -0
  55. data/app/controllers/decidim/sdgs/admin/application_controller.rb +15 -0
  56. data/app/controllers/decidim/sdgs/admin/sdgs_controller.rb +18 -0
  57. data/app/controllers/decidim/sdgs/application_controller.rb +13 -0
  58. data/app/controllers/decidim/sdgs/sdgs_controller.rb +24 -0
  59. data/app/controllers/decidim/solutions/admin/application_controller.rb +15 -0
  60. data/app/controllers/decidim/solutions/admin/solution_publications_controller.rb +53 -0
  61. data/app/controllers/decidim/solutions/admin/solutions_controller.rb +106 -0
  62. data/app/controllers/decidim/solutions/application_controller.rb +13 -0
  63. data/app/controllers/decidim/solutions/solutions_controller.rb +115 -0
  64. data/app/forms/decidim/challenges/admin/challenge_surveys_form.rb +35 -0
  65. data/app/forms/decidim/challenges/admin/challenges_form.rb +78 -0
  66. data/app/forms/decidim/problems/admin/problems_form.rb +90 -0
  67. data/app/forms/decidim/solutions/admin/solutions_form.rb +71 -0
  68. data/app/forms/decidim/solutions/solutions_form.rb +50 -0
  69. data/app/helpers/decidim/challenges/application_helper.rb +25 -0
  70. data/app/helpers/decidim/challenges/challenge_cells_helper.rb +57 -0
  71. data/app/helpers/decidim/challenges/challenges_helper.rb +39 -0
  72. data/app/helpers/decidim/problems/application_helper.rb +10 -0
  73. data/app/helpers/decidim/problems/problem_cells_helper.rb +23 -0
  74. data/app/helpers/decidim/problems/problems_helper.rb +24 -0
  75. data/app/helpers/decidim/sdgs/sdgs_helper.rb +28 -0
  76. data/app/helpers/decidim/show_filters_helper.rb +13 -0
  77. data/app/helpers/decidim/solutions/application_helper.rb +27 -0
  78. data/app/helpers/decidim/solutions/solution_cells_helper.rb +56 -0
  79. data/app/helpers/decidim/solutions/solutions_helper.rb +21 -0
  80. data/app/models/decidim/application_record.rb +8 -0
  81. data/app/models/decidim/challenges/challenge.rb +82 -0
  82. data/app/models/decidim/challenges/survey.rb +19 -0
  83. data/app/models/decidim/problems/problem.rb +118 -0
  84. data/app/models/decidim/sdgs/sdg.rb +47 -0
  85. data/app/models/decidim/solutions/application_record.rb +10 -0
  86. data/app/models/decidim/solutions/solution.rb +69 -0
  87. data/app/packs/entrypoints/decidim_challenges.js +4 -0
  88. data/app/packs/entrypoints/decidim_challenges.scss +1 -0
  89. data/app/packs/entrypoints/decidim_problems.js +1 -0
  90. data/app/packs/entrypoints/decidim_problems.scss +1 -0
  91. data/app/packs/entrypoints/decidim_sdgs.js +6 -0
  92. data/app/packs/entrypoints/decidim_sdgs.scss +3 -0
  93. data/app/packs/entrypoints/decidim_shared.js +1 -0
  94. data/app/packs/entrypoints/decidim_shared.scss +1 -0
  95. data/app/packs/entrypoints/decidim_solutions.js +4 -0
  96. data/app/packs/entrypoints/decidim_solutions.scss +1 -0
  97. data/app/packs/fonts/Oswald-Medium.ttf +0 -0
  98. data/app/packs/images/decidim/challenges/decidim_challenges_icon.svg +1 -0
  99. data/app/packs/images/decidim/sdgs/ods-01.svg +11 -0
  100. data/app/packs/images/decidim/sdgs/ods-02.svg +11 -0
  101. data/app/packs/images/decidim/sdgs/ods-03.svg +11 -0
  102. data/app/packs/images/decidim/sdgs/ods-04.svg +12 -0
  103. data/app/packs/images/decidim/sdgs/ods-05.svg +12 -0
  104. data/app/packs/images/decidim/sdgs/ods-06.svg +20 -0
  105. data/app/packs/images/decidim/sdgs/ods-07.svg +18 -0
  106. data/app/packs/images/decidim/sdgs/ods-08.svg +13 -0
  107. data/app/packs/images/decidim/sdgs/ods-09.svg +25 -0
  108. data/app/packs/images/decidim/sdgs/ods-10.svg +13 -0
  109. data/app/packs/images/decidim/sdgs/ods-11.svg +41 -0
  110. data/app/packs/images/decidim/sdgs/ods-12.svg +11 -0
  111. data/app/packs/images/decidim/sdgs/ods-13.svg +57 -0
  112. data/app/packs/images/decidim/sdgs/ods-14.svg +14 -0
  113. data/app/packs/images/decidim/sdgs/ods-15.svg +11 -0
  114. data/app/packs/images/decidim/sdgs/ods-16.svg +14 -0
  115. data/app/packs/images/decidim/sdgs/ods-17.svg +39 -0
  116. data/app/packs/images/decidim/sdgs/sdg-wheel.png +0 -0
  117. data/app/packs/images/decidim/sdgs/un_emblem_square.png +0 -0
  118. data/app/packs/src/decidim/challenges/add_challenge.js +3 -0
  119. data/app/packs/src/decidim/challenges/admin/challenges_form.js +3 -0
  120. data/app/packs/src/decidim/sdgs/filter.js +75 -0
  121. data/app/packs/stylesheets/decidim/challenges/challenges.scss +3 -0
  122. data/app/packs/stylesheets/decidim/problems/problems.scss +3 -0
  123. data/app/packs/stylesheets/decidim/sdgs/ods.scss +185 -0
  124. data/app/packs/stylesheets/decidim/sdgs/sdgs_filter/button.scss +27 -0
  125. data/app/packs/stylesheets/decidim/sdgs/sdgs_filter/modal.scss +60 -0
  126. data/app/packs/stylesheets/decidim/shared/_cards.scss +52 -0
  127. data/app/packs/stylesheets/decidim/shared/base.scss +9 -0
  128. data/app/packs/stylesheets/decidim/solutions/solutions.scss +3 -0
  129. data/app/permissions/decidim/challenges/admin/permissions.rb +88 -0
  130. data/app/permissions/decidim/challenges/permissions.rb +27 -0
  131. data/app/permissions/decidim/problems/admin/permissions.rb +52 -0
  132. data/app/permissions/decidim/problems/permissions.rb +16 -0
  133. data/app/permissions/decidim/solutions/admin/permissions.rb +58 -0
  134. data/app/permissions/decidim/solutions/permissions.rb +17 -0
  135. data/app/presenters/decidim/challenges/challenge_presenter.rb +134 -0
  136. data/app/presenters/decidim/problems/problem_presenter.rb +108 -0
  137. data/app/presenters/decidim/solutions/solution_presenter.rb +198 -0
  138. data/app/serializers/decidim/challenges/data_serializer.rb +46 -0
  139. data/app/serializers/decidim/challenges/survey_serializer.rb +37 -0
  140. data/app/types/decidim/challenges/challenge_type.rb +28 -0
  141. data/app/types/decidim/challenges/challenges_type.rb +32 -0
  142. data/app/types/decidim/problems/problem_type.rb +29 -0
  143. data/app/types/decidim/problems/problems_type.rb +32 -0
  144. data/app/types/decidim/solutions/solution_type.rb +25 -0
  145. data/app/types/decidim/solutions/solutions_type.rb +32 -0
  146. data/app/views/decidim/challenges/admin/challenges/_form.html.erb +78 -0
  147. data/app/views/decidim/challenges/admin/challenges/edit.html.erb +24 -0
  148. data/app/views/decidim/challenges/admin/challenges/index.html.erb +88 -0
  149. data/app/views/decidim/challenges/admin/challenges/new.html.erb +18 -0
  150. data/app/views/decidim/challenges/admin/surveys/_form.html.erb +9 -0
  151. data/app/views/decidim/challenges/admin/surveys/edit.html.erb +39 -0
  152. data/app/views/decidim/challenges/admin/surveys/index.html.erb +49 -0
  153. data/app/views/decidim/challenges/admin/surveys/show.html.erb +43 -0
  154. data/app/views/decidim/challenges/challenges/_challenge.html.erb +1 -0
  155. data/app/views/decidim/challenges/challenges/_challenges.html.erb +11 -0
  156. data/app/views/decidim/challenges/challenges/_related_problems.html.erb +14 -0
  157. data/app/views/decidim/challenges/challenges/_sidebar_data.html.erb +60 -0
  158. data/app/views/decidim/challenges/challenges/index.html.erb +29 -0
  159. data/app/views/decidim/challenges/challenges/index.js.erb +5 -0
  160. data/app/views/decidim/challenges/challenges/show.html.erb +41 -0
  161. data/app/views/decidim/problems/admin/problems/_form.html.erb +79 -0
  162. data/app/views/decidim/problems/admin/problems/edit.html.erb +24 -0
  163. data/app/views/decidim/problems/admin/problems/index.html.erb +93 -0
  164. data/app/views/decidim/problems/admin/problems/new.html.erb +18 -0
  165. data/app/views/decidim/problems/problems/_problem.html.erb +1 -0
  166. data/app/views/decidim/problems/problems/_problems.html.erb +11 -0
  167. data/app/views/decidim/problems/problems/_sidebar_data.html.erb +70 -0
  168. data/app/views/decidim/problems/problems/index.html.erb +28 -0
  169. data/app/views/decidim/problems/problems/index.js.erb +5 -0
  170. data/app/views/decidim/problems/problems/show.html.erb +38 -0
  171. data/app/views/decidim/sdgs/sdgs/_objectives.html.erb +320 -0
  172. data/app/views/decidim/sdgs/sdgs/_ods.html.erb +132 -0
  173. data/app/views/decidim/sdgs/sdgs/index.html.erb +64 -0
  174. data/app/views/decidim/sdgs/sdgs_filter/_filter_selector.html.erb +7 -0
  175. data/app/views/decidim/sdgs/sdgs_filter/_modal.html.erb +35 -0
  176. data/app/views/decidim/shared/_related_solutions.html.erb +14 -0
  177. data/app/views/decidim/solutions/admin/solutions/_documents.html.erb +13 -0
  178. data/app/views/decidim/solutions/admin/solutions/_form.html.erb +70 -0
  179. data/app/views/decidim/solutions/admin/solutions/_photos.html.erb +13 -0
  180. data/app/views/decidim/solutions/admin/solutions/edit.html.erb +24 -0
  181. data/app/views/decidim/solutions/admin/solutions/index.html.erb +97 -0
  182. data/app/views/decidim/solutions/admin/solutions/new.html.erb +18 -0
  183. data/app/views/decidim/solutions/admin/solutions/show.html.erb +114 -0
  184. data/app/views/decidim/solutions/solutions/_form.html.erb +22 -0
  185. data/app/views/decidim/solutions/solutions/_sidebar_data.html.erb +114 -0
  186. data/app/views/decidim/solutions/solutions/_solution.html.erb +1 -0
  187. data/app/views/decidim/solutions/solutions/_solutions.html.erb +13 -0
  188. data/app/views/decidim/solutions/solutions/index.html.erb +37 -0
  189. data/app/views/decidim/solutions/solutions/index.js.erb +5 -0
  190. data/app/views/decidim/solutions/solutions/new.html.erb +20 -0
  191. data/app/views/decidim/solutions/solutions/show.html.erb +72 -0
  192. data/config/assets.rb +27 -0
  193. data/config/i18n-tasks.yml +10 -0
  194. data/config/locales/ca.yml +1244 -0
  195. data/config/locales/cs.yml +668 -0
  196. data/config/locales/en.yml +1245 -0
  197. data/config/locales/es.yml +1250 -0
  198. data/config/locales/oc.yml +1229 -0
  199. data/db/migrate/20201014125000_create_decidim_challenges_challenges.rb +23 -0
  200. data/db/migrate/20201103155100_create_decidim_problems_problems.rb +25 -0
  201. data/db/migrate/20201116111200_create_decidim_solutions_solutions.rb +21 -0
  202. data/db/migrate/20210413083244_create_decidim_challenges_surveys.rb +14 -0
  203. data/db/migrate/20210413083507_add_survey_attributes_to_challenges.rb +7 -0
  204. data/db/migrate/20210413083604_add_questionnaire_to_existing_challenges.rb +15 -0
  205. data/db/migrate/20210413112229_add_challenge_reference_to_solutions.rb +7 -0
  206. data/db/migrate/20210427091033_remove_require_null_in_problems_for_solutions.rb +7 -0
  207. data/db/migrate/20220407110503_add_card_image_to_challenges.rb +7 -0
  208. data/db/migrate/20240919094714_add_public_fields_to_solutions.rb +10 -0
  209. data/lib/decidim/challenges/admin.rb +10 -0
  210. data/lib/decidim/challenges/admin_engine.rb +37 -0
  211. data/lib/decidim/challenges/component.rb +65 -0
  212. data/lib/decidim/challenges/engine.rb +39 -0
  213. data/lib/decidim/challenges/test/factories.rb +59 -0
  214. data/lib/decidim/challenges/version.rb +14 -0
  215. data/lib/decidim/challenges.rb +13 -0
  216. data/lib/decidim/problems/admin.rb +10 -0
  217. data/lib/decidim/problems/admin_engine.rb +25 -0
  218. data/lib/decidim/problems/component.rb +51 -0
  219. data/lib/decidim/problems/engine.rb +31 -0
  220. data/lib/decidim/problems/test/factories.rb +39 -0
  221. data/lib/decidim/sdgs/admin.rb +10 -0
  222. data/lib/decidim/sdgs/admin_engine.rb +28 -0
  223. data/lib/decidim/sdgs/component.rb +44 -0
  224. data/lib/decidim/sdgs/engine.rb +23 -0
  225. data/lib/decidim/sdgs/test/factories.rb +13 -0
  226. data/lib/decidim/solutions/admin.rb +10 -0
  227. data/lib/decidim/solutions/admin_engine.rb +25 -0
  228. data/lib/decidim/solutions/component.rb +52 -0
  229. data/lib/decidim/solutions/engine.rb +31 -0
  230. data/lib/decidim/solutions/test/factories.rb +28 -0
  231. metadata +316 -0
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Challenges
5
+ #
6
+ # Decorator for challenge
7
+ #
8
+ class ChallengePresenter < SimpleDelegator
9
+ include Rails.application.routes.mounted_helpers
10
+ include ActionView::Helpers::UrlHelper
11
+ include Decidim::SanitizeHelper
12
+ include Decidim::TranslatableAttributes
13
+
14
+ delegate :url, to: :card_image, prefix: true
15
+
16
+ def challenge
17
+ __getobj__
18
+ end
19
+
20
+ def challenge_path
21
+ Decidim::ResourceLocatorPresenter.new(challenge).path
22
+ end
23
+
24
+ def display_mention
25
+ link_to title, challenge_path
26
+ end
27
+
28
+ # Render the challenge title
29
+ #
30
+ # links - should render hashtags as links?
31
+ # extras - should include extra hashtags?
32
+ #
33
+ # Returns a String.
34
+ def title(links: false, extras: true, html_escape: false)
35
+ text = translated_attribute(challenge.title)
36
+ text = decidim_html_escape(text) if html_escape
37
+
38
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
39
+ renderer.render(links:, extras:).html_safe
40
+ end
41
+
42
+ def id_and_title(links: false, extras: true, html_escape: false)
43
+ "##{challenge.id} - #{title(links:, extras:, html_escape:)}"
44
+ end
45
+
46
+ # Render the challenge local_description
47
+ #
48
+ # links - should render hashtags as links?
49
+ # extras - should include extra hashtags?
50
+ #
51
+ # Returns a String.
52
+ def local_description(links: false, extras: true, strip_tags: false)
53
+ text = translated_attribute(challenge.local_description)
54
+
55
+ text = strip_tags(sanitize_text(text)) if strip_tags
56
+
57
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
58
+ text = renderer.render(links:, extras:).html_safe
59
+
60
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
61
+ text
62
+ end
63
+
64
+ # Render the challenge global_description
65
+ #
66
+ # links - should render hashtags as links?
67
+ # extras - should include extra hashtags?
68
+ #
69
+ # Returns a String.
70
+ def global_description(links: false, extras: true, strip_tags: false)
71
+ text = translated_attribute(challenge.global_description)
72
+
73
+ text = strip_tags(sanitize_text(text)) if strip_tags
74
+
75
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
76
+ text = renderer.render(links:, extras:).html_safe
77
+
78
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
79
+ text
80
+ end
81
+
82
+ def card_image_url
83
+ return if challenge.card_image.blank?
84
+
85
+ challenge.attached_uploader(:card_image).url(host: challenge.organization.host)
86
+ end
87
+
88
+ delegate :count, to: :versions, prefix: true
89
+
90
+ def resource_manifest
91
+ challenge.class.resource_manifest
92
+ end
93
+
94
+ private
95
+
96
+ def sanitize_unordered_lists(text)
97
+ text.gsub(%r{(?=.*</ul>)(?!.*?<li>.*?</ol>.*?</ul>)<li>}) { |li| "#{li}• " }
98
+ end
99
+
100
+ def sanitize_ordered_lists(text)
101
+ i = 0
102
+
103
+ text.gsub(%r{(?=.*</ol>)(?!.*?<li>.*?</ul>.*?</ol>)<li>}) do |li|
104
+ i += 1
105
+
106
+ li + "#{i}. "
107
+ end
108
+ end
109
+
110
+ def add_line_feeds_to_paragraphs(text)
111
+ text.gsub("</p>") { |p| "#{p}\n\n" }
112
+ end
113
+
114
+ def add_line_feeds_to_list_items(text)
115
+ text.gsub("</li>") { |li| "#{li}\n" }
116
+ end
117
+
118
+ # Adds line feeds after the paragraph and list item closing tags.
119
+ #
120
+ # Returns a String.
121
+ def add_line_feeds(text)
122
+ add_line_feeds_to_paragraphs(add_line_feeds_to_list_items(text))
123
+ end
124
+
125
+ # Maintains the paragraphs and lists separations with their bullet points and
126
+ # list numberings where appropriate.
127
+ #
128
+ # Returns a String.
129
+ def sanitize_text(text)
130
+ add_line_feeds(sanitize_ordered_lists(sanitize_unordered_lists(text)))
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Problems
5
+ #
6
+ # Decorator for problem
7
+ #
8
+ class ProblemPresenter < SimpleDelegator
9
+ include Rails.application.routes.mounted_helpers
10
+ include ActionView::Helpers::UrlHelper
11
+ include Decidim::SanitizeHelper
12
+ include Decidim::TranslatableAttributes
13
+
14
+ def problem
15
+ __getobj__
16
+ end
17
+
18
+ def problem_path
19
+ Decidim::ResourceLocatorPresenter.new(problem).path
20
+ end
21
+
22
+ def display_mention
23
+ link_to title, problem_path
24
+ end
25
+
26
+ # Render the problem title
27
+ #
28
+ # links - should render hashtags as links?
29
+ # extras - should include extra hashtags?
30
+ #
31
+ # Returns a String.
32
+ def title(links: false, extras: true, html_escape: false)
33
+ text = translated_attribute(problem.title)
34
+ text = decidim_html_escape(text) if html_escape
35
+
36
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
37
+ renderer.render(links:, extras:).html_safe
38
+ end
39
+
40
+ def id_and_title(links: false, extras: true, html_escape: false)
41
+ "##{problem.id} - #{title(links:, extras:, html_escape:)}"
42
+ end
43
+
44
+ # Render the problem description
45
+ #
46
+ # links - should render hashtags as links?
47
+ # extras - should include extra hashtags?
48
+ #
49
+ # Returns a String.
50
+ def description(links: false, extras: true, strip_tags: false)
51
+ text = translated_attribute(problem.description)
52
+
53
+ text = strip_tags(sanitize_text(text)) if strip_tags
54
+
55
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
56
+ text = renderer.render(links:, extras:).html_safe
57
+
58
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
59
+ text
60
+ end
61
+
62
+ delegate :count, to: :versions, prefix: true
63
+
64
+ def resource_manifest
65
+ problem.class.resource_manifest
66
+ end
67
+
68
+ private
69
+
70
+ def sanitize_unordered_lists(text)
71
+ text.gsub(%r{(?=.*</ul>)(?!.*?<li>.*?</ol>.*?</ul>)<li>}) { |li| "#{li}• " }
72
+ end
73
+
74
+ def sanitize_ordered_lists(text)
75
+ i = 0
76
+
77
+ text.gsub(%r{(?=.*</ol>)(?!.*?<li>.*?</ul>.*?</ol>)<li>}) do |li|
78
+ i += 1
79
+
80
+ li + "#{i}. "
81
+ end
82
+ end
83
+
84
+ def add_line_feeds_to_paragraphs(text)
85
+ text.gsub("</p>") { |p| "#{p}\n\n" }
86
+ end
87
+
88
+ def add_line_feeds_to_list_items(text)
89
+ text.gsub("</li>") { |li| "#{li}\n" }
90
+ end
91
+
92
+ # Adds line feeds after the paragraph and list item closing tags.
93
+ #
94
+ # Returns a String.
95
+ def add_line_feeds(text)
96
+ add_line_feeds_to_paragraphs(add_line_feeds_to_list_items(text))
97
+ end
98
+
99
+ # Maintains the paragraphs and lists separations with their bullet points and
100
+ # list numberings where appropriate.
101
+ #
102
+ # Returns a String.
103
+ def sanitize_text(text)
104
+ add_line_feeds(sanitize_ordered_lists(sanitize_unordered_lists(text)))
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Solutions
5
+ #
6
+ # Decorator for solution
7
+ #
8
+ class SolutionPresenter < SimpleDelegator
9
+ include Rails.application.routes.mounted_helpers
10
+ include ActionView::Helpers::UrlHelper
11
+ include Decidim::SanitizeHelper
12
+ include Decidim::TranslatableAttributes
13
+
14
+ def solution
15
+ __getobj__
16
+ end
17
+
18
+ def solution_path
19
+ Decidim::ResourceLocatorPresenter.new(solution).path
20
+ end
21
+
22
+ def display_mention
23
+ link_to title, solution_path
24
+ end
25
+
26
+ # Render the solution title
27
+ #
28
+ # links - should render hashtags as links?
29
+ # extras - should include extra hashtags?
30
+ #
31
+ # Returns a String.
32
+ def title(links: false, extras: true, html_escape: false)
33
+ text = translated_attribute(solution.title)
34
+ text = decidim_html_escape(text) if html_escape
35
+
36
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
37
+ renderer.render(links:, extras:).html_safe
38
+ end
39
+
40
+ def id_and_title(links: false, extras: true, html_escape: false)
41
+ "##{solution.id} - #{title(links:, extras:, html_escape:)}"
42
+ end
43
+
44
+ # Render the solution description
45
+ #
46
+ # links - should render hashtags as links?
47
+ # extras - should include extra hashtags?
48
+ #
49
+ # Returns a String.
50
+ def description(links: false, extras: true, strip_tags: false)
51
+ text = translated_attribute(solution.description)
52
+
53
+ text = strip_tags(sanitize_text(text)) if strip_tags
54
+
55
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
56
+ text = renderer.render(links:, extras:).html_safe
57
+
58
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
59
+ text
60
+ end
61
+
62
+ # Render the solution indicators
63
+ #
64
+ # links - should render hashtags as links?
65
+ # extras - should include extra hashtags?
66
+ #
67
+ # Returns a String.
68
+ def indicators(links: false, extras: true, strip_tags: false)
69
+ text = translated_attribute(solution.indicators)
70
+
71
+ text = strip_tags(sanitize_text(text)) if strip_tags
72
+
73
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
74
+ text = renderer.render(links:, extras:).html_safe
75
+
76
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
77
+ text
78
+ end
79
+
80
+ # Render the solution beneficiaries
81
+ #
82
+ # links - should render hashtags as links?
83
+ # extras - should include extra hashtags?
84
+ #
85
+ # Returns a String.
86
+ def beneficiaries(links: false, extras: true, strip_tags: false)
87
+ text = translated_attribute(solution.beneficiaries)
88
+
89
+ text = strip_tags(sanitize_text(text)) if strip_tags
90
+
91
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
92
+ text = renderer.render(links:, extras:).html_safe
93
+
94
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
95
+ text
96
+ end
97
+
98
+ # Render the solution requirements
99
+ #
100
+ # links - should render hashtags as links?
101
+ # extras - should include extra hashtags?
102
+ #
103
+ # Returns a String.
104
+ def requirements(links: false, extras: true, strip_tags: false)
105
+ text = translated_attribute(solution.requirements)
106
+
107
+ text = strip_tags(sanitize_text(text)) if strip_tags
108
+
109
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
110
+ text = renderer.render(links:, extras:).html_safe
111
+
112
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
113
+ text
114
+ end
115
+
116
+ # Render the solution financing_type
117
+ #
118
+ # links - should render hashtags as links?
119
+ # extras - should include extra hashtags?
120
+ #
121
+ # Returns a String.
122
+ def financing_type(links: false, extras: true, strip_tags: false)
123
+ text = translated_attribute(solution.financing_type)
124
+
125
+ text = strip_tags(sanitize_text(text)) if strip_tags
126
+
127
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
128
+ text = renderer.render(links:, extras:).html_safe
129
+
130
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
131
+ text
132
+ end
133
+
134
+ # Render the solution objectives
135
+ #
136
+ # links - should render hashtags as links?
137
+ # extras - should include extra hashtags?
138
+ #
139
+ # Returns a String.
140
+ def objectives(links: false, extras: true, strip_tags: false)
141
+ text = translated_attribute(solution.objectives)
142
+
143
+ text = strip_tags(sanitize_text(text)) if strip_tags
144
+
145
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
146
+ text = renderer.render(links:, extras:).html_safe
147
+
148
+ text = Decidim::ContentRenderers::LinkRenderer.new(text).render if links
149
+ text
150
+ end
151
+
152
+ delegate :count, to: :versions, prefix: true
153
+
154
+ def resource_manifest
155
+ solution.class.resource_manifest
156
+ end
157
+
158
+ private
159
+
160
+ def sanitize_unordered_lists(text)
161
+ text.gsub(%r{(?=.*</ul>)(?!.*?<li>.*?</ol>.*?</ul>)<li>}) { |li| "#{li}• " }
162
+ end
163
+
164
+ def sanitize_ordered_lists(text)
165
+ i = 0
166
+
167
+ text.gsub(%r{(?=.*</ol>)(?!.*?<li>.*?</ul>.*?</ol>)<li>}) do |li|
168
+ i += 1
169
+
170
+ li + "#{i}. "
171
+ end
172
+ end
173
+
174
+ def add_line_feeds_to_paragraphs(text)
175
+ text.gsub("</p>") { |p| "#{p}\n\n" }
176
+ end
177
+
178
+ def add_line_feeds_to_list_items(text)
179
+ text.gsub("</li>") { |li| "#{li}\n" }
180
+ end
181
+
182
+ # Adds line feeds after the paragraph and list item closing tags.
183
+ #
184
+ # Returns a String.
185
+ def add_line_feeds(text)
186
+ add_line_feeds_to_paragraphs(add_line_feeds_to_list_items(text))
187
+ end
188
+
189
+ # Maintains the paragraphs and lists separations with their bullet points and
190
+ # list numberings where appropriate.
191
+ #
192
+ # Returns a String.
193
+ def sanitize_text(text)
194
+ add_line_feeds(sanitize_ordered_lists(sanitize_unordered_lists(text)))
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Challenges
5
+ # This class serializes the specific data in each Survey.
6
+ # This is `Questionnaire->questions->answer_options` but not `answers`
7
+ # and `answer_choices`.
8
+ class DataSerializer < Decidim::Exporters::Serializer
9
+ # Returns: Array of Decidim::Forms::Questionnaire as a json hash,
10
+ # or nil if none exists.
11
+ def serialize
12
+ component = resource
13
+ surveys = Decidim::Challenges::Survey.where(component:)
14
+ surveys.collect do |survey|
15
+ next if survey.questionnaire.nil?
16
+
17
+ json = serialize_survey(survey)
18
+ json.with_indifferent_access.merge(survey_id: survey.id)
19
+ end
20
+ end
21
+
22
+ def serialize_survey(survey)
23
+ questionnaire = survey.questionnaire
24
+ questionnaire_json = questionnaire.attributes.as_json
25
+ questionnaire_json[:questions] = serialize_questions(questionnaire.questions.order(:position))
26
+ json = survey.attributes.as_json
27
+ json[:questionnaire] = questionnaire_json
28
+ json
29
+ end
30
+
31
+ def serialize_questions(questions)
32
+ questions.collect do |question|
33
+ json = question.attributes.as_json
34
+ json[:answer_options] = serialize_answer_options(question.answer_options)
35
+ json
36
+ end
37
+ end
38
+
39
+ def serialize_answer_options(answer_options)
40
+ answer_options.collect do |option|
41
+ option.attributes.as_json
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Challenges
5
+ class SurveySerializer < Decidim::Exporters::Serializer
6
+ include Decidim::TranslationsHelper
7
+ # Serializes a registration
8
+ def serialize
9
+ {
10
+ id: resource.id,
11
+ user: {
12
+ name: resource.user.name,
13
+ email: resource.user.email,
14
+ },
15
+ survey_form_answers: serialize_answers,
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def serialize_answers
22
+ questions = resource.challenge.questionnaire.questions
23
+ answers = resource.challenge.questionnaire.answers.where(user: resource.user)
24
+ questions.each_with_index.inject({}) do |serialized, (question, idx)|
25
+ answer = answers.find_by(question:)
26
+ serialized.update("#{idx + 1}. #{translated_attribute(question.body)}" => normalize_body(answer))
27
+ end
28
+ end
29
+
30
+ def normalize_body(answer)
31
+ return "" unless answer
32
+
33
+ answer.body || answer.choices.pluck(:body)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Challenges
5
+ class ChallengeType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::ScopableInterface
7
+ implements Decidim::Core::AttachableInterface
8
+ implements Decidim::Core::TraceableInterface
9
+ implements Decidim::Core::TimestampsInterface
10
+
11
+ graphql_name "Challenge"
12
+ description "A challenge"
13
+
14
+ field :id, GraphQL::Types::ID, null: false
15
+ field :title, Decidim::Core::TranslatedFieldType, "The title of this challenge (same as the component name).", null: false
16
+ field :local_description, Decidim::Core::TranslatedFieldType, "The local description of this challenge.", null: true
17
+ field :global_description, Decidim::Core::TranslatedFieldType, "The global description of this challenge.", null: true
18
+ field :tags, Decidim::Core::TranslatedFieldType, "The tags of this challenge.", null: true
19
+ field :sdg_code, GraphQL::Types::String, "The Sustainable Development Goal this challenge is associated with.", null: true
20
+ field :state, GraphQL::Types::String, "The state for this challenge.", null: true
21
+ field :start_date, Decidim::Core::DateType, "The start date", null: true
22
+ field :end_date, Decidim::Core::DateType, "The end date", null: true
23
+ field :published_at, Decidim::Core::DateTimeType, "The moment at which it was published", null: true
24
+ field :coordinating_entities, GraphQL::Types::String, "The entities coordinating this challenge.", null: true
25
+ field :collaborating_entities, GraphQL::Types::String, "The entities collaborating with this challenge.", null: true
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Challenges
5
+ class ChallengesType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::ComponentInterface
7
+
8
+ graphql_name "Challenges"
9
+ description "A challenges component of a participatory space."
10
+
11
+ field :challenges, ChallengeType.connection_type, null: true, connection: true
12
+
13
+ def challenges
14
+ ChallengesTypeHelper.base_scope(object).includes(:component)
15
+ end
16
+
17
+ field(:challenge, ChallengeType, null: true) do
18
+ argument :id, GraphQL::Types::ID, required: true
19
+ end
20
+
21
+ def challenge(**args)
22
+ ChallengesTypeHelper.base_scope(object).find_by(id: args[:id])
23
+ end
24
+ end
25
+
26
+ module ChallengesTypeHelper
27
+ def self.base_scope(component)
28
+ Challenge.where(component:).published
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Problems
5
+ class ProblemType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::TraceableInterface
7
+ implements Decidim::Core::TimestampsInterface
8
+
9
+ graphql_name "Problem"
10
+ description "A problem"
11
+
12
+ field :id, GraphQL::Types::ID, null: false
13
+ field :title, Decidim::Core::TranslatedFieldType, "The title of this problem (same as the component name).", null: false
14
+ field :description, Decidim::Core::TranslatedFieldType, "The description of this problem.", null: true
15
+ field :challenge, Decidim::Challenges::ChallengeType, "The related Challenge", null: true
16
+ field :sectorial_scope, Decidim::Core::ScopeApiType, "The object's sectorial scope", null: true
17
+ field :technological_scope, Decidim::Core::ScopeApiType, "The object's technological scope", null: true
18
+ field :tags, Decidim::Core::TranslatedFieldType, "The tags of this problem.", null: true
19
+ field :state, GraphQL::Types::String, "The state for this problem.", null: true
20
+ field :start_date, Decidim::Core::DateType, "The start date", null: true
21
+ field :end_date, Decidim::Core::DateType, "The end date", null: true
22
+ field :published_at, Decidim::Core::DateTimeType, "The moment at which it was published", null: true
23
+ field :causes, GraphQL::Types::String, "The entities proposing this problem.", null: true
24
+ field :groups_affected, GraphQL::Types::String, "The entities proposing this problem.", null: true
25
+ field :proposing_entities, GraphQL::Types::String, "The entities proposing this problem.", null: true
26
+ field :collaborating_entities, GraphQL::Types::String, "The entities collaborating with this problem.", null: true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Problems
5
+ class ProblemsType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::ComponentInterface
7
+
8
+ graphql_name "Problems"
9
+ description "A problems component of a participatory space."
10
+
11
+ field :problems, ProblemType.connection_type, null: true, connection: true
12
+
13
+ def problems
14
+ ProblemsTypeHelper.base_scope(object).includes(:component)
15
+ end
16
+
17
+ field(:problem, ProblemType, null: true) do
18
+ argument :id, GraphQL::Types::ID, required: true
19
+ end
20
+
21
+ def problem(**args)
22
+ ProblemsTypeHelper.base_scope(object).find_by(id: args[:id])
23
+ end
24
+ end
25
+
26
+ module ProblemsTypeHelper
27
+ def self.base_scope(component)
28
+ Problem.where(component:).published
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Solutions
5
+ class SolutionType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::TraceableInterface
7
+ implements Decidim::Core::TimestampsInterface
8
+
9
+ graphql_name "Solution"
10
+ description "A solution"
11
+
12
+ field :id, GraphQL::Types::ID, null: false
13
+ field :title, Decidim::Core::TranslatedFieldType, "The title of this solution (same as the component name).", null: false
14
+ field :description, Decidim::Core::TranslatedFieldType, "The description of this solution.", null: true
15
+ field :problem, Decidim::Problems::ProblemType, "The related Problem", null: true
16
+ field :tags, Decidim::Core::TranslatedFieldType, "The tags of this solution.", null: true
17
+ field :indicators, Decidim::Core::TranslatedFieldType, "The indicators of this solution.", null: true
18
+ field :beneficiaries, Decidim::Core::TranslatedFieldType, "The beneficiaries of this solution.", null: true
19
+ field :requirements, Decidim::Core::TranslatedFieldType, "The requirements of this solution.", null: true
20
+ field :financing_type, Decidim::Core::TranslatedFieldType, "The financing_type of this solution.", null: true
21
+ field :objectives, Decidim::Core::TranslatedFieldType, "The objectives of this solution.", null: true
22
+ field :published_at, Decidim::Core::DateTimeType, "The moment at which it was published", null: true
23
+ end
24
+ end
25
+ end