decidim-core 0.24.0.rc1 → 0.24.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of decidim-core might be problematic. Click here for more details.

Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim.js.es6 +1 -0
  3. data/app/assets/javascripts/decidim/editor/linebreak_module.js.es6 +104 -0
  4. data/app/assets/javascripts/decidim/editor/modified_enter.js.es6 +15 -10
  5. data/app/assets/javascripts/decidim/gallery.js.es6 +5 -0
  6. data/app/assets/javascripts/decidim/map/controller/markers.js.es6 +13 -1
  7. data/app/cells/decidim/activity/show.erb +1 -1
  8. data/app/cells/decidim/activity_cell.rb +6 -0
  9. data/app/cells/decidim/address/details.erb +5 -5
  10. data/app/cells/decidim/address_cell.rb +21 -0
  11. data/app/cells/decidim/announcement_cell.rb +22 -7
  12. data/app/cells/decidim/content_blocks/hero_cell.rb +2 -2
  13. data/app/cells/decidim/content_blocks/last_activity_cell.rb +1 -1
  14. data/app/commands/decidim/destroy_account.rb +2 -0
  15. data/app/controllers/concerns/decidim/devise_controllers.rb +1 -0
  16. data/app/controllers/concerns/decidim/has_stored_path.rb +40 -0
  17. data/app/controllers/concerns/decidim/paginable.rb +8 -1
  18. data/app/controllers/decidim/application_controller.rb +1 -30
  19. data/app/forms/decidim/account_form.rb +1 -1
  20. data/app/helpers/decidim/application_helper.rb +1 -0
  21. data/app/helpers/decidim/cache_helper.rb +14 -0
  22. data/app/helpers/decidim/filters_helper.rb +0 -9
  23. data/app/helpers/decidim/map_helper.rb +1 -1
  24. data/app/mailers/concerns/decidim/localised_mailer.rb +4 -2
  25. data/app/mailers/decidim/notification_mailer.rb +0 -2
  26. data/app/models/decidim/follow.rb +1 -1
  27. data/app/models/decidim/user.rb +7 -1
  28. data/app/models/decidim/user_base_entity.rb +1 -1
  29. data/app/models/decidim/user_group.rb +4 -0
  30. data/app/queries/decidim/user_groups/accepted_memberships.rb +1 -1
  31. data/app/queries/decidim/user_groups/admin_memberships.rb +1 -1
  32. data/app/queries/decidim/user_groups/member_memberships.rb +1 -1
  33. data/app/views/decidim/application/_document.html.erb +1 -1
  34. data/app/views/decidim/newsletters/show.html.erb +1 -1
  35. data/app/views/decidim/reported_mailer/report.html.erb +9 -1
  36. data/app/views/decidim/searches/_filters_small_view.html.erb +1 -1
  37. data/config/locales/ca.yml +51 -13
  38. data/config/locales/cs.yml +43 -43
  39. data/config/locales/de.yml +5 -9
  40. data/config/locales/el.yml +0 -4
  41. data/config/locales/en.yml +2 -2
  42. data/config/locales/es-MX.yml +52 -2
  43. data/config/locales/es-PY.yml +52 -2
  44. data/config/locales/es.yml +55 -5
  45. data/config/locales/eu.yml +3 -3
  46. data/config/locales/fi-plain.yml +6 -6
  47. data/config/locales/fi.yml +11 -11
  48. data/config/locales/fr-CA.yml +0 -4
  49. data/config/locales/fr.yml +1 -5
  50. data/config/locales/gl.yml +4 -0
  51. data/config/locales/is-IS.yml +7 -0
  52. data/config/locales/it.yml +6 -10
  53. data/config/locales/ja.yml +0 -3
  54. data/config/locales/nl.yml +2 -2
  55. data/config/locales/no.yml +0 -4
  56. data/config/locales/pl.yml +8 -14
  57. data/config/locales/pt.yml +0 -4
  58. data/config/locales/ro-RO.yml +88 -6
  59. data/config/locales/ru.yml +4 -6
  60. data/config/locales/sk.yml +1 -1
  61. data/config/locales/sv.yml +77 -2
  62. data/config/locales/tr-TR.yml +2 -6
  63. data/config/locales/uk.yml +4 -6
  64. data/config/locales/zh-CN.yml +0 -3
  65. data/db/migrate/20201127114444_encrypt_authorization_metadatas.rb +1 -1
  66. data/db/migrate/20210302150803_invalidate_all_sessions_for_deleted_users.rb +11 -0
  67. data/db/migrate/20210310120640_add_followable_counter_cache_to_users.rb +16 -0
  68. data/lib/decidim/api/interfaces/categorizable_interface.rb +1 -1
  69. data/lib/decidim/attribute_encryptor.rb +9 -1
  70. data/lib/decidim/core/test.rb +0 -1
  71. data/lib/decidim/core/test/shared_examples/categorizable_interface_examples.rb +14 -6
  72. data/lib/decidim/core/test/shared_examples/controller_render_views.rb +8 -3
  73. data/lib/decidim/core/test/shared_examples/localised_email.rb +8 -0
  74. data/lib/decidim/core/version.rb +1 -1
  75. data/lib/decidim/exporters/csv.rb +1 -1
  76. data/lib/decidim/followable.rb +6 -1
  77. data/lib/decidim/geocodable.rb +4 -0
  78. data/lib/decidim/record_encryptor.rb +20 -2
  79. data/lib/decidim/reportable.rb +2 -2
  80. metadata +17 -13
  81. data/lib/decidim/core/test/shared_examples/user_localised_email_examples.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba53626a99146ce44d79185a60c063ee183c5ddf2e3c8a16ace87d59b8d5096a
