Merge pull request #1072 from ArthurHoaro/feature/modern-front-end

Manage frontend dependencies with npm/yarn and webpack
This commit is contained in:
ArthurHoaro 2018-03-28 19:08:06 +02:00 committed by GitHub
commit c81f1afc0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 5755 additions and 9118 deletions

View file

@ -10,7 +10,7 @@ trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.{htaccess,html,xml}]
[*.{htaccess,html,xml,js}]
indent_size = 2
[*.php]

12
.eslintrc.js Normal file
View file

@ -0,0 +1,12 @@
module.exports = {
"extends": "airbnb-base",
"env": {
"browser": true,
},
"rules": {
"no-param-reassign": 0, // manipulate DOM style properties
"no-restricted-globals": 0, // currently Shaarli uses alert/confirm, could be be improved later
"no-alert": 0, // currently Shaarli uses alert/confirm, could be be improved later
"no-cond-assign": [2, "except-parens"], // assignment in while loops is readable and avoid assignment duplication
}
};

27
.gitattributes vendored
View file

@ -25,16 +25,17 @@ Dockerfile text
*.mo binary
# Exclude from Git archives
.editorconfig export-ignore
.gitattributes export-ignore
.github export-ignore
.gitignore export-ignore
.travis.yml export-ignore
doc/**/*.json export-ignore
doc/**/*.md export-ignore
docker/ export-ignore
Doxyfile export-ignore
Makefile export-ignore
mkdocs.yml export-ignore
phpunit.xml export-ignore
tests/ export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.github export-ignore
.gitignore export-ignore
.travis.yml export-ignore
doc/**/*.json export-ignore
doc/**/*.md export-ignore
docker/ export-ignore
Doxyfile export-ignore
Makefile export-ignore
node_modules/ export-ignore
mkdocs.yml export-ignore
phpunit.xml export-ignore
tests/ export-ignore

10
.gitignore vendored
View file

@ -36,3 +36,13 @@ doc/html/
tpl/*
!tpl/default
!tpl/vintage
# Front end
node_modules
tpl/default/js
tpl/default/css
tpl/default/fonts
tpl/default/img
tpl/vintage/js
tpl/vintage/css
tpl/vintage/img

View file

@ -2,20 +2,22 @@ sudo: false
dist: trusty
language: php
cache:
yarn: true
directories:
- $HOME/.composer/cache
- $HOME/.cache/yarn
php:
- 7.2
- 7.1
- 7.0
- 5.6
install:
- composer self-update
- yarn install
- composer install --prefer-dist
- locale -a
before_script:
- PATH=${PATH//:\.\/node_modules\/\.bin/}
script:
- make clean
- make check_permissions
- make eslint
- make all_tests

View file

@ -157,15 +157,23 @@ composer_dependencies: clean
composer install --no-dev --prefer-dist
find vendor/ -name ".git" -type d -exec rm -rf {} +
### download 3rd-party frontend libraries
frontend_dependencies:
yarn install
### Build frontend dependencies
build_frontend: frontend_dependencies
yarn run build
### generate a release tarball and include 3rd-party dependencies and translations
release_tar: composer_dependencies htmldoc translate
release_tar: composer_dependencies htmldoc translate build_frontend
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).tar HEAD
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^vendor|$(ARCHIVE_PREFIX)vendor|" vendor/
tar rvf $(ARCHIVE_VERSION).tar --transform "s|^doc/html|$(ARCHIVE_PREFIX)doc/html|" doc/html/
gzip $(ARCHIVE_VERSION).tar
### generate a release zip and include 3rd-party dependencies and translations
release_zip: composer_dependencies htmldoc translate
release_zip: composer_dependencies htmldoc translate build_frontend
git archive --prefix=$(ARCHIVE_PREFIX) -o $(ARCHIVE_VERSION).zip -9 HEAD
mkdir -p $(ARCHIVE_PREFIX)/{doc,vendor}
rsync -a doc/html/ $(ARCHIVE_PREFIX)doc/html/
@ -207,3 +215,8 @@ htmldoc:
### Generate Shaarli's translation compiled file (.mo)
translate:
@find inc/languages/ -name shaarli.po -execdir msgfmt shaarli.po -o shaarli.mo \;
### Run ESLint check against Shaarli's JS files
eslint:
@yarn run eslint assets/vintage/js/
@yarn run eslint assets/default/js/

13
assets/.htaccess Normal file
View file

@ -0,0 +1,13 @@
<IfModule version_module>
<IfVersion >= 2.4>
Require all denied
</IfVersion>
<IfVersion < 2.4>
Allow from none
Deny from all
</IfVersion>
</IfModule>
<IfModule !version_module>
Require all denied
</IfModule>

View file

@ -0,0 +1,10 @@
import Blazy from 'blazy';
(() => {
const picwall = document.getElementById('picwall_container');
if (picwall != null) {
// Suppress ESLint error because that's how bLazy works
/* eslint-disable no-new */
new Blazy();
}
})();

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View file

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 530 B

View file

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

606
assets/default/js/base.js Normal file
View file

@ -0,0 +1,606 @@
import Awesomplete from 'awesomplete';
/**
* Find a parent element according to its tag and its attributes
*
* @param element Element where to start the search
* @param tagName Expected parent tag name
* @param attributes Associative array of expected attributes (name=>value).
*
* @returns Found element or null.
*/
function findParent(element, tagName, attributes) {
const parentMatch = key => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1;
while (element) {
if (element.tagName.toLowerCase() === tagName) {
if (Object.keys(attributes).find(parentMatch)) {
return element;
}
}
element = element.parentElement;
}
return null;
}
/**
* Ajax request to refresh the CSRF token.
*/
function refreshToken() {
const xhr = new XMLHttpRequest();
xhr.open('GET', '?do=token');
xhr.onload = () => {
const token = document.getElementById('token');
token.setAttribute('value', xhr.responseText);
};
xhr.send();
}
function createAwesompleteInstance(element, tags = []) {
const awesome = new Awesomplete(Awesomplete.$(element));
// Tags are separated by a space
awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
// Insert new selected tag in the input
awesome.replace = (text) => {
const before = awesome.input.value.match(/^.+ \s*|/)[0];
awesome.input.value = `${before}${text} `;
};
// Highlight found items
awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(/[^ ]*$/)[0]);
// Don't display already selected items
const reg = /(\w+) /g;
let match;
awesome.data = (item, input) => {
while ((match = reg.exec(input))) {
if (item === match[1]) {
return '';
}
}
return item;
};
awesome.minChars = 1;
if (tags.length) {
awesome.list = tags;
}
return awesome;
}
/**
* Update awesomplete list of tag for all elements matching the given selector
*
* @param selector CSS selector
* @param tags Array of tags
* @param instances List of existing awesomplete instances
*/
function updateAwesompleteList(selector, tags, instances) {
if (instances.length === 0) {
// First load: create Awesomplete instances
const elements = document.querySelectorAll(selector);
[...elements].forEach((element) => {
instances.push(createAwesompleteInstance(element, tags));
});
} else {
// Update awesomplete tag list
instances.map((item) => {
item.list = tags;
return item;
});
}
return instances;
}
/**
* html_entities in JS
*
* @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript
*/
function htmlEntities(str) {
return str.replace(/[\u00A0-\u9999<>&]/gim, i => `&#${i.charCodeAt(0)};`);
}
function activateFirefoxSocial(node) {
const loc = location.href;
const baseURL = loc.substring(0, loc.lastIndexOf('/') + 1);
const data = {
name: document.title,
description: document.getElementById('translation-delete-link').innerHTML,
author: 'Shaarli',
version: '1.0.0',
iconURL: `${baseURL}/images/favicon.ico`,
icon32URL: `${baseURL}/images/favicon.ico`,
icon64URL: `${baseURL}/images/favicon.ico`,
shareURL: `${baseURL}?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi`,
homepageURL: baseURL,
};
node.setAttribute('data-service', JSON.stringify(data));
const activate = new CustomEvent('ActivateSocialFeature');
node.dispatchEvent(activate);
}
/**
* Add the class 'hidden' to city options not attached to the current selected continent.
*
* @param cities List of <option> elements
* @param currentContinent Current selected continent
* @param reset Set to true to reset the selected value
*/
function hideTimezoneCities(cities, currentContinent, reset = null) {
let first = true;
if (reset == null) {
reset = false;
}
[...cities].forEach((option) => {
if (option.getAttribute('data-continent') !== currentContinent) {
option.className = 'hidden';
} else {
option.className = '';
if (reset === true && first === true) {
option.setAttribute('selected', 'selected');
first = false;
}
}
});
}
/**
* Retrieve an element up in the tree from its class name.
*/
function getParentByClass(el, className) {
const p = el.parentNode;
if (p == null || p.classList.contains(className)) {
return p;
}
return getParentByClass(p, className);
}
function toggleHorizontal() {
[...document.getElementById('shaarli-menu').querySelectorAll('.menu-transform')].forEach((el) => {
el.classList.toggle('pure-menu-horizontal');
});
}
function toggleMenu(menu) {
// set timeout so that the panel has a chance to roll up
// before the menu switches states
if (menu.classList.contains('open')) {
setTimeout(toggleHorizontal, 500);
} else {
toggleHorizontal();
}
menu.classList.toggle('open');
document.getElementById('menu-toggle').classList.toggle('x');
}
function closeMenu(menu) {
if (menu.classList.contains('open')) {
toggleMenu(menu);
}
}
function toggleFold(button, description, thumb) {
// Switch fold/expand - up = fold
if (button.classList.contains('fa-chevron-up')) {
button.title = document.getElementById('translation-expand').innerHTML;
if (description != null) {
description.style.display = 'none';
}
if (thumb != null) {
thumb.style.display = 'none';
}
} else {
button.title = document.getElementById('translation-fold').innerHTML;
if (description != null) {
description.style.display = 'block';
}
if (thumb != null) {
thumb.style.display = 'block';
}
}
button.classList.toggle('fa-chevron-down');
button.classList.toggle('fa-chevron-up');
}
function removeClass(element, classname) {
element.className = element.className.replace(new RegExp(`(?:^|\\s)${classname}(?:\\s|$)`), ' ');
}
function init(description) {
function resize() {
/* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
const scrollTop = window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body).scrollTop;
description.style.height = 'auto';
description.style.height = `${description.scrollHeight + 10}px`;
window.scrollTo(0, scrollTop);
}
/* 0-timeout to get the already changed text */
function delayedResize() {
window.setTimeout(resize, 0);
}
const observe = (element, event, handler) => {
element.addEventListener(event, handler, false);
};
observe(description, 'change', resize);
observe(description, 'cut', delayedResize);
observe(description, 'paste', delayedResize);
observe(description, 'drop', delayedResize);
observe(description, 'keydown', delayedResize);
resize();
}
(() => {
/**
* Handle responsive menu.
* Source: http://purecss.io/layouts/tucked-menu-vertical/
*/
const menu = document.getElementById('shaarli-menu');
const WINDOW_CHANGE_EVENT = ('onorientationchange' in window) ? 'orientationchange' : 'resize';
const menuToggle = document.getElementById('menu-toggle');
if (menuToggle != null) {
menuToggle.addEventListener('click', () => toggleMenu(menu));
}
window.addEventListener(WINDOW_CHANGE_EVENT, () => closeMenu(menu));
/**
* Fold/Expand shaares description and thumbnail.
*/
const foldAllButtons = document.getElementsByClassName('fold-all');
const foldButtons = document.getElementsByClassName('fold-button');
[...foldButtons].forEach((foldButton) => {
// Retrieve description
let description = null;
let thumbnail = null;
const linklistItem = getParentByClass(foldButton, 'linklist-item');
if (linklistItem != null) {
description = linklistItem.querySelector('.linklist-item-description');
thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
if (description != null || thumbnail != null) {
foldButton.style.display = 'inline';
}
}
foldButton.addEventListener('click', (event) => {
event.preventDefault();
toggleFold(event.target, description, thumbnail);
});
});
if (foldAllButtons != null) {
[].forEach.call(foldAllButtons, (foldAllButton) => {
foldAllButton.addEventListener('click', (event) => {
event.preventDefault();
const state = foldAllButton.firstElementChild.getAttribute('class').indexOf('down') !== -1 ? 'down' : 'up';
[].forEach.call(foldButtons, (foldButton) => {
if ((foldButton.firstElementChild.classList.contains('fa-chevron-up') && state === 'down')
|| (foldButton.firstElementChild.classList.contains('fa-chevron-down') && state === 'up')
) {
return;
}
// Retrieve description
let description = null;
let thumbnail = null;
const linklistItem = getParentByClass(foldButton, 'linklist-item');
if (linklistItem != null) {
description = linklistItem.querySelector('.linklist-item-description');
thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
if (description != null || thumbnail != null) {
foldButton.style.display = 'inline';
}
}
toggleFold(foldButton.firstElementChild, description, thumbnail);
});
foldAllButton.firstElementChild.classList.toggle('fa-chevron-down');
foldAllButton.firstElementChild.classList.toggle('fa-chevron-up');
foldAllButton.title = state === 'down'
? document.getElementById('translation-fold-all').innerHTML
: document.getElementById('translation-expand-all').innerHTML;
});
});
}
/**
* Confirmation message before deletion.
*/
const deleteLinks = document.querySelectorAll('.confirm-delete');
[...deleteLinks].forEach((deleteLink) => {
deleteLink.addEventListener('click', (event) => {
if (!confirm(document.getElementById('translation-delete-link').innerHTML)) {
event.preventDefault();
}
});
});
/**
* Close alerts
*/
const closeLinks = document.querySelectorAll('.pure-alert-close');
[...closeLinks].forEach((closeLink) => {
closeLink.addEventListener('click', (event) => {
const alert = getParentByClass(event.target, 'pure-alert-closable');
alert.style.display = 'none';
});
});
/**
* New version dismiss.
* Hide the message for one week using localStorage.
*/
const newVersionDismiss = document.getElementById('new-version-dismiss');
const newVersionMessage = document.querySelector('.new-version-message');
if (newVersionMessage != null
&& localStorage.getItem('newVersionDismiss') != null
&& parseInt(localStorage.getItem('newVersionDismiss'), 10) + (7 * 24 * 60 * 60 * 1000) > (new Date()).getTime()
) {
newVersionMessage.style.display = 'none';
}
if (newVersionDismiss != null) {
newVersionDismiss.addEventListener('click', () => {
localStorage.setItem('newVersionDismiss', (new Date()).getTime().toString());
});
}
const hiddenReturnurl = document.getElementsByName('returnurl');
if (hiddenReturnurl != null) {
hiddenReturnurl.value = window.location.href;
}
/**
* Autofocus text fields
*/
const autofocusElements = document.querySelectorAll('.autofocus');
let breakLoop = false;
[].forEach.call(autofocusElements, (autofocusElement) => {
if (autofocusElement.value === '' && !breakLoop) {
autofocusElement.focus();
breakLoop = true;
}
});
/**
* Handle sub menus/forms
*/
const openers = document.getElementsByClassName('subheader-opener');
if (openers != null) {
[...openers].forEach((opener) => {
opener.addEventListener('click', (event) => {
event.preventDefault();
const id = opener.getAttribute('data-open-id');
const sub = document.getElementById(id);
if (sub != null) {
[...document.getElementsByClassName('subheader-form')].forEach((element) => {
if (element !== sub) {
removeClass(element, 'open');
}
});
sub.classList.toggle('open');
}
});
});
}
/**
* Remove CSS target padding (for fixed bar)
*/
if (location.hash !== '') {
const anchor = document.getElementById(location.hash.substr(1));
if (anchor != null) {
const padsize = anchor.clientHeight;
window.scroll(0, window.scrollY - padsize);
anchor.style.paddingTop = '0';
}
}
/**
* Text area resizer
*/
const description = document.getElementById('lf_description');
if (description != null) {
init(description);
// Submit editlink form with CTRL + Enter in the text area.
description.addEventListener('keydown', (event) => {
if (event.ctrlKey && event.keyCode === 13) {
document.getElementById('button-save-edit').click();
}
});
}
/**
* Bookmarklet alert
*/
const bookmarkletLinks = document.querySelectorAll('.bookmarklet-link');
const bkmMessage = document.getElementById('bookmarklet-alert');
[].forEach.call(bookmarkletLinks, (link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
alert(bkmMessage.value);
});
});
/**
* Firefox Social
*/
const ffButton = document.getElementById('ff-social-button');
if (ffButton != null) {
ffButton.addEventListener('click', (event) => {
activateFirefoxSocial(event.target);
});
}
const continent = document.getElementById('continent');
const city = document.getElementById('city');
if (continent != null && city != null) {
continent.addEventListener('change', () => {
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
});
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
}
/**
* Bulk actions
*/
const linkCheckboxes = document.querySelectorAll('.delete-checkbox');
const bar = document.getElementById('actions');
[...linkCheckboxes].forEach((checkbox) => {
checkbox.style.display = 'inline-block';
checkbox.addEventListener('click', () => {
const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
const count = [...linkCheckedCheckboxes].length;
if (count === 0 && bar.classList.contains('open')) {
bar.classList.toggle('open');
} else if (count > 0 && !bar.classList.contains('open')) {
bar.classList.toggle('open');
}
});
});
const deleteButton = document.getElementById('actions-delete');
const token = document.getElementById('token');
if (deleteButton != null && token != null) {
deleteButton.addEventListener('click', (event) => {
event.preventDefault();
const links = [];
const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
[...linkCheckedCheckboxes].forEach((checkbox) => {
links.push({
id: checkbox.value,
title: document.querySelector(`.linklist-item[data-id="${checkbox.value}"] .linklist-link`).innerHTML,
});
});
let message = `Are you sure you want to delete ${links.length} links?\n`;
message += 'This action is IRREVERSIBLE!\n\nTitles:\n';
const ids = [];
links.forEach((item) => {
message += ` - ${item.title}\n`;
ids.push(item.id);
});
if (window.confirm(message)) {
window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`;
}
});
}
/**
* Tag list operations
*
* TODO: support error code in the backend for AJAX requests
*/
const tagList = document.querySelector('input[name="taglist"]');
let existingTags = tagList ? tagList.value.split(' ') : [];
let awesomepletes = [];
// Display/Hide rename form
const renameTagButtons = document.querySelectorAll('.rename-tag');
[...renameTagButtons].forEach((rename) => {
rename.addEventListener('click', (event) => {
event.preventDefault();
const block = findParent(event.target, 'div', { class: 'tag-list-item' });
const form = block.querySelector('.rename-tag-form');
if (form.style.display === 'none' || form.style.display === '') {
form.style.display = 'block';
} else {
form.style.display = 'none';
}
block.querySelector('input').focus();
});
});
// Rename a tag with an AJAX request
const renameTagSubmits = document.querySelectorAll('.validate-rename-tag');
[...renameTagSubmits].forEach((rename) => {
rename.addEventListener('click', (event) => {
event.preventDefault();
const block = findParent(event.target, 'div', { class: 'tag-list-item' });
const input = block.querySelector('.rename-tag-input');
const totag = input.value.replace('/"/g', '\\"');
if (totag.trim() === '') {
return;
}
const refreshedToken = document.getElementById('token').value;
const fromtag = block.getAttribute('data-tag');
const xhr = new XMLHttpRequest();
xhr.open('POST', '?do=changetag');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
if (xhr.status !== 200) {
alert(`An error occurred. Return code: ${xhr.status}`);
location.reload();
} else {
block.setAttribute('data-tag', totag);
input.setAttribute('name', totag);
input.setAttribute('value', totag);
findParent(input, 'div', { class: 'rename-tag-form' }).style.display = 'none';
block.querySelector('a.tag-link').innerHTML = htmlEntities(totag);
block.querySelector('a.tag-link').setAttribute('href', `?searchtags=${encodeURIComponent(totag)}`);
block.querySelector('a.rename-tag').setAttribute('href', `?do=changetag&fromtag=${encodeURIComponent(totag)}`);
// Refresh awesomplete values
existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag));
awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
}
};
xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
refreshToken();
});
});
// Validate input with enter key
const renameTagInputs = document.querySelectorAll('.rename-tag-input');
[...renameTagInputs].forEach((rename) => {
rename.addEventListener('keypress', (event) => {
if (event.keyCode === 13) { // enter
findParent(event.target, 'div', { class: 'tag-list-item' }).querySelector('.validate-rename-tag').click();
}
});
});
// Delete a tag with an AJAX query (alert popup confirmation)
const deleteTagButtons = document.querySelectorAll('.delete-tag');
[...deleteTagButtons].forEach((rename) => {
rename.style.display = 'inline';
rename.addEventListener('click', (event) => {
event.preventDefault();
const block = findParent(event.target, 'div', { class: 'tag-list-item' });
const tag = block.getAttribute('data-tag');
const refreshedToken = document.getElementById('token');
if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
const xhr = new XMLHttpRequest();
xhr.open('POST', '?do=changetag');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
block.remove();
};
xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`));
refreshToken();
existingTags = existingTags.filter(tagItem => tagItem !== tag);
awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
}
});
});
const autocompleteFields = document.querySelectorAll('input[data-multiple]');
[...autocompleteFields].forEach((autocompleteField) => {
awesomepletes.push(createAwesompleteInstance(autocompleteField));
});
})();

