decidim-decidim_awesome 0.6.2 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -6
  3. data/app/assets/config/decidim_admin_decidim_awesome_manifest.js +1 -0
  4. data/app/assets/javascripts/decidim/decidim_awesome/admin.js +1 -0
  5. data/app/assets/javascripts/decidim/decidim_awesome/admin/codemirror.js.es6 +15 -0
  6. data/app/assets/javascripts/decidim/decidim_awesome/admin/form_exit_warn.js.es6 +30 -0
  7. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/hashtags.js.es6 +48 -0
  8. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/layers.js.es6 +106 -0
  9. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/legacy_map.js.es6 +12 -19
  10. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/legacy_proposals.js.es6 +3 -2
  11. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/map.js.es6 +166 -170
  12. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/markers.js.es6 +56 -0
  13. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/meetings.js.es6 +4 -3
  14. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/proposals.js.es6 +17 -4
  15. data/app/assets/javascripts/decidim/decidim_awesome/awesome_map/utilities.js.es6 +48 -0
  16. data/app/assets/stylesheets/decidim/decidim_awesome/admin.scss +10 -3
  17. data/app/assets/stylesheets/decidim/decidim_awesome/admin/codemirror.scss +16 -0
  18. data/app/assets/stylesheets/decidim/decidim_awesome/awesome_map/leaflet.scss.erb +9 -0
  19. data/app/assets/stylesheets/decidim/decidim_awesome/awesome_map/map.scss +95 -0
  20. data/app/assets/stylesheets/decidim/decidim_awesome/editors/markdown_editor.scss +1 -1
  21. data/app/awesome_overrides/presenters/decidim/menu_presenter_override.rb +39 -0
  22. data/app/commands/decidim/decidim_awesome/admin/create_menu_hack.rb +51 -0
  23. data/app/commands/decidim/decidim_awesome/admin/create_scoped_style.rb +34 -0
  24. data/app/commands/decidim/decidim_awesome/admin/destroy_menu_hack.rb +47 -0
  25. data/app/commands/decidim/decidim_awesome/admin/destroy_scoped_style.rb +40 -0
  26. data/app/commands/decidim/decidim_awesome/admin/update_config.rb +5 -2
  27. data/app/commands/decidim/decidim_awesome/admin/update_menu_hack.rb +47 -0
  28. data/app/controllers/decidim/decidim_awesome/admin/application_controller.rb +4 -3
  29. data/app/controllers/decidim/decidim_awesome/admin/config_controller.rb +40 -4
  30. data/app/controllers/decidim/decidim_awesome/admin/constraints_controller.rb +13 -0
  31. data/app/controllers/decidim/decidim_awesome/admin/menu_hacks_controller.rb +116 -0
  32. data/app/forms/decidim/decidim_awesome/admin/config_form.rb +22 -2
  33. data/app/forms/decidim/decidim_awesome/admin/constraint_form.rb +0 -2
  34. data/app/forms/decidim/decidim_awesome/admin/intergram_form.rb +0 -2
  35. data/app/forms/decidim/decidim_awesome/admin/menu_form.rb +39 -0
  36. data/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb +5 -1
  37. data/app/helpers/decidim/decidim_awesome/map_helper.rb +9 -3
  38. data/app/permissions/decidim/decidim_awesome/admin/permissions.rb +19 -0
  39. data/app/permissions/decidim/decidim_awesome/permissions.rb +2 -0
  40. data/app/views/decidim/decidim_awesome/admin/config/_form_styles.html.erb +28 -0
  41. data/app/views/decidim/decidim_awesome/admin/config/show.html.erb +2 -3
  42. data/app/views/decidim/decidim_awesome/admin/menu_hacks/_form.html.erb +7 -0
  43. data/app/views/decidim/decidim_awesome/admin/menu_hacks/edit.html.erb +13 -0
  44. data/app/views/decidim/decidim_awesome/admin/menu_hacks/index.html.erb +44 -0
  45. data/app/views/decidim/decidim_awesome/admin/menu_hacks/new.html.erb +13 -0
  46. data/app/views/decidim/decidim_awesome/map_component/map/show.html.erb +9 -6
  47. data/app/views/layouts/decidim/admin/decidim_awesome.html.erb +10 -0
  48. data/app/views/layouts/decidim/decidim_awesome/_awesome_config.html.erb +5 -2
  49. data/app/views/layouts/decidim/decidim_awesome/_custom_styles.html.erb +3 -0
  50. data/app/views/v0.22/layouts/decidim/_head.html.erb +1 -0
  51. data/app/views/v0.23/layouts/decidim/_head.html.erb +1 -0
  52. data/config/locales/ca.yml +70 -2
  53. data/config/locales/cs.yml +71 -3
  54. data/config/locales/en.yml +75 -2
  55. data/config/locales/es.yml +70 -2
  56. data/config/locales/eu.yml +225 -0
  57. data/config/locales/fr.yml +172 -104
  58. data/config/locales/nl.yml +225 -0
  59. data/config/locales/sv.yml +93 -25
  60. data/lib/decidim/decidim_awesome.rb +27 -0
  61. data/lib/decidim/decidim_awesome/admin_engine.rb +3 -0
  62. data/lib/decidim/decidim_awesome/awesome_helpers.rb +16 -0
  63. data/lib/decidim/decidim_awesome/checksums.yml +6 -0
  64. data/lib/decidim/decidim_awesome/config.rb +13 -12
  65. data/lib/decidim/decidim_awesome/engine.rb +1 -1
  66. data/lib/decidim/decidim_awesome/map_component/component.rb +7 -1
  67. data/lib/decidim/decidim_awesome/menu_hacker.rb +90 -0
  68. data/lib/decidim/decidim_awesome/test/shared_examples/config_examples.rb +4 -2
  69. data/lib/decidim/decidim_awesome/test/shared_examples/menu_hack_contexts.rb +71 -0
  70. data/lib/decidim/decidim_awesome/version.rb +1 -1
  71. data/vendor/assets/javascripts/codemirror.js +9801 -0
  72. data/vendor/assets/javascripts/jquery.truncate.js +105 -0
  73. data/vendor/assets/javascripts/keymap/sublime.js +720 -0
  74. data/vendor/assets/javascripts/mode/css/css.js +864 -0
  75. data/vendor/assets/stylesheets/codemirror.css +350 -0
  76. data/vendor/assets/stylesheets/inscrybmde.min.scss +180 -0
  77. metadata +48 -3
  78. data/vendor/assets/stylesheets/inscrybmde.min.css +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3eae6aea754d4ce898a8631f7d8c3b03946a11fc0cb15c5e1d373d98774aabeb