4
- data.tar.gz: db2b391dc61a51d6418b03b11672bc89c275b87b3b20325b95ce97eb9cbba386
3
+ metadata.gz: f3951a2b1d212641b4f8d6d237c46f04a51acdba30baaf937d051ddc1a5a657b
4
+ data.tar.gz: 91ede7442a9ba6ccc60328bcea6384962e93ebffa195efca31a0ba27aae35eac
5
5
  SHA512:
6
- metadata.gz: 71cf88667385e031e66d0b5b2d82c2d487daa89987e66e256a87ecdbeab75fed3aa54a5ab1c6387d9efa7d97a4df9e86a4f83506fcb0e2c5180edc2837a3ee90
7
- data.tar.gz: 9483f050bf203aee28dcb885ff797a384d73189ea71b167e621335d1f35fe379c2291cdc914849698582bdc10c9ec039ea3d4df03e99fa9b43ba7491e57a4c72
6
+ metadata.gz: dd77e400d4d79c85b294f128a401f9408e2b8acf964cba301cf88cb53ca4305683bebf735e3e66092a3f7a66c774f9be3db6244eae9367b90c54e6a51ddfb4d5
7
+ data.tar.gz: 9fb9dede7ce22aebcb6d83fd83e076d1926cbaceefecc033fce2ff19d4837a759abb82dc67caf5438435d1e868df77c1481d0e59d28952f0a4942dfe1a4f0675
@@ -39,6 +39,7 @@
39
39
  // = require decidim/geocoding/attach_input
40
40
  // = require decidim/security/selfxss_warning
41
41
  // = require decidim/session_timeouter
42
+ // = require decidim/gallery
42
43
 
43
44
  // = require_self
44
45
  // = require decidim/configuration
@@ -10,6 +10,8 @@
10
10
  const Delta = Quill.import("delta");
11
11
  const Break = Quill.import("blots/break");
12
12
  const Embed = Quill.import("blots/embed");
13
+ const Scroll = Quill.import("blots/scroll");
14
+ const Parchment = Quill.import("parchment");
13
15
  const { HistoryOverride } = exports.Decidim.Editor
14
16
  Quill.register({"modules/history": HistoryOverride}, true)
