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.
- checksums.yaml +4 -4
- data/app/assets/config/decidim_core_manifest.js +1 -1
- data/app/assets/images/decidim/gamification/badges/invitations.svg +117 -0
- data/app/assets/javascripts/decidim.js.es6 +4 -1
- data/app/assets/javascripts/decidim/ajax_modals.js.es6 +17 -0
- data/app/assets/javascripts/decidim/conferences.js.es6 +16 -0
- data/app/assets/javascripts/decidim/input_hashtags.js.es6 +115 -0
- data/app/assets/javascripts/decidim/input_mentions.js.es6 +2 -3
- data/app/assets/javascripts/decidim/vizzs/areachart.js.es6 +226 -0
- data/app/assets/javascripts/decidim/vizzs/metrics.js.es6 +26 -0
- data/app/assets/javascripts/decidim/vizzs/orgchart.js.es6 +701 -0
- data/app/assets/javascripts/decidim/vizzs/renders.js.es6 +11 -0
- data/app/assets/stylesheets/decidim/extras/_proposal_form.scss +3 -1
- data/app/assets/stylesheets/decidim/layouts/_home.scss +1 -1
- data/app/assets/stylesheets/decidim/modules/_areachart.scss +74 -0
- data/app/assets/stylesheets/decidim/modules/_badges.scss +116 -0
- data/app/assets/stylesheets/decidim/modules/_buttons.scss +5 -0
- data/app/assets/stylesheets/decidim/modules/_cards.scss +21 -4
- data/app/assets/stylesheets/decidim/modules/_chart-tooltip.scss +42 -0
- data/app/assets/stylesheets/decidim/modules/_collapsible-list.scss +12 -8
- data/app/assets/stylesheets/decidim/modules/_conference-nav.scss +31 -0
- data/app/assets/stylesheets/decidim/modules/_conference-programme.scss +110 -0
- data/app/assets/stylesheets/decidim/modules/_conference-speaker.scss +86 -0
- data/app/assets/stylesheets/decidim/modules/_conversation.scss +58 -0
- data/app/assets/stylesheets/decidim/modules/_help.scss +38 -0
- data/app/assets/stylesheets/decidim/modules/_hover-section.scss +29 -0
- data/app/assets/stylesheets/decidim/modules/_icons.scss +10 -4
- data/app/assets/stylesheets/decidim/modules/_input-hashtags.scss +124 -0
- data/app/assets/stylesheets/decidim/modules/_loading-spinner.scss +12 -0
- data/app/assets/stylesheets/decidim/modules/_margins.scss +2 -2
- data/app/assets/stylesheets/decidim/modules/_modules.scss +15 -0
- data/app/assets/stylesheets/decidim/modules/_navbar.scss +9 -0
- data/app/assets/stylesheets/decidim/modules/_orgchart.scss +62 -0
- data/app/assets/stylesheets/decidim/modules/_status-labels.scss +2 -1
- data/app/assets/stylesheets/decidim/modules/_typography.scss +9 -0
- data/app/assets/stylesheets/decidim/utils/_helpers.scss +28 -0
- data/app/assets/stylesheets/decidim/utils/_mixins.scss +63 -0
- data/app/cells/decidim/author/withdraw.erb +1 -1
- data/app/cells/decidim/author_cell.rb +1 -1
- data/app/cells/decidim/badge/show.erb +36 -0
- data/app/cells/decidim/badge_cell.rb +53 -0
- data/app/cells/decidim/badges/show.erb +6 -0
- data/app/cells/decidim/badges_cell.rb +14 -0
- data/app/cells/decidim/card_m/header.erb +1 -1
- data/app/cells/decidim/card_m/show.erb +1 -2
- data/app/cells/decidim/card_m/top.erb +7 -0
- data/app/cells/decidim/card_m_cell.rb +14 -17
- data/app/cells/decidim/coauthorships_cell.rb +77 -0
- data/app/cells/decidim/collapsible_authors/show.erb +0 -1
- data/app/cells/decidim/collapsible_authors_cell.rb +4 -4
- data/app/cells/decidim/collapsible_list/show.erb +12 -4
- data/app/cells/decidim/collapsible_list_cell.rb +14 -12
- data/app/cells/decidim/content_blocks/footer_sub_hero/show.erb +14 -0
- data/app/cells/decidim/content_blocks/footer_sub_hero_cell.rb +12 -0
- data/app/{views/decidim/pages/home/_hero.html.erb → cells/decidim/content_blocks/hero/show.erb} +4 -4
- data/app/cells/decidim/content_blocks/hero_cell.rb +25 -0
- data/app/cells/decidim/content_blocks/hero_settings_form/show.erb +7 -0
- data/app/cells/decidim/content_blocks/hero_settings_form_cell.rb +13 -0
- data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +24 -0
- data/app/cells/decidim/content_blocks/highlighted_content_banner_cell.rb +16 -0
- data/app/{views/decidim/pages/home/_extended.html.erb → cells/decidim/content_blocks/how_to_participate/show.erb} +10 -10
- data/app/cells/decidim/content_blocks/how_to_participate_cell.rb +9 -0
- data/app/{views/decidim/pages/home/_statistics.html.erb → cells/decidim/content_blocks/stats/show.erb} +2 -2
- data/app/cells/decidim/content_blocks/stats_cell.rb +18 -0
- data/app/{views/decidim/pages/home/_sub_hero.html.erb → cells/decidim/content_blocks/sub_hero/show.erb} +2 -2
- data/app/cells/decidim/content_blocks/sub_hero_cell.rb +17 -0
- data/app/cells/decidim/conversation/show.erb +18 -0
- data/app/cells/decidim/conversation_cell.rb +23 -0
- data/app/cells/decidim/conversation_header/show.erb +17 -0
- data/app/cells/decidim/conversation_header_cell.rb +16 -0
- data/app/cells/decidim/conversations/show.erb +45 -0
- data/app/cells/decidim/conversations_cell.rb +24 -0
- data/app/cells/decidim/follow_button/show.erb +3 -3
- data/app/cells/decidim/follow_button_cell.rb +1 -5
- data/app/cells/decidim/following_cell.rb +1 -7
- data/app/cells/decidim/message/show.erb +15 -0
- data/app/cells/decidim/message_cell.rb +23 -0
- data/app/cells/decidim/new_conversation/show.erb +19 -0
- data/app/cells/decidim/new_conversation_cell.rb +19 -0
- data/app/cells/decidim/notifications/show.erb +1 -1
- data/app/cells/decidim/profile/show.erb +27 -0
- data/app/cells/decidim/profile_cell.rb +33 -0
- data/app/cells/decidim/profile_sidebar/show.erb +57 -0
- data/app/cells/decidim/profile_sidebar_cell.rb +31 -0
- data/app/cells/decidim/tos_page_cell.rb +0 -4
- data/app/cells/decidim/user_profile/header.erb +1 -1
- data/app/controllers/concerns/decidim/action_authorization.rb +13 -38
- data/app/controllers/concerns/decidim/needs_permission.rb +15 -6
- data/app/controllers/decidim/application_controller.rb +1 -0
- data/app/controllers/decidim/authorization_modals_controller.rb +35 -0
- data/app/controllers/decidim/components/base_controller.rb +0 -1
- data/app/controllers/decidim/devise/invitations_controller.rb +2 -1
- data/app/controllers/decidim/messaging/conversations_controller.rb +2 -11
- data/app/controllers/decidim/newsletters_controller.rb +4 -6
- data/app/controllers/decidim/notifications_controller.rb +4 -0
- data/app/controllers/decidim/pages_controller.rb +3 -7
- data/app/controllers/decidim/profiles_controller.rb +17 -7
- data/app/forms/decidim/notifications_settings_form.rb +1 -1
- data/app/forms/decidim/registration_form.rb +1 -1
- data/app/helpers/decidim/action_authorization_helper.rb +51 -46
- data/app/helpers/decidim/application_helper.rb +18 -0
- data/app/helpers/decidim/card_helper.rb +1 -1
- data/app/helpers/decidim/cells_helper.rb +6 -2
- data/app/helpers/decidim/resource_helper.rb +8 -1
- data/app/helpers/decidim/searches_helper.rb +5 -4
- data/app/helpers/decidim/traceability_helper.rb +5 -1
- data/app/models/decidim/authorization.rb +2 -2
- data/app/models/decidim/content_block.rb +144 -0
- data/app/models/decidim/gamification/badge_score.rb +13 -0
- data/app/models/decidim/messaging/message.rb +1 -1
- data/app/models/decidim/messaging/receipt.rb +1 -1
- data/app/models/decidim/organization.rb +1 -5
- data/app/models/decidim/resource_permission.rb +8 -0
- data/app/models/decidim/searchable_resource.rb +1 -1
- data/app/models/decidim/user.rb +17 -1
- data/app/permissions/decidim/default_permissions.rb +4 -3
- data/app/permissions/decidim/permissions.rb +33 -1
- data/app/presenters/decidim/hashtag_presenter.rb +32 -0
- data/app/presenters/decidim/resource_locator_presenter.rb +13 -0
- data/app/presenters/decidim/user_presenter.rb +1 -1
- data/app/queries/decidim/messaging/user_conversations.rb +1 -1
- data/app/resolvers/decidim/hashtags_resolver.rb +15 -0
- data/app/services/decidim/action_authorizer.rb +9 -8
- data/app/types/decidim/core/date_time_type.rb +1 -1
- data/app/types/decidim/core/hashtag_type.rb +13 -0
- data/app/uploaders/decidim/homepage_image_uploader.rb +1 -1
- data/app/uploaders/decidim/image_uploader.rb +1 -0
- data/app/views/decidim/authorization_modals/show.html.erb +32 -0
- data/app/views/decidim/messaging/conversations/create.js.erb +1 -1
- data/app/views/decidim/messaging/conversations/index.html.erb +1 -51
- data/app/views/decidim/messaging/conversations/new.html.erb +1 -5
- data/app/views/decidim/messaging/conversations/show.html.erb +1 -9
- data/app/views/decidim/messaging/conversations/update.js.erb +1 -1
- data/app/views/decidim/notifications/index.html.erb +1 -0
- data/app/views/decidim/pages/decidim_page.html.erb +9 -0
- data/app/views/decidim/pages/home.html.erb +12 -16
- data/app/views/decidim/pages/index.html.erb +8 -0
- data/app/views/decidim/profiles/_user_follow.erb +2 -2
- data/app/views/decidim/profiles/show.html.erb +1 -37
- data/app/views/decidim/searches/_results.html.erb +1 -1
- data/app/views/decidim/shared/_author_reference.html.erb +1 -1
- data/app/views/decidim/shared/_authorization_modal.html.erb +1 -0
- data/app/views/decidim/shared/_tags.html.erb +1 -1
- data/app/views/kaminari/decidim/_page.html.erb +1 -1
- data/app/views/layouts/decidim/_application.html.erb +6 -1
- data/app/views/layouts/decidim/_edit_link.html.erb +8 -0
- data/app/views/layouts/decidim/_impersonation_warning.html.erb +1 -1
- data/app/views/layouts/decidim/_user_menu.html.erb +2 -2
- data/app/views/layouts/decidim/_wrapper.html.erb +14 -1
- data/config/initializers/carrierwave.rb +15 -0
- data/config/locales/ca.yml +78 -30
- data/config/locales/en.yml +78 -30
- data/config/locales/es-PY.yml +78 -30
- data/config/locales/es.yml +78 -30
- data/config/locales/eu.yml +78 -30
- data/config/locales/fi.yml +262 -214
- data/config/locales/fr.yml +78 -30
- data/config/locales/gl.yml +78 -30
- data/config/locales/hu.yml +781 -0
- data/config/locales/it.yml +78 -30
- data/config/locales/nl.yml +78 -30
- data/config/locales/pl.yml +78 -30
- data/config/locales/pt-BR.yml +106 -58
- data/config/locales/pt.yml +78 -30
- data/config/locales/ru.yml +52 -32
- data/config/locales/sv.yml +183 -135
- data/config/locales/uk.yml +60 -40
- data/config/routes.rb +8 -6
- data/db/migrate/20180705091019_create_decidim_resource_permissions.rb +12 -0
- data/db/migrate/20180706104107_add_nickname_to_managed_users.rb +14 -0
- data/db/migrate/20180706111847_fix_result_follows.rb +9 -0
- data/db/migrate/20180724103814_add_content_blocks.rb +22 -0
- data/db/migrate/20180726112510_create_decidim_hashtags.rb +17 -0
- data/db/migrate/20180730071851_add_core_content_blocks.rb +28 -0
- data/db/migrate/20180802132147_rename_content_block_options_to_settings.rb +7 -0
- data/db/migrate/20180806095628_add_badge_scores.rb +11 -0
- data/db/migrate/20180808135006_add_images_to_content_blocks.rb +7 -0
- data/db/migrate/20180810092428_move_organization_fields_to_hero_content_block.rb +23 -0
- data/db/seeds.rb +10 -2
- data/lib/decidim/api/authorable_interface.rb +1 -1
- data/lib/decidim/coauthorable.rb +1 -0
- data/lib/decidim/content_block_manifest.rb +58 -0
- data/lib/decidim/content_block_registry.rb +87 -0
- data/lib/decidim/content_parsers.rb +1 -0
- data/lib/decidim/content_parsers/hashtag_parser.rb +36 -0
- data/lib/decidim/content_processor.rb +11 -0
- data/lib/decidim/content_renderers.rb +1 -0
- data/lib/decidim/content_renderers/hashtag_renderer.rb +43 -0
- data/lib/decidim/core.rb +28 -6
- data/lib/decidim/core/api.rb +1 -0
- data/lib/decidim/core/engine.rb +52 -1
- data/lib/decidim/core/test.rb +3 -0
- data/lib/decidim/core/test/factories.rb +32 -17
- data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +10 -0
- data/lib/decidim/core/test/shared_examples/coauthorable.rb +3 -0
- data/lib/decidim/core/test/shared_examples/edit_link_shared_examples.rb +30 -0
- data/lib/decidim/core/test/shared_examples/has_space_in_mcell_examples.rb +15 -0
- data/lib/decidim/core/test/shared_examples/publicable.rb +1 -1
- data/lib/decidim/core/test/shared_examples/railtie_examples.rb +15 -0
- data/lib/decidim/core/test/shared_examples/scope_helper_examples.rb +1 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/events/base_event.rb +2 -1
- data/lib/decidim/form_builder.rb +9 -3
- data/lib/decidim/friendly_dates.rb +1 -1
- data/lib/decidim/gamification.rb +109 -0
- data/lib/decidim/gamification/badge.rb +54 -0
- data/lib/decidim/gamification/badge_earned_event.rb +9 -0
- data/lib/decidim/gamification/badge_registry.rb +63 -0
- data/lib/decidim/gamification/badge_scorer.rb +118 -0
- data/lib/decidim/gamification/badge_status.rb +41 -0
- data/lib/decidim/gamification/base_event.rb +40 -0
- data/lib/decidim/gamification/level_up_event.rb +9 -0
- data/lib/decidim/hashtag.rb +15 -0
- data/lib/decidim/hashtaggable.rb +20 -0
- data/lib/decidim/query_extensions.rb +10 -0
- data/lib/decidim/resource_manifest.rb +10 -0
- data/lib/decidim/resourceable.rb +13 -0
- data/lib/decidim/search_resource_fields_mapper.rb +8 -3
- data/lib/decidim/searchable.rb +8 -0
- data/lib/decidim/translatable_attributes.rb +6 -18
- data/lib/decidim/view_model.rb +6 -0
- data/lib/devise/models/decidim_newsletterable.rb +1 -1
- data/vendor/assets/javascripts/d3.js +17813 -0
- metadata +125 -27
- data/app/cells/decidim/card_m/author.erb +0 -3
- data/app/cells/decidim/card_m/authors.erb +0 -9
- data/app/views/decidim/messaging/conversations/_message.html.erb +0 -14
- data/app/views/decidim/messaging/conversations/_reply.html.erb +0 -11
- data/app/views/decidim/messaging/conversations/_show.html.erb +0 -21
- data/app/views/decidim/messaging/conversations/_start.html.erb +0 -12
- data/app/views/decidim/pages/home/_footer_sub_hero.html.erb +0 -14
- data/app/views/decidim/pages/home/_highlighted_content_banner.html.erb +0 -26
- data/app/views/decidim/pages/home/_highlighted_processes.html.erb +0 -7
- data/app/views/decidim/profiles/_user.html.erb +0 -59
- data/app/views/decidim/shared/_action_authorization_modal.html.erb +0 -39
- data/app/views/layouts/decidim/_component_authorization_modals.html.erb +0 -5
data/lib/decidim/coauthorable.rb
CHANGED
@@ -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
|
@@ -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
|
#
|
@@ -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
|
-
|
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.
|
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
|
data/lib/decidim/core/api.rb
CHANGED
@@ -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
|
data/lib/decidim/core/engine.rb
CHANGED
@@ -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
|
data/lib/decidim/core/test.rb
CHANGED
@@ -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.
|
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::
|
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.
|
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 {
|
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
|