aboutsummaryrefslogtreecommitdiffhomepage
path: root/assets/default
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2018-03-28 19:08:06 +0200
committerGitHub <noreply@github.com>2018-03-28 19:08:06 +0200
commitc81f1afc0a3a16daf98741a63c7524b27835da99 (patch)
treedfe81b73f028dfc27eda916b14bf4bc17966b082 /assets/default
parent9b2bd66fb60ffd5a833480bf329062c7d57bc8c4 (diff)
parentd7eb06bd7c4d01bbdf67f4f100af7a3e300098d3 (diff)
downloadShaarli-c81f1afc0a3a16daf98741a63c7524b27835da99.tar.gz
Shaarli-c81f1afc0a3a16daf98741a63c7524b27835da99.tar.zst
Shaarli-c81f1afc0a3a16daf98741a63c7524b27835da99.zip
Merge pull request #1072 from ArthurHoaro/feature/modern-front-end
Manage frontend dependencies with npm/yarn and webpack
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.scss1357
11 files changed, 2044 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..25440de1
--- /dev/null
+++ b/assets/default/scss/shaarli.scss
@@ -0,0 +1,1357 @@
1$fa-font-path: "~font-awesome/fonts";
2
3@import "~font-awesome/scss/font-awesome.scss";
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/**
10 * General
11 */
12body {
13 background: #d0d0d0;
14}
15
16.strong {
17 font-weight: bold;
18}
19
20.clear {
21 clear: both;
22}
23
24.center {
25 text-align: center;
26 margin: auto;
27}
28
29.label {
30 display: inline-block;
31 padding: .25em .4em;
32 font-size: 75%;
33 font-weight: 700;
34 line-height: 1;
35 text-align: center;
36 white-space: nowrap;
37 vertical-align: baseline;
38 border-radius: .25rem;
39}
40
41pre {
42 max-width: 100%;
43}
44
45@font-face {
46 font-family: 'Roboto';
47 font-weight: 400;
48 font-style: normal;
49 src:
50 local('Roboto'),
51 local('Roboto-Regular'),
52 url('../fonts/Roboto-Regular.woff2') format('woff2'),
53 url('../fonts/Roboto-Regular.woff') format('woff');
54}
55
56@font-face {
57 font-family: 'Roboto';
58 font-weight: 700;
59 font-style: normal;
60 src:
61 local('Roboto'),
62 local('Roboto-Bold'),
63 url('../fonts/Roboto-Bold.woff2') format('woff2'),
64 url('../fonts/Roboto-Bold.woff') format('woff');
65}
66
67body, .pure-g [class*="pure-u"] {
68 font-family: Roboto, Arial, sans-serif;
69}
70
71/**
72 * Extends Pure grids responsive to hide items.
73 * Use xx-0 to hide an item on xx screen.
74 * Display it at any level with xx-visible.
75 */
76.pure-u-0 { display: none !important; }
77@media screen and (min-width: 35.5em) {
78 .pure-u-sm-0 { display: none !important; }
79 .pure-u-sm-visible { display: inline-block !important; }
80}
81@media screen and (min-width: 48em) {
82 .pure-u-md-0 { display: none !important; }
83 .pure-u-md-visible { display: inline-block !important; }
84}
85@media screen and (min-width: 64em) {
86 .pure-u-lg-0 { display: none !important; }
87 .pure-u-lg-visible { display: inline-block !important; }
88}
89@media screen and (min-width: 80em) {
90 .pure-u-xl-0 { display: none !important; }
91 .pure-u-xl-visible { display: inline-block !important; }
92}
93
94/**
95 * Make pure-extras alert closable.
96 */
97.pure-alert-closable .fa-times {
98 float: right;
99}
100.pure-alert-close {
101 cursor: pointer;
102}
103
104.pure-alert-success {
105 background-color: #1b926c;
106}
107
108.anchor:target {
109 padding-top: 40px;
110}
111/**
112 * MENU
113 **/
114.shaarli-menu {
115 position: fixed;
116 top: 0;
117 width: 100%;
118 --height: 50px;
119 background: #1b926c;
120 -webkit-font-smoothing: antialiased;
121 /* Hack to transition with auto height: http://stackoverflow.com/a/8331169/1484919 */
122 max-height: 45px;
123 transition: max-height 0.5s;
124 overflow: hidden;
125 z-index: 999;
126}
127
128/* Chrome bugfix: with 100% height, it only displays the first element. */
129.pure-menu-item {
130 height: 45px;
131}
132
133.shaarli-menu.open {
134 max-height: 500px;
135 transition: max-height 0.75s;
136}
137
138.head-logo {
139 float: left;
140 margin: 0 5px 0 0;
141}
142
143.pure-menu-link,
144.pure-menu-link:visited,
145.pure-menu-selected .pure-menu-link,
146.pure-menu-selected .pure-menu-link:visited {
147 padding: 0.8em 1em;
148 color: #f5f5f5;
149}
150
151.pure-menu-link:hover, .pure-menu-link:focus,
152.pure-menu-selected .pure-menu-link:hover,
153.pure-menu-selected .pure-menu-link:focus {
154 color: #fff;
155 background: transparent;
156}
157
158.pure-menu-item:hover::after {
159 margin: -4px auto 0 auto;
160 display: block;
161 content:"";
162 background: #fff;
163 height: 4px;
164 width: 100%;
165}
166
167.menu-toggle {
168 width: 34px;
169 height: 45px;
170 position: absolute;
171 top: 5px;
172 right: 0;
173 display: none;
174}
175
176.menu-toggle .bar {
177 background-color: #b0ddce;
178 display: block;
179 width: 20px;
180 height: 2px;
181 border-radius: 100px;
182 position: absolute;
183 top: 18px;
184 right: 7px;
185 transition: all 0.5s;
186}
187
188.menu-toggle .bar:first-child {
189 transform: translateY(-6px);
190}
191
192.menu-toggle.x .bar {
193 transform: rotate(45deg);
194}
195
196.menu-toggle.x .bar:first-child {
197 transform: rotate(-45deg);
198}
199
200@media screen and (max-width: 64em) {
201 .menu-toggle {
202 display: block;
203 }
204}
205
206.header-buttons {
207 text-align: right;
208}
209
210.linkcount {
211 color: #252525;
212 font-size: 0.8em;
213}
214
215@media screen and (min-width: 64em) {
216 .linkcount {
217 position: absolute;
218 right: 5px;
219 }
220}
221
222#search, #search-linklist, #search-tagcloud {
223 text-align: center;
224 width: 100%;
225}
226
227#search input[type="text"], #search-linklist input[type="text"] {
228 padding: 0 5px;
229 height: 30px;
230 width: 260px;
231 background: #f5f5f5;
232 border: medium none currentColor;
233 box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 1px rgba(0, 0, 0, 0.298) inset;
234 border-radius: 2px;
235 color: #252525;
236}
237@media screen and (max-width: 64em) {
238 .searchform {
239 max-width: 260px;
240 margin: 0 auto;
241 }
242}
243
244/* because chrome */
245#search input[type="text"]::-webkit-input-placeholder,
246#search-linklist input[type="text"]::-webkit-input-placeholder {
247 color: #777777;
248}
249
250#search button,
251#search-tagcloud button,
252#search-linklist button {
253 padding: 4px 8px 6px 8px;
254 background-color: #1B926C;
255 color: #f5f5f5;
256 border: none;
257 border-radius: 2px;
258}
259
260#search-tagcloud button {
261 width: 90%;
262}
263
264@media screen and (max-width: 64em) {
265 #search-linklist button {
266 width: 100%;
267 }
268 #search-linklist .awesomplete {
269 margin: 5px 0;
270 }
271}
272
273#search button:hover,
274#search-linklist button:hover,
275#search-tagcloud button:hover {
276 color: #d0d0d0;
277}
278
279#search,
280#search-linklist {
281 padding: 6px 0;
282}
283
284@media screen and (max-width: 64em) {
285 #search, #search * {
286 visibility: hidden;
287 }
288}
289
290.subheader-form a.button {
291 color: #f5f5f5;
292 font-weight: bold;
293 text-decoration: none;
294 border: 2px solid #f5f5f5;
295 border-radius: 5px;
296 padding: 3px 10px;
297}
298
299.linklist-item-editbuttons .delete-checkbox {
300 display: none;
301}
302
303#header-login-form input[type="text"], #header-login-form input[type="password"] {
304 width: 200px;
305}
306
307/* because chrome */
308#header-login-form input[type="text"]::-webkit-input-placeholder,
309#header-login-form input[type="password"]::-webkit-input-placeholder {
310 color: #777777;
311}
312
313.subheader-form {
314 visibility: hidden;
315 position: fixed;
316 width: 100%;
317 text-align: center;
318 background: #1b926c;
319 display: block;
320 z-index: 999;
321 height: 30px;
322 padding: 5px 0;
323}
324
325@media screen and (min-width: 64em) {
326 .subheader-form.open, .subheader-form.open * {
327 visibility: visible;
328 }
329}
330
331.subheader-form input[type="text"], .subheader-form input[type="password"], .subheader-form .remember-me {
332 padding: 5px 5px 3px 15px;
333 height: 20px;
334 width: 20%;
335 background: #f5f5f5;
336 border: medium none currentColor;
337 border-radius: 2px;
338 box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 4px rgba(0, 0, 0, 0.298) inset;
339 color: #252525;
340}
341
342/* because chrome */
343.subheader-form input[type="text"]::-webkit-input-placeholder,
344.subheader-form input[type="password"]::-webkit-input-placeholder
345{
346 color: #252525;
347}
348
349.subheader-form .remember-me {
350 display: inline-block;
351 width: auto;
352 padding: 5px 20px 3px 20px;
353 cursor: pointer;
354}
355
356.subheader-form .remember-me label, .subheader-form .remember-me input {
357 cursor: pointer;
358}
359
360.subheader-form input[type="submit"] {
361 display: inline-block;
362 margin: 0 0 5px 0;
363 padding: 4px 0 4px 0;
364 height: 28px;
365 width: 100px;
366 background: #1b926c;
367 border: 1px solid #f5f5f5;
368 color: #f5f5f5;
369 border-radius: 2px;
370}
371
372.subheader-form input[type="submit"]:hover {
373 background: #f5f5f5;
374 color: #1b926c;
375}
376
377.new-version-message {
378 text-align: center;
379}
380
381.new-version-message a {
382 color: rgb(151, 96, 13);
383 font-weight: bold;
384}
385
386/**
387 * CONTENT - GENERAL
388 */
389#content {
390 position: relative;
391 z-index: 2;
392 margin-top: 45px;
393}
394
395/**
396 * Plugins additional forms
397 */
398.toolbar-plugin {
399 margin: 5px 0;
400 text-align: center;
401}
402
403.toolbar-plugin input[type="text"] {
404 padding: 0 5px;
405 height: 30px;
406 width: 300px;
407 background: #f5f5f5;
408 border: medium none currentColor;
409 box-shadow: 0 1px 0 rgba(255, 255, 255, 0.078), 0 1px 1px rgba(0, 0, 0, 0.298) inset;
410 border-radius: 2px;
411 color: #252525;
412}
413
414/* because chrome */
415.toolbar-plugin input[type="text"]::-webkit-input-placeholder {
416 color: #777777;
417}
418
419.toolbar-plugin input[type="submit"] {
420 padding: 0 10px;
421 height: 30px;
422 background: #f5f5f5;
423 border: medium none currentColor;
424 border-radius: 2px;
425 color: #252525;
426}
427
428.toolbar-plugin input[type="submit"]:hover {
429 background: #fff;
430}
431
432@media screen and (max-width: 64em) {
433 .toolbar-plugin input[type="text"] {
434 width: 70%;
435
436 }
437}
438
439/**
440 * CONTENT - LINKLIST PAGING
441 * 64em -> lg
442 */
443.linklist-filters {
444 margin: 5px 0;
445 color: #252525;
446 font-size: 0.9em;
447}
448
449.linklist-filters a {
450 padding: 5px 8px;
451 text-decoration: none;
452}
453
454.linklist-filters .filter-off {
455 color: #252525;
456 background: #f5f5f5;
457}
458
459.linklist-filters .filter-on {
460 color: #b0ddce;
461 background: #1b926c;
462}
463
464.linklist-filters .filter-block {
465 color: #f5f5f5;
466 background: #ac2925;
467}
468
469.linklist-pages {
470 margin: 5px 0;
471 color: #252525;
472 text-align: center;
473}
474
475.linklist-pages a {
476 color: #252525;
477 text-decoration: none;
478}
479
480.linklist-pages a:hover {
481 color: #fff;
482}
483
484.linksperpage {
485 margin: 5px 0;
486 text-align: right;
487 color: #252525;
488 font-size: 0.9em;
489}
490
491.linksperpage a {
492 padding: 5px 5px;
493 text-decoration: none;
494 color: #252525;
495 background: #f5f5f5;
496}
497
498.linksperpage a, .linksperpage input[type="text"] {
499 display: inline-block;
500 width: 20px;
501 text-align: center;
502}
503
504.linksperpage form {
505 display: inline;
506}
507
508.linksperpage input[type="text"] {
509 height: 20px;
510 margin: 0;
511 padding: 4px 5px 3px 8px;
512 background: #f5f5f5;
513 border: medium none currentColor;
514 color: #252525;
515 font-size: 0.8em;
516}
517
518/**
519 * CONTENT - LINKLIST ITEMS
520 */
521.linklist-item {
522 margin: 0 0 10px 0;
523 background: #f5f5f5;
524 box-shadow: 1px 1px 3px #797979;
525}
526
527.linklist-item-buttons {
528 background: transparent;
529 position: relative;
530 width: 23px;
531 z-index: 99;
532}
533
534.linklist-item-buttons-right {
535 float: right;
536 margin-right: -25px;
537}
538
539.linklist-item-buttons * {
540 display: block;
541 float: left;
542 width:100%;
543 margin: auto;
544 text-align: center;
545}
546
547.linklist-item-title, .linklist-item-title h2 {
548 margin: 0;
549 word-wrap: break-word;
550}
551
552.linklist-item-title {
553 position: relative;
554 background: #f5f5f5;
555}
556
557.linklist-item-title h2 {
558 padding: 3px 10px 0 10px;
559 line-height: 30px;
560}
561
562.linklist-item-title h2 a {
563 font-size: 0.7em;
564 color: #252525;
565 text-decoration: none;
566 vertical-align: middle;
567}
568
569.linklist-item-title .linklist-link {
570 font-size: 1.1em;
571 color: #1b926c;
572}
573
574.linklist-item-title h2 a:visited .linklist-link {
575 color: #2a4c41;
576}
577
578.linklist-item-title h2 a:hover, .linklist-item-title .linklist-link:hover{
579 color: #252525;
580}
581
582
583.linklist-item-title .label-private {
584 border: solid 1px #F89406;
585 font-family: Arial, sans-serif;
586 font-size: 0.65em;
587 color: #F89406;
588}
589
590.fold-button {
591 display: none;
592 color: #252525;
593}
594
595.linklist-item-editbuttons {
596 float: right;
597 padding: 8px 5px;
598}
599
600.linklist-item-editbuttons * {
601 display: block;
602 float: left;
603 margin: 0 1px;
604}
605
606.linklist-item-editbuttons a {
607 font-size: 1em;
608}
609
610.edit-link {
611 font-size: 1.2em;
612 color: #0b5ea6;
613}
614
615.delete-link {
616 font-size: 1.3em;
617 color: #ac2925 !important;
618}
619
620.linklist-item-description {
621 position: relative;
622 padding: 0 10px;
623 word-wrap: break-word;
624 color: #252525;
625 line-height: 1.3em;
626}
627
628.linklist-item-description a {
629 text-decoration: none;
630 color: #1b926c;
631}
632
633.linklist-item-description a:hover {
634 color: #252525;
635}
636
637.linklist-item-description a:visited {
638 color: #14553f;
639}
640
641.linklist-item-thumbnail {
642 position: relative;
643 padding: 0 0 0 5px;
644 margin: 0;
645 float: right;
646 z-index: 50;
647 height: 90px;
648}
649
650.linklist-item.private .linklist-item-title::before,
651.linklist-item.private .linklist-item-description::before {
652 position: absolute;
653 left: 3px;
654 top: 0;
655 display: block;
656 content:"";
657 background: #F89406;
658 height: 96%;
659 width: 2px;
660 z-index: 1;
661}
662
663.linklist-item.private .linklist-item-description::before {
664 height: 100%;
665}
666
667.linklist-item.private .linklist-item-title::before {
668 margin-top: 3px;
669}
670
671.linklist-item-infos {
672 padding: 4px 8px 4px 8px;
673 background: #ddd;
674 color: #252525;
675}
676
677.linklist-item-infos a {
678 color: #252525;
679 text-decoration: none;
680}
681
682.linklist-item-infos a:hover {
683 color: #000;
684}
685
686.linklist-item-infos .linklist-item-tags {
687 font-size: 0.8em;
688}
689
690.linklist-item-infos .label-tag {
691 font-size: 1em;
692}
693
694.linklist-item-infos-dateblock {
695 font-size: 0.9em;
696}
697
698.linklist-plugin-icon {
699 width: 13px;
700 height: 13px;
701}
702
703.linklist-item-infos-url {
704 text-align: right;
705 white-space: nowrap;
706 overflow: hidden;
707 text-overflow: ellipsis;
708 font-size: 0.8em;
709 height:23px;
710 line-height:23px;
711}
712
713.linklist-item-infos .mobile-buttons {
714 text-align: right;
715}
716
717.linklist-item-infos .linklist-plugin-icon {
718 display: inline-block;
719 margin: 0 2px;
720 width: 16px;
721 height: 16px;
722}
723
724.linklist-item-infos-controls-group {
725 display: inline-block;
726 border-right: 1px solid #5d5d5d;
727 padding-right: 6px;
728}
729
730.ctrl-edit {
731 margin: 0 7px;
732}
733
734/** 64em -> lg **/
735@media screen and (max-width: 64em) {
736 .linklist-item-infos-url {
737 text-align: left;
738 }
739}
740
741/**
742 * Footer
743 */
744#footer {
745 margin: 20px 0;
746 padding: 5px;
747 text-align: center;
748 color: #252525;
749}
750
751#footer:before {
752 display: block;
753 content:"";
754 background: linear-gradient(to right, #949393, #252525, #949393);
755 height: 1px;
756 width: 80%;
757 margin: 10px auto;
758}
759
760#footer a {
761 color: #252525;
762}
763
764/**
765 * PAGE FORM
766 */
767.page-form {
768 margin: 20px 0 0 0;
769 background: #f5f5f5;
770 box-shadow: 1px 1px 2px #797979;
771 color: #252525;
772 overflow: hidden;
773}
774
775.page-form .window-title {
776 margin: 0 0 10px 0;
777 padding: 10px 0;
778 width: 100%;
779 color: #1b926c;
780 background: #f5f5f5;
781 text-align: center;
782}
783
784.page-form .window-subtitle {
785 text-align: center;
786}
787
788.page-form a {
789 color: #1b926c;
790 font-weight: bold;
791 text-decoration: none;
792}
793
794.page-form p {
795 padding: 5px 10px;
796 margin: 0;
797}
798
799.page-form input[type="text"],
800.page-form input[type="password"],
801.page-form textarea {
802 box-sizing: border-box;
803 margin: 10px 0;
804 padding: 5px 5px 3px 15px;
805 height: 35px;
806 width: 90%;
807 background: #eeeeee;
808 border: solid 1px #d8d8d8;
809 border-radius: 2px;
810 color: #252525;
811}
812
813.page-form textarea {
814 min-height: 240px;
815 padding: 15px 5px 3px 15px;
816 resize: vertical;
817 overflow-y: auto;
818 word-wrap:break-word
819}
820
821/* because chrome */
822.page-form input[type="text"]::-webkit-input-placeholder,
823.page-form input[type="password"]::-webkit-input-placeholder {
824 color: #777777;
825}
826
827.page-form input[type="submit"], .page-form a.button {
828 margin: 15px 5px;
829 height: 35px;
830 line-height: 35px;
831 width: 150px;
832 background: #1b926c;
833 color: #f5f5f5;
834 border: none;
835 box-shadow: 1px 1px 1px #ddd, -1px -1px 6px #ddd, -1px 1px 2px #ddd, 1px -1px 2px #ddd;
836 font-size: 1.2em;
837 text-decoration: none;
838 vertical-align: center;
839 font-weight: normal;
840 display: inline-block;
841}
842
843
844.page-form .button.button-red {
845 background: #ac2925;
846}
847
848.page-form .submit-buttons {
849 margin-bottom: 10px;
850}
851
852@media screen and (min-width: 64em) {
853 .page-form .submit-buttons {
854 position: relative;
855 }
856
857 .page-form .submit-buttons .button.button-red {
858 position: absolute;
859 right: 5%;
860 }
861}
862
863@media screen and (max-width: 64em) {
864 .page-form .submit-buttons .button {
865 display: block;
866 margin: auto;
867 }
868}
869
870.page-form select {
871 color: #252525;
872}
873
874/**
875 * PAGE FORM - LIGHT
876 */
877.page-form-light div, .page-form-light p {
878 text-align: center;
879}
880
881/**
882 * PAGE FORM - COMPLETE
883 */
884.page-form-complete div, .page-form-complete p {
885 color: #252525;
886}
887
888.page-form-complete .form-label, .page-form-complete .form-input {
889 position: relative;
890 height: 60px;
891}
892
893.page-form-complete .form-label label,
894.page-form-complete .form-input input,
895.page-form-complete .form-input select.align,
896.page-form-complete .timezone {
897 position: absolute;
898 top: 50%;
899 transform: translateY(-50%);
900}
901
902.page-form-complete .form-label label {
903 text-align: right;
904 right: 0;
905 padding: 0 20px;
906}
907
908.page-form-complete .label-name {
909 font-weight: bold;
910}
911
912.page-form-complete .label-desc {
913 font-size: 0.8em;
914}
915
916.page-form-complete input[type="text"],
917.page-form-complete input[type="password"],
918.page-form-complete textarea {
919 margin: 0;
920}
921
922.page-form section {
923 margin: 10px 0 25px 0;
924}
925
926.page-form table {
927 margin: auto;
928 width: 90%;
929}
930
931.page-form table .order {
932 text-decoration: none;
933 color: #252525;
934}
935
936.page-form table, .page-form th, .page-form td {
937 border-width: 1px 0;
938 border-style: solid;
939 border-color: #aaaaaa;
940}
941
942.page-form th, .page-form td {
943 padding: 5px;
944
945}
946
947/* Awesomeplete fix */
948div.awesomplete {
949 width: inherit;
950}
951
952div.awesomplete > input {
953 display: inherit;
954}
955
956div.awesomplete > ul {
957 z-index: 9999;
958}
959
960.page-form .awesomplete {
961 width: 90%;
962}
963
964.page-form .awesomplete input {
965 width: 100%;
966}
967
968.page-form div.awesomplete > ul {
969 color: black;
970}
971
972form[name="linkform"].page-form {
973 overflow: visible;
974}
975
976@media screen and (max-width: 64em) {
977 .page-form-complete .form-label {
978 height: inherit;
979 }
980
981 .page-form-complete .form-label label,
982 .page-form-complete .form-input input,
983 .page-form-complete .timezone {
984 position: inherit;
985 top: inherit;
986 transform: translateY(0);
987 }
988
989 .page-form-complete .form-input input[type="checkbox"] {
990 position: absolute;
991 top: 50%;
992 right: 50%;
993 transform: translateY(-50%);
994 }
995
996 .page-form-complete .form-input {
997 text-align: center;
998 }
999
1000 .page-form-complete .form-label label {
1001 display: block;
1002 text-align: left;
1003 margin: 10px 0 0 0;
1004 }
1005
1006 .timezone-continent:after {
1007 content:"\a\a";
1008 white-space: pre;
1009 }
1010
1011 .page-form-complete .radio-buttons {
1012 text-align: left;
1013 padding: 5px 15px;
1014 }
1015}
1016
1017/**
1018 * Page visitor (page form extended)
1019 */
1020.page-visitor {
1021 color: #252525;
1022}
1023
1024#page404 {
1025 color: #3f3f3f;
1026}
1027
1028/**
1029 * EDIT LINK
1030 */
1031#editlinkform .created-date {
1032 color: #767676;
1033 margin-bottom: 10px;
1034}
1035
1036/**
1037 * LOGIN
1038 */
1039#login-form .remember-me {
1040 margin: 5px 0;
1041}
1042
1043/**
1044 * Search results
1045 */
1046.search-result a {
1047 color: white;
1048 text-decoration: none;
1049}
1050
1051.search-result .label-tag {
1052 border-color: white;
1053}
1054
1055.search-result .label-tag .remove {
1056 border-left: white 1px solid;
1057 padding: 0 0 0 5px;
1058 margin: 0 0 0 5px;
1059}
1060
1061.search-result .label-private {
1062 border: 1px solid white;
1063}
1064
1065/**
1066 * TOOLS
1067 */
1068.tools-item {
1069 margin: 10px 0;
1070}
1071
1072.tools-item .pure-button:hover {
1073 background-image: none;
1074 background-color: #1b926c;
1075 color: #f5f5f5;
1076}
1077
1078/**
1079 * PLUGIN ADMIN
1080 */
1081#pluginform .mobile-row {
1082 font-size: 0.9em;
1083}
1084
1085#pluginform .more {
1086 margin-top: 10px;
1087}
1088
1089@media screen and (max-width: 64em) {
1090 #pluginform .main-row, #pluginform .main-row td {
1091 border-bottom-style: none;
1092 }
1093
1094 #pluginform .mobile-row, #pluginform .mobile-row td {
1095 border-top-style: none;
1096 }
1097}
1098
1099/**
1100 * IMPORT
1101 */
1102#import-field {
1103 margin: 15px 0;
1104}
1105
1106/**
1107 * TAG CLOUD
1108 */
1109#cloudtag {
1110 padding: 10px;
1111 text-align: center;
1112}
1113
1114#cloudtag, #cloudtag a {
1115 color: #252525;
1116 text-decoration: none;
1117}
1118
1119#cloudtag .count {
1120 color: #7f7f7f;
1121}
1122
1123/**
1124 * TAG LIST
1125 */
1126#taglist {
1127 padding: 0 10px;
1128}
1129
1130#taglist a {
1131 color: #252525;
1132 text-decoration: none;
1133}
1134
1135#taglist .count {
1136 display: inline-block;
1137 width: 35px;
1138 text-align: right;
1139 color: #7f7f7f;
1140}
1141
1142#taglist .rename-tag-form {
1143 display: none;
1144}
1145
1146#taglist .delete-tag {
1147 color: #ac2925;
1148 display: none;
1149}
1150
1151#taglist .rename-tag {
1152 color: #0b5ea6;
1153}
1154
1155#taglist .validate-rename-tag {
1156 color: #1b926c;
1157}
1158
1159/**
1160 * Picture wall CSS
1161 */
1162#picwall_container {
1163 margin: 0 10px 10px 10px;
1164 color: #252525;
1165 background-color: #f5f5f5;
1166 clear: both;
1167}
1168
1169.picwall_pictureframe {
1170 margin: 2px;
1171 background-color: #f5f5f5;
1172 z-index: 5;
1173 position: relative;
1174 display: table-cell;
1175 vertical-align: middle;
1176 width: 90px;
1177 height: 90px;
1178 overflow: hidden;
1179 text-align: center;
1180 float: left;
1181}
1182
1183.b-lazy {
1184 -webkit-transition: opacity 500ms ease-in-out;
1185 -moz-transition: opacity 500ms ease-in-out;
1186 -o-transition: opacity 500ms ease-in-out;
1187 transition: opacity 500ms ease-in-out;
1188 opacity: 0;
1189}
1190.b-lazy.b-loaded {
1191 opacity: 1;
1192}
1193
1194.picwall_pictureframe img {
1195 max-width: 100%;
1196 height: auto;
1197 color: transparent;
1198} /* Adapt the width of the image */
1199
1200.picwall_pictureframe a {
1201 text-decoration: none;
1202}
1203
1204/* CSS to show title when hovering an image - no javascript required. */
1205.picwall_pictureframe span.info {
1206 display: none;
1207 font-family: Arial, sans-serif;
1208}
1209
1210.picwall_pictureframe:hover span.info {
1211 display: block;
1212 position: absolute;
1213 top: 0;
1214 left: 0;
1215 width: 90px;
1216 height: 90px;
1217 font-weight: bold;
1218 font-size: 9pt;
1219 color: #f5f5f5;
1220 text-align: left;
1221 background-color: rgba(0, 0, 0, 0.8);
1222}
1223
1224/**
1225 * DAILY
1226 */
1227.daily-desc {
1228 color: #7f7f7f;
1229 font-size: 0.8em;
1230}
1231
1232.daily-about a {
1233 color: #343434;
1234 text-decoration: none;
1235}
1236
1237.daily-about a:hover {
1238 color: #7f7f7f;
1239}
1240
1241.daily-about h3:before, .daily-about h3:after {
1242 display: block;
1243 content:"";
1244 background: linear-gradient(to right, #d5d4d4, #252525, #d5d4d4);
1245 height: 1px;
1246 width: 90%;
1247 margin: 10px auto;
1248}
1249
1250.daily-entry {
1251 padding: 0 10px;
1252}
1253
1254.daily-entry .daily-entry-title:after {
1255 display: block;
1256 content:"";
1257 background: linear-gradient(to right, #fff, #515151, #fff);
1258 height: 1px;
1259 width: 70%;
1260 margin: 5px auto;
1261}
1262
1263.daily-entry .daily-entry-title {
1264 margin: 10px 0 0 0;
1265}
1266
1267.daily-entry .daily-entry-title a {
1268 color: #000;
1269 text-decoration: none;
1270}
1271
1272.daily-entry .daily-entry-description {
1273 padding: 5px 5px 0 5px;
1274 font-size: 0.9em;
1275 text-align: justify;
1276 word-wrap: break-word;
1277}
1278
1279.daily-entry .daily-entry-tags {
1280 padding: 0 5px 5px 5px;
1281 font-size: 0.8em;
1282}
1283
1284.daily-entry-thumbnail {
1285 float: left;
1286 margin: 15px 5px 5px 15px;
1287}
1288
1289.daily-entry-description a {
1290 text-decoration: none;
1291 color: #1b926c;
1292}
1293
1294.daily-entry-description a:hover {
1295 text-shadow: 1px 1px #ddd;
1296}
1297
1298.daily-entry-description a:visited {
1299 color: #20b988;
1300}
1301
1302/*
1303 * Fix empty bookmarklet name in Firefox
1304 */
1305.pure-button {
1306 -moz-user-select: auto;
1307}
1308
1309.tag-sort {
1310 margin-top: 30px;
1311 text-align: center;
1312}
1313
1314.tag-sort a {
1315 display: inline-block;
1316 margin: 0 15px;
1317 color: white;
1318 text-decoration: none;
1319 font-weight: bold;
1320}
1321
1322/**
1323 * Markdown
1324 */
1325.markdown p {
1326 margin: 0 !important;
1327}
1328
1329.markdown p + p {
1330 margin: 0.5em 0 0 0 !important;
1331}
1332
1333.markdown *:first-child {
1334 margin-top: 0 !important;
1335}
1336
1337.markdown *:last-child {
1338 margin-bottom: 5px !important;
1339}
1340
1341/**
1342 * Pure Button
1343 */
1344.pure-button-success,
1345.pure-button-error,
1346.pure-button-warning,
1347.pure-button-primary,
1348.pure-button-shaarli,
1349.pure-button-secondary {
1350 color: white !important;
1351 border-radius: 4px;
1352 text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
1353}
1354
1355.pure-button-shaarli {
1356 background-color: #1B926C;
1357}