diff --git a/decidim-core/app/cells/decidim/diff/attribute.erb b/decidim-core/app/cells/decidim/diff/attribute.erb index cb2f4040a9fc2..3d4649f0bd5d4 100644 --- a/decidim-core/app/cells/decidim/diff/attribute.erb +++ b/decidim-core/app/cells/decidim/diff/attribute.erb @@ -5,23 +5,13 @@ -
" id="<%= attribute_diff_id("diff_view_unified_unescaped") %>"> - <%= diff_unified(data, :unescaped_html) %> +
+ <%= diff_unified(data, format) %>
-
" id="<%= attribute_diff_id("diff_view_unified_escaped") %>"> - <%= diff_unified(data, :html) %> +
+ <%= diff_split(data, "left", format) %> + <%= diff_split(data, "right", format) %>
-
"> - <%= diff_split(data, "left", :unescaped_html) %> - - <%= diff_split(data, "right", :unescaped_html) %> -
- -
"> - <%= diff_split(data, "left", :html) %> - - <%= diff_split(data, "right", :html) %> -
diff --git a/decidim-core/app/cells/decidim/diff/diff_mode_dropdown.erb b/decidim-core/app/cells/decidim/diff/diff_mode_dropdown.erb index 5166529a0bf11..e31dc38517107 100644 --- a/decidim-core/app/cells/decidim/diff/diff_mode_dropdown.erb +++ b/decidim-core/app/cells/decidim/diff/diff_mode_dropdown.erb @@ -17,13 +17,13 @@
-
-
- <%= output_split_diff(data, direction, format) %> + <% available_locales_for(data).each do |locale, filled| %> + <% next unless filled %> + <% unless I18n.locale.to_s == locale %> +

<%= locale_name(locale) %>

+ <% end %> +
+
+ <%= output_split_diff(data, direction, format, locale) %> +
-
+ <% end %>
diff --git a/decidim-core/app/cells/decidim/diff/diff_unified.erb b/decidim-core/app/cells/decidim/diff/diff_unified.erb index 6b95eabe5b9ee..afbd8e3b8c1cb 100644 --- a/decidim-core/app/cells/decidim/diff/diff_unified.erb +++ b/decidim-core/app/cells/decidim/diff/diff_unified.erb @@ -1,7 +1,13 @@
-
-
- <%= output_unified_diff(data, format) %> + <% available_locales_for(data).each do |locale, filled| %> + <% next unless filled %> + <% unless I18n.locale.to_s == locale %> +

<%= locale_name(locale) %>

+ <% end %> +
+
+ <%= output_unified_diff(data, format, locale) %> +
-
+ <% end %>
diff --git a/decidim-core/app/cells/decidim/diff/show.erb b/decidim-core/app/cells/decidim/diff/show.erb index ef3d22f96d9a7..d38d236d11fd3 100644 --- a/decidim-core/app/cells/decidim/diff/show.erb +++ b/decidim-core/app/cells/decidim/diff/show.erb @@ -1,8 +1,50 @@ -<%= render :diff_mode_dropdown %> -<%= render :diff_mode_html %> +
+
+
    +
  • + +
    + <%= link_to "#diff-text", class: "button button--nomargin button--sc hollow" do %> + <%= t("versions.tabs.text") %> + <% end %> +
    +
  • +
  • + +
    + <%= link_to "#diff-source", class: "button button--nomargin button--sc hollow" do %> + <%= t("versions.tabs.source") %> + <% end %> +
    +
  • +
  • + +
    + <%= link_to "#diff-preview", class: "button button--nomargin button--sc hollow" do %> + <%= t("versions.tabs.preview") %> + <% end %> +
    +
  • +
