diff --git a/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb b/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb
index 2716b347e418c..35e299f904d29 100644
--- a/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb
+++ b/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb
@@ -4,9 +4,14 @@ module Decidim
module Budgets
# This cell renders the budget item list in the budgets list
class BudgetListItemCell < BaseCell
+ include Decidim::SanitizeHelper
+ include Decidim::ApplicationHelper
+ include Decidim::Budgets::ProjectsHelper
+
delegate :voting_finished?, to: :controller
+ delegate :highlighted, to: :current_workflow
- property :title
+ property :title, :description, :total_budget
alias budget model
private
@@ -17,6 +22,7 @@ def card_class
list << "card--list__data-added" if voted?
list << "card--list__data-progress" if progress?
end
+ list << "budget--highlighted" if highlighted?
end.join(" ")
end
@@ -32,9 +38,31 @@ def progress?
current_user && status == :progress
end
+ def highlighted?
+ highlighted.include?(budget)
+ end
+
def status
@status ||= current_workflow.status(budget)
end
+
+ def button_class
+ "hollow" if voted? || !highlighted?
+ end
+
+ def button_text
+ key = if current_workflow.vote_allowed?(budget) && !voted?
+ progress? ? :progress : :vote
+ else
+ :show
+ end
+
+ t(key, scope: i18n_scope)
+ end
+
+ def i18n_scope
+ "decidim.budgets.budgets_list"
+ end
end
end
end
diff --git a/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb b/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb
index ab05907a4801f..244e1909c5dfd 100644
--- a/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb
+++ b/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb
@@ -5,7 +5,6 @@ module Budgets
# This cell renders the Medium (:m) budget card
# for an given instance of a Decidim::Budgets::Budget
class BudgetMCell < Decidim::CardMCell
- include ActiveSupport::NumberHelper
include Decidim::Budgets::ProjectsHelper
def statuses
diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb b/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb
index 12fe6b4a64731..a881b59840962 100644
--- a/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb
+++ b/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb
@@ -1,7 +1,3 @@
-
-
-
- <%= decidim_sanitize(landing_page_content) %>
-
-
+
+ <%= decidim_sanitize(landing_page_content) %>
diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb b/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb
index 0cabeaaa6e718..45710a6d0cc93 100644
--- a/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb
+++ b/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb
@@ -9,6 +9,10 @@ class BudgetsHeaderCell < BaseCell
def landing_page_content
translated_attribute(current_settings.landing_page_content).presence || translated_attribute(settings.landing_page_content)
end
+
+ def landing_page_instructions
+ translated_attribute(current_settings.landing_page_instructions).presence || translated_attribute(settings.landing_page_instructions)
+ end
end
end
end
diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb b/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb
index 8cbbe75b3a228..e22c514657368 100644
--- a/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb
+++ b/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb
@@ -1,7 +1,18 @@
-
- <% budgets.each do |budget| %>
- <% next if highlighted.include?(budget) && !voting_finished? %>
+<%# show highlighted budgets first %>
+<% if highlighted.any? %>
+
+ <% highlighted.each do |budget| %>
+ <%= cell("decidim/budgets/budget_list_item", budget) %>
+ <% end %>
+
+<% end %>
- <%= cell("decidim/budgets/budget_list_item", budget) %>
- <% end %>
-
<%= cell("decidim/budgets/project_votes_count",
model,
diff --git a/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb b/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb
index 2de429ae8de9c..222df75e40b7d 100644
--- a/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb
+++ b/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb
@@ -5,7 +5,6 @@ module Budgets
# This cell renders a horizontal project card
# for an given instance of a Project in a budget list
class ProjectListItemCell < Decidim::ViewModel
- include ActiveSupport::NumberHelper
include Decidim::LayoutHelper
include Decidim::ActionAuthorizationHelper
include Decidim::Budgets::ProjectsHelper
diff --git a/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb b/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb
index 71e70c6550552..e0a2cede5a1e6 100644
--- a/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb
+++ b/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb
@@ -5,7 +5,6 @@ module Budgets
# This cell renders the Medium (:m) project card
# for an given instance of a Project
class ProjectMCell < Decidim::CardMCell
- include ActiveSupport::NumberHelper
include Decidim::Budgets::ProjectsHelper
private
diff --git a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb b/decidim-budgets/app/cells/decidim/budgets/project_vote_button/show.erb
similarity index 84%
rename from decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb
rename to decidim-budgets/app/cells/decidim/budgets/project_vote_button/show.erb
index b2b13e701ebb9..523ac38479849 100644
--- a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb
+++ b/decidim-budgets/app/cells/decidim/budgets/project_vote_button/show.erb
@@ -3,12 +3,13 @@
method: vote_button_method,
remote: true,
class: "button tiny budget-list__action #{vote_button_class}",
+ id: "project-vote-button-#{model.id}",
data: {
add: !resource_added?,
disable: true,
budget: model.budget_amount,
allocation: resource_allocation,
- "redirect-url": resource_path
+ "redirect-url": budget_projects_path(model.budget)
},
disabled: vote_button_disabled?,
title: vote_button_label do %>
diff --git a/decidim-budgets/app/cells/decidim/budgets/project_vote_button_cell.rb b/decidim-budgets/app/cells/decidim/budgets/project_vote_button_cell.rb
new file mode 100644
index 0000000000000..bd4d9d6d43c76
--- /dev/null
+++ b/decidim-budgets/app/cells/decidim/budgets/project_vote_button_cell.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Decidim
+ module Budgets
+ # This cell renders an authorized_action button
+ # to vote a given instance of a Project in a budget list
+ class ProjectVoteButtonCell < ProjectListItemCell
+ end
+ end
+end
diff --git a/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb b/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb
index ab2f512ceb520..388fc87a78f8f 100644
--- a/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb
+++ b/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb
@@ -23,7 +23,7 @@ def initialize(current_order, project, current_user)
# Returns nothing.
def call
transaction do
- return broadcast(:invalid) if voting_not_enabled? || order.checked_out?
+ return broadcast(:invalid) if voting_not_enabled? || order.checked_out? || exceeds_budget?
add_line_item
broadcast(:ok, order)
@@ -44,6 +44,10 @@ def add_line_item
end
end
+ def exceeds_budget?
+ order.allocation_for(project) + order.total > order.available_allocation
+ end
+
def voting_not_enabled?
project.component.current_settings.votes != "enabled"
end
diff --git a/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb b/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb
index 9b254a38e7a51..964b5c025fe6b 100644
--- a/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb
+++ b/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb
@@ -8,6 +8,7 @@ module Budgets
# Note that it inherits from `Decidim::Components::BaseController`, which
# override its layout and provide all kinds of useful methods.
class ApplicationController < Decidim::Components::BaseController
+ helper Decidim::FocusModeHelper
helper_method :current_workflow, :voting_finished?, :voting_open?
def current_workflow
diff --git a/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb b/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb
index 227612c0b8a4b..f81b01e2f3ed9 100644
--- a/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb
+++ b/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb
@@ -20,7 +20,7 @@ def create
end
on(:invalid) do
- render nothing: true, status: :unprocessable_entity
+ format.js { render "update_budget", status: :unprocessable_entity }
end
end
end
@@ -35,7 +35,7 @@ def destroy
end
on(:invalid) do
- render nothing: true, status: :unprocessable_entity
+ format.js { render "update_budget", status: :unprocessable_entity }
end
end
end
diff --git a/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb b/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb
index 3aa9b0d04223a..a41affeff9aa9 100644
--- a/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb
+++ b/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb
@@ -4,6 +4,8 @@ module Decidim
module Budgets
# A helper to render order and budgets actions
module ProjectsHelper
+ include ActiveSupport::NumberHelper
+
# Render a budget as a currency
#
# budget - A integer to represent a budget
@@ -13,7 +15,7 @@ def budget_to_currency(budget)
# Return a percentage of the current order budget from the total budget
def current_order_budget_percent
- current_order&.budget_percent.to_f.floor
+ current_order&.budget_percent.to_f.floor.clamp(0, 100)
end
# Return the minimum percentage of the current order budget from the total budget
diff --git a/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb b/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb
index 8f57b162f9d98..7b52505e5be94 100644
--- a/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb
+++ b/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb
@@ -1,5 +1,34 @@
<%= render partial: "decidim/shared/component_announcement" %>
+<% start_date, end_date = %w(start_date end_date).map { |attribute| current_component.participatory_space.try(:active_step)&.try(attribute) || current_component.participatory_space.try(attribute) } %>
+<% instructions = translated_attribute(current_component.current_settings.landing_page_instructions).presence || translated_attribute(current_component.settings.landing_page_instructions) %>
-<%= cell("decidim/budgets/budgets_header", current_workflow) %>
+
diff --git a/decidim-budgets/config/locales/ca.yml b/decidim-budgets/config/locales/ca.yml
index d2629dfa62e25..55cc62e4939ce 100644
--- a/decidim-budgets/config/locales/ca.yml
+++ b/decidim-budgets/config/locales/ca.yml
@@ -103,6 +103,10 @@ ca:
update: "%{user_name} ha actualitzat el projecte %{resource_name} de l'espai %{space_name}"
budget:
view: Veure tots els projectes de pressupost
+ budgets:
+ index:
+ date: Dates de votació
+ instructions: Com participar
budget_information_modal:
back_to: Tornar a %{component_name}
close_modal: Tancar el modal
@@ -116,6 +120,9 @@ ca:
highlighted_cta: Votar a %{name}
if_change_opinion: Si has canviat d'opinió, pots
my_budgets: Els meus pressupostos
+ progress: Finalitza la votació
+ show: Veure projectes
+ vote: Vota
voted_on: Has votat a %{links}
limit_announcement:
cant_vote: No pots votar a aquests pressupostos. Prova amb un altre pressupost .
@@ -155,27 +162,31 @@ ca:
budget_summary:
are_you_sure: Segur que vols cancel·lar el teu vot?
assigned: 'Assignat:'
+ back_to_budgets: Tornar a %{component_name}
+ back_to_projects: Tornar a %{budget_name}
cancel_order: eliminar el teu vot i començar de nou
checked_out:
description: Ja has votat pel pressupost. Si has canviat d'idea, pots %{cancel_link}.
title: Vot pels pressupostos completat
minimum_projects_rule:
- description: A quins projectes creus que hem de destinar el pressupost? Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost.
- instruction: "
Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost.
"
+ description: A quins projectes creus que hem de destinar el pressupost? Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost.
+ instruction: "Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost."
projects_rule:
- description: A quins projectes creus que hem de destinar el pressupost? Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
- instruction: "
Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
"
+ description: A quins projectes creus que hem de destinar el pressupost? Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
+ instruction: "Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost."
projects_rule_maximum_only:
- description: A quins projectes creus que hem de destinar el pressupost? Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
- instruction: "
Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
"
+ description: A quins projectes creus que hem de destinar el pressupost? Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
+ instruction: "Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost."
+ remaining: 'Restant:'
rules:
title: Regles de votació
title: Tu decideixes el pressupost
total_budget: Pressupost total
total_projects: Vots totals
vote_threshold_percent_rule:
- description: A quins projectes creus que hem de destinar el pressupost? Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost.
- instruction: "
Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost.
"
+ description: A quins projectes creus que hem de destinar el pressupost? Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost.
+ instruction: "Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost."
+ vote: Vota
count:
projects_count:
one: 1 projecte
@@ -242,6 +253,7 @@ ca:
budget_voting_rule_only_one: Cal activar com a mínim una norma per a la votació
budget_voting_rule_required: Es requereix una norma per a la votació
landing_page_content: Pàgina d'inici de pressupostos
+ landing_page_instructions: Instruccions per la pàgina d'inici de pressupostos
more_information_modal: Finestra de "Més informació"
projects_per_page: Projectes per pàgina
resources_permissions_enabled: Es poden establir permisos d'accions per a cada projecte
@@ -265,6 +277,7 @@ ca:
comments_blocked: Comentaris bloquejats
highlighted_heading: Capçalera destacada
landing_page_content: Pàgina d'inici de pressupostos
+ landing_page_instructions: Instruccions per la pàgina d'inici de pressupostos
list_heading: Títol de la llista
more_information_modal: Finestra de "Més informació"
show_votes: Mostra els suports
diff --git a/decidim-budgets/config/locales/en.yml b/decidim-budgets/config/locales/en.yml
index 3025cec149899..a721cadad6652 100644
--- a/decidim-budgets/config/locales/en.yml
+++ b/decidim-budgets/config/locales/en.yml
@@ -109,6 +109,10 @@ en:
close_modal: Close modal
continue: Continue
more_information: More information
+ budgets:
+ index:
+ date: Voting dates
+ instructions: How to participate
budgets_list:
cancel_order:
more_than_one: delete your vote on %{name} and start over
@@ -117,6 +121,9 @@ en:
highlighted_cta: Vote on %{name}
if_change_opinion: If you've changed your mind, you can
my_budgets: My budgets
+ progress: Finish voting
+ show: See projects
+ vote: Vote
voted_on: You've voted on %{links}
limit_announcement:
cant_vote: You can't vote on this budget. Try on another budget.
@@ -156,27 +163,30 @@ en:
budget_summary:
are_you_sure: Are you sure you want to cancel your vote?
assigned: 'Assigned:'
+ back_to_budgets: Back to %{component_name}
+ back_to_projects: Back to %{budget_name}
cancel_order: delete your vote and start over
checked_out:
description: You've already voted for the budget. If you've changed your mind, you can %{cancel_link}.
title: Budget vote completed
minimum_projects_rule:
- description: What projects do you think we should allocate budget for? Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget.
- instruction: "
Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget.
"
+ description: What projects do you think we should allocate budget for? Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget.
+ instruction: "Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget."
projects_rule:
- description: What projects do you think we should allocate budget for? Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
- instruction: "
Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
"
+ description: What projects do you think we should allocate budget for? Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
+ instruction: "Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget."
projects_rule_maximum_only:
- description: What projects do you think we should allocate budget for? Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
- instruction: "
Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
"
+ description: What projects do you think we should allocate budget for? Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
+ instruction: "Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget."
+ remaining: 'Remaining:'
rules:
title: Budget rules
title: You decide the budget
total_budget: Total budget
total_projects: Total votes
vote_threshold_percent_rule:
- description: What projects do you think we should allocate budget for? Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget.
- instruction: "
Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget.
"
+ description: What projects do you think we should allocate budget for? Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget.
+ instruction: "Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget."
count:
projects_count:
one: 1 project
@@ -243,6 +253,7 @@ en:
budget_voting_rule_only_one: Only one voting rule must be enabled
budget_voting_rule_required: One voting rule is required
landing_page_content: Budgets landing page
+ landing_page_instructions: Budgets landing page instructions
more_information_modal: More information modal
projects_per_page: Projects per page
resources_permissions_enabled: Actions permissions can be set for each project
@@ -266,6 +277,7 @@ en:
comments_blocked: Comments blocked
highlighted_heading: Highlighted heading
landing_page_content: Budgets landing page
+ landing_page_instructions: Budgets landing page instructions
list_heading: List heading
more_information_modal: More information modal
show_votes: Show votes
diff --git a/decidim-budgets/config/locales/es.yml b/decidim-budgets/config/locales/es.yml
index 7a534df44266c..bd7daaf6a478a 100644
--- a/decidim-budgets/config/locales/es.yml
+++ b/decidim-budgets/config/locales/es.yml
@@ -103,6 +103,10 @@ es:
update: "%{user_name} actualizó el proyecto %{resource_name} en el espacio %{space_name}"
budget:
view: Ver todos los proyectos de presupuesto
+ budgets:
+ index:
+ date: Fechas de votación
+ instructions: Cómo participar
budget_information_modal:
back_to: Volver a %{component_name}
close_modal: Cierra el modal
@@ -116,6 +120,9 @@ es:
highlighted_cta: Votar en %{name}
if_change_opinion: Si has cambiado de opinión, puedes
my_budgets: Mis presupuestos
+ progress: Finaliza la votación
+ show: Ver proyectos
+ vote: Vota
voted_on: Has votado en %{links}
limit_announcement:
cant_vote: No puedes votar en este presupuesto. Prueba con otro presupuesto.
@@ -155,27 +162,31 @@ es:
budget_summary:
are_you_sure: '¿Estás seguro de que deseas cancelar tu voto?'
assigned: 'Asignado:'
+ back_to_budgets: Volver a %{component_name}
+ back_to_projects: Volver a %{budget_name}
cancel_order: eliminar tu voto y empezar de nuevo
checked_out:
description: Ya has votado para el presupuesto. Si has cambiado de idea, puedes %{cancel_link}.
title: Voto para los presupuestos completado
minimum_projects_rule:
- description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona por lo menos %{minimum_number} y vota según tus preferencias para definir el presupuesto.'
- instruction: "
Selecciona al menos %{minimum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
"
+ description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona por lo menos %{minimum_number} proyectos y vota según tus preferencias para definir el presupuesto.'
+ instruction: "Selecciona al menos %{minimum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto."
projects_rule:
- description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona entre %{minimum_number} y %{maximum_number} proyectos, y vota según tus preferencias para definir el presupuesto.'
- instruction: "
Selecciona al menos %{minimum_number} y hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
"
+ description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona entre %{minimum_number} y %{maximum_number} proyectos, y vota según tus preferencias para definir el presupuesto.'
+ instruction: "Selecciona al menos %{minimum_number} y hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto."
projects_rule_maximum_only:
- description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona hasta %{maximum_number} y vota según tus preferencias para definir el presupuesto.'
- instruction: "
Selecciona hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
"
+ description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona hasta %{maximum_number} proyectos y vota según tus preferencias para definir el presupuesto.'
+ instruction: "Selecciona hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto."
+ remaining: 'Restante:'
rules:
title: Reglas de presupuesto
title: Tú decides el presupuesto
total_budget: Presupuesto total
total_projects: Total de votos
vote_threshold_percent_rule:
- description: '¿A qué proyectos crees que deberíamos asignar el presupuesto? Asigna por lo menos %{minimum_budget} a los proyectos que desees y vota para definir el presupuesto.'
- instruction: "
Selecciona al menos %{minimum_budget} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
"
+ description: '¿A qué proyectos crees que deberíamos asignar el presupuesto? Asigna por lo menos %{minimum_budget} a los proyectos que desees y vota para definir el presupuesto.'
+ instruction: "Selecciona al menos %{minimum_budget} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto."
+ vote: Vota
count:
projects_count:
one: 1 proyecto
@@ -242,6 +253,7 @@ es:
budget_voting_rule_only_one: Hay que activar por lo menos una norma para la votación
budget_voting_rule_required: Se requiere una norma para la votación
landing_page_content: Página de inicio de presupuestos
+ landing_page_instructions: Instrucciones para la página de inicio de presupuestos
more_information_modal: Modal de "Más información"
projects_per_page: Proyectos por página
resources_permissions_enabled: Se pueden establecer permisos de acciones para cada proyecto
@@ -265,6 +277,7 @@ es:
comments_blocked: Comentarios bloqueados
highlighted_heading: Encabezado destacado
landing_page_content: Página de inicio de presupuestos
+ landing_page_instructions: Instrucciones para la página de inicio de presupuestos
list_heading: Título de lista
more_information_modal: Modal de "Más información"
show_votes: Mostrar los votos
diff --git a/decidim-budgets/lib/decidim/budgets/component.rb b/decidim-budgets/lib/decidim/budgets/component.rb
index 52883293498fd..209b98b2e832a 100644
--- a/decidim-budgets/lib/decidim/budgets/component.rb
+++ b/decidim-budgets/lib/decidim/budgets/component.rb
@@ -94,6 +94,7 @@
settings.attribute :announcement, type: :text, translated: true, editor: true
settings.attribute :landing_page_content, type: :text, translated: true, editor: true
+ settings.attribute :landing_page_instructions, type: :text, translated: true, editor: true
settings.attribute :more_information_modal, type: :text, translated: true
end
@@ -104,6 +105,7 @@
settings.attribute :announcement, type: :text, translated: true, editor: true
settings.attribute :landing_page_content, type: :text, translated: true, editor: true
+ settings.attribute :landing_page_instructions, type: :text, translated: true, editor: true
settings.attribute :more_information_modal, type: :text, translated: true
settings.attribute :announcement, type: :text, translated: true, editor: true
end
diff --git a/decidim-budgets/lib/decidim/budgets/test/factories.rb b/decidim-budgets/lib/decidim/budgets/test/factories.rb
index 251c0066f9c90..419ae4e2d15c2 100644
--- a/decidim-budgets/lib/decidim/budgets/test/factories.rb
+++ b/decidim-budgets/lib/decidim/budgets/test/factories.rb
@@ -98,6 +98,22 @@
}
end
end
+
+ trait :with_landing_page_content do
+ transient do
+ landing_page_content { Decidim::Faker::Localized.wrapped("
") }
+ end
+
+ step_settings do
+ {
+ participatory_space.active_step.id => {
+ landing_page_content: landing_page_content,
+ landing_page_instructions: landing_page_instructions
+ }
+ }
+ end
+ end
end
factory :budget, class: "Decidim::Budgets::Budget" do
diff --git a/decidim-budgets/spec/system/explore_budgets_spec.rb b/decidim-budgets/spec/system/explore_budgets_spec.rb
index b28c02250df48..2c5ba00c074a4 100644
--- a/decidim-budgets/spec/system/explore_budgets_spec.rb
+++ b/decidim-budgets/spec/system/explore_budgets_spec.rb
@@ -6,6 +6,29 @@
include_context "with a component"
let(:manifest_name) { "budgets" }
+ let!(:component) do
+ create(:budgets_component,
+ :with_vote_threshold_percent,
+ :with_landing_page_content,
+ manifest: manifest,
+ participatory_space: participatory_process,
+ landing_page_content: { en: "
Big title
" },
+ landing_page_instructions: { en: "
Follow your instincts
" })
+ end
+
+ let!(:current_step) do
+ create(:participatory_process_step,
+ start_date: 1.day.ago,
+ end_date: 3.days.from_now,
+ participatory_process: participatory_process
+ )
+ end
+
+ before do
+ participatory_process.steps.first.update(start_date: 1.month.ago, end_date: 1.day.ago - 1.hour, active: false)
+ participatory_process.steps.last.update(active: true)
+ end
+
context "with only one budget" do
let!(:budgets) { create_list(:budget, 1, component: component) }
@@ -17,15 +40,86 @@
end
context "with many budgets" do
- let!(:budgets) { create_list(:budget, 6, component: component) }
+ let!(:budgets) do
+ 1.upto(6).to_a.map { |x| create(:budget, component: component, total_budget: x * 10_000_000, description: { en: "This is budget #{x}" }) }
+ end
- it "lists all the budgets" do
+ before do
visit_component
+ end
+ it "shows the content" do
+ expect(page).to have_content("Big title")
+ end
+
+ it "shows the dates" do
+ expect(page).to have_content("Voting dates")
+
+ within ".extra__date-container" do
+ expect(page).to have_content(I18n.l(current_step.start_date, format: "%B"))
+ expect(page).to have_content(current_step.start_date.day)
+ expect(page).to have_content(I18n.l(current_step.end_date, format: "%B"))
+ expect(page).to have_content(current_step.end_date.day)
+ end
+ end
+
+ it "shows the instructions" do
+ expect(page).to have_content("How to participate")
+ expect(page).to have_content("Follow your instincts")
+ end
+
+ it "lists all the budgets" do
expect(page).to have_selector(".card--list__item", count: 6)
budgets.each do |budget|
expect(page).to have_content(translated(budget.title))
+ expect(page).to have_content("This is budget 1")
+ expect(page).to have_content("€10,000,000")
+ end
+ end
+
+ describe "budget list item" do
+ let(:budget) { budgets.first }
+ let(:item) { page.find(".budget-list .card--list__item:first-child", match: :first) }
+ let!(:projects) { create_list(:project, 3, budget: budget, budget_amount: 10_000_000) }
+
+ before do
+ login_as user, scope: :user
+ end
+
+ it "has a clickable title" do
+ expect(item).to have_link(translated(budget.title), href: budget_path(budget))
+ end
+
+ context "when an item is bookmarked" do
+ let!(:order) { create(:order, user: user, budget: budget) }
+ let!(:line_item) { create(:line_item, order: order, project: projects.first) }
+
+ it "shows the bookmark icon" do
+ visit_component
+
+ expect(item).to have_selector(".budget-list__icon .icon--bookmark")
+ expect(item).to have_link("Finish voting", href: budget_path(budget))
+ end
+ end
+
+ context "when an item is voted" do
+ let(:item) { page.find("#voted-budgets .card--list__item:first-child") }
+
+ let!(:order) do
+ order = create(:order, user: user, budget: budget)
+ order.projects = [projects.first]
+ order.checked_out_at = Time.current
+ order.save!
+ order
+ end
+
+ it "shows the check icon" do
+ visit_component
+
+ expect(item).to have_selector(".budget-list__icon .icon--check")
+ expect(item).to have_link("Show", href: budget_path(budget))
+ end
end
end
end
@@ -35,4 +129,8 @@
let(:target_path) { Decidim::EngineRouter.main_proxy(component).budget_path(99_999_999) }
end
end
+
+ def budget_path(budget)
+ Decidim::EngineRouter.main_proxy(component).budget_path(budget.id)
+ end
end
diff --git a/decidim-budgets/spec/system/explore_projects_spec.rb b/decidim-budgets/spec/system/explore_projects_spec.rb
index ca97bbbd83b63..60734722f56d6 100644
--- a/decidim-budgets/spec/system/explore_projects_spec.rb
+++ b/decidim-budgets/spec/system/explore_projects_spec.rb
@@ -14,8 +14,13 @@
let(:categories) { create_list(:category, 3, participatory_space: component.participatory_space) }
describe "index" do
- it "shows all resources for the given component" do
+ before do
visit_budget
+ end
+
+ it_behaves_like "has focus mode", "TOTAL BUDGET"
+
+ it "shows all resources for the given component" do
within "#projects" do
expect(page).to have_selector(".budget-list__item", count: projects_count)
end
diff --git a/decidim-budgets/spec/system/orders_spec.rb b/decidim-budgets/spec/system/orders_spec.rb
index 13e279c3a0046..f87a5430101b1 100644
--- a/decidim-budgets/spec/system/orders_spec.rb
+++ b/decidim-budgets/spec/system/orders_spec.rb
@@ -21,9 +21,13 @@
context "when the user is not logged in" do
let!(:projects) { create_list(:project, 1, budget: budget, budget_amount: 25_000_000) }
- it "is given the option to sign in" do
+ before do
visit_budget
+ end
+ it_behaves_like "has focus mode", "TOTAL BUDGET"
+
+ it "is given the option to sign in" do
within "#project-#{project.id}-item" do
page.find(".budget-list__action").click
end
@@ -44,15 +48,12 @@
visit_budget
end
+ it_behaves_like "has focus mode", "You decide the budget"
+
context "when voting by percentage threshold" do
- it "displays description messages" do
+ it "displays description messages and voting rules" do
within ".budget-summary" do
- expect(page).to have_content("You decide the budget\nWhat projects do you think we should allocate budget for? Assign at least €70,000,000 to the projects you want and vote according to your preferences to define the budget.")
- end
- end
-
- it "displays rules" do
- within ".voting-rules" do
+ expect(page).to have_content("You decide the budget")
expect(page).to have_content("Assign at least €70,000,000 to the projects you want and vote according to your preferences to define the budget.")
end
end
@@ -66,15 +67,23 @@
participatory_space: participatory_process)
end
- it "displays description messages" do
+ it "displays voting rules" do
within ".budget-summary" do
- expect(page).to have_content("What projects do you think we should allocate budget for? Select at least 3 projects you want and vote according to your preferences to define the budget.")
+ expect(page).to have_content("Select at least 3 projects you want and vote according to your preferences to define the budget.")
end
end
- it "displays rules" do
- within ".voting-rules" do
- expect(page).to have_content("Select at least 3 projects you want and vote according to your preferences to define the budget.")
+ it "shows the project count in the progress bar" do
+ within ".progress-meter-text" do
+ expect(page).to have_content("0 projects selected")
+ end
+
+ within "#project-#{project.id}-item" do
+ page.find(".budget-list__action").click
+ end
+
+ within ".progress-meter-text" do
+ expect(page).to have_content("1 project selected")
end
end
end
@@ -88,14 +97,8 @@
participatory_space: participatory_process)
end
- it "displays description messages" do
+ it "displays voting rules" do
within ".budget-summary" do
- expect(page).to have_content("What projects do you think we should allocate budget for? Select up to 6 projects you want and vote according to your preferences to define the budget.")
- end
- end
-
- it "displays rules" do
- within ".voting-rules" do
expect(page).to have_content("Select up to 6 projects you want and vote according to your preferences to define the budget.")
end
end
@@ -109,14 +112,8 @@
participatory_space: participatory_process)
end
- it "displays description messages" do
+ it "displays voting rules" do
within ".budget-summary" do
- expect(page).to have_content("What projects do you think we should allocate budget for? Select at least 3 and up to 6 projects you want and vote according to your preferences to define the budget.")
- end
- end
-
- it "displays rules" do
- within ".voting-rules" do
expect(page).to have_content("Select at least 3 and up to 6 projects you want and vote according to your preferences to define the budget.")
end
end
@@ -137,6 +134,7 @@
expect(page).to have_selector ".budget-list__data--added", count: 1
expect(page).to have_content "ASSIGNED: €25,000,000"
+ expect(page).to have_content "REMAINING: €75,000,000"
expect(page).to have_content "1 project selected"
within ".budget-summary__selected" do
@@ -172,6 +170,7 @@
expect(page).to have_selector ".budget-list__data--added", count: 1
expect(page).to have_content "ASSIGNED: €25,000,000"
+ expect(page).to have_content "REMAINING: €75,000,000"
expect(page).to have_content "1 project selected"
within ".budget-summary__selected" do
@@ -179,7 +178,7 @@
end
within "#order-progress .budget-summary__progressbox" do
- expect(page).to have_content "25%"
+ expect(page).to have_content "1 project selected"
expect(page).to have_selector("button.small:disabled")
end
end
@@ -295,6 +294,7 @@
visit_budget
expect(page).to have_content "ASSIGNED: €25,000,000"
+ expect(page).to have_content "REMAINING: €75,000,000"
within "#project-#{project.id}-item" do
page.find(".budget-list__action").click
@@ -317,11 +317,13 @@
visit_budget
expect(page).to have_content "ASSIGNED: €25,000,000"
+ expect(page).to have_content "REMAINING: €75,000,000"
# Note that this is not a default alert box, this is the default browser
# prompt for verifying the page unload. Therefore, `dismiss_prompt` is
# used instead of `dismiss_confirm`.
dismiss_prompt do
+ page.find(".focus-mode__close").click
page.find(".logo-wrapper a").click
end
@@ -491,6 +493,7 @@
expect(page).to have_content("Budget vote completed")
+ page.find(".focus-mode__close").click
page.find(".logo-wrapper a").click
expect(page).to have_current_path decidim.root_path
@@ -562,7 +565,7 @@
visit_budget
- expect(page).to have_selector("[id^=project-]", count: 1)
+ expect(page).to have_selector("[id^=project-][id$=-item]", count: 1)
end
it "respects the projects_per_page setting when it matches total projects" do
@@ -572,7 +575,7 @@
visit_budget
- expect(page).to have_selector("[id^=project-]", count: 2)
+ expect(page).to have_selector("[id^=project-][id$=-item]", count: 2)
end
it "respects the projects_per_page setting when over total projects" do
@@ -582,7 +585,7 @@
visit_budget
- expect(page).to have_selector("[id^=project-]", count: 2)
+ expect(page).to have_selector("[id^=project-][id$=-item]", count: 2)
end
end
diff --git a/decidim-core/app/assets/javascripts/decidim/focus_mode.js.es6 b/decidim-core/app/assets/javascripts/decidim/focus_mode.js.es6
new file mode 100644
index 0000000000000..400386661c03f
--- /dev/null
+++ b/decidim-core/app/assets/javascripts/decidim/focus_mode.js.es6
@@ -0,0 +1,124 @@
+$(() => {
+ const $focusModeOn = $("[data-focus-on]");
+ const $focusModeOff = $("[data-focus-off]");
+ const $wrapper = $(".focus-mode__body");
+ const $focusContent = $("[data-focus-body]");
+ const $pageContent = $("#content");
+ const $closer = $("[data-focus-close]");
+ const $opener = $("[data-focus-open]");
+ const $flashMessagesContainer = $(".focus-mode__flash-messages");
+
+ const $background = $(".title-bar, [data-set='nav-holder'], .process-header");
+
+ const $overlay = $(".omnipresent-banner, .cookie-warning");
+ const $cookieButton = $(".cookie-bar__button");
+
+ const FADEOUT_TIME = 200;
+
+ const flashMessagesObserver = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.addedNodes !== null) {
+ var $nodes = $(mutation.addedNodes);
+ if ($nodes.filter(".flash.callout").length > 0) {
+ moveFlashMessages();
+ }
+ }
+ });
+ });
+
+ const watchFlashMessages = () => {
+ // Pass in the target node, as well as the observer options
+ flashMessagesObserver.observe($("#content")[0], { attributes: true, childList: true, characterData: true });
+ };
+
+ const unwatchFlashMessages = () => {
+ flashMessagesObserver.disconnect();
+ };
+
+ const moveFlashMessages = () => {
+ $(".flash.callout").appendTo($flashMessagesContainer);
+ };
+
+ const overlayHeight = () => {
+ var h = 0;
+ $overlay.outerHeight((i, v) => {
+ if ($($overlay[i]).is(":visible")) h += v;
+ });
+ return h;
+ };
+
+ const moveToShowOverlay = () => {
+ const top = $(document).scrollTop();
+ const height = overlayHeight();
+
+ if (top <= height) {
+ $focusModeOn.css({ top: `${height}px` })
+ } else {
+ $focusModeOn.css({ top: "0px" })
+ }
+ }
+
+ const moveOverlay = () => {
+ if (!$overlay.length) return;
+
+ if ($cookieButton.length) {
+ $cookieButton.on("click", moveToShowOverlay);
+ }
+
+ moveToShowOverlay();
+ window.addEventListener("scroll", moveToShowOverlay);
+ }
+
+ const focusModeOn = function(fadeTime) {
+ if ($opener.length) $opener.fadeOut(fadeTime);
+
+ $background.hide(fadeTime);
+ $pageContent.animate({ "margin-top": `${$focusModeOn.outerHeight() + overlayHeight() - 50}px` }, fadeTime);
+
+ moveOverlay();
+ moveFlashMessages();
+
+ $focusContent.fadeOut(fadeTime, () => {
+ $focusContent.detach().prependTo($wrapper);
+ $focusModeOn.fadeIn(fadeTime, () => {
+ $focusContent.fadeIn(fadeTime, () => {});
+ });
+ });
+
+ watchFlashMessages();
+ }
+
+ const focusModeOff = function(fadeTime) {
+ $focusContent.fadeOut(fadeTime);
+ $background.show(fadeTime);
+ $pageContent.animate({ "margin-top": "0px" }, fadeTime);
+
+ $focusModeOn.fadeOut(fadeTime, () => {
+ $focusContent.detach().prependTo($focusModeOff);
+ $focusModeOff.fadeIn(fadeTime, () => {
+ $focusContent.fadeIn(fadeTime, () => {});
+ if ($opener.length) $opener.fadeIn(fadeTime);
+ });
+ });
+
+ unwatchFlashMessages();
+ }
+
+ const initializeFocusMode = () => {
+ const focusModePresent = !!$focusModeOn.length;
+
+ $closer.on("click", () => { focusModeOff(FADEOUT_TIME) });
+
+ if ($opener.length) $opener.on("click", () => { focusModeOn(FADEOUT_TIME) });
+
+ if (focusModePresent > 0 && window.matchMedia('(min-width: 800px)').matches) {
+ focusModeOn(0);
+ } else {
+ focusModeOff(0);
+ }
+ }
+
+ initializeFocusMode();
+
+ $(window).resize(initializeFocusMode);
+});
diff --git a/decidim-core/app/assets/stylesheets/decidim/layouts/_focus_mode.scss b/decidim-core/app/assets/stylesheets/decidim/layouts/_focus_mode.scss
new file mode 100644
index 0000000000000..660657ad44588
--- /dev/null
+++ b/decidim-core/app/assets/stylesheets/decidim/layouts/_focus_mode.scss
@@ -0,0 +1,63 @@
+.focus-mode {
+ position: fixed;
+ z-index: 1000;
+ width: 100%;
+ top: 0;
+ left: 0;
+ background: $medium-gray;
+ box-shadow: 0 3px 5px rgba(0, 0, 0, 0.3);
+
+ &__title {
+ text-align: center;
+ }
+
+ &__bar {
+ padding: 0.5em;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ align-items: center;
+
+ &--right {
+ display: flex;
+ justify-content: right;
+ align-items: center;
+ gap: 1em;
+ text-align: right;
+ }
+ }
+
+ &__close {
+ position: relative;
+ margin: 0;
+ right: 0;
+ top: 0;
+ line-height: 0.5em;
+ }
+
+ &__wrapper {
+ background: white;
+ }
+
+ &__body {
+ max-width: 75rem;
+ padding: 1em;
+ font-size: 80%;
+ margin: auto;
+
+ // Reduce sizes of content as much as possible
+ .card,
+ .card__content {
+ margin: 0;
+ padding: 0;
+ border: none;
+ }
+
+ ul {
+ margin-bottom: 0;
+ }
+
+ .budget-summary__selected-list {
+ margin-top: 0;
+ }
+ }
+}
diff --git a/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss b/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss
index b8d3070c76821..1039549967edf 100644
--- a/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss
+++ b/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss
@@ -1,3 +1,4 @@
+@import "decidim/layouts/focus_mode";
@import "decidim/layouts/highlighted_banner";
@import "decidim/layouts/home";
@import "decidim/layouts/logo";
diff --git a/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss b/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss
index 7814ebc313d04..1d9806002eaba 100644
--- a/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss
+++ b/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss
@@ -634,6 +634,10 @@ a .card__title{
pointer-events: none;
}
+ &--inline{
+ display: inline-block;
+ }
+
svg{
flex-basis: auto;
}
diff --git a/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss b/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss
index c63ca46ba1ec7..7e5e94ba4df9d 100644
--- a/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss
+++ b/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss
@@ -32,6 +32,14 @@
.extra__date-container{
margin-bottom: 1rem;
+
+ &--horizontal{
+ margin-bottom: 1rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 2rem;
+ }
}
.extra__month{
diff --git a/decidim-core/app/cells/decidim/date/show.erb b/decidim-core/app/cells/decidim/date/show.erb
index 7f294f07796f4..84d6efdf2377d 100644
--- a/decidim-core/app/cells/decidim/date/show.erb
+++ b/decidim-core/app/cells/decidim/date/show.erb
@@ -1,4 +1,4 @@
-
+
<% if same_day? %>
<%= l start_time, format: "%d" %>
diff --git a/decidim-core/app/cells/decidim/date_cell.rb b/decidim-core/app/cells/decidim/date_cell.rb
index cae40687951c2..ec6a95a19d3bf 100644
--- a/decidim-core/app/cells/decidim/date_cell.rb
+++ b/decidim-core/app/cells/decidim/date_cell.rb
@@ -31,6 +31,10 @@ def end_time
model[:end]
end
+ def extra_classes
+ model[:extra_classes]
+ end
+
def same_day?
start_time.beginning_of_day == end_time.beginning_of_day
end
diff --git a/decidim-core/app/cells/decidim/focus_mode/show.erb b/decidim-core/app/cells/decidim/focus_mode/show.erb
new file mode 100644
index 0000000000000..16e0310fe1449
--- /dev/null
+++ b/decidim-core/app/cells/decidim/focus_mode/show.erb
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ <%= yield %>
+
+
+
+
+
+
+
+ <% if opener_button %>
+
+ <%= t(".focus_mode") %>
+
+ <% end %>
+
diff --git a/decidim-core/app/cells/decidim/focus_mode_cell.rb b/decidim-core/app/cells/decidim/focus_mode_cell.rb
new file mode 100644
index 0000000000000..1ccbe519047bf
--- /dev/null
+++ b/decidim-core/app/cells/decidim/focus_mode_cell.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require "decidim/diffy_extension"
+
+module Decidim
+ # This cell renders a dismissable overlay to highlight information about the current
+ # action being performed by the user
+ class FocusModeCell < Decidim::ViewModel
+ include Cell::ViewModel::Partial
+ include LayoutHelper
+
+ def show(&block)
+ render(&block)
+ end
+
+ def title
+ options[:title]
+ end
+
+ def user
+ return unless current_user
+
+ link_to current_user.name, decidim.account_path
+ end
+
+ def opener_button
+ options[:opener_button]
+ end
+ end
+end
diff --git a/decidim-core/app/helpers/decidim/focus_mode_helper.rb b/decidim-core/app/helpers/decidim/focus_mode_helper.rb
new file mode 100644
index 0000000000000..0bd961a4356e6
--- /dev/null
+++ b/decidim-core/app/helpers/decidim/focus_mode_helper.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Decidim
+ # Helpers related to focus mode
+ module FocusModeHelper
+ def focus_mode(opts, &body)
+ cell(FocusModeCell, nil, opts).call do
+ capture(&body)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/decidim-core/config/locales/en.yml b/decidim-core/config/locales/en.yml
index 367608c363780..703324dc4ccfc 100644
--- a/decidim-core/config/locales/en.yml
+++ b/decidim-core/config/locales/en.yml
@@ -688,6 +688,8 @@ en:
source: Source
title: Fingerprint
value: Value
+ focus_mode:
+ focus_mode: Enter focus mode
followers:
followers_count:
one: "%{count} follower"
diff --git a/decidim-core/lib/decidim/core/test.rb b/decidim-core/lib/decidim/core/test.rb
index b7cc7f779f78f..b165140b60d32 100644
--- a/decidim-core/lib/decidim/core/test.rb
+++ b/decidim-core/lib/decidim/core/test.rb
@@ -10,6 +10,7 @@
require "decidim/core/test/shared_examples/has_attachments"
require "decidim/core/test/shared_examples/has_attachment_collections"
require "decidim/core/test/shared_examples/has_component"
+require "decidim/core/test/shared_examples/has_focus_mode"
require "decidim/core/test/shared_examples/has_scope"
require "decidim/core/test/shared_examples/has_category"
require "decidim/core/test/shared_examples/has_reference"
diff --git a/decidim-core/lib/decidim/core/test/shared_examples/has_focus_mode.rb b/decidim-core/lib/decidim/core/test/shared_examples/has_focus_mode.rb
new file mode 100644
index 0000000000000..44ccd99f1acf8
--- /dev/null
+++ b/decidim-core/lib/decidim/core/test/shared_examples/has_focus_mode.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+shared_examples_for "has focus mode" do |content|
+ it "has the necessary elements" do
+ expect(page).to have_selector("#focus-mode")
+ expect(page).to have_selector("#off-focus-mode")
+ end
+
+ it "is opened by default" do
+ within "#focus-mode" do
+ expect(page).to have_content(content)
+ end
+ end
+
+ it "can be closed" do
+ expect(page).to have_selector(".focus-mode__close")
+
+ page.find(".focus-mode__close").click
+
+ within "#focus-mode" do
+ expect(page).not_to have_content(content)
+ end
+
+ within "#off-focus-mode" do
+ expect(page).to have_content(content)
+ end
+ end
+end
diff --git a/decidim-core/spec/cells/decidim/date_cell_spec.rb b/decidim-core/spec/cells/decidim/date_cell_spec.rb
index 3b00bb367b652..c3094c374a79a 100644
--- a/decidim-core/spec/cells/decidim/date_cell_spec.rb
+++ b/decidim-core/spec/cells/decidim/date_cell_spec.rb
@@ -10,7 +10,8 @@
let(:my_cell) { cell("decidim/date", model) }
let!(:organization) { create(:organization) }
let(:user) { create(:user, :confirmed, organization: organization) }
- let(:model) { { start: start_time, end: end_time } }
+ let(:model) { { start: start_time, end: end_time, extra_classes: extra_classes } }
+ let(:extra_classes) { "extra__class" }
let(:start_time) { Time.zone.now - 1.hour }
let(:start_time_past_year) { Time.zone.now - 1.year }
let(:end_time_same_date) { Time.zone.now + 1.hour }
@@ -24,6 +25,10 @@
it "renders a Date card" do
expect(subject).to have_css(".extra__date-container")
end
+
+ it "renders a the extra classes" do
+ expect(subject).to have_css(".extra__class")
+ end
end
context "when start and end time are on the same date" do
diff --git a/decidim-core/spec/cells/decidim/focus_mode_cell_spec.rb b/decidim-core/spec/cells/decidim/focus_mode_cell_spec.rb
new file mode 100644
index 0000000000000..e170685ca7814
--- /dev/null
+++ b/decidim-core/spec/cells/decidim/focus_mode_cell_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe Decidim::FocusModeCell, type: :cell do
+ subject { my_cell.call { "content" } }
+
+ controller Decidim::Budgets::BudgetsController
+
+ let(:my_cell) { cell("decidim/focus_mode", nil, options) }
+ let!(:organization) { create(:organization) }
+ let(:user) { create(:user, :confirmed, organization: organization) }
+ let(:options) { { title: "A title for focus mode", opener_button: opener_button, context: { current_user: user, current_organization: organization } } }
+ let(:opener_button) { false }
+ let(:decidim) { Decidim::Core::Engine.routes }
+
+ it "renders the content passed as a block" do
+ expect(subject).to have_content("content")
+ end
+
+ it "renders the title passed in the options" do
+ expect(subject).to have_content("A title for focus mode")
+ end
+
+ it "renders the organization name linking to the home page" do
+ expect(subject).to have_link(organization.name, href: "http://#{organization.host}/")
+ end
+
+ context "when opener_button option is false" do
+ let(:opener_button) { false }
+
+ it "does not render the opener button" do
+ expect(subject).not_to have_css("[data-focus-open]")
+ end
+ end
+
+ context "when opener_button option is true" do
+ let(:opener_button) { true }
+
+ it "renders the opener button" do
+ expect(subject).to have_css("[data-focus-open]")
+ end
+ end
+
+ context "when the user is logged in" do
+ it "renders the user name with a link to the account page" do
+ expect(subject).to have_css(".focus-mode__user")
+ expect(subject).to have_link(user.name, href: "/account")
+ end
+ end
+
+ context "when the user is not logged in" do
+ let(:user) { nil }
+
+ it "does not render the user name" do
+ expect(subject).not_to have_css(".focus-mode__user")
+ end
+ end
+end
diff --git a/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb b/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb
index 893c33458a24b..a0bf38dd14e53 100644
--- a/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb
+++ b/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb
@@ -140,6 +140,10 @@ def cache_hash
end
hash << model.follows_count
hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s)
+ hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?)
+ hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step)
+ hash << has_footer?
+ hash << has_actions?
hash.join("/")
end