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