+
+ +
+ +
+ <%= render :diff_mode_dropdown %> + <% diff_data.each do |data| %> + <%= attribute(data, :text) %> + <% end %> +
+
+ <%= render :diff_mode_dropdown %> + <% diff_data.each do |data| %> + <%= attribute(data, :html) %> + <% end %> +
+
+ <%= preview %> +
+
-
- <% diff_data.each do |data| %> - <%= attribute(data) %> - <% end %>
diff --git a/decidim-core/app/cells/decidim/diff_cell.rb b/decidim-core/app/cells/decidim/diff_cell.rb index 63cc301707da0..b2d75b12aead2 100644 --- a/decidim-core/app/cells/decidim/diff_cell.rb +++ b/decidim-core/app/cells/decidim/diff_cell.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -require "decidim/diffy_extension" - module Decidim # This cell renders the diff between `:old_data` and `:new_data`. class DiffCell < Decidim::ViewModel include Cell::ViewModel::Partial + include ::HtmlToPlainText # from the premailer gem + include LanguageChooserHelper include LayoutHelper - def attribute(data) - render locals: { data: data } + def attribute(data, format) + render locals: { data: data, format: format } end def diff_unified(data, format) @@ -22,12 +22,6 @@ def diff_split(data, direction, format) private - # Adds a unique ID prefix for the attribute div IDs to avoid duplicate IDs - # in the DOM. - def attribute_diff_id(id) - "#{SecureRandom.uuid}_#{id}" - end - # A PaperTrail::Version. def current_version model @@ -38,6 +32,11 @@ def item current_version.item end + # preview (if associated item allows it) + def preview + diff_renderer.preview + end + # DiffRenderer class for the current_version's item; falls back to `BaseDiffRenderer`. def diff_renderer_class if current_version.item_type.deconstantize == "Decidim" @@ -63,17 +62,32 @@ def diff_data diff_renderer.diff.values end + def available_locales_for(data) + locales = { I18n.locale.to_s => true } + + locales.merge! valid_locale_keys(data[:old_value]) if data[:old_value].is_a?(Hash) + locales.merge! valid_locale_keys(data[:new_value]) if data[:new_value].is_a?(Hash) + + locales.filter { |k| I18n.locale_available?(k) } + end + + def valid_locale_keys(input) + locales = input.transform_values(&:present?) + locales.merge!(input["machine_translations"].transform_values(&:present?)) if input["machine_translations"].is_a?(Hash) + locales + end + # Outputs the diff as HTML with inline highlighting of the character # changes between lines. # # Returns an HTML-safe string. - def output_unified_diff(data, format) + def output_unified_diff(data, format, locale) Diffy::Diff.new( - data[:old_value].to_s, - data[:new_value].to_s, + old_new_values(data, format, locale)[0], + old_new_values(data, format, locale)[1], allow_empty_diff: false, include_plus_and_minus_in_html: true - ).to_s(format) + ).to_s(:html) end # Outputs the diff as HTML with side-by-side changes between lines. @@ -81,16 +95,37 @@ def output_unified_diff(data, format) # The left side represents deletions while the right side represents insertions. # # Returns an HTML-safe string. - def output_split_diff(data, direction, format) + def output_split_diff(data, direction, format, locale) Diffy::SplitDiff.new( - data[:old_value].to_s, - data[:new_value].to_s, + old_new_values(data, format, locale)[0], + old_new_values(data, format, locale)[1], allow_empty_diff: false, - format: format, + format: :html, include_plus_and_minus_in_html: true ).send(direction) end + def old_new_values(data, format, locale) + original_translations = data[:old_value].try(:filter) { |key, value| value.present? && key != "machine_translations" } + [ + value_from_locale(data[:old_value], format, locale), + value_from_locale(data[:new_value], format, locale, original_translations) + ] + end + + def value_from_locale(value, format, locale, skip_machine_keys = {}) + text = value.is_a?(Hash) ? find_locale_value(value, locale, skip_machine_keys) : value + + text = text.first if text.is_a?(Array) + # return text.to_s if format == :html || text.blank? + + convert_to_text(text.to_s.dup, 100) + end + + def find_locale_value(input, locale, skip_machine_keys = {}) + input.dig(locale).presence || skip_machine_keys[locale].presence || input.dig("machine_translations", locale) + end + # Gives the option to view HTML unescaped for better user experience. # Official means created from admin (where rich text editor is enabled). def show_html_view_dropdown? diff --git a/decidim-core/app/commands/decidim/update_account.rb b/decidim-core/app/commands/decidim/update_account.rb index 807af1398ab6e..507f7c5a86109 100644 --- a/decidim-core/app/commands/decidim/update_account.rb +++ b/decidim-core/app/commands/decidim/update_account.rb @@ -38,6 +38,7 @@ def update_personal_data @user.email = @form.email @user.personal_url = @form.personal_url @user.about = @form.about + @user.time_zone = @form.time_zone end def update_avatar diff --git a/decidim-core/app/controllers/concerns/decidim/use_organization_time_zone.rb b/decidim-core/app/controllers/concerns/decidim/use_organization_time_zone.rb index 41d7ea3d8b548..13ba69e616c3a 100644 --- a/decidim-core/app/controllers/concerns/decidim/use_organization_time_zone.rb +++ b/decidim-core/app/controllers/concerns/decidim/use_organization_time_zone.rb @@ -25,7 +25,7 @@ def use_organization_time_zone(&action) # # Returns a String. def organization_time_zone - @organization_time_zone ||= current_organization.time_zone + @organization_time_zone ||= current_user&.time_zone.presence || current_organization.time_zone end end end diff --git a/decidim-core/app/forms/decidim/account_form.rb b/decidim-core/app/forms/decidim/account_form.rb index 4388b2c7e3801..4d5ce6658a40b 100644 --- a/decidim-core/app/forms/decidim/account_form.rb +++ b/decidim-core/app/forms/decidim/account_form.rb @@ -18,6 +18,7 @@ class AccountForm < Form attribute :remove_avatar, Boolean, default: false attribute :personal_url attribute :about + attribute :time_zone validates :name, presence: true, format: { with: Decidim::User::REGEXP_NAME } validates :email, presence: true, "valid_email_2/email": { disposable: true } @@ -28,6 +29,7 @@ class AccountForm < Form validates :password, password: { name: :name, email: :email, username: :nickname }, if: -> { password.present? } validates :password_confirmation, presence: true, if: :password_present validates :avatar, passthru: { to: Decidim::User } + validates :time_zone, time_zone: true, if: -> { time_zone.present? } validate :unique_email validate :unique_nickname diff --git a/decidim-core/app/models/decidim/user.rb b/decidim-core/app/models/decidim/user.rb index db6734b06c0fc..09e5568faf563 100644 --- a/decidim-core/app/models/decidim/user.rb +++ b/decidim-core/app/models/decidim/user.rb @@ -45,6 +45,7 @@ def self.all validates :tos_agreement, acceptance: true, allow_nil: false, on: :create validates :tos_agreement, acceptance: true, if: :user_invited? validates :email, :nickname, uniqueness: { scope: :organization }, unless: -> { deleted? || managed? || nickname.blank? } + validates :time_zone, time_zone: true, if: -> { time_zone.present? } validate :all_roles_are_valid diff --git a/decidim-core/app/packs/src/decidim/diff_mode_dropdown.js b/decidim-core/app/packs/src/decidim/diff_mode_dropdown.js index 04ac309f50165..8fd7e5f87d213 100644 --- a/decidim-core/app/packs/src/decidim/diff_mode_dropdown.js +++ b/decidim-core/app/packs/src/decidim/diff_mode_dropdown.js @@ -3,8 +3,10 @@ $(() => { $(document).on("click", ".diff-view-by a.diff-view-mode", (event) => { event.preventDefault(); - const $target = $(event.target) - let type = "escaped"; + const $target = $(event.target); + const $container = $target.closest(".tabs-panel"); + const $unified = $container.find(".diff_view_unified") + const $split = $container.find(".diff_view_split") const $selected = $target.parents(".is-dropdown-submenu-parent").find("#diff-view-selected"); if ($selected.text().trim() === $target.text().trim()) { return; @@ -12,38 +14,13 @@ $(() => { $selected.text($target.text()); - if ($target.attr("id") === "diff-view-unified") { - if ($(".row.diff_view_split_escaped").hasClass("hide")) { - type = "unescaped"; - } - - $allDiffViews.addClass("hide"); - $(`.row.diff_view_unified_${type}`).removeClass("hide"); - } - if ($target.attr("id") === "diff-view-split") { - if ($(".row.diff_view_unified_escaped").hasClass("hide")) { - type = "unescaped"; - } - - $allDiffViews.addClass("hide"); - $(`.row.diff_view_split_${type}`).removeClass("hide"); - } - }) - - $(document).on("click", ".diff-view-by a.diff-view-html", (event) => { - event.preventDefault(); - const $target = $(event.target); - $target.parents(".is-dropdown-submenu-parent").find("#diff-view-html-selected").text($target.text()); - const $visibleDiffViewsId = $allDiffViews.not(".hide").first().attr("id").split("_").slice(1, -1).join("_"); - const $visibleDiffViews = $allDiffViews.filter(`[id*=${$visibleDiffViewsId}]`) - - if ($target.attr("id") === "diff-view-escaped-html") { - $visibleDiffViews.filter("[id$=_unescaped]").addClass("hide"); - $visibleDiffViews.filter("[id$=_escaped]").removeClass("hide"); + if ($target.hasClass("diff-view-unified")) { + $split.addClass("hide"); + $unified.removeClass("hide"); } - if ($target.attr("id") === "diff-view-unescaped-html") { - $visibleDiffViews.filter("[id$=_escaped]").addClass("hide"); - $visibleDiffViews.filter("[id$=_unescaped]").removeClass("hide"); + if ($target.hasClass("diff-view-split")) { + $unified.addClass("hide"); + $split.removeClass("hide"); } }) -}); +}); \ No newline at end of file diff --git a/decidim-core/app/packs/stylesheets/decidim/modules/_versions.scss b/decidim-core/app/packs/stylesheets/decidim/modules/_versions.scss index 1219712295e9a..308feba12fc21 100644 --- a/decidim-core/app/packs/stylesheets/decidim/modules/_versions.scss +++ b/decidim-core/app/packs/stylesheets/decidim/modules/_versions.scss @@ -1,3 +1,55 @@ +.versions-diff{ + .versions-selector{ + border-bottom: $border; + padding-bottom: $global-margin * .5; + color: $muted; + font-size: rem-calc(19); + display: flex; + align-items: center; + + //Override foundation + .tabs{ + width: 100%; + + @include flexgap(.5rem); + } + } + + //Override foundation + .tabs, + .tabs-content{ + background: transparent; + } + + //Override foundation + .tabs-title{ + > a{ + padding: 0; + + &:hover{ + background: transparent; + } + } + + > a[aria-selected='true']{ + background: transparent; + } + + &:not(.is-active) .button{ + opacity: .4; + } + } + + .diff-preview{ + margin: 1em 0; + + .heading3{ + font-size: 1.5em; + margin-bottom: 1em; + } + } +} + .diff-direction-label{ display: block; font-size: 85%; @@ -13,7 +65,6 @@ padding: 0; width: 100%; background: transparent; - min-height: 2.7rem; } del, @@ -28,19 +79,23 @@ text-decoration: none; } - del strong{ + .del strong, + .del strong *{ font-weight: normal; - background: scale-color($color-removal, $lightness: -8%); + background: scale-color($color-removal, $lightness: -10%); + display: inline; } - ins strong{ + .ins strong, + .ins strong *{ font-weight: normal; - background: scale-color($color-addition, $lightness: -8%); + background: scale-color($color-addition, $lightness: -10%); + display: inline; } li{ position: relative; - padding: .5rem 1rem .5rem 1.5rem; + padding: .1rem 1rem .1rem 1.5rem; margin: 0; &.ins, @@ -48,18 +103,18 @@ .symbol{ position: absolute; left: .5rem; - top: .5rem; + top: .1rem; width: 1rem; } } &.ins{ - background: $color-addition; + background: scale-color($color-addition, $lightness: 30%, $saturation: 30%); color: scale-color($color-addition, $lightness: -75%, $saturation: -75%); } &.del{ - background: $color-removal; + background: scale-color($color-removal, $lightness: 30%, $saturation: 30%); color: scale-color($color-removal, $lightness: -75%, $saturation: -75%); } @@ -71,4 +126,4 @@ background: none repeat scroll 0 0 gray; } } -} +} \ No newline at end of file diff --git a/decidim-core/app/services/decidim/base_diff_renderer.rb b/decidim-core/app/services/decidim/base_diff_renderer.rb index cc3997410e53b..049fc068e43d8 100644 --- a/decidim-core/app/services/decidim/base_diff_renderer.rb +++ b/decidim-core/app/services/decidim/base_diff_renderer.rb @@ -28,6 +28,11 @@ def diff end end + # implement if item can be previewed + def preview + nil + end + private attr_reader :version diff --git a/decidim-core/app/views/decidim/account/show.html.erb b/decidim-core/app/views/decidim/account/show.html.erb index 14207f164c0cd..e1e7abcc5cd37 100644 --- a/decidim-core/app/views/decidim/account/show.html.erb +++ b/decidim-core/app/views/decidim/account/show.html.erb @@ -25,6 +25,9 @@ ) %>