15
17
  let icons = Quill.import("ui/icons");
@@ -31,6 +33,101 @@
31
33
  }
32
34
  Quill.register(SmartBreak);
33
35
 
36
+ // Override quill/blots/scroll.js
37
+ class ScrollOvderride extends Scroll {
38
+ optimize(mutations = [], context = {}) {
39
+ if (this.batch === true) {
40
+ return;
41
+ }
42
+
43
+ this.parchmentOptimize(mutations, context);
44
+
45
+ if (mutations.length > 0) {
46
+ // quill/core/emitter.js, Emitter.events.SCROLL_OPTIMIZE = "scroll-optimize"
47
+ this.emitter.emit("scroll-optimize", mutations, context);
48
+ }
49
+ }
50
+
51
+ // Override parchment/src/blot/scroll.ts
52
+ parchmentOptimize(mutations = [], context = {}) {
53
+ // super.optimize(context);
54
+ Reflect.apply(Parchment.Container.prototype.optimize, this, [context]);
55
+
56
+ // We must modify mutations directly, cannot make copy and then modify
57
+ // let records = [].slice.call(this.observer.takeRecords());
58
+ let records = [...this.observer.takeRecords()];
59
+ // Array.push currently seems to be implemented by a non-tail recursive function
60
+ // so we cannot just mutations.push.apply(mutations, this.observer.takeRecords());
61
+ while (records.length > 0) {
62
+ mutations.push(records.pop());
63
+ }
64
+ let mark = (blot, markParent) => {
65
+ if (!blot || blot === this) {
66
+ return;
67
+ }
68
+ if (!blot.domNode.parentNode) {
69
+ return;
70
+ }
71
+ if (blot.domNode.__blot && blot.domNode.__blot.mutations === null) {
72
+ blot.domNode.__blot.mutations = [];
73
+ }
74
+ if (markParent) {
75
+ mark(blot.parent);
76
+ }
77
+ };
78
+ let optimize = (blot) => {
79
+ // Post-order traversal
80
+ if (!blot.domNode.__blot) {
81
+ return;
82
+ }
83
+
84
+ if (blot instanceof Parchment.Container) {
85
+ blot.children.forEach(optimize);
86
+ }
87
+ blot.optimize(context);
88
+ };
89
+ let remaining = mutations;
90
+ for (let ind = 0; remaining.length > 0; ind += 1) {
91
+ // MAX_OPTIMIZE_ITERATIONS = 100
92
+ if (ind >= 100) {
93
+ throw new Error("[Parchment] Maximum optimize iterations reached");
94
+ }
95
+ remaining.forEach((mutation) => {
96
+ let blot = Parchment.find(mutation.target, true);
97
+ if (!blot) {
98
+ return;
99
+ }
100
+ if (blot.domNode === mutation.target) {
101
+ if (mutation.type === "childList") {
102
+ mark(Parchment.find(mutation.previousSibling, false));
103
+
104
+ mutation.addedNodes.forEach((node) => {
105
+ let child = Parchment.find(node, false);
106
+ mark(child, false);
107
+ if (child instanceof Parchment.Container) {
108
+ child.children.forEach(function(grandChild) {
109
+ mark(grandChild, false);
110
+ });
111
+ }
112
+ });
113
+ } else if (mutation.type === "attributes") {
114
+ mark(blot.prev);
115
+ }
116
+ }
117
+ mark(blot);
118
+ });
119
+ this.children.forEach(optimize);
120
+ remaining = [...this.observer.takeRecords()];
121
+ records = remaining.slice();
122
+ while (records.length > 0) {
123
+ mutations.push(records.pop());
124
+ }
125
+ }
126
+ }
127
+ }
128
+ Quill.register("blots/scroll", ScrollOvderride, true);
129
+ Parchment.register(ScrollOvderride);
130
+
34
131
  const lineBreakButtonHandler = (quill) => {
35
132
  let range = quill.selection.getRange()[0];
36
133
  let currentLeaf = quill.getLeaf(range.index)[0];
@@ -72,6 +169,13 @@
72
169
  }
