decidim-core 0.13.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of decidim-core might be problematic. Click here for more details.

Files changed (236) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/decidim_core_manifest.js +1 -1
  3. data/app/assets/images/decidim/gamification/badges/invitations.svg +117 -0
  4. data/app/assets/javascripts/decidim.js.es6 +4 -1
  5. data/app/assets/javascripts/decidim/ajax_modals.js.es6 +17 -0
  6. data/app/assets/javascripts/decidim/conferences.js.es6 +16 -0
  7. data/app/assets/javascripts/decidim/input_hashtags.js.es6 +115 -0
  8. data/app/assets/javascripts/decidim/input_mentions.js.es6 +2 -3
  9. data/app/assets/javascripts/decidim/vizzs/areachart.js.es6 +226 -0
  10. data/app/assets/javascripts/decidim/vizzs/metrics.js.es6 +26 -0
  11. data/app/assets/javascripts/decidim/vizzs/orgchart.js.es6 +701 -0
  12. data/app/assets/javascripts/decidim/vizzs/renders.js.es6 +11 -0
  13. data/app/assets/stylesheets/decidim/extras/_proposal_form.scss +3 -1
  14. data/app/assets/stylesheets/decidim/layouts/_home.scss +1 -1
  15. data/app/assets/stylesheets/decidim/modules/_areachart.scss +74 -0
  16. data/app/assets/stylesheets/decidim/modules/_badges.scss +116 -0
  17. data/app/assets/stylesheets/decidim/modules/_buttons.scss +5 -0
  18. data/app/assets/stylesheets/decidim/modules/_cards.scss +21 -4
  19. data/app/assets/stylesheets/decidim/modules/_chart-tooltip.scss +42 -0
  20. data/app/assets/stylesheets/decidim/modules/_collapsible-list.scss +12 -8
  21. data/app/assets/stylesheets/decidim/modules/_conference-nav.scss +31 -0
  22. data/app/assets/stylesheets/decidim/modules/_conference-programme.scss +110 -0
  23. data/app/assets/stylesheets/decidim/modules/_conference-speaker.scss +86 -0
  24. data/app/assets/stylesheets/decidim/modules/_conversation.scss +58 -0
  25. data/app/assets/stylesheets/decidim/modules/_help.scss +38 -0
  26. data/app/assets/stylesheets/decidim/modules/_hover-section.scss +29 -0
  27. data/app/assets/stylesheets/decidim/modules/_icons.scss +10 -4
  28. data/app/assets/stylesheets/decidim/modules/_input-hashtags.scss +124 -0
  29. data/app/assets/stylesheets/decidim/modules/_loading-spinner.scss +12 -0
  30. data/app/assets/stylesheets/decidim/modules/_margins.scss +2 -2
  31. data/app/assets/stylesheets/decidim/modules/_modules.scss +15 -0
  32. data/app/assets/stylesheets/decidim/modules/_navbar.scss +9 -0
  33. data/app/assets/stylesheets/decidim/modules/_orgchart.scss +62 -0
  34. data/app/assets/stylesheets/decidim/modules/_status-labels.scss +2 -1
  35. data/app/assets/stylesheets/decidim/modules/_typography.scss +9 -0
  36. data/app/assets/stylesheets/decidim/utils/_helpers.scss +28 -0
  37. data/app/assets/stylesheets/decidim/utils/_mixins.scss +63 -0
  38. data/app/cells/decidim/author/withdraw.erb +1 -1
  39. data/app/cells/decidim/author_cell.rb +1 -1
  40. data/app/cells/decidim/badge/show.erb +36 -0
  41. data/app/cells/decidim/badge_cell.rb +53 -0
  42. data/app/cells/decidim/badges/show.erb +6 -0
  43. data/app/cells/decidim/badges_cell.rb +14 -0
  44. data/app/cells/decidim/card_m/header.erb +1 -1
  45. data/app/cells/decidim/card_m/show.erb +1 -2
  46. data/app/cells/decidim/card_m/top.erb +7 -0
  47. data/app/cells/decidim/card_m_cell.rb +14 -17
  48. data/app/cells/decidim/coauthorships_cell.rb +77 -0
  49. data/app/cells/decidim/collapsible_authors/show.erb +0 -1
  50. data/app/cells/decidim/collapsible_authors_cell.rb +4 -4
  51. data/app/cells/decidim/collapsible_list/show.erb +12 -4
  52. data/app/cells/decidim/collapsible_list_cell.rb +14 -12
  53. data/app/cells/decidim/content_blocks/footer_sub_hero/show.erb +14 -0
  54. data/app/cells/decidim/content_blocks/footer_sub_hero_cell.rb +12 -0
  55. data/app/{views/decidim/pages/home/_hero.html.erb → cells/decidim/content_blocks/hero/show.erb} +4 -4
  56. data/app/cells/decidim/content_blocks/hero_cell.rb +25 -0
  57. data/app/cells/decidim/content_blocks/hero_settings_form/show.erb +7 -0
  58. data/app/cells/decidim/content_blocks/hero_settings_form_cell.rb +13 -0
  59. data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +24 -0
  60. data/app/cells/decidim/content_blocks/highlighted_content_banner_cell.rb +16 -0
  61. data/app/{views/decidim/pages/home/_extended.html.erb → cells/decidim/content_blocks/how_to_participate/show.erb} +10 -10
  62. data/app/cells/decidim/content_blocks/how_to_participate_cell.rb +9 -0
  63. data/app/{views/decidim/pages/home/_statistics.html.erb → cells/decidim/content_blocks/stats/show.erb} +2 -2
  64. data/app/cells/decidim/content_blocks/stats_cell.rb +18 -0
  65. data/app/{views/decidim/pages/home/_sub_hero.html.erb → cells/decidim/content_blocks/sub_hero/show.erb} +2 -2
  66. data/app/cells/decidim/content_blocks/sub_hero_cell.rb +17 -0
  67. data/app/cells/decidim/conversation/show.erb +18 -0
  68. data/app/cells/decidim/conversation_cell.rb +23 -0
  69. data/app/cells/decidim/conversation_header/show.erb +17 -0
  70. data/app/cells/decidim/conversation_header_cell.rb +16 -0
  71. data/app/cells/decidim/conversations/show.erb +45 -0
  72. data/app/cells/decidim/conversations_cell.rb +24 -0
  73. data/app/cells/decidim/follow_button/show.erb +3 -3
  74. data/app/cells/decidim/follow_button_cell.rb +1 -5
  75. data/app/cells/decidim/following_cell.rb +1 -7
  76. data/app/cells/decidim/message/show.erb +15 -0
  77. data/app/cells/decidim/message_cell.rb +23 -0
  78. data/app/cells/decidim/new_conversation/show.erb +19 -0
  79. data/app/cells/decidim/new_conversation_cell.rb +19 -0
  80. data/app/cells/decidim/notifications/show.erb +1 -1
  81. data/app/cells/decidim/profile/show.erb +27 -0
  82. data/app/cells/decidim/profile_cell.rb +33 -0
  83. data/app/cells/decidim/profile_sidebar/show.erb +57 -0
  84. data/app/cells/decidim/profile_sidebar_cell.rb +31 -0
  85. data/app/cells/decidim/tos_page_cell.rb +0 -4
  86. data/app/cells/decidim/user_profile/header.erb +1 -1
  87. data/app/controllers/concerns/decidim/action_authorization.rb +13 -38
  88. data/app/controllers/concerns/decidim/needs_permission.rb +15 -6
  89. data/app/controllers/decidim/application_controller.rb +1 -0
  90. data/app/controllers/decidim/authorization_modals_controller.rb +35 -0
  91. data/app/controllers/decidim/components/base_controller.rb +0 -1
  92. data/app/controllers/decidim/devise/invitations_controller.rb +2 -1
  93. data/app/controllers/decidim/messaging/conversations_controller.rb +2 -11
  94. data/app/controllers/decidim/newsletters_controller.rb +4 -6
  95. data/app/controllers/decidim/notifications_controller.rb +4 -0
  96. data/app/controllers/decidim/pages_controller.rb +3 -7
  97. data/app/controllers/decidim/profiles_controller.rb +17 -7
  98. data/app/forms/decidim/notifications_settings_form.rb +1 -1
  99. data/app/forms/decidim/registration_form.rb +1 -1
  100. data/app/helpers/decidim/action_authorization_helper.rb +51 -46
  101. data/app/helpers/decidim/application_helper.rb +18 -0
  102. data/app/helpers/decidim/card_helper.rb +1 -1
  103. data/app/helpers/decidim/cells_helper.rb +6 -2
  104. data/app/helpers/decidim/resource_helper.rb +8 -1
  105. data/app/helpers/decidim/searches_helper.rb +5 -4
  106. data/app/helpers/decidim/traceability_helper.rb +5 -1
  107. data/app/models/decidim/authorization.rb +2 -2
  108. data/app/models/decidim/content_block.rb +144 -0
  109. data/app/models/decidim/gamification/badge_score.rb +13 -0
  110. data/app/models/decidim/messaging/message.rb +1 -1
  111. data/app/models/decidim/messaging/receipt.rb +1 -1
  112. data/app/models/decidim/organization.rb +1 -5
  113. data/app/models/decidim/resource_permission.rb +8 -0
  114. data/app/models/decidim/searchable_resource.rb +1 -1
  115. data/app/models/decidim/user.rb +17 -1
  116. data/app/permissions/decidim/default_permissions.rb +4 -3
  117. data/app/permissions/decidim/permissions.rb +33 -1
  118. data/app/presenters/decidim/hashtag_presenter.rb +32 -0
  119. data/app/presenters/decidim/resource_locator_presenter.rb +13 -0
  120. data/app/presenters/decidim/user_presenter.rb +1 -1
  121. data/app/queries/decidim/messaging/user_conversations.rb +1 -1
  122. data/app/resolvers/decidim/hashtags_resolver.rb +15 -0
  123. data/app/services/decidim/action_authorizer.rb +9 -8
  124. data/app/types/decidim/core/date_time_type.rb +1 -1
  125. data/app/types/decidim/core/hashtag_type.rb +13 -0
  126. data/app/uploaders/decidim/homepage_image_uploader.rb +1 -1
  127. data/app/uploaders/decidim/image_uploader.rb +1 -0
  128. data/app/views/decidim/authorization_modals/show.html.erb +32 -0
  129. data/app/views/decidim/messaging/conversations/create.js.erb +1 -1
  130. data/app/views/decidim/messaging/conversations/index.html.erb +1 -51
  131. data/app/views/decidim/messaging/conversations/new.html.erb +1 -5
  132. data/app/views/decidim/messaging/conversations/show.html.erb +1 -9
  133. data/app/views/decidim/messaging/conversations/update.js.erb +1 -1
  134. data/app/views/decidim/notifications/index.html.erb +1 -0
  135. data/app/views/decidim/pages/decidim_page.html.erb +9 -0
  136. data/app/views/decidim/pages/home.html.erb +12 -16
  137. data/app/views/decidim/pages/index.html.erb +8 -0
  138. data/app/views/decidim/profiles/_user_follow.erb +2 -2
  139. data/app/views/decidim/profiles/show.html.erb +1 -37
  140. data/app/views/decidim/searches/_results.html.erb +1 -1
  141. data/app/views/decidim/shared/_author_reference.html.erb +1 -1
  142. data/app/views/decidim/shared/_authorization_modal.html.erb +1 -0
  143. data/app/views/decidim/shared/_tags.html.erb +1 -1
  144. data/app/views/kaminari/decidim/_page.html.erb +1 -1
  145. data/app/views/layouts/decidim/_application.html.erb +6 -1
  146. data/app/views/layouts/decidim/_edit_link.html.erb +8 -0
  147. data/app/views/layouts/decidim/_impersonation_warning.html.erb +1 -1
  148. data/app/views/layouts/decidim/_user_menu.html.erb +2 -2
  149. data/app/views/layouts/decidim/_wrapper.html.erb +14 -1
  150. data/config/initializers/carrierwave.rb +15 -0
  151. data/config/locales/ca.yml +78 -30
  152. data/config/locales/en.yml +78 -30
  153. data/config/locales/es-PY.yml +78 -30
  154. data/config/locales/es.yml +78 -30
  155. data/config/locales/eu.yml +78 -30
  156. data/config/locales/fi.yml +262 -214
  157. data/config/locales/fr.yml +78 -30
  158. data/config/locales/gl.yml +78 -30
  159. data/config/locales/hu.yml +781 -0
  160. data/config/locales/it.yml +78 -30
  161. data/config/locales/nl.yml +78 -30
  162. data/config/locales/pl.yml +78 -30
  163. data/config/locales/pt-BR.yml +106 -58
  164. data/config/locales/pt.yml +78 -30
  165. data/config/locales/ru.yml +52 -32
  166. data/config/locales/sv.yml +183 -135
  167. data/config/locales/uk.yml +60 -40
  168. data/config/routes.rb +8 -6
  169. data/db/migrate/20180705091019_create_decidim_resource_permissions.rb +12 -0
  170. data/db/migrate/20180706104107_add_nickname_to_managed_users.rb +14 -0
  171. data/db/migrate/20180706111847_fix_result_follows.rb +9 -0
  172. data/db/migrate/20180724103814_add_content_blocks.rb +22 -0
  173. data/db/migrate/20180726112510_create_decidim_hashtags.rb +17 -0
  174. data/db/migrate/20180730071851_add_core_content_blocks.rb +28 -0
  175. data/db/migrate/20180802132147_rename_content_block_options_to_settings.rb +7 -0
  176. data/db/migrate/20180806095628_add_badge_scores.rb +11 -0
  177. data/db/migrate/20180808135006_add_images_to_content_blocks.rb +7 -0
  178. data/db/migrate/20180810092428_move_organization_fields_to_hero_content_block.rb +23 -0
  179. data/db/seeds.rb +10 -2
  180. data/lib/decidim/api/authorable_interface.rb +1 -1
  181. data/lib/decidim/coauthorable.rb +1 -0
  182. data/lib/decidim/content_block_manifest.rb +58 -0
  183. data/lib/decidim/content_block_registry.rb +87 -0
  184. data/lib/decidim/content_parsers.rb +1 -0
  185. data/lib/decidim/content_parsers/hashtag_parser.rb +36 -0
  186. data/lib/decidim/content_processor.rb +11 -0
  187. data/lib/decidim/content_renderers.rb +1 -0
  188. data/lib/decidim/content_renderers/hashtag_renderer.rb +43 -0
  189. data/lib/decidim/core.rb +28 -6
  190. data/lib/decidim/core/api.rb +1 -0
  191. data/lib/decidim/core/engine.rb +52 -1
  192. data/lib/decidim/core/test.rb +3 -0
  193. data/lib/decidim/core/test/factories.rb +32 -17
  194. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +10 -0
  195. data/lib/decidim/core/test/shared_examples/coauthorable.rb +3 -0
  196. data/lib/decidim/core/test/shared_examples/edit_link_shared_examples.rb +30 -0
  197. data/lib/decidim/core/test/shared_examples/has_space_in_mcell_examples.rb +15 -0
  198. data/lib/decidim/core/test/shared_examples/publicable.rb +1 -1
  199. data/lib/decidim/core/test/shared_examples/railtie_examples.rb +15 -0
  200. data/lib/decidim/core/test/shared_examples/scope_helper_examples.rb +1 -0
  201. data/lib/decidim/core/version.rb +1 -1
  202. data/lib/decidim/events/base_event.rb +2 -1
  203. data/lib/decidim/form_builder.rb +9 -3
  204. data/lib/decidim/friendly_dates.rb +1 -1
  205. data/lib/decidim/gamification.rb +109 -0
  206. data/lib/decidim/gamification/badge.rb +54 -0
  207. data/lib/decidim/gamification/badge_earned_event.rb +9 -0
  208. data/lib/decidim/gamification/badge_registry.rb +63 -0
  209. data/lib/decidim/gamification/badge_scorer.rb +118 -0
  210. data/lib/decidim/gamification/badge_status.rb +41 -0
  211. data/lib/decidim/gamification/base_event.rb +40 -0
  212. data/lib/decidim/gamification/level_up_event.rb +9 -0
  213. data/lib/decidim/hashtag.rb +15 -0
  214. data/lib/decidim/hashtaggable.rb +20 -0
  215. data/lib/decidim/query_extensions.rb +10 -0
  216. data/lib/decidim/resource_manifest.rb +10 -0
  217. data/lib/decidim/resourceable.rb +13 -0
  218. data/lib/decidim/search_resource_fields_mapper.rb +8 -3
  219. data/lib/decidim/searchable.rb +8 -0
  220. data/lib/decidim/translatable_attributes.rb +6 -18
  221. data/lib/decidim/view_model.rb +6 -0
  222. data/lib/devise/models/decidim_newsletterable.rb +1 -1
  223. data/vendor/assets/javascripts/d3.js +17813 -0
  224. metadata +125 -27
  225. data/app/cells/decidim/card_m/author.erb +0 -3
  226. data/app/cells/decidim/card_m/authors.erb +0 -9
  227. data/app/views/decidim/messaging/conversations/_message.html.erb +0 -14
  228. data/app/views/decidim/messaging/conversations/_reply.html.erb +0 -11
  229. data/app/views/decidim/messaging/conversations/_show.html.erb +0 -21
  230. data/app/views/decidim/messaging/conversations/_start.html.erb +0 -12
  231. data/app/views/decidim/pages/home/_footer_sub_hero.html.erb +0 -14
  232. data/app/views/decidim/pages/home/_highlighted_content_banner.html.erb +0 -26
  233. data/app/views/decidim/pages/home/_highlighted_processes.html.erb +0 -7
  234. data/app/views/decidim/profiles/_user.html.erb +0 -59
  235. data/app/views/decidim/shared/_action_authorization_modal.html.erb +0 -39
  236. data/app/views/layouts/decidim/_component_authorization_modals.html.erb +0 -5
