decidim-decidim_awesome 0.12.5 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -4
  3. data/README.md +3 -2
  4. data/Rakefile +4 -12
  5. data/app/cells/decidim/decidim_awesome/content_blocks/map/show.erb +1 -1
  6. data/app/cells/decidim/decidim_awesome/content_blocks/map_cell.rb +2 -10
  7. data/app/cells/decidim/decidim_awesome/content_blocks/map_form/show.erb +13 -0
  8. data/app/cells/decidim/decidim_awesome/voting/proposal_metadata_cell.rb +1 -1
  9. data/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal/show.erb +1 -1
  10. data/app/cells/decidim/decidim_awesome/voting/voting_cards_proposal_cell.rb +1 -1
  11. data/app/commands/decidim/decidim_awesome/admin/rename_scope_label.rb +2 -0
  12. data/app/controllers/concerns/decidim/decidim_awesome/admin_accountability/admin/filterable.rb +2 -0
  13. data/app/controllers/concerns/decidim/decidim_awesome/needs_hashcash.rb +2 -2
  14. data/app/controllers/decidim/decidim_awesome/admin/admin_accountability_controller.rb +2 -1
  15. data/app/controllers/decidim/decidim_awesome/admin/custom_redirects_controller.rb +1 -1
  16. data/app/controllers/decidim/decidim_awesome/required_authorizations_controller.rb +4 -0
  17. data/app/forms/decidim/decidim_awesome/admin/config_form.rb +1 -1
  18. data/app/helpers/concerns/decidim/decidim_awesome/proposals/application_helper_override.rb +4 -4
  19. data/app/helpers/decidim/decidim_awesome/map_helper.rb +17 -14
  20. data/app/jobs/decidim/decidim_awesome/destroy_private_data_job.rb +1 -1
  21. data/app/jobs/decidim/decidim_awesome/export_admin_actions_job.rb +6 -2
  22. data/app/models/decidim/decidim_awesome/paper_trail_version.rb +21 -8
  23. data/app/overrides/decidim/devise/sessions/new/add_hashcash.html.erb.deface +1 -1
  24. data/app/overrides/decidim/proposals/proposals/_proposal_actions/limit_amendments_modal.html.erb.deface +2 -0
  25. data/app/overrides/decidim/proposals/proposals/_votes_count/replace_counter.html.erb.deface +1 -1
  26. data/app/overrides/decidim/shared/_login_modal/add_hashcash.html.erb.deface +1 -1
  27. data/app/packs/src/decidim/decidim_awesome/amendments/show_modal_on_limits.js +13 -10
  28. data/app/packs/src/decidim/decidim_awesome/awesome_map/api/fetcher.js +6 -5
  29. data/app/packs/src/decidim/decidim_awesome/awesome_map/api/meetings_fetcher.js +1 -1
  30. data/app/packs/src/decidim/decidim_awesome/awesome_map/api/proposals_fetcher.js +1 -1
  31. data/app/packs/src/decidim/decidim_awesome/awesome_map/awesome_map.js +12 -12
  32. data/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/controller.js +11 -8
  33. data/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/meetings_controller.js +1 -1
  34. data/app/packs/src/decidim/decidim_awesome/awesome_map/controllers/proposals_controller.js +6 -6
  35. data/app/packs/src/decidim/decidim_awesome/awesome_map/controls_ui.js +150 -71
  36. data/app/packs/src/decidim/decidim_awesome/awesome_map/load_map.js +1 -1
  37. data/app/packs/stylesheets/decidim/decidim_awesome/admin/admin_accountability.scss +10 -6
  38. data/app/packs/stylesheets/decidim/decidim_awesome/awesome_map/map.scss +39 -6
  39. data/app/presenters/decidim/decidim_awesome/role_base_presenter.rb +3 -1
  40. data/app/presenters/decidim/decidim_awesome/user_entity_presenter.rb +2 -2
  41. data/app/serializers/concerns/decidim/decidim_awesome/proposals/proposal_serializer_methods.rb +3 -3
  42. data/app/serializers/decidim/decidim_awesome/proposals/private_proposal_serializer.rb +1 -1
  43. data/app/validators/concerns/decidim/decidim_awesome/etiquette_validator_override.rb +21 -3
  44. data/app/views/decidim/decidim_awesome/admin/admin_accountability/index.html.erb +5 -8
  45. data/app/views/decidim/decidim_awesome/admin/custom_redirects/_form.html.erb +18 -12
  46. data/app/views/decidim/decidim_awesome/admin/custom_redirects/edit.html.erb +15 -11
  47. data/app/views/decidim/decidim_awesome/admin/custom_redirects/new.html.erb +16 -11
  48. data/app/views/decidim/decidim_awesome/admin/shared/_filters_with_date.html.erb +1 -5
  49. data/app/views/decidim/decidim_awesome/map_component/map/_map_template.html.erb +4 -4
  50. data/app/views/decidim/decidim_awesome/map_component/map/show.html.erb +3 -1
  51. data/app/views/layouts/decidim/decidim_awesome/_awesome_config.html.erb +2 -2
  52. data/config/i18n-tasks.yml +1 -0
  53. data/config/locales/ca.yml +8 -3
  54. data/config/locales/cs.yml +8 -3
  55. data/config/locales/de.yml +8 -3
  56. data/config/locales/en.yml +8 -2
  57. data/config/locales/es.yml +8 -3
  58. data/config/locales/eu.yml +8 -3
  59. data/config/locales/fr.yml +5 -3
  60. data/config/locales/hu.yml +0 -3
  61. data/config/locales/it.yml +0 -3
  62. data/config/locales/ja.yml +8 -3
  63. data/config/locales/lt.yml +0 -1
  64. data/config/locales/nl.yml +0 -2
  65. data/config/locales/pt-BR.yml +0 -3
  66. data/config/locales/pt-PT.yml +0 -1
  67. data/config/locales/ro-RO.yml +0 -6
  68. data/config/locales/sv.yml +0 -2
  69. data/lib/decidim/decidim_awesome/checksums.yml +29 -3
  70. data/lib/decidim/decidim_awesome/engine.rb +10 -6
  71. data/lib/decidim/decidim_awesome/map_component/component.rb +2 -1
  72. data/lib/decidim/decidim_awesome/map_component/engine.rb +2 -1
  73. data/lib/decidim/decidim_awesome/test/shared_examples/admin_accountability_contexts.rb +11 -0
  74. data/lib/decidim/decidim_awesome/version.rb +2 -2
  75. data/lib/tasks/decidim_awesome_migrate_menu_categories.rake +38 -0
  76. data/lib/tasks/decidim_awesome_upgrade_tasks.rake +4 -0
  77. data/package.json +7 -8
  78. metadata +14 -12
  79. data/app/overrides/decidim/proposals/proposals/_proposal_aside/limit_amendments_modal.html.erb.deface +0 -5
