# Decidim::DecidimAwesome [![[CI] Tests 0.28](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests.yml/badge.svg)](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/tests.yml) [![[CI] Lint](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/lint.yml/badge.svg)](https://github.com/decidim-ice/decidim-module-decidim_awesome/actions/workflows/lint.yml) [![Maintainability](https://api.codeclimate.com/v1/badges/2dada53525dd5a944089/maintainability)](https://codeclimate.com/github/decidim-ice/decidim-module-decidim_awesome/maintainability) [![Test Coverage](https://codecov.io/gh/decidim-ice/decidim-module-decidim_awesome/branch/main/graph/badge.svg?token=TFBMCLLZJG)](https://codecov.io/gh/decidim-ice/decidim-module-decidim_awesome) **Usability and UX tweaks for Decidim.** This plugin allows the administrators to expand the possibilities of Decidim beyond some existing limitations. All tweaks are provided in a optional fashion with granular permissions that let the administrator to choose exactly where to apply those mods. Some tweaks can be applied to an assembly, other in an specific participatory process or even in a type of component only (for instance, only in proposals). **DISCLAIMER: This module is heavily tested and widely used, however we do not accept any responsibility for breaking anything. Feedback is appreciated though.** ## Why this plugin? Decidim is an awesome platform, but it has some limitations that can be annoying for the users or the admins. This plugin tries to solve some of them. See the list of tweaks below. ## Usage Read the [CHANGELOG](CHANGELOG.md) for Decidim compatibility. > **TL;DR people**: Jump to the [installation part](#installation) DecidimAwesome is a module that hacks Decidim in order to provide more features or improve some aspects of it. It generates and admin module that allows to choose what hacks to apply. Each hack can be scoped to one or more specific participatory spaces or components. ### Tweaks: #### 1. Image support for the RichText editor Modifies the WYSIWYG editor in Decidim by adding the possibility to insert images. When uploading images, Drag & Drop is supported. Images will be uploaded to the server and inserted as external resources (it doesn't use base64 in-line encoding). This feature allows you use images in newsletters as well. ![Images in RichText Editor](examples/quill-images.png) #### 2. Auto-save for surveys and forms With this feature admins can activate (globally or scoped) an auto-save feature for any form in Decidim. It works purely in the client side by using LocalStorage capabilities of the browser. Data is store every time any field changes and retrieved automatically if the same user with the same browser returns to it in the future. Saving the form removes the stored data. ![Auto save in forms](examples/auto-save.gif) #### 3. Images in proposals Event if you haven't activated the WYSIWYG editor (RichText) in public views (eg: proposals use a simple textarea if rich text editor has not been activated for users). You can allow users to upload images in them by drag & drop over the text area. ![Proposal images](examples/proposal-images.png) #### 4. Restrict scope for tweaks All tweaks can be configured and scoped to a specific participatory space, a type of participatory space, a type of component or a specific component. Many scopes can be defined for every tweak. If a tweak is not scoped, it will be applied globally. ![Tweak scopes](examples/tweak-scopes.png) #### 5. Awesome map component This is a component you can add in any participatory space. It retrieves all the geolocated content in that participatory space (meetings or proposals) and displays it in a big map. It also provides a simple search by category, each category is assigned to a different color. ![Awesome map](examples/awesome-map.png) #### 6. Fullscreen Iframe component Another simple component that can be used to embed and Iframe with any external content in it that fills all the viewport. ![Fullscreen iframe](examples/fullscreen-iframe.png) #### 7. Live support chat With this feature you can have a support chat in Decidim. It is linked to a [Telegram](https://telegram.org/) group or a single user chat using the [IntergramBot](https://web.telegram.org/#/im?p=@IntergramBot). Just invite the bot to a group or chat with it directly, grab your ID, put it on the Awesome settings and have fun!. For more info or for hosting your own version of the bot check the [Intergram project](https://github.com/idoco/intergram). ![Intergram screenshot](examples/intergram.png) #### 8. Custom CSS applied only according scoped restrictions With this feature you can create directly in the admin a CSS snipped that is only applied globally, in a particular assembly or even a single proposal! ![CSS screenshot](examples/custom_styles.png) #### 9. Change the main menu of Decidim entirely! Feel free to hide, modify or add items in the Decidim's main menu. You can also change the order, establish some conditions (like showing only for logged users) or open in a new window. ![Menu hacks screenshot](examples/menu-1.png) ![Menu hacks screenshot](examples/menu-2.png) ![Menu hacks screenshot](examples/menu-3.png) ![Menu hacks screenshot](examples/menu-4.png) #### 10. Assign admins to specific scopes and prevent them modify anything else Convert any user on the platform (that is not currently an admin) to a limited subset of participatory spaces or event components. Just add users to a box and scope them to some constraints. These users will see the "Edit" button in everywhere they have permissions. Any access to non allowed zones will redirect the user to the admin index page. ![Scoped admins authorized](examples/scoped_admins_authorized.png) ![Scoped admins unauthorized](examples/scoped_admins_unauthorized.png) ![Scoped admins configuration](examples/scoped_admins_config.png) #### 11. Custom fields for proposals Now admins can substitute the body of a proposal with a set of form fields. Edition is make with a Drag & Drop interface in the admin and can (and should) be scoped to apply only to certain proposal components. Technically, the content is stored in the database as an XML document compatible with normal HTML (it uses the DL/DT/DD elements). ![Custom fields screenshot](examples/custom-fields-1.png) ![Custom fields screenshot](examples/custom-fields-2.png) ![Custom fields screenshot](examples/custom-fields-1.gif) Note that the custom fields are build using the jQuery library [formBuilder](https://formbuilder.online). This package is included in Decidim Awesome but the i18n translations are not. By default they are dynamically downloaded from the CDN https://cdn.jsdelivr.net/npm/formbuilder-languages@1.1.0/. If you wish to provide an alternative place for those files, you can configure the variable `form_builder_langs_location` in an initializer: ```ruby # config/initializers/awesome_defaults.rb # A URL where to obtain the translations for the FormBuilder component # you can a custom place if you are worried about the CDN geolocation # Download them from https://github.com/kevinchappell/formBuilder-languages # For instance, copy them to your /public/fb_locales/ directory and set the path here: Decidim::DecidimAwesome.configure do |config| config.form_builder_langs_location = "/fb_locales/" end ``` ##### 11.1. GraphQL types for custom fields #### 11.1. GraphQL types for custom fields Custom fields are displayed in the GaphQL API according to their definition in a formatted array of objects in the attribute `bodyFields`. A query to extract this information could look like this (see that the original `body` is also available): ```graphql { component(id: 999) { ... on Proposals { proposals { edges { node { id bodyFields { locales translation(locale: "en") translations { locale fields machineTranslated } } body { locales translations { locale text machineTranslated } } } } } } } } ``` You can then use this custom type in your GraphQL queries and mutations to handle the custom fields in proposals. ##### 11.2. Private Custom fields Similar to the custom fields feature, but only admins can see the content of the fields. This is useful for adding metadata to proposals that should not be visible to the public (such as contact data). Data is stored encrypted in the database. ![Private Custom fields screenshot](examples/private_custom_fields.png) #### 12. Custom Redirections (or URL shortener feature) Admins can create custom paths that redirect to other places. Destinations can be internal absolute paths or external sites. There's also possible to choose to sanitize (ie: remove) any query string or to maintain it (so you can decide to use). For instance you can create a redirection like * `/take-me-somewhere` => `/processes/canary-islands` Using a link with a query string (ie: `/take-me-somewhere?locale=es`) that will redirect the user to: * `/processes/canary-islands` if query string is sanitized * `/processes/canary-islands?locale=es` if query string is not sanitized > Redirections work only after all other routes have been processed, you cannot override an existing route. > The admin panel comes with a button to check if the redirection works (meaning that no other route is used by the application). > Non-working routes will simply be ignored. ![Custom redirections screenshot](examples/custom-redirections.png) #### 13. Custom validation rules for title and body in proposals Configure as you wish how the fields "title" and "body" are validated in proposals creation. Rules available: * Minimum title and body length (defaults to 15 chars). * Maximum percentage of capital letters for title and body (defaults to 25%). * Maximum number of "marks" (aka: exclamation and interrogation signs) that can be consecutive in the title or the body (defaults to 1). * Enable/disable forcing to start the title or the body with a capital letter (defaults to "enabled"). ![Custom validations](examples/custom_validations.png) #### 14. Admin accountability This feature allows you to list all the users that are, or have been at any point in time, admins, valuators, user managers or any other role in Decidim. Including global admin roles or private admins of a particular participatory space. Results can be filtered by role and by time range and also exported as CSV or other formats. ![Admin accountability](examples/admin_accountability.png) #### 15. Additional proposal sortings ![Proposal sorting](examples/proposal_sorting.png) ![Proposal sorting admin](examples/proposal_sorting-admin.png) This feature allows you to add additional sorting options to the proposals component. By default 4 additional sortings are included: - `supported_first`: Sort proposals supported by me first. - `supported_last`: Sort proposals supported by me last. - `az`: Sort proposals alphabetically. - `za`: Sort proposals alphabetically (reverse). By enabling this feature the user choosed sorting method will be stored in the browser's session. This means that if the user changes the sorting method and then navigates to another page, the same sorting will be applied. You can disable or configure the available sorting types by setting the variable `additional_proposal_sortings` configuration in an initializer: ```ruby # config/initializers/awesome_defaults.rb Decidim::DecidimAwesome.configure do |config| config.additional_proposal_sortings = :disabled end # Or, to disable alphabetical sorting: Decidim::DecidimAwesome.configure do |config| config.additional_proposal_sortings = [:supported_first, :supported_last] end ``` #### 16. Weighted voting This feature allows you to configure a proposals component to use a weighted voting system. This means that each vote can have a different weight and the result of the vote is calculated as the sum of all the weights. Weighted voting can have different presentations that can be registered in a manifest. Admins can then choose between what type of voting they want for their proposals according to the different manifests registered (classic is always available). Some manifests are included by default in Decidim Awesome, if you consider to create (or pay) for a new one, please open a PR or contact us. For instance, here is how the 3-flag voting system looks like: ![Weighted voting](examples/weighted_voting.png) ##### Creating a new manifest for weighted voting A manifest is defined in a initializer in this way: ```ruby if Decidim::DecidimAwesome.enabled?(:weighted_proposal_voting) # register available processors Decidim::DecidimAwesome.voting_registry.register(:no_admins_vote) do |voting| voting.show_vote_button_view = "decidim/decidim_awesome/voting/no_admins_vote/show_vote_button" voting.show_votes_count_view = "decidim/decidim_awesome/voting/no_admins_vote/show_votes_count" # voting.show_votes_count_view = "" # hide votes count if needed voting.proposal_metadata_cell = "decidim/decidim_awesome/voting/proposal_metadata" # define a weight validator (optional, by default all weights are valid) voting.weight_validator do |weight, context| # don't allow admins to vote next if context[:user].admin? # don't allow to vote official proposals next if context[:proposal].official? weight.in? [1, 2, 3, 5] end # optionally, define a label generator block # by default labels are extracted from a I18n key following this rule # "decidim.decidim_awesome.voting.{MANIFEST_NAME}.weights.weight_{WEIGHT}" # # voting.label_generator do |weight, context| # "Weight #{weight.round}" # end end end ``` A manifest must define a vote button view for the main proposal view, a vote count view for the proposal list view, a footer for the proposal cell (used in lists) and a validator for the weight value. All views are optional, if set to `nil` they will use the original ones. If set to an empty string `""` they will be hidden. The `weight_validator` is a `Proc` that receives the weight value and the context with the current user and the proposal and returns true or false if the weight is valid or not. **Notes for view `show_vote_button_view`** When building a new view for the vote button ([see the original](https://github.com/decidim/decidim/blob/release/0.28-stable/decidim-proposals/app/views/decidim/proposals/proposals/_vote_button.html.erb)) is important to take into account the following situations: - If there's a `current_user` logged in - If votes are blocked `if current_settings.votes_blocked?` - If the user has already voted `if @voted_proposals ? @voted_proposals.include?(proposal.id) : proposal.voted_by?(current_user)` - If maximum votes have already reached `if proposal.maximum_votes_reached?` - If the proposal can accumulate supports beyond maximum `if proposal.can_accumulate_supports_beyond_threshold` - If the current component allows the user to participate `if current_component.participatory_space.can_participate?(current_user)` - Note that the [original view](https://github.com/decidim/decidim/blob/release/0.28-stable/decidim-proposals/app/views/decidim/proposals/proposals/_vote_button.html.erb) is overridden only inside the tag `