<%= t(".available_locales_helper") %>

+ <%= f.time_zone_select :time_zone, nil, default: organization_time_zone %> +

<%= t(".time_zone_helper") %>

+ <% if @account.errors[:password].any? || @account.errors[:password_confirmation].any? %> <%= render partial: "password_fields", locals: { form: f } %> <% else %> diff --git a/decidim-core/config/locales/en.yml b/decidim-core/config/locales/en.yml index 6362fdad07288..35c100747641a 100644 --- a/decidim-core/config/locales/en.yml +++ b/decidim-core/config/locales/en.yml @@ -139,6 +139,7 @@ en: show: available_locales_helper: Choose the language you want to use to browse and receive notifications in Decidim change_password: Change password + time_zone_helper: Use your personal time zone to display dates in your local time when you are logged in update_account: Update account update: error: There was a problem updating your account. @@ -1733,6 +1734,10 @@ en: short: "%d/%m/%Y %H:%M" time_of_day: "%H:%M" versions: + tabs: + text: Text diff + source: Source diff + preview: Preview directions: left: Deletions right: Additions diff --git a/decidim-core/db/migrate/20220823094517_add_time_zone_to_users.rb b/decidim-core/db/migrate/20220823094517_add_time_zone_to_users.rb new file mode 100644 index 0000000000000..a3728c50a047a --- /dev/null +++ b/decidim-core/db/migrate/20220823094517_add_time_zone_to_users.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddTimeZoneToUsers < ActiveRecord::Migration[6.0] + def change + add_column :decidim_users, :time_zone, :string + end +end diff --git a/decidim-core/lib/decidim/diffy_extension.rb b/decidim-core/lib/decidim/diffy_extension.rb deleted file mode 100644 index 9f29facda60a0..0000000000000 --- a/decidim-core/lib/decidim/diffy_extension.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Decidim - # Extending Diffy gem to accomodate the needs of app/cells/decidim/diff_cell.rb - module DiffyExtension - # HtmlFormatter that returns basic html output (no inline highlighting) - # and does not escape HTML tags. - class UnescapedHtmlFormatter < Diffy::HtmlFormatter - # We exclude the tags `del` and `ins` so the diffy styling does not apply. - TAGS = (UserInputScrubber.new.tags.to_a - %w(del ins)).freeze - - def to_s - str = wrap_lines(@diff.map { |line| wrap_line(line) }) - ActionView::Base.new(ActionView::LookupContext.new(nil), {}, nil).sanitize(str, tags: TAGS) - end - end - - # Adding a new method to Diffy::Format so we can pass the - # `:unescaped_html` option when calling Diffy::Diff#to_s. - Diffy::Format.module_eval do - def unescaped_html - UnescapedHtmlFormatter.new(self, options).to_s - end - end - - # The private "split" method SplitDiff needs to be overriden to take into - # account the new :unescaped_html format, and the fact that the tags - # are not there anymore - Diffy::SplitDiff.module_eval do - private - - def split - return [split_left, split_right] unless @format == :unescaped_html - - [unescaped_split_left, unescaped_split_right] - end - - def unescaped_split_left - @diff.gsub(%r{
  • ([\s\S]*?)
  • }, "") - end - - def unescaped_split_right - @diff.gsub(%r{
  • ([\s\S]*?)
  • }, "") - end - end - end -end diff --git a/decidim-core/spec/commands/decidim/update_account_spec.rb b/decidim-core/spec/commands/decidim/update_account_spec.rb index 889e259210dbe..895024da94790 100644 --- a/decidim-core/spec/commands/decidim/update_account_spec.rb +++ b/decidim-core/spec/commands/decidim/update_account_spec.rb @@ -6,6 +6,7 @@ module Decidim describe UpdateAccount do let(:command) { described_class.new(user, form) } let(:user) { create(:user, :confirmed) } + let(:time_zone) { "UTC" } let(:data) do { name: user.name, @@ -17,7 +18,8 @@ module Decidim remove_avatar: nil, personal_url: "https://example.org", about: "This is a description of me", - locale: "es" + locale: "es", + time_zone: time_zone } end @@ -32,7 +34,8 @@ module Decidim remove_avatar: data[:remove_avatar], personal_url: data[:personal_url], about: data[:about], - locale: data[:locale] + locale: data[:locale], + time_zone: data[:time_zone] ).with_context(current_organization: user.organization, current_user: user) end @@ -47,6 +50,14 @@ module Decidim expect { command.call }.to broadcast(:invalid) expect(user.reload.name).to eq(old_name) end + + context "when timezone is invalid" do + let(:time_zone) { "giberish" } + + it "returns invalid" do + expect { command.call }.to broadcast(:invalid) + end + end end context "when valid" do @@ -77,6 +88,22 @@ module Decidim expect(user.reload.locale).to eq("es") end + context "when timezone is defined" do + it "updates the time zone" do + expect { command.call }.to broadcast(:ok) + expect(user.reload.time_zone).to eq("UTC") + end + end + + context "when timezone is not defined" do + let(:time_zone) { "" } + + it "updates the time zone" do + expect { command.call }.to broadcast(:ok) + expect(user.reload.time_zone).to eq("") + end + end + describe "updating the email" do before do form.email = "new@email.com" diff --git a/decidim-core/spec/controllers/concerns/use_organization_time_zone_spec.rb b/decidim-core/spec/controllers/concerns/use_organization_time_zone_spec.rb index d548998a53bd2..20aab8ae23181 100644 --- a/decidim-core/spec/controllers/concerns/use_organization_time_zone_spec.rb +++ b/decidim-core/spec/controllers/concerns/use_organization_time_zone_spec.rb @@ -7,9 +7,12 @@ module Decidim let(:utc_time_zone) { "UTC" } let(:alt_time_zone) { "Hawaii" } let(:organization) { create(:organization, time_zone: time_zone) } + let(:user) { create :user, :confirmed, organization: organization, time_zone: user_time_zone } + let(:user_time_zone) { "" } before do request.env["decidim.current_organization"] = organization + allow(controller).to receive(:current_user) { user } end context "when time zone is UTC" do @@ -85,5 +88,42 @@ module Decidim end end end + + context "when time zone is defined by the user" do + let(:time_zone) { utc_time_zone } + let(:user_time_zone) { "London" } + + it "controller uses London" do + expect(controller.organization_time_zone).to eq("London") + end + + it "Time uses UTC zone within the controller scope" do + controller.use_organization_time_zone do + expect(Time.zone.name).to eq("London") + end + end + + it "Time uses Rails timezone outside the controller scope" do + expect(Time.zone.name).to eq("UTC") + end + end + + context "when user's time zone in not present" do + let(:time_zone) { utc_time_zone } + let(:user_time_zone) { "" } + + it "controller uses time zone of organization" do + expect(controller.organization_time_zone).to eq(utc_time_zone) + end + end + + context "when user is not present" do + let(:time_zone) { utc_time_zone } + let(:user) { nil } + + it "controller uses time zone of organization" do + expect(controller.organization_time_zone).to eq(utc_time_zone) + end + end end end diff --git a/decidim-core/spec/forms/account_form_spec.rb b/decidim-core/spec/forms/account_form_spec.rb index a10d8e8d3dc2c..79e3c57595085 100644 --- a/decidim-core/spec/forms/account_form_spec.rb +++ b/decidim-core/spec/forms/account_form_spec.rb @@ -15,7 +15,8 @@ module Decidim remove_avatar: remove_avatar, personal_url: personal_url, about: about, - locale: "es" + locale: "es", + time_zone: time_zone ).with_context( current_organization: organization, current_user: user @@ -34,6 +35,7 @@ module Decidim let(:remove_avatar) { false } let(:personal_url) { "http://example.org" } let(:about) { "This is a description about me" } + let(:time_zone) { "UTC" } context "with correct data" do it "is valid" do @@ -172,5 +174,23 @@ module Decidim end end end + + describe "time_zone" do + context "when an empty time_zone" do + let(:time_zone) { "" } + + it "is invalid" do + expect(subject).to be_valid + end + end + + context "when time_zone has more 255 chars" do + let(:time_zone) { [*("A".."Z")].sample(256).join } + + it "is invalid" do + expect(subject).not_to be_valid + end + end + end end end diff --git a/decidim-proposals/app/helpers/decidim/proposals/application_helper.rb b/decidim-proposals/app/helpers/decidim/proposals/application_helper.rb index 1c3badce0dca3..c21dac4cf9d0c 100644 --- a/decidim-proposals/app/helpers/decidim/proposals/application_helper.rb +++ b/decidim-proposals/app/helpers/decidim/proposals/application_helper.rb @@ -8,6 +8,7 @@ module ApplicationHelper include Decidim::Comments::CommentsHelper include PaginateHelper include ProposalVotesHelper + include ProposalPresenterHelper include ::Decidim::EndorsableHelper include ::Decidim::FollowableHelper include Decidim::MapHelper diff --git a/decidim-proposals/app/helpers/decidim/proposals/proposal_presenter_helper.rb b/decidim-proposals/app/helpers/decidim/proposals/proposal_presenter_helper.rb new file mode 100644 index 0000000000000..a5f34f319fbd0 --- /dev/null +++ b/decidim-proposals/app/helpers/decidim/proposals/proposal_presenter_helper.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Decidim + module Proposals + module ProposalPresenterHelper + include Decidim::ApplicationHelper + + def not_from_collaborative_draft(proposal) + proposal.linked_resources(:proposals, "created_from_collaborative_draft").empty? + end + + def not_from_participatory_text(proposal) + proposal.participatory_text_level.nil? + end + + # If the proposal is official or the rich text editor is enabled on the + # frontend, the proposal body is considered as safe content; that's unless + # the proposal comes from a collaborative_draft or a participatory_text. + def safe_content? + rich_text_editor_in_public_views? && not_from_collaborative_draft(@proposal) || + (@proposal.official? || @proposal.official_meeting?) && not_from_participatory_text(@proposal) + end + + def render_proposal_title(proposal) + Decidim::Proposals::ProposalPresenter.new(proposal).title(links: true, html_escape: true) + end + + # If the content is safe, HTML tags are sanitized, otherwise, they are stripped. + def render_proposal_body(proposal) + Decidim::ContentProcessor.render(render_sanitized_content(proposal, :body), "div") + end + end + end +end diff --git a/decidim-proposals/app/services/decidim/proposals/diff_renderer.rb b/decidim-proposals/app/services/decidim/proposals/diff_renderer.rb index aa607cf606bd1..9ca60f57dddfc 100644 --- a/decidim-proposals/app/services/decidim/proposals/diff_renderer.rb +++ b/decidim-proposals/app/services/decidim/proposals/diff_renderer.rb @@ -3,6 +3,19 @@ module Decidim module Proposals class DiffRenderer < BaseDiffRenderer + include ActionView::Helpers::TextHelper + include ActionView::Helpers::TagHelper + include ProposalPresenterHelper + include SanitizeHelper + + delegate :organization, to: :proposal, prefix: :current + + def preview + title = content_tag(:h3, render_proposal_title(proposal), class: "heading3") + body = content_tag(:div, render_proposal_body(proposal), class: "body") + content_tag(:div, "#{title}#{body}".html_safe, class: "diff-preview diff-proposal") + end + private # Lists which attributes will be diffable and how they should be rendered.