@@ -63,6 +63,7 @@ module Decidim
63
63
  def add_coauthor(user, extra_attrs = {})
64
64
  attrs = { coauthorable: self, author: user }
65
65
  Decidim::Coauthorship.create!(attrs.merge(extra_attrs))
66
+ Decidim::Follow.create!(followable: self, user: user) unless followers.exists?(user.id)
66
67
  end
67
68
  end
68
69
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # This class acts as a manifest for content blocks.
5
+ #
6
+ # A content block is a view section in a given page. Those sections can be
7
+ # registered by Decidim modules, and are configurable and sortable. They are a
8
+ # useful way to customize a given page, without having to rely on overwriting
9
+ # the views files. Also, this system is more powerful than basic view hooks
10
+ # (see the `ViewHooks` class for reference), as view hooks don't have a way to
11
+ # explicitly control the order of the hooked views.
12
+ #
13
+ # Content blocks are intended to be used in the home page, for example.
14
+ #
15
+ # A content block has a set of settings and an associated `cell` that will
16
+ # handle the layout logic. They can also have attached images that can be used
17
+ # as background images, for example. You must explicitly specify the number of
18
+ # images the block will have (this means the number of attached images cannot
19
+ # be configurable). Each content block is identified by a name, which has to
20
+ # be unique per scope.
21
+ class ContentBlockManifest
22
+ include ActiveModel::Model
23
+ include Virtus.model
24
+
25
+ attribute :name, Symbol
26
+ attribute :public_name_key, String
27
+ attribute :cell, String
28
+ attribute :settings_form_cell, String
29
+ attribute :images, Array[Hash]
30
+
31
+ validates :name, :cell, :public_name_key, presence: true
32
+ validates :settings_form_cell, presence: true, if: :has_settings?
33
+ validate :image_names_are_unique
34
+ validate :images_have_an_uploader
35
+
36
+ def has_settings?
37
+ settings.attributes.any?
38
+ end
39
+
40
+ def settings(&block)
41
+ @settings ||= SettingsManifest.new
42
+ yield(@settings) if block
43
+ @settings
44
+ end
45
+
46
+ private
47
+
48
+ def image_names_are_unique
49
+ image_names = images.map { |image| image[:name] }
50
+ errors.add(:images, "names must be unique per manifest") if image_names.count != image_names.uniq.count
51
+ end
52
+
53
+ def images_have_an_uploader
54
+ uploaders = images.map { |image| image[:uploader].presence }
55
+ errors.add(:images, "must have an uploader") if uploaders.compact.count != images.count
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # This class acts as a registry for content blocks. Check the docs on the
5
+ # `ContentBlockManifest` class to learn how they work.
6
+ #
7
+ # In order to register a content block, you can follow this example:
8
+ #
9
+ # Decidim.content_blocks.register(:homepage, :global_stats) do |content_block|
10
+ # content_block.cell = "decidim/content_blocks/stats_block"
11
+ # content_block.public_name_key = "decidim.content_blocks.stats_block.name"
12
+ # content_block.settings_form_cell = "decidim/content_blocks/stats_block_settings_form"
13
+ #
14
+ # content_block.settings do |settings|
15
+ # settings.attribute :minimum_priority_level,
16
+ # type: :integer
17
+ # default: lambda { StatsRegistry::HIGH_PRIORITY }
18
+ # end
19
+ # end
20
+ #
21
+ # Content blocks can also register attached images. Here's an example of a
22
+ # content block with 4 attached images:
23
+ #
24
+ # Decidim.content_blocks.register(:homepage, :carousel) do |content_block|
25
+ # content_block.cell = "decidim/content_blocks/carousel_block"
26
+ # content_block.public_name_key = "decidim.content_blocks.carousel_block.name"
27
+ #
28
+ # content_block.images = [
29
+ # {
30
+ # name: :image_1,
31
+ # uploader: "Decidim::ImageUploader"
32
+ # },
33
+ # {
34
+ # name: :image_2,
35
+ # uploader: "Decidim::ImageUploader"
36
+ # }
37
+ # ]
38
+ # end
39
+ #
40
+ # You will probably want to register your content blocks in an initializer in
41
+ # the `engine.rb` file of your module.
42
+ class ContentBlockRegistry
43
+ # Public: Registers a content block for the home page.
44
+ #
45
+ # scope - a symbol or string representing the scope of the content block.
46
+ # Will be persisted as a string.
47
+ # name - a symbol representing the name of the content block
48
+ # &block - The content block definition.
49
+ #
50
+ # Returns nothing. Raises an error if there's already a content block
51
+ # registered with that name.
52
+ def register(scope, name)
53
+ scope = scope.to_s
54
+ block_exists = content_blocks[scope].any? { |content_block| content_block.name == name }
55
+
56
+ if block_exists
57
+ raise(
58
+ ContentBlockAlreadyRegistered,
59
+ "There's a content block already registered with the name `:#{name}` for the scope `:#{scope}, must be unique"
60
+ )
61
+ end
62
+
63
+ content_block = ContentBlockManifest.new(name: name)
64
+
65
+ yield(content_block)
66
+
67
+ content_block.validate!
68
+ content_blocks[scope].push(content_block)
69
+ end
70
+
71
+ def for(scope)
72
+ content_blocks[scope.to_s]
73
+ end
74
+
75
+ def all
76
+ content_blocks
77
+ end
78
+
79
+ class ContentBlockAlreadyRegistered < StandardError; end
80
+
81
+ private
82
+
83
+ def content_blocks
84
+ @content_blocks ||= Hash.new { |h, k| h[k] = [] }
85
+ end
86
+ end
87
+ end
@@ -4,5 +4,6 @@ module Decidim
4
4
  module ContentParsers
