diff options
Diffstat (limited to 'assets/default/js/base.js')
-rw-r--r-- | assets/default/js/base.js | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/assets/default/js/base.js b/assets/default/js/base.js new file mode 100644 index 00000000..cf628e87 --- /dev/null +++ b/assets/default/js/base.js | |||
@@ -0,0 +1,664 @@ | |||
1 | /** @licstart The following is the entire license notice for the | ||
2 | * JavaScript code in this page. | ||
3 | * | ||
4 | * Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net> | ||
5 | * (c) 2011-2017 The Shaarli Community, see AUTHORS | ||
6 | * | ||
7 | * This software is provided 'as-is', without any express or implied warranty. | ||
8 | * In no event will the authors be held liable for any damages arising from | ||
9 | * the use of this software. | ||
10 | * | ||
11 | * Permission is granted to anyone to use this software for any purpose, | ||
12 | * including commercial applications, and to alter it and redistribute it | ||
13 | * freely, subject to the following restrictions: | ||
14 | * | ||
15 | * 1. The origin of this software must not be misrepresented; you must not | ||
16 | * claim that you wrote the original software. If you use this software | ||
17 | * in a product, an acknowledgment in the product documentation would | ||
18 | * be appreciated but is not required. | ||
19 | * | ||
20 | * 2. Altered source versions must be plainly marked as such, and must | ||
21 | * not be misrepresented as being the original software. | ||
22 | * | ||
23 | * 3. This notice may not be removed or altered from any source distribution. | ||
24 | * | ||
25 | * @licend The above is the entire license notice | ||
26 | * for the JavaScript code in this page. | ||
27 | */ | ||
28 | |||
29 | window.onload = function () { | ||
30 | |||
31 | /** | ||
32 | * Retrieve an element up in the tree from its class name. | ||
33 | */ | ||
34 | function getParentByClass(el, className) { | ||
35 | var p = el.parentNode; | ||
36 | if (p == null || p.classList.contains(className)) { | ||
37 | return p; | ||
38 | } | ||
39 | return getParentByClass(p, className); | ||
40 | } | ||
41 | |||
42 | |||
43 | /** | ||
44 | * Handle responsive menu. | ||
45 | * Source: http://purecss.io/layouts/tucked-menu-vertical/ | ||
46 | */ | ||
47 | (function (window, document) { | ||
48 | var menu = document.getElementById('shaarli-menu'), | ||
49 | WINDOW_CHANGE_EVENT = ('onorientationchange' in window) ? 'orientationchange':'resize'; | ||
50 | |||
51 | function toggleHorizontal() { | ||
52 | [].forEach.call( | ||
53 | document.getElementById('shaarli-menu').querySelectorAll('.menu-transform'), | ||
54 | function(el){ | ||
55 | el.classList.toggle('pure-menu-horizontal'); | ||
56 | } | ||
57 | ); | ||
58 | }; | ||
59 | |||
60 | function toggleMenu() { | ||
61 | // set timeout so that the panel has a chance to roll up | ||
62 | // before the menu switches states | ||
63 | if (menu.classList.contains('open')) { | ||
64 | setTimeout(toggleHorizontal, 500); | ||
65 | } | ||
66 | else { | ||
67 | toggleHorizontal(); | ||
68 | } | ||
69 | menu.classList.toggle('open'); | ||
70 | document.getElementById('menu-toggle').classList.toggle('x'); | ||
71 | }; | ||
72 | |||
73 | function closeMenu() { | ||
74 | if (menu.classList.contains('open')) { | ||
75 | toggleMenu(); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | var menuToggle = document.getElementById('menu-toggle'); | ||
80 | if (menuToggle != null) { | ||
81 | menuToggle.addEventListener('click', function (e) { | ||
82 | toggleMenu(); | ||
83 | }); | ||
84 | } | ||
85 | |||
86 | window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu); | ||
87 | })(this, this.document); | ||
88 | |||
89 | /** | ||
90 | * Fold/Expand shaares description and thumbnail. | ||
91 | */ | ||
92 | var foldAllButtons = document.getElementsByClassName('fold-all'); | ||
93 | var foldButtons = document.getElementsByClassName('fold-button'); | ||
94 | |||
95 | [].forEach.call(foldButtons, function (foldButton) { | ||
96 | // Retrieve description | ||
97 | var description = null; | ||
98 | var thumbnail = null; | ||
99 | var linklistItem = getParentByClass(foldButton, 'linklist-item'); | ||
100 | if (linklistItem != null) { | ||
101 | description = linklistItem.querySelector('.linklist-item-description'); | ||
102 | thumbnail = linklistItem.querySelector('.linklist-item-thumbnail'); | ||
103 | if (description != null || thumbnail != null) { | ||
104 | foldButton.style.display = 'inline'; | ||
105 | } | ||
106 | } | ||
107 | |||
108 | foldButton.addEventListener('click', function (event) { | ||
109 | event.preventDefault(); | ||
110 | toggleFold(event.target, description, thumbnail); | ||
111 | }); | ||
112 | }); | ||
113 | |||
114 | if (foldAllButtons != null) { | ||
115 | [].forEach.call(foldAllButtons, function (foldAllButton) { | ||
116 | foldAllButton.addEventListener('click', function (event) { | ||
117 | event.preventDefault(); | ||
118 | var state = foldAllButton.firstElementChild.getAttribute('class').indexOf('down') != -1 ? 'down' : 'up'; | ||
119 | [].forEach.call(foldButtons, function (foldButton) { | ||
120 | if (foldButton.firstElementChild.classList.contains('fa-chevron-up') && state == 'down' | ||
121 | || foldButton.firstElementChild.classList.contains('fa-chevron-down') && state == 'up' | ||
122 | ) { | ||
123 | return; | ||
124 | } | ||
125 | // Retrieve description | ||
126 | var description = null; | ||
127 | var thumbnail = null; | ||
128 | var linklistItem = getParentByClass(foldButton, 'linklist-item'); | ||
129 | if (linklistItem != null) { | ||
130 | description = linklistItem.querySelector('.linklist-item-description'); | ||
131 | thumbnail = linklistItem.querySelector('.linklist-item-thumbnail'); | ||
132 | if (description != null || thumbnail != null) { | ||
133 | foldButton.style.display = 'inline'; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | toggleFold(foldButton.firstElementChild, description, thumbnail); | ||
138 | }); | ||
139 | foldAllButton.firstElementChild.classList.toggle('fa-chevron-down'); | ||
140 | foldAllButton.firstElementChild.classList.toggle('fa-chevron-up'); | ||
141 | foldAllButton.title = state === 'down' | ||
142 | ? document.getElementById('translation-fold-all').innerHTML | ||
143 | : document.getElementById('translation-expand-all').innerHTML | ||
144 | }); | ||
145 | }); | ||
146 | } | ||
147 | |||
148 | function toggleFold(button, description, thumb) | ||
149 | { | ||
150 | // Switch fold/expand - up = fold | ||
151 | if (button.classList.contains('fa-chevron-up')) { | ||
152 | button.title = document.getElementById('translation-expand').innerHTML; | ||
153 | if (description != null) { | ||
154 | description.style.display = 'none'; | ||
155 | } | ||
156 | if (thumb != null) { | ||
157 | thumb.style.display = 'none'; | ||
158 | } | ||
159 | } | ||
160 | else { | ||
161 | button.title = document.getElementById('translation-fold').innerHTML; | ||
162 | if (description != null) { | ||
163 | description.style.display = 'block'; | ||
164 | } | ||
165 | if (thumb != null) { | ||
166 | thumb.style.display = 'block'; | ||
167 | } | ||
168 | } | ||
169 | button.classList.toggle('fa-chevron-down'); | ||
170 | button.classList.toggle('fa-chevron-up'); | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Confirmation message before deletion. | ||
175 | */ | ||
176 | var deleteLinks = document.querySelectorAll('.confirm-delete'); | ||
177 | [].forEach.call(deleteLinks, function(deleteLink) { | ||
178 | deleteLink.addEventListener('click', function(event) { | ||
179 | if(! confirm(document.getElementById('translation-delete-link').innerHTML)) { | ||
180 | event.preventDefault(); | ||
181 | } | ||
182 | }); | ||
183 | }); | ||
184 | |||
185 | /** | ||
186 | * Close alerts | ||
187 | */ | ||
188 | var closeLinks = document.querySelectorAll('.pure-alert-close'); | ||
189 | [].forEach.call(closeLinks, function(closeLink) { | ||
190 | closeLink.addEventListener('click', function(event) { | ||
191 | var alert = getParentByClass(event.target, 'pure-alert-closable'); | ||
192 | alert.style.display = 'none'; | ||
193 | }); | ||
194 | }); | ||
195 | |||
196 | /** | ||
197 | * New version dismiss. | ||
198 | * Hide the message for one week using localStorage. | ||
199 | */ | ||
200 | var newVersionDismiss = document.getElementById('new-version-dismiss'); | ||
201 | var newVersionMessage = document.querySelector('.new-version-message'); | ||
202 | if (newVersionMessage != null | ||
203 | && localStorage.getItem('newVersionDismiss') != null | ||
204 | && parseInt(localStorage.getItem('newVersionDismiss')) + 7*24*60*60*1000 > (new Date()).getTime() | ||
205 | ) { | ||
206 | newVersionMessage.style.display = 'none'; | ||
207 | } | ||
208 | if (newVersionDismiss != null) { | ||
209 | newVersionDismiss.addEventListener('click', function () { | ||
210 | localStorage.setItem('newVersionDismiss', (new Date()).getTime()); | ||
211 | }); | ||
212 | } | ||
213 | |||
214 | var hiddenReturnurl = document.getElementsByName('returnurl'); | ||
215 | if (hiddenReturnurl != null) { | ||
216 | hiddenReturnurl.value = window.location.href; | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * Autofocus text fields | ||
221 | */ | ||
222 | var autofocusElements = document.querySelectorAll('.autofocus'); | ||
223 | var breakLoop = false; | ||
224 | [].forEach.call(autofocusElements, function(autofocusElement) { | ||
225 | if (autofocusElement.value == '' && ! breakLoop) { | ||
226 | autofocusElement.focus(); | ||
227 | breakLoop = true; | ||
228 | } | ||
229 | }); | ||
230 | |||
231 | /** | ||
232 | * Handle sub menus/forms | ||
233 | */ | ||
234 | var openers = document.getElementsByClassName('subheader-opener'); | ||
235 | if (openers != null) { | ||
236 | [].forEach.call(openers, function(opener) { | ||
237 | opener.addEventListener('click', function(event) { | ||
238 | event.preventDefault(); | ||
239 | |||
240 | var id = opener.getAttribute('data-open-id'); | ||
241 | var sub = document.getElementById(id); | ||
242 | |||
243 | if (sub != null) { | ||
244 | [].forEach.call(document.getElementsByClassName('subheader-form'), function (element) { | ||
245 | if (element != sub) { | ||
246 | removeClass(element, 'open') | ||
247 | } | ||
248 | }); | ||
249 | |||
250 | sub.classList.toggle('open'); | ||
251 | } | ||
252 | }); | ||
253 | }); | ||
254 | } | ||
255 | |||
256 | function removeClass(element, classname) { | ||
257 | element.className = element.className.replace(new RegExp('(?:^|\\s)'+ classname + '(?:\\s|$)'), ' '); | ||
258 | } | ||
259 | |||
260 | /** | ||
261 | * Remove CSS target padding (for fixed bar) | ||
262 | */ | ||
263 | if (location.hash != '') { | ||
264 | var anchor = document.getElementById(location.hash.substr(1)); | ||
265 | if (anchor != null) { | ||
266 | var padsize = anchor.clientHeight; | ||
267 | this.window.scroll(0, this.window.scrollY - padsize); | ||
268 | anchor.style.paddingTop = 0; | ||
269 | } | ||
270 | } | ||
271 | |||
272 | /** | ||
273 | * Text area resizer | ||
274 | */ | ||
275 | var description = document.getElementById('lf_description'); | ||
276 | var observe = function (element, event, handler) { | ||
277 | element.addEventListener(event, handler, false); | ||
278 | }; | ||
279 | function init () { | ||
280 | function resize () { | ||
281 | /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */ | ||
282 | var scrollTop = window.pageYOffset || | ||
283 | (document.documentElement || document.body.parentNode || document.body).scrollTop; | ||
284 | |||
285 | description.style.height = 'auto'; | ||
286 | description.style.height = description.scrollHeight+10+'px'; | ||
287 | |||
288 | window.scrollTo(0, scrollTop); | ||
289 | } | ||
290 | /* 0-timeout to get the already changed text */ | ||
291 | function delayedResize () { | ||
292 | window.setTimeout(resize, 0); | ||
293 | } | ||
294 | observe(description, 'change', resize); | ||
295 | observe(description, 'cut', delayedResize); | ||
296 | observe(description, 'paste', delayedResize); | ||
297 | observe(description, 'drop', delayedResize); | ||
298 | observe(description, 'keydown', delayedResize); | ||
299 | |||
300 | resize(); | ||
301 | } | ||
302 | |||
303 | if (description != null) { | ||
304 | init(); | ||
305 | // Submit editlink form with CTRL + Enter in the text area. | ||
306 | description.addEventListener('keydown', function (event) { | ||
307 | if (event.ctrlKey && event.keyCode === 13) { | ||
308 | document.getElementById('button-save-edit').click(); | ||
309 | } | ||
310 | }); | ||
311 | } | ||
312 | |||
313 | /** | ||
314 | * Awesomplete trigger. | ||
315 | */ | ||
316 | var tags = document.getElementById('lf_tags'); | ||
317 | if (tags != null) { | ||
318 | awesompleteUniqueTag('#lf_tags'); | ||
319 | } | ||
320 | |||
321 | /** | ||
322 | * bLazy trigger | ||
323 | */ | ||
324 | var picwall = document.getElementById('picwall_container'); | ||
325 | if (picwall != null) { | ||
326 | var bLazy = new Blazy(); | ||
327 | } | ||
328 | |||
329 | /** | ||
330 | * Bookmarklet alert | ||
331 | */ | ||
332 | var bookmarkletLinks = document.querySelectorAll('.bookmarklet-link'); | ||
333 | var bkmMessage = document.getElementById('bookmarklet-alert'); | ||
334 | [].forEach.call(bookmarkletLinks, function(link) { | ||
335 | link.addEventListener('click', function(event) { | ||
336 | event.preventDefault(); | ||
337 | alert(bkmMessage.value); | ||
338 | }); | ||
339 | }); | ||
340 | |||
341 | /** | ||
342 | * Firefox Social | ||
343 | */ | ||
344 | var ffButton = document.getElementById('ff-social-button'); | ||
345 | if (ffButton != null) { | ||
346 | ffButton.addEventListener('click', function(event) { | ||
347 | activateFirefoxSocial(event.target); | ||
348 | }); | ||
349 | } | ||
350 | |||
351 | /** | ||
352 | * Plugin admin order | ||
353 | */ | ||
354 | var orderPA = document.querySelectorAll('.order'); | ||
355 | [].forEach.call(orderPA, function(link) { | ||
356 | link.addEventListener('click', function(event) { | ||
357 | event.preventDefault(); | ||
358 | if (event.target.classList.contains('order-up')) { | ||
359 | return orderUp(event.target.parentNode.parentNode.getAttribute('data-order')); | ||
360 | } else if (event.target.classList.contains('order-down')) { | ||
361 | return orderDown(event.target.parentNode.parentNode.getAttribute('data-order')); | ||
362 | } | ||
363 | }); | ||
364 | }); | ||
365 | |||
366 | var continent = document.getElementById('continent'); | ||
367 | var city = document.getElementById('city'); | ||
368 | if (continent != null && city != null) { | ||
369 | continent.addEventListener('change', function (event) { | ||
370 | hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true); | ||
371 | }); | ||
372 | hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false); | ||
373 | } | ||
374 | |||
375 | /** | ||
376 | * Bulk actions | ||
377 | */ | ||
378 | var linkCheckboxes = document.querySelectorAll('.delete-checkbox'); | ||
379 | var bar = document.getElementById('actions'); | ||
380 | [].forEach.call(linkCheckboxes, function(checkbox) { | ||
381 | checkbox.style.display = 'inline-block'; | ||
382 | checkbox.addEventListener('click', function(event) { | ||
383 | var count = 0; | ||
384 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); | ||
385 | [].forEach.call(linkCheckedCheckboxes, function(checkbox) { | ||
386 | count++; | ||
387 | }); | ||
388 | if (count == 0 && bar.classList.contains('open')) { | ||
389 | bar.classList.toggle('open'); | ||
390 | } else if (count > 0 && ! bar.classList.contains('open')) { | ||
391 | bar.classList.toggle('open'); | ||
392 | } | ||
393 | }); | ||
394 | }); | ||
395 | |||
396 | var deleteButton = document.getElementById('actions-delete'); | ||
397 | var token = document.querySelector('input[type="hidden"][name="token"]'); | ||
398 | if (deleteButton != null && token != null) { | ||
399 | deleteButton.addEventListener('click', function(event) { | ||
400 | event.preventDefault(); | ||
401 | |||
402 | var links = []; | ||
403 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); | ||
404 | [].forEach.call(linkCheckedCheckboxes, function(checkbox) { | ||
405 | links.push({ | ||
406 | 'id': checkbox.value, | ||
407 | 'title': document.querySelector('.linklist-item[data-id="'+ checkbox.value +'"] .linklist-link').innerHTML | ||
408 | }); | ||
409 | }); | ||
410 | |||
411 | var message = 'Are you sure you want to delete '+ links.length +' links?\n'; | ||
412 | message += 'This action is IRREVERSIBLE!\n\nTitles:\n'; | ||
413 | var ids = []; | ||
414 | links.forEach(function(item) { | ||
415 | message += ' - '+ item['title'] +'\n'; | ||
416 | ids.push(item['id']); | ||
417 | }); | ||
418 | |||
419 | if (window.confirm(message)) { | ||
420 | window.location = '?delete_link&lf_linkdate='+ ids.join('+') +'&token='+ token.value; | ||
421 | } | ||
422 | }); | ||
423 | } | ||
424 | |||
425 | /** | ||
426 | * Tag list operations | ||
427 | * | ||
428 | * TODO: support error code in the backend for AJAX requests | ||
429 | */ | ||
430 | var tagList = document.querySelector('input[name="taglist"]'); | ||
431 | var existingTags = tagList ? tagList.value.split(' ') : []; | ||
432 | var awesomepletes = []; | ||
433 | |||
434 | // Display/Hide rename form | ||
435 | var renameTagButtons = document.querySelectorAll('.rename-tag'); | ||
436 | [].forEach.call(renameTagButtons, function(rename) { | ||
437 | rename.addEventListener('click', function(event) { | ||
438 | event.preventDefault(); | ||
439 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
440 | var form = block.querySelector('.rename-tag-form'); | ||
441 | if (form.style.display == 'none' || form.style.display == '') { | ||
442 | form.style.display = 'block'; | ||
443 | } else { | ||
444 | form.style.display = 'none'; | ||
445 | } | ||
446 | block.querySelector('input').focus(); | ||
447 | }); | ||
448 | }); | ||
449 | |||
450 | // Rename a tag with an AJAX request | ||
451 | var renameTagSubmits = document.querySelectorAll('.validate-rename-tag'); | ||
452 | [].forEach.call(renameTagSubmits, function(rename) { | ||
453 | rename.addEventListener('click', function(event) { | ||
454 | event.preventDefault(); | ||
455 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
456 | var input = block.querySelector('.rename-tag-input'); | ||
457 | var totag = input.value.replace('/"/g', '\\"'); | ||
458 | if (totag.trim() == '') { | ||
459 | return; | ||
460 | } | ||
461 | var fromtag = block.getAttribute('data-tag'); | ||
462 | var token = document.getElementById('token').value; | ||
463 | |||
464 | xhr = new XMLHttpRequest(); | ||
465 | xhr.open('POST', '?do=changetag'); | ||
466 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
467 | xhr.onload = function() { | ||
468 | if (xhr.status !== 200) { | ||
469 | alert('An error occurred. Return code: '+ xhr.status); | ||
470 | location.reload(); | ||
471 | } else { | ||
472 | block.setAttribute('data-tag', totag); | ||
473 | input.setAttribute('name', totag); | ||
474 | input.setAttribute('value', totag); | ||
475 | findParent(input, 'div', {'class': 'rename-tag-form'}).style.display = 'none'; | ||
476 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); | ||
477 | block.querySelector('a.tag-link').setAttribute('href', '?searchtags='+ encodeURIComponent(totag)); | ||
478 | block.querySelector('a.rename-tag').setAttribute('href', '?do=changetag&fromtag='+ encodeURIComponent(totag)); | ||
479 | |||
480 | // Refresh awesomplete values | ||
481 | for (var key in existingTags) { | ||
482 | if (existingTags[key] == fromtag) { | ||
483 | existingTags[key] = totag; | ||
484 | } | ||
485 | } | ||
486 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | ||
487 | } | ||
488 | }; | ||
489 | xhr.send('renametag=1&fromtag='+ encodeURIComponent(fromtag) +'&totag='+ encodeURIComponent(totag) +'&token='+ token); | ||
490 | refreshToken(); | ||
491 | }); | ||
492 | }); | ||
493 | |||
494 | // Validate input with enter key | ||
495 | var renameTagInputs = document.querySelectorAll('.rename-tag-input'); | ||
496 | [].forEach.call(renameTagInputs, function(rename) { | ||
497 | |||
498 | rename.addEventListener('keypress', function(event) { | ||
499 | if (event.keyCode === 13) { // enter | ||
500 | findParent(event.target, 'div', {'class': 'tag-list-item'}).querySelector('.validate-rename-tag').click(); | ||
501 | } | ||
502 | }); | ||
503 | }); | ||
504 | |||
505 | // Delete a tag with an AJAX query (alert popup confirmation) | ||
506 | var deleteTagButtons = document.querySelectorAll('.delete-tag'); | ||
507 | [].forEach.call(deleteTagButtons, function(rename) { | ||
508 | rename.style.display = 'inline'; | ||
509 | rename.addEventListener('click', function(event) { | ||
510 | event.preventDefault(); | ||
511 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
512 | var tag = block.getAttribute('data-tag'); | ||
513 | var token = document.getElementById('token').value; | ||
514 | |||
515 | if (confirm('Are you sure you want to delete the tag "'+ tag +'"?')) { | ||
516 | xhr = new XMLHttpRequest(); | ||
517 | xhr.open('POST', '?do=changetag'); | ||
518 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
519 | xhr.onload = function() { | ||
520 | block.remove(); | ||
521 | }; | ||
522 | xhr.send(encodeURI('deletetag=1&fromtag='+ tag +'&token='+ token)); | ||
523 | refreshToken(); | ||
524 | } | ||
525 | }); | ||
526 | }); | ||
527 | |||
528 | updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | ||
529 | }; | ||
530 | |||
531 | /** | ||
532 | * Find a parent element according to its tag and its attributes | ||
533 | * | ||
534 | * @param element Element where to start the search | ||
535 | * @param tagName Expected parent tag name | ||
536 | * @param attributes Associative array of expected attributes (name=>value). | ||
537 | * | ||
538 | * @returns Found element or null. | ||
539 | */ | ||
540 | function findParent(element, tagName, attributes) | ||
541 | { | ||
542 | while (element) { | ||
543 | if (element.tagName.toLowerCase() == tagName) { | ||
544 | var match = true; | ||
545 | for (var key in attributes) { | ||
546 | if (! element.hasAttribute(key) | ||
547 | || (attributes[key] != '' && element.getAttribute(key).indexOf(attributes[key]) == -1) | ||
548 | ) { | ||
549 | match = false; | ||
550 | break; | ||
551 | } | ||
552 | } | ||
553 | |||
554 | if (match) { | ||
555 | return element; | ||
556 | } | ||
557 | } | ||
558 | element = element.parentElement; | ||
559 | } | ||
560 | return null; | ||
561 | } | ||
562 | |||
563 | /** | ||
564 | * Ajax request to refresh the CSRF token. | ||
565 | */ | ||
566 | function refreshToken() | ||
567 | { | ||
568 | var xhr = new XMLHttpRequest(); | ||
569 | xhr.open('GET', '?do=token'); | ||
570 | xhr.onload = function() { | ||
571 | var token = document.getElementById('token'); | ||
572 | token.setAttribute('value', xhr.responseText); | ||
573 | }; | ||
574 | xhr.send(); | ||
575 | } | ||
576 | |||
577 | /** | ||
578 | * Update awesomplete list of tag for all elements matching the given selector | ||
579 | * | ||
580 | * @param selector CSS selector | ||
581 | * @param tags Array of tags | ||
582 | * @param instances List of existing awesomplete instances | ||
583 | */ | ||
584 | function updateAwesompleteList(selector, tags, instances) | ||
585 | { | ||
586 | // First load: create Awesomplete instances | ||
587 | if (instances.length == 0) { | ||
588 | var elements = document.querySelectorAll(selector); | ||
589 | [].forEach.call(elements, function (element) { | ||
590 | instances.push(new Awesomplete( | ||
591 | element, | ||
592 | {'list': tags} | ||
593 | )); | ||
594 | }); | ||
595 | } else { | ||
596 | // Update awesomplete tag list | ||
597 | for (var key in instances) { | ||
598 | instances[key].list = tags; | ||
599 | } | ||
600 | } | ||
601 | return instances; | ||
602 | } | ||
603 | |||
604 | /** | ||
605 | * html_entities in JS | ||
606 | * | ||
607 | * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript | ||
608 | */ | ||
609 | function htmlEntities(str) | ||
610 | { | ||
611 | return str.replace(/[\u00A0-\u9999<>\&]/gim, function(i) { | ||
612 | return '&#'+i.charCodeAt(0)+';'; | ||
613 | }); | ||
614 | } | ||
615 | |||
616 | function activateFirefoxSocial(node) { | ||
617 | var loc = location.href; | ||
618 | var baseURL = loc.substring(0, loc.lastIndexOf("/") + 1); | ||
619 | var title = document.title; | ||
620 | |||
621 | // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable. | ||
622 | var data = { | ||
623 | name: title, | ||
624 | description: document.getElementById('translation-delete-link').innerHTML, | ||
625 | author: "Shaarli", | ||
626 | version: "1.0.0", | ||
627 | |||
628 | iconURL: baseURL + "/images/favicon.ico", | ||
629 | icon32URL: baseURL + "/images/favicon.ico", | ||
630 | icon64URL: baseURL + "/images/favicon.ico", | ||
631 | |||
632 | shareURL: baseURL + "?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi", | ||
633 | homepageURL: baseURL | ||
634 | }; | ||
635 | node.setAttribute("data-service", JSON.stringify(data)); | ||
636 | |||
637 | var activate = new CustomEvent("ActivateSocialFeature"); | ||
638 | node.dispatchEvent(activate); | ||
639 | } | ||
640 | |||
641 | /** | ||
642 | * Add the class 'hidden' to city options not attached to the current selected continent. | ||
643 | * | ||
644 | * @param cities List of <option> elements | ||
645 | * @param currentContinent Current selected continent | ||
646 | * @param reset Set to true to reset the selected value | ||
647 | */ | ||
648 | function hideTimezoneCities(cities, currentContinent) { | ||
649 | var first = true; | ||
650 | if (reset == null) { | ||
651 | reset = false; | ||
652 | } | ||
653 | [].forEach.call(cities, function (option) { | ||
654 | if (option.getAttribute('data-continent') != currentContinent) { | ||
655 | option.className = 'hidden'; | ||
656 | } else { | ||
657 | option.className = ''; | ||
658 | if (reset === true && first === true) { | ||
659 | option.setAttribute('selected', 'selected'); | ||
660 | first = false; | ||
661 | } | ||
662 | } | ||
663 | }); | ||
664 | } | ||