diff options
Diffstat (limited to 'tpl/default/js')
-rw-r--r-- | tpl/default/js/shaarli.js | 260 |
1 files changed, 251 insertions, 9 deletions
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js index 4d47fcd0..4ebb7815 100644 --- a/tpl/default/js/shaarli.js +++ b/tpl/default/js/shaarli.js | |||
@@ -216,14 +216,14 @@ window.onload = function () { | |||
216 | /** | 216 | /** |
217 | * Autofocus text fields | 217 | * Autofocus text fields |
218 | */ | 218 | */ |
219 | // ES6 syntax | 219 | var autofocusElements = document.querySelectorAll('.autofocus'); |
220 | let autofocusElements = document.querySelectorAll('.autofocus'); | 220 | var breakLoop = false; |
221 | for (let autofocusElement of autofocusElements) { | 221 | [].forEach.call(autofocusElements, function(autofocusElement) { |
222 | if (autofocusElement.value == '') { | 222 | if (autofocusElement.value == '' && ! breakLoop) { |
223 | autofocusElement.focus(); | 223 | autofocusElement.focus(); |
224 | break; | 224 | breakLoop = true; |
225 | } | 225 | } |
226 | } | 226 | }); |
227 | 227 | ||
228 | /** | 228 | /** |
229 | * Handle sub menus/forms | 229 | * Handle sub menus/forms |
@@ -357,13 +357,252 @@ window.onload = function () { | |||
357 | var continent = document.getElementById('continent'); | 357 | var continent = document.getElementById('continent'); |
358 | var city = document.getElementById('city'); | 358 | var city = document.getElementById('city'); |
359 | if (continent != null && city != null) { | 359 | if (continent != null && city != null) { |
360 | continent.addEventListener('change', function(event) { | 360 | continent.addEventListener('change', function (event) { |
361 | hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true); | 361 | hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true); |
362 | }); | 362 | }); |
363 | hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false); | 363 | hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false); |
364 | } | 364 | } |
365 | |||
366 | /** | ||
367 | * Bulk actions | ||
368 | */ | ||
369 | var linkCheckboxes = document.querySelectorAll('.delete-checkbox'); | ||
370 | var bar = document.getElementById('actions'); | ||
371 | [].forEach.call(linkCheckboxes, function(checkbox) { | ||
372 | checkbox.style.display = 'block'; | ||
373 | checkbox.addEventListener('click', function(event) { | ||
374 | var count = 0; | ||
375 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); | ||
376 | [].forEach.call(linkCheckedCheckboxes, function(checkbox) { | ||
377 | count++; | ||
378 | }); | ||
379 | if (count == 0 && bar.classList.contains('open')) { | ||
380 | bar.classList.toggle('open'); | ||
381 | } else if (count > 0 && ! bar.classList.contains('open')) { | ||
382 | bar.classList.toggle('open'); | ||
383 | } | ||
384 | }); | ||
385 | }); | ||
386 | |||
387 | var deleteButton = document.getElementById('actions-delete'); | ||
388 | var token = document.querySelector('input[type="hidden"][name="token"]'); | ||
389 | if (deleteButton != null && token != null) { | ||
390 | deleteButton.addEventListener('click', function(event) { | ||
391 | event.preventDefault(); | ||
392 | |||
393 | var links = []; | ||
394 | var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked'); | ||
395 | [].forEach.call(linkCheckedCheckboxes, function(checkbox) { | ||
396 | links.push({ | ||
397 | 'id': checkbox.value, | ||
398 | 'title': document.querySelector('.linklist-item[data-id="'+ checkbox.value +'"] .linklist-link').innerHTML | ||
399 | }); | ||
400 | }); | ||
401 | |||
402 | var message = 'Are you sure you want to delete '+ links.length +' links?\n'; | ||
403 | message += 'This action is IRREVERSIBLE!\n\nTitles:\n'; | ||
404 | var ids = ''; | ||
405 | links.forEach(function(item) { | ||
406 | message += ' - '+ item['title'] +'\n'; | ||
407 | ids += item['id'] +'+'; | ||
408 | }); | ||
409 | |||
410 | if (window.confirm(message)) { | ||
411 | window.location = '?delete_link&lf_linkdate='+ ids +'&token='+ token.value; | ||
412 | } | ||
413 | }); | ||
414 | } | ||
415 | |||
416 | /** | ||
417 | * Tag list operations | ||
418 | * | ||
419 | * TODO: support error code in the backend for AJAX requests | ||
420 | */ | ||
421 | var existingTags = document.querySelector('input[name="taglist"]').value.split(' '); | ||
422 | var awesomepletes = []; | ||
423 | |||
424 | // Display/Hide rename form | ||
425 | var renameTagButtons = document.querySelectorAll('.rename-tag'); | ||
426 | [].forEach.call(renameTagButtons, function(rename) { | ||
427 | rename.addEventListener('click', function(event) { | ||
428 | event.preventDefault(); | ||
429 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
430 | var form = block.querySelector('.rename-tag-form'); | ||
431 | if (form.style.display == 'none' || form.style.display == '') { | ||
432 | form.style.display = 'block'; | ||
433 | } else { | ||
434 | form.style.display = 'none'; | ||
435 | } | ||
436 | block.querySelector('input').focus(); | ||
437 | }); | ||
438 | }); | ||
439 | |||
440 | // Rename a tag with an AJAX request | ||
441 | var renameTagSubmits = document.querySelectorAll('.validate-rename-tag'); | ||
442 | [].forEach.call(renameTagSubmits, function(rename) { | ||
443 | rename.addEventListener('click', function(event) { | ||
444 | event.preventDefault(); | ||
445 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
446 | var input = block.querySelector('.rename-tag-input'); | ||
447 | var totag = input.value.replace('/"/g', '\\"'); | ||
448 | if (totag.trim() == '') { | ||
449 | return; | ||
450 | } | ||
451 | var fromtag = block.getAttribute('data-tag'); | ||
452 | var token = document.getElementById('token').value; | ||
453 | |||
454 | xhr = new XMLHttpRequest(); | ||
455 | xhr.open('POST', '?do=changetag'); | ||
456 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
457 | xhr.onload = function() { | ||
458 | if (xhr.status !== 200) { | ||
459 | alert('An error occurred. Return code: '+ xhr.status); | ||
460 | location.reload(); | ||
461 | } else { | ||
462 | block.setAttribute('data-tag', totag); | ||
463 | input.setAttribute('name', totag); | ||
464 | input.setAttribute('value', totag); | ||
465 | findParent(input, 'div', {'class': 'rename-tag-form'}).style.display = 'none'; | ||
466 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); | ||
467 | block.querySelector('a.tag-link').setAttribute('href', '?searchtags='+ encodeURIComponent(totag)); | ||
468 | block.querySelector('a.rename-tag').setAttribute('href', '?do=changetag&fromtag='+ encodeURIComponent(totag)); | ||
469 | |||
470 | // Refresh awesomplete values | ||
471 | for (var key in existingTags) { | ||
472 | if (existingTags[key] == fromtag) { | ||
473 | existingTags[key] = totag; | ||
474 | } | ||
475 | } | ||
476 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | ||
477 | } | ||
478 | }; | ||
479 | xhr.send('renametag=1&fromtag='+ encodeURIComponent(fromtag) +'&totag='+ encodeURIComponent(totag) +'&token='+ token); | ||
480 | refreshToken(); | ||
481 | }); | ||
482 | }); | ||
483 | |||
484 | // Validate input with enter key | ||
485 | var renameTagInputs = document.querySelectorAll('.rename-tag-input'); | ||
486 | [].forEach.call(renameTagInputs, function(rename) { | ||
487 | |||
488 | rename.addEventListener('keypress', function(event) { | ||
489 | if (event.keyCode === 13) { // enter | ||
490 | findParent(event.target, 'div', {'class': 'tag-list-item'}).querySelector('.validate-rename-tag').click(); | ||
491 | } | ||
492 | }); | ||
493 | }); | ||
494 | |||
495 | // Delete a tag with an AJAX query (alert popup confirmation) | ||
496 | var deleteTagButtons = document.querySelectorAll('.delete-tag'); | ||
497 | [].forEach.call(deleteTagButtons, function(rename) { | ||
498 | rename.style.display = 'inline'; | ||
499 | rename.addEventListener('click', function(event) { | ||
500 | event.preventDefault(); | ||
501 | var block = findParent(event.target, 'div', {'class': 'tag-list-item'}); | ||
502 | var tag = block.getAttribute('data-tag'); | ||
503 | var token = document.getElementById('token').value; | ||
504 | |||
505 | if (confirm('Are you sure you want to delete the tag "'+ tag +'"?')) { | ||
506 | xhr = new XMLHttpRequest(); | ||
507 | xhr.open('POST', '?do=changetag'); | ||
508 | xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
509 | xhr.onload = function() { | ||
510 | block.remove(); | ||
511 | }; | ||
512 | xhr.send(encodeURI('deletetag=1&fromtag='+ tag +'&token='+ token)); | ||
513 | refreshToken(); | ||
514 | } | ||
515 | }); | ||
516 | }); | ||
517 | |||
518 | updateAwesompleteList('.rename-tag-input', document.querySelector('input[name="taglist"]').value.split(' '), awesomepletes); | ||
365 | }; | 519 | }; |
366 | 520 | ||
521 | /** | ||
522 | * Find a parent element according to its tag and its attributes | ||
523 | * | ||
524 | * @param element Element where to start the search | ||
525 | * @param tagName Expected parent tag name | ||
526 | * @param attributes Associative array of expected attributes (name=>value). | ||
527 | * | ||
528 | * @returns Found element or null. | ||
529 | */ | ||
530 | function findParent(element, tagName, attributes) | ||
531 | { | ||
532 | while (element) { | ||
533 | if (element.tagName.toLowerCase() == tagName) { | ||
534 | var match = true; | ||
535 | for (var key in attributes) { | ||
536 | if (! element.hasAttribute(key) | ||
537 | || (attributes[key] != '' && element.getAttribute(key).indexOf(attributes[key]) == -1) | ||
538 | ) { | ||
539 | match = false; | ||
540 | break; | ||
541 | } | ||
542 | } | ||
543 | |||
544 | if (match) { | ||
545 | return element; | ||
546 | } | ||
547 | } | ||
548 | element = element.parentElement; | ||
549 | } | ||
550 | return null; | ||
551 | } | ||
552 | |||
553 | /** | ||
554 | * Ajax request to refresh the CSRF token. | ||
555 | */ | ||
556 | function refreshToken() | ||
557 | { | ||
558 | var xhr = new XMLHttpRequest(); | ||
559 | xhr.open('GET', '?do=token'); | ||
560 | xhr.onload = function() { | ||
561 | var token = document.getElementById('token'); | ||
562 | token.setAttribute('value', xhr.responseText); | ||
563 | }; | ||
564 | xhr.send(); | ||
565 | } | ||
566 | |||
567 | /** | ||
568 | * Update awesomplete list of tag for all elements matching the given selector | ||
569 | * | ||
570 | * @param selector CSS selector | ||
571 | * @param tags Array of tags | ||
572 | * @param instances List of existing awesomplete instances | ||
573 | */ | ||
574 | function updateAwesompleteList(selector, tags, instances) | ||
575 | { | ||
576 | // First load: create Awesomplete instances | ||
577 | if (instances.length == 0) { | ||
578 | var elements = document.querySelectorAll(selector); | ||
579 | [].forEach.call(elements, function (element) { | ||
580 | instances.push(new Awesomplete( | ||
581 | element, | ||
582 | {'list': tags} | ||
583 | )); | ||
584 | }); | ||
585 | } else { | ||
586 | // Update awesomplete tag list | ||
587 | for (var key in instances) { | ||
588 | instances[key].list = tags; | ||
589 | } | ||
590 | } | ||
591 | return instances; | ||
592 | } | ||
593 | |||
594 | /** | ||
595 | * html_entities in JS | ||
596 | * | ||
597 | * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript | ||
598 | */ | ||
599 | function htmlEntities(str) | ||
600 | { | ||
601 | return str.replace(/[\u00A0-\u9999<>\&]/gim, function(i) { | ||
602 | return '&#'+i.charCodeAt(0)+';'; | ||
603 | }); | ||
604 | } | ||
605 | |||
367 | function activateFirefoxSocial(node) { | 606 | function activateFirefoxSocial(node) { |
368 | var loc = location.href; | 607 | var loc = location.href; |
369 | var baseURL = loc.substring(0, loc.lastIndexOf("/")); | 608 | var baseURL = loc.substring(0, loc.lastIndexOf("/")); |
@@ -395,9 +634,12 @@ function activateFirefoxSocial(node) { | |||
395 | * @param currentContinent Current selected continent | 634 | * @param currentContinent Current selected continent |
396 | * @param reset Set to true to reset the selected value | 635 | * @param reset Set to true to reset the selected value |
397 | */ | 636 | */ |
398 | function hideTimezoneCities(cities, currentContinent, reset = false) { | 637 | function hideTimezoneCities(cities, currentContinent) { |
399 | var first = true; | 638 | var first = true; |
400 | [].forEach.call(cities, function(option) { | 639 | if (reset == null) { |
640 | reset = false; | ||
641 | } | ||
642 | [].forEach.call(cities, function (option) { | ||
401 | if (option.getAttribute('data-continent') != currentContinent) { | 643 | if (option.getAttribute('data-continent') != currentContinent) { |
402 | option.className = 'hidden'; | 644 | option.className = 'hidden'; |
403 | } else { | 645 | } else { |