aboutsummaryrefslogtreecommitdiffhomepage
path: root/tpl/default/js/shaarli.js
diff options
context:
space:
mode:
Diffstat (limited to 'tpl/default/js/shaarli.js')
-rw-r--r--tpl/default/js/shaarli.js265
1 files changed, 254 insertions, 11 deletions
diff --git a/tpl/default/js/shaarli.js b/tpl/default/js/shaarli.js
index 4d47fcd0..4f49affa 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,16 +357,256 @@ 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 tagList = document.querySelector('input[name="taglist"]');
422 var existingTags = tagList ? tagList.value.split(' ') : [];
423 var awesomepletes = [];
424
425 // Display/Hide rename form
426 var renameTagButtons = document.querySelectorAll('.rename-tag');
427 [].forEach.call(renameTagButtons, function(rename) {
428 rename.addEventListener('click', function(event) {
429 event.preventDefault();
430 var block = findParent(event.target, 'div', {'class': 'tag-list-item'});
431 var form = block.querySelector('.rename-tag-form');
432 if (form.style.display == 'none' || form.style.display == '') {
433 form.style.display = 'block';
434 } else {
435 form.style.display = 'none';
436 }
437 block.querySelector('input').focus();
438 });
439 });
440
441 // Rename a tag with an AJAX request
442 var renameTagSubmits = document.querySelectorAll('.validate-rename-tag');
443 [].forEach.call(renameTagSubmits, function(rename) {
444 rename.addEventListener('click', function(event) {
445 event.preventDefault();
446 var block = findParent(event.target, 'div', {'class': 'tag-list-item'});
447 var input = block.querySelector('.rename-tag-input');
448 var totag = input.value.replace('/"/g', '\\"');
449 if (totag.trim() == '') {
450 return;
451 }
452 var fromtag = block.getAttribute('data-tag');
453 var token = document.getElementById('token').value;
454
455 xhr = new XMLHttpRequest();
456 xhr.open('POST', '?do=changetag');
457 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
458 xhr.onload = function() {
459 if (xhr.status !== 200) {
460 alert('An error occurred. Return code: '+ xhr.status);
461 location.reload();
462 } else {
463 block.setAttribute('data-tag', totag);
464 input.setAttribute('name', totag);
465 input.setAttribute('value', totag);
466 findParent(input, 'div', {'class': 'rename-tag-form'}).style.display = 'none';
467 block.querySelector('a.tag-link').innerHTML = htmlEntities(totag);
468 block.querySelector('a.tag-link').setAttribute('href', '?searchtags='+ encodeURIComponent(totag));
469 block.querySelector('a.rename-tag').setAttribute('href', '?do=changetag&fromtag='+ encodeURIComponent(totag));
470
471 // Refresh awesomplete values
472 for (var key in existingTags) {
473 if (existingTags[key] == fromtag) {
474 existingTags[key] = totag;
475 }
476 }
477 awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
478 }
479 };
480 xhr.send('renametag=1&fromtag='+ encodeURIComponent(fromtag) +'&totag='+ encodeURIComponent(totag) +'&token='+ token);
481 refreshToken();
482 });
483 });
484
485 // Validate input with enter key
486 var renameTagInputs = document.querySelectorAll('.rename-tag-input');
487 [].forEach.call(renameTagInputs, function(rename) {
488
489 rename.addEventListener('keypress', function(event) {
490 if (event.keyCode === 13) { // enter
491 findParent(event.target, 'div', {'class': 'tag-list-item'}).querySelector('.validate-rename-tag').click();
492 }
493 });
494 });
495
496 // Delete a tag with an AJAX query (alert popup confirmation)
497 var deleteTagButtons = document.querySelectorAll('.delete-tag');
498 [].forEach.call(deleteTagButtons, function(rename) {
499 rename.style.display = 'inline';
500 rename.addEventListener('click', function(event) {
501 event.preventDefault();
502 var block = findParent(event.target, 'div', {'class': 'tag-list-item'});
503 var tag = block.getAttribute('data-tag');
504 var token = document.getElementById('token').value;
505
506 if (confirm('Are you sure you want to delete the tag "'+ tag +'"?')) {
507 xhr = new XMLHttpRequest();
508 xhr.open('POST', '?do=changetag');
509 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
510 xhr.onload = function() {
511 block.remove();
512 };
513 xhr.send(encodeURI('deletetag=1&fromtag='+ tag +'&token='+ token));
514 refreshToken();
515 }
516 });
517 });
518
519 updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
365}; 520};
366 521
522/**
523 * Find a parent element according to its tag and its attributes
524 *
525 * @param element Element where to start the search
526 * @param tagName Expected parent tag name
527 * @param attributes Associative array of expected attributes (name=>value).
528 *
529 * @returns Found element or null.
530 */
531function findParent(element, tagName, attributes)
532{
533 while (element) {
534 if (element.tagName.toLowerCase() == tagName) {
535 var match = true;
536 for (var key in attributes) {
537 if (! element.hasAttribute(key)
538 || (attributes[key] != '' && element.getAttribute(key).indexOf(attributes[key]) == -1)
539 ) {
540 match = false;
541 break;
542 }
543 }
544
545 if (match) {
546 return element;
547 }
548 }
549 element = element.parentElement;
550 }
551 return null;
552}
553
554/**
555 * Ajax request to refresh the CSRF token.
556 */
557function refreshToken()
558{
559 var xhr = new XMLHttpRequest();
560 xhr.open('GET', '?do=token');
561 xhr.onload = function() {
562 var token = document.getElementById('token');
563 token.setAttribute('value', xhr.responseText);
564 };
565 xhr.send();
566}
567
568/**
569 * Update awesomplete list of tag for all elements matching the given selector
570 *
571 * @param selector CSS selector
572 * @param tags Array of tags
573 * @param instances List of existing awesomplete instances
574 */
575function updateAwesompleteList(selector, tags, instances)
576{
577 // First load: create Awesomplete instances
578 if (instances.length == 0) {
579 var elements = document.querySelectorAll(selector);
580 [].forEach.call(elements, function (element) {
581 instances.push(new Awesomplete(
582 element,
583 {'list': tags}
584 ));
585 });
586 } else {
587 // Update awesomplete tag list
588 for (var key in instances) {
589 instances[key].list = tags;
590 }
591 }
592 return instances;
593}
594
595/**
596 * html_entities in JS
597 *
598 * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript
599 */
600function htmlEntities(str)
601{
602 return str.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
603 return '&#'+i.charCodeAt(0)+';';
604 });
605}
606
367function activateFirefoxSocial(node) { 607function activateFirefoxSocial(node) {
368 var loc = location.href; 608 var loc = location.href;
369 var baseURL = loc.substring(0, loc.lastIndexOf("/")); 609 var baseURL = loc.substring(0, loc.lastIndexOf("/") + 1);
370 610
371 // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable. 611 // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable.
372 var data = { 612 var data = {
@@ -379,7 +619,7 @@ function activateFirefoxSocial(node) {
379 icon32URL: baseURL + "/images/favicon.ico", 619 icon32URL: baseURL + "/images/favicon.ico",
380 icon64URL: baseURL + "/images/favicon.ico", 620 icon64URL: baseURL + "/images/favicon.ico",
381 621
382 shareURL: baseURL + "{noparse}?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi{/noparse}", 622 shareURL: baseURL + "?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi",
383 homepageURL: baseURL 623 homepageURL: baseURL
384 }; 624 };
385 node.setAttribute("data-service", JSON.stringify(data)); 625 node.setAttribute("data-service", JSON.stringify(data));
@@ -395,9 +635,12 @@ function activateFirefoxSocial(node) {
395 * @param currentContinent Current selected continent 635 * @param currentContinent Current selected continent
396 * @param reset Set to true to reset the selected value 636 * @param reset Set to true to reset the selected value
397 */ 637 */
398function hideTimezoneCities(cities, currentContinent, reset = false) { 638function hideTimezoneCities(cities, currentContinent) {
399 var first = true; 639 var first = true;
400 [].forEach.call(cities, function(option) { 640 if (reset == null) {
641 reset = false;
642 }
643 [].forEach.call(cities, function (option) {
401 if (option.getAttribute('data-continent') != currentContinent) { 644 if (option.getAttribute('data-continent') != currentContinent) {
402 option.className = 'hidden'; 645 option.className = 'hidden';
403 } else { 646 } else {