decidim-core 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/decidim_core_manifest.js +2 -0
  3. data/app/assets/javascripts/decidim/map.js.es6.erb +95 -0
  4. data/app/assets/stylesheets/decidim/map.css +3 -0
  5. data/app/assets/stylesheets/decidim/modules/_layout.scss +2 -4
  6. data/app/assets/stylesheets/decidim/modules/_margins.scss +3 -2
  7. data/app/assets/stylesheets/decidim/modules/_process-header.scss +15 -2
  8. data/app/assets/stylesheets/decidim/modules/_process-phase.scss +4 -1
  9. data/app/assets/stylesheets/decidim/utils/_settings.scss +3 -3
  10. data/app/commands/decidim/create_report.rb +90 -0
  11. data/app/controllers/concerns/decidim/devise_controllers.rb +26 -0
  12. data/app/controllers/concerns/decidim/payload_info.rb +23 -0
  13. data/app/controllers/concerns/decidim/user_profile.rb +2 -1
  14. data/app/controllers/decidim/application_controller.rb +25 -1
  15. data/app/controllers/decidim/authorizations_controller.rb +10 -2
  16. data/app/controllers/decidim/cookie_policy_controller.rb +5 -0
  17. data/app/controllers/decidim/devise/confirmations_controller.rb +1 -9
  18. data/app/controllers/decidim/devise/invitations_controller.rb +1 -9
  19. data/app/controllers/decidim/devise/omniauth_registrations_controller.rb +1 -10
  20. data/app/controllers/decidim/devise/passwords_controller.rb +1 -9
  21. data/app/controllers/decidim/devise/registrations_controller.rb +2 -15
  22. data/app/controllers/decidim/devise/sessions_controller.rb +9 -15
  23. data/{lib → app/controllers}/decidim/features/base_controller.rb +5 -1
  24. data/app/controllers/decidim/participatory_process_groups_controller.rb +22 -0
  25. data/app/controllers/decidim/participatory_process_steps_controller.rb +2 -0
  26. data/app/controllers/decidim/participatory_processes_controller.rb +14 -3
  27. data/app/controllers/decidim/reports_controller.rb +33 -0
  28. data/app/controllers/decidim/static_map_controller.rb +18 -0
  29. data/app/forms/decidim/report_form.rb +12 -0
  30. data/app/helpers/decidim/application_helper.rb +0 -2
  31. data/app/helpers/decidim/feature_reference_helper.rb +22 -0
  32. data/app/helpers/decidim/map_helper.rb +41 -0
  33. data/app/helpers/decidim/organization_scopes_helper.rb +41 -0
  34. data/app/helpers/decidim/replace_buttons_helper.rb +42 -0
  35. data/app/helpers/decidim/resource_helper.rb +2 -1
  36. data/app/helpers/decidim/translations_helper.rb +1 -1
  37. data/app/mailers/decidim/reported_mailer.rb +35 -0
  38. data/app/middleware/decidim/current_organization.rb +29 -3
  39. data/app/models/decidim/abilities/everyone.rb +1 -0
  40. data/app/models/decidim/moderation.rb +13 -0
  41. data/app/models/decidim/newsletter.rb +1 -0
  42. data/app/models/decidim/organization.rb +2 -0
  43. data/app/models/decidim/participatory_process.rb +11 -0
  44. data/app/models/decidim/participatory_process_group.rb +15 -0
  45. data/app/models/decidim/participatory_process_step.rb +1 -1
  46. data/app/models/decidim/report.rb +23 -0
  47. data/app/services/decidim/public_processes.rb +42 -0
  48. data/app/services/decidim/resource_search.rb +17 -1
  49. data/app/services/decidim/static_map_generator.rb +49 -0
  50. data/app/uploaders/decidim/application_uploader.rb +4 -1
  51. data/app/uploaders/decidim/official_image_footer_uploader.rb +2 -2
  52. data/app/uploaders/decidim/official_image_header_uploader.rb +1 -1
  53. data/app/validators/geocoding_validator.rb +22 -0
  54. data/app/views/decidim/authorizations/new.html.erb +1 -1
  55. data/app/views/decidim/participatory_process_groups/_participatory_process_group.html.erb +22 -0
  56. data/app/views/decidim/participatory_process_groups/show.html.erb +11 -0
  57. data/app/views/decidim/participatory_processes/index.html.erb +1 -1
  58. data/app/views/decidim/participatory_processes/show.html.erb +18 -5
  59. data/app/views/decidim/reported_mailer/hide.html.erb +5 -0
  60. data/app/views/decidim/reported_mailer/report.html.erb +5 -0
  61. data/app/views/decidim/shared/_address_details.html.erb +9 -0
  62. data/app/views/decidim/shared/_flag_modal.html.erb +20 -0
  63. data/app/views/decidim/shared/_share_modal.html.erb +5 -5
  64. data/app/views/decidim/shared/_static_map.html.erb +17 -0
  65. data/app/views/devise/mailer/invite_collaborator.html.erb +17 -0
  66. data/app/views/devise/mailer/invite_collaborator.text.erb +15 -0
  67. data/app/views/layouts/decidim/_application.html.erb +5 -12
  68. data/app/views/layouts/decidim/{_social_meta.html.erb → _head.html.erb} +13 -0
  69. data/app/views/layouts/decidim/_head_extra.html.erb +7 -0
  70. data/app/views/layouts/decidim/_process_header.html.erb +15 -9
  71. data/app/views/layouts/decidim/_process_header_steps.html.erb +3 -3
  72. data/app/views/layouts/decidim/{_header.html.erb → _wrapper.html.erb} +47 -10
  73. data/app/views/pages/home/_highlighted_processes.html.erb +2 -2
  74. data/config/i18n-tasks.yml +3 -0
  75. data/config/locales/ca.yml +40 -1
  76. data/config/locales/en.yml +40 -1
  77. data/config/locales/es.yml +40 -1
  78. data/config/locales/eu.yml +235 -1
  79. data/config/locales/fi.yml +375 -0
  80. data/config/routes.rb +4 -0
  81. data/db/migrate/20170215115407_add_organization_custom_reference.rb +11 -0
  82. data/db/migrate/20170220110740_remove_steps_short_description.rb +21 -0
  83. data/db/migrate/20170221094835_add_scopes_to_processes.rb +6 -0
  84. data/db/migrate/20170228142440_add_participatory_process_groups.rb +15 -0
  85. data/db/migrate/20170306144354_add_secondary_hosts_to_organizations.rb +5 -0
  86. data/db/migrate/20170307084957_create_reports.rb +15 -0
  87. data/db/migrate/20170308091316_create_moderations.rb +12 -0
  88. data/db/migrate/20170313095436_add_available_authorizations_to_organization.rb +10 -0
  89. data/db/seeds.rb +20 -10
  90. data/lib/decidim/core.rb +12 -1
  91. data/lib/decidim/core/api.rb +1 -0
  92. data/lib/decidim/core/api/decidim_type.rb +12 -0
  93. data/lib/decidim/core/api/process_step_type.rb +0 -6
  94. data/lib/decidim/core/api/user_type.rb +4 -0
  95. data/lib/decidim/core/engine.rb +27 -0
  96. data/lib/decidim/core/test.rb +5 -0
  97. data/lib/decidim/core/test/factories.rb +52 -3
  98. data/lib/decidim/core/test/shared_examples/comments_examples.rb +215 -0
  99. data/lib/decidim/core/test/shared_examples/has_reference.rb +30 -0
  100. data/lib/decidim/core/test/shared_examples/manage_moderations_examples.rb +75 -0
  101. data/lib/decidim/core/test/shared_examples/reportable.rb +44 -0
  102. data/lib/decidim/core/test/shared_examples/reports_examples.rb +64 -0
  103. data/lib/decidim/core/version.rb +2 -2
  104. data/lib/decidim/has_reference.rb +50 -0
  105. data/lib/decidim/query_extensions.rb +4 -0
  106. data/lib/decidim/reportable.rb +46 -0
  107. data/vendor/assets/javascripts/leaflet-svg-icon.js +179 -0
  108. data/vendor/assets/javascripts/leaflet-tilelayer-here.js +166 -0
  109. data/vendor/assets/javascripts/leaflet.js +9 -0
  110. data/vendor/assets/javascripts/leaflet.markercluster.js +7 -0
  111. data/vendor/assets/javascripts/quill.min.js +8 -8
  112. data/vendor/assets/javascripts/quill.min.js.map +1 -1
  113. data/vendor/assets/stylesheets/MarkerCluster.Default.css +60 -0
  114. data/vendor/assets/stylesheets/MarkerCluster.css +14 -0
  115. data/vendor/assets/stylesheets/leaflet.css +624 -0
  116. data/vendor/assets/stylesheets/quill.bubble.css +75 -4
  117. data/vendor/assets/stylesheets/quill.core.css +21 -2
  118. data/vendor/assets/stylesheets/quill.snow.css +29 -3
  119. metadata +107 -27
  120. data/app/views/layouts/decidim/_footer.html.erb +0 -36
  121. data/app/views/layouts/decidim/_meta.html.erb +0 -4
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require "active_support/concern"
3
+
4
+ module Decidim
5
+ # A concern with the features needed when you want a model to have a
6
+ # reference.
7
+ module HasReference
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ before_save :store_reference
12
+
13
+ validates :reference, presence: true
14
+
15
+ def reference
16
+ self[:reference] || calculate_reference
17
+ end
18
+
19
+ private
20
+
21
+ # Public: Calculates a unique reference for the model in
22
+ # the following format:
23
+ #
24
+ # "BCN-DPP-2017-02-6589" which in this example translates to:
25
+ #
26
+ # BCN: A setting configured at the organization to be prepended to each reference.
27
+ # PROP: Unique name identifier for a resource: Decidim::Proposals::Proposal (MEET for meetings or PROJ for projects).
28
+ # 2017-02: Year-Month of the resource creation date
29
+ # 6589: ID of the resource
30
+ #
31
+ # Returns a String.
32
+ def calculate_reference
33
+ return unless feature
34
+
35
+ ref = feature.participatory_process.organization.reference_prefix
36
+ class_identifier = self.class.name.demodulize[0..3].upcase
37
+ year_month = (created_at || Time.current).strftime("%Y-%m")
38
+
39
+ [ref, class_identifier, year_month, id].join("-")
40
+ end
41
+
42
+ # Internal: Sets the unique reference to the model.
43
+ #
44
+ # Returns nothing.
45
+ def store_reference
46
+ self[:reference] ||= calculate_reference
47
+ end
48
+ end
49
+ end
50
+ end
@@ -27,6 +27,10 @@ module Decidim
27
27
  ctx[:current_user]
