decidim-toggle 0.1.3

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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/.erb-lint.yml +2134 -0
  3. data/.github/workflows/website.yml +57 -0
  4. data/.gitignore +13 -0
  5. data/.gitlab-ci.yml +165 -0
  6. data/.node-version +1 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +2 -0
  9. data/.ruby-version +1 -0
  10. data/.simplecov +18 -0
  11. data/CONTRIBUTING.md +17 -0
  12. data/Gemfile +33 -0
  13. data/Gemfile.lock +843 -0
  14. data/LICENSE.md +661 -0
  15. data/README.md +90 -0
  16. data/Rakefile +38 -0
  17. data/app/commands/decidim/toggle/update_authorizations_command.rb +31 -0
  18. data/app/commands/decidim/toggle/update_emails_command.rb +30 -0
  19. data/app/commands/decidim/toggle/update_file_upload_settings_command.rb +31 -0
  20. data/app/commands/decidim/toggle/update_locale_command.rb +47 -0
  21. data/app/commands/decidim/toggle/update_module_config_command.rb +31 -0
  22. data/app/commands/decidim/toggle/update_name_command.rb +31 -0
  23. data/app/commands/decidim/toggle/update_omniauth_command.rb +30 -0
  24. data/app/commands/decidim/toggle/update_security_command.rb +31 -0
  25. data/app/controllers/decidim_toggle/system/settings_tab_controller.rb +64 -0
  26. data/app/forms/decidim/toggle/update_authorizations_form.rb +54 -0
  27. data/app/forms/decidim/toggle/update_emails_form.rb +28 -0
  28. data/app/forms/decidim/toggle/update_file_upload_settings_form.rb +26 -0
  29. data/app/forms/decidim/toggle/update_locale_form.rb +116 -0
  30. data/app/forms/decidim/toggle/update_name_form.rb +63 -0
  31. data/app/forms/decidim/toggle/update_omniauth_form.rb +37 -0
  32. data/app/forms/decidim/toggle/update_security_form.rb +65 -0
  33. data/app/helpers/decidim/toggle/javascript_config_helper.rb +11 -0
  34. data/app/helpers/decidim/toggle/system_settings_tab_helper.rb +59 -0
  35. data/app/models/decidim/toggle/organization_module_config.rb +15 -0
  36. data/app/overrides/add_toggle_javascript_admin.rb +11 -0
  37. data/app/overrides/add_toggle_javascript_public.rb +11 -0
  38. data/app/packs/entrypoints/decidim_toggle.js +3 -0
  39. data/app/packs/src/decidim/toggle/organization_settings_tabs.js +114 -0
  40. data/app/packs/stylesheets/decidim/toggle/organization_settings.scss +160 -0
  41. data/app/views/decidim/system/organizations/edit.html.erb +20 -0
  42. data/app/views/decidim_toggle/system/organizations/_default_form_tab.html.erb +5 -0
  43. data/app/views/decidim_toggle/system/organizations/_encryption_not_configured_callout.html.erb +6 -0
  44. data/app/views/decidim_toggle/system/organizations/_settings_tab_active_tab_field.html.erb +1 -0
  45. data/app/views/decidim_toggle/system/organizations/_settings_tab_submit.html.erb +4 -0
  46. data/app/views/decidim_toggle/system/organizations/_settings_tabs.html.erb +31 -0
  47. data/app/views/decidim_toggle/system/organizations/tabs/_authorizations_tab.html.erb +9 -0
  48. data/app/views/decidim_toggle/system/organizations/tabs/_emails_tab.html.erb +5 -0
  49. data/app/views/decidim_toggle/system/organizations/tabs/_file_upload_tab.html.erb +5 -0
  50. data/app/views/decidim_toggle/system/organizations/tabs/_language_tab.html.erb +35 -0
  51. data/app/views/decidim_toggle/system/organizations/tabs/_omniauth_tab.html.erb +5 -0
  52. data/app/views/decidim_toggle/system/organizations/tabs/_security_tab.html.erb +20 -0
  53. data/app/views/layouts/decidim/toggle/_javascript_config.html.erb +3 -0
  54. data/bin/check +10 -0
  55. data/bin/i18n-tasks +27 -0
  56. data/bin/postversion +14 -0
  57. data/config/assets.rb +8 -0
  58. data/config/locales/decidim_toggle_en.yml +58 -0
  59. data/crowdin.yml +15 -0
  60. data/db/migrate/20260321120000_create_decidim_toggle_organization_module_configs.rb +20 -0
  61. data/decidim-toggle.gemspec +35 -0
  62. data/docker-compose.yml +41 -0
  63. data/lib/decidim/toggle/engine.rb +62 -0
  64. data/lib/decidim/toggle/expose_attributes_to_js.rb +26 -0
  65. data/lib/decidim/toggle/expose_attributes_to_js_validator.rb +32 -0
  66. data/lib/decidim/toggle/gem_registry.rb +15 -0
  67. data/lib/decidim/toggle/informative_callouts.rb +76 -0
  68. data/lib/decidim/toggle/javascript_config.rb +87 -0
  69. data/lib/decidim/toggle/module_config.rb +64 -0
  70. data/lib/decidim/toggle/module_config_form.rb +41 -0
  71. data/lib/decidim/toggle/module_config_i18n.rb +44 -0
  72. data/lib/decidim/toggle/module_configuration_presenter.rb +55 -0
  73. data/lib/decidim/toggle/organization_settings_tabs.rb +69 -0
  74. data/lib/decidim/toggle/settings_form_builder.rb +200 -0
  75. data/lib/decidim/toggle/settings_tab_item.rb +37 -0
  76. data/lib/decidim/toggle/settings_tab_registry.rb +109 -0
  77. data/lib/decidim/toggle/settings_tabs.rb +56 -0
  78. data/lib/decidim/toggle/tab_form.rb +20 -0
  79. data/lib/decidim/toggle/version.rb +14 -0
  80. data/lib/decidim/toggle.rb +36 -0
  81. data/lib/tasks/decidim/toggle/toggle_upgrade.rake +13 -0
  82. data/lib/tasks/decidim/toggle/toggle_webpacker.rake +60 -0
  83. data/log/.gitignore +2 -0
  84. data/package.json +18 -0
  85. data/prettier.config.js +15 -0
  86. data/spec/commands/decidim/toggle/update_authorizations_command_spec.rb +41 -0
  87. data/spec/commands/decidim/toggle/update_emails_command_spec.rb +84 -0
  88. data/spec/commands/decidim/toggle/update_file_upload_settings_command_spec.rb +28 -0
  89. data/spec/commands/decidim/toggle/update_locale_command_spec.rb +53 -0
  90. data/spec/commands/decidim/toggle/update_module_config_command_spec.rb +38 -0
  91. data/spec/commands/decidim/toggle/update_name_command_spec.rb +49 -0
  92. data/spec/commands/decidim/toggle/update_omniauth_command_spec.rb +80 -0
  93. data/spec/commands/decidim/toggle/update_security_command_spec.rb +25 -0
  94. data/spec/decidim/toggle/settings_tab_item_spec.rb +34 -0
  95. data/spec/decidim/toggle/settings_tab_registry_spec.rb +66 -0
  96. data/spec/decidim/toggle/settings_tabs_spec.rb +60 -0
  97. data/spec/forms/concerns/decidim/toggle/informative_callouts_spec.rb +48 -0
  98. data/spec/forms/decidim/toggle/update_authorizations_form_spec.rb +40 -0
  99. data/spec/forms/decidim/toggle/update_emails_form_spec.rb +35 -0
  100. data/spec/forms/decidim/toggle/update_file_upload_settings_form_spec.rb +20 -0
  101. data/spec/forms/decidim/toggle/update_locale_form_spec.rb +64 -0
  102. data/spec/forms/decidim/toggle/update_name_form_spec.rb +57 -0
  103. data/spec/forms/decidim/toggle/update_omniauth_form_spec.rb +56 -0
  104. data/spec/forms/decidim/toggle/update_security_form_spec.rb +32 -0
  105. data/spec/helpers/decidim/toggle/system_settings_tab_helper_spec.rb +99 -0
  106. data/spec/lib/decidim/toggle/expose_attributes_to_js_spec.rb +31 -0
  107. data/spec/lib/decidim/toggle/expose_attributes_to_js_validator_spec.rb +30 -0
  108. data/spec/lib/decidim/toggle/gem_registry_spec.rb +30 -0
  109. data/spec/lib/decidim/toggle/javascript_config_spec.rb +169 -0
  110. data/spec/lib/decidim/toggle/module_config_form_spec.rb +45 -0
  111. data/spec/lib/decidim/toggle/module_config_spec.rb +74 -0
  112. data/spec/lib/decidim/toggle/module_configuration_presenter_spec.rb +53 -0
  113. data/spec/lib/decidim/toggle/settings_form_builder_spec.rb +115 -0
  114. data/spec/requests/decidim_toggle/system/settings_tab_spec.rb +144 -0
  115. data/spec/spec_helper.rb +12 -0
  116. data/spec/support/decidim_factories.rb +3 -0
  117. data/spec/support/devise.rb +5 -0
  118. data/spec/system/decidim_toggle/javascript_config_spec.rb +56 -0
  119. data/website/.gitignore +4 -0
  120. data/website/docs/developer/_category_.json +8 -0
  121. data/website/docs/developer/code-of-conduct.md +99 -0
  122. data/website/docs/developer/contribute.md +50 -0
  123. data/website/docs/developer/deface-usage.md +37 -0
  124. data/website/docs/developer/documentation.md +58 -0
  125. data/website/docs/developer/error-handling.md +51 -0
  126. data/website/docs/developer/tab-registry.md +51 -0
  127. data/website/docs/developer/view-customization.md +49 -0
  128. data/website/docs/index.md +80 -0
  129. data/website/docs/integrate/_category_.json +8 -0
  130. data/website/docs/integrate/attributes.md +121 -0
  131. data/website/docs/integrate/customize-views.md +62 -0
  132. data/website/docs/integrate/index.md +38 -0
  133. data/website/docs/integrate/informative_callout.md +80 -0
  134. data/website/docs/integrate/javascript.md +84 -0
  135. data/website/docs/integrate/labels.md +91 -0
  136. data/website/docs/integrate/quickstart.md +77 -0
  137. data/website/docusaurus.config.ts +100 -0
  138. data/website/package.json +31 -0
  139. data/website/sidebars.ts +7 -0
  140. data/website/src/css/custom.css +37 -0
  141. data/website/static/img/logo.svg +1 -0
  142. data/website/static/img/schema_overview.png +0 -0
  143. data/website/static/img/screenshot_informative_callouts.png +0 -0
  144. data/website/static/img/screenshot_saved_flash.png +0 -0
  145. data/website/static/img/screenshots_locale_tab.png +0 -0
  146. data/website/static/img/screenshots_name_tab.png +0 -0
  147. data/website/static/img/screenshots_security_tab.png +0 -0
  148. data/website/tsconfig.json +6 -0
  149. data/website/yarn.lock +8336 -0
  150. data/yarn.lock +13 -0
  151. metadata +249 -0
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Decidim Toggle
2
+
3
+ <p align="center">
4
+ <img src="website/static/img/logo.svg" alt="Decidim Toggle" width="120" />
5
+ </p>
6
+
7
+ [![pipeline status](https://git.octree.ch/decidim/vocacity/decidim-modules/decidim-toggle/badges/main/pipeline.svg)](https://git.octree.ch/decidim/vocacity/decidim-modules/decidim-toggle/-/commits/main)
8
+ [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE.md)
9
+ [![Docs](https://img.shields.io/badge/docs-octree--gva.github.io-informational)](https://octree-gva.github.io/decidim-toggle/)
10
+
11
+ Register organization settings tabs for Decidim modules—one form, one command, one read API per gem.
12
+
13
+ ## Contents
14
+
15
+ - [Quick start](#quick-start)
16
+ - [Host app install](#host-app-install)
17
+ - [Documentation](#documentation)
18
+ - [Development](#development-this-gem)
19
+ - [Support](#support)
20
+ - [Contributing](#contributing)
21
+ - [License](#license)
22
+
23
+ ## Quick start
24
+
25
+ Add a settings tab in three steps:
26
+
27
+ 1. **Form** — include `Decidim::Toggle::ModuleConfigForm`, set `module_config_name`, declare attributes.
28
+
29
+ ```ruby
30
+ module MyModule
31
+ class AdminConfigForm < Decidim::Form
32
+ include Decidim::Toggle::ModuleConfigForm
33
+
34
+ self.module_config_name = "my_module"
35
+ mimic :organization
36
+ attribute :enabled, :boolean
37
+ end
38
+ end
39
+ ```
40
+
41
+ 2. **Register** — in your engine, after `decidim_toggle.organization_settings_tabs`:
42
+
43
+ ```ruby
44
+ initializer "decidim_my_module.organization_settings_tab", after: "decidim_toggle.organization_settings_tabs" do
45
+ Decidim::Toggle.settings_tabs :organization_settings do |tabs|
46
+ tabs.add_tab :my_module, "My module",
47
+ form: MyModule::AdminConfigForm,
48
+ command: Decidim::Toggle::UpdateModuleConfigCommand,
49
+ module_name: :my_module
50
+ end
51
+ end
52
+ ```
53
+
54
+ 3. **Read** — `Decidim::Toggle.config_for(organization, :my_module)`.
55
+
56
+ Optional view partial: `form_layout_partial:` on `add_tab` — see [Customize views](https://octree-gva.github.io/decidim-toggle/integrate/customize-views).
57
+
58
+ Full walkthrough: [Add a settings tab](https://octree-gva.github.io/decidim-toggle/integrate/quickstart).
59
+
60
+ ## Host app install
61
+
62
+ ```ruby
63
+ gem "decidim-toggle"
64
+ ```
65
+
66
+ ```bash
67
+ bundle install
68
+ rails decidim_toggle:install:migrations
69
+ rails db:migrate
70
+ ```
71
+
72
+ ## Documentation
73
+
74
+ [octree-gva.github.io/decidim-toggle](https://octree-gva.github.io/decidim-toggle/) — integrator guides (registration, views, storage) and contributor docs.
75
+
76
+ ## Development (this gem)
77
+
78
+ Docker + `./bin/check` (RuboCop, erblint, RSpec) — see [CONTRIBUTING.md](CONTRIBUTING.md) and [Contribute](https://octree-gva.github.io/decidim-toggle/contributing).
79
+
80
+ ## Support
81
+
82
+ [GitLab issues](https://git.octree.ch/decidim/vocacity/decidim-modules/decidim-toggle/-/issues) for bugs and integration questions.
83
+
84
+ ## Contributing
85
+
86
+ Pull requests welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a merge request.
87
+
88
+ ## License
89
+
90
+ [AGPL-3.0](LICENSE.md).
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/dev/common_rake"
4
+
5
+ def install_deps(path)
6
+ Dir.chdir(path) do
7
+ system("bundle exec rails decidim:update")
8
+ system("bundle exec rake db:migrate")
9
+ end
10
+ end
11
+
12
+ def seed_db(path)
13
+ Dir.chdir(path) do
14
+ system("bundle exec rake db:seed")
15
+ end
16
+ end
17
+
18
+ desc "Generates a dummy app for testing"
19
+ task test_app: "decidim:generate_external_test_app"
20
+
21
+ desc "Generates a development app."
22
+ task :development_app do
23
+ Bundler.with_original_env do
24
+ generate_decidim_app(
25
+ "development_app",
26
+ "--app_name",
27
+ "#{base_app_name}_development_app",
28
+ "--path",
29
+ "..",
30
+ "--recreate_db",
31
+ "--demo",
32
+ "--locales",
33
+ "en,ca,es,fr"
34
+ )
35
+ end
36
+ install_deps("development_app")
37
+ seed_db("development_app")
38
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateAuthorizationsCommand < Decidim::Command
6
+ def initialize(organization, form)
7
+ @organization = organization
8
+ @form = form
9
+ end
10
+
11
+ def call
12
+ return broadcast(:invalid) if form.invalid?
13
+
14
+ transaction do
15
+ organization.available_authorizations = form.clean_available_authorizations
16
+ organization.save!
17
+ end
18
+
19
+ Decidim::OrganizationSettings.reload(organization) if defined?(Decidim::OrganizationSettings)
20
+
21
+ broadcast(:ok)
22
+ rescue ActiveRecord::RecordInvalid
23
+ broadcast(:invalid)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :organization, :form
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateEmailsCommand < Decidim::Command
6
+ def initialize(organization, form)
7
+ @organization = organization
8
+ @form = form
9
+ end
10
+
11
+ def call
12
+ return broadcast(:invalid) if form.invalid?
13
+
14
+ transaction do
15
+ organization.smtp_settings = form.encrypted_smtp_settings
16
+ organization.save!
17
+ end
18
+
19
+ Decidim::OrganizationSettings.reload(organization) if defined?(Decidim::OrganizationSettings)
20
+ broadcast(:ok)
21
+ rescue ActiveRecord::RecordInvalid
22
+ broadcast(:invalid)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :organization, :form
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateFileUploadSettingsCommand < Decidim::Command
6
+ def initialize(organization, form)
7
+ @organization = organization
8
+ @form = form
9
+ end
10
+
11
+ def call
12
+ return broadcast(:invalid) if form.invalid?
13
+
14
+ transaction do
15
+ organization.file_upload_settings = form.file_upload_settings.final
16
+ organization.save!
17
+ end
18
+
19
+ Decidim::OrganizationSettings.reload(organization) if defined?(Decidim::OrganizationSettings)
20
+
21
+ broadcast(:ok)
22
+ rescue ActiveRecord::RecordInvalid
23
+ broadcast(:invalid)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :organization, :form
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ class UpdateLocaleCommand < Decidim::Command
8
+ def initialize(organization, form)
9
+ @organization = organization
10
+ @form = form
11
+ end
12
+
13
+ def call
14
+ return broadcast(:invalid) if form.invalid?
15
+
16
+ transaction do
17
+ update_organization_locales
18
+ rebuild_search_index
19
+ end
20
+
21
+ broadcast(:ok)
22
+ rescue ActiveRecord::RecordInvalid
23
+ broadcast(:invalid)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :organization, :form
29
+
30
+ def update_organization_locales
31
+ organization.available_locales = form.clean_available_locales
32
+ organization.default_locale = form.default_locale
33
+ if organization.respond_to?(:enable_machine_translations=)
34
+ organization.enable_machine_translations = form.enable_machine_translations
35
+ organization.machine_translation_display_priority = form.machine_translation_display_priority || "original"
36
+ end
37
+ organization.save!
38
+ end
39
+
40
+ def rebuild_search_index
41
+ Rails.application.load_tasks
42
+ Rake::Task["decidim:locales:rebuild_search"].reenable
43
+ Rake::Task["decidim:locales:rebuild_search"].invoke
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ # Persists a form that includes {ModuleConfigForm} into {OrganizationModuleConfig#config}.
6
+ class UpdateModuleConfigCommand < Decidim::Command
7
+ def initialize(organization, form)
8
+ @organization = organization
9
+ @form = form
10
+ end
11
+
12
+ def call
13
+ return broadcast(:invalid) if form.class.module_config_name.blank?
14
+ return broadcast(:invalid) if form.invalid?
15
+
16
+ Decidim::Toggle.save_config!(
17
+ organization,
18
+ form.class.module_config_name,
19
+ form.to_h
20
+ )
21
+ broadcast(:ok)
22
+ rescue ActiveRecord::RecordInvalid
23
+ broadcast(:invalid)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :organization, :form
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateNameCommand < Decidim::Command
6
+ def initialize(organization, form)
7
+ @organization = organization
8
+ @form = form
9
+ end
10
+
11
+ def call
12
+ return broadcast(:invalid) if form.invalid?
13
+
14
+ transaction do
15
+ organization.name = form.name
16
+ organization.host = form.host
17
+ organization.secondary_hosts = form.clean_secondary_hosts
18
+ organization.save!
19
+ end
20
+
21
+ broadcast(:ok)
22
+ rescue ActiveRecord::RecordInvalid
23
+ broadcast(:invalid)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :organization, :form
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateOmniauthCommand < Decidim::Command
6
+ def initialize(organization, form)
7
+ @organization = organization
8
+ @form = form
9
+ end
10
+
11
+ def call
12
+ return broadcast(:invalid) if form.invalid?
13
+
14
+ transaction do
15
+ organization.omniauth_settings = form.encrypted_omniauth_settings
16
+ organization.save!
17
+ end
18
+
19
+ Decidim::OrganizationSettings.reload(organization) if defined?(Decidim::OrganizationSettings)
20
+ broadcast(:ok)
21
+ rescue ActiveRecord::RecordInvalid
22
+ broadcast(:invalid)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :organization, :form
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateSecurityCommand < Decidim::Command
6
+ def initialize(organization, form)
7
+ @organization = organization
8
+ @form = form
9
+ end
10
+
11
+ def call
12
+ return broadcast(:invalid) if form.invalid?
13
+
14
+ transaction do
15
+ organization.force_users_to_authenticate_before_access_organization = form.force_users_to_authenticate_before_access_organization
16
+ organization.users_registration_mode = form.users_registration_mode
17
+ organization.content_security_policy = form.content_security_policy
18
+ organization.save!
19
+ end
20
+
21
+ broadcast(:ok)
22
+ rescue ActiveRecord::RecordInvalid
23
+ broadcast(:invalid)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :organization, :form
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DecidimToggle
4
+ module System
5
+ class SettingsTabController < Decidim::System::ApplicationController
6
+ def update
7
+ build_tabs_registry
8
+ config = registry.form_tab(params[:tab_id])
9
+ return head :not_found unless config
10
+
11
+ @organization = Decidim::Organization.find(params[:organization_id])
12
+ form_class = config[:form]
13
+ command_class = config[:command]
14
+ @form = form(form_class).from_params(params.permit!).with_context(current_organization: @organization)
15
+
16
+ command_class.call(@organization, @form) do
17
+ on(:ok) do
18
+ flash[:notice] = t("decidim_toggle.system.organizations.form_tab.success")
19
+ redirect_to settings_tab_redirect_target
20
+ end
21
+
22
+ on(:invalid) do
23
+ flash[:decidim_toggle_invalid_settings_tab] = {
24
+ organization_id: @organization.id,
25
+ tab_id: params[:tab_id].to_s,
26
+ params: params.fetch(:organization, {}).permit!.to_h,
27
+ errors: @form.errors.messages.transform_keys(&:to_s).transform_values { |messages| messages.map(&:to_s) }
28
+ }
29
+ redirect_to settings_tab_redirect_target
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def build_tabs_registry
37
+ tabs = Decidim::Toggle::SettingsTabs.new(:organization_settings)
38
+ tabs.build_for(self)
39
+ end
40
+
41
+ def registry
42
+ Decidim::Toggle::SettingsTabRegistry.find(:organization_settings)
43
+ end
44
+
45
+ def settings_tab_redirect_target
46
+ anchor = tab_anchor_fragment
47
+ return decidim_system.edit_organization_path(@organization) if anchor.blank?
48
+
49
+ decidim_system.edit_organization_path(@organization, anchor:)
50
+ end
51
+
52
+ def tab_anchor_fragment
53
+ tab_id = params[:decidim_toggle_active_tab].presence
54
+ return nil unless tab_id
55
+
56
+ tabs = Decidim::Toggle::SettingsTabs.new(:organization_settings)
57
+ tabs.build_for(self)
58
+ return nil unless tabs.items.any? { |item| item.identifier.to_s == tab_id.to_s }
59
+
60
+ "panel-toggle-#{tab_id}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ # Vanilla Decidim: `available_authorizations` is a string array of workflow names.
6
+ # Other gems may replace this tab (same identifier `:authorizations`); see
7
+ # {Decidim::Toggle::OrganizationSettingsTabs}.
8
+ class UpdateAuthorizationsForm < Decidim::Form
9
+ mimic :organization
10
+
11
+ attribute :available_authorizations, [String]
12
+
13
+ validate :available_authorizations_subset_of_workflows
14
+
15
+ def self.from_model(organization)
16
+ from_params(organization: { available_authorizations: Array(organization.available_authorizations).map(&:to_s) })
17
+ end
18
+
19
+ def self.collection_for_available_authorizations
20
+ Decidim.authorization_workflows.map { |w| [w.name, w.description] }
21
+ end
22
+
23
+ def self.from_params(params, additional_params = {})
24
+ params = params.to_h.with_indifferent_access if params.respond_to?(:to_h)
25
+ attrs = params[:organization] || params
26
+ if attrs[:available_authorizations].nil?
27
+ params = params.dup
28
+ params[:organization] = (params[:organization] || {}).merge(available_authorizations: [])
29
+ end
30
+ super(params, additional_params)
31
+ end
32
+
33
+ def clean_available_authorizations
34
+ @clean_available_authorizations ||= if available_authorizations.blank?
35
+ []
36
+ else
37
+ available_authorizations.map(&:to_s).select(&:present?)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def available_authorizations_subset_of_workflows
44
+ return if clean_available_authorizations.blank?
45
+
46
+ allowed = Decidim.authorization_workflows.to_set(&:name)
47
+ invalid = Array(clean_available_authorizations).map(&:to_s).reject { |a| allowed.include?(a) }
48
+ return if invalid.empty?
49
+
50
+ errors.add(:available_authorizations, :invalid)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateEmailsForm < Decidim::System::BaseOrganizationForm
6
+ mimic :organization
7
+
8
+ # These tabs only update a small JSONB slice (SMTP settings).
9
+ # The generic system form validates presence of `host` and
10
+ # `users_registration_mode`, so fall back to the current organization when
11
+ # those attributes aren't part of the tab payload.
12
+ def host
13
+ super.presence || current_organization&.host
14
+ end
15
+
16
+ def users_registration_mode
17
+ super.presence || current_organization&.users_registration_mode
18
+ end
19
+
20
+ private
21
+
22
+ # No-op: this tab doesn't touch organization identity/uniqueness.
23
+ def validate_organization_uniqueness
24
+ # intentionally blank
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ # Wraps {Decidim::System::FileUploadSettingsForm} for the settings-tab endpoint
6
+ # so file upload settings can be saved without submitting the full organization form.
7
+ class UpdateFileUploadSettingsForm < Decidim::Form
8
+ mimic :organization
9
+
10
+ attribute :file_upload_settings, Decidim::System::FileUploadSettingsForm
11
+
12
+ def self.from_model(organization)
13
+ from_params(
14
+ organization: {
15
+ file_upload_settings: Decidim::System::FileUploadSettingsForm.from_model(organization.file_upload_settings)
16
+ }
17
+ )
18
+ end
19
+
20
+ def self.from_params(params, additional_params = {})
21
+ params = params.to_h.with_indifferent_access if params.respond_to?(:to_h)
22
+ super(params, additional_params)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Toggle
5
+ class UpdateLocaleForm < Decidim::Form
6
+ mimic :organization
7
+
8
+ attribute :available_locales, Array
9
+ attribute :default_locale, String
10
+ attribute :enable_machine_translations, Boolean
11
+ attribute :machine_translation_display_priority, String
12
+
13
+ # Decidim::Form delegates :available_locales to current_organization; this form is often
14
+ # built without context (e.g. system settings tabs), so prefer the attribute value.
15
+ def available_locales
16
+ attributes["available_locales"]
17
+ end
18
+
19
+ validates :available_locales, presence: true
20
+ validates :default_locale, presence: true
21
+ validates :machine_translation_display_priority,
22
+ inclusion: { in: %w(original translation) },
23
+ if: -> { defined?(Decidim::Organization::AVAILABLE_MACHINE_TRANSLATION_DISPLAY_PRIORITIES) && enable_machine_translations }
24
+ validate :available_locales_subset_of_i18n
25
+ validate :default_locale_in_available_locales
26
+ validate :default_locale_in_i18n
27
+
28
+ def self.from_model(organization)
29
+ attrs = locale_attrs_from(organization)
30
+ attrs[:enable_machine_translations] = organization.enable_machine_translations if organization.respond_to?(:enable_machine_translations)
31
+ attrs[:machine_translation_display_priority] = organization.machine_translation_display_priority if organization.respond_to?(:machine_translation_display_priority)
32
+ from_params(attrs)
33
+ end
34
+
35
+ def self.locale_attrs_from(organization)
36
+ record =
37
+ if organization.is_a?(Decidim::Organization)
38
+ organization
39
+ elsif organization.respond_to?(:id) && organization.id.present?
40
+ Decidim::Organization.find_by(id: organization.id)
41
+ end
42
+
43
+ source = record || organization
44
+
45
+ {
46
+ available_locales: Array(source.try(:available_locales)).map(&:to_s),
47
+ default_locale: source.try(:default_locale)&.to_s
48
+ }
49
+ end
50
+ private_class_method :locale_attrs_from
51
+
52
+ def self.collection_for_machine_translation_display_priority
53
+ return [] unless defined?(Decidim::Organization::AVAILABLE_MACHINE_TRANSLATION_DISPLAY_PRIORITIES)
54
+
55
+ Decidim::Organization::AVAILABLE_MACHINE_TRANSLATION_DISPLAY_PRIORITIES.map do |priority|
56
+ [
57
+ priority,
58
+ I18n.t("activemodel.attributes.organization.machine_translation_display_priority_#{priority}", default: priority)
59
+ ]
60
+ end
61
+ end
62
+
63
+ def self.collection_for_available_locales
64
+ I18n.available_locales.map { |l| [l.to_s, l.to_s] }
65
+ end
66
+
67
+ def self.collection_for_default_locale
68
+ I18n.available_locales.map { |l| [l.to_s, l.to_s] }
69
+ end
70
+
71
+ def self.from_params(params, additional_params = {})
72
+ params = params.to_h.with_indifferent_access if params.respond_to?(:to_h)
73
+ attrs = params[:organization] || params
74
+ if attrs[:available_locales].is_a?(Hash)
75
+ params = params.dup
76
+ params[:organization] = (params[:organization] || {}).merge(available_locales: attrs[:available_locales].keys.compact_blank)
77
+ end
78
+ super(params, additional_params)
79
+ end
80
+
81
+ def clean_available_locales
82
+ return [] if available_locales.blank?
83
+
84
+ allowed = i18n_available_locales_set
85
+ Array(available_locales).map(&:to_s).select { |l| allowed.include?(l) }
86
+ end
87
+
88
+ private
89
+
90
+ def i18n_available_locales_set
91
+ @i18n_available_locales_set ||= I18n.available_locales.to_set(&:to_s)
92
+ end
93
+
94
+ def available_locales_subset_of_i18n
95
+ return if available_locales.blank?
96
+
97
+ invalid = Array(available_locales).map(&:to_s) - i18n_available_locales_set.to_a
98
+ return if invalid.empty?
99
+
100
+ errors.add(:available_locales, :inclusion)
101
+ end
102
+
103
+ def default_locale_in_available_locales
104
+ return if default_locale.blank?
105
+
106
+ errors.add(:default_locale, :inclusion) unless Array(available_locales).map(&:to_s).include?(default_locale.to_s)
107
+ end
108
+
109
+ def default_locale_in_i18n
110
+ return if default_locale.blank?
111
+
112
+ errors.add(:default_locale, :inclusion) unless i18n_available_locales_set.include?(default_locale.to_s)
113
+ end
114
+ end
115
+ end
116
+ end