73
170
  });
74
171
 
172
+ quill.clipboard.addMatcher("BR", (node) => {
173
+ if (node && node.parentNode && node.parentNode.tagName && node.parentNode.tagName === "A") {
174
+ return new Delta().insert("\n");
175
+ }
176
+ return new Delta().insert({"break": ""});
177
+ });
178
+
75
179
  addEnterBindings(quill);
76
180
  backspaceBindingsRangeAny(quill);
77
181
  backspaceBindings(quill);
@@ -40,17 +40,28 @@
40
40
  const currentLeaf = quill.getLeaf(range.index)[0];
41
41
  const nextLeaf = quill.getLeaf(range.index + 1)[0];
42
42
  const previousChar = quill.getText(range.index - 1, 1);
43
+ const formats = quill.getFormat(range.index);
43
44
 
44
- quill.insertEmbed(range.index, "break", true, "user");
45
- quill.formatText(range.index + 1, "bold", true)
46
- if (nextLeaf === null || (currentLeaf.parent !== nextLeaf.parent)) {
45
+ if ((currentLeaf && currentLeaf.next && currentLeaf.next.domNode &&
46
+ currentLeaf.next.domNode.tagName && currentLeaf.next.domNode.tagName === "A") ||
47
+ (nextLeaf && nextLeaf.parent && nextLeaf.parent.domNode && nextLeaf.parent.domNode.tagName &&
48
+ nextLeaf.parent.domNode.tagName === "A")) {
49
+ quill.insertEmbed(range.index, "break", true, "user");
50
+ quill.removeFormat(range.index, 1, Quill.sources.SILENT)
51
+ } else {
52
+ quill.insertEmbed(range.index, "break", true, "user");
53
+ }
54
+
55
+ if (nextLeaf === null) {
47
56
  quill.insertEmbed(range.index, "break", true, "user");
48
57
  } else if (context.offset === 1 && previousChar === "\n") {
49
58
  const delta = new Delta().retain(range.index).insert("\n");
50
59
  quill.updateContents(delta, Quill.sources.USER);
51
60
  }
52
61
 
53
- quill.format(name, context.format[name], Quill.sources.USER);
62
+ Object.keys(formats).forEach((format) => {
63
+ quill.format(format, context.format[format], Quill.sources.USER);
64
+ });
54
65
  quill.setSelection(range.index + 1, Quill.sources.SILENT);
55
66
 
56
67
  const lineFormats = getLineFormats(context);
@@ -58,12 +69,6 @@
58
69
  };
59
70
 
60
71
  const addEnterBindings = (quill) => {
61
- quill.clipboard.addMatcher("BR", () => {
62
- let newDelta = new Delta();
63
- newDelta.insert({"break": ""});
64
- return newDelta;
65
- });
66
-
67
72
  quill.keyboard.addBinding({
68
73
  key: 13,
69
74
  shiftKey: true
@@ -0,0 +1,5 @@
1
+ $(() => {
2
+ $(".gallery__container").on("closed.zf.callout", (event) => {
3
+ $(event.target).remove();
4
+ });
5
+ });
@@ -69,7 +69,19 @@
69
69
  this.markerClusters.addLayer(marker);
70
70
  });
71
71
 
72
- this.map.fitBounds(bounds, { padding: [100, 100] });
72
+ // Make sure there is enough space in the map for the padding to be
73
+ // applied. Otherwise the map will automatically zoom out (test it on
74
+ // mobile). Make sure there is at least the same amount of width and
75
+ // height available on both sides + the padding (i.e. 4x padding in
76
+ // total).
77
+ const size = this.map.getSize();
78
+ if (size.y >= 400 && size.x >= 400) {
79
+ this.map.fitBounds(bounds, { padding: [100, 100] });
80
+ } else if (size.y >= 120 && size.x >= 120) {
81
+ this.map.fitBounds(bounds, { padding: [30, 30] });
82
+ } else {
83
+ this.map.fitBounds(bounds);
84
+ }
73
85
  }