5
5
  autoload :BaseParser, "decidim/content_parsers/base_parser"
6
6
  autoload :UserParser, "decidim/content_parsers/user_parser"
7
+ autoload :HashtagParser, "decidim/content_parsers/hashtag_parser"
7
8
  end
8
9
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ContentParsers
5
+ # A parser that searches user mentions in content.
6
+ #
7
+ # A word starting with `#` will be considered as a possible hashtagging if
8
+ # they only contains letters, numbers or underscores. If the `#` is
9
+ # followed with an underscore, then it is not considered.
10
+ #
11
+ # @see BaseParser Examples of how to use a content parser
12
+ class HashtagParser < BaseParser
13
+ Metadata = Struct.new(:hashtags)
14
+
15
+ # Replaces found hashtags matching a name of an existing
16
+ # hashtag with a global id.
17
+ #
18
+ # @return [String] the content with the hashtags replaced by a global id
19
+ def rewrite
20
+ content.gsub(Decidim::Hashtag::HASHTAG_REGEX) do |match|
21
+ if (hashtag = Decidim::Hashtag.find_or_create_by(organization: context[:current_organization], name: Regexp.last_match[2].downcase))
22
+ Regexp.last_match[1] + hashtag.to_global_id.to_s
23
+ else
24
+ match
25
+ end
26
+ end
27
+ end
28
+
29
+ def metadata
30
+ Metadata.new(
31
+ Decidim::Hashtag.where(organization: context[:current_organization], name: content.scan(Decidim::Hashtag::HASHTAG_REGEX).flatten)
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -60,6 +60,17 @@ module Decidim
60
60
  Result.new(parsed[:rewrite], parsed[:metadata])
61
61
  end
62
62
 
63
+ def self.parse_with_processor(_type, content, context)
64
+ parsed = Decidim.content_processors.each_with_object(rewrite: content, metadata: {}) do |type, result|
65
+ next unless type == :hashtag
66
+ parser = parser_klass(type).constantize.new(result[:rewrite], context)
67
+ result[:rewrite] = parser.rewrite
68
+ result[:metadata][type] = parser.metadata
69
+ end
70
+
71
+ Result.new(parsed[:rewrite], parsed[:metadata])
72
+ end
73
+
63
74
  # This calls all registered processors one after the other and returns
64
75
  # the processed content ready to display.
65
76
  #
@@ -4,5 +4,6 @@ module Decidim
4
4
  module ContentRenderers
5
5
  autoload :BaseRenderer, "decidim/content_renderers/base_renderer"
6
6
  autoload :UserRenderer, "decidim/content_renderers/user_renderer"
7
+ autoload :HashtagRenderer, "decidim/content_renderers/hashtag_renderer"
7
8
  end
8
9
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ContentRenderers
5
+ # A renderer that searches Global IDs representing hashtags in content
6
+ # and replaces it with a link to their detail page with the name.
7
+ #
8
+ # e.g. gid://<APP_NAME>/Decidim::Hashtag/1
9
+ #
10
+ # @see BaseRenderer Examples of how to use a content renderer
11
+ class HashtagRenderer < BaseRenderer
12
+ # Matches a global id representing a Decidim::Hashtag
13
+ GLOBAL_ID_REGEX = %r{gid:\/\/[\w-]*\/Decidim::Hashtag\/(\d+)}
14
+
15
+ # Replaces found Global IDs matching an existing hashtag with
16
+ # a link to their detail page. The Global IDs representing an
17
+ # invalid Decidim::Hashtag are replaced with an empty string.
18
+ #
19
+ # @return [String] the content ready to display (contains HTML)
20
+ def render
21
+ content.gsub(GLOBAL_ID_REGEX) do |hashtag_gid|
22
+ begin
23
+ hashtag = GlobalID::Locator.locate(hashtag_gid)
24
+ Decidim::HashtagPresenter.new(hashtag).display_hashtag
25
+ rescue ActiveRecord::RecordNotFound => _ex
26
+ ""
27
+ end
28
+ end
29
+ end
30
+
31
+ def render_without_link
32
+ content.gsub(GLOBAL_ID_REGEX) do |hashtag_gid|
33
+ begin
34
+ hashtag = GlobalID::Locator.locate(hashtag_gid)
35
+ Decidim::HashtagPresenter.new(hashtag).display_hashtag_name
36
+ rescue ActiveRecord::RecordNotFound => _ex
37
+ ""
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/decidim/core.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require "decidim/core/engine"
4
4
  require "decidim/core/api"
5
5
  require "decidim/core/version"
6
-
7
6
  # Decidim configuration.
8
7
  module Decidim
9
8
  autoload :TranslatableAttributes, "decidim/translatable_attributes"
@@ -49,6 +48,8 @@ module Decidim
49
48
  autoload :EngineRouter, "decidim/engine_router"
50
49
  autoload :Events, "decidim/events"
51
50
  autoload :ViewHooks, "decidim/view_hooks"
51
+ autoload :ContentBlockRegistry, "decidim/content_block_registry"
52
+ autoload :ContentBlockManifest, "decidim/content_block_manifest"
52
53
  autoload :NewsletterEncryptor, "decidim/newsletter_encryptor"
53
54
  autoload :Searchable, "decidim/searchable"
54
55
  autoload :SearchResourceFieldsMapper, "decidim/search_resource_fields_mapper"
@@ -62,9 +63,10 @@ module Decidim
62
63
  autoload :DataPortabilitySerializers, "decidim/data_portability_serializers"
63
64
  autoload :DataPortabilityFileReader, "decidim/data_portability_file_reader"
64
65
  autoload :DataPortabilityFileZipper, "decidim/data_portability_file_zipper"
65
-
66
+ autoload :Gamification, "decidim/gamification"
67
+ autoload :Hashtag, "decidim/hashtag"
68
+ autoload :Hashtaggable, "decidim/hashtaggable"
66
69
  include ActiveSupport::Configurable
67
-
68
70
  # Loads seeds from all engines.
69
71
  def self.seed!
70
72
  # Faker needs to have the `:en` locale in order to work properly, so we
@@ -78,7 +80,17 @@ module Decidim
78
80
  railtie.load_seed
79
81
  end
80
82
 
81
- Decidim.participatory_space_manifests.each(&:seed!)
83
+ participatory_space_manifests.each(&:seed!)
84
+ Gamification.badges.each do |badge|
85
+ puts "Setting random values for the \"#{badge.name}\" badge..."
86
+ User.all.find_each do |user|
87
+ Gamification::BadgeScore.find_or_create_by!(
88
+ user: user,
89
+ badge_name: badge.name,
90
+ value: Random.rand(0...20)
91
+ )
92
+ end
93
+ end
82
94
 
83
95
  I18n.available_locales = original_locale
84
96
  end
@@ -92,7 +104,7 @@ module Decidim
92
104
 
93
105
  # Exposes a configuration option: The application available locales.
94
106
  config_accessor :available_locales do
95
- %w(en ca es es-PY eu fi fr gl it nl pt pt-BR ru sv uk)
107
+ %w(en ca es es-PY eu fi fr gl hu it nl pt pt-BR ru sv uk)
96
108
  end
97
109
 
98
110
  # Exposes a configuration option: an array of symbols representing processors
@@ -156,6 +168,11 @@ module Decidim
156
168
  "€"
157
169
  end
158
170
 
171
+ # Exposes a configuration option: The image uploader quality.
172
+ config_accessor :image_uploader_quality do
173
+ 80
174
+ end
175
+
159
176
  # Exposes a configuration option: The maximum file size of an attachment.
160
177
  config_accessor :maximum_attachment_size do
161
178
  10.megabytes
@@ -212,7 +229,7 @@ module Decidim
212
229
  #
213
230
  # Returns nothing.
214
231
  def self.register_global_engine(name, engine, options = {})
215
- return if global_engines.keys.include?(name)
232
+ return if global_engines.has_key?(name)
216
233
 
217
234
  options[:at] ||= "/#{name}"
218
235
 
@@ -354,6 +371,11 @@ module Decidim
354
371
  @view_hooks ||= ViewHooks.new
355
372
  end
356
373
 
374
+ # Public: Stores an instance of ContentBlockRegistry
375
+ def self.content_blocks
376
+ @content_blocks ||= ContentBlockRegistry.new
377
+ end
378
+
357
379
  # Public: Stores an instance of Traceability
358
380
  def self.traceability
359
381
  @traceability ||= Traceability.new
@@ -9,5 +9,6 @@ module Decidim
9
9
  autoload :CategorizableInterface, "decidim/api/categorizable_interface"
10
10
  autoload :ScopableInterface, "decidim/api/scopable_interface"
11
11
  autoload :AttachableInterface, "decidim/api/attachable_interface"
12
+ autoload :HashtagInterface, "decidim/api/hashtag_interface"
12
13
  end
13
14
  end
@@ -200,7 +200,7 @@ module Decidim
200
200
 
201
201
  initializer "decidim.content_processors" do |_app|
202
202
  Decidim.configure do |config|
203
- config.content_processors += [:user]
203
+ config.content_processors += [:user, :hashtag]
204
204
  end
205
205
  end
206
206
 
@@ -275,6 +275,57 @@ module Decidim
275
275
  resource.card = "decidim/user_profile"
276
276
  end
277
277
  end
278
+
279
+ initializer "decidim.core.content_blocks" do
280
+ Decidim.content_blocks.register(:homepage, :hero) do |content_block|
281
+ content_block.cell = "decidim/content_blocks/hero"
282
+ content_block.settings_form_cell = "decidim/content_blocks/hero_settings_form"
283
+ content_block.public_name_key = "decidim.content_blocks.hero.name"
284
+
285
+ content_block.images = [
286
+ {
287
+ name: :background_image,
288
+ uploader: "Decidim::HomepageImageUploader"
289
+ }
290
+ ]
291
+
292
+ content_block.settings do |settings|
293
+ settings.attribute :welcome_text, type: :text, translated: true
294
+ end
295
+ end
296
+
297
+ Decidim.content_blocks.register(:homepage, :sub_hero) do |content_block|
298
+ content_block.cell = "decidim/content_blocks/sub_hero"
299
+ content_block.public_name_key = "decidim.content_blocks.sub_hero.name"
300
+ end
301
+
302
+ Decidim.content_blocks.register(:homepage, :highlighted_content_banner) do |content_block|
303
+ content_block.cell = "decidim/content_blocks/highlighted_content_banner"
304
+ content_block.public_name_key = "decidim.content_blocks.highlighted_content_banner.name"
305
+ end
306
+
307
+ Decidim.content_blocks.register(:homepage, :how_to_participate) do |content_block|
308
+ content_block.cell = "decidim/content_blocks/how_to_participate"
309
+ content_block.public_name_key = "decidim.content_blocks.how_to_participate.name"
310
+ end
311
+
312
+ Decidim.content_blocks.register(:homepage, :stats) do |content_block|
313
+ content_block.cell = "decidim/content_blocks/stats"
314
+ content_block.public_name_key = "decidim.content_blocks.stats.name"
315
+ end
316
+
317
+ Decidim.content_blocks.register(:homepage, :footer_sub_hero) do |content_block|
318
+ content_block.cell = "decidim/content_blocks/footer_sub_hero"
319
+ content_block.public_name_key = "decidim.content_blocks.footer_sub_hero.name"
320
+ end
321
+ end
322
+
323
+ initializer "decidim.core.add_badges" do
324
+ Decidim::Gamification.register_badge(:invitations) do |badge|
325
+ badge.levels = [1, 5, 10, 30, 50]
326
+ badge.reset = ->(user) { Decidim::User.where(invited_by: user.id).count }
327
+ end
328
+ end
278
329
  end
279
330
  end
280
331
  end
@@ -24,3 +24,6 @@ require "decidim/core/test/shared_examples/simple_event"
24
24
  require "decidim/core/test/shared_examples/component_type"
25
25
  require "decidim/core/test/shared_examples/fingerprint_examples"
26
26
  require "decidim/core/test/shared_examples/searchable_results_examples"
27
+ require "decidim/core/test/shared_examples/has_space_in_mcell_examples"
28
+ require "decidim/core/test/shared_examples/railtie_examples"
29
+ require "decidim/core/test/shared_examples/edit_link_shared_examples"
@@ -16,6 +16,10 @@ FactoryBot.define do
16
16
  "#{Faker::Lorem.characters(rand(1..10))}_#{n}"
17
17
  end
18
18
 
19
+ sequence(:hashtag_name) do |n|
20
+ "#{Faker::Lorem.characters(rand(1..10))}_#{n}"
21
+ end
22
+
19
23
  sequence(:email) do |n|
20
24
  "user#{n}@example.org"
21
25
  end
@@ -59,16 +63,14 @@ FactoryBot.define do
59
63
  github_handler { Faker::Hipster.word }
60
64
  sequence(:host) { |n| "#{n}.lvh.me" }
61
65
  description { Decidim::Faker::Localized.wrapped("<p>", "</p>") { Decidim::Faker::Localized.sentence(2) } }
62
- welcome_text { Decidim::Faker::Localized.wrapped("<p>", "</p>") { Decidim::Faker::Localized.sentence(2) } }
63
- homepage_image { Decidim::Dev.test_file("city.jpeg", "image/jpeg") }
64
66
  favicon { Decidim::Dev.test_file("icon.png", "image/png") }
65
67
  default_locale { Decidim.default_locale }
66
68
  available_locales { Decidim.available_locales }
67
69
  official_img_header { Decidim::Dev.test_file("avatar.jpg", "image/jpeg") }
68
70
  official_img_footer { Decidim::Dev.test_file("avatar.jpg", "image/jpeg") }
69
71
  official_url { Faker::Internet.url }
70
- highlighted_content_banner_enabled false
71
- enable_omnipresent_banner false
72
+ highlighted_content_banner_enabled { false }
73
+ enable_omnipresent_banner { false }
72
74
  tos_version { Time.current }
73
75
 
74
76
  trait :with_tos do
@@ -81,13 +83,13 @@ FactoryBot.define do
81
83
 
82
84
  factory :user, class: "Decidim::User" do
83
85
  email { generate(:email) }
84
- password "password1234"
86
+ password { "password1234" }
85
87
  password_confirmation { password }
86
88
  name { generate(:name) }
87
89
  nickname { generate(:nickname) }
88
90
  organization
89
91
  locale { organization.default_locale }
90
- tos_agreement "1"
92
+ tos_agreement { "1" }
91
93
  avatar { Decidim::Dev.test_file("avatar.jpg", "image/jpeg") }
92
94
  personal_url { Faker::Internet.url }
93
95
  about { Faker::Lorem.paragraph(2) }
@@ -104,7 +106,7 @@ FactoryBot.define do
104
106
  end
105
107
 
106
108
  trait :deleted do
107
- email ""
109
+ email { "" }
108
110
  deleted_at { Time.current }
109
111
  end
110
112
 
@@ -124,7 +126,7 @@ FactoryBot.define do
124
126
  end
125
127
 
126
128
  trait :officialized do
127
- officialized_at { Time.zone.now }
129
+ officialized_at { Time.current }
128
130
  officialized_as { Decidim::Faker::Localized.sentence(3) }
129
131
  end
130
132
  end
@@ -140,14 +142,14 @@ FactoryBot.define do
140
142
  end
141
143
 
142
144
  factory :user_group, class: "Decidim::UserGroup" do
143
- name { Faker::Educator.unique.course }
145
+ sequence(:name) { |n| "#{Faker::Company.name} #{n}" }
144
146
  document_number { Faker::Number.number(8) + "X" }
145
147
  phone { Faker::PhoneNumber.phone_number }
146
148
  avatar { Decidim::Dev.test_file("avatar.jpg", "image/jpeg") }
147
149
  organization
148
150
 
149
151
  transient do
150
- users []
152
+ users { [] }
151
153
  end
152
154
 
153
155
  trait :verified do
@@ -174,7 +176,7 @@ FactoryBot.define do
174
176
  end
175
177
 
176
178
  factory :identity, class: "Decidim::Identity" do
177
- provider "facebook"
179
+ provider { "facebook" }
178
180
  sequence(:uid)
179
181
  user
180
182
  organization { user.organization }
@@ -191,7 +193,7 @@ FactoryBot.define do
191
193
  end
192
194
 
193
195
  trait :pending do
194
- granted_at nil
196
+ granted_at { nil }
195
197
  end
196
198
  end
197
199
 
@@ -245,7 +247,7 @@ FactoryBot.define do
245
247
 
246
248
  name { Decidim::Faker::Localized.sentence(3) }
247
249
  participatory_space { create(:participatory_process, organization: organization) }
248
- manifest_name "dummy"
250
+ manifest_name { "dummy" }
249
251
  published_at { Time.current }
250
252
 
251
253
  trait :unpublished do
@@ -266,7 +268,7 @@ FactoryBot.define do
266
268
  factory :scope, class: "Decidim::Scope" do
267
269
  name { Decidim::Faker::Localized.literal(generate(:scope_name)) }
268
270
  code { generate(:scope_code) }
269
- scope_type
271
+ scope_type { create(:scope_type, organization: organization) }
270
272
  organization { parent ? parent.organization : build(:organization) }
271
273
  end
272
274
 
@@ -335,7 +337,7 @@ FactoryBot.define do
335
337
  factory :report, class: "Decidim::Report" do
336
338
  moderation
337
339
  user { build(:user, organization: moderation.reportable.organization) }
338
- reason "spam"
340
+ reason { "spam" }
339
341
  end
340
342
 
341
343
  factory :impersonation_log, class: "Decidim::ImpersonationLog" do
@@ -419,7 +421,7 @@ FactoryBot.define do
419
421
  application { build(:oauth_application) }
420
422
  token { SecureRandom.hex(32) }
421
423
  expires_in { 1.month.from_now }
422
- created_at { Time.zone.now }
424
+ created_at { Time.current }
423
425
  scopes { "public" }
424
426
  end
425
427
 
@@ -432,6 +434,19 @@ FactoryBot.define do
432
434
  locale { I18n.locale }
433
435
  scope { resource.scope }
434
436
  content_a { Faker::Lorem.sentence }
435
- datetime { DateTime.current }
437
+ datetime { Time.current }
438
+ end
439
+
440
+ factory :content_block, class: "Decidim::ContentBlock" do
441
+ organization
442
+ scope { :homepage }
443
+ manifest_name { :hero }
444
+ weight { 1 }
445
+ published_at { Time.current }
446
+ end
447
+
448
+ factory :hashtag, class: "Decidim::Hashtag" do
449
+ name { generate(:hashtag_name) }
450
+ organization
436
451
  end
437
452
  end