4
- data.tar.gz: 7f3107a8bfa4d3069c34cf28c7347686d38811fd7f9d6b1a18ff41e470216af2
3
+ metadata.gz: 32cc1fc0147bd8a5daf75a5deab25f18ede902f0ceb8c9a4a0073528dec97568
4
+ data.tar.gz: 2ed32bdc25c0e2ab7b5b8b7362adad7a191634290b896e6f3e0f630687ba0824
5
5
  SHA512:
6
- metadata.gz: dbbaaea663e4c9d317e7f971e0da7446577d6427245da4f17993b971c166a951e7eaeeb2d0264f01eaded4b3eb65b79c07ecec9e216cafc7e45ed6dc1fc010a4
7
- data.tar.gz: d77a435d6ebf77601d27344a744048e1eff43f599e64a330dcb9e7ea3f22e1efbbf808fb676891dd4404d334acc217a5a19fd892482631cc148992c342295893
6
+ metadata.gz: df2b4b28994f8ad2f11e87080c683edb9fc8f215c76a7b6e15ebd27df018317fb64daf5f1c9b3910d2bd6c676d746caadec6defdaeb57716509cb9b3b33b67d8
7
+ data.tar.gz: 95a51e3cf9e87c8b2c9756ad6ab11ab1ee90d252b16f99740a20e190742bb0017e88bb36357ac2031d588bcf792b86d1337fe0685c7dfb309789beb4a7aa6797
data/README.md CHANGED
@@ -33,7 +33,7 @@ Each hack can be scoped to one or more specific participatory spaces or componen
33
33
 
34
34
  #### 1. Image support for the Quill editor
35
35
 
