import Awesomplete from 'awesomplete';
+import he from 'he';
/**
* Find a parent element according to its tag and its attributes
* @returns Found element or null.
*/
function findParent(element, tagName, attributes) {
- const parentMatch = key => attributes[key] !== '' && element.getAttribute(key).indexOf(attributes[key]) !== -1;
+ 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)) {
/**
* Ajax request to refresh the CSRF token.
*/
-function refreshToken() {
+function refreshToken(basePath, callback) {
const xhr = new XMLHttpRequest();
- xhr.open('GET', '?do=token');
+ xhr.open('GET', `${basePath}/admin/token`);
xhr.onload = () => {
- const token = document.getElementById('token');
- token.setAttribute('value', xhr.responseText);
+ const elements = document.querySelectorAll('input[name="token"]');
+ [...elements].forEach((element) => {
+ element.setAttribute('value', xhr.responseText);
+ });
+
+ if (callback) {
+ callback(xhr.response);
+ }
};
xhr.send();
}
-function createAwesompleteInstance(element, tags = []) {
+function createAwesompleteInstance(element, separator, tags = []) {
const awesome = new Awesomplete(Awesomplete.$(element));
- // Tags are separated by a space
- awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
+
+ // Tags are separated by separator
+ awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(new RegExp(`[^${separator}]*$`))[0]);
// Insert new selected tag in the input
awesome.replace = (text) => {
- const before = awesome.input.value.match(/^.+ \s*|/)[0];
- awesome.input.value = `${before}${text} `;
+ const before = awesome.input.value.match(new RegExp(`^.+${separator}+|`))[0];
+ awesome.input.value = `${before}${text}${separator}`;
};
// Highlight found items
- awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(/[^ ]*$/)[0]);
+ awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${separator}]*$`))[0]);
// Don't display already selected items
- const reg = /(\w+) /g;
+ // WARNING: pseudo classes does not seem to work with string litterals...
+ const reg = new RegExp(`([^${separator}]+)${separator}`, 'g');
let match;
awesome.data = (item, input) => {
while ((match = reg.exec(input))) {
* @param selector CSS selector
* @param tags Array of tags
* @param instances List of existing awesomplete instances
+ * @param separator Tags separator character
*/
-function updateAwesompleteList(selector, tags, instances) {
+function updateAwesompleteList(selector, tags, instances, separator) {
if (instances.length === 0) {
// First load: create Awesomplete instances
const elements = document.querySelectorAll(selector);
[...elements].forEach((element) => {
- instances.push(createAwesompleteInstance(element, tags));
+ instances.push(createAwesompleteInstance(element, separator, tags));
});
} else {
// Update awesomplete tag list
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)};`);
-}
-
/**
* Add the class 'hidden' to city options not attached to the current selected continent.
*
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;
+ const scrollTop = window.pageYOffset
+ || (document.documentElement || document.body.parentNode || document.body).scrollTop;
description.style.height = 'auto';
description.style.height = `${description.scrollHeight + 10}px`;
}
(() => {
+ const basePath = document.querySelector('input[name="js_base_path"]').value;
+ const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]');
+ const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || ' ' : ' ';
+
/**
* Handle responsive menu.
* Source: http://purecss.io/layouts/tucked-menu-vertical/
const deleteLinks = document.querySelectorAll('.confirm-delete');
[...deleteLinks].forEach((deleteLink) => {
deleteLink.addEventListener('click', (event) => {
- if (!confirm(document.getElementById('translation-delete-link').innerHTML)) {
+ const type = event.currentTarget.getAttribute('data-type') || 'link';
+ if (!confirm(document.getElementById(`translation-delete-${type}`).innerHTML)) {
event.preventDefault();
}
});
/**
* Bulk actions
*/
- const linkCheckboxes = document.querySelectorAll('.delete-checkbox');
+ const linkCheckboxes = document.querySelectorAll('.link-checkbox');
const bar = document.getElementById('actions');
[...linkCheckboxes].forEach((checkbox) => {
checkbox.style.display = 'inline-block';
- checkbox.addEventListener('click', () => {
- const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
+ checkbox.addEventListener('change', () => {
+ const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
const count = [...linkCheckedCheckboxes].length;
if (count === 0 && bar.classList.contains('open')) {
bar.classList.toggle('open');
event.preventDefault();
const links = [];
- const linkCheckedCheckboxes = document.querySelectorAll('.delete-checkbox:checked');
+ const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
[...linkCheckedCheckboxes].forEach((checkbox) => {
links.push({
id: checkbox.value,
});
if (window.confirm(message)) {
- window.location = `?delete_link&lf_linkdate=${ids.join('+')}&token=${token.value}`;
+ window.location = `${basePath}/admin/shaare/delete?id=${ids.join('+')}&token=${token.value}`;
}
});
}
+ const changeVisibilityButtons = document.querySelectorAll('.actions-change-visibility');
+ if (changeVisibilityButtons != null && token != null) {
+ [...changeVisibilityButtons].forEach((button) => {
+ button.addEventListener('click', (event) => {
+ event.preventDefault();
+ const visibility = event.target.getAttribute('data-visibility');
+
+ const links = [];
+ const linkCheckedCheckboxes = document.querySelectorAll('.link-checkbox:checked');
+ [...linkCheckedCheckboxes].forEach((checkbox) => {
+ links.push({
+ id: checkbox.value,
+ title: document.querySelector(`.linklist-item[data-id="${checkbox.value}"] .linklist-link`).innerHTML,
+ });
+ });
+
+ const ids = links.map((item) => item.id);
+ window.location = (
+ `${basePath}/admin/shaare/visibility?token=${token.value}&newVisibility=${visibility}&id=${ids.join('+')}`
+ );
+ });
+ });
+ }
+
+ /**
+ * Select all button
+ */
+ const selectAllButtons = document.querySelectorAll('.select-all-button');
+ [...selectAllButtons].forEach((selectAllButton) => {
+ selectAllButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ const checked = selectAllButton.classList.contains('filter-off');
+ [...selectAllButtons].forEach((selectAllButton2) => {
+ selectAllButton2.classList.toggle('filter-off');
+ selectAllButton2.classList.toggle('filter-on');
+ });
+ [...linkCheckboxes].forEach((linkCheckbox) => {
+ linkCheckbox.checked = checked;
+ linkCheckbox.dispatchEvent(new Event('change'));
+ });
+ });
+ });
+
/**
* Tag list operations
*
}
const refreshedToken = document.getElementById('token').value;
const fromtag = block.getAttribute('data-tag');
+ const fromtagUrl = block.getAttribute('data-tag-url');
const xhr = new XMLHttpRequest();
- xhr.open('POST', '?do=changetag');
+ xhr.open('POST', `${basePath}/admin/tags`);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
if (xhr.status !== 200) {
location.reload();
} else {
block.setAttribute('data-tag', totag);
+ block.setAttribute('data-tag-url', encodeURIComponent(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)}`);
+ block.querySelector('a.tag-link').innerHTML = he.encode(totag);
+ block
+ .querySelector('a.tag-link')
+ .setAttribute('href', `${basePath}/?searchtags=${encodeURIComponent(totag)}`);
+ block
+ .querySelector('a.count')
+ .setAttribute('href', `${basePath}/add-tag/${encodeURIComponent(totag)}`);
+ block
+ .querySelector('a.rename-tag')
+ .setAttribute('href', `${basePath}/admin/tags?fromtag=${encodeURIComponent(totag)}`);
// Refresh awesomplete values
- existingTags = existingTags.map(tag => (tag === fromtag ? totag : tag));
- awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
+ existingTags = existingTags.map((tag) => (tag === fromtag ? totag : tag));
+ awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator);
}
};
- xhr.send(`renametag=1&fromtag=${encodeURIComponent(fromtag)}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
- refreshToken();
+ xhr.send(`renametag=1&fromtag=${fromtagUrl}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
+ refreshToken(basePath);
});
});
event.preventDefault();
const block = findParent(event.target, 'div', { class: 'tag-list-item' });
const tag = block.getAttribute('data-tag');
+ const tagUrl = block.getAttribute('data-tag-url');
const refreshedToken = document.getElementById('token').value;
if (confirm(`Are you sure you want to delete the tag "${tag}"?`)) {
const xhr = new XMLHttpRequest();
- xhr.open('POST', '?do=changetag');
+ xhr.open('POST', `${basePath}/admin/tags`);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = () => {
block.remove();
};
- xhr.send(encodeURI(`deletetag=1&fromtag=${tag}&token=${refreshedToken}`));
- refreshToken();
+ xhr.send(`deletetag=1&fromtag=${tagUrl}&token=${refreshedToken}`);
+ refreshToken(basePath);
- existingTags = existingTags.filter(tagItem => tagItem !== tag);
- awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
+ existingTags = existingTags.filter((tagItem) => tagItem !== tag);
+ awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator);
}
});
});
const autocompleteFields = document.querySelectorAll('input[data-multiple]');
[...autocompleteFields].forEach((autocompleteField) => {
- awesomepletes.push(createAwesompleteInstance(autocompleteField));
+ awesomepletes.push(createAwesompleteInstance(autocompleteField, tagsSeparator));
});
+
+ const exportForm = document.querySelector('#exportform');
+ if (exportForm != null) {
+ exportForm.addEventListener('submit', (event) => {
+ event.preventDefault();
+
+ refreshToken(basePath, () => {
+ event.target.submit();
+ });
+ });
+ }
+
+ const bulkCreationButton = document.querySelector('.addlink-batch-show-more-block');
+ if (bulkCreationButton != null) {
+ const toggleBulkCreationVisibility = (showMoreBlockElement, formElement) => {
+ if (bulkCreationButton.classList.contains('pure-u-0')) {
+ showMoreBlockElement.classList.remove('pure-u-0');
+ formElement.classList.add('pure-u-0');
+ } else {
+ showMoreBlockElement.classList.add('pure-u-0');
+ formElement.classList.remove('pure-u-0');
+ }
+ };
+
+ const bulkCreationForm = document.querySelector('.addlink-batch-form-block');
+
+ toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm);
+ bulkCreationButton.querySelector('a').addEventListener('click', (e) => {
+ e.preventDefault();
+ toggleBulkCreationVisibility(bulkCreationButton, bulkCreationForm);
+ });
+
+ // Force to send falsy value if the checkbox is not checked.
+ const privateButton = bulkCreationForm.querySelector('input[type="checkbox"][name="private"]');
+ const privateHiddenButton = bulkCreationForm.querySelector('input[type="hidden"][name="private"]');
+ privateButton.addEventListener('click', () => {
+ privateHiddenButton.disabled = !privateHiddenButton.disabled;
+ });
+ privateHiddenButton.disabled = privateButton.checked;
+ }
})();