diff options
Diffstat (limited to 'assets/default')
-rw-r--r-- | assets/default/js/base.js | 73 | ||||
-rw-r--r-- | assets/default/scss/shaarli.scss | 199 |
2 files changed, 247 insertions, 25 deletions
diff --git a/assets/default/js/base.js b/assets/default/js/base.js index be986ae0..dd532bb7 100644 --- a/assets/default/js/base.js +++ b/assets/default/js/base.js | |||
@@ -1,4 +1,5 @@ | |||
1 | import Awesomplete from 'awesomplete'; | 1 | import Awesomplete from 'awesomplete'; |
2 | import 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 |
@@ -41,19 +42,21 @@ function refreshToken(basePath, callback) { | |||
41 | xhr.send(); | 42 | xhr.send(); |
42 | } | 43 | } |
43 | 44 | ||
44 | function createAwesompleteInstance(element, tags = []) { | 45 | function createAwesompleteInstance(element, separator, tags = []) { |
45 | const awesome = new Awesomplete(Awesomplete.$(element)); | 46 | const awesome = new Awesomplete(Awesomplete.$(element)); |
46 | // Tags are separated by a space | 47 | |
47 | awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]); | 48 | // Tags are separated by separator |
49 | awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(new RegExp(`[^${separator}]*$`))[0]); | ||
48 | // Insert new selected tag in the input | 50 | // Insert new selected tag in the input |
49 | awesome.replace = (text) => { | 51 | awesome.replace = (text) => { |
50 | const before = awesome.input.value.match(/^.+ \s*|/)[0]; | 52 | const before = awesome.input.value.match(new RegExp(`^.+${separator}+|`))[0]; |
51 | awesome.input.value = `${before}${text} `; | 53 | awesome.input.value = `${before}${text}${separator}`; |
52 | }; | 54 | }; |
53 | // Highlight found items | 55 | // Highlight found items |
54 | awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(/[^ ]*$/)[0]); | 56 | awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${separator}]*$`))[0]); |
55 | // Don't display already selected items | 57 | // Don't display already selected items |
56 | const reg = /(\w+) /g; | 58 | // WARNING: pseudo classes does not seem to work with string litterals... |
59 | const reg = new RegExp(`([^${separator}]+)${separator}`, 'g'); | ||
57 | let match; | 60 | let match; |
58 | awesome.data = (item, input) => { | 61 | awesome.data = (item, input) => { |
59 | while ((match = reg.exec(input))) { | 62 | while ((match = reg.exec(input))) { |
@@ -77,13 +80,14 @@ function createAwesompleteInstance(element, tags = []) { | |||
77 | * @param selector CSS selector | 80 | * @param selector CSS selector |
78 | * @param tags Array of tags | 81 | * @param tags Array of tags |
79 | * @param instances List of existing awesomplete instances | 82 | * @param instances List of existing awesomplete instances |
83 | * @param separator Tags separator character | ||
80 | */ | 84 | */ |
81 | function updateAwesompleteList(selector, tags, instances) { | 85 | function updateAwesompleteList(selector, tags, instances, separator) { |
82 | if (instances.length === 0) { | 86 | if (instances.length === 0) { |
83 | // First load: create Awesomplete instances | 87 | // First load: create Awesomplete instances |
84 | const elements = document.querySelectorAll(selector); | 88 | const elements = document.querySelectorAll(selector); |
85 | [...elements].forEach((element) => { | 89 | [...elements].forEach((element) => { |
86 | instances.push(createAwesompleteInstance(element, tags)); | 90 | instances.push(createAwesompleteInstance(element, separator, tags)); |
87 | }); | 91 | }); |
88 | } else { | 92 | } else { |
89 | // Update awesomplete tag list | 93 | // Update awesomplete tag list |
@@ -96,15 +100,6 @@ function updateAwesompleteList(selector, tags, instances) { | |||
96 | } | 100 | } |
97 | 101 | ||
98 | /** | 102 | /** |
99 | * html_entities in JS | ||
100 | * | ||
101 | * @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript | ||
102 | */ | ||
103 | function htmlEntities(str) { | ||
104 | return str.replace(/[\u00A0-\u9999<>&]/gim, (i) => `&#${i.charCodeAt(0)};`); | ||
105 | } | ||
106 | |||
107 | /** | ||
108 | * Add the class 'hidden' to city options not attached to the current selected continent. | 103 | * Add the class 'hidden' to city options not attached to the current selected continent. |
109 | * | 104 | * |
110 | * @param cities List of <option> elements | 105 | * @param cities List of <option> elements |
@@ -222,6 +217,8 @@ function init(description) { | |||
222 | 217 | ||
223 | (() => { | 218 | (() => { |
224 | const basePath = document.querySelector('input[name="js_base_path"]').value; | 219 | const basePath = document.querySelector('input[name="js_base_path"]').value; |
220 | const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]'); | ||
221 | const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || ' ' : ' '; | ||
225 | 222 | ||
226 | /** | 223 | /** |
227 | * Handle responsive menu. | 224 | * Handle responsive menu. |
@@ -302,7 +299,8 @@ function init(description) { | |||
302 | const deleteLinks = document.querySelectorAll('.confirm-delete'); | 299 | const deleteLinks = document.querySelectorAll('.confirm-delete'); |
303 | [...deleteLinks].forEach((deleteLink) => { | 300 | [...deleteLinks].forEach((deleteLink) => { |
304 | deleteLink.addEventListener('click', (event) => { | 301 | deleteLink.addEventListener('click', (event) => { |
305 | if (!confirm(document.getElementById('translation-delete-link').innerHTML)) { | 302 | const type = event.currentTarget.getAttribute('data-type') || 'link'; |
303 | if (!confirm(document.getElementById(`translation-delete-${type}`).innerHTML)) { | ||
306 | event.preventDefault(); | 304 | event.preventDefault(); |
307 | } | 305 | } |
308 | }); | 306 | }); |
@@ -569,7 +567,7 @@ function init(description) { | |||
569 | input.setAttribute('name', totag); | 567 | input.setAttribute('name', totag); |
570 | input.setAttribute('value', totag); | 568 | input.setAttribute('value', totag); |
571 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; | 569 | findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none'; |
572 | block.querySelector('a.tag-link').innerHTML = htmlEntities(totag); | 570 | block.querySelector('a.tag-link').innerHTML = he.encode(totag); |
573 | block | 571 | block |
574 | .querySelector('a.tag-link') | 572 | .querySelector('a.tag-link') |
575 | .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`); | 573 | .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`); |
@@ -582,7 +580,7 @@ function init(description) { | |||
582 | 580 | ||
583 | // Refresh awesomplete values | 581 | // Refresh awesomplete values |
584 | existingTags = existingTags.map((tag) => (tag === fromtag ? totag : tag)); | 582 | existingTags = existingTags.map((tag) => (tag === fromtag ? totag : tag)); |
585 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | 583 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator); |
586 | } | 584 | } |
587 | }; | 585 | }; |
588 | xhr.send(`renametag=1&fromtag=${fromtagUrl}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); | 586 | xhr.send(`renametag=1&fromtag=${fromtagUrl}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`); |
@@ -622,14 +620,14 @@ function init(description) { | |||
622 | refreshToken(basePath); | 620 | refreshToken(basePath); |
623 | 621 | ||
624 | existingTags = existingTags.filter((tagItem) => tagItem !== tag); | 622 | existingTags = existingTags.filter((tagItem) => tagItem !== tag); |
625 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes); | 623 | awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator); |
626 | } | 624 | } |
627 | }); | 625 | }); |
628 | }); | 626 | }); |
629 | 627 | ||
630 | const autocompleteFields = document.querySelectorAll('input[data-multiple]'); | 628 | const autocompleteFields = document.querySelectorAll('input[data-multiple]'); |
631 | [...autocompleteFields].forEach((autocompleteField) => { | 629 | [...autocompleteFields].forEach((autocompleteField) => { |
632 | awesomepletes.push(createAwesompleteInstance(autocompleteField)); | 630 | awesomepletes.push(createAwesompleteInstance(autocompleteField, tagsSeparator)); |
633 | }); | 631 | }); |
634 | 632 | ||
635 | const exportForm = document.querySelector('#exportform'); | 633 | const exportForm = document.querySelector('#exportform'); |
@@ -642,4 +640,33 @@ function init(description) { | |||
642 | }); | 640 | }); |
643 | }); | 641 | }); |
644 | } | 642 | } |
643 | |||
644 | const bulkCreationButton = document.querySelector('.addlink-batch-show-more-block'); | ||
645 | if (bulkCreationButton != null) { | ||
646 | const toggleBulkCreationVisibility = (showMoreBlockElement, formElement) => { | ||
647 | if (bulkCreationButton.classList.contains('pure-u-0')) { | ||
648 | showMoreBlockElement.classList.remove('pure-u-0'); | ||
649 | formElement.classList.add('pure-u-0'); | ||
650 | } else { | ||
651 | showMoreBlockElement.classList.add('pure-u-0'); | ||
652 | formElement.classList.remove('pure-u-0'); | ||
653 | } | ||
654 | }; | ||
655 | |||
656 | const bulkCreationForm = document.querySelector('.addlink-batch-form-block'); | ||
657 | |||
658 | toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm); | ||
659 | bulkCreationButton.querySelector('a').addEventListener('click', (e) => { | ||
660 | e.preventDefault(); | ||
661 | toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm); | ||
662 | }); | ||
663 | |||
664 | // Force to send falsy value if the checkbox is not checked. | ||
665 | const privateButton = bulkCreationForm.querySelector('input[type="checkbox"][name="private"]'); | ||
666 | const privateHiddenButton = bulkCreationForm.querySelector('input[type="hidden"][name="private"]'); | ||
667 | privateButton.addEventListener('click', () => { | ||
668 | privateHiddenButton.disabled = !privateHiddenButton.disabled; | ||
669 | }); | ||
670 | privateHiddenButton.disabled = privateButton.checked; | ||
671 | } | ||
645 | })(); | 672 | })(); |
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss index a528adb0..cc8ccc1e 100644 --- a/assets/default/scss/shaarli.scss +++ b/assets/default/scss/shaarli.scss | |||
@@ -139,6 +139,16 @@ body, | |||
139 | } | 139 | } |
140 | } | 140 | } |
141 | 141 | ||
142 | .page-form, | ||
143 | .pure-alert { | ||
144 | code { | ||
145 | display: inline-block; | ||
146 | padding: 0 2px; | ||
147 | color: $dark-grey; | ||
148 | background-color: var(--background-color); | ||
149 | } | ||
150 | } | ||
151 | |||
142 | // Make pure-extras alert closable. | 152 | // Make pure-extras alert closable. |
143 | .pure-alert-closable { | 153 | .pure-alert-closable { |
144 | .fa-times { | 154 | .fa-times { |
@@ -671,6 +681,10 @@ body, | |||
671 | content: ''; | 681 | content: ''; |
672 | } | 682 | } |
673 | } | 683 | } |
684 | |||
685 | .search-highlight { | ||
686 | background-color: yellow; | ||
687 | } | ||
674 | } | 688 | } |
675 | 689 | ||
676 | .linklist-item-buttons { | 690 | .linklist-item-buttons { |
@@ -1019,6 +1033,10 @@ body, | |||
1019 | &.button-red { | 1033 | &.button-red { |
1020 | background: $red; | 1034 | background: $red; |
1021 | } | 1035 | } |
1036 | |||
1037 | &.button-grey { | ||
1038 | background: $light-grey; | ||
1039 | } | ||
1022 | } | 1040 | } |
1023 | 1041 | ||
1024 | .submit-buttons { | 1042 | .submit-buttons { |
@@ -1043,7 +1061,7 @@ body, | |||
1043 | } | 1061 | } |
1044 | 1062 | ||
1045 | table { | 1063 | table { |
1046 | margin: auto; | 1064 | margin: 10px auto 25px auto; |
1047 | width: 90%; | 1065 | width: 90%; |
1048 | 1066 | ||
1049 | .order { | 1067 | .order { |
@@ -1079,6 +1097,11 @@ body, | |||
1079 | position: absolute; | 1097 | position: absolute; |
1080 | right: 5%; | 1098 | right: 5%; |
1081 | } | 1099 | } |
1100 | |||
1101 | &.button-grey { | ||
1102 | position: absolute; | ||
1103 | left: 5%; | ||
1104 | } | ||
1082 | } | 1105 | } |
1083 | } | 1106 | } |
1084 | } | 1107 | } |
@@ -1253,11 +1276,15 @@ form { | |||
1253 | margin: 70px 0 25px; | 1276 | margin: 70px 0 25px; |
1254 | } | 1277 | } |
1255 | 1278 | ||
1279 | a { | ||
1280 | color: var(--main-color); | ||
1281 | } | ||
1282 | |||
1256 | pre { | 1283 | pre { |
1257 | margin: 0 20%; | 1284 | margin: 0 20%; |
1258 | padding: 20px 0; | 1285 | padding: 20px 0; |
1259 | text-align: left; | 1286 | text-align: left; |
1260 | line-height: .7em; | 1287 | line-height: 1em; |
1261 | } | 1288 | } |
1262 | } | 1289 | } |
1263 | 1290 | ||
@@ -1269,6 +1296,57 @@ form { | |||
1269 | } | 1296 | } |
1270 | } | 1297 | } |
1271 | 1298 | ||
1299 | .loading-input { | ||
1300 | position: relative; | ||
1301 | |||
1302 | @keyframes around { | ||
1303 | 0% { | ||
1304 | transform: rotate(0deg); | ||
1305 | } | ||
1306 | |||
1307 | 100% { | ||
1308 | transform: rotate(360deg); | ||
1309 | } | ||
1310 | } | ||
1311 | |||
1312 | .icon-container { | ||
1313 | position: absolute; | ||
1314 | right: 60px; | ||
1315 | top: calc(50% - 10px); | ||
1316 | } | ||
1317 | |||
1318 | .loader { | ||
1319 | position: relative; | ||
1320 | height: 20px; | ||
1321 | width: 20px; | ||
1322 | display: inline-block; | ||
1323 | animation: around 5.4s infinite; | ||
1324 | |||
1325 | &::after, | ||
1326 | &::before { | ||
1327 | content: ""; | ||
1328 | background: $form-input-background; | ||
1329 | position: absolute; | ||
1330 | display: inline-block; | ||
1331 | width: 100%; | ||
1332 | height: 100%; | ||
1333 | border-width: 2px; | ||
1334 | border-color: #333 #333 transparent transparent; | ||
1335 | border-style: solid; | ||
1336 | border-radius: 20px; | ||
1337 | box-sizing: border-box; | ||
1338 | top: 0; | ||
1339 | left: 0; | ||
1340 | animation: around 0.7s ease-in-out infinite; | ||
1341 | } | ||
1342 | |||
1343 | &::after { | ||
1344 | animation: around 0.7s ease-in-out 0.1s infinite; | ||
1345 | background: transparent; | ||
1346 | } | ||
1347 | } | ||
1348 | } | ||
1349 | |||
1272 | // LOGIN | 1350 | // LOGIN |
1273 | .login-form-container { | 1351 | .login-form-container { |
1274 | .remember-me { | 1352 | .remember-me { |
@@ -1641,6 +1719,123 @@ form { | |||
1641 | } | 1719 | } |
1642 | } | 1720 | } |
1643 | 1721 | ||
1722 | // SERVER PAGE | ||
1723 | |||
1724 | .server-tables-page, | ||
1725 | .server-tables { | ||
1726 | .window-subtitle { | ||
1727 | &::before { | ||
1728 | display: block; | ||
1729 | margin: 8px auto; | ||
1730 | background: linear-gradient(to right, var(--background-color), $dark-grey, var(--background-color)); | ||
1731 | width: 50%; | ||
1732 | height: 1px; | ||
1733 | content: ''; | ||
1734 | } | ||
1735 | } | ||
1736 | |||
1737 | .server-row { | ||
1738 | p { | ||
1739 | height: 25px; | ||
1740 | padding: 0 10px; | ||
1741 | } | ||
1742 | } | ||
1743 | |||
1744 | .server-label { | ||
1745 | text-align: right; | ||
1746 | font-weight: bold; | ||
1747 | } | ||
1748 | |||
1749 | i { | ||
1750 | &.fa-color-green { | ||
1751 | color: $main-green; | ||
1752 | } | ||
1753 | |||
1754 | &.fa-color-orange { | ||
1755 | color: $orange; | ||
1756 | } | ||
1757 | |||
1758 | &.fa-color-red { | ||
1759 | color: $red; | ||
1760 | } | ||
1761 | } | ||
1762 | |||
1763 | @media screen and (max-width: 64em) { | ||
1764 | .server-label { | ||
1765 | text-align: center; | ||
1766 | } | ||
1767 | |||
1768 | .server-row { | ||
1769 | p { | ||
1770 | text-align: center; | ||
1771 | } | ||
1772 | } | ||
1773 | } | ||
1774 | } | ||
1775 | |||
1776 | // Batch creation | ||
1777 | input[name='save_edit_batch'] { | ||
1778 | @extend %page-form-button; | ||
1779 | } | ||
1780 | |||
1781 | .addlink-batch-show-more { | ||
1782 | display: flex; | ||
1783 | align-items: center; | ||
1784 | margin: 20px 0 8px; | ||
1785 | |||
1786 | a { | ||
1787 | color: var(--main-color); | ||
1788 | text-decoration: none; | ||
1789 | } | ||
1790 | |||
1791 | &::before, | ||
1792 | &::after { | ||
1793 | content: ""; | ||
1794 | flex-grow: 1; | ||
1795 | background: rgba(0, 0, 0, 0.35); | ||
1796 | height: 1px; | ||
1797 | font-size: 0; | ||
1798 | line-height: 0; | ||
1799 | } | ||
1800 | |||
1801 | &::before { | ||
1802 | margin: 0 16px 0 0; | ||
1803 | } | ||
1804 | |||
1805 | &::after { | ||
1806 | margin: 0 0 0 16px; | ||
1807 | } | ||
1808 | } | ||
1809 | |||
1810 | .dark-layer { | ||
1811 | display: none; | ||
1812 | position: fixed; | ||
1813 | height: 100%; | ||
1814 | width: 100%; | ||
1815 | z-index: 998; | ||
1816 | background-color: rgba(0, 0, 0, .75); | ||
1817 | color: #fff; | ||
1818 | |||
1819 | .screen-center { | ||
1820 | display: flex; | ||
1821 | flex-direction: column; | ||
1822 | justify-content: center; | ||
1823 | align-items: center; | ||
1824 | text-align: center; | ||
1825 | min-height: 100vh; | ||
1826 | } | ||
1827 | |||
1828 | .progressbar { | ||
1829 | width: 33%; | ||
1830 | } | ||
1831 | } | ||
1832 | |||
1833 | .addlink-batch-form-block { | ||
1834 | .pure-alert { | ||
1835 | margin: 25px 0 0 0; | ||
1836 | } | ||
1837 | } | ||
1838 | |||
1644 | // Print rules | 1839 | // Print rules |
1645 | @media print { | 1840 | @media print { |
1646 | .shaarli-menu { | 1841 | .shaarli-menu { |