Bulk deletion
* Add a checkboxes in linklist which display a sub-header containing action buttons * Strongly rely on JS * Requires a modern browser (ES6 syntax support) * Checkboxes are hidden if the browser is old or JS disabled
This commit is contained in:
parent
bf67ac345f
commit
29a837f347
5 changed files with 106 additions and 11 deletions
13
index.php
13
index.php
|
@ -1329,18 +1329,21 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history)
|
||||||
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
|
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
|
||||||
if ($targetPage == Router::$PAGE_DELETELINK)
|
if ($targetPage == Router::$PAGE_DELETELINK)
|
||||||
{
|
{
|
||||||
// We do not need to ask for confirmation:
|
|
||||||
// - confirmation is handled by JavaScript
|
|
||||||
// - we are protected from XSRF by the token.
|
|
||||||
|
|
||||||
if (! tokenOk($_GET['token'])) {
|
if (! tokenOk($_GET['token'])) {
|
||||||
die('Wrong token.');
|
die('Wrong token.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = intval(escape($_GET['lf_linkdate']));
|
if (strpos($_GET['lf_linkdate'], ' ') !== false) {
|
||||||
|
$ids = array_values(array_filter(preg_split('/\s+/', escape($_GET['lf_linkdate']))));
|
||||||
|
} else {
|
||||||
|
$ids = [$_GET['lf_linkdate']];
|
||||||
|
}
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$id = (int) escape($id);
|
||||||
$link = $LINKSDB[$id];
|
$link = $LINKSDB[$id];
|
||||||
$pluginManager->executeHooks('delete_link', $link);
|
$pluginManager->executeHooks('delete_link', $link);
|
||||||
unset($LINKSDB[$id]);
|
unset($LINKSDB[$id]);
|
||||||
|
}
|
||||||
$LINKSDB->save($conf->get('resource.page_cache')); // save to disk
|
$LINKSDB->save($conf->get('resource.page_cache')); // save to disk
|
||||||
$history->deleteLink($link);
|
$history->deleteLink($link);
|
||||||
|
|
||||||
|
|
|
@ -275,6 +275,19 @@ body, .pure-g [class*="pure-u"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subheader-form a.button {
|
||||||
|
color: #f5f5f5;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 2px solid #f5f5f5;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linklist-item-editbuttons .delete-checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#header-login-form input[type="text"], #header-login-form input[type="password"] {
|
#header-login-form input[type="text"], #header-login-form input[type="password"] {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,11 +357,64 @@ window.onload = function () {
|
||||||
var continent = document.getElementById('continent');
|
var continent = document.getElementById('continent');
|
||||||
var city = document.getElementById('city');
|
var city = document.getElementById('city');
|
||||||
if (continent != null && city != null) {
|
if (continent != null && city != null) {
|
||||||
continent.addEventListener('change', function(event) {
|
continent.addEventListener('change', function (event) {
|
||||||
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
|
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
|
||||||
});
|
});
|
||||||
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
|
hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk actions
|
||||||
|
*
|
||||||
|
* Note: Requires a modern browser.
|
||||||
|
*/
|
||||||
|
if (testEs6Compatibility()) {
|
||||||
|
let linkCheckboxes = document.querySelectorAll('.delete-checkbox');
|
||||||
|
for(let checkbox of linkCheckboxes) {
|
||||||
|
checkbox.style.display = 'block';
|
||||||
|
checkbox.addEventListener('click', function(event) {
|
||||||
|
let count = 0;
|
||||||
|
for(let checkbox of linkCheckboxes) {
|
||||||
|
count = checkbox.checked ? count + 1 : count;
|
||||||
|
}
|
||||||
|
let bar = document.getElementById('actions');
|
||||||
|
if (count == 0 && bar.classList.contains('open')) {
|
||||||
|
bar.classList.toggle('open');
|
||||||
|
} else if (count > 0 && ! bar.classList.contains('open')) {
|
||||||
|
bar.classList.toggle('open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let deleteButton = document.getElementById('actions-delete');
|
||||||
|
let token = document.querySelector('input[type="hidden"][name="token"]');
|
||||||
|
if (deleteButton != null && token != null) {
|
||||||
|
deleteButton.addEventListener('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let links = [];
|
||||||
|
for(let checkbox of linkCheckboxes) {
|
||||||
|
if (checkbox.checked) {
|
||||||
|
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';
|
||||||
|
let ids = '';
|
||||||
|
for (let item of links) {
|
||||||
|
message += ' - '+ item['title'] +'\n';
|
||||||
|
ids += item['id'] +'+';
|
||||||
|
}
|
||||||
|
if (window.confirm(message)) {
|
||||||
|
window.location = '?delete_link&lf_linkdate='+ ids +'&token='+ token.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function activateFirefoxSocial(node) {
|
function activateFirefoxSocial(node) {
|
||||||
|
@ -397,7 +450,7 @@ function activateFirefoxSocial(node) {
|
||||||
*/
|
*/
|
||||||
function hideTimezoneCities(cities, currentContinent, reset = false) {
|
function hideTimezoneCities(cities, currentContinent, reset = false) {
|
||||||
var first = true;
|
var first = true;
|
||||||
[].forEach.call(cities, function(option) {
|
[].forEach.call(cities, function (option) {
|
||||||
if (option.getAttribute('data-continent') != currentContinent) {
|
if (option.getAttribute('data-continent') != currentContinent) {
|
||||||
option.className = 'hidden';
|
option.className = 'hidden';
|
||||||
} else {
|
} else {
|
||||||
|
@ -409,3 +462,19 @@ function hideTimezoneCities(cities, currentContinent, reset = false) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the browser is compatible with ECMAScript 6 syntax
|
||||||
|
*
|
||||||
|
* Source: http://stackoverflow.com/a/29046739/1484919
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function testEs6Compatibility()
|
||||||
|
{
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
try { eval("var foo = (x)=>x+1"); }
|
||||||
|
catch (e) { return false; }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="token" value="{$token}">
|
||||||
|
|
||||||
<div id="search-linklist">
|
<div id="search-linklist">
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
|
@ -121,7 +123,7 @@
|
||||||
<div class="pure-u-lg-20-24 pure-u-22-24">
|
<div class="pure-u-lg-20-24 pure-u-22-24">
|
||||||
{loop="links"}
|
{loop="links"}
|
||||||
<div class="anchor" id="{$value.shorturl}"></div>
|
<div class="anchor" id="{$value.shorturl}"></div>
|
||||||
<div class="linklist-item{if="$value.class"} {$value.class}{/if}">
|
<div class="linklist-item linklist-item{if="$value.class"} {$value.class}{/if}" data-id="{$value.id}">
|
||||||
|
|
||||||
<div class="linklist-item-title">
|
<div class="linklist-item-title">
|
||||||
{if="isLoggedIn()"}
|
{if="isLoggedIn()"}
|
||||||
|
@ -129,6 +131,7 @@
|
||||||
{if="$value.private"}
|
{if="$value.private"}
|
||||||
<span class="label label-private">{'Private'|t}</span>
|
<span class="label label-private">{'Private'|t}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
<input type="checkbox" class="delete-checkbox" value="{$value.id}">
|
||||||
<!-- FIXME! JS translation -->
|
<!-- FIXME! JS translation -->
|
||||||
<a href="?edit_link={$value.id}" title="{'Edit'|t}"><i class="fa fa-pencil-square-o edit-link"></i></a>
|
<a href="?edit_link={$value.id}" title="{'Edit'|t}"><i class="fa fa-pencil-square-o edit-link"></i></a>
|
||||||
<a href="#" title="{'Fold'|t}" class="fold-button"><i class="fa fa-chevron-up"></i></a>
|
<a href="#" title="{'Fold'|t}" class="fold-button"><i class="fa fa-chevron-up"></i></a>
|
||||||
|
|
|
@ -122,6 +122,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="actions" class="subheader-form">
|
||||||
|
<div class="pure-g">
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<a href="" id="actions-delete" class="button">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{if="!isLoggedIn()"}
|
{if="!isLoggedIn()"}
|
||||||
<form method="post" name="loginform">
|
<form method="post" name="loginform">
|
||||||
<div class="subheader-form" id="header-login-form">
|
<div class="subheader-form" id="header-login-form">
|
||||||
|
|
Loading…
Reference in a new issue