jumbo-jekyll-theme 1.4.5.1 → 1.4.6
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.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/_data/authors.yml +7 -7
- data/assets/js/app/search.js +1 -1
- data/assets/js/package-blog.js +3 -3
- data/assets/js/package-developer-services.js +4 -4
- data/assets/js/package-extended.js +4 -4
- data/assets/js/package-home.js +4 -4
- data/assets/js/package-main.js +3 -3
- data/assets/js/package-search.js +3 -3
- data/assets/js/vendor/cookieconsent.js +1504 -0
- data/assets/js/vendor/jquery.js +10364 -0
- data/assets/js/vendor/{jquery.rss.min.js → jquery.rss.js} +1 -1
- data/assets/js/vendor/jquery.validate.js +1601 -0
- data/assets/js/vendor/lazysizes.js +698 -0
- data/assets/js/vendor/owl.carousel.js +3475 -0
- data/assets/js/vendor/picturefill.js +1471 -0
- metadata +11 -27
- data/_sass/custom.scss +0 -0
- data/assets/images/96boards-Logo.svg +0 -1
- data/assets/images/Linaro-Sprinkle.svg +0 -13
- data/assets/images/bof-devicetree.jpg +0 -0
- data/assets/images/css3.png +0 -0
- data/assets/images/device-tree-vertical-logo.png +0 -0
- data/assets/images/html5.png +0 -0
- data/assets/images/jekyll.svg +0 -1
- data/assets/images/js.jpeg +0 -0
- data/assets/js/app/developer-services.js +0 -26
- data/assets/js/app/mixitup.js +0 -23
- data/assets/js/app/openhours-timer.js +0 -23
- data/assets/js/app/rss.js +0 -96
- data/assets/js/app/sticky-tab-bar-concept.js +0 -74
- data/assets/js/vendor/cookieconsent.min.js +0 -8
- data/assets/js/vendor/filtrify.js +0 -11
- data/assets/js/vendor/flipclock.min.js +0 -2
- data/assets/js/vendor/jquery.min.js +0 -4
- data/assets/js/vendor/jquery.validate.min.js +0 -4
- data/assets/js/vendor/lazysizes.min.js +0 -2
- data/assets/js/vendor/loadCSS.min.js +0 -2
- data/assets/js/vendor/moment.min.js +0 -1
- data/assets/js/vendor/owl.carousel.min.js +0 -103
- data/assets/js/vendor/picturefill.min.js +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 339e7dc076e245f36d2f225a99ae768516424f59
|
|
4
|
+
data.tar.gz: '04353188df885b6c72588d59c723af4498490eb1'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: adf9da07694434c0707b8f97a4081fa219b16e68de83860fa8b928d982621f136a4a66e128f6ed474bd5fe5b0884f75bcbe70590a1823bd1bd4f44d998a6fe7f
|
|
7
|
+
data.tar.gz: 272e62b5be1186fcaac6d9df29ca813af24bd6377c635a51527753f458f78fbf013119c3f2917c862f940e5573cb4a91014f8e0311fbfa307d5c3c9814a1af9a
|
data/README.md
CHANGED
|
@@ -16,4 +16,11 @@ Some of the features this theme offers:
|
|
|
16
16
|
|
|
17
17
|
* Lazy loading of content.
|
|
18
18
|
* Generated breadcrumb
|
|
19
|
-
* Easy navigation / footer management using YAML Data files.
|
|
19
|
+
* Easy navigation / footer management using YAML Data files.
|
|
20
|
+
|
|
21
|
+
# The Docs
|
|
22
|
+
The documentation for this theme is currently available through the Collaborate space. I will be adding to readthedocs/github in due course.
|
|
23
|
+
|
|
24
|
+
# Feature Requests / Bug Fixes
|
|
25
|
+
|
|
26
|
+
If anyone that uses the theme has any useful bug fixes / feature requests that may be of interest then please feel free to fork/submit a PR with your fixes/features.
|
data/_data/authors.yml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
entries:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
# entries:
|
|
2
|
+
# - shortname: kylekirkby
|
|
3
|
+
# avatar: https://en.gravatar.com/userimage/54786027/fc975171b4e83cd1c19b20697a1d4329.jpeg
|
|
4
|
+
# first_name: Kyle
|
|
5
|
+
# second_name: Kirkby
|
|
6
|
+
# website: https://kylekirkby.co.uk
|
|
7
|
+
# github: https://github.com/kylekirkby
|
data/assets/js/app/search.js
CHANGED
|
@@ -37,4 +37,4 @@ function getAllUrlParams(url) {
|
|
|
37
37
|
|
|
38
38
|
var searchQuery = getAllUrlParams().s;
|
|
39
39
|
|
|
40
|
-
$('#searchIframe').attr('src', "https://search.linaro.org/search
|
|
40
|
+
$('#searchIframe').attr('src', "https://search.linaro.org/search/&q=" + searchQuery);
|
data/assets/js/package-blog.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
//= require vendor/jquery
|
|
1
|
+
//= require vendor/jquery
|
|
2
2
|
//= require vendor/bootstrap
|
|
3
|
-
//= require vendor/cookieconsent
|
|
4
|
-
//= require vendor/lazysizes
|
|
3
|
+
//= require vendor/cookieconsent
|
|
4
|
+
//= require vendor/lazysizes
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
//= require vendor/lightbox
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
//= require vendor/jquery
|
|
1
|
+
//= require vendor/jquery
|
|
2
2
|
//= require vendor/bootstrap
|
|
3
|
-
//= require vendor/cookieconsent
|
|
4
|
-
//= require vendor/lazysizes
|
|
3
|
+
//= require vendor/cookieconsent
|
|
4
|
+
//= require vendor/lazysizes
|
|
5
5
|
|
|
6
6
|
//= require app/main
|
|
7
7
|
//= require app/tables
|
|
8
8
|
//= require vendor/mc-validate
|
|
9
|
-
//= require vendor/jquery.validate
|
|
9
|
+
//= require vendor/jquery.validate
|
|
10
10
|
//= require app/developer-services
|
|
11
11
|
//= require app/custom
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
//= require vendor/jquery
|
|
1
|
+
//= require vendor/jquery
|
|
2
2
|
//= require vendor/bootstrap
|
|
3
|
-
//= require vendor/cookieconsent
|
|
4
|
-
//= require vendor/owl.carousel
|
|
5
|
-
//= require vendor/lazysizes
|
|
3
|
+
//= require vendor/cookieconsent
|
|
4
|
+
//= require vendor/owl.carousel
|
|
5
|
+
//= require vendor/lazysizes
|
|
6
6
|
//= require vendor/ls.unveilhooks
|
|
7
7
|
//= require vendor/lightbox
|
|
8
8
|
//= require app/sticky-tab-bar
|
data/assets/js/package-home.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
//= require vendor/jquery
|
|
1
|
+
//= require vendor/jquery
|
|
2
2
|
//= require vendor/bootstrap
|
|
3
|
-
//= require vendor/cookieconsent
|
|
4
|
-
//= require vendor/lazysizes
|
|
5
|
-
//= require vendor/owl.carousel
|
|
3
|
+
//= require vendor/cookieconsent
|
|
4
|
+
//= require vendor/lazysizes
|
|
5
|
+
//= require vendor/owl.carousel
|
|
6
6
|
|
|
7
7
|
//= require app/main
|
|
8
8
|
//= require app/sticky-tab-bar
|
data/assets/js/package-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
//= require vendor/jquery
|
|
1
|
+
//= require vendor/jquery
|
|
2
2
|
//= require vendor/bootstrap
|
|
3
|
-
//= require vendor/cookieconsent
|
|
4
|
-
//= require vendor/lazysizes
|
|
3
|
+
//= require vendor/cookieconsent
|
|
4
|
+
//= require vendor/lazysizes
|
|
5
5
|
|
|
6
6
|
//= require app/main
|
|
7
7
|
//= require app/scroll-to-anchors
|
data/assets/js/package-search.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
//= require vendor/jquery
|
|
1
|
+
//= require vendor/jquery
|
|
2
2
|
//= require vendor/bootstrap
|
|
3
|
-
//= require vendor/cookieconsent
|
|
4
|
-
//= require vendor/lazysizes
|
|
3
|
+
//= require vendor/cookieconsent
|
|
4
|
+
//= require vendor/lazysizes
|
|
5
5
|
|
|
6
6
|
//= require app/main
|
|
7
7
|
//= require app/search
|
|
@@ -0,0 +1,1504 @@
|
|
|
1
|
+
(function(cc) {
|
|
2
|
+
// stop from running again, if accidently included more than once.
|
|
3
|
+
if (cc.hasInitialised) return;
|
|
4
|
+
|
|
5
|
+
var util = {
|
|
6
|
+
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
|
|
7
|
+
escapeRegExp: function(str) {
|
|
8
|
+
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
hasClass: function(element, selector) {
|
|
12
|
+
var s = ' ';
|
|
13
|
+
return element.nodeType === 1 &&
|
|
14
|
+
(s + element.className + s).replace(/[\n\t]/g, s).indexOf(s + selector + s) >= 0;
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
addClass: function(element, className) {
|
|
18
|
+
element.className += ' ' + className;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
removeClass: function(element, className) {
|
|
22
|
+
var regex = new RegExp('\\b' + this.escapeRegExp(className) + '\\b');
|
|
23
|
+
element.className = element.className.replace(regex, '');
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
interpolateString: function(str, callback) {
|
|
27
|
+
var marker = /{{([a-z][a-z0-9\-_]*)}}/ig;
|
|
28
|
+
return str.replace(marker, function(matches) {
|
|
29
|
+
return callback(arguments[1]) || '';
|
|
30
|
+
})
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
getCookie: function(name) {
|
|
34
|
+
var value = '; ' + document.cookie;
|
|
35
|
+
var parts = value.split('; ' + name + '=');
|
|
36
|
+
return parts.length != 2 ?
|
|
37
|
+
undefined : parts.pop().split(';').shift();
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
setCookie: function(name, value, expiryDays, domain, path) {
|
|
41
|
+
var exdate = new Date();
|
|
42
|
+
exdate.setDate(exdate.getDate() + (expiryDays || 365));
|
|
43
|
+
|
|
44
|
+
var cookie = [
|
|
45
|
+
name + '=' + value,
|
|
46
|
+
'expires=' + exdate.toUTCString(),
|
|
47
|
+
'path=' + (path || '/')
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
if (domain) {
|
|
51
|
+
cookie.push('domain=' + domain);
|
|
52
|
+
}
|
|
53
|
+
document.cookie = cookie.join(';');
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// only used for extending the initial options
|
|
57
|
+
deepExtend: function(target, source) {
|
|
58
|
+
for (var prop in source) {
|
|
59
|
+
if (source.hasOwnProperty(prop)) {
|
|
60
|
+
if (prop in target && this.isPlainObject(target[prop]) && this.isPlainObject(source[prop])) {
|
|
61
|
+
this.deepExtend(target[prop], source[prop]);
|
|
62
|
+
} else {
|
|
63
|
+
target[prop] = source[prop];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return target;
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// only used for throttling the 'mousemove' event (used for animating the revoke button when `animateRevokable` is true)
|
|
71
|
+
throttle: function(callback, limit) {
|
|
72
|
+
var wait = false;
|
|
73
|
+
return function() {
|
|
74
|
+
if (!wait) {
|
|
75
|
+
callback.apply(this, arguments);
|
|
76
|
+
wait = true;
|
|
77
|
+
setTimeout(function() {
|
|
78
|
+
wait = false;
|
|
79
|
+
}, limit);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// only used for hashing json objects (used for hash mapping palette objects, used when custom colours are passed through JavaScript)
|
|
85
|
+
hash: function(str) {
|
|
86
|
+
var hash = 0,
|
|
87
|
+
i, chr, len;
|
|
88
|
+
if (str.length === 0) return hash;
|
|
89
|
+
for (i = 0, len = str.length; i < len; ++i) {
|
|
90
|
+
chr = str.charCodeAt(i);
|
|
91
|
+
hash = ((hash << 5) - hash) + chr;
|
|
92
|
+
hash |= 0;
|
|
93
|
+
}
|
|
94
|
+
return hash;
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
normaliseHex: function(hex) {
|
|
98
|
+
if (hex[0] == '#') {
|
|
99
|
+
hex = hex.substr(1);
|
|
100
|
+
}
|
|
101
|
+
if (hex.length == 3) {
|
|
102
|
+
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
103
|
+
}
|
|
104
|
+
return hex;
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// used to get text colors if not set
|
|
108
|
+
getContrast: function(hex) {
|
|
109
|
+
hex = this.normaliseHex(hex);
|
|
110
|
+
var r = parseInt(hex.substr(0, 2), 16);
|
|
111
|
+
var g = parseInt(hex.substr(2, 2), 16);
|
|
112
|
+
var b = parseInt(hex.substr(4, 2), 16);
|
|
113
|
+
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
|
114
|
+
return (yiq >= 128) ? '#000' : '#fff';
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// used to change color on highlight
|
|
118
|
+
getLuminance: function(hex) {
|
|
119
|
+
var num = parseInt(this.normaliseHex(hex), 16),
|
|
120
|
+
amt = 38,
|
|
121
|
+
R = (num >> 16) + amt,
|
|
122
|
+
B = (num >> 8 & 0x00FF) + amt,
|
|
123
|
+
G = (num & 0x0000FF) + amt;
|
|
124
|
+
var newColour = (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (B<255?B<1?0:B:255)*0x100 + (G<255?G<1?0:G:255)).toString(16).slice(1);
|
|
125
|
+
return '#'+newColour;
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
isMobile: function() {
|
|
129
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
isPlainObject: function(obj) {
|
|
133
|
+
// The code "typeof obj === 'object' && obj !== null" allows Array objects
|
|
134
|
+
return typeof obj === 'object' && obj !== null && obj.constructor == Object;
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// valid cookie values
|
|
139
|
+
cc.status = {
|
|
140
|
+
deny: 'deny',
|
|
141
|
+
allow: 'allow',
|
|
142
|
+
dismiss: 'dismiss'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// detects the `transitionend` event name
|
|
146
|
+
cc.transitionEnd = (function() {
|
|
147
|
+
var el = document.createElement('div');
|
|
148
|
+
var trans = {
|
|
149
|
+
t: "transitionend",
|
|
150
|
+
OT: "oTransitionEnd",
|
|
151
|
+
msT: "MSTransitionEnd",
|
|
152
|
+
MozT: "transitionend",
|
|
153
|
+
WebkitT: "webkitTransitionEnd",
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
for (var prefix in trans) {
|
|
157
|
+
if (trans.hasOwnProperty(prefix) && typeof el.style[prefix + 'ransition'] != 'undefined') {
|
|
158
|
+
return trans[prefix];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return '';
|
|
162
|
+
}());
|
|
163
|
+
|
|
164
|
+
cc.hasTransition = !!cc.transitionEnd;
|
|
165
|
+
|
|
166
|
+
// array of valid regexp escaped statuses
|
|
167
|
+
var __allowedStatuses = Object.keys(cc.status).map(util.escapeRegExp);
|
|
168
|
+
|
|
169
|
+
// contains references to the custom <style> tags
|
|
170
|
+
cc.customStyles = {};
|
|
171
|
+
|
|
172
|
+
cc.Popup = (function() {
|
|
173
|
+
|
|
174
|
+
var defaultOptions = {
|
|
175
|
+
|
|
176
|
+
// if false, this prevents the popup from showing (useful for giving to control to another piece of code)
|
|
177
|
+
enabled: true,
|
|
178
|
+
|
|
179
|
+
// optional (expecting a HTML element) if passed, the popup is appended to this element. default is `document.body`
|
|
180
|
+
container: null,
|
|
181
|
+
|
|
182
|
+
// defaults cookie options - it is RECOMMENDED to set these values to correspond with your server
|
|
183
|
+
cookie: {
|
|
184
|
+
// This is the name of this cookie - you can ignore this
|
|
185
|
+
name: 'cookieconsent_status',
|
|
186
|
+
|
|
187
|
+
// This is the url path that the cookie 'name' belongs to. The cookie can only be read at this location
|
|
188
|
+
path: '/',
|
|
189
|
+
|
|
190
|
+
// This is the domain that the cookie 'name' belongs to. The cookie can only be read on this domain.
|
|
191
|
+
// - Guide to cookie domains - http://erik.io/blog/2014/03/04/definitive-guide-to-cookie-domains/
|
|
192
|
+
domain: '',
|
|
193
|
+
|
|
194
|
+
// The cookies expire date, specified in days (specify -1 for no expiry)
|
|
195
|
+
expiryDays: 365,
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// these callback hooks are called at certain points in the program execution
|
|
199
|
+
onPopupOpen: function() {},
|
|
200
|
+
onPopupClose: function() {},
|
|
201
|
+
onInitialise: function(status) {},
|
|
202
|
+
onStatusChange: function(status, chosenBefore) {},
|
|
203
|
+
onRevokeChoice: function() {},
|
|
204
|
+
|
|
205
|
+
// each item defines the inner text for the element that it references
|
|
206
|
+
content: {
|
|
207
|
+
header: 'Cookies used on the website!',
|
|
208
|
+
message: 'This website uses cookies to ensure you get the best experience on our website.',
|
|
209
|
+
dismiss: 'Got it!',
|
|
210
|
+
allow: 'Allow cookies',
|
|
211
|
+
deny: 'Decline',
|
|
212
|
+
link: 'Learn more',
|
|
213
|
+
href: 'http://cookiesandyou.com',
|
|
214
|
+
close: '❌',
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// This is the HTML for the elements above. The string {{header}} will be replaced with the equivalent text below.
|
|
218
|
+
// You can remove "{{header}}" and write the content directly inside the HTML if you want.
|
|
219
|
+
//
|
|
220
|
+
// - ARIA rules suggest to ensure controls are tabbable (so the browser can find the first control),
|
|
221
|
+
// and to set the focus to the first interactive control (http://w3c.github.io/aria-in-html/)
|
|
222
|
+
elements: {
|
|
223
|
+
header: '<span class="cc-header">{{header}}</span> ',
|
|
224
|
+
message: '<span id="cookieconsent:desc" class="cc-message">{{message}}</span>',
|
|
225
|
+
messagelink: '<span id="cookieconsent:desc" class="cc-message">{{message}} <a aria-label="learn more about cookies" role=button tabindex="0" class="cc-link" href="{{href}}" rel="noopener noreferrer nofollow" target="_blank">{{link}}</a></span>',
|
|
226
|
+
dismiss: '<a aria-label="dismiss cookie message" role=button tabindex="0" class="cc-btn cc-dismiss">{{dismiss}}</a>',
|
|
227
|
+
allow: '<a aria-label="allow cookies" role=button tabindex="0" class="cc-btn cc-allow">{{allow}}</a>',
|
|
228
|
+
deny: '<a aria-label="deny cookies" role=button tabindex="0" class="cc-btn cc-deny">{{deny}}</a>',
|
|
229
|
+
link: '<a aria-label="learn more about cookies" role=button tabindex="0" class="cc-link" href="{{href}}" target="_blank">{{link}}</a>',
|
|
230
|
+
close: '<span aria-label="dismiss cookie message" role=button tabindex="0" class="cc-close">{{close}}</span>',
|
|
231
|
+
|
|
232
|
+
//compliance: compliance is also an element, but it is generated by the application, depending on `type` below
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
// The placeholders {{classes}} and {{children}} both get replaced during initialisation:
|
|
236
|
+
// - {{classes}} is where additional classes get added
|
|
237
|
+
// - {{children}} is where the HTML children are placed
|
|
238
|
+
window: '<div role="dialog" aria-live="polite" aria-label="cookieconsent" aria-describedby="cookieconsent:desc" class="cc-window {{classes}}"><!--googleoff: all-->{{children}}<!--googleon: all--></div>',
|
|
239
|
+
|
|
240
|
+
// This is the html for the revoke button. This only shows up after the user has selected their level of consent
|
|
241
|
+
// It can be enabled of disabled using the `revokable` option
|
|
242
|
+
revokeBtn: '<div class="cc-revoke {{classes}}">Cookie Policy</div>',
|
|
243
|
+
|
|
244
|
+
// define types of 'compliance' here. '{{value}}' strings in here are linked to `elements`
|
|
245
|
+
compliance: {
|
|
246
|
+
'info': '<div class="cc-compliance">{{dismiss}}</div>',
|
|
247
|
+
'opt-in': '<div class="cc-compliance cc-highlight">{{dismiss}}{{allow}}</div>',
|
|
248
|
+
'opt-out': '<div class="cc-compliance cc-highlight">{{deny}}{{dismiss}}</div>',
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
// select your type of popup here
|
|
252
|
+
type: 'info', // refers to `compliance` (in other words, the buttons that are displayed)
|
|
253
|
+
|
|
254
|
+
// define layout layouts here
|
|
255
|
+
layouts: {
|
|
256
|
+
// the 'block' layout tend to be for square floating popups
|
|
257
|
+
'basic': '{{messagelink}}{{compliance}}',
|
|
258
|
+
'basic-close': '{{messagelink}}{{compliance}}{{close}}',
|
|
259
|
+
'basic-header': '{{header}}{{message}}{{link}}{{compliance}}',
|
|
260
|
+
|
|
261
|
+
// add a custom layout here, then add some new css with the class '.cc-layout-my-cool-layout'
|
|
262
|
+
//'my-cool-layout': '<div class="my-special-layout">{{message}}{{compliance}}</div>{{close}}',
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// default layout (see above)
|
|
266
|
+
layout: 'basic',
|
|
267
|
+
|
|
268
|
+
// this refers to the popup windows position. we currently support:
|
|
269
|
+
// - banner positions: top, bottom
|
|
270
|
+
// - floating positions: top-left, top-right, bottom-left, bottom-right
|
|
271
|
+
//
|
|
272
|
+
// adds a class `cc-floating` or `cc-banner` which helps when styling
|
|
273
|
+
position: 'bottom', // default position is 'bottom'
|
|
274
|
+
|
|
275
|
+
// Available styles
|
|
276
|
+
// -block (default, no extra classes)
|
|
277
|
+
// -edgeless
|
|
278
|
+
// -classic
|
|
279
|
+
// use your own style name and use `.cc-theme-STYLENAME` class in CSS to edit.
|
|
280
|
+
// Note: style "wire" is used for the configurator, but has no CSS styles of its own, only palette is used.
|
|
281
|
+
theme: 'block',
|
|
282
|
+
|
|
283
|
+
// The popup is `fixed` by default, but if you want it to be static (inline with the page content), set this to false
|
|
284
|
+
// Note: by default, we animate the height of the popup from 0 to full size
|
|
285
|
+
static: false,
|
|
286
|
+
|
|
287
|
+
// if you want custom colours, pass them in here. this object should look like this.
|
|
288
|
+
// ideally, any custom colours/themes should be created in a separate style sheet, as this is more efficient.
|
|
289
|
+
// {
|
|
290
|
+
// popup: {background: '#000000', text: '#fff', link: '#fff'},
|
|
291
|
+
// button: {background: 'transparent', border: '#f8e71c', text: '#f8e71c'},
|
|
292
|
+
// highlight: {background: '#f8e71c', border: '#f8e71c', text: '#000000'},
|
|
293
|
+
// }
|
|
294
|
+
// `highlight` is optional and extends `button`. if it exists, it will apply to the first button
|
|
295
|
+
// only background needs to be defined for every element. if not set, other colors can be calculated from it
|
|
296
|
+
palette: null,
|
|
297
|
+
|
|
298
|
+
// Some countries REQUIRE that a user can change their mind. You can configure this yourself.
|
|
299
|
+
// Most of the time this should be false, but the `cookieconsent.law` can change this to `true` if it detects that it should
|
|
300
|
+
revokable: false,
|
|
301
|
+
|
|
302
|
+
// if true, the revokable button will tranlate in and out
|
|
303
|
+
animateRevokable: true,
|
|
304
|
+
|
|
305
|
+
// used to disable link on existing layouts
|
|
306
|
+
// replaces element messagelink with message and removes content of link
|
|
307
|
+
showLink: true,
|
|
308
|
+
|
|
309
|
+
// set value as scroll range to enable
|
|
310
|
+
dismissOnScroll: false,
|
|
311
|
+
|
|
312
|
+
// set value as time in milliseconds to autodismiss after set time
|
|
313
|
+
dismissOnTimeout: false,
|
|
314
|
+
|
|
315
|
+
// The application automatically decide whether the popup should open.
|
|
316
|
+
// Set this to false to prevent this from happening and to allow you to control the behaviour yourself
|
|
317
|
+
autoOpen: true,
|
|
318
|
+
|
|
319
|
+
// By default the created HTML is automatically appended to the container (which defaults to <body>). You can prevent this behaviour
|
|
320
|
+
// by setting this to false, but if you do, you must attach the `element` yourself, which is a public property of the popup instance:
|
|
321
|
+
//
|
|
322
|
+
// var instance = cookieconsent.factory(options);
|
|
323
|
+
// document.body.appendChild(instance.element);
|
|
324
|
+
//
|
|
325
|
+
autoAttach: true,
|
|
326
|
+
|
|
327
|
+
// simple whitelist/blacklist for pages. specify page by:
|
|
328
|
+
// - using a string : '/index.html' (matches '/index.html' exactly) OR
|
|
329
|
+
// - using RegExp : /\/page_[\d]+\.html/ (matched '/page_1.html' and '/page_2.html' etc)
|
|
330
|
+
whitelistPage: [],
|
|
331
|
+
blacklistPage: [],
|
|
332
|
+
|
|
333
|
+
// If this is defined, then it is used as the inner html instead of layout. This allows for ultimate customisation.
|
|
334
|
+
// Be sure to use the classes `cc-btn` and `cc-allow`, `cc-deny` or `cc-dismiss`. They enable the app to register click
|
|
335
|
+
// handlers. You can use other pre-existing classes too. See `src/styles` folder.
|
|
336
|
+
overrideHTML: null,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
function CookiePopup() {
|
|
340
|
+
this.initialise.apply(this, arguments);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
CookiePopup.prototype.initialise = function(options) {
|
|
344
|
+
if (this.options) {
|
|
345
|
+
this.destroy(); // already rendered
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// set options back to default options
|
|
349
|
+
util.deepExtend(this.options = {}, defaultOptions);
|
|
350
|
+
|
|
351
|
+
// merge in user options
|
|
352
|
+
if (util.isPlainObject(options)) {
|
|
353
|
+
util.deepExtend(this.options, options);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// returns true if `onComplete` was called
|
|
357
|
+
if (checkCallbackHooks.call(this)) {
|
|
358
|
+
// user has already answered
|
|
359
|
+
this.options.enabled = false;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// apply blacklist / whitelist
|
|
363
|
+
if (arrayContainsMatches(this.options.blacklistPage, location.pathname)) {
|
|
364
|
+
this.options.enabled = false;
|
|
365
|
+
}
|
|
366
|
+
if (arrayContainsMatches(this.options.whitelistPage, location.pathname)) {
|
|
367
|
+
this.options.enabled = true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// the full markup either contains the wrapper or it does not (for multiple instances)
|
|
371
|
+
var cookiePopup = this.options.window
|
|
372
|
+
.replace('{{classes}}', getPopupClasses.call(this).join(' '))
|
|
373
|
+
.replace('{{children}}', getPopupInnerMarkup.call(this));
|
|
374
|
+
|
|
375
|
+
// if user passes html, use it instead
|
|
376
|
+
var customHTML = this.options.overrideHTML;
|
|
377
|
+
if (typeof customHTML == 'string' && customHTML.length) {
|
|
378
|
+
cookiePopup = customHTML;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// if static, we need to grow the element from 0 height so it doesn't jump the page
|
|
382
|
+
// content. we wrap an element around it which will mask the hidden content
|
|
383
|
+
if (this.options.static) {
|
|
384
|
+
// `grower` is a wrapper div with a hidden overflow whose height is animated
|
|
385
|
+
var wrapper = appendMarkup.call(this, '<div class="cc-grower">' + cookiePopup + '</div>');
|
|
386
|
+
|
|
387
|
+
wrapper.style.display = ''; // set it to visible (because appendMarkup hides it)
|
|
388
|
+
this.element = wrapper.firstChild; // get the `element` reference from the wrapper
|
|
389
|
+
this.element.style.display = 'none';
|
|
390
|
+
util.addClass(this.element, 'cc-invisible');
|
|
391
|
+
} else {
|
|
392
|
+
this.element = appendMarkup.call(this, cookiePopup);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
applyAutoDismiss.call(this);
|
|
396
|
+
|
|
397
|
+
applyRevokeButton.call(this);
|
|
398
|
+
|
|
399
|
+
if (this.options.autoOpen) {
|
|
400
|
+
this.autoOpen();
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
CookiePopup.prototype.destroy = function() {
|
|
405
|
+
if (this.onButtonClick && this.element) {
|
|
406
|
+
this.element.removeEventListener('click', this.onButtonClick);
|
|
407
|
+
this.onButtonClick = null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (this.dismissTimeout) {
|
|
411
|
+
clearTimeout(this.dismissTimeout);
|
|
412
|
+
this.dismissTimeout = null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (this.onWindowScroll) {
|
|
416
|
+
window.removeEventListener('scroll', this.onWindowScroll);
|
|
417
|
+
this.onWindowScroll = null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (this.onMouseMove) {
|
|
421
|
+
window.removeEventListener('mousemove', this.onMouseMove);
|
|
422
|
+
this.onMouseMove = null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (this.element && this.element.parentNode) {
|
|
426
|
+
this.element.parentNode.removeChild(this.element);
|
|
427
|
+
}
|
|
428
|
+
this.element = null;
|
|
429
|
+
|
|
430
|
+
if (this.revokeBtn && this.revokeBtn.parentNode) {
|
|
431
|
+
this.revokeBtn.parentNode.removeChild(this.revokeBtn);
|
|
432
|
+
}
|
|
433
|
+
this.revokeBtn = null;
|
|
434
|
+
|
|
435
|
+
removeCustomStyle(this.options.palette);
|
|
436
|
+
this.options = null;
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
CookiePopup.prototype.open = function(callback) {
|
|
440
|
+
if (!this.element) return;
|
|
441
|
+
|
|
442
|
+
if (!this.isOpen()) {
|
|
443
|
+
if (cc.hasTransition) {
|
|
444
|
+
this.fadeIn();
|
|
445
|
+
} else {
|
|
446
|
+
this.element.style.display = '';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (this.options.revokable) {
|
|
450
|
+
this.toggleRevokeButton();
|
|
451
|
+
}
|
|
452
|
+
this.options.onPopupOpen.call(this);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return this;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
CookiePopup.prototype.close = function(showRevoke) {
|
|
459
|
+
if (!this.element) return;
|
|
460
|
+
|
|
461
|
+
if (this.isOpen()) {
|
|
462
|
+
if (cc.hasTransition) {
|
|
463
|
+
this.fadeOut();
|
|
464
|
+
} else {
|
|
465
|
+
this.element.style.display = 'none';
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (showRevoke && this.options.revokable) {
|
|
469
|
+
this.toggleRevokeButton(true);
|
|
470
|
+
}
|
|
471
|
+
this.options.onPopupClose.call(this);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return this;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
CookiePopup.prototype.fadeIn = function() {
|
|
478
|
+
var el = this.element;
|
|
479
|
+
|
|
480
|
+
if (!cc.hasTransition || !el)
|
|
481
|
+
return;
|
|
482
|
+
|
|
483
|
+
// This should always be called AFTER fadeOut (which is governed by the 'transitionend' event).
|
|
484
|
+
// 'transitionend' isn't all that reliable, so, if we try and fadeIn before 'transitionend' has
|
|
485
|
+
// has a chance to run, then we run it ourselves
|
|
486
|
+
if (this.afterTransition) {
|
|
487
|
+
afterFadeOut.call(this, el)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (util.hasClass(el, 'cc-invisible')) {
|
|
491
|
+
el.style.display = '';
|
|
492
|
+
|
|
493
|
+
if (this.options.static) {
|
|
494
|
+
var height = this.element.clientHeight;
|
|
495
|
+
this.element.parentNode.style.maxHeight = height + 'px';
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
var fadeInTimeout = 20; // (ms) DO NOT MAKE THIS VALUE SMALLER. See below
|
|
499
|
+
|
|
500
|
+
// Although most browsers can handle values less than 20ms, it should remain above this value.
|
|
501
|
+
// This is because we are waiting for a "browser redraw" before we remove the 'cc-invisible' class.
|
|
502
|
+
// If the class is remvoed before a redraw could happen, then the fadeIn effect WILL NOT work, and
|
|
503
|
+
// the popup will appear from nothing. Therefore we MUST allow enough time for the browser to do
|
|
504
|
+
// its thing. The actually difference between using 0 and 20 in a set timeout is neglegible anyway
|
|
505
|
+
this.openingTimeout = setTimeout(afterFadeIn.bind(this, el), fadeInTimeout);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
CookiePopup.prototype.fadeOut = function() {
|
|
510
|
+
var el = this.element;
|
|
511
|
+
|
|
512
|
+
if (!cc.hasTransition || !el)
|
|
513
|
+
return;
|
|
514
|
+
|
|
515
|
+
if (this.openingTimeout) {
|
|
516
|
+
clearTimeout(this.openingTimeout);
|
|
517
|
+
afterFadeIn.bind(this, el);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (!util.hasClass(el, 'cc-invisible')) {
|
|
521
|
+
if (this.options.static) {
|
|
522
|
+
this.element.parentNode.style.maxHeight = '';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this.afterTransition = afterFadeOut.bind(this, el);
|
|
526
|
+
el.addEventListener(cc.transitionEnd, this.afterTransition);
|
|
527
|
+
|
|
528
|
+
util.addClass(el, 'cc-invisible');
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
CookiePopup.prototype.isOpen = function() {
|
|
533
|
+
return this.element && this.element.style.display == '' && (cc.hasTransition ? !util.hasClass(this.element, 'cc-invisible') : true);
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
CookiePopup.prototype.toggleRevokeButton = function(show) {
|
|
537
|
+
if (this.revokeBtn) this.revokeBtn.style.display = show ? '' : 'none';
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
CookiePopup.prototype.revokeChoice = function(preventOpen) {
|
|
541
|
+
this.options.enabled = true;
|
|
542
|
+
this.clearStatus();
|
|
543
|
+
|
|
544
|
+
this.options.onRevokeChoice.call(this);
|
|
545
|
+
|
|
546
|
+
if (!preventOpen) {
|
|
547
|
+
this.autoOpen();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// returns true if the cookie has a valid value
|
|
552
|
+
CookiePopup.prototype.hasAnswered = function(options) {
|
|
553
|
+
return Object.keys(cc.status).indexOf(this.getStatus()) >= 0;
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// returns true if the cookie indicates that consent has been given
|
|
557
|
+
CookiePopup.prototype.hasConsented = function(options) {
|
|
558
|
+
var val = this.getStatus();
|
|
559
|
+
return val == cc.status.allow || val == cc.status.dismiss;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// opens the popup if no answer has been given
|
|
563
|
+
CookiePopup.prototype.autoOpen = function(options) {
|
|
564
|
+
!this.hasAnswered() && this.options.enabled && this.open();
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
CookiePopup.prototype.setStatus = function(status) {
|
|
568
|
+
var c = this.options.cookie;
|
|
569
|
+
var value = util.getCookie(c.name);
|
|
570
|
+
var chosenBefore = Object.keys(cc.status).indexOf(value) >= 0;
|
|
571
|
+
|
|
572
|
+
// if `status` is valid
|
|
573
|
+
if (Object.keys(cc.status).indexOf(status) >= 0) {
|
|
574
|
+
util.setCookie(c.name, status, c.expiryDays, c.domain, c.path);
|
|
575
|
+
|
|
576
|
+
this.options.onStatusChange.call(this, status, chosenBefore);
|
|
577
|
+
} else {
|
|
578
|
+
this.clearStatus();
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
CookiePopup.prototype.getStatus = function() {
|
|
583
|
+
return util.getCookie(this.options.cookie.name);
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
CookiePopup.prototype.clearStatus = function() {
|
|
587
|
+
var c = this.options.cookie;
|
|
588
|
+
util.setCookie(c.name, '', -1, c.domain, c.path);
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// This needs to be called after 'fadeIn'. This is the code that actually causes the fadeIn to work
|
|
592
|
+
// There is a good reason why it's called in a timeout. Read 'fadeIn';
|
|
593
|
+
function afterFadeIn(el) {
|
|
594
|
+
this.openingTimeout = null;
|
|
595
|
+
util.removeClass(el, 'cc-invisible');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// This is called on 'transitionend' (only on the transition of the fadeOut). That's because after we've faded out, we need to
|
|
599
|
+
// set the display to 'none' (so there aren't annoying invisible popups all over the page). If for whenever reason this function
|
|
600
|
+
// is not called (lack of support), the open/close mechanism will still work.
|
|
601
|
+
function afterFadeOut(el) {
|
|
602
|
+
el.style.display = 'none'; // after close and before open, the display should be none
|
|
603
|
+
el.removeEventListener(cc.transitionEnd, this.afterTransition);
|
|
604
|
+
this.afterTransition = null;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// this function calls the `onComplete` hook and returns true (if needed) and returns false otherwise
|
|
608
|
+
function checkCallbackHooks() {
|
|
609
|
+
var complete = this.options.onInitialise.bind(this);
|
|
610
|
+
|
|
611
|
+
if (!window.navigator.cookieEnabled) {
|
|
612
|
+
complete(cc.status.deny);
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (window.CookiesOK || window.navigator.CookiesOK) {
|
|
617
|
+
complete(cc.status.allow);
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
var allowed = Object.keys(cc.status);
|
|
622
|
+
var answer = this.getStatus();
|
|
623
|
+
var match = allowed.indexOf(answer) >= 0;
|
|
624
|
+
|
|
625
|
+
if (match) {
|
|
626
|
+
complete(answer);
|
|
627
|
+
}
|
|
628
|
+
return match;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function getPositionClasses() {
|
|
632
|
+
var positions = this.options.position.split('-'); // top, bottom, left, right
|
|
633
|
+
var classes = [];
|
|
634
|
+
|
|
635
|
+
// top, left, right, bottom
|
|
636
|
+
positions.forEach(function(cur) {
|
|
637
|
+
classes.push('cc-' + cur);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
return classes;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function getPopupClasses() {
|
|
644
|
+
var opts = this.options;
|
|
645
|
+
var positionStyle = (opts.position == 'top' || opts.position == 'bottom') ? 'banner' : 'floating';
|
|
646
|
+
|
|
647
|
+
if (util.isMobile()) {
|
|
648
|
+
positionStyle = 'floating';
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
var classes = [
|
|
652
|
+
'cc-' + positionStyle, // floating or banner
|
|
653
|
+
'cc-type-' + opts.type, // add the compliance type
|
|
654
|
+
'cc-theme-' + opts.theme, // add the theme
|
|
655
|
+
];
|
|
656
|
+
|
|
657
|
+
if (opts.static) {
|
|
658
|
+
classes.push('cc-static');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
classes.push.apply(classes, getPositionClasses.call(this));
|
|
662
|
+
|
|
663
|
+
// we only add extra styles if `palette` has been set to a valid value
|
|
664
|
+
var didAttach = attachCustomPalette.call(this, this.options.palette);
|
|
665
|
+
|
|
666
|
+
// if we override the palette, add the class that enables this
|
|
667
|
+
if (this.customStyleSelector) {
|
|
668
|
+
classes.push(this.customStyleSelector);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return classes;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function getPopupInnerMarkup() {
|
|
675
|
+
var interpolated = {};
|
|
676
|
+
var opts = this.options;
|
|
677
|
+
|
|
678
|
+
// removes link if showLink is false
|
|
679
|
+
if (!opts.showLink) {
|
|
680
|
+
opts.elements.link = '';
|
|
681
|
+
opts.elements.messagelink = opts.elements.message;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
Object.keys(opts.elements).forEach(function(prop) {
|
|
685
|
+
interpolated[prop] = util.interpolateString(opts.elements[prop], function(name) {
|
|
686
|
+
var str = opts.content[name];
|
|
687
|
+
return (name && typeof str == 'string' && str.length) ? str : '';
|
|
688
|
+
})
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// checks if the type is valid and defaults to info if it's not
|
|
692
|
+
var complianceType = opts.compliance[opts.type];
|
|
693
|
+
if (!complianceType) {
|
|
694
|
+
complianceType = opts.compliance.info;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// build the compliance types from the already interpolated `elements`
|
|
698
|
+
interpolated.compliance = util.interpolateString(complianceType, function(name) {
|
|
699
|
+
return interpolated[name];
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// checks if the layout is valid and defaults to basic if it's not
|
|
703
|
+
var layout = opts.layouts[opts.layout];
|
|
704
|
+
if (!layout) {
|
|
705
|
+
layout = opts.layouts.basic;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return util.interpolateString(layout, function(match) {
|
|
709
|
+
return interpolated[match];
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function appendMarkup(markup) {
|
|
714
|
+
var opts = this.options;
|
|
715
|
+
var div = document.createElement('div');
|
|
716
|
+
var cont = (opts.container && opts.container.nodeType === 1) ? opts.container : document.body;
|
|
717
|
+
|
|
718
|
+
div.innerHTML = markup;
|
|
719
|
+
|
|
720
|
+
var el = div.children[0];
|
|
721
|
+
|
|
722
|
+
el.style.display = 'none';
|
|
723
|
+
|
|
724
|
+
if (util.hasClass(el, 'cc-window') && cc.hasTransition) {
|
|
725
|
+
util.addClass(el, 'cc-invisible');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// save ref to the function handle so we can unbind it later
|
|
729
|
+
this.onButtonClick = handleButtonClick.bind(this);
|
|
730
|
+
|
|
731
|
+
el.addEventListener('click', this.onButtonClick);
|
|
732
|
+
|
|
733
|
+
if (opts.autoAttach) {
|
|
734
|
+
if (!cont.firstChild) {
|
|
735
|
+
cont.appendChild(el);
|
|
736
|
+
} else {
|
|
737
|
+
cont.insertBefore(el, cont.firstChild)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return el;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function handleButtonClick(event) {
|
|
745
|
+
var targ = event.target;
|
|
746
|
+
if (util.hasClass(targ, 'cc-btn')) {
|
|
747
|
+
|
|
748
|
+
var matches = targ.className.match(new RegExp("\\bcc-(" + __allowedStatuses.join('|') + ")\\b"));
|
|
749
|
+
var match = (matches && matches[1]) || false;
|
|
750
|
+
|
|
751
|
+
if (match) {
|
|
752
|
+
this.setStatus(match);
|
|
753
|
+
this.close(true);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (util.hasClass(targ, 'cc-close')) {
|
|
757
|
+
this.setStatus(cc.status.dismiss);
|
|
758
|
+
this.close(true);
|
|
759
|
+
}
|
|
760
|
+
if (util.hasClass(targ, 'cc-revoke')) {
|
|
761
|
+
this.revokeChoice();
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// I might change this function to use inline styles. I originally chose a stylesheet because I could select many elements with a
|
|
766
|
+
// single rule (something that happened a lot), the apps has changed slightly now though, so inline styles might be more applicable.
|
|
767
|
+
function attachCustomPalette(palette) {
|
|
768
|
+
var hash = util.hash(JSON.stringify(palette));
|
|
769
|
+
var selector = 'cc-color-override-' + hash;
|
|
770
|
+
var isValid = util.isPlainObject(palette);
|
|
771
|
+
|
|
772
|
+
this.customStyleSelector = isValid ? selector : null;
|
|
773
|
+
|
|
774
|
+
if (isValid) {
|
|
775
|
+
addCustomStyle(hash, palette, '.' + selector);
|
|
776
|
+
}
|
|
777
|
+
return isValid;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function addCustomStyle(hash, palette, prefix) {
|
|
781
|
+
|
|
782
|
+
// only add this if a style like it doesn't exist
|
|
783
|
+
if (cc.customStyles[hash]) {
|
|
784
|
+
// custom style already exists, so increment the reference count
|
|
785
|
+
++cc.customStyles[hash].references;
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
var colorStyles = {};
|
|
790
|
+
var popup = palette.popup;
|
|
791
|
+
var button = palette.button;
|
|
792
|
+
var highlight = palette.highlight;
|
|
793
|
+
|
|
794
|
+
// needs background colour, text and link will be set to black/white if not specified
|
|
795
|
+
if (popup) {
|
|
796
|
+
// assumes popup.background is set
|
|
797
|
+
popup.text = popup.text ? popup.text : util.getContrast(popup.background);
|
|
798
|
+
popup.link = popup.link ? popup.link : popup.text;
|
|
799
|
+
colorStyles[prefix + '.cc-window'] = [
|
|
800
|
+
'color: ' + popup.text,
|
|
801
|
+
'background-color: ' + popup.background
|
|
802
|
+
];
|
|
803
|
+
colorStyles[prefix + '.cc-revoke'] = [
|
|
804
|
+
'color: ' + popup.text,
|
|
805
|
+
'background-color: ' + popup.background
|
|
806
|
+
];
|
|
807
|
+
colorStyles[prefix + ' .cc-link,' + prefix + ' .cc-link:active,' + prefix + ' .cc-link:visited'] = [
|
|
808
|
+
'color: ' + popup.link
|
|
809
|
+
];
|
|
810
|
+
|
|
811
|
+
if (button) {
|
|
812
|
+
// assumes button.background is set
|
|
813
|
+
button.text = button.text ? button.text : util.getContrast(button.background);
|
|
814
|
+
button.border = button.border ? button.border : 'transparent';
|
|
815
|
+
colorStyles[prefix + ' .cc-btn'] = [
|
|
816
|
+
'color: ' + button.text,
|
|
817
|
+
'border-color: ' + button.border,
|
|
818
|
+
'background-color: ' + button.background
|
|
819
|
+
];
|
|
820
|
+
|
|
821
|
+
if(button.background != 'transparent')
|
|
822
|
+
colorStyles[prefix + ' .cc-btn:hover, ' + prefix + ' .cc-btn:focus'] = [
|
|
823
|
+
'background-color: ' + getHoverColour(button.background)
|
|
824
|
+
];
|
|
825
|
+
|
|
826
|
+
if (highlight) {
|
|
827
|
+
//assumes highlight.background is set
|
|
828
|
+
highlight.text = highlight.text ? highlight.text : util.getContrast(highlight.background);
|
|
829
|
+
highlight.border = highlight.border ? highlight.border : 'transparent';
|
|
830
|
+
colorStyles[prefix + ' .cc-highlight .cc-btn:first-child'] = [
|
|
831
|
+
'color: ' + highlight.text,
|
|
832
|
+
'border-color: ' + highlight.border,
|
|
833
|
+
'background-color: ' + highlight.background
|
|
834
|
+
];
|
|
835
|
+
} else {
|
|
836
|
+
// sets highlight text color to popup text. background and border are transparent by default.
|
|
837
|
+
colorStyles[prefix + ' .cc-highlight .cc-btn:first-child'] = [
|
|
838
|
+
'color: ' + popup.text
|
|
839
|
+
];
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// this will be interpretted as CSS. the key is the selector, and each array element is a rule
|
|
846
|
+
var style = document.createElement('style');
|
|
847
|
+
document.head.appendChild(style);
|
|
848
|
+
|
|
849
|
+
// custom style doesn't exist, so we create it
|
|
850
|
+
cc.customStyles[hash] = {
|
|
851
|
+
references: 1,
|
|
852
|
+
element: style.sheet
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
var ruleIndex = -1;
|
|
856
|
+
for (var prop in colorStyles) {
|
|
857
|
+
if (colorStyles.hasOwnProperty(prop)) {
|
|
858
|
+
style.sheet.insertRule(prop + '{' + colorStyles[prop].join(';') + '}', ++ruleIndex);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function getHoverColour(hex) {
|
|
864
|
+
hex = util.normaliseHex(hex);
|
|
865
|
+
// for black buttons
|
|
866
|
+
if (hex == '000000') {
|
|
867
|
+
return '#222';
|
|
868
|
+
}
|
|
869
|
+
return util.getLuminance(hex);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function removeCustomStyle(palette) {
|
|
873
|
+
if (util.isPlainObject(palette)) {
|
|
874
|
+
var hash = util.hash(JSON.stringify(palette));
|
|
875
|
+
var customStyle = cc.customStyles[hash];
|
|
876
|
+
if (customStyle && !--customStyle.references) {
|
|
877
|
+
var styleNode = customStyle.element.ownerNode;
|
|
878
|
+
if (styleNode && styleNode.parentNode) {
|
|
879
|
+
styleNode.parentNode.removeChild(styleNode);
|
|
880
|
+
}
|
|
881
|
+
cc.customStyles[hash] = null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function arrayContainsMatches(array, search) {
|
|
887
|
+
for (var i = 0, l = array.length; i < l; ++i) {
|
|
888
|
+
var str = array[i];
|
|
889
|
+
// if regex matches or string is equal, return true
|
|
890
|
+
if ((str instanceof RegExp && str.test(search)) ||
|
|
891
|
+
(typeof str == 'string' && str.length && str === search)) {
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function applyAutoDismiss() {
|
|
899
|
+
var setStatus = this.setStatus.bind(this);
|
|
900
|
+
|
|
901
|
+
var delay = this.options.dismissOnTimeout;
|
|
902
|
+
if (typeof delay == 'number' && delay >= 0) {
|
|
903
|
+
this.dismissTimeout = window.setTimeout(function() {
|
|
904
|
+
setStatus(cc.status.dismiss);
|
|
905
|
+
}, Math.floor(delay));
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
var scrollRange = this.options.dismissOnScroll;
|
|
909
|
+
if (typeof scrollRange == 'number' && scrollRange >= 0) {
|
|
910
|
+
var onWindowScroll = function(evt) {
|
|
911
|
+
if (window.pageYOffset > Math.floor(scrollRange)) {
|
|
912
|
+
setStatus(cc.status.dismiss);
|
|
913
|
+
|
|
914
|
+
window.removeEventListener('scroll', onWindowScroll);
|
|
915
|
+
this.onWindowScroll = null;
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
this.onWindowScroll = onWindowScroll;
|
|
920
|
+
window.addEventListener('scroll', onWindowScroll);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function applyRevokeButton() {
|
|
925
|
+
// revokable is true if advanced compliance is selected
|
|
926
|
+
if (this.options.type != 'info') this.options.revokable = true;
|
|
927
|
+
// animateRevokable false for mobile devices
|
|
928
|
+
if (util.isMobile()) this.options.animateRevokable = false;
|
|
929
|
+
|
|
930
|
+
if (this.options.revokable) {
|
|
931
|
+
var classes = getPositionClasses.call(this);
|
|
932
|
+
if (this.options.animateRevokable) {
|
|
933
|
+
classes.push('cc-animate');
|
|
934
|
+
}
|
|
935
|
+
if (this.customStyleSelector) {
|
|
936
|
+
classes.push(this.customStyleSelector)
|
|
937
|
+
}
|
|
938
|
+
var revokeBtn = this.options.revokeBtn.replace('{{classes}}', classes.join(' '));
|
|
939
|
+
this.revokeBtn = appendMarkup.call(this, revokeBtn);
|
|
940
|
+
|
|
941
|
+
var btn = this.revokeBtn;
|
|
942
|
+
if (this.options.animateRevokable) {
|
|
943
|
+
var wait = false;
|
|
944
|
+
var onMouseMove = util.throttle(function(evt) {
|
|
945
|
+
var active = false;
|
|
946
|
+
var minY = 20;
|
|
947
|
+
var maxY = (window.innerHeight - 20);
|
|
948
|
+
|
|
949
|
+
if (util.hasClass(btn, 'cc-top') && evt.clientY < minY) active = true;
|
|
950
|
+
if (util.hasClass(btn, 'cc-bottom') && evt.clientY > maxY) active = true;
|
|
951
|
+
|
|
952
|
+
if (active) {
|
|
953
|
+
if (!util.hasClass(btn, 'cc-active')) {
|
|
954
|
+
util.addClass(btn, 'cc-active');
|
|
955
|
+
}
|
|
956
|
+
} else {
|
|
957
|
+
if (util.hasClass(btn, 'cc-active')) {
|
|
958
|
+
util.removeClass(btn, 'cc-active');
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}, 200);
|
|
962
|
+
|
|
963
|
+
this.onMouseMove = onMouseMove;
|
|
964
|
+
window.addEventListener('mousemove', onMouseMove);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return CookiePopup
|
|
970
|
+
}());
|
|
971
|
+
|
|
972
|
+
cc.Location = (function() {
|
|
973
|
+
|
|
974
|
+
// An object containing all the location services we have already set up.
|
|
975
|
+
// When using a service, it could either return a data structure in plain text (like a JSON object) or an executable script
|
|
976
|
+
// When the response needs to be executed by the browser, then `isScript` must be set to true, otherwise it won't work.
|
|
977
|
+
|
|
978
|
+
// When the service uses a script, the chances are that you'll have to use the script to make additional requests. In these
|
|
979
|
+
// cases, the services `callback` property is called with a `done` function. When performing async operations, this must be called
|
|
980
|
+
// with the data (or Error), and `cookieconsent.locate` will take care of the rest
|
|
981
|
+
var defaultOptions = {
|
|
982
|
+
|
|
983
|
+
// The default timeout is 5 seconds. This is mainly needed to catch JSONP requests that error.
|
|
984
|
+
// Otherwise there is no easy way to catch JSONP errors. That means that if a JSONP fails, the
|
|
985
|
+
// app will take `timeout` milliseconds to react to a JSONP network error.
|
|
986
|
+
timeout: 5000,
|
|
987
|
+
|
|
988
|
+
// the order that services will be attempted in
|
|
989
|
+
services: [
|
|
990
|
+
'freegeoip',
|
|
991
|
+
'ipinfo',
|
|
992
|
+
'maxmind'
|
|
993
|
+
|
|
994
|
+
/*
|
|
995
|
+
|
|
996
|
+
// 'ipinfodb' requires some options, so we define it using an object
|
|
997
|
+
// this object will be passed to the function that defines the service
|
|
998
|
+
|
|
999
|
+
{
|
|
1000
|
+
name: 'ipinfodb',
|
|
1001
|
+
interpolateUrl: {
|
|
1002
|
+
// obviously, this is a fake key
|
|
1003
|
+
api_key: 'vOgI3748dnIytIrsJcxS7qsDf6kbJkE9lN4yEDrXAqXcKUNvjjZPox3ekXqmMMld'
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
|
|
1007
|
+
// as well as defining an object, you can define a function that returns an object
|
|
1008
|
+
|
|
1009
|
+
function () {
|
|
1010
|
+
return {name: 'ipinfodb'};
|
|
1011
|
+
},
|
|
1012
|
+
|
|
1013
|
+
*/
|
|
1014
|
+
],
|
|
1015
|
+
|
|
1016
|
+
serviceDefinitions: {
|
|
1017
|
+
|
|
1018
|
+
freegeoip: function() {
|
|
1019
|
+
return {
|
|
1020
|
+
// This service responds with JSON, but they do not have CORS set, so we must use JSONP and provide a callback
|
|
1021
|
+
// The `{callback}` is automatically rewritten by the tool
|
|
1022
|
+
url: '//freegeoip.net/json/?callback={callback}',
|
|
1023
|
+
isScript: true, // this is JSONP, therefore we must set it to run as a script
|
|
1024
|
+
callback: function(done, response) {
|
|
1025
|
+
try{
|
|
1026
|
+
var json = JSON.parse(response);
|
|
1027
|
+
return json.error ? toError(json) : {
|
|
1028
|
+
code: json.country_code
|
|
1029
|
+
};
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
return toError({error: 'Invalid response ('+err+')'});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
|
|
1037
|
+
ipinfo: function() {
|
|
1038
|
+
return {
|
|
1039
|
+
// This service responds with JSON, so we simply need to parse it and return the country code
|
|
1040
|
+
url: '//ipinfo.io',
|
|
1041
|
+
headers: ['Accept: application/json'],
|
|
1042
|
+
callback: function(done, response) {
|
|
1043
|
+
try{
|
|
1044
|
+
var json = JSON.parse(response);
|
|
1045
|
+
return json.error ? toError(json) : {
|
|
1046
|
+
code: json.country
|
|
1047
|
+
};
|
|
1048
|
+
} catch (err) {
|
|
1049
|
+
return toError({error: 'Invalid response ('+err+')'});
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
|
|
1055
|
+
// This service requires an option to define `key`. Options are proived using objects or functions
|
|
1056
|
+
ipinfodb: function(options) {
|
|
1057
|
+
return {
|
|
1058
|
+
// This service responds with JSON, so we simply need to parse it and return the country code
|
|
1059
|
+
url: '//api.ipinfodb.com/v3/ip-country/?key={api_key}&format=json&callback={callback}',
|
|
1060
|
+
isScript: true, // this is JSONP, therefore we must set it to run as a script
|
|
1061
|
+
callback: function(done, response) {
|
|
1062
|
+
try{
|
|
1063
|
+
var json = JSON.parse(response);
|
|
1064
|
+
return json.statusCode == 'ERROR' ? toError({error: json.statusMessage}) : {
|
|
1065
|
+
code: json.countryCode
|
|
1066
|
+
};
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
return toError({error: 'Invalid response ('+err+')'});
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
|
|
1074
|
+
maxmind: function() {
|
|
1075
|
+
return {
|
|
1076
|
+
// This service responds with a JavaScript file which defines additional functionality. Once loaded, we must
|
|
1077
|
+
// make an additional AJAX call. Therefore we provide a `done` callback that can be called asynchronously
|
|
1078
|
+
url: '//js.maxmind.com/js/apis/geoip2/v2.1/geoip2.js',
|
|
1079
|
+
isScript: true, // this service responds with a JavaScript file, so it must be run as a script
|
|
1080
|
+
callback: function(done) {
|
|
1081
|
+
// if everything went okay then `geoip2` WILL be defined
|
|
1082
|
+
if (!window.geoip2) {
|
|
1083
|
+
done(new Error('Unexpected response format. The downloaded script should have exported `geoip2` to the global scope'));
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
geoip2.country(function(location) {
|
|
1088
|
+
try {
|
|
1089
|
+
done({
|
|
1090
|
+
code: location.country.iso_code
|
|
1091
|
+
});
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
done(toError(err));
|
|
1094
|
+
}
|
|
1095
|
+
}, function(err) {
|
|
1096
|
+
done(toError(err));
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
// We can't return anything, because we need to wait for the second AJAX call to return.
|
|
1100
|
+
// Then we can 'complete' the service by passing data or an error to the `done` callback.
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
function Location(options) {
|
|
1108
|
+
// Set up options
|
|
1109
|
+
util.deepExtend(this.options = {}, defaultOptions);
|
|
1110
|
+
|
|
1111
|
+
if (util.isPlainObject(options)) {
|
|
1112
|
+
util.deepExtend(this.options, options);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
this.currentServiceIndex = -1; // the index (in options) of the service we're currently using
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
Location.prototype.getNextService = function() {
|
|
1119
|
+
var service;
|
|
1120
|
+
|
|
1121
|
+
do {
|
|
1122
|
+
service = this.getServiceByIdx(++this.currentServiceIndex);
|
|
1123
|
+
} while (this.currentServiceIndex < this.options.services.length && !service);
|
|
1124
|
+
|
|
1125
|
+
return service;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
Location.prototype.getServiceByIdx = function(idx) {
|
|
1129
|
+
// This can either be the name of a default locationService, or a function.
|
|
1130
|
+
var serviceOption = this.options.services[idx];
|
|
1131
|
+
|
|
1132
|
+
// If it's a string, use one of the location services.
|
|
1133
|
+
if (typeof serviceOption === 'function') {
|
|
1134
|
+
var dynamicOpts = serviceOption();
|
|
1135
|
+
if (dynamicOpts.name) {
|
|
1136
|
+
util.deepExtend(dynamicOpts, this.options.serviceDefinitions[dynamicOpts.name](dynamicOpts));
|
|
1137
|
+
}
|
|
1138
|
+
return dynamicOpts;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// If it's a string, use one of the location services.
|
|
1142
|
+
if (typeof serviceOption === 'string') {
|
|
1143
|
+
return this.options.serviceDefinitions[serviceOption]();
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// If it's an object, assume {name: 'ipinfo', ...otherOptions}
|
|
1147
|
+
// Allows user to pass in API keys etc.
|
|
1148
|
+
if (util.isPlainObject(serviceOption)) {
|
|
1149
|
+
return this.options.serviceDefinitions[serviceOption.name](serviceOption);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
return null;
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
// This runs the service located at index `currentServiceIndex`.
|
|
1156
|
+
// If the service fails, `runNextServiceOnError` will continue trying each service until all fail, or one completes successfully
|
|
1157
|
+
Location.prototype.locate = function(complete, error) {
|
|
1158
|
+
var service = this.getNextService();
|
|
1159
|
+
|
|
1160
|
+
if (!service) {
|
|
1161
|
+
error(new Error('No services to run'));
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
this.callbackComplete = complete;
|
|
1166
|
+
this.callbackError = error;
|
|
1167
|
+
|
|
1168
|
+
this.runService(service, this.runNextServiceOnError.bind(this));
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
// Potentially adds a callback to a url for jsonp.
|
|
1172
|
+
Location.prototype.setupUrl = function(service) {
|
|
1173
|
+
var serviceOpts = this.getCurrentServiceOpts();
|
|
1174
|
+
return service.url.replace(/\{(.*?)\}/g, function(_, param) {
|
|
1175
|
+
if (param === 'callback') {
|
|
1176
|
+
var tempName = 'callback' + Date.now();
|
|
1177
|
+
window[tempName] = function(res) {
|
|
1178
|
+
service.__JSONP_DATA = JSON.stringify(res);
|
|
1179
|
+
}
|
|
1180
|
+
return tempName;
|
|
1181
|
+
}
|
|
1182
|
+
if (param in serviceOpts.interpolateUrl) {
|
|
1183
|
+
return serviceOpts.interpolateUrl[param];
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
// requires a `service` object that defines at least a `url` and `callback`
|
|
1189
|
+
Location.prototype.runService = function(service, complete) {
|
|
1190
|
+
var self = this;
|
|
1191
|
+
|
|
1192
|
+
// basic check to ensure it resembles a `service`
|
|
1193
|
+
if (!service || !service.url || !service.callback) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// we call either `getScript` or `makeAsyncRequest` depending on the type of resource
|
|
1198
|
+
var requestFunction = service.isScript ? getScript : makeAsyncRequest;
|
|
1199
|
+
|
|
1200
|
+
var url = this.setupUrl(service);
|
|
1201
|
+
|
|
1202
|
+
// both functions have similar signatures so we can pass the same arguments to both
|
|
1203
|
+
requestFunction(url, function(xhr) {
|
|
1204
|
+
// if `!xhr`, then `getScript` function was used, so there is no response text
|
|
1205
|
+
var responseText = xhr ? xhr.responseText : '';
|
|
1206
|
+
|
|
1207
|
+
// if the resource is a script, then this function is called after the script has been run.
|
|
1208
|
+
// if the script is JSONP, then a time defined function `callback_{Date.now}` has already
|
|
1209
|
+
// been called (as the JSONP callback). This callback sets the __JSONP_DATA property
|
|
1210
|
+
if (service.__JSONP_DATA) {
|
|
1211
|
+
responseText = service.__JSONP_DATA;
|
|
1212
|
+
delete service.__JSONP_DATA;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// call the service callback with the response text (so it can parse the response)
|
|
1216
|
+
self.runServiceCallback.call(self, complete, service, responseText);
|
|
1217
|
+
|
|
1218
|
+
}, this.options.timeout, service.data, service.headers);
|
|
1219
|
+
|
|
1220
|
+
// `service.data` and `service.headers` are optional (they only count if `!service.isScript` anyway)
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// The service request has run (and possibly has a `responseText`) [no `responseText` if `isScript`]
|
|
1224
|
+
// We need to run its callback which determines if its successful or not
|
|
1225
|
+
// `complete` is called on success or failure
|
|
1226
|
+
Location.prototype.runServiceCallback = function(complete, service, responseText) {
|
|
1227
|
+
var self = this;
|
|
1228
|
+
// this is the function that is called if the service uses the async callback in its handler method
|
|
1229
|
+
var serviceResultHandler = function (asyncResult) {
|
|
1230
|
+
// if `result` is a valid value, then this function shouldn't really run
|
|
1231
|
+
// even if it is called by `service.callback`
|
|
1232
|
+
if (!result) {
|
|
1233
|
+
self.onServiceResult.call(self, complete, asyncResult)
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
// the function `service.callback` will either extract a country code from `responseText` and return it (in `result`)
|
|
1238
|
+
// or (if it has to make additional requests) it will call a `done` callback with the country code when it is ready
|
|
1239
|
+
var result = service.callback(serviceResultHandler, responseText);
|
|
1240
|
+
|
|
1241
|
+
if (result) {
|
|
1242
|
+
this.onServiceResult.call(this, complete, result);
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
// This is called with the `result` from `service.callback` regardless of how it provided that result (sync or async).
|
|
1247
|
+
// `result` will be whatever is returned from `service.callback`. A service callback should provide an object with data
|
|
1248
|
+
Location.prototype.onServiceResult = function(complete, result) {
|
|
1249
|
+
// convert result to nodejs style async callback
|
|
1250
|
+
if (result instanceof Error || (result && result.error)) {
|
|
1251
|
+
complete.call(this, result, null);
|
|
1252
|
+
} else {
|
|
1253
|
+
complete.call(this, null, result);
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
// if `err` is set, the next service handler is called
|
|
1258
|
+
// if `err` is null, the `onComplete` handler is called with `data`
|
|
1259
|
+
Location.prototype.runNextServiceOnError = function(err, data) {
|
|
1260
|
+
if (err) {
|
|
1261
|
+
this.logError(err);
|
|
1262
|
+
|
|
1263
|
+
var nextService = this.getNextService();
|
|
1264
|
+
|
|
1265
|
+
if (nextService) {
|
|
1266
|
+
this.runService(nextService, this.runNextServiceOnError.bind(this));
|
|
1267
|
+
} else {
|
|
1268
|
+
this.completeService.call(this, this.callbackError, new Error('All services failed'));
|
|
1269
|
+
}
|
|
1270
|
+
} else {
|
|
1271
|
+
this.completeService.call(this, this.callbackComplete, data);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
Location.prototype.getCurrentServiceOpts = function() {
|
|
1276
|
+
var val = this.options.services[this.currentServiceIndex];
|
|
1277
|
+
|
|
1278
|
+
if (typeof val == 'string') {
|
|
1279
|
+
return {name: val};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if (typeof val == 'function') {
|
|
1283
|
+
return val();
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
if (util.isPlainObject(val)) {
|
|
1287
|
+
return val;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
return {};
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
// calls the `onComplete` callback after resetting the `currentServiceIndex`
|
|
1294
|
+
Location.prototype.completeService = function(fn, data) {
|
|
1295
|
+
this.currentServiceIndex = -1;
|
|
1296
|
+
|
|
1297
|
+
fn && fn(data);
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
Location.prototype.logError = function (err) {
|
|
1301
|
+
var idx = this.currentServiceIndex;
|
|
1302
|
+
var service = this.getServiceByIdx(idx);
|
|
1303
|
+
|
|
1304
|
+
console.error('The service[' + idx + '] (' + service.url + ') responded with the following error', err);
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
function getScript(url, callback, timeout) {
|
|
1308
|
+
var timeoutIdx, s = document.createElement('script');
|
|
1309
|
+
|
|
1310
|
+
s.type = 'text/' + (url.type || 'javascript');
|
|
1311
|
+
s.src = url.src || url;
|
|
1312
|
+
s.async = false;
|
|
1313
|
+
|
|
1314
|
+
s.onreadystatechange = s.onload = function() {
|
|
1315
|
+
// this code handles two scenarios, whether called by onload or onreadystatechange
|
|
1316
|
+
var state = s.readyState;
|
|
1317
|
+
|
|
1318
|
+
clearTimeout(timeoutIdx);
|
|
1319
|
+
|
|
1320
|
+
if (!callback.done && (!state || /loaded|complete/.test(state))) {
|
|
1321
|
+
callback.done = true;
|
|
1322
|
+
callback();
|
|
1323
|
+
s.onreadystatechange = s.onload = null;
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
document.body.appendChild(s);
|
|
1328
|
+
|
|
1329
|
+
// You can't catch JSONP Errors, because it's handled by the script tag
|
|
1330
|
+
// one way is to use a timeout
|
|
1331
|
+
timeoutIdx = setTimeout(function () {
|
|
1332
|
+
callback.done = true;
|
|
1333
|
+
callback();
|
|
1334
|
+
s.onreadystatechange = s.onload = null;
|
|
1335
|
+
}, timeout);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function makeAsyncRequest(url, onComplete, timeout, postData, requestHeaders) {
|
|
1339
|
+
var xhr = new(window.XMLHttpRequest || window.ActiveXObject)('MSXML2.XMLHTTP.3.0');
|
|
1340
|
+
|
|
1341
|
+
xhr.open(postData ? 'POST' : 'GET', url, 1);
|
|
1342
|
+
|
|
1343
|
+
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
1344
|
+
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
1345
|
+
|
|
1346
|
+
if (Array.isArray(requestHeaders)) {
|
|
1347
|
+
for (var i = 0, l = requestHeaders.length; i < l; ++i) {
|
|
1348
|
+
var split = requestHeaders[i].split(':', 2)
|
|
1349
|
+
xhr.setRequestHeader(split[0].replace(/^\s+|\s+$/g, ''), split[1].replace(/^\s+|\s+$/g, ''));
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (typeof onComplete == 'function') {
|
|
1354
|
+
xhr.onreadystatechange = function() {
|
|
1355
|
+
if (xhr.readyState > 3) {
|
|
1356
|
+
onComplete(xhr);
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
xhr.send(postData);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function toError(obj) {
|
|
1365
|
+
return new Error('Error [' + (obj.code || 'UNKNOWN') + ']: ' + obj.error);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
return Location;
|
|
1369
|
+
}());
|
|
1370
|
+
|
|
1371
|
+
cc.Law = (function() {
|
|
1372
|
+
|
|
1373
|
+
var defaultOptions = {
|
|
1374
|
+
// Make this false if you want to disable all regional overrides for settings.
|
|
1375
|
+
// If true, options can differ by country, depending on their cookie law.
|
|
1376
|
+
// It does not affect hiding the popup for countries that do not have cookie law.
|
|
1377
|
+
regionalLaw: true,
|
|
1378
|
+
|
|
1379
|
+
// countries that enforce some version of a cookie law
|
|
1380
|
+
hasLaw: ['AT', 'BE', 'BG', 'HR', 'CZ', 'CY', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'SK', 'SI', 'ES', 'SE', 'GB', 'UK'],
|
|
1381
|
+
|
|
1382
|
+
// countries that say that all cookie consent choices must be revokable (a user must be able too change their mind)
|
|
1383
|
+
revokable: ['HR', 'CY', 'DK', 'EE', 'FR', 'DE', 'LV', 'LT', 'NL', 'PT', 'ES'],
|
|
1384
|
+
|
|
1385
|
+
// countries that say that a person can only "consent" if the explicitly click on "I agree".
|
|
1386
|
+
// in these countries, consent cannot be implied via a timeout or by scrolling down the page
|
|
1387
|
+
explicitAction: ['HR', 'IT', 'ES'],
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
function Law(options) {
|
|
1391
|
+
this.initialise.apply(this, arguments);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
Law.prototype.initialise = function(options) {
|
|
1395
|
+
// set options back to default options
|
|
1396
|
+
util.deepExtend(this.options = {}, defaultOptions);
|
|
1397
|
+
|
|
1398
|
+
// merge in user options
|
|
1399
|
+
if (util.isPlainObject(options)) {
|
|
1400
|
+
util.deepExtend(this.options, options);
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
Law.prototype.get = function(countryCode) {
|
|
1405
|
+
var opts = this.options;
|
|
1406
|
+
return {
|
|
1407
|
+
hasLaw: opts.hasLaw.indexOf(countryCode) >= 0,
|
|
1408
|
+
revokable: opts.revokable.indexOf(countryCode) >= 0,
|
|
1409
|
+
explicitAction: opts.explicitAction.indexOf(countryCode) >= 0,
|
|
1410
|
+
};
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
Law.prototype.applyLaw = function(options, countryCode) {
|
|
1414
|
+
var country = this.get(countryCode);
|
|
1415
|
+
|
|
1416
|
+
if (!country.hasLaw) {
|
|
1417
|
+
// The country has no cookie law
|
|
1418
|
+
options.enabled = false;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (this.options.regionalLaw) {
|
|
1422
|
+
if (country.revokable) {
|
|
1423
|
+
// We must provide an option to revoke consent at a later time
|
|
1424
|
+
options.revokable = true;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (country.explicitAction) {
|
|
1428
|
+
// The user must explicitly click the consent button
|
|
1429
|
+
options.dismissOnScroll = false;
|
|
1430
|
+
options.dismissOnTimeout = false;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return options;
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
return Law;
|
|
1437
|
+
}());
|
|
1438
|
+
|
|
1439
|
+
// This function initialises the app by combining the use of the Popup, Locator and Law modules
|
|
1440
|
+
// You can string together these three modules yourself however you want, by writing a new function.
|
|
1441
|
+
cc.initialise = function(options, complete, error) {
|
|
1442
|
+
var law = new cc.Law(options.law);
|
|
1443
|
+
|
|
1444
|
+
if (!complete) complete = function() {};
|
|
1445
|
+
if (!error) error = function() {};
|
|
1446
|
+
|
|
1447
|
+
cc.getCountryCode(options, function(result) {
|
|
1448
|
+
// don't need the law or location options anymore
|
|
1449
|
+
delete options.law;
|
|
1450
|
+
delete options.location;
|
|
1451
|
+
|
|
1452
|
+
if (result.code) {
|
|
1453
|
+
options = law.applyLaw(options, result.code);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
complete(new cc.Popup(options));
|
|
1457
|
+
}, function(err) {
|
|
1458
|
+
// don't need the law or location options anymore
|
|
1459
|
+
delete options.law;
|
|
1460
|
+
delete options.location;
|
|
1461
|
+
|
|
1462
|
+
error(err, new cc.Popup(options));
|
|
1463
|
+
});
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
// This function tries to find your current location. It either grabs it from a hardcoded option in
|
|
1467
|
+
// `options.law.countryCode`, or attempts to make a location service request. This function accepts
|
|
1468
|
+
// options (which can configure the `law` and `location` modules) and fires a callback with which
|
|
1469
|
+
// passes an object `{code: countryCode}` as the first argument (which can have undefined properties)
|
|
1470
|
+
cc.getCountryCode = function(options, complete, error) {
|
|
1471
|
+
if (options.law && options.law.countryCode) {
|
|
1472
|
+
complete({
|
|
1473
|
+
code: options.law.countryCode
|
|
1474
|
+
});
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (options.location) {
|
|
1478
|
+
var locator = new cc.Location(options.location);
|
|
1479
|
+
locator.locate(function(serviceResult) {
|
|
1480
|
+
complete(serviceResult || {});
|
|
1481
|
+
}, error);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
complete({});
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
// export utils (no point in hiding them, so we may as well expose them)
|
|
1488
|
+
cc.utils = util;
|
|
1489
|
+
|
|
1490
|
+
// prevent this code from being run twice
|
|
1491
|
+
cc.hasInitialised = true;
|
|
1492
|
+
|
|
1493
|
+
window.cookieconsent = cc;
|
|
1494
|
+
|
|
1495
|
+
}(window.cookieconsent || {}));
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
window.addEventListener("load", function(){
|
|
1499
|
+
window.cookieconsent.initialise({
|
|
1500
|
+
"palette": {
|
|
1501
|
+
},
|
|
1502
|
+
"theme": "classic",
|
|
1503
|
+
"position": "bottom-right"
|
|
1504
|
+
})});
|