@@ -50,7 +50,11 @@ export default class Controller {
50
50
  // subgroups don't have th addLayers utility
51
51
  collectionEdges.forEach((item) => {
52
52
  this.awesomeMap.layers[this.component.type].group.addLayer(item.node.marker);
53
- this.addMarkerCategory(item.node.marker, item.node.category);
53
+ if (item.node.taxonomies && item.node.taxonomies.length > 0) {
54
+ item.node.taxonomies.forEach((taxonomy) => {
55
+ this.addMarkerTaxonomy(item.node.marker, taxonomy);
56
+ });
57
+ }
54
58
  this.addMarkerHashtags(item.node.marker, item.node.hashtags);
55
59
  });
56
60
  }
@@ -103,15 +107,14 @@ export default class Controller {
103
107
  this.allNodes.push(node);
104
108
  }
105
109
 
106
- addMarkerCategory(marker, category) {
107
- // Add to category layer
108
- const cat = this.awesomeMap.getCategory(category);
109
- if (this.awesomeMap.layers[cat.id]) {
110
+ addMarkerTaxonomy(marker, taxonomy) {
111
+ const tax = this.awesomeMap.getTaxonomy(taxonomy);
112
+ if (this.awesomeMap.layers[tax.id]) {
110
113
  try {
111
- this.awesomeMap.layers[cat.id].group.addLayer(marker);
112
- this.awesomeMap.controls.showCategory(cat);
114
+ this.awesomeMap.layers[tax.id].group.addLayer(marker);
115
+ this.awesomeMap.controls.showTaxonomy(tax);
113
116
  } catch (evt) {
114
- console.error("Failed category marker assignation. category:", category, "marker:", marker, evt.message);
117
+ console.error("Failed taxonomy marker assignation. taxonomy:", taxonomy, "marker:", marker, evt.message);
115
118
  }
116
119
  }
117
120
  }
@@ -13,7 +13,7 @@ export default class MeetingsController extends Controller {
13
13
  // for each meeting, create a marker with an associated popup
14
14
  this.fetcher.onNode = (meeting) => {
15
15
  let marker = new L.Marker([meeting.coordinates.latitude, meeting.coordinates.longitude], {
16
- icon: this.createIcon(this.awesomeMap.getCategory(meeting.category).color),
16
+ icon: this.createIcon(this.awesomeMap.getTaxonomy(meeting.taxonomies && meeting.taxonomies[0]).color),
17
17
  title: meeting.title.translation
18
18
  });
19
19
  // console.log("new meeting", meeting, marker)
@@ -28,15 +28,15 @@ export default class ProposalsController extends Controller {
28
28
  // for each proposal, create a marker with an associated popup
29
29
  this.fetcher.onNode = (proposal) => {
30
30
  let marker = new L.Marker([proposal.coordinates.latitude, proposal.coordinates.longitude], {
31
- icon: this.createIcon(this.awesomeMap.getCategory(proposal.category).color),
31
+ icon: this.createIcon(this.awesomeMap.getTaxonomy(proposal.taxonomies && proposal.taxonomies[0]).color),
32
32
  title: proposal.title.translation
33
33
  });
34
34
 
35
35
  // Check if it has amendments, add it to a list
36
- // also assign parent's proposal categories to it
36
+ // also assign parent's proposal taxonomies to it
37
37
  // console.log("onNode proposal", proposal, "amendment:", proposal.amendments)
38
38
  if (proposal.amendments && proposal.amendments.length) {
39
- proposal.amendments.forEach((amendment) => {
39
+ proposal.amendments.filter((amendment) => amendment && amendment.emendation).forEach((amendment) => {
40
40
  this.amendments[amendment.emendation.id] = proposal;
41
41
  });
42
42
  }
@@ -67,10 +67,10 @@ export default class ProposalsController extends Controller {
67
67
  }
68
68
  if (this.awesomeMap.config.menu.amendments) {
69
69
  marker.marker.addTo(this.awesomeMap.layers.amendments.group);
70
- // mimic parent category (amendments doesn't have categories)
71
- if (parent.category) {
70
+ // mimic parent taxonomy (amendments doesn't have taxonomies)
71
+ if (parent.taxonomy) {
72
72
  marker.marker.setIcon(this.createIcon("text-secondary"));
73
- this.addMarkerCategory(marker.marker, parent.category)
73
+ this.addMarkerTaxonomy(marker.marker, parent.taxonomy)
74
74
  }
75
75
  }
76
76
  }
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-ternary, multiline-ternary */
1
+ /* eslint-disable no-ternary, multiline-ternary, no-nested-ternary, max-lines */
2
2
 
3
3
  import * as L from "leaflet";
4
4
 
@@ -21,7 +21,7 @@ export default class ControlsUI {
21
21
  this.onHashtag = this._orderHashtags;
22
22
 
23
23
  this.awesomeMap.map.on("overlayadd", () => {
24
- this.removeHiddenCategories();
24
+ this.removeHiddenTaxonomies();
25
25
  });
26
26
  }
27
27
 
@@ -30,18 +30,18 @@ export default class ControlsUI {
30
30
  this.main.addTo(this.awesomeMap.map);
31
31
 
32
32
  this.addSearchControls();
33
- if (this.awesomeMap.config.menu.categories) {
34
- this.addCategoriesControls();
33
+ if (this.awesomeMap.config.menu.taxonomies) {
34
+ this.addTaxonomiesControls();
35
35
  }
36
36
 
37
37
  // sub-layer hashtag title toggle
38
38
  $("#awesome-map").on("click", ".awesome_map-title-control", (evt) => {
39
39
  evt.preventDefault();
40
40
  evt.stopPropagation();
41
- const categories = document.getElementById("awesome_map-categories-control");
41
+ const taxonomies = document.getElementById("awesome_map-taxonomies-control");
42
42
  const hashtags = document.getElementById("awesome_map-hashtags-control");
43
- if (categories) {
44
- categories.classList.toggle("active");
43
+ if (taxonomies) {
44
+ taxonomies.classList.toggle("active");
45
45
  }
46
46
  if (hashtags) {
47
47
  hashtags.classList.toggle("active");
@@ -82,62 +82,146 @@ export default class ControlsUI {
82
82
  addSearchControls() {
83
83
  const section = this.main.getContainer().querySelector(".leaflet-control-layers-list");
84
84
  if (section) {
85
- section.insertAdjacentHTML("beforeend", `<div id="awesome_map-categories-control" class="active"><b class="awesome_map-title-control">${window.DecidimAwesome.i18n.categories}</b><div class="categories-container"></div></div>
85
+ section.insertAdjacentHTML("beforeend", `<div id="awesome_map-taxonomies-control" class="active"><b class="awesome_map-title-control">${window.DecidimAwesome.i18n.taxonomies}</b><div class="taxonomies-container"></div></div>
86
86
  <div id="awesome_map-hashtags-control"><b class="awesome_map-title-control">${window.DecidimAwesome.i18n.hashtags}</b><div class="hashtags-container"></div><a href="#" class="awesome_map-toggle_all_tags">${window.DecidimAwesome.i18n.selectDeselectAll}</a></div>`);
87
87
  } else {
88
88
  console.error("Can't find the section to insert the controls");
89
89
  }
90
90
  }
91
91
 
92
- addCategoriesControls() {
93
- this.awesomeMap.categories.forEach((category) => {
94
- // add control layer for this category
95
- const label = `<i class="awesome_map-category_${category.id}"></i> ${category.name}`;
96
- this.awesomeMap.layers[category.id] = {
97
- label: label,
98
- group: new L.FeatureGroup.SubGroup(this.awesomeMap.cluster)
99
- };
100
- this.awesomeMap.layers[category.id].group.addTo(this.awesomeMap.map);
101
- const categories = document.querySelector("#awesome_map-categories-control .categories-container");
102
- if (categories) {
103
- categories.insertAdjacentHTML("beforeend", `<label data-layer="${category.id}" class="awesome_map-category-${category.id}${category.parent ? " subcategory" : ""}" data-parent="${category.parent}"><input type="checkbox" class="awesome_map-categories-selector" checked><span>${label}</span></label>`);
104
- } else {
105
- console.error("Can't find the section to insert the categories");
106
- }
107
- })
92
+ addTaxonomiesControls() {
93
+ // First, organize taxonomies by levels
94
+ const rootTaxonomies = this.awesomeMap.taxonomies.filter((tax) => !tax.parent);
95
+
96
+ // Process each root taxonomy independently
97
+ rootTaxonomies.forEach((rootTaxonomy) => {
98
+ // Add the root taxonomy
99
+ this._addTaxonomyToUI(rootTaxonomy, 0);
100
+
101
+ // Find and add direct children of this root taxonomy
102
+ const children = this.awesomeMap.taxonomies.filter((tax) => tax.parent === rootTaxonomy.id);
108
103
 
109
- // category events
110
- $("#awesome-map").on("change", ".awesome_map-categories-selector", (evt) => {
104
+ children.forEach((child) => {
105
+ this._addTaxonomyToUI(child, 1);
106
+
107
+ // Find and add grandchildren of this child
108
+ const grandchildren = this.awesomeMap.taxonomies.filter((tax) => tax.parent === child.id);
109
+
110
+ grandchildren.forEach((grandchild) => {
111
+ this._addTaxonomyToUI(grandchild, 2);
112
+ });
113
+ });
114
+ });
115
+
116
+ // taxonomy events
117
+ $("#awesome-map").on("change", ".awesome_map-taxonomies-selector", (evt) => {
111
118
  evt.preventDefault();
112
119
  evt.stopPropagation();
113
120
 
114
121
  const id = $(evt.target).closest("label").data("layer");
115
- const cat = this.awesomeMap.getCategory(id);
116
- // console.log("changed, layer", id, "cat", cat, "checked", evt.target.checked, e);
117
- if (cat) {
118
- const layer = this.awesomeMap.layers[cat.id];
119
- if (evt.target.checked) {
120
- // show group of markers
121
- this.awesomeMap.map.addLayer(layer.group);
122
+ const taxonomy = this.awesomeMap.getTaxonomy(id);
123
+
124
+ if (taxonomy) {
125
+ this._handleTaxonomyToggle(taxonomy, evt.target.checked);
126
+ }
127
+ });
128
+ }
129
+
130
+ _addTaxonomyToUI(taxonomy, level) {
131
+ // Create control layer for this taxonomy
132
+ const circleIcon = taxonomy.parent ? `<i class="awesome_map-taxonomy_${taxonomy.id}"></i> ` : "";
133
+ const label = `${circleIcon}${taxonomy.name}`;
134
+ this.awesomeMap.layers[taxonomy.id] = {
135
+ label: label,
136
+ group: new L.FeatureGroup.SubGroup(this.awesomeMap.cluster)
137
+ };
138
+ this.awesomeMap.layers[taxonomy.id].group.addTo(this.awesomeMap.map);
139
+
140
+ const taxonomiesContainer = document.querySelector("#awesome_map-taxonomies-control .taxonomies-container");
141
+ if (taxonomiesContainer) {
142
+ const levelClass = level === 0 ? "root-taxonomy" : level === 1 ? "child-taxonomy" : "grandchild-taxonomy";
143
+ const indentStyle = `style="padding-left: ${level * 20}px;"`;
144
+
145
+ taxonomiesContainer.insertAdjacentHTML("beforeend",
146
+ `<label data-layer="${taxonomy.id}"
147
+ class="awesome_map-taxonomy-${taxonomy.id} ${levelClass}"
148
+ data-parent="${taxonomy.parent || ""}"
149
+ data-level="${level}"
150
+ ${indentStyle}>
151
+ <input type="checkbox" class="awesome_map-taxonomies-selector" checked>
152
+ <span>${label}</span>
153
+ </label>`
154
+ );
155
+ } else {
156
+ console.error("Can't find the section to insert the taxonomies");
157
+ }
158
+ }
159
+
160
+ _handleTaxonomyToggle(taxonomy, isChecked) {
161
+ const layer = this.awesomeMap.layers[taxonomy.id];
162
+
163
+ if (isChecked) {
164
+ // Show group of markers
165
+ this.awesomeMap.map.addLayer(layer.group);
166
+ } else {
167
+ // Hide group of markers
168
+ this.awesomeMap.map.removeLayer(layer.group);
169
+ // Uncheck all children when parent is unchecked
170
+ this._uncheckChildrenTaxonomies(taxonomy.id);
171
+ }
172
+
173
+ // Update parent state (indeterminate/checked/unchecked)
174
+ this._updateParentTaxonomyState(taxonomy);
175
+
176
+ // sync hashtags
177
+ this.updateHashtagLayers();
178
+ }
179
+
180
+ _uncheckChildrenTaxonomies(taxonomyId) {
181
+ const children = this.awesomeMap.taxonomies.filter((tax) => tax.parent === taxonomyId);
182
+ children.forEach((child) => {
183
+ const childInput = document.querySelector(`label[data-layer="${child.id}"] input`);
184
+ if (childInput && childInput.checked) {
185
+ childInput.checked = false;
186
+ this.awesomeMap.map.removeLayer(this.awesomeMap.layers[child.id].group);
187
+ // Recursively uncheck grandchildren
188
+ this._uncheckChildrenTaxonomies(child.id);
189
+ }
190
+ });
191
+ }
192
+
193
+ _updateParentTaxonomyState(taxonomy) {
194
+ if (taxonomy.parent) {
195
+ const parentInput = document.querySelector(`label[data-layer="${taxonomy.parent}"] input`);
196
+ if (parentInput) {
197
+ const siblings = this.awesomeMap.taxonomies.filter((tax) => tax.parent === taxonomy.parent);
198
+ const checkedSiblings = siblings.filter((sibling) => {
199
+ const siblingInput = document.querySelector(`label[data-layer="${sibling.id}"] input`);
200
+ return siblingInput && siblingInput.checked;
201
+ });
202
+
203
+ if (checkedSiblings.length === 0) {
204
+ // No siblings checked - uncheck parent
205
+ parentInput.checked = true;
206
+ this.awesomeMap.map.removeLayer(this.awesomeMap.layers[taxonomy].group);
207
+ } else if (checkedSiblings.length === siblings.length) {
208
+ // All siblings checked - check parent
209
+ parentInput.checked = true;
210
+ this.awesomeMap.map.addLayer(this.awesomeMap.layers[taxonomy.parent].group);
122
211
  } else {
123
- // hide group of markers
124
- this.awesomeMap.map.removeLayer(layer.group);
125
- // cat.children().forEach((c) => {
126
- // let $el = $(`.awesome_map-category-${c.id}`);
127
- // if($el.contents("input").prop("checked")) {
128
- // $el.click();
129
- // }
130
- // });
212
+ // Some siblings checked
213
+ parentInput.checked = true;
214
+ this.awesomeMap.map.addLayer(this.awesomeMap.layers[taxonomy.parent].group);
131
215
  }
132
- // if it's a children, put the parent to indeterminate
133
- this._indeterminateParentInput(cat);
134
- // sync tags
135
- this.updateHashtagLayers();
216
+
217
+ // Recursively update grandparent
218
+ const parentTaxonomy = this.awesomeMap.getTaxonomy(taxonomy.parent);
219
+ this._updateParentTaxonomyState(parentTaxonomy);
136
220
  }
137
- });
221
+ }
138
222
  }
139
223
 
140
- // Hashtags are collected directly from proposals (this is different than categories)
224
+ // Hashtags are collected directly from proposals (this is different than taxonomies)
141
225
  addHashtagsControls(hashtags, marker) {
142
226
  // show hashtag layer
143
227
  if (hashtags && hashtags.length) {
@@ -163,20 +247,24 @@ export default class ControlsUI {
163
247
  }
164
248
  }
165
249
 
166
- showCategory(cat) {
167
- document.getElementById("awesome_map-categories-control").style.display = "block";
168
- // show category if hidden
169
- const label = document.querySelector(`label.awesome_map-category-${cat.id}`);
170
- const parent = document.querySelector(`label.awesome_map-category-${cat.parent}`);
171
- if (label) {
250
+ showTaxonomy(tax) {
251
+ document.getElementById("awesome_map-taxonomies-control").style.display = "block";
252
+
253
+ // Show taxonomy if hidden
254
+ const label = document.querySelector(`label.awesome_map-taxonomy-${tax.id}`);
255
+ if (label) {
172
256
  label.style.display = "block";
173
257
  // update number of items
174
- label.setAttribute("title", `${parseInt(label.title || 0, 10) + 1} ${window.DecidimAwesome.i18n.items}`);
258
+ const currentCount = parseInt(label.title || 0, 10) + 1;
259
+ label.setAttribute("title", `${currentCount} ${window.DecidimAwesome.i18n.items}`);
175
260
  }
176
- if (parent) {
177
- // show parent if apply
178
- parent.style.display = "block"
179
- parent.setAttribute("title", `${parseInt(parent.title || 0, 10) + 1} ${window.DecidimAwesome.i18n.items}`);
261
+
262
+ // Also show all parent taxonomies up the hierarchy
263
+ if (tax.parent) {
264
+ const parentTaxonomy = this.awesomeMap.getTaxonomy(tax.parent);
265
+ if (parentTaxonomy) {
266
+ this.showTaxonomy(parentTaxonomy);
267
+ }
180
268
  }
181
269
  }
182
270
 
@@ -191,8 +279,8 @@ export default class ControlsUI {
191
279
  });
192
280
  }
193
281
 
194
- removeHiddenCategories() {
195
- $(".awesome_map-categories-selector:not(:checked)").each((_idx, el) => {
282
+ removeHiddenTaxonomies() {
283
+ $(".awesome_map-taxonomies-selector:not(:checked)").each((_idx, el) => {
196
284
  const layer = this.awesomeMap.layers[$(el).closest("label").data("layer")];
197
285
  if (layer) {
198
286
  this.awesomeMap.map.addLayer(layer.group);
@@ -216,9 +304,9 @@ export default class ControlsUI {
216
304
  this.awesomeMap.map.addLayer(layer.group);
217
305
  }
218
306
  });
219
- // hide non-selected categories
307
+ // hide non-selected taxonomies
220
308
  this.removeHiddenComponents();
221
- this.removeHiddenCategories();
309
+ this.removeHiddenTaxonomies();
222
310
  }
223
311
 
224
312
  updateStats(uid, total) {
@@ -227,15 +315,6 @@ export default class ControlsUI {
227
315
  $component.attr("title", `${total} ${window.DecidimAwesome.i18n.items}`);
228
316
  }
229
317
 
230
- _indeterminateParentInput(cat) {
231
- if (cat.parent) {
232
- let $input = $(`.awesome_map-category-${cat.parent}`).contents("input");
233
- let $subcats = $(`[class^="awesome_map-category-"][data-parent="${cat.parent}"]:visible`);
234
- let numChecked = $subcats.contents("input:checked").length;
235
- $input.prop("indeterminate", numChecked !== $subcats.length && numChecked !== 0);
236
- }
237
- }
238
-
239
318
  // order hashtags alphabetically
240
319
  _orderHashtags(_hashtag, $div) {
241
320
  let $last = $div.contents("label:last");
@@ -30,7 +30,7 @@ document.addEventListener("DOMContentLoaded", () => {
30
30
  menu: {
31
31
  amendments: parse(dataset.menuAmendments),
32
32
  meetings: parse(dataset.menuMeetings),
33
- categories: parse(dataset.menuCategories),
33
+ taxonomies: parse(dataset.menuTaxonomies),
34
34
  hashtags: parse(dataset.menuHashtags),
35
35
  mergeComponents: parse(dataset.menuMergeComponents)
36
36
  },
@@ -49,10 +49,6 @@
49
49
  }
50
50
  }
51
51
 
52
- .row.column:last-child {
53
- @apply flex items-center justify-center;
54
- }
55
-
56
52
  .search-button-container {
57
53
  @apply flex flex-col items-start;
58
54
 
@@ -61,7 +57,15 @@
61
57
  }
62
58
  }
63
59
 
64
- .row.column.search-field {
65
- @apply flex-grow;
60
+ .row.column {
61
+ @apply mb-0;
62
+
63
+ &:last-child {
64
+ @apply flex items-center justify-center;
65
+ }
66
+
67
+ &.search-field {
68
+ @apply flex-grow;
69
+ }
66
70
  }
67
71
  }
@@ -110,7 +110,7 @@
110
110
  }
111
111
 
112
112
  #awesome_map- {
113
- &categories-control {
113
+ &taxonomies-control {
114
114
  display: none;
115
115
 
116
116
  label {
@@ -123,20 +123,53 @@
123
123
  width: 0.8rem;
124
124
  height: 0.8rem;
125
125
  border-radius: 50%;
126
- background-color: var(-- primary);
127
126
  }
128
127
 
129
- &.subcategory {
130
- padding-left: 1em;
128
+ // Hierarchical styling for taxonomies
129
+ &.root-taxonomy {
130
+ font-weight: bold;
131
+ margin-bottom: 0.3em;
132
+ }
133
+
134
+ &.child-taxonomy {
135
+ font-weight: 500;
136
+ margin-bottom: 0.2em;
137
+ }
138
+
139
+ &.grandchild-taxonomy {
140
+ font-weight: normal;
141
+ margin-bottom: 0.1em;
142
+
143
+ i {
144
+ width: 0.6rem;
145
+ height: 0.6rem;
146
+ }
147
+ }
148
+
149
+ // Indeterminate checkbox styling
150
+ input[type="checkbox"]:indeterminate {
151
+ background-color: #6c757d;
152
+ border-color: #6c757d;
153
+
154
+ &::before {
155
+ content: "−";
156
+ color: white;
157
+ font-weight: bold;
158
+ text-align: center;
159
+ line-height: 1;
160
+ display: block;
161
+ }
131
162
  }
132
163
  }
133
164
 
134
- .categories-container {
165
+ .taxonomies-container {
135
166
  display: none;
167
+ max-height: 300px;
168
+ overflow-y: auto;
136
169
  }
137
170
 
138
171
  &.active {
139
- .categories-container {
172
+ .taxonomies-container {
140
173
  display: block;
141
174
  }
142
175
  }
@@ -60,7 +60,9 @@ module Decidim
60
60
  end
61
61
 
62
62
  def created_at
63
- entry.changeset["created_at"]&.last || entry&.created_at
63
+ Time.zone.parse(entry.changeset["created_at"]&.last)
64
+ rescue StandardError
65
+ entry&.created_at
64
66
  end
65
67
 
66
68
  def created_date
@@ -9,9 +9,9 @@ module Decidim
9
9
  query = PaperTrail::Version.where(item_type:, event: "update", item_id:)
10
10
  .where("id > ?", entry.id)
11
11
  if roles.include? "admin"
12
- query.where("object_changes LIKE '%\nadmin:\n- true\n- false%'").first
12
+ query.where("object_changes @> ?", { "admin" => [true, false] }.to_json).first
13
13
  else
14
- query.where("object_changes LIKE '%\nroles:\n- - %'").first
14
+ query.where("object_changes @> ?", { "roles" => [[], []] }.to_json).first
15
15
  end
16
16
  end
17
17
  end
@@ -30,12 +30,12 @@ module Decidim
30
30
  if body.is_a?(Hash)
31
31
  body.each do |translation_locale, value|
32
32
  fields_entries(custom_fields, value) do |field_key, field_value|
33
- payload["body/#{field_key}/#{translation_locale}".to_sym] = field_value if payload["body/#{field_key}/#{translation_locale}".to_sym].blank?
33
+ payload[:"body/#{field_key}/#{translation_locale}"] = field_value if payload[:"body/#{field_key}/#{translation_locale}"].blank?
34
34
  end
35
35
  end
36
36
  else
37
37
  fields_entries(custom_fields, body) do |key, value|
38
- payload["body/#{key}/#{locale}".to_sym] = value
38
+ payload[:"body/#{key}/#{locale}"] = value
39
39
  end
40
40
  end
41
41
  end
@@ -49,7 +49,7 @@ module Decidim
49
49
  if private_custom_fields.present?
50
50
  fields_entries(private_custom_fields, proposal.private_body) do |key, value|
51
51
  value = value.first if value.is_a? Array
52
- payload["private_body/#{key}".to_sym] = value
52
+ payload[:"private_body/#{key}"] = value
53
53
  end
54
54
  end
55
55
  payload
@@ -20,7 +20,7 @@ module Decidim
20
20
  return payload unless notes.any?
21
21
 
22
22
  notes.each do |note|
23
- payload["notes/#{note.id}".to_sym] = {
23
+ payload[:"notes/#{note.id}"] = {
24
24
  created_at: note.created_at,
25
25
  note: note.body,
26
26
  author: author_name(note.author)
@@ -6,24 +6,36 @@ module Decidim
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
+ alias_method :original_validate_caps, :validate_caps
10
+ alias_method :original_validate_marks, :validate_marks
11
+ alias_method :original_validate_caps_first, :validate_caps_first
12
+
9
13
  private
10
14
 
11
15
  def validate_caps(record, attribute, value)
12
- percent = awesome_config(record, "validate_#{attribute}_max_caps_percent").to_f
16
+ awesome_config = awesome_config(record, "validate_#{attribute_without_locale(attribute)}_max_caps_percent")
17
+ return original_validate_caps(record, attribute, value) if awesome_config.nil?
18
+
19
+ percent = awesome_config.to_f
13
20
  return if value.scan(/[[:upper:]]/).length < value.length * percent / 100
14
21
 
15
22
  record.errors.add(attribute, options[:message] || I18n.t("too_much_caps", scope: "decidim.decidim_awesome.validators", percent: percent.round))
16
23
  end
17
24
 
18
25
  def validate_marks(record, attribute, value)
19
- marks = awesome_config(record, "validate_#{attribute}_max_marks_together").to_i + 1
26
+ awesome_config = awesome_config(record, "validate_#{attribute_without_locale(attribute)}_max_marks_together")
27
+ return original_validate_marks(record, attribute, value) if awesome_config.nil?
28
+
29
+ marks = awesome_config.to_i + 1
20
30
  return if value.scan(/[!?¡¿]{#{marks},}/).empty?
21
31
 
22
32
  record.errors.add(attribute, options[:message] || :too_many_marks)
23
33
  end
24
34
 
25
35
  def validate_caps_first(record, attribute, value)
26
- return unless awesome_config(record, "validate_#{attribute}_start_with_caps")
36
+ awesome_config = awesome_config(record, "validate_#{attribute_without_locale(attribute)}_start_with_caps")
37
+ return original_validate_caps_first(record, attribute, value) if awesome_config.nil?
38
+ return unless awesome_config
27
39
  return if value.scan(/\A[[:lower:]]{1}/).empty?
28
40
 
29
41
  record.errors.add(attribute, options[:message] || :must_start_with_caps)
@@ -35,6 +47,12 @@ module Decidim
35
47
 
36
48
  config[var.to_sym]
37
49
  end
50
+
51
+ def attribute_without_locale(attribute)
52
+ return attribute unless Decidim.available_locales.map { |locale| "_#{locale}" }.any? { |str| attribute.ends_with?(str) }
53
+
54
+ attribute.to_s[0...-3]
55
+ end
38
56
  end
39
57
  end
40
58
  end
@@ -1,8 +1,7 @@
1
- <div class="card">
2
- <div class="bg-gray-6 p-4 rounded-t">
3
- <h2 class="card-title flex md:flex-row justify-between sm:flex-wrap">
1
+ <div class="item_show__header">
2
+ <h1 class="item_show__header-title">
4
3
  <%= t(".title") %>
5
- <div class="flex gap-4">
4
+ <div class="gap-4">
6
5
  <%= link_to t(global? ? ".see_spaces" : ".see_global"), admin_accountability_path(admins: !global?), class: "button button__sm button__transparent-secondary tiny button--title new" %>
7
6
  <span class="exports button button__sm button__secondary tiny button--simple button--title" data-toggle="export-dropdown">
8
7
  <%= t "exports.button", scope: "decidim.decidim_awesome.admin.admin_accountability" %>
@@ -21,13 +20,12 @@
21
20
  </ul>
22
21
  </div>
23
22
  </div>
24
- </h2>
23
+ </h1>
25
24
  </div>
26
25
 
27
26
  <%= render partial: "decidim/decidim_awesome/admin/shared/filters_with_date", locals: { i18n_ctx: "admin_accountability" } %>
28
27
 
29
- <div class="card-section p-4">
30
- <p class="help-text"><%= t(global? ? ".global_description" : ".description") %></p>
28
+ <p class="help-text mt-3 mb-3"><%= t(global? ? ".global_description" : ".description") %></p>
31
29
 
32
30
  <% if global_users_missing_date %>
33
31
  <div class="callout warning"><%= t(".missing_users", date: l(global_users_missing_date, format: :decidim_short)) %></div>
@@ -67,5 +65,4 @@
67
65
  <%= paginate admin_actions, theme: "decidim" %>
68
66
  </div>
69
67
  </div>
70
- </div>
71
68
  <% append_stylesheet_pack_tag "decidim_admin_decidim_awesome_search_form" %>