Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
c98c244
Improve components and spaces sharing with administrable tokens (#13221)
microstudi Oct 16, 2024
1c44e5c
remove non valid methods
microstudi Jun 3, 2025
8f116e8
Remove flaky test in share tokens preview spec (#13546)
microstudi Oct 16, 2024
a298e9b
Merge branch 'release/0.29-stable' into 0.29-backports
microstudi Jun 6, 2025
b13950d
Merge branch 'release/0.29-stable' into 0.29-backports
microstudi Jul 30, 2025
3a7ea88
Waiting list for the meeting (#14492)
antopalidi Apr 16, 2025
06f9a25
Customizing meeting reminders (#14470)
antopalidi May 14, 2025
b2876ec
Fix meetings duplication feature (#14943)
greenwoodt Jul 25, 2025
4fb8959
Allow meeting addresses to be unset/TBD (#14389)
antopalidi Apr 3, 2025
5f58be7
linting
microstudi Jul 31, 2025
3781a7e
fix conflic resolution
microstudi Jul 31, 2025
23c2e36
fix conflic resolution
microstudi Jul 31, 2025
38cacf0
fix controller spec
microstudi Jul 31, 2025
126c51b
add translations
microstudi Jul 31, 2025
d6d17c5
New Crowdin updates (#14965)
decidim-bot Aug 4, 2025
10c775d
Fixed proposal creation for admin when "Participants can create propo…
decidim-bot Aug 12, 2025
fea0bf8
Backport 'Upgrade Chrome and ChromeDriver to 139.0.7258.66' to v0.29 …
alecslupu Aug 20, 2025
fee1e7b
Fix specs running on local development environments (#15069)
decidim-bot Aug 20, 2025
101fdf2
New Crowdin updates (#15042)
decidim-bot Aug 21, 2025
5c58e8d
Change minimun_characters to minimum_characters (#15078)
decidim-bot Aug 25, 2025
45407fb
Fix aria-current on active filter in search page (#15110)
decidim-bot Aug 28, 2025
8683aee
Backport 'Fix editor mention selection range when selecting item with…
decidim-bot Aug 29, 2025
b771f97
Backport 'Error saving survey with empty mandatory fields' to v0.29 (…
alecslupu Sep 1, 2025
9257b23
Backport 'Fix callout/flash message announcement to the screen reader…
alecslupu Sep 1, 2025
61fcaef
Previewing questionnaires as a process admin (#15103)
alecslupu Sep 1, 2025
e4508c9
Fix accessibility in header (#15102)
alecslupu Sep 1, 2025
f3bf19e
Fix accessibility in footer (#15113)
alecslupu Sep 2, 2025
a25e366
Remove Platoniq/decidim-install references (#15137)
decidim-bot Sep 4, 2025
a5d9c1e
Define the generated app's sidekiq version based on the redis version…
alecslupu Sep 5, 2025
d46c9fc
Fixed formatting error in documentation (#15153)
decidim-bot Sep 8, 2025
43d6d61
Fix proposal cards render alt-text when PDF attached (#15150)
decidim-bot Sep 9, 2025
f09912a
Fix accessibility in breadcrumb_menu by replacing div and span by p (…
decidim-bot Sep 10, 2025
a83bc37
Unsubscribed page redesign (#15166)
decidim-bot Sep 16, 2025
48df9a7
Backport 'Refactor amendment permissions' to v0.29 (#15169)
alecslupu Sep 16, 2025
0dd3bd4
Remove the word "new" in front of GDPR from the newsletter opt in mai…
decidim-bot Sep 19, 2025
7ba58b0
Fix 'Skip to main content' button visualization (#15205)
decidim-bot Sep 20, 2025
c0ecd14
Backport 'Fix size of the search button and contrast of the search fo…
decidim-bot Sep 22, 2025
4a00d28
New Crowdin updates (#15080)
decidim-bot Sep 22, 2025
1815123
Update charlock_holmes to 0.7.9 (#15208)
alecslupu Sep 22, 2025
2b1635a
Bump to v0.29.5 version (#15210)
alecslupu Sep 23, 2025
d0e6eea
Fix some accessibility violations in design's a11y page (#15232)
decidim-bot Sep 25, 2025
0c3320a
Prevent reload on filters when the 'Skip to' links are clicked (#15243)
decidim-bot Sep 27, 2025
597af4e
Fix translation missing for duplicate landing blocks (#15250)
alecslupu Sep 29, 2025
933c968
Backport 'Fix drag and drop sorting for questionnaires' to v0.29 (#15…
alecslupu Sep 30, 2025
f4bd48a
Merge branch 'release/0.29-stable' into 0.29-backports
microstudi Oct 1, 2025
4b7a05e
Fix incorrectly named initialize methods in (#15294)
decidim-bot Oct 4, 2025
d40785d
Backport 'Fix officialization view for users without nickname' to v0.…
alecslupu Oct 6, 2025
2050742
Fix focus with tab navigation on small avatar images (#15304)
decidim-bot Oct 7, 2025
1177a5b
Merge branch 'release/0.29-stable' into 0.29-backports
microstudi Oct 9, 2025
e83cefb
save geocoding data when present
microstudi Oct 7, 2025
c3b437c
Fix adjacent links in the related documents/attachments links (#15332)
decidim-bot Oct 10, 2025
47fa94e
Fix w3c validator CI pipeline (NuValidator json errors) (#15344)
decidim-bot Oct 13, 2025
17cc688
Allow password validator to use the fallback translation (#15335)
decidim-bot Oct 13, 2025
496f37d
Fix error unexpected value at params[:host] (#15338)
decidim-bot Oct 13, 2025
a41e1f0
Merge branch 'release/0.29-stable' into 0.29-backports
microstudi Oct 15, 2025
7928d98
fix params new map
microstudi Oct 15, 2025
ab83d65
Fix displaying the map for new proposals for v0.29 (#15363)
microstudi Oct 16, 2025
8759dbd
Make the WYSIWYG editor vertical resizable (#15372)
decidim-bot Oct 16, 2025
549980c
Fix deleted comments on public profile (#15378)
decidim-bot Oct 17, 2025
e8311cc
Show badges next to user nicknames (#15351)
alecslupu Oct 17, 2025
aef56b0
Revert "Fix w3c validator CI pipeline (NuValidator json errors) (#153…
alecslupu Oct 17, 2025
c6941de
Fix drag_and_drop on mobile without scrolling (#15389)
decidim-bot Oct 17, 2025
d9a8c5a
Add format validation for name and nickname in UserGroupForm (#15368)…
alecslupu Oct 17, 2025
3502e27
Add missing translation key for enable machine translation (#15386)
decidim-bot Oct 17, 2025
581806a
New Crowdin updates (#15216)
decidim-bot Oct 27, 2025
aceff16
Prepare 0.29.6 release (#15431)
alecslupu Oct 28, 2025
181cfc4
Merge branch 'release/0.29-stable' into 0.29-backports
microstudi Oct 29, 2025
bf65581
Revert "Backport 'Fix drag and drop sorting for questionnaires' to v0…
alecslupu Nov 8, 2025
1526090
Lock graphql-ws to stable version (#15476)
decidim-bot Nov 10, 2025
f8629b7
Add local app/views to Cell::ViewModel view paths (#15482)
decidim-bot Nov 11, 2025
69a8554
Prevent shakapacker related flakys in CI (#15515)
alecslupu Nov 13, 2025
da2d7db
Order posts by publication date (#15523)
decidim-bot Nov 13, 2025
5932d4b
Fix markers displayed in meeting page (#15518)
decidim-bot Nov 13, 2025
ca0c8e1
Use same card for Meeting always (#15547)
decidim-bot Nov 15, 2025
fee05ca
Fix links in footer when using the organizations' description (#15544)
decidim-bot Nov 15, 2025
e606845
Add missing i18n string in Conference program page (#15555)
decidim-bot Nov 17, 2025
809adb8
Fix strong tags color in footer organization description (#15561)
decidim-bot Nov 17, 2025
b6c926f
New Crowdin updates (#15439)
decidim-bot Nov 19, 2025
c5caa44
Prepare 0.29.7 release (#15574)
alecslupu Nov 20, 2025
c8535da
New Crowdin updates (#15593)
decidim-bot Dec 9, 2025
5bee4ce
Merge branch 'release/0.29-stable' into 0.29-backports
microstudi Mar 10, 2026
baceaec
Vote on proposal from listing page (#13883)
alecslupu Feb 6, 2025
13fd72f
fix action override
microstudi Mar 11, 2026
2881076
fix remaining votes count method
microstudi Mar 19, 2026
03e5e7c
fix ajax update of vote buttons in proposals
microstudi Mar 24, 2026
6a37a64
apply #16424
davidbeig Mar 24, 2026
a3e489f
fix cache votes
microstudi Apr 2, 2026
2289c1b
add current user into cache keys
microstudi Apr 15, 2026
3789939
add current user into cache keys
microstudi Apr 15, 2026
9a4d36f
remove cache altogether. Resend all proposals on unvoting
microstudi Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

require "spec_helper"

describe "Preview accountability with share token" do
describe "preview accountability with a share token" do
let(:manifest_name) { "accountability" }

include_context "with a component"
it_behaves_like "preview component with share_token"
it_behaves_like "preview component with a share_token"
end
39 changes: 39 additions & 0 deletions decidim-admin/app/commands/decidim/admin/create_share_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module Decidim
module Admin
# A command with all the business logic to create a share token.
# This command is called from the controller.
class CreateShareToken < Decidim::Commands::CreateResource
fetch_form_attributes :token, :expires_at, :registered_only, :organization, :user, :token_for

protected

def resource_class = Decidim::ShareToken

def extra_params
{
participatory_space: {
title: participatory_space&.title
},
resource: {
title: component&.name
}
}
end

def participatory_space
return form.token_for if form.token_for.try(:manifest).is_a?(Decidim::ParticipatorySpaceManifest)
return current_participatory_space if respond_to?(:current_participatory_space)

component&.participatory_space
end

def component
return form.token_for if form.token_for.is_a?(Decidim::Component)

form.token_for.try(:component)
end
end
end
end
22 changes: 22 additions & 0 deletions decidim-admin/app/commands/decidim/admin/destroy_share_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Decidim
module Admin
# A command with all the business logic to destroy a share token.
# This command is called from the controller.
class DestroyShareToken < Decidim::Commands::DestroyResource
delegate :participatory_space, :component, to: :resource

def extra_params
{
participatory_space: {
title: participatory_space&.title
},
resource: {
title: component&.name
}
}
end
end
end
end
24 changes: 24 additions & 0 deletions decidim-admin/app/commands/decidim/admin/update_share_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Decidim
module Admin
# A command with all the business logic to update a share token.
# This command is called from the controller.
class UpdateShareToken < Decidim::Commands::UpdateResource
fetch_form_attributes :expires_at, :registered_only

delegate :participatory_space, :component, to: :resource

def extra_params
{
participatory_space: {
title: participatory_space&.title
},
resource: {
title: component&.name
}
}
end
end
end
end
116 changes: 109 additions & 7 deletions decidim-admin/app/controllers/decidim/admin/share_tokens_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,66 @@

module Decidim
module Admin
# This is an abstract controller allows sharing unpublished things.
# Final implementation must inherit from this controller and implement the `resource` method.
class ShareTokensController < Decidim::Admin::ApplicationController
include Decidim::Admin::Filterable

helper_method :current_token, :resource, :resource_title, :share_tokens_path

def index
enforce_permission_to :read, :share_tokens
@share_tokens = filtered_collection
end

def new
enforce_permission_to :create, :share_tokens
@form = form(ShareTokenForm).instance
end

def create
enforce_permission_to :create, :share_tokens
@form = form(ShareTokenForm).from_params(params, resource:)

CreateShareToken.call(@form) do
on(:ok) do
flash[:notice] = I18n.t("share_tokens.create.success", scope: "decidim.admin")
redirect_to share_tokens_path
end

on(:invalid) do
flash.now[:alert] = I18n.t("share_tokens.create.invalid", scope: "decidim.admin")
render action: "new"
end
end
end

def edit
enforce_permission_to(:update, :share_tokens, share_token: current_token)
@form = form(ShareTokenForm).from_model(current_token)
end

def update
enforce_permission_to(:update, :share_tokens, share_token: current_token)
@form = form(ShareTokenForm).from_params(params, resource:)

UpdateShareToken.call(@form, current_token) do
on(:ok) do
flash[:notice] = I18n.t("share_tokens.update.success", scope: "decidim.admin")
redirect_to share_tokens_path
end

on(:invalid) do
flash.now[:alert] = I18n.t("share_tokens.update.error", scope: "decidim.admin")
render :edit
end
end
end

def destroy
enforce_permission_to(:destroy, :share_token, share_token:)
enforce_permission_to(:destroy, :share_tokens, share_token: current_token)

Decidim::Commands::DestroyResource.call(share_token, current_user) do
DestroyShareToken.call(current_token, current_user) do
on(:ok) do
flash[:notice] = I18n.t("share_tokens.destroy.success", scope: "decidim.admin")
end
Expand All @@ -15,15 +70,62 @@ def destroy
end
end

redirect_back(fallback_location: root_path)
redirect_to share_tokens_path
end

private

def share_token
@share_token ||= Decidim::ShareToken.where(
organization: current_organization
).find(params[:id])
# override this method in the destination controller to specify the resource associated with the shared token (ie: a component)
def resource
raise NotImplementedError
end

# Override also this method if resource does not respond to a translatable name or title
def resource_title
translated_attribute(resource.try(:name) || resource.title)
end

# sets the prefix for the route helper methods (this may vary depending on the resource type)
# This setup works fine for participatory spaces and components, override if needed
def route_name
@route_name ||= "#{resource.manifest.route_name}_"
end

def route_proxy
@route_proxy ||= EngineRouter.admin_proxy(resource.try(:participatory_space) || resource)
end

# returns the proper path for managing a share token according to the resource
# this works fine for components and participatory spaces, override if needed
def share_tokens_path(method = :index, options = {})
args = resource.is_a?(Decidim::Component) ? [resource, options] : [options]

case method
when :index, :create
route_proxy.send("#{route_name}share_tokens_path", *args)
when :new
route_proxy.send("new_#{route_name}share_token_path", *args)
when :update, :destroy
route_proxy.send("#{route_name}share_token_path", *args)
when :edit
route_proxy.send("edit_#{route_name}share_token_path", *args)
end
end

def base_query
collection
end

def collection
@collection ||= Decidim::ShareToken.where(organization: current_organization, token_for: resource)
end

def filters
[]
end

def current_token
@current_token ||= collection.find(params[:id])
end
end
end
Expand Down
55 changes: 55 additions & 0 deletions decidim-admin/app/forms/decidim/admin/share_token_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

module Decidim
module Admin
class ShareTokenForm < Decidim::Form
mimic :share_token

attribute :token, String
attribute :automatic_token, Boolean, default: true
attribute :expires_at, Decidim::Attributes::TimeWithZone
attribute :no_expiration, Boolean, default: true
attribute :registered_only, Boolean, default: false

validates :token, presence: true, if: ->(form) { form.automatic_token.blank? }
validate :token_uniqueness, if: ->(form) { form.automatic_token.blank? }

validates_format_of :token, with: /\A[a-zA-Z0-9_-]+\z/, allow_blank: true
validates :expires_at, presence: true, if: ->(form) { form.no_expiration.blank? }

def map_model(model)
self.no_expiration = model.expires_at.blank?
end

def token
super.strip.upcase.gsub(/\s+/, "-") if super.present?
end

def expires_at
return nil if no_expiration.present?

super
end

def token_for
context[:resource]
end

def organization
context[:current_organization]
end

def user
context[:current_user]
end

private

def token_uniqueness
return unless Decidim::ShareToken.where(organization:, token_for:, token:).where.not(id:).any?

errors.add(:token, :taken)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
<span class="action-space icon"></span>
<% end %>

<% if allowed_to? :share, :component, component: component %>
<%= icon_link_to "share-line", url_for(action: :share, id: component, controller: "components"), t("actions.share", scope: "decidim.admin"), target: :blank, class: "action-icon--share" %>
<% if component.manifest.admin_engine && allowed_to?(:share, :component, component: component) %>
<%= icon_link_to "share-line", component_share_tokens_path(component_id: component), t("actions.share_tokens", scope: "decidim.admin"), class: "action-icon--share" %>
<% else %>
<span class="action-space icon"></span>
<% end %>

<% if allowed_to? :update, :component, component: component %>
<%= icon_link_to "settings-4-line", url_for(action: :edit, id: component, controller: "components"), t("actions.configure", scope: "decidim.admin"), class: "action-icon--configure" %>
<% else %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,4 @@
</div>
</div>
<% end %>
<% if component && component.persisted? && !component.published? %>
<div class="card" data-component="accordion" id="accordion-share_tokens">
<div id="panel-share_tokens" class="card-section">
<div class="row column">
<%= render partial: "decidim/admin/share_tokens/share_tokens", locals: { share_tokens: form.object.share_tokens } %>
</div>
</div>
</div>
<% end %>
</div>
52 changes: 52 additions & 0 deletions decidim-admin/app/views/decidim/admin/share_tokens/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div class="row column">
<label><%= t("share_tokens.form.expires_at", scope: "decidim.admin") %></label>
<%= form.collection_radio_buttons :no_expiration, [[true, t("share_tokens.form.never_expire", scope: "decidim.admin")], [false, t("share_tokens.form.custom", scope: "decidim.admin")]], :first, :last do |b| %>
<div>
<%= b.radio_button %>
<%= b.label %>
</div>
<% end %>
<div id="expires_at_field_wrapper" class="hidden mt-4 row column">
<%= form.datetime_field :expires_at, label: t("share_tokens.form.custom_expiration", scope: "decidim.admin") %>
</div>
</div>

<div class="row column">
<label><%= t("share_tokens.form.registered_only", scope: "decidim.admin") %></label>
<%= form.collection_radio_buttons :registered_only, [
[t("share_tokens.form.true", scope: "decidim.admin"), true],
[t("share_tokens.form.false", scope: "decidim.admin"), false]
], :last, :first do |b| %>
<div>
<%= b.label do %>
<%= b.radio_button %>
<%= b.text %>
<% end %>
</div>
<% end %>
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
const expiresButton = document.querySelector("input[name='share_token[no_expiration]'][value='false']");
const expiresAtRadioButtons = document.querySelectorAll("input[name='share_token[no_expiration]']");
const expiresAtWrapper = document.getElementById("expires_at_field_wrapper");
const expiresAtInput = document.querySelector("input[name='share_token[expires_at]']");

const toggleExpiresAtField = () => {
if (expiresButton.checked) {
expiresAtWrapper.classList.remove("hidden");
} else {
expiresAtWrapper.classList.add("hidden");
expiresAtInput.value = "";
expiresAtInput.removeAttribute("required");
}
};

expiresAtRadioButtons.forEach(expiresAtRadioButton => {
expiresAtRadioButton.addEventListener("change", toggleExpiresAtField);
});

toggleExpiresAtField();
});
</script>
Loading