View file

@ -0,0 +1,81 @@
/**
* Change the position counter of a row.
*
* @param elem Element Node to change.
* @param toPos int New position.
*/
function changePos(elem, toPos) {
const elemName = elem.getAttribute('data-line');
elem.setAttribute('data-order', toPos);
const hiddenInput = document.querySelector(`[name="order_${elemName}"]`);
hiddenInput.setAttribute('value', toPos);
}
/**
* Move a row up or down.
*
* @param pos Element Node to move.
* @param move int Move: +1 (down) or -1 (up)
*/
function changeOrder(pos, move) {
const newpos = parseInt(pos, 10) + move;
let lines = document.querySelectorAll(`[data-order="${pos}"]`);
const changelines = document.querySelectorAll(`[data-order="${newpos}"]`);
// If we go down reverse lines to preserve the rows order
if (move > 0) {
lines = [].slice.call(lines).reverse();
}
for (let i = 0; i < lines.length; i += 1) {
const parent = changelines[0].parentNode;
changePos(lines[i], newpos);
changePos(changelines[i], parseInt(pos, 10));
const changeItem = move < 0 ? changelines[0] : changelines[changelines.length - 1].nextSibling;
parent.insertBefore(lines[i], changeItem);
}
}
/**
* Move a row up in the table.
*
* @param pos int row counter.
*
* @return false
*/
function orderUp(pos) {
if (pos !== 0) {
changeOrder(pos, -1);
}
}
/**
* Move a row down in the table.
*
* @param pos int row counter.
*
* @returns false
*/
function orderDown(pos) {
const lastpos = parseInt(document.querySelector('[data-order]:last-child').getAttribute('data-order'), 10);
if (pos !== lastpos) {
changeOrder(pos, 1);
}
}
(() => {
/**
* Plugin admin order
*/
const orderPA = document.querySelectorAll('.order');
[...orderPA].forEach((link) => {
link.addEventListener('click', (event) => {
event.preventDefault();
if (event.target.classList.contains('order-up')) {
orderUp(parseInt(event.target.parentNode.parentNode.getAttribute('data-order'), 10));
} else if (event.target.classList.contains('order-down')) {
orderDown(parseInt(event.target.parentNode.parentNode.getAttribute('data-order'), 10));
}
});
});
})();

View file