28
28
  }
29
29
  end
30
+
31
+ field :decidim, DecidimType, "Decidim's framework properties." do
32
+ resolve ->(_obj, _args, ctx) { Decidim }
33
+ end
30
34
  end
31
35
  end
32
36
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ require "active_support/concern"
3
+
4
+ module Decidim
5
+ # A concern with the features needed when you want a model to be reportable
6
+ module Reportable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ has_one :moderation, as: :reportable, foreign_key: "decidim_reportable_id", foreign_type: "decidim_reportable_type", class_name: "Decidim::Moderation"
11
+ has_many :reports, through: :moderation
12
+
13
+ scope :reported, -> { left_outer_joins(:moderation).where(Decidim::Moderation.arel_table[:report_count].gt 0) }
14
+ scope :hidden, -> { left_outer_joins(:moderation).where.not(Decidim::Moderation.arel_table[:hidden_at].eq nil) }
15
+ scope :not_hidden, -> { left_outer_joins(:moderation).where(Decidim::Moderation.arel_table[:hidden_at].eq nil) }
16
+
17
+ # Public: Check if the user has reported the proposal.
18
+ #
19
+ # Returns Boolean.
20
+ def reported_by?(user)
21
+ reports.where(user: user).any?
22
+ end
23
+
24
+ # Public: Checks if the proposal is hidden or not.
25
+ #
26
+ # Returns Boolean.
27
+ def hidden?
28
+ moderation&.hidden_at&.present?
29
+ end
30
+
31
+ # Public: Checks if the proposal has been reported or not.
32
+ #
33
+ # Returns Boolean.
34
+ def reported?
35
+ moderation&.report_count&.positive?
36
+ end
37
+
38
+ # Public: The reported content
39
+ #
40
+ # Returns html content
41
+ def reported_content
42
+ raise NotImplementedError
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,179 @@
1
+ //Leaflet-SVGIcon
2
+ //SVG icon for any marker class
3
+ //Ilya Atkin
4
+ //ilya.atkin@unh.edu
5
+
6
+ L.DivIcon.SVGIcon = L.DivIcon.extend({
7
+ options: {
8
+ "circleText": "",
9
+ "className": "svg-icon",
10
+ "circleAnchor": null, //defaults to [iconSize.x/2, iconSize.x/2]
11
+ "circleColor": null, //defaults to color
12
+ "circleOpacity": null, // defaults to opacity
13
+ "circleFillColor": "rgb(255,255,255)",
14
+ "circleFillOpacity": null, //default to opacity
15
+ "circleRatio": 0.5,
16
+ "circleWeight": null, //defaults to weight
17
+ "color": "rgb(0,102,255)",
18
+ "fillColor": null, // defaults to color
19
+ "fillOpacity": 0.4,
20
+ "fontColor": "rgb(0, 0, 0)",
21
+ "fontOpacity": "1",
22
+ "fontSize": null, // defaults to iconSize.x/4
23
+ "iconAnchor": null, //defaults to [iconSize.x/2, iconSize.y] (point tip)
24
+ "iconSize": L.point(32,48),
25
+ "opacity": 1,
26
+ "popupAnchor": null,
27
+ "weight": 2
28
+ },
29
+ initialize: function(options) {
30
+ options = L.Util.setOptions(this, options)
31
+
32
+ if (!options.circleAnchor) {
33
+ options.circleAnchor = L.point(Number(options.iconSize.x)/2, Number(options.iconSize.x)/2)
34
+ }
35
+ if (!options.circleColor) {
36
+ options.circleColor = options.color
37
+ }
38
+ if (!options.circleFillOpacity) {
39
+ options.circleFillOpacity = options.opacity
40
+ }
41
+ if (!options.circleOpacity) {
42
+ options.circleOpacity = options.opacity
43
+ }
44
+ if (!options.circleWeight) {
45
+ options.circleWeight = options.weight
46
+ }
47
+ if (!options.fillColor) {
48
+ options.fillColor = options.color
49
+ }
50
+ if (!options.fontSize) {
51
+ options.fontSize = Number(options.iconSize.x/4)
52
+ }
53
+ if (!options.iconAnchor) {
54
+ options.iconAnchor = L.point(Number(options.iconSize.x)/2, Number(options.iconSize.y))
55
+ }
56
+ if (!options.popupAnchor) {
57
+ options.popupAnchor = L.point(0, (-0.75)*(options.iconSize.y))
58
+ }
59
+
60
+ var path = this._createPath()
61
+ var circle = this._createCircle()
62
+
63
+ options.html = this._createSVG()
64
+ },
65
+ _createCircle: function() {
66
+ var cx = Number(this.options.circleAnchor.x)
67
+ var cy = Number(this.options.circleAnchor.y)
68
+ var radius = this.options.iconSize.x/2 * Number(this.options.circleRatio)
69
+ var fill = this.options.circleFillColor.replace("rgb(", "rgba(").replace(")", "," + this.options.circleFillOpacity + ")")
70
+ var stroke = this.options.circleColor.replace("rgb(", "rgba(").replace(")", "," + this.options.circleOpacity + ")")
71
+ var strokeWidth = this.options.circleWeight
72
+ var className = this.options.className + "-circle"
73
+
74
+ var circle = '<circle class="' + className + '" cx="' + cx + '" cy="' + cy + '" r="' + radius + '" fill="' + fill + '" stroke="' + stroke + '" stroke-width="' + strokeWidth + '"/>'
75
+
76
+ return circle
77
+ },
78
+ _createPathDescription: function() {
79
+ var height = Number(this.options.iconSize.y)
80
+ var width = Number(this.options.iconSize.x)
81
+ var weight = Number(this.options.weight)
82
+ var margin = weight / 2
83
+
84
+ var startPoint = "M " + margin + " " + (width/2) + " "
85
+ var leftLine = "L " + (width/2) + " " + (height - weight) + " "
86
+ var rightLine = "L " + (width - margin) + " " + (width/2) + " "
87
+ var arc = "A " + (width/4) + " " + (width/4) + " 0 0 0 " + margin + " " + (width/2) + " Z"
88
+
89
+ var d = startPoint + leftLine + rightLine + arc
90
+
91
+ return d
92
+ },
93
+ _createPath: function() {
94
+ var pathDescription = this._createPathDescription()
95
+ var strokeWidth = this.options.weight
96
+ var stroke = this.options.color.replace("rgb(", "rgba(").replace(")", "," + this.options.opacity + ")")
97
+ var fill = this.options.fillColor.replace("rgb(", "rgba(").replace(")", "," + this.options.fillOpacity + ")")
98
+ var className = this.options.className + "-path"
99
+
100
+ var path = '<path class="' + className + '" d="' + pathDescription + '" stroke-width="' + strokeWidth + '" stroke="' + stroke + '" fill="' + fill + '"/>'
101
+
102
+ return path
103
+ },
104
+ _createSVG: function() {
105
+ var path = this._createPath()
106
+ var circle = this._createCircle()
107
+ var text = this._createText()
108
+ var className = this.options.className + "-svg"
109
+
110
+ var style = "width:" + this.options.iconSize.x + "; height:" + this.options.iconSize.y + ";"
111
+
112
+ var svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="' + className + '" style="' + style + '">' + path + circle + text + '</svg>'
113
+
114
+ return svg
115
+ },
116
+ _createText: function() {
117
+ var fontSize = this.options.fontSize + "px"
118
+ var lineHeight = Number(this.options.fontSize)
119
+
120
+ var x = Number(this.options.iconSize.x) / 2
121
+ var y = x + (lineHeight * 0.35) //35% was found experimentally
122
+ var circleText = this.options.circleText
123
+ var textColor = this.options.fontColor.replace("rgb(", "rgba(").replace(")", "," + this.options.fontOpacity + ")")
124
+
125
+ var text = '<text text-anchor="middle" x="' + x + '" y="' + y + '" style="font-size: ' + fontSize + '" fill="' + textColor + '">' + circleText + '</text>'
126
+
127
+ return text
128
+ }
129
+ })
130
+
131
+ L.divIcon.svgIcon = function(options) {
132
+ return new L.DivIcon.SVGIcon(options)
133
+ }
134
+
135
+ L.Marker.SVGMarker = L.Marker.extend({
136
+ options: {
137
+ "iconFactory": L.divIcon.svgIcon,
138
+ "iconOptions": {}
139
+ },
140
+ initialize: function(latlng, options) {
141
+ options = L.Util.setOptions(this, options)
142
+ options.icon = options.iconFactory(options.iconOptions)
143
+ this._latlng = latlng
144
+ },
145
+ onAdd: function(map) {
146
+ L.Marker.prototype.onAdd.call(this, map)
147
+ },
148
+ setStyle: function(style) {
149
+ if (this._icon) {
150
+ var svg = this._icon.children[0]
151
+ var iconBody = this._icon.children[0].children[0]
152
+ var iconCircle = this._icon.children[0].children[1]
153
+
154
+ if (style.color && !style.iconOptions) {
155
+ var stroke = style.color.replace("rgb","rgba").replace(")", ","+this.options.icon.options.opacity+")")
156
+ var fill = style.color.replace("rgb","rgba").replace(")", ","+this.options.icon.options.fillOpacity+")")
157
+ iconBody.setAttribute("stroke", stroke)
158
+ iconBody.setAttribute("fill", fill)
159
+ iconCircle.setAttribute("stroke", stroke)
160
+
161
+ this.options.icon.fillColor = fill
162
+ this.options.icon.color = stroke
163
+ this.options.icon.circleColor = stroke
164
+ }
165
+ if (style.opacity) {
166
+ this.setOpacity(style.opacity)
167
+ }
168
+ if (style.iconOptions) {
169
+ if (style.color) { style.iconOptions.color = style.color }
170
+ iconOptions = L.Util.setOptions(this.options.icon, style.iconOptions)
171
+ this.setIcon(L.divIcon.svgIcon(iconOptions))
172
+ }
173
+ }
174
+ }
175
+ })
176
+
177
+ L.marker.svgMarker = function(latlng, options) {
178
+ return new L.Marker.SVGMarker(latlng, options)
179
+ }
@@ -0,0 +1,166 @@
1
+
2
+ // 🍂class TileLayer.HERE
3
+ // Tile layer for HERE maps tiles.
4
+ L.TileLayer.HERE = L.TileLayer.extend({
5
+
6
+ options: {
7
+ subdomains: '1234',
8
+ minZoom: 2,
9
+ maxZoom: 18,
10
+
11
+ // 🍂option scheme: String = 'normal.day'
12
+ // The "map scheme", as documented in the HERE API.
13
+ scheme: 'normal.day',
14
+
15
+ // 🍂option resource: String = 'maptile'
16
+ // The "map resource", as documented in the HERE API.
17
+ resource: 'maptile',
18
+
19
+ // 🍂option mapId: String = 'newest'
20
+ // Version of the map tiles to be used, or a hash of an unique map
21
+ mapId: 'newest',
22
+
23
+ // 🍂option format: String = 'png8'
24
+ // Image format to be used (`png8`, `png`, or `jpg`)
25
+ format: 'png8',
26
+
27
+ // 🍂option appId: String = ''
28
+ // Required option. The `app_id` provided as part of the HERE credentials
29
+ appId: '',
30
+
31
+ // 🍂option appCode: String = ''
32
+ // Required option. The `app_code` provided as part of the HERE credentials
33
+ appCode: '',
34
+ },
35
+
36
+
37
+ initialize: function initialize(options) {
38
+ options = L.setOptions(this, options);
39
+
40
+ // Decide if this scheme uses the aerial servers or the basemap servers
41
+ var schemeStart = options.scheme.split('.')[0];
42
+ options.tileResolution = 256;
43
+
44
+ if (L.Browser.retina) {
45
+ options.tileResolution = 512;
46
+ }
47
+
48
+ // {Base URL}{Path}/{resource (tile type)}/{map id}/{scheme}/{zoom}/{column}/{row}/{size}/{format}
49
+ // ?app_id={YOUR_APP_ID}
50
+ // &app_code={YOUR_APP_CODE}
51
+ // &{param}={value}
52
+
53
+ var path = '/{resource}/2.1/{resource}/{mapId}/{scheme}/{z}/{x}/{y}/{tileResolution}/{format}?app_id={appId}&app_code={appCode}';
54
+ var attributionPath = '/maptile/2.1/copyright/{mapId}?app_id={appId}&app_code={appCode}';
55
+
56
+ var tileServer = 'base.maps.api.here.com';
57
+ if (schemeStart == 'satellite' ||
58
+ schemeStart == 'terrain' ||
59
+ schemeStart == 'hybrid') {
60
+ tileServer = 'aerial.maps.api.here.com';
61
+ }
62
+ if (options.scheme.indexOf('.traffic.') !== -1) {
63
+ tileServer = 'traffic.maps.api.here.com';
64
+ }
65
+
66
+ var tileUrl = 'https://{s}.' + tileServer + path;
67
+
68
+ this._attributionUrl = L.Util.template('https://1.' + tileServer + attributionPath, this.options);
69
+
70
+ L.TileLayer.prototype.initialize.call(this, tileUrl, options);
71
+
72
+ this._attributionText = '';
73
+
74
+ },
75
+
76
+ onAdd: function onAdd(map) {
77
+ L.TileLayer.prototype.onAdd.call(this, map);
78
+
79
+ if (!this._attributionBBoxes) {
80
+ this._fetchAttributionBBoxes();
81
+ }
82
+ },
83
+
84
+ onRemove: function onRemove(map) {
85
+ L.TileLayer.prototype.onRemove.call(this, map);
86
+
87
+ this._map.attributionControl.removeAttribution(this._attributionText);
88
+
89
+ this._map.off('moveend zoomend resetview', this._findCopyrightBBox, this);
90
+ },
91
+
92
+ _fetchAttributionBBoxes: function _onMapMove() {
93
+ var xmlhttp = new XMLHttpRequest();
94
+ xmlhttp.onreadystatechange = L.bind(function(){
95
+ if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
96
+ this._parseAttributionBBoxes(JSON.parse(xmlhttp.responseText));
97
+ }
98
+ }, this);
99
+ xmlhttp.open("GET", this._attributionUrl, true);
100
+ xmlhttp.send();
101
+ },
102
+
103
+ _parseAttributionBBoxes: function _parseAttributionBBoxes(json) {
104
+ if (!this._map) { return; }
105
+ var providers = json[this.options.scheme.split('.')[0]] || json.normal;
106
+ for (var i=0; i<providers.length; i++) {
107
+ if (providers[i].boxes) {
108
+ for (var j=0; j<providers[i].boxes.length; j++) {
109
+ var box = providers[i].boxes[j];
110
+ providers[i].boxes[j] = L.latLngBounds( [ [box[0], box[1]], [box[2], box[3]] ]);
111
+ }
112
+ }
113
+ }
114
+
115
+ this._map.on('moveend zoomend resetview', this._findCopyrightBBox, this);
116
+
117
+ this._attributionProviders = providers;
118
+
119
+ this._findCopyrightBBox();
120
+ },
121
+
122
+ _findCopyrightBBox: function _findCopyrightBBox() {
123
+ if (!this._map) { return; }
124
+ var providers = this._attributionProviders;
125
+ var visibleProviders = [];
126
+ var zoom = this._map.getZoom();
127
+ var visibleBounds = this._map.getBounds();
128
+
129
+ for (var i=0; i<providers.length; i++) {
130
+ if (providers[i].minLevel < zoom && providers[i].maxLevel > zoom)
131
+
132
+ if (!providers[i].boxes) {
133
+ // No boxes = attribution always visible
134
+ visibleProviders.push(providers[i]);
135
+ break;
136
+ }
137
+
138
+ for (var j=0; j<providers[i].boxes.length; j++) {
139
+ var box = providers[i].boxes[j];
140
+ if (visibleBounds.overlaps(box)) {
141
+ visibleProviders.push(providers[i]);
142
+ break;
143
+ }
144
+ }
145
+ }
146
+
147
+ var attributions = ['<a href="https://legal.here.com/terms/serviceterms/gb/">HERE maps</a>'];
148
+ for (var i=0; i<visibleProviders.length; i++) {
149
+ var provider = visibleProviders[i];
150
+ attributions.push('<abbr title="' + provider.alt + '">' + provider.label + '</abbr>');
151
+ }
152
+
153
+ var attributionText = '© ' + attributions.join(', ') + '. ';
154
+
155
+ if (attributionText !== this._attributionText) {
156
+ this._map.attributionControl.removeAttribution(this._attributionText);
157
+ this._map.attributionControl.addAttribution(this._attributionText = attributionText);
158
+ }
159
+ },
160
+
161
+ });
162
+
163
+
164
+ L.tileLayer.here = function(opts){
165
+ return new L.TileLayer.HERE(opts);
166
+ }