aboutsummaryrefslogtreecommitdiffhomepage
path: root/assets/default
diff options
context:
space:
mode:
Diffstat (limited to 'assets/default')
-rw-r--r--assets/default/js/base.js114
-rw-r--r--assets/default/scss/shaarli.scss234
2 files changed, 296 insertions, 52 deletions
diff --git a/assets/default/js/base.js b/assets/default/js/base.js
index d5c29c69..4163577d 100644
--- a/assets/default/js/base.js
+++ b/assets/default/js/base.js
@@ -1,4 +1,5 @@
1import Awesomplete from 'awesomplete'; 1import Awesomplete from 'awesomplete';
2import he from 'he';
2 3
3/** 4/**
4 * Find a parent element according to its tag and its attributes 5 * Find a parent element according to its tag and its attributes
@@ -10,7 +11,7 @@ import Awesomplete from 'awesomplete';
10 * @returns Found element or null. 11 * @returns Found element or null.
11 */ 12 */
12function findParent(element, tagName, attributes) { 13function findParent(element, tagName, attributes) {
13 const parentMatch = key => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1; 14 const parentMatch = (key) => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1;
14 while (element) { 15 while (element) {
15 if (element.tagName.toLowerCase() === tagName) { 16 if (element.tagName.toLowerCase() === tagName) {
16 if (Object.keys(attributes).find(parentMatch)) { 17 if (Object.keys(attributes).find(parentMatch)) {
@@ -25,12 +26,18 @@ function findParent(element, tagName, attributes) {
25/** 26/**
26 * Ajax request to refresh the CSRF token. 27 * Ajax request to refresh the CSRF token.
27 */ 28 */
28function refreshToken() { 29function refreshToken(basePath, callback) {
29 const xhr = new XMLHttpRequest(); 30 const xhr = new XMLHttpRequest();
30 xhr.open('GET', '?do=token'); 31 xhr.open('GET', `${basePath}/admin/token`);
31 xhr.onload = () => { 32 xhr.onload = () => {
32 const token = document.getElementById('token'); 33 const elements = document.querySelectorAll('input[name="token"]');
33 token.setAttribute('value', xhr.responseText); 34 [...elements].forEach((element) => {
35 element.setAttribute('value', xhr.responseText);
36 });
37
38 if (callback) {
39 callback(xhr.response);
40 }
34 }; 41 };
35 xhr.send(); 42 xhr.send();
36} 43}
@@ -90,15 +97,6 @@ function updateAwesompleteList(selector, tags, instances) {
90} 97}
91 98
92/** 99/**
93 * html_entities in JS
94 *
95 * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript
96 */
97function htmlEntities(str) {
98 return str.replace(/[\u00A0-\u9999<>&]/gim, i => `&#${i.charCodeAt(0)};`);
99}
100
101/**
102 * Add the class 'hidden' to city options not attached to the current selected continent. 100 * Add the class 'hidden' to city options not attached to the current selected continent.
103 * 101 *
104 * @param cities List of <option> elements 102 * @param cities List of <option> elements
@@ -188,8 +186,8 @@ function removeClass(element, classname) {
188function init(description) { 186function init(description) {
189 function resize() { 187 function resize() {
190 /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */ 188 /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
191 const scrollTop = window.pageYOffset || 189 const scrollTop = window.pageYOffset
192 (document.documentElement || document.body.parentNode || document.body).scrollTop; 190 || (document.documentElement || document.body.parentNode || document.body).scrollTop;
193 191
194 description.style.height = 'auto'; 192 description.style.height = 'auto';
195 description.style.height = `${description.scrollHeight + 10}px`; 193 description.style.height = `${description.scrollHeight + 10}px`;
@@ -215,6 +213,8 @@ function init(description) {
215} 213}
216 214
217(() => { 215(() => {
216 const basePath = document.querySelector('input[name="js_base_path"]').value;
217
218 /** 218 /**
219 * Handle responsive menu. 219 * Handle responsive menu.
220 * Source: http://purecss.io/layouts/tucked-menu-vertical/ 220 * Source: http://purecss.io/layouts/tucked-menu-vertical/
@@ -294,7 +294,7 @@ function init(description) {
294 const deleteLinks = document.querySelectorAll('.confirm-delete'); 294 const deleteLinks = document.querySelectorAll('.confirm-delete');
295 [...deleteLinks].forEach((deleteLink) => { 295 [...deleteLinks].forEach((deleteLink) => {
296 deleteLink.addEventListener('click', (event) => { 296 deleteLink.addEventListener('click', (event) => {
297 if (!confirm(document.getElementById('translation-delete-link').innerHTML)) { 297 if (!confirm(document.getElementById('translation-delete-tag').innerHTML)) {
298 event.preventDefault(); 298 event.preventDefault();
299 } 299 }
300 }); 300 });
@@ -461,7 +461,7 @@ function init(description) {
461 }); 461 });
462 462
463 if (window.confirm(message)) { 463 if (window.confirm(message)) {
464 window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`; 464 window.location = `${basePath}/admin/shaare/delete?id=${ids.join('+')}&token=${token.value}`;
465 } 465 }
466 }); 466 });
467 } 467 }
@@ -482,8 +482,10 @@ function init(description) {
482 }); 482 });
483 }); 483 });
484 484
485 const ids = links.map(item => item.id); 485 const ids = links.map((item) => item.id);
486 window.location = `?change_visibility&token=${token.value}&newVisibility=${visibility}&ids=${ids.join('+')}`; 486 window.location = (
487 `${basePath}/admin/shaare/visibility?token=${token.value}&newVisibility=${visibility}&id=${ids.join('+')}`
488 );
487 }); 489 });
488 }); 490 });
489 } 491 }
@@ -545,8 +547,9 @@ function init(description) {
545 } 547 }
546 const refreshedToken = document.getElementById('token').value; 548 const refreshedToken = document.getElementById('token').value;
547 const fromtag = block.getAttribute('data-tag'); 549 const fromtag = block.getAttribute('data-tag');
550 const fromtagUrl = block.getAttribute('data-tag-url');
548 const xhr = new XMLHttpRequest(); 551 const xhr = new XMLHttpRequest();
549 xhr.open('POST', '?do=changetag'); 552 xhr.open('POST', `${basePath}/admin/tags`);
550 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 553 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
551 xhr.onload = () => { 554 xhr.onload = () => {
552 if (xhr.status !== 200) { 555 if (xhr.status !== 200) {
@@ -554,20 +557,28 @@ function init(description) {
554 location.reload(); 557 location.reload();
555 } else { 558 } else {
556 block.setAttribute('data-tag', totag); 559 block.setAttribute('data-tag', totag);
560 block.setAttribute('data-tag-url', encodeURIComponent(totag));
557 input.setAttribute('name', totag); 561 input.setAttribute('name', totag);
558 input.setAttribute('value', totag); 562 input.setAttribute('value', totag);
559 findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; 563 findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none';
560 block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); 564 block.querySelector('a.tag-link').innerHTML = he.encode(totag);
561 block.querySelector('a.tag-link').setAttribute('href', `?searchtags=${encodeURIComponent(totag)}`); 565 block
562 block.querySelector('a.rename-tag').setAttribute('href', `?do=changetag&fromtag=${encodeURIComponent(totag)}`); 566 .querySelector('a.tag-link')
567 .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`);
568 block
569 .querySelector('a.count')
570 .setAttribute('href', `${basePath}/add-tag/${encodeURIComponent(totag)}`);
571 block
572 .querySelector('a.rename-tag')
573 .setAttribute('href', `${basePath}/admin/tags?fromtag=${encodeURIComponent(totag)}`);
563 574
564 // Refresh awesomplete values 575 // Refresh awesomplete values
565 existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag)); 576 existingTags = existingTags.map((tag) => (tag === fromtag ? totag : tag));
566 awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); 577 awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
567 } 578 }
568 }; 579 };
569 xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); 580 xhr.send(`renametag=1&fromtag=${fromtagUrl}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
570 refreshToken(); 581 refreshToken(basePath);
571 }); 582 });
572 }); 583 });
573 584
@@ -589,19 +600,20 @@ function init(description) {
589 event.preventDefault(); 600 event.preventDefault();
590 const block = findParent(event.target, 'div', { class: 'tag-list-item' }); 601 const block = findParent(event.target, 'div', { class: 'tag-list-item' });
591 const tag = block.getAttribute('data-tag'); 602 const tag = block.getAttribute('data-tag');
603 const tagUrl = block.getAttribute('data-tag-url');
592 const refreshedToken = document.getElementById('token').value; 604 const refreshedToken = document.getElementById('token').value;
593 605
594 if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) { 606 if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
595 const xhr = new XMLHttpRequest(); 607 const xhr = new XMLHttpRequest();
596 xhr.open('POST', '?do=changetag'); 608 xhr.open('POST', `${basePath}/admin/tags`);
597 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 609 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
598 xhr.onload = () => { 610 xhr.onload = () => {
599 block.remove(); 611 block.remove();
600 }; 612 };
601 xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`)); 613 xhr.send(`deletetag=1&fromtag=${tagUrl}&token=${refreshedToken}`);
602 refreshToken(); 614 refreshToken(basePath);
603 615
604 existingTags = existingTags.filter(tagItem => tagItem !== tag); 616 existingTags = existingTags.filter((tagItem) => tagItem !== tag);
605 awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); 617 awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
606 } 618 }
607 }); 619 });
@@ -611,4 +623,44 @@ function init(description) {
611 [...autocompleteFields].forEach((autocompleteField) => { 623 [...autocompleteFields].forEach((autocompleteField) => {
612 awesomepletes.push(createAwesompleteInstance(autocompleteField)); 624 awesomepletes.push(createAwesompleteInstance(autocompleteField));
613 }); 625 });
626
627 const exportForm = document.querySelector('#exportform');
628 if (exportForm != null) {
629 exportForm.addEventListener('submit', (event) => {
630 event.preventDefault();
631
632 refreshToken(basePath, () => {
633 event.target.submit();
634 });
635 });
636 }
637
638 const bulkCreationButton = document.querySelector('.addlink-batch-show-more-block');
639 if (bulkCreationButton != null) {
640 const toggleBulkCreationVisibility = (showMoreBlockElement, formElement) => {
641 if (bulkCreationButton.classList.contains('pure-u-0')) {
642 showMoreBlockElement.classList.remove('pure-u-0');
643 formElement.classList.add('pure-u-0');
644 } else {
645 showMoreBlockElement.classList.add('pure-u-0');
646 formElement.classList.remove('pure-u-0');
647 }
648 };
649
650 const bulkCreationForm = document.querySelector('.addlink-batch-form-block');
651
652 toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm);
653 bulkCreationButton.querySelector('a').addEventListener('click', (e) => {
654 e.preventDefault();
655 toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm);
656 });
657
658 // Force to send falsy value if the checkbox is not checked.
659 const privateButton = bulkCreationForm.querySelector('input[type="checkbox"][name="private"]');
660 const privateHiddenButton = bulkCreationForm.querySelector('input[type="hidden"][name="private"]');
661 privateButton.addEventListener('click', () => {
662 privateHiddenButton.disabled = !privateHiddenButton.disabled;
663 });
664 privateHiddenButton.disabled = privateButton.checked;
665 }
614})(); 666})();
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss
index 243ab1b2..a7f091e9 100644
--- a/assets/default/scss/shaarli.scss
+++ b/assets/default/scss/shaarli.scss
@@ -69,20 +69,22 @@ pre {
69 font-family: 'Roboto'; 69 font-family: 'Roboto';
70 font-weight: 400; 70 font-weight: 400;
71 font-style: normal; 71 font-style: normal;
72 src: local('Roboto'), 72 src:
73 local('Roboto-Regular'), 73 local('Roboto'),
74 url('../fonts/Roboto-Regular.woff2') format('woff2'), 74 local('Roboto-Regular'),
75 url('../fonts/Roboto-Regular.woff') format('woff'); 75 url('../fonts/Roboto-Regular.woff2') format('woff2'),
76 url('../fonts/Roboto-Regular.woff') format('woff');
76} 77}
77 78
78@font-face { 79@font-face {
79 font-family: 'Roboto'; 80 font-family: 'Roboto';
80 font-weight: 700; 81 font-weight: 700;
81 font-style: normal; 82 font-style: normal;
82 src: local('Roboto'), 83 src:
83 local('Roboto-Bold'), 84 local('Roboto'),
84 url('../fonts/Roboto-Bold.woff2') format('woff2'), 85 local('Roboto-Bold'),
85 url('../fonts/Roboto-Bold.woff') format('woff'); 86 url('../fonts/Roboto-Bold.woff2') format('woff2'),
87 url('../fonts/Roboto-Bold.woff') format('woff');
86} 88}
87 89
88body, 90body,
@@ -375,7 +377,7 @@ body,
375} 377}
376 378
377@media screen and (max-width: 64em) { 379@media screen and (max-width: 64em) {
378 .header-search , 380 .header-search,
379 .header-search * { 381 .header-search * {
380 visibility: hidden; 382 visibility: hidden;
381 } 383 }
@@ -490,6 +492,10 @@ body,
490 } 492 }
491} 493}
492 494
495.header-alert-message {
496 text-align: center;
497}
498
493// CONTENT - GENERAL 499// CONTENT - GENERAL
494.container { 500.container {
495 position: relative; 501 position: relative;
@@ -550,7 +556,6 @@ body,
550 color: $dark-grey; 556 color: $dark-grey;
551 font-size: .9em; 557 font-size: .9em;
552 558
553
554 a { 559 a {
555 display: inline-block; 560 display: inline-block;
556 margin: 3px 0; 561 margin: 3px 0;
@@ -612,6 +617,11 @@ body,
612 padding: 5px; 617 padding: 5px;
613 text-decoration: none; 618 text-decoration: none;
614 color: $dark-grey; 619 color: $dark-grey;
620
621 &.selected {
622 background: var(--main-color);
623 color: $white;
624 }
615 } 625 }
616 626
617 input { 627 input {
@@ -661,6 +671,10 @@ body,
661 content: ''; 671 content: '';
662 } 672 }
663 } 673 }
674
675 .search-highlight {
676 background-color: yellow;
677 }
664} 678}
665 679
666.linklist-item-buttons { 680.linklist-item-buttons {
@@ -1009,6 +1023,10 @@ body,
1009 &.button-red { 1023 &.button-red {
1010 background: $red; 1024 background: $red;
1011 } 1025 }
1026
1027 &.button-grey {
1028 background: $light-grey;
1029 }
1012 } 1030 }
1013 1031
1014 .submit-buttons { 1032 .submit-buttons {
@@ -1033,7 +1051,7 @@ body,
1033 } 1051 }
1034 1052
1035 table { 1053 table {
1036 margin: auto; 1054 margin: 10px auto 25px auto;
1037 width: 90%; 1055 width: 90%;
1038 1056
1039 .order { 1057 .order {
@@ -1069,6 +1087,11 @@ body,
1069 position: absolute; 1087 position: absolute;
1070 right: 5%; 1088 right: 5%;
1071 } 1089 }
1090
1091 &.button-grey {
1092 position: absolute;
1093 left: 5%;
1094 }
1072 } 1095 }
1073 } 1096 }
1074 } 1097 }
@@ -1259,6 +1282,57 @@ form {
1259 } 1282 }
1260} 1283}
1261 1284
1285.loading-input {
1286 position: relative;
1287
1288 @keyframes around {
1289 0% {
1290 transform: rotate(0deg);
1291 }
1292
1293 100% {
1294 transform: rotate(360deg);
1295 }
1296 }
1297
1298 .icon-container {
1299 position: absolute;
1300 right: 60px;
1301 top: calc(50% - 10px);
1302 }
1303
1304 .loader {
1305 position: relative;
1306 height: 20px;
1307 width: 20px;
1308 display: inline-block;
1309 animation: around 5.4s infinite;
1310
1311 &::after,
1312 &::before {
1313 content: "";
1314 background: $form-input-background;
1315 position: absolute;
1316 display: inline-block;
1317 width: 100%;
1318 height: 100%;
1319 border-width: 2px;
1320 border-color: #333 #333 transparent transparent;
1321 border-style: solid;
1322 border-radius: 20px;
1323 box-sizing: border-box;
1324 top: 0;
1325 left: 0;
1326 animation: around 0.7s ease-in-out infinite;
1327 }
1328
1329 &::after {
1330 animation: around 0.7s ease-in-out 0.1s infinite;
1331 background: transparent;
1332 }
1333 }
1334}
1335
1262// LOGIN 1336// LOGIN
1263.login-form-container { 1337.login-form-container {
1264 .remember-me { 1338 .remember-me {
@@ -1548,11 +1622,11 @@ form {
1548 text-align: center; 1622 text-align: center;
1549 1623
1550 a { 1624 a {
1625 background: $almost-white;
1551 display: inline-block; 1626 display: inline-block;
1552 margin: 0 15px; 1627 padding: 5px;
1553 text-decoration: none; 1628 text-decoration: none;
1554 color: $white; 1629 color: $dark-grey;
1555 font-weight: bold;
1556 } 1630 }
1557} 1631}
1558 1632
@@ -1600,13 +1674,14 @@ form {
1600 1674
1601 > div { 1675 > div {
1602 border-radius: 10px; 1676 border-radius: 10px;
1603 background: repeating-linear-gradient( 1677 background:
1604 -45deg, 1678 repeating-linear-gradient(
1605 $almost-white, 1679 -45deg,
1606 $almost-white 6px, 1680 $almost-white,
1607 var(--background-color) 6px, 1681 $almost-white 6px,
1608 var(--background-color) 12px 1682 var(--background-color) 6px,
1609 ); 1683 var(--background-color) 12px
1684 );
1610 width: 0%; 1685 width: 0%;
1611 height: 10px; 1686 height: 10px;
1612 } 1687 }
@@ -1630,6 +1705,123 @@ form {
1630 } 1705 }
1631} 1706}
1632 1707
1708// SERVER PAGE
1709
1710.server-tables-page,
1711.server-tables {
1712 .window-subtitle {
1713 &::before {
1714 display: block;
1715 margin: 8px auto;
1716 background: linear-gradient(to right, var(--background-color), $dark-grey, var(--background-color));
1717 width: 50%;
1718 height: 1px;
1719 content: '';
1720 }
1721 }
1722
1723 .server-row {
1724 p {
1725 height: 25px;
1726 padding: 0 10px;
1727 }
1728 }
1729
1730 .server-label {
1731 text-align: right;
1732 font-weight: bold;
1733 }
1734
1735 i {
1736 &.fa-color-green {
1737 color: $main-green;
1738 }
1739
1740 &.fa-color-orange {
1741 color: $orange;
1742 }
1743
1744 &.fa-color-red {
1745 color: $red;
1746 }
1747 }
1748
1749 @media screen and (max-width: 64em) {
1750 .server-label {
1751 text-align: center;
1752 }
1753
1754 .server-row {
1755 p {
1756 text-align: center;
1757 }
1758 }
1759 }
1760}
1761
1762// Batch creation
1763input[name='save_edit_batch'] {
1764 @extend %page-form-button;
1765}
1766
1767.addlink-batch-show-more {
1768 display: flex;
1769 align-items: center;
1770 margin: 20px 0 8px;
1771
1772 a {
1773 color: var(--main-color);
1774 text-decoration: none;
1775 }
1776
1777 &::before,
1778 &::after {
1779 content: "";
1780 flex-grow: 1;
1781 background: rgba(0, 0, 0, 0.35);
1782 height: 1px;
1783 font-size: 0;
1784 line-height: 0;
1785 }
1786
1787 &::before {
1788 margin: 0 16px 0 0;
1789 }
1790
1791 &::after {
1792 margin: 0 0 0 16px;
1793 }
1794}
1795
1796.dark-layer {
1797 display: none;
1798 position: fixed;
1799 height: 100%;
1800 width: 100%;
1801 z-index: 998;
1802 background-color: rgba(0, 0, 0, .75);
1803 color: #fff;
1804
1805 .screen-center {
1806 display: flex;
1807 flex-direction: column;
1808 justify-content: center;
1809 align-items: center;
1810 text-align: center;
1811 min-height: 100vh;
1812 }
1813
1814 .progressbar {
1815 width: 33%;
1816 }
1817}
1818
1819.addlink-batch-form-block {
1820 .pure-alert {
1821 margin: 25px 0 0 0;
1822 }
1823}
1824
1633// Print rules 1825// Print rules
1634@media print { 1826@media print {
1635 .shaarli-menu { 1827 .shaarli-menu {