@ -1,3 +1,11 @@
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome.scss";
@import '~purecss/build/pure.css';
@import '~purecss/build/grids-responsive.css';
@import '~pure-extras/css/pure-extras.css';
@import '~awesomplete/awesomplete.css';
/**
* General
*/
@ -39,10 +47,10 @@ pre {
font-weight: 400;
font-style: normal;
src:
local('Roboto'),
local('Roboto-Regular'),
url('../fonts/Roboto-Regular.woff2') format('woff2'),
url('../fonts/Roboto-Regular.woff') format('woff');
local('Roboto'),
local('Roboto-Regular'),
url('../fonts/Roboto-Regular.woff2') format('woff2'),
url('../fonts/Roboto-Regular.woff') format('woff');
}
@font-face {
@ -50,10 +58,10 @@ pre {
font-weight: 700;
font-style: normal;
src:
local('Roboto'),
local('Roboto-Bold'),
url('../fonts/Roboto-Bold.woff2') format('woff2'),
url('../fonts/Roboto-Bold.woff') format('woff');
local('Roboto'),
local('Roboto-Bold'),
url('../fonts/Roboto-Bold.woff2') format('woff2'),
url('../fonts/Roboto-Bold.woff') format('woff');
}
body, .pure-g [class*="pure-u"] {
@ -873,10 +881,6 @@ body, .pure-g [class*="pure-u"] {
/**
* PAGE FORM - COMPLETE
*/
.page-form-complete {
#background: #f5f5f5;
}
.page-form-complete div, .page-form-complete p {
color: #252525;
}

View file

@ -113,7 +113,7 @@ a.bigbutton, #pageheader a.bigbutton {
}
#pageheader #logo {
background-image: url('../../../images/logo.png');
background-image: url('../img/logo.png');
background-repeat: no-repeat;
float: left;
margin: 0 10px 0 10px;
@ -433,7 +433,7 @@ a.bigbutton, #pageheader a.bigbutton {
}
#linklist li.private {
background: url('../images/private.png') no-repeat 4px center;
background: url('../img/private.png') no-repeat 4px center;
padding-left: 30px;
}
@ -465,7 +465,7 @@ a.bigbutton, #pageheader a.bigbutton {
}
.linkdate a {
background-image: url('../images/calendar.png');
background-image: url('../img/calendar.png');
padding: 2px 0 3px 20px;
background-repeat: no-repeat;
text-decoration: none;
@ -516,7 +516,7 @@ a.bigbutton, #pageheader a.bigbutton {
height: 20px;
border-radius: 3px;
cursor: pointer;
background-image: url('../images/tag_blue.png');
background-image: url('../img/tag_blue.png');
background-repeat: no-repeat;
background-position: 3px center;
background-color: #ffffff;
@ -762,7 +762,7 @@ div.daily {
/* Background paper texture by BashCorpo:
http://www.bashcorpo.dk/textures.php
http://bashcorpo.deviantart.com/art/Grungy-paper-texture-v-5-22966998 */
background-image: url("../images/Paper_texture_v5_by_bashcorpo_w1000.jpg");
background-image: url("../img/Paper_texture_v5_by_bashcorpo_w1000.jpg");
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
@ -860,7 +860,7 @@ div.dailyEntryThumbnail {
width: 100%;
text-align: center;
background-color: rgb(128, 128, 128);
background: url(../images/50pc_transparent.png);
background: url(../img/50pc_transparent.png);
padding: 4px 0px 2px 0px;
}

View file

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 599 B

View file

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View file

Before

Width:  |  Height:  |  Size: 650 B

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/vintage/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

Before

Width:  |  Height:  |  Size: 813 B

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

View file

Before

Width:  |  Height:  |  Size: 720 B

After

Width:  |  Height:  |  Size: 720 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 714 B

30
assets/vintage/js/base.js Normal file
View file

@ -0,0 +1,30 @@
import Awesomplete from 'awesomplete';
import 'awesomplete/awesomplete.css';
(() => {
const awp = Awesomplete.$;
const autocompleteFields = document.querySelectorAll('input[data-multiple]');
[...autocompleteFields].forEach((autocompleteField) => {
const awesomplete = new Awesomplete(awp(autocompleteField));
awesomplete.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
awesomplete.replace = (text) => {
const before = awesomplete.input.value.match(/^.+ \s*|/)[0];
awesomplete.input.value = `${before}${text} `;
};
awesomplete.minChars = 1;
autocompleteField.addEventListener('input', () => {
const proposedTags = autocompleteField.getAttribute('data-list').replace(/,/g, '').split(' ');
const reg = /(\w+) /g;
let match;
while ((match = reg.exec(autocompleteField.value)) !== null) {
const id = proposedTags.indexOf(match[1]);
if (id !== -1) {
proposedTags.splice(id, 1);
}
}
awesomplete.list = proposedTags;
});
});
})();

View file

@ -2,8 +2,8 @@
A [`Makefile`](https://github.com/shaarli/Shaarli/blob/master/Makefile) is available to perform project-related operations:
- Documentation - generate a local HTML copy of the GitHub wiki
- [Static analysis](Static analysis) - check that the code is compliant to PHP conventions
- [Unit tests](Unit tests) - ensure there are no regressions introduced by new commits
- [Static analysis](Static-analysis) - check that the code is compliant to PHP conventions
- [Unit tests](Unit-tests) - ensure there are no regressions introduced by new commits
## Automatic builds
[Travis CI](http://docs.travis-ci.com/) is a Continuous Integration build server, that runs a build:
@ -17,7 +17,8 @@ Each build job:
- updates Composer
- installs 3rd-party test dependencies with Composer
- runs [Unit tests](Unit tests)
- runs [Unit tests](Unit-tests)
- runs ESLint check
After all jobs have finished, Travis returns the results to GitHub:

View file

@ -3,8 +3,11 @@
Please have a look at the following pages:
- [Contributing to Shaarli](https://github.com/shaarli/Shaarli/tree/master/CONTRIBUTING.md)
- [Static analysis](Static analysis) - patches should try to stick to the [PHP Standard Recommendations](http://www.php-fig.org/psr/) (PSR), especially:
- [Static analysis](Static-analysis) - patches should try to stick to the
[PHP Standard Recommendations](http://www.php-fig.org/psr/) (PSR), especially:
- [PSR-1](http://www.php-fig.org/psr/psr-1/) - Basic Coding Standard
- [PSR-2](http://www.php-fig.org/psr/psr-2/) - Coding Style Guide
- [Unit tests](Unit tests)
- [GnuPG signature](GnuPG signature) for tags/releases
- [Unit tests](Unit-tests)
- Javascript linting - Shaarli uses [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript).
Run `make eslint` to check JS style.
- [GnuPG signature](GnuPG-signature) for tags/releases

View file

@ -18,12 +18,18 @@ Here is the directory structure of Shaarli and the purpose of the different file
├── utils # utilities to ease testing
│ └── ReferenceLinkDB.php
└── UtilsTest.php
assets/
├── common/ # Assets shared by multiple themes
├── ...
├── default/ # Assets for the default template, before compilation
├── fonts/ # Font files
├── img/ # Images used by the default theme
├── js/ # JavaScript files in ES6 syntax
├── scss/ # SASS files
└── vintage/ # Assets for the vintage template, before compilation
└── ...
COPYING # Shaarli license
inc/ # static assets and 3rd party libraries
├── awesomplete.* # tags autocompletion library
├── blazy.* # picture wall lazy image loading library
├── shaarli.css, reset.css # Shaarli stylesheet.
├── qr.* # qr code generation library
└── rain.tpl.class.php # RainTPL templating library
images/ # Images and icons used in Shaarli
data/ # data storage: bookmark database, configuration, logs, banlist...
@ -33,6 +39,13 @@ Here is the directory structure of Shaarli and the purpose of the different file
├── lastupdatecheck.txt # Update check timestamp file
└── log.txt # login/IPban log.
tpl/ # RainTPL templates for Shaarli. They are used to build the pages.
├── default/ # Default Shaarli theme
├── fonts/ # Font files
├── img/ # Images
├── js/ # JavaScript files compiled by Babel and compatible with all browsers
├── css/ # CSS files compiled with SASS
└── vintage/ # Legacy Shaarli theme
└── ...
cache/ # thumbnails cache
# This directory is automatically created. You can erase it anytime you want.
tmp/ # Temporary directory for compiled RainTPL templates.

View file

@ -38,12 +38,14 @@ $ mv Shaarli /path/to/shaarli/
Cloning using `git` or downloading Github branches as zip files requires additional steps:
* Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies.
* Install [yarn](https://yarnpkg.com/lang/en/docs/install/) to build the frontend dependencies.
* Install [python3-virtualenv](https://pypi.python.org/pypi/virtualenv) to build the local HTML documentation.
```
$ mkdir -p /path/to/shaarli && cd /path/to/shaarli/
$ git clone -b latest https://github.com/shaarli/Shaarli.git .
$ composer install --no-dev --prefer-dist
$ make build_frontend
$ make translate
$ make htmldoc
```
@ -91,7 +93,9 @@ $ composer install --no-dev --prefer-dist
_Use at your own risk!_
Install [Composer](Unit-tests.md#install_composer) to manage Shaarli dependencies.
Install [Composer](Unit-tests.md#install_composer) to manage Shaarli PHP dependencies,
and [yarn](https://yarnpkg.com/lang/en/docs/install/)
for front-end dependencies.
To get the latest changes from the `master` branch:
@ -101,6 +105,7 @@ $ git clone https://github.com/shaarli/Shaarli.git -b master /path/to/shaarli/
# install/update third-party dependencies
$ cd /path/to/shaarli
$ composer install --no-dev --prefer-dist
$ make build_frontend
$ make translate
$ make htmldoc
```

View file

@ -83,6 +83,13 @@ $ make translate
If you use translations in gettext mode, reload your web server.
Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install
[yarn](https://yarnpkg.com/lang/en/docs/install/):
```bash
$ make build_frontend
```
### Migrating and upgrading from Sebsauvage's repository
If you have installed Shaarli from [Sebsauvage's original Git repository](https://github.com/sebsauvage/Shaarli), you can use [Git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to update your working copy.
@ -170,6 +177,13 @@ $ make translate
If you use translations in gettext mode, reload your web server.
Shaarli >= `v0.10.0` manages its front-end dependencies with nodejs. You need to install
[yarn](https://yarnpkg.com/lang/en/docs/install/):
```bash
$ make build_frontend
```
Optionally, you can delete information related to the legacy version:
```bash

View file

@ -1,66 +0,0 @@
/** @licstart The following is the entire license notice for the
* JavaScript code in this page.
*
* Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
* (c) 2011-2017 The Shaarli Community, see AUTHORS
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would
* be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must
* not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* @licend The above is the entire license notice
* for the JavaScript code in this page.
*/
var awp = Awesomplete.$;
var autocompleteFields = document.querySelectorAll('input[data-multiple]');
[].forEach.call(autocompleteFields, function(autocompleteField) {
awesomplete = new Awesomplete(awp(autocompleteField), {
filter: function (text, input) {
return Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
},
replace: function (text) {
var before = this.input.value.match(/^.+ \s*|/)[0];
this.input.value = before + text + " ";
},
minChars: 1
})
});
/**
* Remove already selected items from autocompletion list.
* HTML list is never updated, so removing a tag will add it back to awesomplete.
*
* FIXME: This a workaround waiting for awesomplete to handle this.
* https://github.com/LeaVerou/awesomplete/issues/16749
*/
function awesompleteUniqueTag(selector) {
var input = document.querySelector(selector);
input.addEventListener('input', function()
{
proposedTags = input.getAttribute('data-list').replace(/,/g, '').split(' ');
reg = /(\w+) /g;
while((match = reg.exec(input.value)) !== null) {
id = proposedTags.indexOf(match[1]);
if(id != -1 ) {
proposedTags.splice(id, 1);
}
}
awesomplete.list = proposedTags;
});
}

View file

@ -1,97 +0,0 @@
[hidden] { display: none; }
.visually-hidden {
position: absolute;
clip: rect(0, 0, 0, 0);
}
div.awesomplete {
display: inline-block;
position: relative;
width: 100%;
}
div.awesomplete > input {
display: block;
}
div.awesomplete > ul {
position: absolute;
left: 0;
z-index: 1;
min-width: 100%;
box-sizing: border-box;
list-style: none;
padding: 0;
border-radius: .3em;
margin: .2em 0 0;
background: #FFF;
border: 1px solid rgba(0,0,0,.3);
box-shadow: .05em .2em .6em rgba(0,0,0,.2);
text-shadow: none;
}
div.awesomplete > ul[hidden],
div.awesomplete > ul:empty {
display: none;
}
@supports (transform: scale(0)) {
div.awesomplete > ul {
transition: .3s cubic-bezier(.4,.2,.5,1.4);
transform-origin: 1.43em -.43em;
}
div.awesomplete > ul[hidden],
div.awesomplete > ul:empty {
opacity: 0;
transform: scale(0);
display: block;
transition-timing-function: ease;
}
}
/* Pointer */
div.awesomplete > ul:before {
content: "";
position: absolute;
top: -.43em;
left: 1em;
width: 0; height: 0;
padding: .4em;
background: white;
border: inherit;
border-right: 0;
border-bottom: 0;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
div.awesomplete > ul > li {
position: relative;
padding: .2em .5em;
cursor: pointer;
}
div.awesomplete > ul > li:hover {
background: hsl(200, 40%, 80%);
color: black;
}
div.awesomplete > ul > li[aria-selected="true"] {
background: hsl(205, 40%, 40%);
color: white;
}
div.awesomplete mark {
background: hsl(65, 100%, 50%);
}
div.awesomplete li:hover mark {
background: hsl(68, 101%, 41%);
}
div.awesomplete li[aria-selected="true"] mark {
background: hsl(86, 102%, 21%);
color: inherit;
}

View file

@ -1,450 +0,0 @@
/**
* Simple, lightweight, usable local autocomplete library for modern browsers
* Because there werent enough autocomplete scripts in the world? Because Im completely insane and have NIH syndrome? Probably both. :P
* @author Lea Verou http://leaverou.github.io/awesomplete
* MIT license
*/
(function () {
var _ = function (input, o) {
var me = this;
// Setup
this.isOpened = false;
this.input = $(input);
this.input.setAttribute("autocomplete", "off");
this.input.setAttribute("aria-autocomplete", "list");
o = o || {};
configure(this, {
minChars: 2,
maxItems: 10,
autoFirst: false,
data: _.DATA,
filter: _.FILTER_CONTAINS,
sort: _.SORT_BYLENGTH,
item: _.ITEM,
replace: _.REPLACE
}, o);
this.index = -1;
// Create necessary elements
this.container = $.create("div", {
className: "awesomplete",
around: input
});
this.ul = $.create("ul", {
hidden: "hidden",
inside: this.container
});
this.status = $.create("span", {
className: "visually-hidden",
role: "status",
"aria-live": "assertive",
"aria-relevant": "additions",
inside: this.container
});
// Bind events
$.bind(this.input, {
"input": this.evaluate.bind(this),
"blur": this.close.bind(this, { reason: "blur" }),
"keydown": function(evt) {
var c = evt.keyCode;
// If the dropdown `ul` is in view, then act on keydown for the following keys:
// Enter / Esc / Up / Down
if(me.opened) {
if (c === 13 && me.selected) { // Enter
evt.preventDefault();
me.select();
}
else if (c === 27) { // Esc
me.close({ reason: "esc" });
}
else if (c === 38 || c === 40) { // Down/Up arrow
evt.preventDefault();
me[c === 38? "previous" : "next"]();
}
}
}
});
$.bind(this.input.form, {"submit": this.close.bind(this, { reason: "submit" })});
$.bind(this.ul, {"mousedown": function(evt) {
var li = evt.target;
if (li !== this) {
while (li && !/li/i.test(li.nodeName)) {
li = li.parentNode;
}
if (li && evt.button === 0) { // Only select on left click
evt.preventDefault();
me.select(li, evt.target);
}
}
}});
if (this.input.hasAttribute("list")) {
this.list = "#" + this.input.getAttribute("list");
this.input.removeAttribute("list");
}
else {
this.list = this.input.getAttribute("data-list") || o.list || [];
}
_.all.push(this);
};
_.prototype = {
set list(list) {
if (Array.isArray(list)) {
this._list = list;
}
else if (typeof list === "string" && list.indexOf(",") > -1) {
this._list = list.split(/\s*,\s*/);
}
else { // Element or CSS selector
list = $(list);
if (list && list.children) {
var items = [];
slice.apply(list.children).forEach(function (el) {
if (!el.disabled) {
var text = el.textContent.trim();
var value = el.value || text;
var label = el.label || text;
if (value !== "") {
items.push({ label: label, value: value });
}
}
});
this._list = items;
}
}
if (document.activeElement === this.input) {
this.evaluate();
}
},
get selected() {
return this.index > -1;
},
get opened() {
return this.isOpened;
},
close: function (o) {
if (!this.opened) {
return;
}
this.ul.setAttribute("hidden", "");
this.isOpened = false;
this.index = -1;
$.fire(this.input, "awesomplete-close", o || {});
},
open: function () {
this.ul.removeAttribute("hidden");
this.isOpened = true;
if (this.autoFirst && this.index === -1) {
this.goto(0);
}
$.fire(this.input, "awesomplete-open");
},
next: function () {
var count = this.ul.children.length;
this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) );
},
previous: function () {
var count = this.ul.children.length;
var pos = this.index - 1;
this.goto(this.selected && pos !== -1 ? pos : count - 1);
},
// Should not be used, highlights specific item without any checks!
goto: function (i) {
var lis = this.ul.children;
if (this.selected) {
lis[this.index].setAttribute("aria-selected", "false");
}
this.index = i;
if (i > -1 && lis.length > 0) {
lis[i].setAttribute("aria-selected", "true");
this.status.textContent = lis[i].textContent;
// scroll to highlighted element in case parent's height is fixed
this.ul.scrollTop = lis[i].offsetTop - this.ul.clientHeight + lis[i].clientHeight;
$.fire(this.input, "awesomplete-highlight", {
text: this.suggestions[this.index]
});
}
},
select: function (selected, origin) {
if (selected) {
this.index = $.siblingIndex(selected);
} else {
selected = this.ul.children[this.index];
}
if (selected) {
var suggestion = this.suggestions[this.index];
var allowed = $.fire(this.input, "awesomplete-select", {
text: suggestion,
origin: origin || selected
});
if (allowed) {
this.replace(suggestion);
this.close({ reason: "select" });
$.fire(this.input, "awesomplete-selectcomplete", {
text: suggestion
});
}
}
},
evaluate: function() {
var me = this;
var value = this.input.value;
if (value.length >= this.minChars && this._list.length > 0) {
this.index = -1;
// Populate list with options that match
this.ul.innerHTML = "";
this.suggestions = this._list
.map(function(item) {
return new Suggestion(me.data(item, value));
})
.filter(function(item) {
return me.filter(item, value);
})
.sort(this.sort)
.slice(0, this.maxItems);
this.suggestions.forEach(function(text) {
me.ul.appendChild(me.item(text, value));
});
if (this.ul.children.length === 0) {
this.close({ reason: "nomatches" });
} else {
this.open();
}
}
else {
this.close({ reason: "nomatches" });
}
}
};
// Static methods/properties
_.all = [];
_.FILTER_CONTAINS = function (text, input) {
return RegExp($.regExpEscape(input.trim()), "i").test(text);
};
_.FILTER_STARTSWITH = function (text, input) {
return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text);
};
_.SORT_BYLENGTH = function (a, b) {
if (a.length !== b.length) {
return a.length - b.length;
}
return a < b? -1 : 1;
};
_.ITEM = function (text, input) {
var html = input.trim() === '' ? text : text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "<mark>$&</mark>");
return $.create("li", {
innerHTML: html,
"aria-selected": "false"
});
};
_.REPLACE = function (text) {
this.input.value = text.value;
};
_.DATA = function (item/*, input*/) { return item; };
// Private functions
function Suggestion(data) {
var o = Array.isArray(data)
? { label: data[0], value: data[1] }
: typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data };
this.label = o.label || o.value;
this.value = o.value;
}
Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", {
get: function() { return this.label.length; }
});
Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () {
return "" + this.label;
};
function configure(instance, properties, o) {
for (var i in properties) {
var initial = properties[i],
attrValue = instance.input.getAttribute("data-" + i.toLowerCase());
if (typeof initial === "number") {
instance[i] = parseInt(attrValue);
}
else if (initial === false) { // Boolean options must be false by default anyway
instance[i] = attrValue !== null;
}
else if (initial instanceof Function) {
instance[i] = null;
}
else {
instance[i] = attrValue;
}
if (!instance[i] && instance[i] !== 0) {
instance[i] = (i in o)? o[i] : initial;
}
}
}
// Helpers
var slice = Array.prototype.slice;
function $(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
}
function $$(expr, con) {
return slice.call((con || document).querySelectorAll(expr));
}
$.create = function(tag, o) {
var element = document.createElement(tag);
for (var i in o) {
var val = o[i];
if (i === "inside") {
$(val).appendChild(element);
}
else if (i === "around") {
var ref = $(val);
ref.parentNode.insertBefore(element, ref);
element.appendChild(ref);
}
else if (i in element) {
element[i] = val;
}
else {
element.setAttribute(i, val);
}
}
return element;
};
$.bind = function(element, o) {
if (element) {
for (var event in o) {
var callback = o[event];
event.split(/\s+/).forEach(function (event) {
element.addEventListener(event, callback);
});
}
}
};
$.fire = function(target, type, properties) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true );
for (var j in properties) {
evt[j] = properties[j];
}
return target.dispatchEvent(evt);
};
$.regExpEscape = function (s) {
return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
};
$.siblingIndex = function (el) {
/* eslint-disable no-cond-assign */
for (var i = 0; el = el.previousElementSibling; i++);
return i;
};
// Initialization
function init() {
$$("input.awesomplete").forEach(function (input) {
new _(input);
});
}
// Are we in a browser? Check for Document constructor
if (typeof Document !== "undefined") {
// DOM already loaded?
if (document.readyState !== "loading") {
init();
}
else {
// Wait for it
document.addEventListener("DOMContentLoaded", init);
}
}
_.$ = $;
_.$$ = $$;
// Make sure to export Awesomplete on self when in a browser
if (typeof self !== "undefined") {
self.Awesomplete = _;
}
// Expose Awesomplete as a CJS module
if (typeof module === "object" && module.exports) {
module.exports = _;
}
return _;
}());

View file

@ -1,232 +0,0 @@
/*!
hey, [be]Lazy.js - v1.3.1 - 2015.02.01
A lazy loading and multi-serving image script
(c) Bjoern Klinggaard - @bklinggaard - http://dinbror.dk/blazy
*/
;(function(root, blazy) {
if (typeof define === 'function' && define.amd) {
// AMD. Register bLazy as an anonymous module
define(blazy);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = blazy();
} else {
// Browser globals. Register bLazy on window
root.Blazy = blazy();
}
})(this, function () {
'use strict';
//vars
var source, options, viewport, images, count, isRetina, destroyed;
//throttle vars
var validateT, saveViewportOffsetT;
// constructor
function Blazy(settings) {
//IE7- fallback for missing querySelectorAll support
if (!document.querySelectorAll) {
var s=document.createStyleSheet();
document.querySelectorAll = function(r, c, i, j, a) {
a=document.all, c=[], r = r.replace(/\[for\b/gi, '[htmlFor').split(',');
for (i=r.length; i--;) {
s.addRule(r[i], 'k:v');
for (j=a.length; j--;) a[j].currentStyle.k && c.push(a[j]);
s.removeRule(0);
}
return c;
};
}
//init vars
destroyed = true;
images = [];
viewport = {};
//options
options = settings || {};
options.error = options.error || false;
options.offset = options.offset || 100;
options.success = options.success || false;
options.selector = options.selector || '.b-lazy';
options.separator = options.separator || '|';
options.container = options.container ? document.querySelectorAll(options.container) : false;
options.errorClass = options.errorClass || 'b-error';
options.breakpoints = options.breakpoints || false;
options.successClass = options.successClass || 'b-loaded';
options.src = source = options.src || 'data-src';
isRetina = window.devicePixelRatio > 1;
viewport.top = 0 - options.offset;
viewport.left = 0 - options.offset;
//throttle, ensures that we don't call the functions too often
validateT = throttle(validate, 25);
saveViewportOffsetT = throttle(saveViewportOffset, 50);
saveViewportOffset();
//handle multi-served image src
each(options.breakpoints, function(object){
if(object.width >= window.screen.width) {
source = object.src;
return false;
}
});
// start lazy load
initialize();
}
/* public functions
************************************/
Blazy.prototype.revalidate = function() {
initialize();
};
Blazy.prototype.load = function(element, force){
if(!isElementLoaded(element)) loadImage(element, force);
};
Blazy.prototype.destroy = function(){
if(options.container){
each(options.container, function(object){
unbindEvent(object, 'scroll', validateT);
});
}
unbindEvent(window, 'scroll', validateT);
unbindEvent(window, 'resize', validateT);
unbindEvent(window, 'resize', saveViewportOffsetT);
count = 0;
images.length = 0;
destroyed = true;
};
/* private helper functions
************************************/
function initialize(){
// First we create an array of images to lazy load
createImageArray(options.selector);
// Then we bind resize and scroll events if not already binded
if(destroyed) {
destroyed = false;
if(options.container) {
each(options.container, function(object){
bindEvent(object, 'scroll', validateT);
});
}
bindEvent(window, 'resize', saveViewportOffsetT);
bindEvent(window, 'resize', validateT);
bindEvent(window, 'scroll', validateT);
}
// And finally, we start to lazy load. Should bLazy ensure domready?
validate();
}
function validate() {
for(var i = 0; i<count; i++){
var image = images[i];
if(elementInView(image) || isElementLoaded(image)) {
Blazy.prototype.load(image);
images.splice(i, 1);
count--;
i--;
}
}
if(count === 0) {
Blazy.prototype.destroy();
}
}
function loadImage(ele, force){
// if element is visible
if(force || (ele.offsetWidth > 0 && ele.offsetHeight > 0)) {
var dataSrc = ele.getAttribute(source) || ele.getAttribute(options.src); // fallback to default data-src
if(dataSrc) {
var dataSrcSplitted = dataSrc.split(options.separator);
var src = dataSrcSplitted[isRetina && dataSrcSplitted.length > 1 ? 1 : 0];
var img = new Image();
// cleanup markup, remove data source attributes
each(options.breakpoints, function(object){
ele.removeAttribute(object.src);
});
ele.removeAttribute(options.src);
img.onerror = function() {
if(options.error) options.error(ele, "invalid");
ele.className = ele.className + ' ' + options.errorClass;
};
img.onload = function() {
// Is element an image or should we add the src as a background image?
ele.nodeName.toLowerCase() === 'img' ? ele.src = src : ele.style.backgroundImage = 'url("' + src + '")';
ele.className = ele.className + ' ' + options.successClass;
if(options.success) options.success(ele);
};
img.src = src; //preload image
} else {
if(options.error) options.error(ele, "missing");
ele.className = ele.className + ' ' + options.errorClass;
}
}
}
function elementInView(ele) {
var rect = ele.getBoundingClientRect();
return (
// Intersection
rect.right >= viewport.left
&& rect.bottom >= viewport.top
&& rect.left <= viewport.right
&& rect.top <= viewport.bottom
);
}
function isElementLoaded(ele) {
return (' ' + ele.className + ' ').indexOf(' ' + options.successClass + ' ') !== -1;
}
function createImageArray(selector) {
var nodelist = document.querySelectorAll(selector);
count = nodelist.length;
//converting nodelist to array
for(var i = count; i--; images.unshift(nodelist[i])){}
}
function saveViewportOffset(){
viewport.bottom = (window.innerHeight || document.documentElement.clientHeight) + options.offset;
viewport.right = (window.innerWidth || document.documentElement.clientWidth) + options.offset;
}
function bindEvent(ele, type, fn) {
if (ele.attachEvent) {
ele.attachEvent && ele.attachEvent('on' + type, fn);
} else {
ele.addEventListener(type, fn, false);
}
}
function unbindEvent(ele, type, fn) {
if (ele.detachEvent) {
ele.detachEvent && ele.detachEvent('on' + type, fn);
} else {
ele.removeEventListener(type, fn, false);
}
}
function each(object, fn){
if(object && fn) {
var l = object.length;
for(var i = 0; i<l && fn(object[i], i) !== false; i++){}
}
}
function throttle(fn, minDelay) {
var lastCall = 0;
return function() {
var now = +new Date();
if (now - lastCall < minDelay) {
return;
}
lastCall = now;
fn.apply(images, arguments);
};
}
return Blazy;
});

View file

@ -1,103 +0,0 @@
/** @licstart The following is the entire license notice for the
* JavaScript code in this page.
*
* Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
* (c) 2011-2017 The Shaarli Community, see AUTHORS
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would
* be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must
* not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* @licend The above is the entire license notice
* for the JavaScript code in this page.
*/
/**
* Change the position counter of a row.
*
* @param elem Element Node to change.
* @param toPos int New position.
*/
function changePos(elem, toPos)
{
var elemName = elem.getAttribute('data-line')
elem.setAttribute('data-order', toPos);
var hiddenInput = document.querySelector('[name="order_'+ elemName +'"]');
hiddenInput.setAttribute('value', toPos);
}
/**
* Move a row up or down.
*
* @param pos Element Node to move.
* @param move int Move: +1 (down) or -1 (up)
*/
function changeOrder(pos, move)
{
var newpos = parseInt(pos) + move;
var lines = document.querySelectorAll('[data-order="'+ pos +'"]');
var changelines = document.querySelectorAll('[data-order="'+ newpos +'"]');
// If we go down reverse lines to preserve the rows order
if (move > 0) {
lines = [].slice.call(lines).reverse();
}
for (var i = 0 ; i < lines.length ; i++) {
var parent = changelines[0].parentNode;
changePos(lines[i], newpos);
changePos(changelines[i], parseInt(pos));
var changeItem = move < 0 ? changelines[0] : changelines[changelines.length - 1].nextSibling;
parent.insertBefore(lines[i], changeItem);
}
}
/**
* Move a row up in the table.
*
* @param pos int row counter.
*
* @returns false
*/
function orderUp(pos)
{
if (pos == 0) {
return false;
}
changeOrder(pos, -1);
return false;
}
/**
* Move a row down in the table.
*
* @param pos int row counter.
*
* @returns false
*/
function orderDown(pos)
{
var lastpos = document.querySelector('[data-order]:last-child').getAttribute('data-order');
if (pos == lastpos) {
return false;
}
changeOrder(pos, +1);
return false;
}

30
package.json Normal file
View file

@ -0,0 +1,30 @@
{
"dependencies": {
"awesomplete": "^1.1.2",
"blazy": "^1.8.2",
"font-awesome": "^4.7.0",
"pure-extras": "^1.0.0",
"purecss": "^1.0.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-minify-webpack-plugin": "^0.2.0",
"babel-preset-env": "^1.6.1",
"css-loader": "^0.28.9",
"eslint": "^4.16.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.6",
"node-sass": "^4.7.2",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.1",
"url-loader": "^0.6.2",
"webpack": "^3.10.0"
},
"scripts": {
"build": "webpack",
"watch": "webpack --watch"
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,861 +0,0 @@
/*!
Pure v0.6.0
Copyright 2014 Yahoo! Inc. All rights reserved.
Licensed under the BSD License.
https://github.com/yahoo/pure/blob/master/LICENSE.md
*/
@media screen and (min-width: 35.5em) {
.pure-u-sm-1,
.pure-u-sm-1-1,
.pure-u-sm-1-2,
.pure-u-sm-1-3,
.pure-u-sm-2-3,
.pure-u-sm-1-4,
.pure-u-sm-3-4,
.pure-u-sm-1-5,
.pure-u-sm-2-5,
.pure-u-sm-3-5,
.pure-u-sm-4-5,
.pure-u-sm-5-5,
.pure-u-sm-1-6,
.pure-u-sm-5-6,
.pure-u-sm-1-8,
.pure-u-sm-3-8,
.pure-u-sm-5-8,
.pure-u-sm-7-8,
.pure-u-sm-1-12,
.pure-u-sm-5-12,
.pure-u-sm-7-12,
.pure-u-sm-11-12,
.pure-u-sm-1-24,
.pure-u-sm-2-24,
.pure-u-sm-3-24,
.pure-u-sm-4-24,
.pure-u-sm-5-24,
.pure-u-sm-6-24,
.pure-u-sm-7-24,
.pure-u-sm-8-24,
.pure-u-sm-9-24,
.pure-u-sm-10-24,
.pure-u-sm-11-24,
.pure-u-sm-12-24,
.pure-u-sm-13-24,
.pure-u-sm-14-24,
.pure-u-sm-15-24,
.pure-u-sm-16-24,
.pure-u-sm-17-24,
.pure-u-sm-18-24,
.pure-u-sm-19-24,
.pure-u-sm-20-24,
.pure-u-sm-21-24,
.pure-u-sm-22-24,
.pure-u-sm-23-24,
.pure-u-sm-24-24 {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
}
.pure-u-sm-1-24 {
width: 4.1667%;
*width: 4.1357%;
}
.pure-u-sm-1-12,
.pure-u-sm-2-24 {
width: 8.3333%;
*width: 8.3023%;
}
.pure-u-sm-1-8,
.pure-u-sm-3-24 {
width: 12.5000%;
*width: 12.4690%;
}
.pure-u-sm-1-6,
.pure-u-sm-4-24 {
width: 16.6667%;
*width: 16.6357%;
}
.pure-u-sm-1-5 {
width: 20%;
*width: 19.9690%;
}
.pure-u-sm-5-24 {
width: 20.8333%;
*width: 20.8023%;
}
.pure-u-sm-1-4,
.pure-u-sm-6-24 {
width: 25%;
*width: 24.9690%;
}
.pure-u-sm-7-24 {
width: 29.1667%;
*width: 29.1357%;
}
.pure-u-sm-1-3,
.pure-u-sm-8-24 {
width: 33.3333%;
*width: 33.3023%;
}
.pure-u-sm-3-8,
.pure-u-sm-9-24 {
width: 37.5000%;
*width: 37.4690%;
}
.pure-u-sm-2-5 {
width: 40%;
*width: 39.9690%;
}
.pure-u-sm-5-12,
.pure-u-sm-10-24 {
width: 41.6667%;
*width: 41.6357%;
}
.pure-u-sm-11-24 {
width: 45.8333%;
*width: 45.8023%;
}
.pure-u-sm-1-2,
.pure-u-sm-12-24 {
width: 50%;
*width: 49.9690%;
}
.pure-u-sm-13-24 {
width: 54.1667%;
*width: 54.1357%;
}
.pure-u-sm-7-12,
.pure-u-sm-14-24 {
width: 58.3333%;
*width: 58.3023%;
}
.pure-u-sm-3-5 {
width: 60%;
*width: 59.9690%;
}
.pure-u-sm-5-8,
.pure-u-sm-15-24 {
width: 62.5000%;
*width: 62.4690%;
}
.pure-u-sm-2-3,
.pure-u-sm-16-24 {
width: 66.6667%;
*width: 66.6357%;
}
.pure-u-sm-17-24 {
width: 70.8333%;
*width: 70.8023%;
}
.pure-u-sm-3-4,
.pure-u-sm-18-24 {
width: 75%;
*width: 74.9690%;
}
.pure-u-sm-19-24 {
width: 79.1667%;
*width: 79.1357%;
}
.pure-u-sm-4-5 {
width: 80%;
*width: 79.9690%;
}
.pure-u-sm-5-6,
.pure-u-sm-20-24 {
width: 83.3333%;
*width: 83.3023%;
}
.pure-u-sm-7-8,
.pure-u-sm-21-24 {
width: 87.5000%;
*width: 87.4690%;
}
.pure-u-sm-11-12,
.pure-u-sm-22-24 {
width: 91.6667%;
*width: 91.6357%;
}
.pure-u-sm-23-24 {
width: 95.8333%;
*width: 95.8023%;
}
.pure-u-sm-1,
.pure-u-sm-1-1,
.pure-u-sm-5-5,
.pure-u-sm-24-24 {
width: 100%;
}
}
@media screen and (min-width: 48em) {
.pure-u-md-1,
.pure-u-md-1-1,
.pure-u-md-1-2,
.pure-u-md-1-3,
.pure-u-md-2-3,
.pure-u-md-1-4,
.pure-u-md-3-4,
.pure-u-md-1-5,
.pure-u-md-2-5,
.pure-u-md-3-5,
.pure-u-md-4-5,
.pure-u-md-5-5,
.pure-u-md-1-6,
.pure-u-md-5-6,
.pure-u-md-1-8,
.pure-u-md-3-8,
.pure-u-md-5-8,
.pure-u-md-7-8,
.pure-u-md-1-12,
.pure-u-md-5-12,
.pure-u-md-7-12,
.pure-u-md-11-12,
.pure-u-md-1-24,
.pure-u-md-2-24,
.pure-u-md-3-24,
.pure-u-md-4-24,
.pure-u-md-5-24,
.pure-u-md-6-24,
.pure-u-md-7-24,
.pure-u-md-8-24,
.pure-u-md-9-24,
.pure-u-md-10-24,
.pure-u-md-11-24,
.pure-u-md-12-24,
.pure-u-md-13-24,
.pure-u-md-14-24,
.pure-u-md-15-24,
.pure-u-md-16-24,
.pure-u-md-17-24,
.pure-u-md-18-24,
.pure-u-md-19-24,
.pure-u-md-20-24,
.pure-u-md-21-24,
.pure-u-md-22-24,
.pure-u-md-23-24,
.pure-u-md-24-24 {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
}
.pure-u-md-1-24 {
width: 4.1667%;
*width: 4.1357%;
}
.pure-u-md-1-12,
.pure-u-md-2-24 {
width: 8.3333%;
*width: 8.3023%;
}
.pure-u-md-1-8,
.pure-u-md-3-24 {
width: 12.5000%;
*width: 12.4690%;
}
.pure-u-md-1-6,
.pure-u-md-4-24 {
width: 16.6667%;
*width: 16.6357%;
}
.pure-u-md-1-5 {
width: 20%;
*width: 19.9690%;
}
.pure-u-md-5-24 {
width: 20.8333%;
*width: 20.8023%;
}
.pure-u-md-1-4,
.pure-u-md-6-24 {
width: 25%;
*width: 24.9690%;
}
.pure-u-md-7-24 {
width: 29.1667%;
*width: 29.1357%;
}
.pure-u-md-1-3,
.pure-u-md-8-24 {
width: 33.3333%;
*width: 33.3023%;
}
.pure-u-md-3-8,
.pure-u-md-9-24 {
width: 37.5000%;
*width: 37.4690%;
}
.pure-u-md-2-5 {
width: 40%;
*width: 39.9690%;
}
.pure-u-md-5-12,
.pure-u-md-10-24 {
width: 41.6667%;
*width: 41.6357%;
}
.pure-u-md-11-24 {
width: 45.8333%;
*width: 45.8023%;
}
.pure-u-md-1-2,
.pure-u-md-12-24 {
width: 50%;
*width: 49.9690%;
}
.pure-u-md-13-24 {
width: 54.1667%;
*width: 54.1357%;
}
.pure-u-md-7-12,
.pure-u-md-14-24 {
width: 58.3333%;
*width: 58.3023%;
}
.pure-u-md-3-5 {
width: 60%;
*width: 59.9690%;
}
.pure-u-md-5-8,
.pure-u-md-15-24 {
width: 62.5000%;
*width: 62.4690%;
}
.pure-u-md-2-3,
.pure-u-md-16-24 {
width: 66.6667%;
*width: 66.6357%;
}
.pure-u-md-17-24 {
width: 70.8333%;
*width: 70.8023%;
}
.pure-u-md-3-4,
.pure-u-md-18-24 {
width: 75%;
*width: 74.9690%;
}
.pure-u-md-19-24 {
width: 79.1667%;
*width: 79.1357%;
}
.pure-u-md-4-5 {
width: 80%;
*width: 79.9690%;
}
.pure-u-md-5-6,
.pure-u-md-20-24 {
width: 83.3333%;
*width: 83.3023%;
}
.pure-u-md-7-8,
.pure-u-md-21-24 {
width: 87.5000%;
*width: 87.4690%;
}
.pure-u-md-11-12,
.pure-u-md-22-24 {
width: 91.6667%;
*width: 91.6357%;
}
.pure-u-md-23-24 {
width: 95.8333%;
*width: 95.8023%;
}
.pure-u-md-1,
.pure-u-md-1-1,
.pure-u-md-5-5,
.pure-u-md-24-24 {
width: 100%;
}
}
@media screen and (min-width: 64em) {
.pure-u-lg-1,
.pure-u-lg-1-1,
.pure-u-lg-1-2,
.pure-u-lg-1-3,
.pure-u-lg-2-3,
.pure-u-lg-1-4,
.pure-u-lg-3-4,
.pure-u-lg-1-5,
.pure-u-lg-2-5,
.pure-u-lg-3-5,
.pure-u-lg-4-5,
.pure-u-lg-5-5,
.pure-u-lg-1-6,
.pure-u-lg-5-6,
.pure-u-lg-1-8,
.pure-u-lg-3-8,
.pure-u-lg-5-8,
.pure-u-lg-7-8,
.pure-u-lg-1-12,
.pure-u-lg-5-12,
.pure-u-lg-7-12,
.pure-u-lg-11-12,
.pure-u-lg-1-24,
.pure-u-lg-2-24,
.pure-u-lg-3-24,
.pure-u-lg-4-24,
.pure-u-lg-5-24,
.pure-u-lg-6-24,
.pure-u-lg-7-24,
.pure-u-lg-8-24,
.pure-u-lg-9-24,
.pure-u-lg-10-24,
.pure-u-lg-11-24,
.pure-u-lg-12-24,
.pure-u-lg-13-24,
.pure-u-lg-14-24,
.pure-u-lg-15-24,
.pure-u-lg-16-24,
.pure-u-lg-17-24,
.pure-u-lg-18-24,
.pure-u-lg-19-24,
.pure-u-lg-20-24,
.pure-u-lg-21-24,
.pure-u-lg-22-24,
.pure-u-lg-23-24,
.pure-u-lg-24-24 {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
}
.pure-u-lg-1-24 {
width: 4.1667%;
*width: 4.1357%;
}
.pure-u-lg-1-12,
.pure-u-lg-2-24 {
width: 8.3333%;
*width: 8.3023%;
}
.pure-u-lg-1-8,
.pure-u-lg-3-24 {
width: 12.5000%;
*width: 12.4690%;
}
.pure-u-lg-1-6,
.pure-u-lg-4-24 {
width: 16.6667%;
*width: 16.6357%;
}
.pure-u-lg-1-5 {
width: 20%;
*width: 19.9690%;
}
.pure-u-lg-5-24 {
width: 20.8333%;
*width: 20.8023%;
}
.pure-u-lg-1-4,
.pure-u-lg-6-24 {
width: 25%;
*width: 24.9690%;
}
.pure-u-lg-7-24 {
width: 29.1667%;
*width: 29.1357%;
}
.pure-u-lg-1-3,
.pure-u-lg-8-24 {
width: 33.3333%;
*width: 33.3023%;
}
.pure-u-lg-3-8,
.pure-u-lg-9-24 {
width: 37.5000%;
*width: 37.4690%;
}
.pure-u-lg-2-5 {
width: 40%;
*width: 39.9690%;
}
.pure-u-lg-5-12,
.pure-u-lg-10-24 {
width: 41.6667%;
*width: 41.6357%;
}
.pure-u-lg-11-24 {
width: 45.8333%;
*width: 45.8023%;
}
.pure-u-lg-1-2,
.pure-u-lg-12-24 {
width: 50%;
*width: 49.9690%;
}
.pure-u-lg-13-24 {
width: 54.1667%;
*width: 54.1357%;
}
.pure-u-lg-7-12,
.pure-u-lg-14-24 {
width: 58.3333%;
*width: 58.3023%;
}
.pure-u-lg-3-5 {
width: 60%;
*width: 59.9690%;
}
.pure-u-lg-5-8,
.pure-u-lg-15-24 {
width: 62.5000%;
*width: 62.4690%;
}
.pure-u-lg-2-3,
.pure-u-lg-16-24 {
width: 66.6667%;
*width: 66.6357%;
}
.pure-u-lg-17-24 {
width: 70.8333%;
*width: 70.8023%;
}
.pure-u-lg-3-4,
.pure-u-lg-18-24 {
width: 75%;
*width: 74.9690%;
}
.pure-u-lg-19-24 {
width: 79.1667%;
*width: 79.1357%;
}
.pure-u-lg-4-5 {
width: 80%;
*width: 79.9690%;
}
.pure-u-lg-5-6,
.pure-u-lg-20-24 {
width: 83.3333%;
*width: 83.3023%;
}
.pure-u-lg-7-8,
.pure-u-lg-21-24 {
width: 87.5000%;
*width: 87.4690%;
}
.pure-u-lg-11-12,
.pure-u-lg-22-24 {
width: 91.6667%;
*width: 91.6357%;
}
.pure-u-lg-23-24 {
width: 95.8333%;
*width: 95.8023%;
}
.pure-u-lg-1,
.pure-u-lg-1-1,
.pure-u-lg-5-5,
.pure-u-lg-24-24 {
width: 100%;
}
}
@media screen and (min-width: 80em) {
.pure-u-xl-1,
.pure-u-xl-1-1,
.pure-u-xl-1-2,
.pure-u-xl-1-3,
.pure-u-xl-2-3,
.pure-u-xl-1-4,
.pure-u-xl-3-4,
.pure-u-xl-1-5,
.pure-u-xl-2-5,
.pure-u-xl-3-5,
.pure-u-xl-4-5,
.pure-u-xl-5-5,
.pure-u-xl-1-6,
.pure-u-xl-5-6,
.pure-u-xl-1-8,
.pure-u-xl-3-8,
.pure-u-xl-5-8,
.pure-u-xl-7-8,
.pure-u-xl-1-12,
.pure-u-xl-5-12,
.pure-u-xl-7-12,
.pure-u-xl-11-12,
.pure-u-xl-1-24,
.pure-u-xl-2-24,
.pure-u-xl-3-24,
.pure-u-xl-4-24,
.pure-u-xl-5-24,
.pure-u-xl-6-24,
.pure-u-xl-7-24,
.pure-u-xl-8-24,
.pure-u-xl-9-24,
.pure-u-xl-10-24,
.pure-u-xl-11-24,
.pure-u-xl-12-24,
.pure-u-xl-13-24,
.pure-u-xl-14-24,
.pure-u-xl-15-24,
.pure-u-xl-16-24,
.pure-u-xl-17-24,
.pure-u-xl-18-24,
.pure-u-xl-19-24,
.pure-u-xl-20-24,
.pure-u-xl-21-24,
.pure-u-xl-22-24,
.pure-u-xl-23-24,
.pure-u-xl-24-24 {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
}
.pure-u-xl-1-24 {
width: 4.1667%;
*width: 4.1357%;
}
.pure-u-xl-1-12,
.pure-u-xl-2-24 {
width: 8.3333%;
*width: 8.3023%;
}
.pure-u-xl-1-8,
.pure-u-xl-3-24 {
width: 12.5000%;
*width: 12.4690%;
}
.pure-u-xl-1-6,
.pure-u-xl-4-24 {
width: 16.6667%;
*width: 16.6357%;
}
.pure-u-xl-1-5 {
width: 20%;
*width: 19.9690%;
}
.pure-u-xl-5-24 {
width: 20.8333%;
*width: 20.8023%;
}
.pure-u-xl-1-4,
.pure-u-xl-6-24 {
width: 25%;
*width: 24.9690%;
}
.pure-u-xl-7-24 {
width: 29.1667%;
*width: 29.1357%;
}
.pure-u-xl-1-3,
.pure-u-xl-8-24 {
width: 33.3333%;
*width: 33.3023%;
}
.pure-u-xl-3-8,
.pure-u-xl-9-24 {
width: 37.5000%;
*width: 37.4690%;
}
.pure-u-xl-2-5 {
width: 40%;
*width: 39.9690%;
}
.pure-u-xl-5-12,
.pure-u-xl-10-24 {
width: 41.6667%;
*width: 41.6357%;
}
.pure-u-xl-11-24 {
width: 45.8333%;
*width: 45.8023%;
}
.pure-u-xl-1-2,
.pure-u-xl-12-24 {
width: 50%;
*width: 49.9690%;
}
.pure-u-xl-13-24 {
width: 54.1667%;
*width: 54.1357%;
}
.pure-u-xl-7-12,
.pure-u-xl-14-24 {
width: 58.3333%;
*width: 58.3023%;
}
.pure-u-xl-3-5 {
width: 60%;
*width: 59.9690%;
}
.pure-u-xl-5-8,
.pure-u-xl-15-24 {
width: 62.5000%;
*width: 62.4690%;
}
.pure-u-xl-2-3,
.pure-u-xl-16-24 {
width: 66.6667%;
*width: 66.6357%;
}
.pure-u-xl-17-24 {
width: 70.8333%;
*width: 70.8023%;
}
.pure-u-xl-3-4,
.pure-u-xl-18-24 {
width: 75%;
*width: 74.9690%;
}
.pure-u-xl-19-24 {
width: 79.1667%;
*width: 79.1357%;
}
.pure-u-xl-4-5 {
width: 80%;
*width: 79.9690%;
}
.pure-u-xl-5-6,
.pure-u-xl-20-24 {
width: 83.3333%;
*width: 83.3023%;
}
.pure-u-xl-7-8,
.pure-u-xl-21-24 {
width: 87.5000%;
*width: 87.4690%;
}
.pure-u-xl-11-12,
.pure-u-xl-22-24 {
width: 91.6667%;
*width: 91.6357%;
}
.pure-u-xl-23-24 {
width: 95.8333%;
*width: 95.8023%;
}
.pure-u-xl-1,
.pure-u-xl-1-1,
.pure-u-xl-5-5,
.pure-u-xl-24-24 {
width: 100%;
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,262 +0,0 @@
/* Images */
.pure-img-eliptical {
border-radius: 80%;
}
.pure-img-rounded {
border-radius: 3px;
}
.pure-img-bordered {
background-color: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 5px;
}
/* Thumbnails */
.pure-thumbnails li {
text-align: center;
display: inline-block;
*display: inline;
/* IE7 inline-block hack */
*zoom: 1;
vertical-align: top;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0.5em;
}
.pure-thumbnails {
list-style: none;
margin: 0;
padding: 0;
}
.pure-thumbnails a:focus {
outline: 0 none;
}
.pure-thumb {
display: block;
text-decoration: none;
color: inherit;
}
.pure-thumb img {
max-width: 100%;
margin-right: auto;
margin-left: auto;
vertical-align: middle; /* this will remove a thin line below the image */
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.pure-thumb-bordered {
border: 1px solid rgba(0, 0, 0, 0.2);
}
.pure-thumb .caption {
text-align: left;
display: block;
margin: 0 5px 6px;
}
.pure-thumb .caption p {
margin: 0.3em 0 0;
font-size: 75%;
}
.pure-thumb .caption .caption-head {
font-weight: bold;
margin-top: 0.3em;
}
.pure-thumb-eliptical img {
border-radius: 50%;
}
.pure-thumb-rounded img {
border-radius: 3px;
}
/* Badges/Pills */
.pure-badge,
.pure-badge-error,
.pure-badge-warning,
.pure-badge-success,
.pure-badge-info,
.pure-badge-inverse {
padding: 0.35em 0.9em 0.35em;
background-color: #9D988E;
color: #fff;
display: inline-block;
font-size: 11.844px;
font-weight: bold;
line-height: 1.2em;
vertical-align: baseline;
white-space: nowrap;
border-radius: 20px;
margin: 0.2em;
}
.pure-badge-error {
background-color: #D13C38;
}
.pure-badge-warning {
background-color: #E78C05;
}
.pure-badge-success {
background-color: rgb(83, 180, 79);
}
.pure-badge-info {
background-color: rgb(18, 169, 218);
}
.pure-badge-inverse {
background-color: #4D370C;
}
/* Alerts */
.pure-alert {
position: relative;
margin-bottom: 1em;
padding: 1em;
background: #ccc;
border-radius: 3px;
}
.pure-alert label {
display: inline-block;
*display: inline;
/* IE7 inline-block hack */
*zoom: 1;
white-space: nowrap;
}
.pure-alert {
background-color: rgb(209, 235, 238);
color: rgb(102, 131, 145);
}
.pure-alert-error {
background-color: #D13C38;
color: #fff;
}
.pure-alert-warning {
background-color: rgb(250, 191, 103);
color: rgb(151, 96, 13);
}
.pure-alert-success {
background-color: rgb(83, 180, 79);
color: #fff;
}
/* Contextual Modals */
.pure-popover {
position: relative;
width: 300px;
background-color: #f0f1f3;
color: #2f3034;
padding: 15px;
border: 1px solid #bfc0c8;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
box-padding: border-box;
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}
.pure-arrow-border, .pure-arrow {
border-style: solid;
border-width: 10px;
height:0;
width:0;
position:absolute;
}
/* POPOVER ARROW POSITIONING BOTTOM */
.pure-popover.bottom .pure-arrow-border {
border-color: #bfc0c8 transparent transparent transparent;
bottom: -20px;
left: 50%;
}
.pure-popover.bottom .pure-arrow {
border-color: #f0f1f3 transparent transparent transparent;
bottom:-19px;
left: 50%;
}
/* POPOVER ARROW POSITIONING TOP*/
.pure-popover.top .pure-arrow-border {
border-color: transparent transparent #bfc0c8 transparent;
top: -21px;
left: 50%;
}
.pure-popover.top .pure-arrow {
border-color: transparent transparent #f0f1f3 transparent;
top:-20px;
left: 50%;
}
/* POPOVER ARROW POSITIONING RIGHT*/
.pure-popover.right .pure-arrow-border {
border-color: transparent transparent transparent #bfc0c8;
top: 45%;
right: -21px;
}
.pure-popover.right .pure-arrow {
border-color: transparent transparent transparent #f0f1f3;
top:45%;
right: -20px;
}
/* POPOVER ARROW POSITIONING LEFT*/
.pure-popover.left .pure-arrow-border {
border-color: transparent #bfc0c8 transparent transparent;
top: 45%;
left: -21px;
}
.pure-popover.left .pure-arrow {
border-color: transparent #f0f1f3 transparent transparent;
top:45%;
left: -20px;
}
/* BUTTON IMPROVEMENTS */
.pure-button-block {
display: block;
}
.pure-button-small {
padding: .6em 2em .65em;
font-size:70%;
}
.pure-button-large {
padding: .8em 5em .9em;
font-size:110%;
}
.pure-button-selected {
background-color: #345fcb;
color: #fff;
}
.pure-button-secondary {
background: rgb(161, 195, 238);
color: rgb(26, 88, 122);
}
.pure-button-error {
background: rgb(214, 86, 75);
color: white;
}
.pure-button-success {
background: rgb(54, 197, 71);
color: white;
}
.pure-button-warning {
background: rgb(255, 163, 0);
color: white;
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 434 KiB

View file

@ -6,12 +6,7 @@
<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
<link href="img/favicon.png" rel="shortcut icon" type="image/png" />
<link href="img/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" />
<link type="text/css" rel="stylesheet" href="css/pure.min.css?v={$version_hash}" />
<link type="text/css" rel="stylesheet" href="css/grids-responsive.min.css?v={$version_hash}">
<link type="text/css" rel="stylesheet" href="css/pure-extras.css?v={$version_hash}">
<link type="text/css" rel="stylesheet" href="css/font-awesome.min.css?v={$version_hash}" />
<link type="text/css" rel="stylesheet" href="inc/awesomplete.css?v={$version_hash}#" />
<link type="text/css" rel="stylesheet" href="css/shaarli.css?v={$version_hash}" />
<link type="text/css" rel="stylesheet" href="css/shaarli.min.css?v={$version_hash}" />
{if="is_file('data/user.css')"}
<link type="text/css" rel="stylesheet" href="data/user.css#" />
{/if}

View file

@ -1,664 +0,0 @@
/** @licstart The following is the entire license notice for the
* JavaScript code in this page.
*
* Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
* (c) 2011-2017 The Shaarli Community, see AUTHORS
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would
* be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must
* not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* @licend The above is the entire license notice
* for the JavaScript code in this page.
*/
window.onload = function () {
/**
* Retrieve an element up in the tree from its class name.
*/
function getParentByClass(el, className) {
var p = el.parentNode;
if (p == null || p.classList.contains(className)) {
return p;
}
return getParentByClass(p, className);
}
/**
* Handle responsive menu.
* Source: http://purecss.io/layouts/tucked-menu-vertical/
*/
(function (window, document) {
var menu = document.getElementById('shaarli-menu'),
WINDOW_CHANGE_EVENT = ('onorientationchange' in window) ? 'orientationchange':'resize';
function toggleHorizontal() {
[].forEach.call(
document.getElementById('shaarli-menu').querySelectorAll('.menu-transform'),
function(el){
el.classList.toggle('pure-menu-horizontal');
}
);
};
function toggleMenu() {
// set timeout so that the panel has a chance to roll up
// before the menu switches states
if (menu.classList.contains('open')) {
setTimeout(toggleHorizontal, 500);
}
else {
toggleHorizontal();
}
menu.classList.toggle('open');
document.getElementById('menu-toggle').classList.toggle('x');
};
function closeMenu() {
if (menu.classList.contains('open')) {
toggleMenu();
}
}
var menuToggle = document.getElementById('menu-toggle');
if (menuToggle != null) {
menuToggle.addEventListener('click', function (e) {
toggleMenu();
});
}
window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu);
})(this, this.document);
/**
* Fold/Expand shaares description and thumbnail.
*/
var foldAllButtons = document.getElementsByClassName('fold-all');
var foldButtons = document.getElementsByClassName('fold-button');
[].forEach.call(foldButtons, function (foldButton) {
// Retrieve description
var description = null;
var thumbnail = null;
var linklistItem = getParentByClass(foldButton, 'linklist-item');
if (linklistItem != null) {
description = linklistItem.querySelector('.linklist-item-description');
thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
if (description != null || thumbnail != null) {
foldButton.style.display = 'inline';
}
}
foldButton.addEventListener('click', function (event) {
event.preventDefault();
toggleFold(event.target, description, thumbnail);
});
});
if (foldAllButtons != null) {
[].forEach.call(foldAllButtons, function (foldAllButton) {
foldAllButton.addEventListener('click', function (event) {
event.preventDefault();
var state = foldAllButton.firstElementChild.getAttribute('class').indexOf('down') != -1 ? 'down' : 'up';
[].forEach.call(foldButtons, function (foldButton) {
if (foldButton.firstElementChild.classList.contains('fa-chevron-up') && state == 'down'
|| foldButton.firstElementChild.classList.contains('fa-chevron-down') && state == 'up'
) {
return;
}
// Retrieve description
var description = null;
var thumbnail = null;
var linklistItem = getParentByClass(foldButton, 'linklist-item');
if (linklistItem != null) {
description = linklistItem.querySelector('.linklist-item-description');
thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
if (description != null || thumbnail != null) {
foldButton.style.display = 'inline';
}
}
toggleFold(foldButton.firstElementChild, description, thumbnail);
});
foldAllButton.firstElementChild.classList.toggle('fa-chevron-down');
foldAllButton.firstElementChild.classList.toggle('fa-chevron-up');
foldAllButton.title = state === 'down'
? document.getElementById('translation-fold-all').innerHTML
: document.getElementById('translation-expand-all').innerHTML
});
});
}
function toggleFold(button, description, thumb)
{
// Switch fold/expand - up = fold
if (button.classList.contains('fa-chevron-up')) {
button.title = document.getElementById('translation-expand').innerHTML;
if (description != null) {
description.style.display = 'none';
}
if (thumb != null) {
thumb.style.display = 'none';
}
}
else {
button.title = document.getElementById('translation-fold').innerHTML;
if (description != null) {
description.style.display = 'block';
}
if (thumb != null) {
thumb.style.display = 'block';
}
}
button.classList.toggle('fa-chevron-down');
button.classList.toggle('fa-chevron-up');
}
/**
* Confirmation message before deletion.
*/
var deleteLinks = document.querySelectorAll('.confirm-delete');
[].forEach.call(deleteLinks, function(deleteLink) {
deleteLink.addEventListener('click', function(event) {
if(! confirm(document.getElementById('translation-delete-link').innerHTML)) {
event.preventDefault();
}
});
});
/**
* Close alerts
*/
var closeLinks = document.querySelectorAll('.pure-alert-close');
[].forEach.call(closeLinks, function(closeLink) {
closeLink.addEventListener('click', function(event) {
var alert = getParentByClass(event.target, 'pure-alert-closable');
alert.style.display = 'none';
});
});
/**
* New version dismiss.
* Hide the message for one week using localStorage.
*/
var newVersionDismiss = document.getElementById('new-version-dismiss');
var newVersionMessage = document.querySelector('.new-version-message');
if (newVersionMessage != null
&& localStorage.getItem('newVersionDismiss') != null
&& parseInt(localStorage.getItem('newVersionDismiss')) + 7*24*60*60*1000 > (new Date()).getTime()
) {
newVersionMessage.style.display = 'none';
}
if (newVersionDismiss != null) {
newVersionDismiss.addEventListener('click', function () {
localStorage.setItem('newVersionDismiss', (new Date()).getTime());
});
}
var hiddenReturnurl = document.getElementsByName('returnurl');
if (hiddenReturnurl != null) {
hiddenReturnurl.value = window.location.href;
}
/**
* Autofocus text fields
*/
var autofocusElements = document.querySelectorAll('.autofocus');
var breakLoop = false;
[].forEach.call(autofocusElements, function(autofocusElement) {
if (autofocusElement.value == '' && ! breakLoop) {
autofocusElement.focus();
breakLoop = true;
}
});
/**
* Handle sub menus/forms
*/
var openers = document.getElementsByClassName('subheader-opener');
if (openers != null) {
[].forEach.call(openers, function(opener) {
opener.addEventListener('click', function(event) {
event.preventDefault();
var id = opener.getAttribute('data-open-id');
var sub = document.getElementById(id);
if (sub != null) {
[].forEach.call(document.getElementsByClassName('subheader-form'), function (element) {
if (element != sub) {
removeClass(element, 'open')
}
});
sub.classList.toggle('open');
}
});
});
}
function removeClass(element, classname) {
element.className = element.className.replace(new RegExp('(?:^|\\s)'+ classname + '(?:\\s|$)'), ' ');
}
/**
* Remove CSS target padding (for fixed bar)
*/
if (location.hash != '') {
var anchor = document.getElementById(location.hash.substr(1));
if (anchor != null) {
var padsize = anchor.clientHeight;
this.window.scroll(0, this.window.scrollY - padsize);
anchor.style.paddingTop = 0;
}
}
/**
* Text area resizer
*/
var description = document.getElementById('lf_description');
var observe = function (element, event, handler) {
element.addEventListener(event, handler, false);
};
function init () {
function resize () {
/* Fix jumpy resizing: https://stackoverflow.com/a/18262927/1484919 */
var scrollTop = window.pageYOffset ||
(document.documentElement || document.body.parentNode || document.body).scrollTop;
description.style.height = 'auto';
description.style.height = description.scrollHeight+10+'px';
window.scrollTo(0, scrollTop);
}
/* 0-timeout to get the already changed text */
function delayedResize () {
window.setTimeout(resize, 0);
}
observe(description, 'change', resize);
observe(description, 'cut', delayedResize);
observe(description, 'paste', delayedResize);
observe(description, 'drop', delayedResize);
observe(description, 'keydown', delayedResize);
resize();
}
if (description != null) {
init();
// Submit editlink form with CTRL + Enter in the text area.
description.addEventListener('keydown', function (event) {
if (event.ctrlKey && event.keyCode === 13) {
document.getElementById('button-save-edit').click();
}
});
}
/**
* Awesomplete trigger.
*/
var tags = document.getElementById('lf_tags');
if (tags != null) {
awesompleteUniqueTag('#lf_tags');
}
/**
* bLazy trigger
*/
var picwall = document.getElementById('picwall_container');
if (picwall != null) {
var bLazy = new Blazy();
}
/**
* Bookmarklet alert
*/
var bookmarkletLinks = document.querySelectorAll('.bookmarklet-link');
var bkmMessage = document.getElementById('bookmarklet-alert');
[].forEach.call(bookmarkletLinks, function(link) {
link.addEventListener('click', function(event) {
event.preventDefault();
alert(bkmMessage.value);
});
});
/**
* Firefox Social
*/
var ffButton = document.getElementById('ff-social-button');
if (ffButton != null) {
ffButton.addEventListener('click', function(event) {
activateFirefoxSocial(event.target);
});
}
/**
* Plugin admin order
*/
var orderPA = document.querySelectorAll('.order');
[].forEach.call(orderPA, function(link) {
link.addEventListener('click', function(event) {
event.preventDefault();
if (event.target.classList.contains('order-up')) {
return orderUp(event.target.parentNode.parentNode.getAttribute('data-order'));
} else if (event.target.classList.contains('order-down')) {
return orderDown(event.target.parentNode.parentNode.getAttribute('data-order'));
}
});
});
var continent = document.getElementById('continent');
var city = document.getElementById('city');
if (continent != null && city != null) {
continent.addEventListener('change', function (event) {
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
});
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
}
/**
* Bulk actions
*/
var linkCheckboxes = document.querySelectorAll('.delete-checkbox');
var bar = document.getElementById('actions');
[].forEach.call(linkCheckboxes, function(checkbox) {
checkbox.style.display = 'inline-block';
checkbox.addEventListener('click', function(event) {
var count = 0;
var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
[].forEach.call(linkCheckedCheckboxes, function(checkbox) {
count++;
});
if (count == 0 && bar.classList.contains('open')) {
bar.classList.toggle('open');
} else if (count > 0 && ! bar.classList.contains('open')) {
bar.classList.toggle('open');
}
});
});
var deleteButton = document.getElementById('actions-delete');
var token = document.querySelector('input[type="hidden"][name="token"]');
if (deleteButton != null && token != null) {
deleteButton.addEventListener('click', function(event) {
event.preventDefault();
var links = [];
var linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
[].forEach.call(linkCheckedCheckboxes, function(checkbox) {
links.push({
'id': checkbox.value,
'title': document.querySelector('.linklist-item[data-id="'+ checkbox.value +'"] .linklist-link').innerHTML
});
});
var message = 'Are you sure you want to delete '+ links.length +' links?\n';
message += 'This action is IRREVERSIBLE!\n\nTitles:\n';
var ids = [];
links.forEach(function(item) {
message += ' - '+ item['title'] +'\n';
ids.push(item['id']);
});
if (window.confirm(message)) {
window.location = '?delete_link&lf_linkdate='+ ids.join('+') +'&token='+ token.value;
}
});
}
/**
* Tag list operations
*
* TODO: support error code in the backend for AJAX requests
*/
var tagList = document.querySelector('input[name="taglist"]');
var existingTags = tagList ? tagList.value.split(' ') : [];
var awesomepletes = [];
// Display/Hide rename form
var renameTagButtons = document.querySelectorAll('.rename-tag');
[].forEach.call(renameTagButtons, function(rename) {
rename.addEventListener('click', function(event) {
event.preventDefault();
var block = findParent(event.target, 'div', {'class': 'tag-list-item'});
var form = block.querySelector('.rename-tag-form');
if (form.style.display == 'none' || form.style.display == '') {
form.style.display = 'block';
} else {
form.style.display = 'none';
}
block.querySelector('input').focus();
});
});
// Rename a tag with an AJAX request
var renameTagSubmits = document.querySelectorAll('.validate-rename-tag');
[].forEach.call(renameTagSubmits, function(rename) {
rename.addEventListener('click', function(event) {
event.preventDefault();
var block = findParent(event.target, 'div', {'class': 'tag-list-item'});
var input = block.querySelector('.rename-tag-input');
var totag = input.value.replace('/"/g', '\\"');
if (totag.trim() == '') {
return;
}
var fromtag = block.getAttribute('data-tag');
var token = document.getElementById('token').value;
xhr = new XMLHttpRequest();
xhr.open('POST', '?do=changetag');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
if (xhr.status !== 200) {
alert('An error occurred. Return code: '+ xhr.status);
location.reload();
} else {
block.setAttribute('data-tag', totag);
input.setAttribute('name', totag);
input.setAttribute('value', totag);
findParent(input, 'div', {'class': 'rename-tag-form'}).style.display = 'none';
block.querySelector('a.tag-link').innerHTML = htmlEntities(totag);
block.querySelector('a.tag-link').setAttribute('href', '?searchtags='+ encodeURIComponent(totag));
block.querySelector('a.rename-tag').setAttribute('href', '?do=changetag&fromtag='+ encodeURIComponent(totag));
// Refresh awesomplete values
for (var key in existingTags) {
if (existingTags[key] == fromtag) {
existingTags[key] = totag;
}
}
awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
}
};
xhr.send('renametag=1&fromtag='+ encodeURIComponent(fromtag) +'&totag='+ encodeURIComponent(totag) +'&token='+ token);
refreshToken();
});
});
// Validate input with enter key
var renameTagInputs = document.querySelectorAll('.rename-tag-input');
[].forEach.call(renameTagInputs, function(rename) {
rename.addEventListener('keypress', function(event) {
if (event.keyCode === 13) { // enter
findParent(event.target, 'div', {'class': 'tag-list-item'}).querySelector('.validate-rename-tag').click();
}
});
});
// Delete a tag with an AJAX query (alert popup confirmation)
var deleteTagButtons = document.querySelectorAll('.delete-tag');
[].forEach.call(deleteTagButtons, function(rename) {
rename.style.display = 'inline';
rename.addEventListener('click', function(event) {
event.preventDefault();
var block = findParent(event.target, 'div', {'class': 'tag-list-item'});
var tag = block.getAttribute('data-tag');
var token = document.getElementById('token').value;
if (confirm('Are you sure you want to delete the tag "'+ tag +'"?')) {
xhr = new XMLHttpRequest();
xhr.open('POST', '?do=changetag');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
block.remove();
};
xhr.send(encodeURI('deletetag=1&fromtag='+ tag +'&token='+ token));
refreshToken();
}
});
});
updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
};
/**
* Find a parent element according to its tag and its attributes
*
* @param element Element where to start the search
* @param tagName Expected parent tag name
* @param attributes Associative array of expected attributes (name=>value).
*
* @returns Found element or null.
*/
function findParent(element, tagName, attributes)
{
while (element) {
if (element.tagName.toLowerCase() == tagName) {
var match = true;
for (var key in attributes) {
if (! element.hasAttribute(key)
|| (attributes[key] != '' && element.getAttribute(key).indexOf(attributes[key]) == -1)
) {
match = false;
break;
}
}
if (match) {
return element;
}
}
element = element.parentElement;
}
return null;
}
/**
* Ajax request to refresh the CSRF token.
*/
function refreshToken()
{
var xhr = new XMLHttpRequest();
xhr.open('GET', '?do=token');
xhr.onload = function() {
var token = document.getElementById('token');
token.setAttribute('value', xhr.responseText);
};
xhr.send();
}
/**
* Update awesomplete list of tag for all elements matching the given selector
*
* @param selector CSS selector
* @param tags Array of tags
* @param instances List of existing awesomplete instances
*/
function updateAwesompleteList(selector, tags, instances)
{
// First load: create Awesomplete instances
if (instances.length == 0) {
var elements = document.querySelectorAll(selector);
[].forEach.call(elements, function (element) {
instances.push(new Awesomplete(
element,
{'list': tags}
));
});
} else {
// Update awesomplete tag list
for (var key in instances) {
instances[key].list = tags;
}
}
return instances;
}
/**
* html_entities in JS
*
* @see http://stackoverflow.com/questions/18749591/encode-html-entities-in-javascript
*/
function htmlEntities(str)
{
return str.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
return '&#'+i.charCodeAt(0)+';';
});
}
function activateFirefoxSocial(node) {
var loc = location.href;
var baseURL = loc.substring(0, loc.lastIndexOf("/") + 1);
var title = document.title;
// Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable.
var data = {
name: title,
description: document.getElementById('translation-delete-link').innerHTML,
author: "Shaarli",
version: "1.0.0",
iconURL: baseURL + "/images/favicon.ico",
icon32URL: baseURL + "/images/favicon.ico",
icon64URL: baseURL + "/images/favicon.ico",
shareURL: baseURL + "?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi",
homepageURL: baseURL
};
node.setAttribute("data-service", JSON.stringify(data));
var activate = new CustomEvent("ActivateSocialFeature");
node.dispatchEvent(activate);
}
/**
* Add the class 'hidden' to city options not attached to the current selected continent.
*
* @param cities List of <option> elements
* @param currentContinent Current selected continent
* @param reset Set to true to reset the selected value
*/
function hideTimezoneCities(cities, currentContinent) {
var first = true;
if (reset == null) {
reset = false;
}
[].forEach.call(cities, function (option) {
if (option.getAttribute('data-continent') != currentContinent) {
option.className = 'hidden';
} else {
option.className = '';
if (reset === true && first === true) {
option.setAttribute('selected', 'selected');
first = false;
}
}
});
}

View file

@ -16,7 +16,6 @@
</div>
<input type="hidden" name="token" value="{$token}">
<div id="search-linklist">
<form method="GET" class="pure-form searchform" name="searchform">

View file

@ -38,6 +38,4 @@
</span>
</div>
<script src="js/shaarli.js?v={$version_hash}"></script>
<script src="inc/awesomplete.js?v={$version_hash}#"></script>
<script src="inc/awesomplete-multiple-tags.js?v={$version_hash}#"></script>
<script src="js/shaarli.min.js?v={$version_hash}"></script>

View file

@ -39,7 +39,7 @@
</div>
{include="page.footer"}
<script src="inc/blazy-1.3.1.min.js#"></script>
<script src="js/picwall.min.js?v={$version_hash}"></script>
</body>
</html>

View file

@ -176,7 +176,7 @@
</form>
{include="page.footer"}
<script src="inc/plugin_admin.js#"></script>
<script src="js/pluginsadmin.min.js?v={$version_hash}"></script>
</body>
</html>

View file

@ -1,8 +1,6 @@
<!DOCTYPE html>
<html>
<head>{include="includes"}
<link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" />
<script src="inc/awesomplete.min.js#"></script>
</head>
<body onload="document.changetag.fromtag.focus();">
<div id="pageheader">

View file

@ -24,13 +24,13 @@
{/loop}
<br>
<a href="?do=dailyrss" title="1 RSS entry per day"><img src="images/feed-icon-14x14.png#" alt="rss_feed">Daily RSS Feed</a>
<a href="?do=dailyrss" title="1 RSS entry per day"><img src="img/feed-icon-14x14.png" alt="rss_feed">Daily RSS Feed</a>
</div>
<div class="dailyTitle">
<img src="images/floral_left.png" width="51" height="50" class="nomobile" alt="floral_left">
<img src="img/floral_left.png" width="51" height="50" class="nomobile" alt="floral_left">
The Daily Shaarli
<img src="images/floral_right.png" width="51" height="50" class="nomobile" alt="floral_right">
<img src="img/floral_right.png" width="51" height="50" class="nomobile" alt="floral_right">
</div>
<div class="dailyDate">
@ -50,7 +50,7 @@
<div class="dailyEntry">
<div class="dailyEntryPermalink">
<a href="?{$value.shorturl}">
<img src="images/squiggle.png" width="25" height="26" title="permalink" alt="permalink">
<img src="img/squiggle.png" width="25" height="26" title="permalink" alt="permalink">
</a>
</div>
{if="!$hide_timestamps || isLoggedIn()"}
@ -94,7 +94,7 @@
{$value}
{/loop}
</div>
<div id="closing"><img src="images/squiggle_closing.png" width="66" height="61" alt="-"></div>
<div id="closing"><img src="img/squiggle_closing.png" width="66" height="61" alt="-"></div>
</div>
{include="page.footer"}
</body>

View file

@ -54,10 +54,5 @@
{if="$source !== 'firefoxsocialapi'"}
{include="page.footer"}
{/if}
<script src="inc/awesomplete.min.js#"></script>
<script src="inc/awesomplete-multiple-tags.js#"></script>
<script>
awesompleteUniqueTag('#lf_tags');
</script>
</body>
</html>

View file

@ -5,9 +5,8 @@
<meta name="referrer" content="same-origin">
<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
<link href="images/favicon.ico#" rel="shortcut icon" type="image/x-icon" />
<link type="text/css" rel="stylesheet" href="css/reset.css" />
<link type="text/css" rel="stylesheet" href="css/shaarli.css" />
<link href="img/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link type="text/css" rel="stylesheet" href="css/shaarli.min.css" />
{if="is_file('data/user.css')"}<link type="text/css" rel="stylesheet" href="data/user.css#" />{/if}
{loop="$plugins_includes.css_files"}
<link type="text/css" rel="stylesheet" href="{$value}#"/>

View file

@ -1,32 +0,0 @@
window.onload = function () {
var continent = document.getElementById('continent');
var city = document.getElementById('city');
if (continent != null && city != null) {
continent.addEventListener('change', function(event) {
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
});
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
}
};
/**
* Add the class 'hidden' to city options not attached to the current selected continent.
*
* @param cities List of <option> elements
* @param currentContinent Current selected continent
* @param reset Set to true to reset the selected value
*/
function hideTimezoneCities(cities, currentContinent, reset = false) {
var first = true;
[].forEach.call(cities, function(option) {
if (option.getAttribute('data-continent') != currentContinent) {
option.className = 'hidden';
} else {
option.className = '';
if (reset === true && first === true) {
option.setAttribute('selected', 'selected');
first = false;
}
}
});
}

View file

@ -22,7 +22,7 @@
{if="!empty($search_tags)"}
value="{$search_tags}"
{/if}
autocomplete="off" class="awesomplete" data-multiple data-minChars="1"
autocomplete="off" data-multiple data-minChars="1"
data-list="{loop="$tags"}{$key}, {/loop}"
>
<input type="submit" value="Search" class="bigbutton">
@ -86,13 +86,13 @@
<div class="linkeditbuttons">
<form method="GET" class="buttoneditform">
<input type="hidden" name="edit_link" value="{$value.id}">
<input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit">
<input type="image" alt="Edit" src="img/edit_icon.png" title="Edit" class="button_edit">
</form><br>
<form method="GET" class="buttoneditform">
<input type="hidden" name="lf_linkdate" value="{$value.id}">
<input type="hidden" name="token" value="{$token}">
<input type="hidden" name="delete_link">
<input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete"
<input type="image" alt="Delete" src="img/delete_icon.png" title="Delete"
class="button_delete" onClick="return confirmDeleteLink();">
</form>
</div>
@ -146,10 +146,5 @@
{include="page.footer"}
<script src="inc/awesomplete.min.js#"></script>
<script src="inc/awesomplete-multiple-tags.js#"></script>
<script>
awesompleteUniqueTag('#tagfilter_value');
</script>
</body>
</html>

View file

@ -1,11 +1,11 @@
<div class="paging">
{if="isLoggedIn()"}
<div class="paging_privatelinks">
<a href="?visibility=private">
<a href="?visibility=private">
{if="$visibility=='private'"}
<img src="images/private_16x16_active.png#" width="16" height="16" title="Filter links by visibility" alt="Filter links by visibility">
{else}
<img src="images/private_16x16.png#" width="16" height="16" title="Filter links by visibility" alt="Filter links by visibility">
<img src="img/private_16x16_active.png" width="16" height="16" title="Click to see all links" alt="Click to see all links">
{else}
<img src="img/private_16x16.png" width="16" height="16" title="Click to see only private links" alt="Click to see only private links">
{/if}
</a>

View file

@ -22,11 +22,13 @@
Error: {$versionError}
</div>
{/if}
<script src="js/shaarli.min.js"></script>
{if="isLoggedIn()"}
<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script>
{/if}
<script src="js/shaarli.js"></script>
{loop="$plugins_footer.js_files"}
<script src="{$value}#"></script>
{/loop}

View file

@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>{include="includes"}
<script src="inc/blazy-1.3.1.min.js#"></script>
</head>
<body>
<div id="pageheader">{include="page.header"}</div>
@ -35,10 +34,6 @@
{include="page.footer"}
<script>
window.onload = function() {
var bLazy = new Blazy();
}
</script>
<script src="js/picwall.min.js"></script>
</body>
</html>

View file

@ -129,6 +129,77 @@
</div>
{include="page.footer"}
<script src="inc/plugin_admin.js#"></script>
<script>
/**
* Change the position counter of a row.
*
* @param elem Element Node to change.
* @param toPos int New position.
*/
function changePos(elem, toPos) {
var elemName = elem.getAttribute('data-line');
elem.setAttribute('data-order', toPos);
var hiddenInput = document.querySelector('[name="order_' + elemName + '"]');
hiddenInput.setAttribute('value', toPos);
}
/**
* Move a row up or down.
*
* @param pos Element Node to move.
* @param move int Move: +1 (down) or -1 (up)
*/
function changeOrder(pos, move) {
var newpos = parseInt(pos) + move;
var lines = document.querySelectorAll('[data-order="' + pos + '"]');
var changelines = document.querySelectorAll('[data-order="' + newpos + '"]');
// If we go down reverse lines to preserve the rows order
if (move > 0) {
lines = [].slice.call(lines).reverse();
}
for (var i = 0; i < lines.length; i++) {
var parent = changelines[0].parentNode;
changePos(lines[i], newpos);
changePos(changelines[i], parseInt(pos));
var changeItem = move < 0 ? changelines[0] : changelines[changelines.length - 1].nextSibling;
parent.insertBefore(lines[i], changeItem);
}
}
/**
* Move a row up in the table.
*
* @param pos int row counter.
*
* @returns false
*/
function orderUp(pos) {
if (pos == 0) {
return false;
}
changeOrder(pos, -1);
return false;
}
/**
* Move a row down in the table.
*
* @param pos int row counter.
*
* @returns false
*/
function orderDown(pos) {
var lastpos = document.querySelector('[data-order]:last-child').getAttribute('data-order');
if (pos == lastpos) {
return false;
}
changeOrder(pos, +1);
return false;
}
</script>
</body>
</html>

View file

@ -81,9 +81,9 @@
author: "Shaarli",
version: "1.0.0",
iconURL: baseURL + "/images/favicon.ico",
icon32URL: baseURL + "/images/favicon.ico",
icon64URL: baseURL + "/images/favicon.ico",
iconURL: baseURL + "/img/favicon.ico",
icon32URL: baseURL + "/img/favicon.ico",
icon64URL: baseURL + "/img/favicon.ico",
shareURL: baseURL + "{noparse}?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi{/noparse}",
homepageURL: baseURL

149
webpack.config.js Normal file
View file

@ -0,0 +1,149 @@
const path = require('path');
const glob = require('glob');
// Minify JS
const MinifyPlugin = require('babel-minify-webpack-plugin');
// This plugin extracts the CSS into its own file instead of tying it with the JS.
// It prevents:
// - not having styles due to a JS error
// - the flash page without styles during JS loading
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCssDefault = new ExtractTextPlugin({
filename: "../css/[name].min.css",
publicPath: 'tpl/default/css/',
});
const extractCssVintage = new ExtractTextPlugin({
filename: "../css/[name].min.css",
publicPath: 'tpl/vintage/css/',
});
module.exports = [
{
entry: {
picwall: './assets/common/js/picwall.js',
pluginsadmin: './assets/default/js/plugins-admin.js',
shaarli: [
'./assets/default/js/base.js',
'./assets/default/scss/shaarli.scss',
].concat(glob.sync('./assets/default/img/*')),
},
output: {
filename: '[name].min.js',
path: path.resolve(__dirname, 'tpl/default/js/')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'babel-preset-env',
]
}
}
},
{
test: /\.scss/,
use: extractCssDefault.extract({
use: [{
loader: "css-loader",
options: {
minimize: true,
}
}, {
loader: "sass-loader"
}],
})
},
{
test: /\.(gif|png|jpe?g|svg|ico)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '../img/[name].[ext]',
publicPath: 'tpl/default/img/',
}
}
],
},
{
test: /\.(eot|ttf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader',
options: {
name: '../fonts/[name].[ext]',
// do not add a publicPath here because it's already handled by CSS's publicPath
publicPath: '',
}
},
],
},
plugins: [
new MinifyPlugin(),
extractCssDefault,
],
},
{
entry: {
shaarli: [
'./assets/vintage/js/base.js',
'./assets/vintage/css/reset.css',
'./assets/vintage/css/shaarli.css',
].concat(glob.sync('./assets/vintage/img/*')),
picwall: './assets/common/js/picwall.js',
},
output: {
filename: '[name].min.js',
path: path.resolve(__dirname, 'tpl/vintage/js/')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'babel-preset-env',
]
}
}
},
{
test: /\.css$/,
use: extractCssVintage.extract({
use: [{
loader: "css-loader",
options: {
minimize: true,
}
}],
})
},
{
test: /\.(gif|png|jpe?g|svg|ico)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '../img/[name].[ext]',
publicPath: '',
}
}
],
},
],
},
plugins: [
new MinifyPlugin(),
extractCssVintage,
],
},
];

4614
yarn.lock Normal file

File diff suppressed because it is too large Load diff