74
86
 
75
87
  clearMarkers() {
@@ -1,4 +1,4 @@
1
- <div class="card card--activity">
1
+ <div class="card card--activity" id="<%= element_id %>">
2
2
  <div class="card__content">
3
3
  <div class="card__text">
4
4
  <div class="text-medium">
@@ -84,6 +84,12 @@ module Decidim
84
84
  model.user_lazy if resource.respond_to?(:user)
85
85
  end
86
86
 
87
+ delegate :action, to: :model
88
+
89
+ def element_id
90
+ "action-#{model.id}"
91
+ end
92
+
87
93
  private
88
94
 
89
95
  def published?
@@ -1,7 +1,7 @@
1
- <% if model.respond_to? :location %>
2
- <strong><%= translated_attribute model.location %></strong><br>
1
+ <% if has_location? %>
2
+ <strong><%= location %></strong><br>
3
3
  <% end %>
4
- <span><%= model.address %></span><br>
5
- <% if model.respond_to? :location_hints %>
6
- <span><%= translated_attribute model.location_hints %></span>
4
+ <span><%= address %></span><br>
5
+ <% if has_location_hints? %>
6
+ <span><%= location_hints %></span>
7
7
  <% end %>
@@ -5,11 +5,32 @@ module Decidim
5
5
  class AddressCell < Decidim::ViewModel
6
6
  include Cell::ViewModel::Partial
7
7
  include LayoutHelper
8
+ include Decidim::SanitizeHelper
8
9
 
9
10
  def details
10
11
  render
11
12
  end
12
13
 
14
+ def has_location?
15
+ model.respond_to?(:location)
16
+ end
17
+
18
+ def has_location_hints?
19
+ model.respond_to?(:location_hints)
20
+ end
21
+
22
+ def location_hints
23
+ decidim_sanitize(translated_attribute(model.location_hints))
24
+ end
25
+
26
+ def location
27
+ decidim_sanitize(translated_attribute(model.location))
28
+ end
29
+
30
+ def address
31
+ decidim_sanitize(translated_attribute(model.address))
32
+ end
33
+
13
34
  private
14
35
 
15
36
  def resource_icon
@@ -2,20 +2,27 @@
2
2
 
3
3
  module Decidim
4
4
  # This cell renders an announcement
5
- # the `model` is spected to be a Hash with two keys:
6
- # `announcement` is mandatory, its the message to show
7
- # `callout_class` is optional, the css class modifier
5
+ #
6
+ # The `model` is expected to be a Hash with two keys:
7
+ # - `body` is mandatory, its the message to show
8
+ # - `title` is mandatory, a title to show
8
9
  #
9
10
  # {
10
- # announcement: { ... },
11
- # callout_class: "warning"
11
+ # title: "...", # mandatory
12
+ # body: "..." # mandatory
12
13
  # }
13
14
  #
15
+ # It can also receive a single value to show as text. It can either be a String
16
+ # or a value accepted by the `translated_attribute` method.
17
+ #
18
+ # As options, the cell accepts a Hash with these keys:
19
+ # - `callout_class`: The Css class to apply. Default to `"secondary"`
20
+ #
14
21
  class AnnouncementCell < Decidim::ViewModel
15
22
  include Decidim::SanitizeHelper
16
23
 
17
24
  def show
18
- return unless announcement.presence
25
+ return if clean_body.blank? && clean_announcement.blank?
19
26
 
20
27
  render :show
21
28
  end
@@ -38,8 +45,16 @@ module Decidim
38
45
  clean(announcement[:title])
39
46
  end
40
47
 
48
+ def body
49
+ return announcement.presence unless announcement.is_a?(Hash)
50
+
51
+ announcement[:body].presence
52
+ end
53
+
41
54
  def clean_body
42
- Array(announcement[:body]).map { |paragraph| tag.p(clean(paragraph)) }.join("")
55
+ return unless body
56
+
57
+ Array(body).map { |paragraph| tag.p(clean(paragraph)) }.join("")
43
58
  end
44
59
 
45
60
  def clean_announcement
@@ -22,12 +22,12 @@ module Decidim
22
22
  private
23
23
 
24
24
  # A MD5 hash of model attributes because is needed because
25
- # the model doesn't respond to cache_version nor updated_at method
25
+ # the model doesn't respond to cache_key_with_version nor updated_at method
26
26
  def cache_hash
27
27
  hash = []
28
28
  hash << "decidim/content_blocks/hero"
29
29
  hash << Digest::MD5.hexdigest(model.attributes.to_s)
30
- hash << current_organization.cache_version
30
+ hash << current_organization.cache_key_with_version
31
31
  hash << I18n.locale.to_s
32
32
 
33
33
  hash.join("/")
@@ -49,7 +49,7 @@ module Decidim
49
49
  def cache_hash
50
50
  hash = []
51
51
  hash << "decidim/content_blocks/last_activity"
52
- hash << Digest::MD5.hexdigest(valid_activities.map(&:updated_at).to_s)
52
+ hash << Digest::MD5.hexdigest(valid_activities.map(&:cache_key_with_version).to_s)
53
53
  hash << I18n.locale.to_s
54
54
 
55
55
  hash.join("/")
@@ -30,6 +30,8 @@ module Decidim
30
30
  private
31
31
 
32
32
  def destroy_user_account!
33
+ @user.invalidate_all_sessions!
34
+
33
35
  @user.name = ""
34
36
  @user.nickname = ""
35
37
  @user.email = ""
@@ -30,6 +30,7 @@ module Decidim
30
30
  helper Decidim::LayoutHelper
31
31
  helper Decidim::MenuHelper
32
32
  helper Decidim::OmniauthHelper
33
+ helper Decidim::CacheHelper
33
34
 
34
35
  layout "layouts/decidim/application"
35
36
 
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # Shared behaviour for signed_in users that require the latest TOS accepted
5
+ module HasStoredPath
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # Saves the location before loading each page so we can return to the
10
+ # right page.
11
+ before_action :store_current_location
12
+ end
13
+
14
+ # Stores the url where the user will be redirected after login.
15
+ #
16
+ # Uses the `redirect_url` param or the current url if there's no param.
17
+ # In Devise controllers we only store the URL if it's from the params, we don't
18
+ # want to overwrite the stored URL for a Devise one.
19
+ def store_current_location
20
+ return if skip_store_location?
21
+
22
+ value = redirect_url || request.url
23
+ store_location_for(:user, value)
24
+ end
25
+
26
+ def skip_store_location?
27
+ # Skip if Devise already handles the redirection
28
+ return true if devise_controller? && redirect_url.blank?
29
+ # Skip for all non-HTML requests"
30
+ return true unless request.format.html?
31
+ # Skip if a signed in user requests the TOS page without having agreed to
32
+ # the TOS. Most of the times this is because of a redirect to the TOS
33
+ # page (in which case the desired location is somewhere else after the
34
+ # TOS is agreed).
35
+ return true if current_user && !current_user.tos_accepted? && request.path == URI(tos_path).path
36
+
37
+ false
38
+ end
39
+ end
40
+ end
@@ -18,7 +18,14 @@ module Decidim
18
18
  end
19
19
 
20
20
  def per_page
21
- params[:per_page] || OPTIONS.first
21
+ if OPTIONS.include?(params[:per_page])
22
+ params[:per_page]
23
+ elsif params[:per_page]
24
+ sorted = OPTIONS.sort
25
+ params[:per_page].to_i.clamp(sorted.first, sorted.last)
26
+ else
27
+ OPTIONS.first
28
+ end
22
29
  end
23
30
 
24
31
  def page_offset