aboutsummaryrefslogtreecommitdiffhomepage
path: root/assets/default
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2018-07-28 11:19:53 +0200
committerArthurHoaro <arthur@hoa.ro>2018-07-28 11:19:53 +0200
commit7982c3ff183aa985177bdaeacda4feb22a739e00 (patch)
tree728e07251072f3a1df63c017c0dce54fa1acb390 /assets/default
parent2075321f6569dfa610905991b315aae1956b7f78 (diff)
parented7e1be12d65bdb1b924c7efb6a84fd591192c6c (diff)
downloadShaarli-7982c3ff183aa985177bdaeacda4feb22a739e00.tar.gz
Shaarli-7982c3ff183aa985177bdaeacda4feb22a739e00.tar.zst
Shaarli-7982c3ff183aa985177bdaeacda4feb22a739e00.zip
Merge tag 'v0.10.0' into latest
Release v0.10.0
Diffstat (limited to 'assets/default')
-rw-r--r--assets/default/fonts/Roboto-Bold.woffbin0 -> 89584 bytes
-rw-r--r--assets/default/fonts/Roboto-Bold.woff2bin0 -> 63320 bytes
-rw-r--r--assets/default/fonts/Roboto-Regular.woffbin0 -> 89732 bytes
-rw-r--r--assets/default/fonts/Roboto-Regular.woff2bin0 -> 63412 bytes
-rw-r--r--assets/default/img/apple-touch-icon.pngbin0 -> 18276 bytes
-rw-r--r--assets/default/img/favicon.pngbin0 -> 41600 bytes
-rw-r--r--assets/default/img/icon.pngbin0 -> 530 bytes
-rw-r--r--assets/default/img/sad_star.pngbin0 -> 7099 bytes
-rw-r--r--assets/default/js/base.js606
-rw-r--r--assets/default/js/plugins-admin.js81
-rw-r--r--assets/default/scss/shaarli.scss1592
11 files changed, 2279 insertions, 0 deletions
diff --git a/assets/default/fonts/Roboto-Bold.woff b/assets/default/fonts/Roboto-Bold.woff
new file mode 100644
index 00000000..3d86753b
--- /dev/null
+++ b/assets/default/fonts/Roboto-Bold.woff
Binary files differ
diff --git a/assets/default/fonts/Roboto-Bold.woff2 b/assets/default/fonts/Roboto-Bold.woff2
new file mode 100644
index 00000000..bd05e2ea
--- /dev/null
+++ b/assets/default/fonts/Roboto-Bold.woff2
Binary files differ
diff --git a/assets/default/fonts/Roboto-Regular.woff b/assets/default/fonts/Roboto-Regular.woff
new file mode 100644
index 00000000..464d2062
--- /dev/null
+++ b/assets/default/fonts/Roboto-Regular.woff
Binary files differ
diff --git a/assets/default/fonts/Roboto-Regular.woff2 b/assets/default/fonts/Roboto-Regular.woff2
new file mode 100644
index 00000000..f9661967
--- /dev/null
+++ b/assets/default/fonts/Roboto-Regular.woff2
Binary files differ
diff --git a/assets/default/img/apple-touch-icon.png b/assets/default/img/apple-touch-icon.png
new file mode 100644
index 00000000..f29210ce
--- /dev/null
+++ b/assets/default/img/apple-touch-icon.png
Binary files differ
diff --git a/assets/default/img/favicon.png b/assets/default/img/favicon.png
new file mode 100644
index 00000000..4644321b
--- /dev/null
+++ b/assets/default/img/favicon.png
Binary files differ
diff --git a/assets/default/img/icon.png b/assets/default/img/icon.png
new file mode 100644
index 00000000..474edec3
--- /dev/null
+++ b/assets/default/img/icon.png
Binary files differ
diff --git a/assets/default/img/sad_star.png b/assets/default/img/sad_star.png
new file mode 100644
index 00000000..ed3bd158
--- /dev/null
+++ b/assets/default/img/sad_star.png
Binary files differ
diff --git a/assets/default/js/base.js b/assets/default/js/base.js
new file mode 100644
index 00000000..5cf037c2
--- /dev/null
+++ b/assets/default/js/base.js
@@ -0,0 +1,606 @@
1import Awesomplete from 'awesomplete';
2
3/**
4 * Find a parent element according to its tag and its attributes
5 *
6 * @param element Element where to start the search
7 * @param tagName Expected parent tag name
8 * @param attributes Associative array of expected attributes (name=>value).
9 *
10 * @returns Found element or null.
11 */
12function findParent(element, tagName, attributes) {
13 const parentMatch = key => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1;
14 while (element) {
15 if (element.tagName.toLowerCase() === tagName) {
16 if (Object.keys(attributes).find(parentMatch)) {
17 return element;
18 }
19 }
20 element = element.parentElement;
21 }
22 return null;
23}
24
25/**
26 * Ajax request to refresh the CSRF token.
27 */
28function refreshToken() {
29 const xhr = new XMLHttpRequest();
30 xhr.open('GET', '?do=token');
31 xhr.onload = () => {
32 const token = document.getElementById('token');
33 token.setAttribute('value', xhr.responseText);
34 };
35 xhr.send();
36}
37
38function createAwesompleteInstance(element, tags = []) {
39 const awesome = new Awesomplete(Awesomplete.$(element));
40 // Tags are separated by a space
41 awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
42 // Insert new selected tag in the input
43 awesome.replace = (text) => {
44 const before = awesome.input.value.match(/^.+ \s*|/)[0];
45 awesome.input.value = `${before}${text} `;
46 };
47 // Highlight found items
48 awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(/[^ ]*$/)[0]);
49 // Don't display already selected items
50 const reg = /(\w+) /g;
51 let match;
52 awesome.data = (item, input) => {
53 while ((match = reg.exec(input))) {
54 if (item === match[1]) {
55 return '';
56 }
57 }
58 return item;
59 };
60 awesome.minChars = 1;
61 if (tags.length) {
62 awesome.list = tags;
63 }
64
65 return awesome;
66}
67
68/**
69 * Update awesomplete list of tag for all elements matching the given selector
70 *
71 * @param selector CSS selector
72 * @param tags Array of tags
73 * @param instances List of existing awesomplete instances
74 */
75function updateAwesompleteList(selector, tags, instances) {
76 if (instances.length === 0) {
77 // First load: create Awesomplete instances
78 const elements = document.querySelectorAll(selector);
79 [...elements].forEach((element) => {
80 instances.push(createAwesompleteInstance(element, tags));
81 });
82 } else {
83 // Update awesomplete tag list
84 instances.map((item) => {
85 item.list = tags;
86 return item;
87 });
88 }
89 return instances;
90}
91
92/**
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
101function activateFirefoxSocial(node) {
102 const loc = location.href;
103 const baseURL = loc.substring(0, loc.lastIndexOf('/') + 1);
104
105 const data = {
106 name: document.title,
107 description: document.getElementById('translation-delete-link').innerHTML,
108 author: 'Shaarli',
109 version: '1.0.0',
110
111 iconURL: `${baseURL}/images/favicon.ico`,
112 icon32URL: `${baseURL}/images/favicon.ico`,
113 icon64URL: `${baseURL}/images/favicon.ico`,
114
115 shareURL: `${baseURL}?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi`,
116 homepageURL: baseURL,
117 };
118 node.setAttribute('data-service', JSON.stringify(data));
119
120 const activate = new CustomEvent('ActivateSocialFeature');
121 node.dispatchEvent(activate);
122}
123
124/**
125 * Add the class 'hidden' to city options not attached to the current selected continent.
126 *
127 * @param cities List of <option> elements
128 * @param currentContinent Current selected continent
129 * @param reset Set to true to reset the selected value
130 */
131function hideTimezoneCities(cities, currentContinent, reset = null) {
132 let first = true;
133 if (reset == null) {
134 reset = false;
135 }
136 [...cities].forEach((option) => {
137 if (option.getAttribute('data-continent') !== currentContinent) {
138 option.className = 'hidden';
139 } else {
140 option.className = '';
141 if (reset === true && first === true) {
142 option.setAttribute('selected', 'selected');
143 first = false;
144 }
145 }
146 });
147}
148
149/**
150 * Retrieve an element up in the tree from its class name.
151 */
152function getParentByClass(el, className) {
153 const p = el.parentNode;
154 if (p == null || p.classList.contains(className)) {
155 return p;
156 }
157 return getParentByClass(p, className);
158}
159
160function toggleHorizontal() {
161 [...document.getElementById('shaarli-menu').querySelectorAll('.menu-transform')].forEach((el) => {
162 el.classList.toggle('pure-menu-horizontal');
163 });
164}
165
166function toggleMenu(menu) {
167 // set timeout so that the panel has a chance to roll up
168 // before the menu switches states
169 if (menu.classList.contains('open')) {
170 setTimeout(toggleHorizontal, 500);
171 } else {
172 toggleHorizontal();
173 }
174 menu.classList.toggle('open');
175 document.getElementById('menu-toggle').classList.toggle('x');
176}
177
178function closeMenu(menu) {
179 if (menu.classList.contains('open')) {
180 toggleMenu(menu);
181 }
182}
183
184function toggleFold(button, description, thumb) {
185 // Switch fold/expand - up = fold
186 if (button.classList.contains('fa-chevron-up')) {
187 button.title = document.getElementById('translation-expand').innerHTML;
188 if (description != null) {
189 description.style.display = 'none';
190 }
191 if (thumb != null) {
192 thumb.style.display = 'none';
193 }
194 } else {
195 button.title = document.getElementById('translation-fold').innerHTML;
196 if (description != null) {
197 description.style.display = 'block';
198 }
199 if (thumb != null) {
200 thumb.style.display = 'block';
201 }
202 }
203 button.classList.toggle('fa-chevron-down');
204 button.classList.toggle('fa-chevron-up');
205}
206
207function removeClass(element, classname) {
208 element.className = element.className.replace(new RegExp(`(?:^|\\s)${classname}(?:\\s|$)`), ' ');
209}
210
211function init(description) {
212 function resize() {
213 /* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
214 const scrollTop = window.pageYOffset ||
215 (document.documentElement || document.body.parentNode || document.body).scrollTop;
216
217 description.style.height = 'auto';
218 description.style.height = `${description.scrollHeight + 10}px`;
219
220 window.scrollTo(0, scrollTop);
221 }
222
223 /* 0-timeout to get the already changed text */
224 function delayedResize() {
225 window.setTimeout(resize, 0);
226 }
227
228 const observe = (element, event, handler) => {
229 element.addEventListener(event, handler, false);
230 };
231 observe(description, 'change', resize);
232 observe(description, 'cut', delayedResize);
233 observe(description, 'paste', delayedResize);
234 observe(description, 'drop', delayedResize);
235 observe(description, 'keydown', delayedResize);
236
237 resize();
238}
239
240(() => {
241 /**
242 * Handle responsive menu.
243 * Source: http://purecss.io/layouts/tucked-menu-vertical/
244 */
245 const menu = document.getElementById('shaarli-menu');
246 const WINDOW_CHANGE_EVENT = ('onorientationchange' in window) ? 'orientationchange' : 'resize';
247
248 const menuToggle = document.getElementById('menu-toggle');
249 if (menuToggle != null) {
250 menuToggle.addEventListener('click', () => toggleMenu(menu));
251 }
252
253 window.addEventListener(WINDOW_CHANGE_EVENT, () => closeMenu(menu));
254
255 /**
256 * Fold/Expand shaares description and thumbnail.
257 */
258 const foldAllButtons = document.getElementsByClassName('fold-all');
259 const foldButtons = document.getElementsByClassName('fold-button');
260
261 [...foldButtons].forEach((foldButton) => {
262 // Retrieve description
263 let description = null;
264 let thumbnail = null;
265 const linklistItem = getParentByClass(foldButton, 'linklist-item');
266 if (linklistItem != null) {
267 description = linklistItem.querySelector('.linklist-item-description');
268 thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
269 if (description != null || thumbnail != null) {
270 foldButton.style.display = 'inline';
271 }
272 }
273
274 foldButton.addEventListener('click', (event) => {
275 event.preventDefault();
276 toggleFold(event.target, description, thumbnail);
277 });
278 });
279
280 if (foldAllButtons != null) {
281 [].forEach.call(foldAllButtons, (foldAllButton) => {
282 foldAllButton.addEventListener('click', (event) => {
283 event.preventDefault();
284 const state = foldAllButton.firstElementChild.getAttribute('class').indexOf('down') !== -1 ? 'down' : 'up';
285 [].forEach.call(foldButtons, (foldButton) => {
286 if ((foldButton.firstElementChild.classList.contains('fa-chevron-up') && state === 'down')
287 || (foldButton.firstElementChild.classList.contains('fa-chevron-down') && state === 'up')
288 ) {
289 return;
290 }
291 // Retrieve description
292 let description = null;
293 let thumbnail = null;
294 const linklistItem = getParentByClass(foldButton, 'linklist-item');
295 if (linklistItem != null) {
296 description = linklistItem.querySelector('.linklist-item-description');
297 thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
298 if (description != null || thumbnail != null) {
299 foldButton.style.display = 'inline';
300 }
301 }
302
303 toggleFold(foldButton.firstElementChild, description, thumbnail);
304 });
305 foldAllButton.firstElementChild.classList.toggle('fa-chevron-down');
306 foldAllButton.firstElementChild.classList.toggle('fa-chevron-up');
307 foldAllButton.title = state === 'down'
308 ? document.getElementById('translation-fold-all').innerHTML
309 : document.getElementById('translation-expand-all').innerHTML;
310 });
311 });
312 }
313
314 /**
315 * Confirmation message before deletion.
316 */
317 const deleteLinks = document.querySelectorAll('.confirm-delete');
318 [...deleteLinks].forEach((deleteLink) => {
319 deleteLink.addEventListener('click', (event) => {
320 if (!confirm(document.getElementById('translation-delete-link').innerHTML)) {
321 event.preventDefault();
322 }
323 });
324 });
325
326 /**
327 * Close alerts
328 */
329 const closeLinks = document.querySelectorAll('.pure-alert-close');
330 [...closeLinks].forEach((closeLink) => {
331 closeLink.addEventListener('click', (event) => {
332 const alert = getParentByClass(event.target, 'pure-alert-closable');
333 alert.style.display = 'none';
334 });
335 });
336
337 /**
338 * New version dismiss.
339 * Hide the message for one week using localStorage.
340 */
341 const newVersionDismiss = document.getElementById('new-version-dismiss');
342 const newVersionMessage = document.querySelector('.new-version-message');
343 if (newVersionMessage != null
344 && localStorage.getItem('newVersionDismiss') != null
345 && parseInt(localStorage.getItem('newVersionDismiss'), 10) + (7 * 24 * 60 * 60 * 1000) > (new Date()).getTime()
346 ) {
347 newVersionMessage.style.display = 'none';
348 }
349 if (newVersionDismiss != null) {
350 newVersionDismiss.addEventListener('click', () => {
351 localStorage.setItem('newVersionDismiss', (new Date()).getTime().toString());
352 });
353 }
354
355 const hiddenReturnurl = document.getElementsByName('returnurl');
356 if (hiddenReturnurl != null) {
357 hiddenReturnurl.value = window.location.href;
358 }
359
360 /**
361 * Autofocus text fields
362 */
363 const autofocusElements = document.querySelectorAll('.autofocus');
364 let breakLoop = false;
365 [].forEach.call(autofocusElements, (autofocusElement) => {
366 if (autofocusElement.value === '' && !breakLoop) {
367 autofocusElement.focus();
368 breakLoop = true;
369 }
370 });
371
372 /**
373 * Handle sub menus/forms
374 */
375 const openers = document.getElementsByClassName('subheader-opener');
376 if (openers != null) {
377 [...openers].forEach((opener) => {
378 opener.addEventListener('click', (event) => {
379 event.preventDefault();
380
381 const id = opener.getAttribute('data-open-id');
382 const sub = document.getElementById(id);
383
384 if (sub != null) {
385 [...document.getElementsByClassName('subheader-form')].forEach((element) => {
386 if (element !== sub) {
387 removeClass(element, 'open');
388 }
389 });
390
391 sub.classList.toggle('open');
392 }
393 });
394 });
395 }
396
397 /**
398 * Remove CSS target padding (for fixed bar)
399 */
400 if (location.hash !== '') {
401 const anchor = document.getElementById(location.hash.substr(1));
402 if (anchor != null) {
403 const padsize = anchor.clientHeight;
404 window.scroll(0, window.scrollY - padsize);
405 anchor.style.paddingTop = '0';
406 }
407 }
408
409 /**
410 * Text area resizer
411 */
412 const description = document.getElementById('lf_description');
413
414 if (description != null) {
415 init(description);
416 // Submit editlink form with CTRL + Enter in the text area.
417 description.addEventListener('keydown', (event) => {
418 if (event.ctrlKey && event.keyCode === 13) {
419 document.getElementById('button-save-edit').click();
420 }
421 });
422 }
423
424 /**
425 * Bookmarklet alert
426 */
427 const bookmarkletLinks = document.querySelectorAll('.bookmarklet-link');
428 const bkmMessage = document.getElementById('bookmarklet-alert');
429 [].forEach.call(bookmarkletLinks, (link) => {
430 link.addEventListener('click', (event) => {
431 event.preventDefault();
432 alert(bkmMessage.value);
433 });
434 });
435
436 /**
437 * Firefox Social
438 */
439 const ffButton = document.getElementById('ff-social-button');
440 if (ffButton != null) {
441 ffButton.addEventListener('click', (event) => {
442 activateFirefoxSocial(event.target);
443 });
444 }
445
446 const continent = document.getElementById('continent');
447 const city = document.getElementById('city');
448 if (continent != null && city != null) {
449 continent.addEventListener('change', () => {
450 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
451 });
452 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
453 }
454
455 /**
456 * Bulk actions
457 */
458 const linkCheckboxes = document.querySelectorAll('.delete-checkbox');
459 const bar = document.getElementById('actions');
460 [...linkCheckboxes].forEach((checkbox) => {
461 checkbox.style.display = 'inline-block';
462 checkbox.addEventListener('click', () => {
463 const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
464 const count = [...linkCheckedCheckboxes].length;
465 if (count === 0 && bar.classList.contains('open')) {
466 bar.classList.toggle('open');
467 } else if (count > 0 && !bar.classList.contains('open')) {
468 bar.classList.toggle('open');
469 }
470 });
471 });
472
473 const deleteButton = document.getElementById('actions-delete');
474 const token = document.getElementById('token');
475 if (deleteButton != null && token != null) {
476 deleteButton.addEventListener('click', (event) => {
477 event.preventDefault();
478
479 const links = [];
480 const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
481 [...linkCheckedCheckboxes].forEach((checkbox) => {
482 links.push({
483 id: checkbox.value,
484 title: document.querySelector(`.linklist-item[data-id="${checkbox.value}"] .linklist-link`).innerHTML,
485 });
486 });
487
488 let message = `Are you sure you want to delete ${links.length} links?\n`;
489 message += 'This action is IRREVERSIBLE!\n\nTitles:\n';
490 const ids = [];
491 links.forEach((item) => {
492 message += ` - ${item.title}\n`;
493 ids.push(item.id);
494 });
495
496 if (window.confirm(message)) {
497 window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`;
498 }
499 });
500 }
501
502 /**
503 * Tag list operations
504 *
505 * TODO: support error code in the backend for AJAX requests
506 */
507 const tagList = document.querySelector('input[name="taglist"]');
508 let existingTags = tagList ? tagList.value.split(' ') : [];
509 let awesomepletes = [];
510
511 // Display/Hide rename form
512 const renameTagButtons = document.querySelectorAll('.rename-tag');
513 [...renameTagButtons].forEach((rename) => {
514 rename.addEventListener('click', (event) => {
515 event.preventDefault();
516 const block = findParent(event.target, 'div', { class: 'tag-list-item' });
517 const form = block.querySelector('.rename-tag-form');
518 if (form.style.display === 'none' || form.style.display === '') {
519 form.style.display = 'block';
520 } else {
521 form.style.display = 'none';
522 }
523 block.querySelector('input').focus();
524 });
525 });
526
527 // Rename a tag with an AJAX request
528 const renameTagSubmits = document.querySelectorAll('.validate-rename-tag');
529 [...renameTagSubmits].forEach((rename) => {
530 rename.addEventListener('click', (event) => {
531 event.preventDefault();
532 const block = findParent(event.target, 'div', { class: 'tag-list-item' });
533 const input = block.querySelector('.rename-tag-input');
534 const totag = input.value.replace('/"/g', '\\"');
535 if (totag.trim() === '') {
536 return;
537 }
538 const refreshedToken = document.getElementById('token').value;
539 const fromtag = block.getAttribute('data-tag');
540 const xhr = new XMLHttpRequest();
541 xhr.open('POST', '?do=changetag');
542 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
543 xhr.onload = () => {
544 if (xhr.status !== 200) {
545 alert(`An error occurred. Return code: ${xhr.status}`);
546 location.reload();
547 } else {
548 block.setAttribute('data-tag', totag);
549 input.setAttribute('name', totag);
550 input.setAttribute('value', totag);
551 findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none';
552 block.querySelector('a.tag-link').innerHTML = htmlEntities(totag);
553 block.querySelector('a.tag-link').setAttribute('href', `?searchtags=${encodeURIComponent(totag)}`);
554 block.querySelector('a.rename-tag').setAttribute('href', `?do=changetag&fromtag=${encodeURIComponent(totag)}`);
555
556 // Refresh awesomplete values
557 existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag));
558 awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
559 }
560 };
561 xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
562 refreshToken();
563 });
564 });
565
566 // Validate input with enter key
567 const renameTagInputs = document.querySelectorAll('.rename-tag-input');
568 [...renameTagInputs].forEach((rename) => {
569 rename.addEventListener('keypress', (event) => {
570 if (event.keyCode === 13) { // enter
571 findParent(event.target, 'div', { class: 'tag-list-item' }).querySelector('.validate-rename-tag').click();
572 }
573 });
574 });
575
576 // Delete a tag with an AJAX query (alert popup confirmation)
577 const deleteTagButtons = document.querySelectorAll('.delete-tag');
578 [...deleteTagButtons].forEach((rename) => {
579 rename.style.display = 'inline';
580 rename.addEventListener('click', (event) => {
581 event.preventDefault();
582 const block = findParent(event.target, 'div', { class: 'tag-list-item' });
583 const tag = block.getAttribute('data-tag');
584 const refreshedToken = document.getElementById('token');
585
586 if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
587 const xhr = new XMLHttpRequest();
588 xhr.open('POST', '?do=changetag');
589 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
590 xhr.onload = () => {
591 block.remove();
592 };
593 xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`));
594 refreshToken();
595
596 existingTags = existingTags.filter(tagItem => tagItem !== tag);
597 awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
598 }
599 });
600 });
601
602 const autocompleteFields = document.querySelectorAll('input[data-multiple]');
603 [...autocompleteFields].forEach((autocompleteField) => {
604 awesomepletes.push(createAwesompleteInstance(autocompleteField));
605 });
606})();
diff --git a/assets/default/js/plugins-admin.js b/assets/default/js/plugins-admin.js
new file mode 100644
index 00000000..46df4a3c
--- /dev/null
+++ b/assets/default/js/plugins-admin.js
@@ -0,0 +1,81 @@
1/**
2 * Change the position counter of a row.
3 *
4 * @param elem Element Node to change.
5 * @param toPos int New position.
6 */
7function changePos(elem, toPos) {
8 const elemName = elem.getAttribute('data-line');
9 elem.setAttribute('data-order', toPos);
10 const hiddenInput = document.querySelector(`[name="order_${elemName}"]`);
11 hiddenInput.setAttribute('value', toPos);
12}
13
14/**
15 * Move a row up or down.
16 *
17 * @param pos Element Node to move.
18 * @param move int Move: +1 (down) or -1 (up)
19 */
20function changeOrder(pos, move) {
21 const newpos = parseInt(pos, 10) + move;
22 let lines = document.querySelectorAll(`[data-order="${pos}"]`);
23 const changelines = document.querySelectorAll(`[data-order="${newpos}"]`);
24
25 // If we go down reverse lines to preserve the rows order
26 if (move > 0) {
27 lines = [].slice.call(lines).reverse();
28 }
29
30 for (let i = 0; i < lines.length; i += 1) {
31 const parent = changelines[0].parentNode;
32 changePos(lines[i], newpos);
33 changePos(changelines[i], parseInt(pos, 10));
34 const changeItem = move < 0 ? changelines[0] : changelines[changelines.length - 1].nextSibling;
35 parent.insertBefore(lines[i], changeItem);
36 }
37}
38
39/**
40 * Move a row up in the table.
41 *
42 * @param pos int row counter.
43 *
44 * @return false
45 */
46function orderUp(pos) {
47 if (pos !== 0) {
48 changeOrder(pos, -1);
49 }
50}
51
52/**
53 * Move a row down in the table.
54 *
55 * @param pos int row counter.
56 *
57 * @returns false
58 */
59function orderDown(pos) {
60 const lastpos = parseInt(document.querySelector('[data-order]:last-child').getAttribute('data-order'), 10);
61 if (pos !== lastpos) {
62 changeOrder(pos, 1);
63 }
64}
65
66(() => {
67 /**
68 * Plugin admin order
69 */
70 const orderPA = document.querySelectorAll('.order');
71 [...orderPA].forEach((link) => {
72 link.addEventListener('click', (event) => {
73 event.preventDefault();
74 if (event.target.classList.contains('order-up')) {
75 orderUp(parseInt(event.target.parentNode.parentNode.getAttribute('data-order'), 10));
76 } else if (event.target.classList.contains('order-down')) {
77 orderDown(parseInt(event.target.parentNode.parentNode.getAttribute('data-order'), 10));
78 }
79 });
80 });
81})();
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss
new file mode 100644
index 00000000..6b286f1e
--- /dev/null
+++ b/assets/default/scss/shaarli.scss
@@ -0,0 +1,1592 @@
1$fa-font-path: '~font-awesome/fonts';
2
3@import '~font-awesome/scss/font-awesome';
4@import '~purecss/build/pure.css';
5@import '~purecss/build/grids-responsive.css';
6@import '~pure-extras/css/pure-extras.css';
7@import '~awesomplete/awesomplete.css';
8
9$white: #fff;
10$black: #000;
11$almost-white: #f5f5f5;
12$dark-grey: #252525;
13$light-grey: #797979;
14$main-green: #1b926c;
15$light-green: #b0ddce;
16$dark-green: #2a4c41;
17$red: #ac2925;
18$orange: #f89406;
19$blue: #0b5ea6;
20$background-color: #d0d0d0;
21$background-linklist-info: #ddd;
22$light-shadow: rgba(255, 255, 255, .078);
23$dark-shadow: rgba(0, 0, 0, .298);
24$warning-text: #97600d;
25$form-input-border: #d8d8d8;
26$form-input-background: #eee;
27
28// General
29body {
30 background: $background-color;
31}
32
33.strong {
34 font-weight: bold;
35}
36
37.clear {
38 clear: both;
39}
40
41.center {
42 margin: auto;
43 text-align: center;
44}
45
46.label {
47 display: inline-block;
48 border-radius: .25rem;
49 padding: .25em .4em;
50 vertical-align: baseline;
51 text-align: center;
52 line-height: 1;
53 white-space: nowrap;
54 font-size: 75%;
55 font-weight: 700;
56}
57
58pre {
59 max-width: 100%;
60}
61
62@font-face {
63 font-family: 'Roboto';
64 font-weight: 400;
65 font-style: normal;
66 src: local('Roboto'),
67 local('Roboto-Regular'),
68 url('../fonts/Roboto-Regular.woff2') format('woff2'),
69 url('../fonts/Roboto-Regular.woff') format('woff');
70}
71
72@font-face {
73 font-family: 'Roboto';
74 font-weight: 700;
75 font-style: normal;
76 src: local('Roboto'),
77 local('Roboto-Bold'),
78 url('../fonts/Roboto-Bold.woff2') format('woff2'),
79 url('../fonts/Roboto-Bold.woff') format('woff');
80}
81
82body,
83.pure-g [class*='pure-u'] {
84 font-family: Roboto, Arial, sans-serif;
85}
86
87// Extends Pure grids responsive to hide items.
88// Use xx-0 to hide an item on xx screen.
89// Display it at any level with xx-visible.
90.pure-u-0 {
91 display: none !important;
92}
93
94@media screen and (min-width: 35.5em) {
95 .pure-u-sm-0 {
96 display: none !important;
97 }
98
99 .pure-u-sm-visible {
100 display: inline-block !important;
101 }
102}
103
104@media screen and (min-width: 48em) {
105 .pure-u-md-0 {
106 display: none !important;
107 }
108
109 .pure-u-md-visible {
110 display: inline-block !important;
111 }
112}
113
114@media screen and (min-width: 64em) {
115 .pure-u-lg-0 {
116 display: none !important;
117 }
118
119 .pure-u-lg-visible {
120 display: inline-block !important;
121 }
122}
123
124@media screen and (min-width: 80em) {
125 .pure-u-xl-0 {
126 display: none !important;
127 }
128
129 .pure-u-xl-visible {
130 display: inline-block !important;
131 }
132}
133
134// Make pure-extras alert closable.
135.pure-alert-closable {
136 .fa-times {
137 float: right;
138 }
139}
140
141.pure-alert-close {
142 cursor: pointer;
143}
144
145.pure-alert-success {
146 background-color: $main-green;
147}
148
149.pure-alert-warning {
150 a {
151 color: $warning-text;
152 font-weight: bold;
153 }
154}
155
156.page-single-alert {
157 margin-top: 100px;
158}
159
160.anchor {
161 &:target {
162 padding-top: 40px;
163 }
164}
165
166// MENU
167.shaarli-menu {
168 position: fixed;
169 top: 0;
170 transition: max-height .5s;
171 z-index: 999;
172 background: $main-green;
173 width: 100%;
174 // Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919
175 max-height: 45px;
176 overflow: hidden;
177 -webkit-font-smoothing: antialiased;
178
179 &.open {
180 transition: max-height .75s;
181 max-height: 500px;
182 }
183}
184
185.pure-menu-item {
186 // Chrome bugfix: with 100% height, it only displays the first element.
187 height: 45px;
188
189 &:hover {
190 &::after {
191 display: block;
192 margin: -4px auto 0;
193 background: $white;
194 width: 100%;
195 height: 4px;
196 content: '';
197 }
198 }
199}
200
201.head-logo {
202 float: left;
203 margin: 0 5px 0 0;
204}
205
206%menu-link {
207 padding: .8em 1em;
208 color: $almost-white;
209}
210
211%menu-link-hover {
212 background: transparent;
213 color: $white;
214}
215
216.pure-menu-link {
217 @extend %menu-link;
218
219 &:visited {
220 @extend %menu-link;
221 }
222
223 &:hover,
224 &:focus {
225 @extend %menu-link-hover;
226 }
227}
228
229.pure-menu-selected {
230 .pure-menu-link {
231 @extend %menu-link;
232
233 &:visited {
234 @extend %menu-link;
235 }
236
237 &:hover,
238 &:focus {
239 @extend %menu-link-hover;
240 }
241 }
242}
243
244.menu-toggle {
245 display: none;
246 position: absolute;
247 top: 5px;
248 right: 0;
249 width: 34px;
250 height: 45px;
251
252 .bar {
253 display: block;
254 position: absolute;
255 top: 18px;
256 right: 7px;
257 border-radius: 100px;
258 background-color: $light-green;
259 width: 20px;
260 height: 2px;
261 transition-duration: .5s;
262
263 &:first-child {
264 transform: translateY(-6px);
265 }
266 }
267
268 &.x {
269 .bar {
270 transform: rotate(45deg);
271
272 &:first-child {
273 transform: rotate(-45deg);
274 }
275 }
276 }
277}
278
279@media screen and (max-width: 64em) {
280 .menu-toggle {
281 display: block;
282 }
283}
284
285.header-buttons {
286 text-align: right;
287}
288
289.linkcount {
290 color: $dark-grey;
291 font-size: .8em;
292}
293
294@media screen and (min-width: 64em) {
295 .linkcount {
296 position: absolute;
297 right: 5px;
298 }
299}
300
301.searchform-block {
302 width: 100%;
303 text-align: center;
304
305 input {
306 &[type='text'] {
307 border: medium none currentColor;
308 border-radius: 2px;
309 box-shadow: 0 1px 0 $light-shadow, 0 1px 1px $dark-shadow inset;
310 background: $almost-white;
311 padding: 0 5px;
312 width: 260px;
313 height: 30px;
314 color: $dark-grey;
315
316 &::-webkit-input-placeholder {
317 color: $light-grey;
318 }
319 }
320 }
321
322 button {
323 border: 0;
324 border-radius: 2px;
325 background-color: $main-green;
326 padding: 4px 8px 6px;
327 color: $almost-white;
328 }
329}
330
331@media screen and (max-width: 64em) {
332 .searchform {
333 margin: 0 auto;
334 max-width: 260px;
335 }
336}
337
338.search-tagcloud {
339 button {
340 width: 90%;
341 }
342}
343
344@media screen and (max-width: 64em) {
345 .search-linklist {
346 button {
347 width: 100%;
348 }
349
350 .awesomplete {
351 margin: 5px 0;
352 }
353 }
354}
355
356.header-search,
357.search-linklist,
358.search-tagcloud {
359 button {
360 &:hover {
361 color: $background-color;
362 }
363 }
364}
365
366.header-search,
367.search-linklist {
368 padding: 6px 0;
369}
370
371@media screen and (max-width: 64em) {
372 .header-search ,
373 .header-search * {
374 visibility: hidden;
375 }
376}
377
378%subheader-form-input {
379 border: medium none currentColor;
380 border-radius: 2px;
381 box-shadow: 0 1px 0 $light-shadow, 0 1px 4px $dark-shadow inset;
382 background: $almost-white;
383 padding: 5px 5px 3px 15px;
384 width: 20%;
385 height: 20px;
386 color: $dark-grey;
387}
388
389.subheader-form {
390 display: block;
391 position: fixed;
392 visibility: hidden;
393 z-index: 999;
394 background: $main-green;
395 padding: 5px 0;
396 width: 100%;
397 height: 30px;
398 text-align: center;
399
400 input {
401 &[type='text'],
402 &[type='password'] {
403 @extend %subheader-form-input;
404
405 &::-webkit-input-placeholder {
406 color: $dark-grey;
407 }
408 }
409 }
410
411 &[type='submit'] {
412 display: inline-block;
413 margin: 0 0 5px;
414 border: 1px solid $almost-white;
415 border-radius: 2px;
416 background: $main-green;
417 padding: 4px 0;
418 width: 100px;
419 height: 28px;
420 color: $almost-white;
421
422 &:hover {
423 background: $almost-white;
424 color: $main-green;
425 }
426 }
427
428 .remember-me {
429 @extend %subheader-form-input;
430
431 display: inline-block;
432 cursor: pointer;
433 padding: 5px 20px 3px;
434 width: auto;
435
436 label,
437 input {
438 cursor: pointer;
439 }
440 }
441
442 a {
443 &.button {
444 border: 2px solid $almost-white;
445 border-radius: 5px;
446 padding: 3px 10px;
447 text-decoration: none;
448 color: $almost-white;
449 font-weight: bold;
450 }
451 }
452}
453
454.header-login-form {
455 input {
456 &[type='text'],
457 &[type='password'] {
458 width: 200px;
459
460 // because chrome
461 &::-webkit-input-placeholder {
462 color: $light-grey;
463 }
464 }
465 }
466}
467
468@media screen and (min-width: 64em) {
469 .subheader-form {
470 &.open {
471 visibility: visible;
472
473 * {
474 visibility: visible;
475 }
476 }
477 }
478}
479
480.new-version-message {
481 text-align: center;
482
483 a {
484 color: $warning-text;
485 font-weight: bold;
486 }
487}
488
489// CONTENT - GENERAL
490.container {
491 position: relative;
492 z-index: 2;
493 margin-top: 45px;
494}
495
496// Plugins additional forms
497.toolbar-plugin {
498 margin: 5px 0;
499 text-align: center;
500
501 input {
502 &[type='text'] {
503 border: medium none currentColor;
504 border-radius: 2px;
505 box-shadow: 0 1px 0 $light-shadow, 0 1px 1px $dark-shadow inset;
506 background: $almost-white;
507 padding: 0 5px;
508 width: 300px;
509 height: 30px;
510 color: $dark-grey;
511
512 &::-webkit-input-placeholder {
513 color: $light-grey;
514 }
515 }
516
517 &[type='submit'] {
518 border: medium none currentColor;
519 border-radius: 2px;
520 background: $almost-white;
521 padding: 0 10px;
522 height: 30px;
523 color: $dark-grey;
524
525 &:hover {
526 background: $white;
527 }
528 }
529 }
530}
531
532@media screen and (max-width: 64em) {
533 .toolbar-plugin {
534 input {
535 &[type='text'] {
536 width: 70%;
537 }
538 }
539 }
540}
541
542// CONTENT - LINKLIST PAGING
543// 64em -> lg
544.linklist-filters {
545 margin: 5px 0;
546 color: $dark-grey;
547 font-size: .9em;
548
549 a {
550 padding: 5px 8px;
551 text-decoration: none;
552 }
553
554 .filter-off {
555 background: $almost-white;
556 color: $dark-grey;
557 }
558
559 .filter-on {
560 background: $main-green;
561 color: $light-green;
562 }
563
564 .filter-block {
565 background: $red;
566 color: $almost-white;
567 }
568}
569
570.linklist-pages {
571 margin: 5px 0;
572 text-align: center;
573 color: $dark-grey;
574
575 a {
576 text-decoration: none;
577 color: $dark-grey;
578
579 &:hover {
580 color: $white;
581 }
582 }
583}
584
585%linksperpage-button {
586 display: inline-block;
587 width: 20px;
588 text-align: center;
589}
590
591.linksperpage {
592 margin: 5px 0;
593 text-align: right;
594 color: $dark-grey;
595 font-size: .9em;
596
597 form {
598 display: inline;
599 }
600
601 a {
602 @extend %linksperpage-button;
603
604 background: $almost-white;
605 padding: 5px;
606 text-decoration: none;
607 color: $dark-grey;
608 }
609
610 input {
611 &[type='text'] {
612 @extend %linksperpage-button;
613
614 margin: 0;
615 border: medium none currentColor;
616 background: $almost-white;
617 padding: 4px 5px 3px 8px;
618 height: 20px;
619 color: $dark-grey;
620 font-size: .8em;
621 }
622 }
623}
624
625// CONTENT - LINKLIST ITEMS
626%private-border {
627 display: block;
628 position: absolute;
629 top: 0;
630 left: 3px;
631 z-index: 1;
632 background: $orange;
633 width: 2px;
634 height: 96%;
635 content: '';
636}
637
638.linklist-item {
639 position: relative;
640 margin: 0 0 10px;
641 box-shadow: 1px 1px 3px $light-grey;
642 background: $almost-white;
643
644 &.private {
645 &::before {
646 display: block;
647 position: absolute;
648 top: 0;
649 left: 0;
650 z-index: 1;
651 background: $orange;
652 width: 2px;
653 height: 100%;
654 content: '';
655 }
656 }
657}
658
659.linklist-item-buttons {
660 position: relative;
661 z-index: 99;
662 background: transparent;
663 width: 23px;
664}
665
666.linklist-item-buttons-right {
667 float: right;
668 margin-right: -25px;
669}
670
671.linklist-item-buttons * {
672 display: block;
673 float: left;
674 margin: auto;
675 width: 100%;
676 text-align: center;
677}
678
679.linklist-item-title {
680 position: relative;
681 margin: 0;
682 background: $almost-white;
683 word-wrap: break-word;
684
685 h2 {
686 margin: 0;
687 padding: 3px 10px 0;
688 line-height: 30px;
689 word-wrap: break-word;
690
691 a {
692 vertical-align: middle;
693 text-decoration: none;
694 color: $dark-grey;
695 font-size: .7em;
696
697 &:visited {
698 .linklist-link {
699 color: $dark-green;
700 }
701 }
702
703 &:hover {
704 color: $dark-grey;
705 }
706 }
707 }
708
709 .linklist-link {
710 color: $main-green;
711 font-size: 1.1em;
712
713 &:hover {
714 color: $dark-grey;
715 }
716 }
717
718 .label-private {
719 border: solid 1px $orange;
720 color: $orange;
721 font-family: Arial, sans-serif;
722 font-size: .65em;
723 }
724}
725
726.fold-button {
727 display: none;
728 color: $dark-grey;
729}
730
731.linklist-item-editbuttons {
732 float: right;
733 padding: 8px 5px;
734
735 * {
736 display: block;
737 float: left;
738 margin: 0 1px;
739 }
740
741 a {
742 font-size: 1em;
743 }
744
745 .delete-checkbox {
746 display: none;
747 }
748}
749
750.edit-link {
751 color: $blue;
752 font-size: 1.2em;
753}
754
755.delete-link {
756 color: $red !important;
757 font-size: 1.3em;
758}
759
760.linklist-item-description {
761 position: relative;
762 padding: 0 10px;
763 line-height: 1.3em;
764 color: $dark-grey;
765 word-wrap: break-word;
766
767 a {
768 text-decoration: none;
769 color: $main-green;
770
771 &:hover {
772 color: $dark-grey;
773 }
774
775 &:visited {
776 color: $dark-green;
777 }
778 }
779}
780
781.linklist-item-thumbnail {
782 position: relative;
783 float: right;
784 z-index: 50;
785 margin: 0;
786 padding: 0 0 0 5px;
787 height: 90px;
788}
789
790.linklist-item-infos {
791 background: $background-linklist-info;
792 padding: 4px 8px;
793 color: $dark-grey;
794
795 a {
796 text-decoration: none;
797 color: $dark-grey;
798
799 &:hover {
800 color: $black;
801 }
802 }
803
804 .linklist-item-tags {
805 font-size: .8em;
806 }
807
808 .label-tag {
809 font-size: 1em;
810 }
811
812 .mobile-buttons {
813 text-align: right;
814 }
815
816 .linklist-plugin-icon {
817 display: inline-block;
818 margin: 0 2px;
819 width: 16px;
820 height: 16px;
821 }
822}
823
824.linklist-item-infos-dateblock {
825 font-size: .9em;
826}
827
828.linklist-plugin-icon {
829 width: 13px;
830 height: 13px;
831}
832
833.linklist-item-infos-url {
834 height: 23px;
835 overflow: hidden;
836 text-align: right;
837 text-overflow: ellipsis;
838 line-height: 23px;
839 white-space: nowrap;
840 font-size: .8em;
841}
842
843.linklist-item-infos-controls-group {
844 display: inline-block;
845 border-right: 1px solid $light-grey;
846 padding-right: 6px;
847}
848
849.ctrl-edit {
850 margin: 0 7px;
851}
852
853// 64em -> lg
854@media screen and (max-width: 64em) {
855 .linklist-item-infos-url {
856 text-align: left;
857 }
858}
859
860// Footer
861.footer-container {
862 margin: 20px 0;
863 padding: 5px;
864 text-align: center;
865 color: $dark-grey;
866
867 &::before {
868 display: block;
869 margin: 10px auto;
870 background: linear-gradient(to right, $background-color, $dark-grey, $background-color);
871 width: 80%;
872 height: 1px;
873 content: '';
874 }
875
876 a {
877 color: $dark-grey;
878 }
879}
880
881// PAGE FORM
882%page-form-input {
883 margin: 10px 0;
884 border: solid 1px $form-input-border;
885 border-radius: 2px;
886 background: $form-input-background;
887 padding: 5px 5px 3px 15px;
888 width: 90%;
889 height: 35px;
890 color: $dark-grey;
891 box-sizing: border-box;
892}
893
894%page-form-button {
895 display: inline-block;
896 margin: 15px 5px;
897 border: 0;
898 box-shadow: 1px 1px 1px $form-input-border, -1px -1px 6px $form-input-border, -1px 1px 2px $form-input-border, 1px -1px 2px $form-input-border;
899 background: $main-green;
900 min-width: 150px;
901 height: 35px;
902 vertical-align: center;
903 text-decoration: none;
904 line-height: 35px;
905 color: $almost-white;
906 font-size: 1.2em;
907 font-weight: normal;
908}
909
910.page-form {
911 margin: 20px 0 0;
912 box-shadow: 1px 1px 2px $light-grey;
913 background: $almost-white;
914 overflow: hidden;
915 color: $dark-grey;
916
917 .window-title {
918 margin: 0 0 10px;
919 background: $almost-white;
920 padding: 10px 0;
921 width: 100%;
922 text-align: center;
923 color: $main-green;
924 }
925
926 .window-subtitle {
927 text-align: center;
928 }
929
930 a {
931 text-decoration: none;
932 color: $main-green;
933 font-weight: bold;
934
935 &.button {
936 @extend %page-form-button;
937 }
938 }
939
940 p {
941 margin: 0;
942 padding: 5px 10px;
943 }
944
945 input {
946 &[type='text'] {
947 @extend %page-form-input;
948
949 &::-webkit-input-placeholder {
950 color: $light-grey;
951 }
952 }
953
954 &[type='password'] {
955 @extend %page-form-input;
956
957 &::-webkit-input-placeholder {
958 color: $light-grey;
959 }
960 }
961
962 &[type='submit'] {
963 @extend %page-form-button;
964 }
965 }
966
967 textarea {
968 @extend %page-form-input;
969
970 padding: 15px 5px 3px 15px;
971 min-height: 240px;
972 resize: vertical;
973 overflow-y: auto;
974 word-wrap: break-word;
975 }
976
977 select {
978 color: $dark-grey;
979 }
980
981 .button {
982 &.button-red {
983 background: $red;
984 }
985 }
986
987 .submit-buttons {
988 margin-bottom: 10px;
989 }
990
991 section {
992 margin: 10px 0 25px;
993 }
994
995 table,
996 th,
997 td {
998 border-width: 1px 0;
999 border-style: solid;
1000 border-color: $light-grey;
1001 }
1002
1003 th,
1004 td {
1005 padding: 5px;
1006 }
1007
1008 table {
1009 margin: auto;
1010 width: 90%;
1011
1012 .order {
1013 text-decoration: none;
1014 color: $dark-grey;
1015 }
1016 }
1017
1018 .awesomplete {
1019 width: 90%;
1020
1021 input {
1022 width: 100%;
1023 }
1024 }
1025
1026 div {
1027 .awesomplete {
1028 > ul {
1029 color: $black;
1030 }
1031 }
1032 }
1033}
1034
1035@media screen and (min-width: 64em) {
1036 .page-form {
1037 .submit-buttons {
1038 position: relative;
1039
1040 .button {
1041 &.button-red {
1042 position: absolute;
1043 right: 5%;
1044 }
1045 }
1046 }
1047 }
1048}
1049
1050@media screen and (max-width: 64em) {
1051 .page-form {
1052 .submit-buttons {
1053 .button {
1054 display: block;
1055 margin: auto;
1056 }
1057 }
1058 }
1059}
1060
1061// PAGE FORM - LIGHT
1062.page-form-light {
1063 div,
1064 p {
1065 text-align: center;
1066 }
1067}
1068
1069// PAGE FORM - COMPLETE
1070%page-form-valign {
1071 position: absolute;
1072 top: 50%;
1073 transform: translateY(-50%);
1074}
1075
1076.page-form-complete {
1077 div,
1078 p {
1079 color: $dark-grey;
1080 }
1081
1082 .form-label,
1083 .form-input {
1084 position: relative;
1085 height: 60px;
1086 }
1087
1088 .form-label {
1089 label {
1090 @extend %page-form-valign;
1091
1092 right: 0;
1093 padding: 0 20px;
1094 text-align: right;
1095 }
1096 }
1097
1098 .label-name {
1099 font-weight: bold;
1100 }
1101
1102 .label-desc {
1103 font-size: .8em;
1104 }
1105
1106 .form-input {
1107 input {
1108 @extend %page-form-valign;
1109
1110 &[type='text'],
1111 &[type='password'] {
1112 margin: 0;
1113 }
1114 }
1115
1116 select {
1117 &.align {
1118 @extend %page-form-valign;
1119 }
1120 }
1121 }
1122
1123 textarea {
1124 margin: 0;
1125 }
1126
1127 .timezone {
1128 @extend %page-form-valign;
1129 }
1130}
1131
1132// Awesomeplete fix
1133div {
1134 &.awesomplete {
1135 width: inherit;
1136
1137 > input {
1138 display: inherit;
1139 }
1140
1141 > ul {
1142 z-index: 9999;
1143 }
1144 }
1145}
1146
1147form {
1148 &[name='linkform'] {
1149 &.page-form {
1150 overflow: visible;
1151 }
1152 }
1153}
1154
1155@media screen and (max-width: 64em) {
1156 %page-form-valign-mobile {
1157 position: inherit;
1158 top: inherit;
1159 transform: translateY(0);
1160 }
1161
1162 .page-form-complete {
1163 .form-label {
1164 height: inherit;
1165
1166 label {
1167 @extend %page-form-valign-mobile;
1168
1169 display: block;
1170 margin: 10px 0 0;
1171 text-align: left;
1172 }
1173 }
1174
1175 .form-input {
1176 text-align: center;
1177
1178 input {
1179 @extend %page-form-valign-mobile;
1180
1181 &[type='checkbox'] {
1182 position: absolute;
1183 top: 50%;
1184 right: 50%;
1185 transform: translateY(-50%);
1186 }
1187 }
1188 }
1189
1190 .timezone {
1191 @extend %page-form-valign-mobile;
1192 }
1193
1194 .radio-buttons {
1195 padding: 5px 15px;
1196 text-align: left;
1197 }
1198 }
1199
1200 .timezone-continent {
1201 &::after {
1202 white-space: pre;
1203 content: '\a\a';
1204 }
1205 }
1206}
1207
1208// Page visitor (page form extended)
1209.page-visitor {
1210 color: $dark-grey;
1211}
1212
1213.page404-container {
1214 color: $dark-grey;
1215}
1216
1217// EDIT LINK
1218.edit-link-container {
1219 .created-date {
1220 margin-bottom: 10px;
1221 color: $light-grey;
1222 }
1223}
1224
1225// LOGIN
1226.login-form-container {
1227 .remember-me {
1228 margin: 5px 0;
1229 }
1230}
1231
1232// Search results
1233.search-result {
1234 a {
1235 text-decoration: none;
1236 color: $white;
1237 }
1238
1239 .label-tag {
1240 border-color: $white;
1241
1242 .remove {
1243 margin: 0 0 0 5px;
1244 border-left: $white 1px solid;
1245 padding: 0 0 0 5px;
1246 }
1247 }
1248
1249 .label-private {
1250 border: 1px solid $white;
1251 }
1252}
1253
1254// TOOLS
1255.tools-item {
1256 margin: 10px 0;
1257
1258 .pure-button {
1259 &:hover {
1260 background-color: $main-green;
1261 background-image: none;
1262 color: $almost-white;
1263 }
1264 }
1265}
1266
1267// PLUGIN ADMIN
1268.pluginform-container {
1269 .mobile-row {
1270 font-size: .9em;
1271 }
1272
1273 .more {
1274 margin-top: 10px;
1275 }
1276}
1277
1278@media screen and (max-width: 64em) {
1279 .pluginform-container {
1280 .main-row {
1281 border-top-style: none;
1282 border-bottom-style: none;
1283
1284 td {
1285 border-top-style: none;
1286 border-bottom-style: none;
1287 }
1288 }
1289 }
1290}
1291
1292// IMPORT
1293.import-field-container {
1294 margin: 15px 0;
1295}
1296
1297// TAG CLOUD
1298.cloudtag-container {
1299 padding: 10px;
1300 text-align: center;
1301 text-decoration: none;
1302 color: $dark-grey;
1303
1304 a {
1305 text-decoration: none;
1306 color: $dark-grey;
1307 }
1308
1309 .count {
1310 color: $light-grey;
1311 }
1312}
1313
1314// TAG LIST
1315.taglist-container {
1316 padding: 0 10px;
1317
1318 a {
1319 text-decoration: none;
1320 color: $dark-grey;
1321 }
1322
1323 .count {
1324 display: inline-block;
1325 width: 35px;
1326 text-align: right;
1327 color: $light-grey;
1328 }
1329
1330 .rename-tag-form {
1331 display: none;
1332 }
1333
1334 .delete-tag {
1335 display: none;
1336 color: $red;
1337 }
1338
1339 .rename-tag {
1340 color: $blue;
1341 }
1342
1343 .validate-rename-tag {
1344 color: $main-green;
1345 }
1346}
1347
1348// Picture wall CSS
1349.picwall-container {
1350 clear: both;
1351 margin: 0 10px 10px;
1352 background-color: $almost-white;
1353 color: $dark-grey;
1354}
1355
1356.picwall-pictureframe {
1357 display: table-cell;
1358 position: relative;
1359 float: left;
1360 z-index: 5;
1361 margin: 2px;
1362 background-color: $almost-white;
1363 width: 90px;
1364 height: 90px;
1365 overflow: hidden;
1366 vertical-align: middle;
1367 text-align: center;
1368
1369 // Adapt the width of the image
1370 img {
1371 max-width: 100%;
1372 height: auto;
1373 color: transparent;
1374 }
1375
1376 a {
1377 text-decoration: none;
1378 }
1379
1380 span {
1381 &.info {
1382 display: none;
1383 font-family: Arial, sans-serif;
1384 }
1385 }
1386
1387 // CSS to show title when hovering an image - no javascript required.
1388 &:hover {
1389 span {
1390 &.info {
1391 display: block;
1392 position: absolute;
1393 top: 0;
1394 left: 0;
1395 background-color: $dark-shadow;
1396 width: 90px;
1397 height: 90px;
1398 text-align: left;
1399 color: $almost-white;
1400 font-size: 9pt;
1401 font-weight: bold;
1402 }
1403 }
1404 }
1405}
1406
1407.b-lazy {
1408 transition: opacity 500ms ease-in-out;
1409 opacity: 0;
1410 -webkit-transition: opacity 500ms ease-in-out;
1411 -moz-transition: opacity 500ms ease-in-out;
1412 -o-transition: opacity 500ms ease-in-out;
1413
1414 &.b-loaded {
1415 opacity: 1;
1416 }
1417}
1418
1419// DAILY
1420.daily-desc {
1421 color: $light-grey;
1422 font-size: .8em;
1423
1424 a {
1425 text-decoration: none;
1426 color: $dark-grey;
1427
1428 &:hover {
1429 color: $light-grey;
1430 }
1431 }
1432}
1433
1434.daily-about {
1435 h3 {
1436 &::before,
1437 &::after {
1438 display: block;
1439 margin: 10px auto;
1440 background: linear-gradient(to right, $background-color, $dark-grey, $background-color);
1441 width: 90%;
1442 height: 1px;
1443 content: '';
1444 }
1445 }
1446}
1447
1448.daily-entry {
1449 padding: 0 10px;
1450
1451 .daily-entry-title {
1452 margin: 10px 0 0;
1453
1454 a {
1455 text-decoration: none;
1456 color: $black;
1457 }
1458
1459 &::after {
1460 display: block;
1461 margin: 5px auto;
1462 background: linear-gradient(to right, $white, $light-grey, $white);
1463 width: 70%;
1464 height: 1px;
1465 content: '';
1466 }
1467 }
1468
1469 .daily-entry-description {
1470 padding: 5px 5px 0;
1471 text-align: justify;
1472 font-size: .9em;
1473 word-wrap: break-word;
1474 }
1475
1476 .daily-entry-tags {
1477 padding: 0 5px 5px;
1478 font-size: .8em;
1479 }
1480}
1481
1482.daily-entry-thumbnail {
1483 float: left;
1484 margin: 15px 5px 5px 15px;
1485}
1486
1487.daily-entry-description {
1488 a {
1489 text-decoration: none;
1490 color: $main-green;
1491
1492 &:hover {
1493 text-shadow: 1px 1px $background-linklist-info;
1494 }
1495
1496 &:visited {
1497 color: $dark-green;
1498 }
1499 }
1500}
1501
1502// Fix empty bookmarklet name in Firefox
1503.pure-button {
1504 -moz-user-select: auto;
1505}
1506
1507.tag-sort {
1508 margin-top: 30px;
1509 text-align: center;
1510
1511 a {
1512 display: inline-block;
1513 margin: 0 15px;
1514 text-decoration: none;
1515 color: $white;
1516 font-weight: bold;
1517 }
1518}
1519
1520// Markdown
1521.markdown {
1522 p {
1523 margin: 0 !important;
1524 }
1525
1526 p + p {
1527 margin: .5em 0 0 !important;
1528 }
1529
1530 * {
1531 &:first-child {
1532 margin-top: 0 !important;
1533 }
1534
1535 &:last-child {
1536 margin-bottom: 5px !important;
1537 }
1538 }
1539}
1540
1541// Pure Button
1542.pure-button-success,
1543.pure-button-error,
1544.pure-button-warning,
1545.pure-button-primary,
1546.pure-button-shaarli,
1547.pure-button-secondary {
1548 border-radius: 4px;
1549 text-shadow: 0 1px 1px $dark-shadow;
1550 color: $white !important;
1551}
1552
1553.pure-button-shaarli {
1554 background-color: $main-green;
1555}
1556
1557.progressbar {
1558 border-radius: 6px;
1559 background-color: $main-green;
1560 padding: 1px;
1561
1562 > div {
1563 border-radius: 10px;
1564 background: repeating-linear-gradient(
1565 -45deg,
1566 $almost-white,
1567 $almost-white 6px,
1568 $background-color 6px,
1569 $background-color 12px
1570 );
1571 width: 0%;
1572 height: 10px;
1573 }
1574}
1575
1576.thumbnails-page-container {
1577 .progress-counter {
1578 padding: 10px 0 20px;
1579 }
1580
1581 .thumbnail-placeholder {
1582 margin: 10px auto;
1583 background-color: $light-grey;
1584 }
1585
1586 .thumbnail-link-title {
1587 padding-bottom: 20px;
1588 overflow: hidden;
1589 text-overflow: ellipsis;
1590 white-space: nowrap;
1591 }
1592}