36
- Modifies the WYSIWYG editor in Decidim by adding the possibility to insert images. When uploading images, Drag & Drop is supported. Images will be uploaded to the server and inserted as external resources (it doesn't use base64 inline encoding).
36
+ Modifies the WYSIWYG editor in Decidim by adding the possibility to insert images. When uploading images, Drag & Drop is supported. Images will be uploaded to the server and inserted as external resources (it doesn't use base64 in-line encoding).
37
37
 
38
38
  This feature allows you use images in newsletters as well.
39
39
 
@@ -71,13 +71,13 @@ Many scopes can be defined for every tweak.
71
71
 
72
72
  This is a component you can add in any participatory space. It retrieves all the geolocated content in that participatory space (meetings or proposals) and displays it in a big map.
73
73
 
74
- It also provides a simple search by category, each category is assignated to a different color.
74
+ It also provides a simple search by category, each category is assigned to a different color.
75
75
 
76
76
  ![Awesome map](examples/awesome-map.png)
77
77
 
78
78
  #### 7. Allow Decidim to use custom CSS themes for every tenant
79
79
 
80
- When customizind CSS for a Decidim installation, each change affects all the organizations (tenant).
80
+ When customizing CSS for a Decidim installation, each change affects all the organizations (tenant).
81
81
 
82
82
  This feature allows to customize each organization css without affecting the others in the same Decidim installation.
83
83
 
@@ -106,14 +106,28 @@ With this feature you can have a support chat in Decidim. It is linked to a [Tel
106
106
 
107
107
  ![Intergram screenshot](examples/intergram.png)
108
108
 
109
+ #### 10. Custom CSS applied only according scoped restrictions
110
+
111
+ With this feature you can create directly in the admin a CSS snipped that is only applied globally, in a particular assembly or even a single proposal!
112
+
113
+ ![CSS screenshot](examples/custom_styles.png)
114
+
115
+ #### 11. Change the main menu of Decidim entirely!
116
+
117
+ Feel free to hide, modify or add items in the Decidim's main menu. You can also change the order, establish some conditions (like showing only for logged users) or open in a new window.
118
+
119
+ ![Menu hacks screenshot](examples/menu-1.png)
120
+ ![Menu hacks screenshot](examples/menu-2.png)
121
+ ![Menu hacks screenshot](examples/menu-3.png)
122
+ ![Menu hacks screenshot](examples/menu-4.png)
109
123
 
110
124
  #### To be continued...
111
125
 
112
126
  Some things in the road-map:
113
127
 
114
128
  1. Improve the conversation in comments by allowing images
115
- 1. Direct export of surveys in PDF
116
- 1. Allow to create surveys where the responding user is known
129
+ 1. Allow to create non-private surveys where the responding user is known by admins
130
+ 1. Manipulate menus (reorder, change texts, add new items)
117
131
  1. Propose something! or even better send a PR!
118
132
 
119
133
  ## Installation
@@ -121,7 +135,7 @@ Some things in the road-map:
121
135
  Add this line to your application's Gemfile:
122
136
 
123
137
  ```ruby
124
- gem "decidim-decidim_awesome", "~> 0.6.2"
138
+ gem "decidim-decidim_awesome", "~> 0.6.6"
125
139
  ```
126
140
 
127
141
  And then execute:
@@ -146,6 +160,8 @@ admins do not even see it.
146
160
 
147
161
  In order to personalize default values, create an initializer such as:
148
162
 
163
+ > **NOTE**: this is not necessary unless you want to **disable** some features. All features are enabled by default.
164
+
149
165
  ```ruby
150
166
  # config/initializers/awesome_defaults.rb
151
167
 
@@ -159,6 +175,9 @@ Decidim::DecidimAwesome.configure do |config|
159
175
 
160
176
  # De-activated, admins don't even see it as an option
161
177
  config.use_markdown_editor = :disabled
178
+
179
+ # any other config var from lib/decidim/decidim_awesome.rb
180
+ ...
162
181
  end
163
182
  ```
164
183
 
@@ -1 +1,2 @@
1
1
  // = link decidim/decidim_awesome/admin.js
2
+ // = link decidim/decidim_awesome/admin/form_exit_warn.js
@@ -1,2 +1,3 @@
1
1
  // = require decidim/decidim_awesome/admin/constraints
2
+ // = require decidim/decidim_awesome/admin/codemirror
2
3
  // = require decidim/decidim_awesome/editors/quill_editor
@@ -0,0 +1,15 @@
1
+ // = require codemirror
2
+ // = require mode/css/css
3
+ // = require keymap/sublime
4
+ // = require_self
5
+
6
+ $(() => {
7
+ $(".awesome-edit-config .scoped-style textarea").each((_idx, el) => {
8
+ console.log(el)
9
+ var editor = CodeMirror.fromTextArea(el, {
10
+ lineNumbers: true,
11
+ mode: "css",
12
+ keymap: "sublime"
13
+ });
14
+ })
15
+ });
@@ -0,0 +1,30 @@
1
+ // = require_self
2
+
3
+ $(() => {
4
+ const $form = $("form.awesome-edit-config");
5
+ if ($form.length > 0) {
6
+ $form.find("input, textarea, select").on("change", () => {
7
+ $form.data("changed", true);
8
+ });
9
+
10
+ const safePath = $form.data("safe-path").split("?")[0];
11
+ $(document).on("click", "a", (event) => {
12
+ window.exitUrl = event.currentTarget.href;
13
+ });
14
+ $(document).on("submit", "form", (event) => {
15
+ window.exitUrl = event.currentTarget.action;
16
+ });
17
+
18
+ window.addEventListener("beforeunload", (event) => {
19
+ const exitUrl = window.exitUrl;
20
+ const hasChanged = $form.data("changed");
21
+ window.exitUrl = null;
22
+
23
+ if (!hasChanged || (exitUrl && exitUrl.includes(safePath))) {
24
+ return null;
25
+ }
26
+
27
+ event.returnValue = true;
28
+ });
29
+ }
30
+ });
@@ -0,0 +1,48 @@
1
+ ((exports) => {
2
+ const hashtags = [];
3
+
4
+ const collectHashtags = (text) => {
5
+ let tags = [];
6
+ if(text) {
7
+ const gids = text.match(/gid:\/\/[^\s<]+/g)
8
+ if(gids) {
9
+ tags = gids.filter(gid => gid.indexOf("/Decidim::Hashtag/") != -1).map(gid => {
10
+ const parts = gid.split("/");
11
+ const fromSelector = parts[5].charAt(0) == '_';
12
+ const tag = fromSelector ? parts[5].substr(1) : parts[5];
13
+ const name = '#' + tag;
14
+ const html = `<a href="/search?term=${name}">${name}</a>`;
15
+ const hashtag = {
16
+ color: getComputedStyle(document.documentElement).getPropertyValue('--secondary'),
17
+ gid: gid,
18
+ id: parseInt(parts[4], 10),
19
+ fromSelector: fromSelector,
20
+ tag: tag,
21
+ name: name,
22
+ html: html
23
+ }
24
+ hashtags.push(hashtag)
25
+ return hashtag;
26
+ });
27
+ }
28
+ }
29
+ return tags;
30
+ };
31
+
32
+ const removeHashtags = (text) => {
33
+ return text.replace(/gid:\/\/[^\s<]+/g, "");
34
+ };
35
+
36
+ const appendHtmlHashtags = (text, tags) => {
37
+ tags.forEach(tag => {
38
+ text += ` ${tag.html}`;
39
+ });
40
+ return text;
41
+ };
42
+
43
+ exports.AwesomeMap = exports.AwesomeMap || {};
44
+ exports.AwesomeMap.hashtags = hashtags;
45
+ exports.AwesomeMap.collectHashtags = collectHashtags;
46
+ exports.AwesomeMap.appendHtmlHashtags = appendHtmlHashtags;
47
+ exports.AwesomeMap.removeHashtags = removeHashtags;
48
+ })(window);
@@ -0,0 +1,106 @@
1
+ // = require leaflet.featuregroup.subgroup
2
+ // = require decidim/decidim_awesome/awesome_map/utilities
3
+ // = require decidim/decidim_awesome/awesome_map/categories
4
+ // = require decidim/decidim_awesome/awesome_map/hashtags
5
+
6
+ ((exports) => {
7
+ const { collapsedMenu, options, categories } = exports.AwesomeMap;
8
+ const layers = {};
9
+ const cluster = L.markerClusterGroup();
10
+
11
+ const control = L.control.layers(null, null, {
12
+ position: 'topleft',
13
+ sortLayers: false,
14
+ collapsed: collapsedMenu,
15
+ // hideSingleBase: true
16
+ });
17
+
18
+ const addProposalsControls = (map, component) => {
19
+ // add control layer for proposals
20
+ layers.proposals = {
21
+ label: `<span class="awesome_map-component" id="awesome_map-component_${component.id}" title="0">${component.name || window.DecidimAwesome.texts.proposals}</span>`,
22
+ group: L.featureGroup.subGroup(cluster)
23
+ };
24
+ control.addOverlay(layers.proposals.group, layers.proposals.label);
25
+ layers.proposals.group.addTo(map);
26
+
27
+ // add control layer for amendments if any
28
+ if(options.menu.amendments && component.amendments) {
29
+ layers.amendments = {
30
+ label: `<span class="awesome_map-component" id="awesome_map-amendments_${component.id}" title="0">${window.DecidimAwesome.texts.amendments}</span>`,
31
+ group: L.featureGroup.subGroup(cluster)
32
+ }
33
+ control.addOverlay(layers.amendments.group, layers.amendments.label);
34
+ layers.amendments.group.addTo(map);
35
+ }
36
+ };
37
+
38
+ const addMeetingsControls = (map, component) => {
39
+ // add control layer for meetings
40
+ layers.meetings = {
41
+ label: `<span class="awesome_map-component" id="awesome_map-component_${component.id}" title="0">${component.name || window.DecidimAwesome.texts.meetings}</span>`,
42
+ group: L.featureGroup.subGroup(cluster)
43
+ };
44
+ control.addOverlay(layers.meetings.group, layers.meetings.label);
45
+ layers.meetings.group.addTo(map);
46
+ };
47
+
48
+ const addSearchControls = () => {
49
+ $(control.getContainer()).contents("form").after(`<div id="awesome_map-categories-control" class="active"><b class="awesome_map-title-control">${window.DecidimAwesome.texts.categories}</b><div class="categories-container"></div></div>
50
+ <div id="awesome_map-hashtags-control"><b class="awesome_map-title-control">${window.DecidimAwesome.texts.hashtags}</b><div class="hashtags-container"></div><a href="#" class="awesome_map-toggle_all_tags">${window.DecidimAwesome.texts.select_deselect_all}</a></div>`);
51
+ };
52
+
53
+ const addCategoriesControls = (map) => {
54
+ if(categories && categories.length) {
55
+ categories.forEach((category) => {
56
+ // add control layer for this category
57
+ const label = `<i class="awesome_map-category-${category.id}"></i> ${category.name}`;
58
+ layers[category.id] = {
59
+ label: label,
60
+ group: L.featureGroup.subGroup(cluster)
61
+ };
62
+ layers[category.id].group.addTo(map);
63
+ // In the next iteration to be sure layers are rendered
64
+ setTimeout(() => {
65
+ $('#awesome_map-categories-control .categories-container').append(`<label data-layer="${category.id}" class="awesome_map-category-${category.id}${category.parent?" subcategory":""}"><input type="checkbox" class="awesome_map-categories-selector" checked><span>${label}</span></label>`);
66
+ });
67
+ });
68
+ }
69
+ };
70
+
71
+ // Hashtags are collected directly from proposals (this is different than categories)
72
+ const addHashtagsControls = (map, hashtags, marker) => {
73
+ // show hashtag layer
74
+ if(hashtags && hashtags.length) {
75
+ $('#awesome_map-hashtags-control').show();
76
+ hashtags.forEach(hashtag => {
77
+ // Add layer if not exists, otherwise just add the marker to the group
78
+ if(!layers[hashtag.tag]) {
79
+ layers[hashtag.tag] = {
80
+ label: hashtag.name,
81
+ group: L.featureGroup.subGroup(cluster)
82
+ };
83
+ layers[hashtag.tag].group.addTo(map);
84
+ $('#awesome_map-hashtags-control .hashtags-container').append(`<label data-layer="${hashtag.tag}" class="awesome_map-hashtag-${hashtag.tag}"><input type="checkbox" class="awesome_map-hashtags-selector" checked><span>${hashtag.name}</span></label>`);
85
+ // Call a trigger, might be in service for customizations
86
+ exports.AwesomeMap.hashtagAdded(hashtag, $('#awesome_map-hashtags-control .hashtags-container'));
87
+ }
88
+ marker.addTo(layers[hashtag.tag].group);
89
+
90
+ const $label = $(`label.awesome_map-hashtag-${hashtag.tag}`);
91
+ // update number of items
92
+ $label.attr("title", (parseInt($label.attr("title") || 0) + 1) + " " + window.DecidimAwesome.texts.items);
93
+ });
94
+ }
95
+ };
96
+
97
+ exports.AwesomeMap.layers = layers;
98
+ exports.AwesomeMap.control = control;
99
+ exports.AwesomeMap.cluster = cluster;
100
+ exports.AwesomeMap.addProposalsControls = addProposalsControls;
101
+ exports.AwesomeMap.addMeetingsControls = addMeetingsControls;
102
+ exports.AwesomeMap.addSearchControls = addSearchControls;
103
+ exports.AwesomeMap.addCategoriesControls = addCategoriesControls;
104
+ exports.AwesomeMap.addHashtagsControls = addHashtagsControls;
105
+ exports.AwesomeMap.hashtagAdded = $.noop;
106
+ })(window);
@@ -1,13 +1,14 @@
1
1
  // = require jsrender.min
2
2
  // = require decidim/map
3
3
  // = require leaflet.featuregroup.subgroup
4
+ // = require decidim/decidim_awesome/awesome_map/utilities
4
5
  // = require decidim/decidim_awesome/awesome_map/categories
5
6
  // = require decidim/decidim_awesome/awesome_map/legacy_proposals
6
7
  // = require decidim/decidim_awesome/awesome_map/meetings
7
8
  // = require_self
8
9
 
9
10
  ((exports) => {
10
- const { fetchProposals, fetchMeetings, getCategory } = exports.AwesomeMap;
11
+ const { fetchProposals, fetchMeetings, getCategory, amendments } = exports.AwesomeMap;
11
12
 
12
13
  const collapsedMenu = $("#map").data("collapsed");
13
14
  const show = {
@@ -22,14 +23,12 @@
22
23
  const popupProposalTemplateId = "legacy-marker-proposal-popup";
23
24
 
24
25
  const cluster = L.markerClusterGroup();
25
- const amendments = [];
26
-
27
26
  const layers = {};
28
27
 
29
28
  const control = L.control.layers(null, null, {
30
- position: 'topleft',
29
+ position: 'topleft',
31
30
  sortLayers: false,
32
- collapsed: collapsedMenu,
31
+ collapsed: collapsedMenu,
33
32
  // hideSingleBase: true
34
33
  });
35
34
  const allMarkers = [];
@@ -39,26 +38,20 @@
39
38
  node = document.createElement("div");
40
39
 
41
40
  $($.templates(`#${tmpl}`).render(element)).appendTo(node);
42
-
41
+
43
42
  marker.bindPopup(node, {
44
43
  maxwidth: 640,
45
44
  minWidth: 500,
46
45
  keepInView: true,
47
46
  className: "map-info"
48
47
  }).openPopup();
49
-
48
+
50
49
  allMarkers.push({
51
50
  marker: marker,
52
51
  component: component,
53
52
  element: element
54
53
  });
55
54
 
56
- // Check if it has amendments, add it to a list
57
- if(element.amendments && element.amendments.length) {
58
- element.amendments.forEach((amendment) => {
59
- amendments.push(amendment.emendation.id);
60
- });
61
- }
62
55
  // Add to category layer
63
56
  let cat = getCategory(element.category);
64
57
  if(layers[cat.id]) {
@@ -86,7 +79,7 @@
86
79
  cluster.addTo(map);
87
80
 
88
81
  // Load markers
89
- components.forEach((component) => {
82
+ components.forEach((component) => {
90
83
  if(component.type == "proposals") {
91
84
  // add control layer for proposals
92
85
  layers.proposals = {
@@ -108,7 +101,7 @@
108
101
 
109
102
  fetchProposals(component, '', (element, marker) => {
110
103
  if(show[element.state || 'notAnswered']) {
111
- drawMarker(element, marker, component).addTo(layers.proposals.group)
104
+ drawMarker(element, marker, component).addTo(layers.proposals.group)
112
105
  }
113
106
  }, () => {
114
107
  // finall call
@@ -122,7 +115,7 @@
122
115
  });
123
116
  });
124
117
  }
125
-
118
+
126
119
  if(component.type == "meetings") {
127
120
  // add control layer for meetings
128
121
  layers.meetings = {
@@ -131,7 +124,7 @@
131
124
  };
132
125
  control.addOverlay(layers.meetings.group, layers.meetings.label);
133
126
  layers.meetings.group.addTo(map);
134
-
127
+
135
128
  fetchMeetings(component, '', (element, marker) => {
136
129
  drawMarker(element, marker, component).addTo(layers.meetings.group);
137
130
  }, () => {
@@ -167,12 +160,12 @@
167
160
 
168
161
  // watch events for subcategories syncronitzation
169
162
  const getCatFromClass = (name) => {
170
- let id = name.match(/awesome_map-category_(\d+)/)
163
+ let id = name.match(/awesome_map-category_(\d+)/)
171
164
  if(!id) return;
172
165
  const cat = getCategory(id[1]);
173
166
  if(!cat || !cat.name) return;
174
167
 
175
- return cat;
168
+ return cat;
176
169
  };
177
170
 
178
171
  const indeterminateInput = (id) => {
@@ -1,8 +1,9 @@
1
1
  // = require decidim/decidim_awesome/awesome_map/api_fetcher
2
2
  // = require decidim/decidim_awesome/awesome_map/categories
3
+ // = require decidim/decidim_awesome/awesome_map/utilities
3
4
 
4
5
  ((exports) => {
5
- const { getCategory } = exports.AwesomeMap;
6
+ const { getCategory, truncate } = exports.AwesomeMap;
6
7
  const query = `query ($id: ID!, $after: String!) {
7
8
  component(id: $id) {
8
9
  id
@@ -48,7 +49,7 @@
48
49
  })
49
50
  });
50
51
 
51
- element.body = element.body.replace(/\n/g, "<br>");
52
+ element.body = truncate(element.body.replace(/\n/g, "<br>"));
52
53
  callback(element, marker);
53
54
  };
54
55
 
@@ -1,82 +1,41 @@
1
- // = require jsrender.min
2
- // = require leaflet.featuregroup.subgroup
1
+ // = require decidim/decidim_awesome/awesome_map/layers
2
+ // = require decidim/decidim_awesome/awesome_map/utilities
3
+ // = require decidim/decidim_awesome/awesome_map/markers
3
4
  // = require decidim/decidim_awesome/awesome_map/categories
4
5
  // = require decidim/decidim_awesome/awesome_map/proposals
5
6
  // = require decidim/decidim_awesome/awesome_map/meetings
6
7
  // = require_self
7
8
 
8
9
  ((exports) => {
9
- const { fetchProposals, fetchMeetings, getCategory } = exports.AwesomeMap;
10
-
11
- const collapsedMenu = $("#awesome-map").data("collapsed");
12
- const show = {
13
- withdrawn: $("#awesome-map").data("show-withdrawn"),
14
- accepted: $("#awesome-map").data("show-accepted"),
15
- evaluating: $("#awesome-map").data("show-evaluating"),
16
- notAnswered: $("#awesome-map").data("show-not-answered"),
17
- rejected: $("#awesome-map").data("show-rejected")
18
- };
19
- const components = $("#awesome-map").data("components");
20
- const popupMeetingTemplateId = "marker-meeting-popup";
21
- const popupProposalTemplateId = "marker-proposal-popup";
22
-
23
- const cluster = L.markerClusterGroup();
24
- const amendments = [];
25
-
26
- const layers = {};
27
-
28
- const control = L.control.layers(null, null, {
29
- position: 'topleft',
30
- sortLayers: false,
31
- collapsed: collapsedMenu,
32
- // hideSingleBase: true
33
- });
34
- const allMarkers = [];
35
-
36
- const drawMarker = (element, marker, component) => {
37
- let tmpl = component.type === "proposals" ? popupProposalTemplateId : popupMeetingTemplateId,
38
- node = document.createElement("div");
39
-
40
- $($.templates(`#${tmpl}`).render(element)).appendTo(node);
41
-
42
- marker.bindPopup(node, {
43
- maxwidth: 640,
44
- minWidth: 500,
45
- keepInView: true,
46
- className: "map-info"
47
- }).openPopup();
48
-
49
- allMarkers.push({
50
- marker: marker,
51
- component: component,
52
- element: element
53
- });
54
-
55
- // Check if it has amendments, add it to a list
56
- if(element.amendments && element.amendments.length) {
57
- element.amendments.forEach((amendment) => {
58
- amendments.push(amendment.emendation.id);
59
- });
60
- }
61
- // Add to category layer
62
- let cat = getCategory(element.category);
63
- if(layers[cat.id]) {
64
- marker.addTo(layers[cat.id].group);
65
- // show category if hidden
66
- const $label = $(`.awesome_map-category_${cat.id}`).closest("label");
67
- const $parent = $(`.awesome_map-category_${cat.parent}`).closest("label");
68
- $label.show();
69
- // update number of items
70
- $label.attr("title", parseInt($label.attr("title") || 0) + 1);
71
- // show parent if apply
72
- $parent.show();
73
- $parent.attr("title", parseInt($parent.attr("title") || 0) + 1);
74
- // update component stats
75
- const $component = $(`#awesome_map-component-${component.id}`);
76
- $component.attr("title", parseInt($component.attr("title") || 0) + 1);
10
+ const {
11
+ layers,
12
+ cluster,
13
+ control,
14
+ addProposalsControls,
15
+ addMeetingsControls,
16
+ addSearchControls,
17
+ addCategoriesControls,
18
+ addHashtagsControls,
19
+ fetchProposals,
20
+ fetchMeetings,
21
+ options,
22
+ show,
23
+ components,
24
+ amendments,
25
+ allMarkers,
26
+ drawMarker,
27
+ getCategory
28
+ } = exports.AwesomeMap;
29
+
30
+ exports.AwesomeMap.allMarkersLoaded = $.noop;
31
+
32
+ const autoResizeMap = (map) => {
33
+ // Setup center/zoom options if specified, otherwise fitbounds
34
+ if(options.center) {
35
+ map.setView(options.center, options.zoom);
36
+ } else {
37
+ map.fitBounds(cluster.getBounds(), { padding: [50, 50] });
77
38
  }
78
-
79
- return marker;
80
39
  };
81
40
 
82
41
  const loadElements = (map) => {
@@ -85,131 +44,168 @@
85
44
  cluster.addTo(map);
86
45
 
87
46
  // Load markers
88
- components.forEach((component) => {
47
+ components.forEach((component) => {
89
48
  if(component.type == "proposals") {
90
- // add control layer for proposals
91
- layers.proposals = {
92
- label: `<span id="awesome_map-component-${component.id}" title="0">${component.name || window.DecidimAwesome.texts.proposals}</span>`,
93
- group: L.featureGroup.subGroup(cluster)
94
- };
95
- control.addOverlay(layers.proposals.group, layers.proposals.label);
96
- layers.proposals.group.addTo(map);
97
-
98
- // add control layer for amendments if any
99
- if(component.amendments) {
100
- layers.amendments = {
101
- label: `<span id="awesome_map-component-${component.d}" title="0">${window.DecidimAwesome.texts.amendments}</span>`,
102
- group: L.featureGroup.subGroup(cluster)
103
- }
104
- control.addOverlay(layers.amendments.group, layers.amendments.label);
105
- layers.amendments.group.addTo(map);
106
- }
49
+ addProposalsControls(map, component);
107
50
 
108
51
  fetchProposals(component, '', (element, marker) => {
109
- console.log(element.state, show[element.state || 'notAnswered'], show, element);
52
+ // console.log(element.state, show[element.state || 'notAnswered'], show, element);
110
53
  if(show[element.state || 'notAnswered']) {
111
- drawMarker(element, marker, component).addTo(layers.proposals.group)
54
+ drawMarker(element, marker, component).addTo(layers.proposals.group);
55
+ // Add hashtags menu items here, only hashtags with proposals associated will be present
56
+ if(options.menu.hashtags) {
57
+ addHashtagsControls(map, element.hashtags, marker);
58
+ }
112
59
  }
113
- }, () => {
114
- // finall call
115
- map.fitBounds(cluster.getBounds(), { padding: [50, 50] });
60
+ }, () => { // final call
61
+ // Setup center/zoom options if specified, otherwise fitbounds
62
+ autoResizeMap(map);
63
+
116
64
  allMarkers.forEach((item) => {
117
65
  // add marker to amendments layers if it's an amendment
118
66
  if(amendments.find((a) => a == item.element.id)) {
119
67
  item.marker.removeFrom(layers.proposals.group);
120
- item.marker.addTo(layers.amendments.group);
68
+ if(options.menu.amendments) {
69
+ item.marker.addTo(layers.amendments.group);
70
+ }
121
71
  }
122
72
  });
73
+ // Call a trigger, might be useful for customizations
74
+ exports.AwesomeMap.allMarkersLoaded();
123
75
  });
124
- }
125
-
126
- if(component.type == "meetings") {
127
- // add control layer for meetings
128
- layers.meetings = {
129
- label: `<span id="awesome_map-component-${component.id}" title="0">${component.name || window.DecidimAwesome.texts.meetings}</span>`,
130
- group: L.featureGroup.subGroup(cluster)
131
- };
132
- control.addOverlay(layers.meetings.group, layers.meetings.label);
133
- layers.meetings.group.addTo(map);
134
-
135
- fetchMeetings(component, '', (element, marker) => {
76
+ }
77
+
78
+ if(options.menu.meetings && component.type == "meetings") {
79
+ addMeetingsControls(map, component);
80
+
81
+ fetchMeetings(component, '', (element, marker) => {
136
82
  drawMarker(element, marker, component).addTo(layers.meetings.group);
137
- }, () => {
138
- map.fitBounds(cluster.getBounds(), { padding: [50, 50] });
83
+ }, () => autoResizeMap(map) );
84
+ }
85
+ });
86
+
87
+ /*
88
+ * We add all categories and hide those that have no proposals
89
+ * This is done this way to ensure all parent categories are displayed
90
+ * even if the have not proposals associated
91
+ */
92
+ addSearchControls(map);
93
+ addCategoriesControls(map);
94
+
95
+ // category events
96
+ $("#awesome-map").on("change", ".awesome_map-categories-selector", (e) => {
97
+ e.preventDefault();
98
+ e.stopPropagation();
99
+ const id = $(e.target).closest("label").data("layer");
100
+ const cat = getCategory(id);
101
+ // console.log("changed, layer", id, "cat", cat, "checked", e.target.checked, e);
102
+ if(cat) {
103
+ const layer = layers[cat.id];
104
+ if(e.target.checked) {
105
+ // show group of markers
106
+ map.addLayer(layer.group);
107
+
108
+ // if it's a children, put the parent to indeterminate
109
+ indeterminateInput(cat.parent);
110
+ } else {
111
+ // hide group of markers
112
+ map.removeLayer(layer.group);
113
+ // if it's a children, put the parent to indeterminate
114
+ cat.children().forEach((c) => {
115
+ let $el = $(`.awesome_map-category-${c.id}`);
116
+ if($el.parent().prev().prop("checked")) {
117
+ $el.click();
118
+ }
139
119
  });
120
+ }
121
+ // sync tags
122
+ updateHashtagLayers();
140
123
  }
141
124
  });
142
125
 
143
-
144
- // add categories control layers
145
- if(window.AwesomeMap.categories.length) {
146
- let lastLayer = layers[Object.keys(layers)[Object.keys(layers).length - 1]];
147
- // Add Categories "title"
148
- if(lastLayer) {
149
- lastLayer.label = `${lastLayer.label}<hr><b>${window.DecidimAwesome.texts.categories}</b>`;
150
- control.removeLayer(lastLayer.group);
151
- control.addOverlay(lastLayer.group, lastLayer.label);
126
+ const indeterminateInput = (id) => {
127
+ $('[class^="awesome_map-category-"]').parent().prev().prop("indeterminate", false);
128
+ if(id) {
129
+ let $input = $(`.awesome_map-category-${id}`).parent().prev();
130
+ if(!$input.prop("checked")) {
131
+ $input.prop("indeterminate", true);
132
+ }
152
133
  }
153
-
154
- window.AwesomeMap.categories.forEach((category) => {
155
- // add control layer for this category
156
- layers[category.id] = {
157
- label: `<i class="awesome_map-category_${category.id}"></i> ${category.name}`,
158
- group: L.featureGroup.subGroup(cluster)
159
- };
160
- layers[category.id].group.addTo(map);
161
- control.addOverlay(layers[category.id].group, layers[category.id].label);
162
- // hide layer by default, it will be activated if there's any marker in it
163
- setTimeout(() => {
164
- $(`.awesome_map-category_${category.id}`).closest("label").hide();
165
- });
134
+ };
135
+
136
+ const updateHashtagLayers = () => {
137
+ // hide all
138
+ $(".awesome_map-hashtags-selector").each((_idx, el) => {
139
+ const layer = layers[$(el).closest("label").data("layer")];
140
+ if(layer) {
141
+ map.removeLayer(layer.group);
142
+ }
166
143
  });
167
-
168
- // watch events for subcategories syncronitzation
169
- const getCatFromClass = (name) => {
170
- let id = name.match(/awesome_map-category_(\d+)/)
171
- if(!id) return;
172
- const cat = getCategory(id[1]);
173
- if(!cat || !cat.name) return;
174
-
175
- return cat;
176
- };
177
-
178
- const indeterminateInput = (id) => {
179
- $('[class^="awesome_map-category_"]').parent().prev().prop("indeterminate", false);
180
- if(id) {
181
- let $input = $(`.awesome_map-category_${id}`).parent().prev();
182
- if(!$input.prop("checked")) {
183
- $input.prop("indeterminate", true);
184
- }
144
+ // show selected only
145
+ $(".awesome_map-hashtags-selector:checked").each((_idx, el) => {
146
+ const layer = layers[$(el).closest("label").data("layer")];
147
+ if(layer) {
148
+ map.addLayer(layer.group);
185
149
  }
186
- };
187
-
188
- map.on('overlayadd', (e) => {
189
- const cat = getCatFromClass(e.name);
190
- if(!cat) return;
191
- // if it's a children, put the parent to indeterminate
192
- indeterminateInput(cat.parent);
193
150
  });
194
-
195
- // on remove a parent category, remove all children
196
- map.on('overlayremove', (e) => {
197
- const cat = getCatFromClass(e.name);
198
- if(!cat) return;
199
- cat.children().forEach((c) => {
200
- let $el = $(`.awesome_map-category_${c.id}`);
201
- if($el.parent().prev().prop("checked")) {
202
- $el.click();
203
- }
204
- });
151
+ // hide non-selected categories
152
+ $(".awesome_map-categories-selector:not(:checked)").each((_idx, el) => {
153
+ const layer = layers[$(el).closest("label").data("layer")];
154
+ console.log(el, layer, map)
155
+ if(layer) {
156
+ map.addLayer(layer.group);
157
+ map.removeLayer(layer.group);
158
+ }
205
159
  });
160
+ };
161
+
162
+ // hashtag events
163
+ $("#awesome-map").on("change", ".awesome_map-hashtags-selector", (e) => {
164
+ e.preventDefault();
165
+ e.stopPropagation();
166
+ const tag = $(e.target).closest("label").data("layer");
167
+ // console.log("changed, layer", tag, "checked", e.target.checked, e);
168
+ if(tag) {
169
+ updateHashtagLayers();
170
+ }
171
+ });
206
172
 
207
- }
173
+ // select/deselect all tags
174
+ $("#awesome-map").on("click", ".awesome_map-toggle_all_tags", (e) => {
175
+ e.preventDefault();
176
+ e.stopPropagation();
177
+ $("#awesome-map .awesome_map-hashtags-selector").prop("checked", $("#awesome-map .awesome_map-hashtags-selector:checked").length < $("#awesome-map .awesome_map-hashtags-selector").length);
178
+ updateHashtagLayers();
179
+ });
208
180
 
181
+ // sub-layer hashtag title toggle
182
+ $("#awesome-map").on("click", ".awesome_map-title-control", (e) => {
183
+ e.preventDefault();
184
+ e.stopPropagation();
185
+ $("#awesome_map-hashtags-control").toggleClass("active");
186
+ });
209
187
  };
210
188
 
211
- $("#map").on("ready.decidim", (ev, map) => {
189
+
190
+ $("#map").on("ready.decidim", (_e, map) => {
191
+ if(options.center) {
192
+ map.setView(options.center, options.zoom);
193
+ }
212
194
  loadElements(map);
195
+
196
+ // order hashtags alphabetically
197
+ exports.AwesomeMap.hashtagAdded = (_hashtag, $div) => {
198
+ let $last = $div.contents("label:last");
199
+ if($last.prev("label").length) {
200
+ // move the label to order it alphabetically
201
+ $div.contents("label").each((_idx, el) => {
202
+ if($(el).text().localeCompare($last.text()) > 0) {
203
+ $(el).before($last);
204
+ return false;
205
+ }
206
+ });
207
+ }
208
+ };
213
209
  });
214
210
 
215
211
  })(window);