From d279c2d3fd9db9a77d7f4d631da115f6910be870 Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 7 May 2026 18:01:41 +0200 Subject: [PATCH 1/7] [WIP] Add attachments to static pages --- .../static_pages_controller_decorator.rb | 11 +++ .../admin/static_pages_form_decorator.rb | 16 ++++ .../admin/update_static_page_decorator.rb | 56 +++++++++++ .../decidim/pages_controller_decorator.rb | 19 ++++ .../decidim/static_pages_decorator.rb | 11 +++ .../decidim/admin/static_pages/_form.html.erb | 66 +++++++++++++ app/views/decidim/pages/show.html.erb | 32 +++++++ config/locales/ca_admin.yml | 10 ++ config/locales/en_admin.yml | 10 ++ config/locales/es_admin.yml | 10 ++ config/locales/oc_admin.yml | 6 ++ .../admin/static_pages_form_decorator_spec.rb | 59 ++++++++++++ .../update_static_page_decorator_spec.rb | 92 +++++++++++++++++++ .../admin_static_page_form_spec.rb | 32 +++++++ 14 files changed, 430 insertions(+) create mode 100644 app/decorators/decidim/admin/static_pages_controller_decorator.rb create mode 100644 app/decorators/decidim/admin/static_pages_form_decorator.rb create mode 100644 app/decorators/decidim/admin/update_static_page_decorator.rb create mode 100644 app/decorators/decidim/pages_controller_decorator.rb create mode 100644 app/decorators/decidim/static_pages_decorator.rb create mode 100644 app/views/decidim/admin/static_pages/_form.html.erb create mode 100644 app/views/decidim/pages/show.html.erb create mode 100644 spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb create mode 100644 spec/decorators/decidim/admin/update_static_page_decorator_spec.rb create mode 100644 spec/system/admin/static_pages/admin_static_page_form_spec.rb diff --git a/app/decorators/decidim/admin/static_pages_controller_decorator.rb b/app/decorators/decidim/admin/static_pages_controller_decorator.rb new file mode 100644 index 000000000..18a1fe509 --- /dev/null +++ b/app/decorators/decidim/admin/static_pages_controller_decorator.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Decidim::Admin::StaticPagesControllerDecorator + def self.decorate + Decidim::Admin::StaticPagesController.class_eval do + helper_method :tab_panel_items + end + end +end + +::Decidim::Admin::StaticPagesControllerDecorator.decorate \ No newline at end of file diff --git a/app/decorators/decidim/admin/static_pages_form_decorator.rb b/app/decorators/decidim/admin/static_pages_form_decorator.rb new file mode 100644 index 000000000..406583c9d --- /dev/null +++ b/app/decorators/decidim/admin/static_pages_form_decorator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Decidim::Admin::StaticPagesFormDecorator + def self.decorate + Decidim::Admin::StaticPageForm.class_eval do + include Decidim::AttachmentAttributes + include Decidim::HasUploadValidations + + attribute :attachment, ::Decidim::AttachmentForm + attachments_attribute :documents + attachments_attribute :photos + end + end +end + +::Decidim::Admin::StaticPagesFormDecorator.decorate \ No newline at end of file diff --git a/app/decorators/decidim/admin/update_static_page_decorator.rb b/app/decorators/decidim/admin/update_static_page_decorator.rb new file mode 100644 index 000000000..8353d4dd0 --- /dev/null +++ b/app/decorators/decidim/admin/update_static_page_decorator.rb @@ -0,0 +1,56 @@ +#frozen_string_literal: true + +module Decidim::Admin::UpdateStaticPageDecorator + def self.decorate + Decidim::Admin::UpdateStaticPage.class_eval do + include ::Decidim::MultipleAttachmentsMethods + include ::Decidim::GalleryMethods + + def initialize(form, page) + @form = form + @page = page + @attached_to = page + end + + def call + return broadcast(:invalid) if @form.invalid? + + if process_attachments? + build_attachments + return broadcast(:invalid) if attachments_invalid? + end + + if process_gallery? + build_gallery + return broadcast(:invalid) if gallery_invalid? + end + + transaction do + update_static_page + document_cleanup! + photo_cleanup! + create_attachments if process_attachments? + create_gallery if process_gallery? + broadcast(:ok) + end + end + + def update_static_page + parsed_content = form.content.transform_values do |value| + Decidim::ContentProcessor.parse(value.to_s, current_organization: form.current_organization).rewrite + end + Decidim.traceability.update!( + page, + form.current_user, + content: parsed_content + ) + end + + private + + attr_reader :form, :page, :current_user + end + end +end + +::Decidim::Admin::UpdateStaticPageDecorator.decorate \ No newline at end of file diff --git a/app/decorators/decidim/pages_controller_decorator.rb b/app/decorators/decidim/pages_controller_decorator.rb new file mode 100644 index 000000000..7d4ec3f0a --- /dev/null +++ b/app/decorators/decidim/pages_controller_decorator.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Decidim::PagesControllerDecorator + def self.decorate + Decidim::PagesController.class_eval do + include ::Decidim::AttachmentsHelper + + helper_method :tab_panel_items + + private + + def tab_panel_items + @tab_panel_items ||= attachments_tab_panel_items(@page) + end + end + end +end + +::Decidim::PagesControllerDecorator.decorate diff --git a/app/decorators/decidim/static_pages_decorator.rb b/app/decorators/decidim/static_pages_decorator.rb new file mode 100644 index 000000000..f318a6a9d --- /dev/null +++ b/app/decorators/decidim/static_pages_decorator.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Decidim::StaticPagesDecorator + def self.decorate + Decidim::StaticPage.class_eval do + include Decidim::HasAttachments + end + end +end + +::Decidim::StaticPagesDecorator.decorate \ No newline at end of file diff --git a/app/views/decidim/admin/static_pages/_form.html.erb b/app/views/decidim/admin/static_pages/_form.html.erb new file mode 100644 index 000000000..ac4d6359e --- /dev/null +++ b/app/views/decidim/admin/static_pages/_form.html.erb @@ -0,0 +1,66 @@ +
+
+
+
+ <%= form.translated :text_field, :title, autofocus: true, aria: { label: :title } %> +
+ + <% if allowed_to? :update_slug, :static_page, static_page: form.object %> +
+
+ <%= form.label :slug %> +
+ <%= form.text_field :slug, label: false, help_text: t(".slug_help_html", url: decidim_form_slug_url("pages", form.object.slug)) %> +
+ <% end %> + +
+ <%= form.translated :editor, :content, aria: { label: :content } %> +
+ +
+
+ <%= form.label :topic %> +
+ <%= form.select :topic_id, form.object.topics.map { |topic| [translated_attribute(topic.title), topic.id] }, include_blank: t(".none"), label: false %> +
+ + <%= render partial: "form_notable_changes", locals: { form: } %> + +
+
+ <%= form.label :weight %> +
+ <%= form.number_field :weight, label: false %> +
+ + <% unless form.object.slug == "terms-of-service" %> +
+ <%= t(".attachment_legend") %> + <%= form.attachment :documents, + multiple: true, + label: t("decidim.admin.static_pages.form.add_documents"), + button_label: t("decidim.admin.static_pages.form.add_documents"), + button_class: "button button__lg button__transparent-secondary w-full", + button_edit_label: t("decidim.admin.static_pages.form.edit_documents"), + help_text: t("decidim.admin.static_pages.form.attachment_legend") %> + + <%= form.attachment :photos, + multiple: true, + label: t("decidim.admin.static_pages.form.add_image"), + button_label: t("decidim.admin.static_pages.form.add_image"), + button_class: "button button__lg button__transparent-secondary w-full", + button_edit_label: t("decidim.admin.static_pages.form.edit_image"), + help_text: t("decidim.admin.static_pages.form.image_legend") %> +
+ <% end %> + + <% if form.object.control_public_access? %> +
+ <%= form.check_box :allow_public_access %> +
+ <% end %> + +
+
+
diff --git a/app/views/decidim/pages/show.html.erb b/app/views/decidim/pages/show.html.erb new file mode 100644 index 000000000..057dad080 --- /dev/null +++ b/app/views/decidim/pages/show.html.erb @@ -0,0 +1,32 @@ +<% add_decidim_meta_tags( + title: translated_attribute(@page.title), + description: translated_attribute(@page.content) +) %> + +<% +edit_link( + decidim_admin.edit_static_page_path(@page), + :update, + :static_page, + static_page: @page +) +%> + +<% if @topic %> + <%= render partial: "tabbed", locals: { + title: translated_attribute(@topic.title), + description: translated_attribute(@topic.description), + page: @page, + pages: @pages + } %> +<% else %> + <%= render partial: "standalone", locals: { + title: translated_attribute(@page.title), + description: nil, + page: @page + } %> +<% end %> + +
+ <%= cell "decidim/tab_panels", tab_panel_items %> +
diff --git a/config/locales/ca_admin.yml b/config/locales/ca_admin.yml index ff40fabe7..e666abae0 100644 --- a/config/locales/ca_admin.yml +++ b/config/locales/ca_admin.yml @@ -5,3 +5,13 @@ ca: file: Fitxer attachment_collection: weight: Pes + decidim: + admin: + static_pages: + form: + add_documents: Afegeix documents + edit_documents: Edita documents + attachment_legend: Afegeix els documents que vols que apareguin a la pàgina estàtica. Pots afegir tants com vulguis. + add_image: Afegeix imatge + edit_image: Edita imatge + image_legend: Afegeix les imatges que vols que apareguin a la pàgina estàtica. Pots afegir tantes com vulguis. diff --git a/config/locales/en_admin.yml b/config/locales/en_admin.yml index 63f1c3e94..ea59d3658 100644 --- a/config/locales/en_admin.yml +++ b/config/locales/en_admin.yml @@ -1 +1,11 @@ en: + decidim: + admin: + static_pages: + form: + add_documents: Add documents + edit_documents: Edit documents + attachment_legend: Add the documents you want to appear on the static page. You can add as many as you want. + add_image: Add image + edit_image: Edit image + image_legend: Add the images you want to appear on the static page. You can add as many as you want. diff --git a/config/locales/es_admin.yml b/config/locales/es_admin.yml index a07a74d17..7240f1f78 100644 --- a/config/locales/es_admin.yml +++ b/config/locales/es_admin.yml @@ -1 +1,11 @@ es: + decidim: + admin: + static_pages: + form: + add_documents: Añadir documentos + edit_documents: Editar documentos + attachment_legend: Añade los documentos que quieres que aparezcan en la página estática. Puedes añadir tantos como quieras. + add_image: Añadir imagen + edit_image: Editar imagen + image_legend: Añade las imágenes que quieres que aparezcan en la página estática. Puedes añadir tantas como quieras. diff --git a/config/locales/oc_admin.yml b/config/locales/oc_admin.yml index 24cdf759c..5e9893b2f 100644 --- a/config/locales/oc_admin.yml +++ b/config/locales/oc_admin.yml @@ -723,6 +723,12 @@ oc: form: none: Cap slug_help: 'Utilitza rutes parcials, no URL complerts aquí. Accepta lletres, números, guions i barres, i ha de començar amb una lletra. Exemple: %{url}' + add_documents: Afegir documents + edit_documents: Editar documents + attachment_legend: Afegir los documents que vòles que apareisson a la pagina estatica. Pòts n'afegir tants coma vòles. + add_image: Afegir imatge + edit_image: Editar imatge + image_legend: Afegir les imatges que vòles que apareisson a la pagina estatica. Pòts n'afegir tantes coma vòles. index: last_notable_change: Últims canvis notables new: diff --git a/spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb b/spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb new file mode 100644 index 000000000..2dcbb2100 --- /dev/null +++ b/spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Decidim::Admin::StaticPageForm do + subject do + described_class.from_params(attributes).with_context( + current_organization: + ) + end + + let(:current_organization) { create(:organization) } + let(:attachment_params) { nil } + + let(:content) do + { + en: "

Content

", + ca: "

Contingut

", + es: "

Contenido

" + } + end + + let(:attributes) do + { + static_page: { + slug: "help", + title: { en: "Help", ca: "Ajuda", es: "Ayuda" }, + content:, + attachment: attachment_params + } + } + end + + context "when everything is OK" do + it { is_expected.to be_valid } + end + + describe "attachment attributes added by decorator" do + it "responds to documents" do + expect(subject).to respond_to(:documents) + end + + it "responds to add_documents" do + expect(subject).to respond_to(:add_documents) + end + + it "responds to photos" do + expect(subject).to respond_to(:photos) + end + + it "responds to add_photos" do + expect(subject).to respond_to(:add_photos) + end + + it "responds to attachment" do + expect(subject).to respond_to(:attachment) + end + end +end diff --git a/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb b/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb new file mode 100644 index 000000000..be4d9ff62 --- /dev/null +++ b/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "rails_helper" +require "spec_helper" + +describe Decidim::Admin::UpdateStaticPage do + let(:form_klass) { Decidim::Admin::StaticPageForm } + + let(:organization) { create(:organization) } + let(:user) { create(:user, :admin, :confirmed, organization:) } + let(:attachment_params) { nil } + let(:uploaded_photos) { [] } + + let(:form) do + form_klass.from_params( + form_params + ).with_context( + current_organization: organization, + current_user: user + ) + end + + let!(:static_page) { create(:static_page, organization:) } + + describe "call" do + let(:form_params) do + { + static_page: { + id: static_page.id, + slug: static_page.slug, + title: { en: "Updated title" }, + content: { en: "

Updated content

" }, + add_documents: nil, + add_photos: uploaded_photos + } + } + end + + let(:command) do + described_class.new(form, static_page) + end + + describe "when the form is not valid" do + before do + allow(form).to receive(:invalid?).and_return(true) + end + + it "broadcast invalid" do + expect { command.call }.to broadcast(:invalid) + end + + it "does not update the static page" do + expect do + command.call + end.not_to change { static_page.reload.content } + end + end + + describe "when the form is valid" do + it "broadcast ok" do + expect { command.call }.to broadcast(:ok) + end + + it "updates the static page content" do + expect do + command.call + end.to change { static_page.reload.content["en"] } + end + + context "when photos are uploaded" do + let(:attachment_params) do + blob = ActiveStorage::Blob.create_and_upload!( + io: Rack::Test::UploadedFile.new(Decidim::Core::Engine.root.join("db", "seeds", "city.jpeg"), "image/jpeg"), + filename: "city.jpeg", + content_type: "image/jpeg" + ) + { + title: "My attachment", + file: blob.signed_id + } + end + let(:uploaded_photos) { [attachment_params] } + + it "creates an attachment for the static page" do + expect { command.call }.to change(Decidim::Attachment, :count).by(1) + last_attachment = Decidim::Attachment.last + expect(last_attachment.attached_to).to eq(static_page) + end + end + end + end +end diff --git a/spec/system/admin/static_pages/admin_static_page_form_spec.rb b/spec/system/admin/static_pages/admin_static_page_form_spec.rb new file mode 100644 index 000000000..ca211552e --- /dev/null +++ b/spec/system/admin/static_pages/admin_static_page_form_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe "Admin static page form", type: :system do + let(:organization) { create(:organization) } + let!(:user) { create(:user, :admin, :confirmed, organization:) } + let!(:tos_page) { organization.static_pages.find_by!(slug: "terms-of-service") } + let!(:other_page) { create(:static_page, organization:) } + + before do + switch_to_host(organization.host) + login_as user, scope: :user + end + + describe "editing a terms-of-service static page" do + it "does not show the attachment fields" do + visit decidim_admin.edit_static_page_path(tos_page) + + expect(page).to have_no_css("label", text: /documents/i) + expect(page).to have_no_css("label", text: /imatge/i) + end + end + + describe "editing a regular static page" do + it "shows the attachment fields" do + visit decidim_admin.edit_static_page_path(other_page) + + expect(page).to have_css("legend", text: I18n.t("decidim.admin.static_pages.form.attachment_legend")) + end + end +end From 324ab4184d3519568d92809dffd3efacb0a1fe23 Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 7 May 2026 18:03:03 +0200 Subject: [PATCH 2/7] Refactor from rubocop --- .../decidim/admin/static_pages_controller_decorator.rb | 2 +- app/decorators/decidim/admin/static_pages_form_decorator.rb | 2 +- app/decorators/decidim/admin/update_static_page_decorator.rb | 4 ++-- app/decorators/decidim/pages_controller_decorator.rb | 2 +- app/decorators/decidim/static_pages_decorator.rb | 2 +- .../decidim/admin/update_static_page_decorator_spec.rb | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/decorators/decidim/admin/static_pages_controller_decorator.rb b/app/decorators/decidim/admin/static_pages_controller_decorator.rb index 18a1fe509..f4dda36fd 100644 --- a/app/decorators/decidim/admin/static_pages_controller_decorator.rb +++ b/app/decorators/decidim/admin/static_pages_controller_decorator.rb @@ -8,4 +8,4 @@ def self.decorate end end -::Decidim::Admin::StaticPagesControllerDecorator.decorate \ No newline at end of file +Decidim::Admin::StaticPagesControllerDecorator.decorate diff --git a/app/decorators/decidim/admin/static_pages_form_decorator.rb b/app/decorators/decidim/admin/static_pages_form_decorator.rb index 406583c9d..f5ef0c742 100644 --- a/app/decorators/decidim/admin/static_pages_form_decorator.rb +++ b/app/decorators/decidim/admin/static_pages_form_decorator.rb @@ -13,4 +13,4 @@ def self.decorate end end -::Decidim::Admin::StaticPagesFormDecorator.decorate \ No newline at end of file +Decidim::Admin::StaticPagesFormDecorator.decorate diff --git a/app/decorators/decidim/admin/update_static_page_decorator.rb b/app/decorators/decidim/admin/update_static_page_decorator.rb index 8353d4dd0..c748a952e 100644 --- a/app/decorators/decidim/admin/update_static_page_decorator.rb +++ b/app/decorators/decidim/admin/update_static_page_decorator.rb @@ -1,4 +1,4 @@ -#frozen_string_literal: true +# frozen_string_literal: true module Decidim::Admin::UpdateStaticPageDecorator def self.decorate @@ -53,4 +53,4 @@ def update_static_page end end -::Decidim::Admin::UpdateStaticPageDecorator.decorate \ No newline at end of file +Decidim::Admin::UpdateStaticPageDecorator.decorate diff --git a/app/decorators/decidim/pages_controller_decorator.rb b/app/decorators/decidim/pages_controller_decorator.rb index 7d4ec3f0a..8d40b8b9b 100644 --- a/app/decorators/decidim/pages_controller_decorator.rb +++ b/app/decorators/decidim/pages_controller_decorator.rb @@ -16,4 +16,4 @@ def tab_panel_items end end -::Decidim::PagesControllerDecorator.decorate +Decidim::PagesControllerDecorator.decorate diff --git a/app/decorators/decidim/static_pages_decorator.rb b/app/decorators/decidim/static_pages_decorator.rb index f318a6a9d..08e7458f3 100644 --- a/app/decorators/decidim/static_pages_decorator.rb +++ b/app/decorators/decidim/static_pages_decorator.rb @@ -8,4 +8,4 @@ def self.decorate end end -::Decidim::StaticPagesDecorator.decorate \ No newline at end of file +Decidim::StaticPagesDecorator.decorate diff --git a/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb b/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb index be4d9ff62..5241140b6 100644 --- a/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb +++ b/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb @@ -52,7 +52,7 @@ it "does not update the static page" do expect do command.call - end.not_to change { static_page.reload.content } + end.not_to(change { static_page.reload.content }) end end @@ -64,7 +64,7 @@ it "updates the static page content" do expect do command.call - end.to change { static_page.reload.content["en"] } + end.to(change { static_page.reload.content["en"] }) end context "when photos are uploaded" do From df9ec7dc8974436d351ff1aa80b490a89427fe27 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 8 May 2026 14:09:35 +0200 Subject: [PATCH 3/7] Changes in public view and form --- .../admin/update_static_page_decorator.rb | 9 ++++ app/views/decidim/pages/_standalone.html.erb | 25 ++++++++++ app/views/decidim/pages/_tabbed.html.erb | 47 +++++++++++++++++++ app/views/decidim/pages/show.html.erb | 32 ------------- .../update_static_page_decorator_spec.rb | 20 ++++++++ 5 files changed, 101 insertions(+), 32 deletions(-) create mode 100644 app/views/decidim/pages/_standalone.html.erb create mode 100644 app/views/decidim/pages/_tabbed.html.erb delete mode 100644 app/views/decidim/pages/show.html.erb diff --git a/app/decorators/decidim/admin/update_static_page_decorator.rb b/app/decorators/decidim/admin/update_static_page_decorator.rb index c748a952e..2ccc35b2d 100644 --- a/app/decorators/decidim/admin/update_static_page_decorator.rb +++ b/app/decorators/decidim/admin/update_static_page_decorator.rb @@ -42,8 +42,17 @@ def update_static_page Decidim.traceability.update!( page, form.current_user, + title: form.title, + slug: form.slug, + weight: form.weight, + topic: form.topic, + allow_public_access: form.allow_public_access, content: parsed_content ) + + return unless form.changed_notably + + Decidim::Admin::UpdateOrganizationTosVersion.call(form.organization, page, form) end private diff --git a/app/views/decidim/pages/_standalone.html.erb b/app/views/decidim/pages/_standalone.html.erb new file mode 100644 index 000000000..aa8f35770 --- /dev/null +++ b/app/views/decidim/pages/_standalone.html.erb @@ -0,0 +1,25 @@ +<%= render layout:"layouts/decidim/shared/layout_center" do %> + +
+

+ <%= title %> +

+
+ +
+ <%= cell "decidim/tos_page", :announcement %> + +
+ <% page_content_blocks.each do |content_block| %> + <%= cell content_block.manifest.cell, content_block %> + <% end %> + + <%= decidim_sanitize_editor_admin translated_attribute page.content %> +
+ + <%= cell "decidim/tos_page", :form %> + + <%= cell "decidim/tab_panels", tab_panel_items %> +
+ +<% end %> diff --git a/app/views/decidim/pages/_tabbed.html.erb b/app/views/decidim/pages/_tabbed.html.erb new file mode 100644 index 000000000..23287fd69 --- /dev/null +++ b/app/views/decidim/pages/_tabbed.html.erb @@ -0,0 +1,47 @@ +<%# NOTE: the structure of this file is the same as: decidim-core/app/views/layouts/decidim/shared/_layout_user_profile.html.erb %> +<%= render layout:"layouts/decidim/shared/layout_center", locals: { columns: 10 } do %> +
+

+ <%= title %> +

+

+ <%= description %> +

+
+ +
+ + +
+ <%= cell "decidim/tos_page", :announcement %> + +

<%= translated_attribute page.title %>

+ +
+ <% page_content_blocks.each do |content_block| %> + <%= cell content_block.manifest.cell, content_block %> + <% end %> + + <%= decidim_sanitize_editor_admin translated_attribute page.content %> +
+ <%= cell "decidim/tos_page", :form %> + + <%= cell "decidim/tab_panels", tab_panel_items %> +
+
+<% end %> diff --git a/app/views/decidim/pages/show.html.erb b/app/views/decidim/pages/show.html.erb deleted file mode 100644 index 057dad080..000000000 --- a/app/views/decidim/pages/show.html.erb +++ /dev/null @@ -1,32 +0,0 @@ -<% add_decidim_meta_tags( - title: translated_attribute(@page.title), - description: translated_attribute(@page.content) -) %> - -<% -edit_link( - decidim_admin.edit_static_page_path(@page), - :update, - :static_page, - static_page: @page -) -%> - -<% if @topic %> - <%= render partial: "tabbed", locals: { - title: translated_attribute(@topic.title), - description: translated_attribute(@topic.description), - page: @page, - pages: @pages - } %> -<% else %> - <%= render partial: "standalone", locals: { - title: translated_attribute(@page.title), - description: nil, - page: @page - } %> -<% end %> - -
- <%= cell "decidim/tab_panels", tab_panel_items %> -
diff --git a/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb b/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb index 5241140b6..bc61ec6c4 100644 --- a/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb +++ b/spec/decorators/decidim/admin/update_static_page_decorator_spec.rb @@ -30,6 +30,8 @@ slug: static_page.slug, title: { en: "Updated title" }, content: { en: "

Updated content

" }, + weight: 5, + allow_public_access: true, add_documents: nil, add_photos: uploaded_photos } @@ -67,6 +69,24 @@ end.to(change { static_page.reload.content["en"] }) end + it "updates the static page title" do + expect do + command.call + end.to(change { static_page.reload.title["en"] }.to("Updated title")) + end + + it "updates the static page weight" do + expect do + command.call + end.to(change { static_page.reload.weight }.to(5)) + end + + it "updates the static page allow_public_access" do + expect do + command.call + end.to(change { static_page.reload.allow_public_access }.to(true)) + end + context "when photos are uploaded" do let(:attachment_params) do blob = ActiveStorage::Blob.create_and_upload!( From c59c8be87b32883009553094cd04871e9bbc2beb Mon Sep 17 00:00:00 2001 From: Ruben Date: Mon, 11 May 2026 09:31:24 +0200 Subject: [PATCH 4/7] Fix lint --- .../admin/update_static_page_decorator.rb | 96 ++++++++++--------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/app/decorators/decidim/admin/update_static_page_decorator.rb b/app/decorators/decidim/admin/update_static_page_decorator.rb index 2ccc35b2d..7b962c191 100644 --- a/app/decorators/decidim/admin/update_static_page_decorator.rb +++ b/app/decorators/decidim/admin/update_static_page_decorator.rb @@ -1,63 +1,67 @@ # frozen_string_literal: true module Decidim::Admin::UpdateStaticPageDecorator - def self.decorate - Decidim::Admin::UpdateStaticPage.class_eval do - include ::Decidim::MultipleAttachmentsMethods - include ::Decidim::GalleryMethods + module Methods + def call + return broadcast(:invalid) if @form.invalid? - def initialize(form, page) - @form = form - @page = page - @attached_to = page + if process_attachments? + build_attachments + return broadcast(:invalid) if attachments_invalid? end - def call - return broadcast(:invalid) if @form.invalid? - - if process_attachments? - build_attachments - return broadcast(:invalid) if attachments_invalid? - end + if process_gallery? + build_gallery + return broadcast(:invalid) if gallery_invalid? + end - if process_gallery? - build_gallery - return broadcast(:invalid) if gallery_invalid? - end + transaction do + update_static_page + document_cleanup! + photo_cleanup! + create_attachments if process_attachments? + create_gallery if process_gallery? + broadcast(:ok) + end + end - transaction do - update_static_page - document_cleanup! - photo_cleanup! - create_attachments if process_attachments? - create_gallery if process_gallery? - broadcast(:ok) - end + def update_static_page + parsed_content = form.content.transform_values do |value| + Decidim::ContentProcessor.parse(value.to_s, current_organization: form.current_organization).rewrite end + Decidim.traceability.update!( + page, + form.current_user, + title: form.title, + slug: form.slug, + weight: form.weight, + topic: form.topic, + allow_public_access: form.allow_public_access, + content: parsed_content + ) + + return unless form.changed_notably - def update_static_page - parsed_content = form.content.transform_values do |value| - Decidim::ContentProcessor.parse(value.to_s, current_organization: form.current_organization).rewrite - end - Decidim.traceability.update!( - page, - form.current_user, - title: form.title, - slug: form.slug, - weight: form.weight, - topic: form.topic, - allow_public_access: form.allow_public_access, - content: parsed_content - ) + Decidim::Admin::UpdateOrganizationTosVersion.call(form.organization, page, form) + end - return unless form.changed_notably + private - Decidim::Admin::UpdateOrganizationTosVersion.call(form.organization, page, form) - end + attr_reader :form, :page, :current_user + end - private + def self.decorate + Decidim::Admin::UpdateStaticPage.class_eval do + include ::Decidim::MultipleAttachmentsMethods + include ::Decidim::GalleryMethods + + def initialize(form, page) + @form = form + @page = page + @attached_to = page + end - attr_reader :form, :page, :current_user + include Methods end end end From ee56af4d5fe359a7d025e6c16efb7488541c1da9 Mon Sep 17 00:00:00 2001 From: Ruben Date: Mon, 11 May 2026 12:09:14 +0200 Subject: [PATCH 5/7] Fix lint static page spec --- .../decidim/admin/static_pages_form_decorator_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb b/spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb index 2dcbb2100..2b178db93 100644 --- a/spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb +++ b/spec/decorators/decidim/admin/static_pages_form_decorator_spec.rb @@ -2,9 +2,9 @@ require "rails_helper" -describe Decidim::Admin::StaticPageForm do +describe Decidim::Admin::StaticPagesFormDecorator do subject do - described_class.from_params(attributes).with_context( + Decidim::Admin::StaticPageForm.from_params(attributes).with_context( current_organization: ) end From a678ad5a661f41555302a552fc8c37b8e9d5b63c Mon Sep 17 00:00:00 2001 From: Ruben Date: Tue, 19 May 2026 10:06:17 +0200 Subject: [PATCH 6/7] Refactor decorator and documentation --- README.md | 1 + .../admin/update_static_page_decorator.rb | 96 +++++++++---------- docs/HOW_TO_UPGRADE.md | 12 +++ 3 files changed, 59 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 850e2f190..666a9f019 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This is the open-source repository for "participa", based on [Decidim](https://g - `Decidim::Regulations`, adds Regulations, a new type of Participatory process. - `Decidim::Admin::Extended`, customize admin menu adding custom configurations. - `Decidim::Recaptcha`, use recaptcha instead invisible captcha. +- Static pages admin: adds attachment and gallery support to `Decidim::Admin::StaticPage` (form, command and controller helper) via decorators. ## Deploying the app diff --git a/app/decorators/decidim/admin/update_static_page_decorator.rb b/app/decorators/decidim/admin/update_static_page_decorator.rb index 7b962c191..84ddad240 100644 --- a/app/decorators/decidim/admin/update_static_page_decorator.rb +++ b/app/decorators/decidim/admin/update_static_page_decorator.rb @@ -1,55 +1,6 @@ # frozen_string_literal: true module Decidim::Admin::UpdateStaticPageDecorator - module Methods - def call - return broadcast(:invalid) if @form.invalid? - - if process_attachments? - build_attachments - return broadcast(:invalid) if attachments_invalid? - end - - if process_gallery? - build_gallery - return broadcast(:invalid) if gallery_invalid? - end - - transaction do - update_static_page - document_cleanup! - photo_cleanup! - create_attachments if process_attachments? - create_gallery if process_gallery? - broadcast(:ok) - end - end - - def update_static_page - parsed_content = form.content.transform_values do |value| - Decidim::ContentProcessor.parse(value.to_s, current_organization: form.current_organization).rewrite - end - Decidim.traceability.update!( - page, - form.current_user, - title: form.title, - slug: form.slug, - weight: form.weight, - topic: form.topic, - allow_public_access: form.allow_public_access, - content: parsed_content - ) - - return unless form.changed_notably - - Decidim::Admin::UpdateOrganizationTosVersion.call(form.organization, page, form) - end - - private - - attr_reader :form, :page, :current_user - end - def self.decorate Decidim::Admin::UpdateStaticPage.class_eval do include ::Decidim::MultipleAttachmentsMethods @@ -61,7 +12,52 @@ def initialize(form, page) @attached_to = page end - include Methods + def call + return broadcast(:invalid) if @form.invalid? + + if process_attachments? + build_attachments + return broadcast(:invalid) if attachments_invalid? + end + + if process_gallery? + build_gallery + return broadcast(:invalid) if gallery_invalid? + end + + transaction do + update_static_page + document_cleanup! + photo_cleanup! + create_attachments if process_attachments? + create_gallery if process_gallery? + broadcast(:ok) + end + end + + def update_static_page + parsed_content = form.content.transform_values do |value| + Decidim::ContentProcessor.parse(value.to_s, current_organization: form.current_organization).rewrite + end + Decidim.traceability.update!( + page, + form.current_user, + title: form.title, + slug: form.slug, + weight: form.weight, + topic: form.topic, + allow_public_access: form.allow_public_access, + content: parsed_content + ) + + return unless form.changed_notably + + Decidim::Admin::UpdateOrganizationTosVersion.call(form.organization, page, form) + end + + private + + attr_reader :form, :page end end end diff --git a/docs/HOW_TO_UPGRADE.md b/docs/HOW_TO_UPGRADE.md index e8ec6feb3..3889da260 100644 --- a/docs/HOW_TO_UPGRADE.md +++ b/docs/HOW_TO_UPGRADE.md @@ -190,6 +190,18 @@ These are custom modules and this is what you have to keep in mind when updating * Override to export proposal emails and names from authors * probably removable from Decidim v0.28 (remember remove test too) + * `app/decorators/decidim/admin/static_pages_form_decorator.rb` + * Adds attachment and gallery attributes (`documents`, `photos`) to `Decidim::Admin::StaticPageForm` + * Includes `Decidim::AttachmentAttributes` and `Decidim::HasUploadValidations` + + * `app/decorators/decidim/admin/update_static_page_decorator.rb` + * Overrides `call` to process attachments and gallery before/after updating the page + * Overrides `initialize` to set `@attached_to` required by attachment methods + * Includes `Decidim::MultipleAttachmentsMethods` and `Decidim::GalleryMethods` + + * `app/decorators/decidim/admin/static_pages_controller_decorator.rb` + * Exposes `tab_panel_items` as a helper method in `Decidim::Admin::StaticPagesController` + * `lib/decidim/has_private_users.rb` * Override to allow private space users to acces public view * Could not use a decorator so the whole class has been copied From 93c6b9ed00e3fb5582186d9fa3ae8db91bbe1312 Mon Sep 17 00:00:00 2001 From: Ruben Date: Tue, 19 May 2026 10:25:08 +0200 Subject: [PATCH 7/7] Ignore rubocop lint --- app/decorators/decidim/admin/update_static_page_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/decorators/decidim/admin/update_static_page_decorator.rb b/app/decorators/decidim/admin/update_static_page_decorator.rb index 84ddad240..63a0d717b 100644 --- a/app/decorators/decidim/admin/update_static_page_decorator.rb +++ b/app/decorators/decidim/admin/update_static_page_decorator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Decidim::Admin::UpdateStaticPageDecorator - def self.decorate + def self.decorate # rubocop:disable Metrics/CyclomaticComplexity Decidim::Admin::UpdateStaticPage.class_eval do include ::Decidim::MultipleAttachmentsMethods include ::Decidim::GalleryMethods