]> git.immae.eu Git - github/shaarli/Shaarli.git/blame - tpl/default/js/shaarli.js
Bulk deletion
[github/shaarli/Shaarli.git] / tpl / default / js / shaarli.js
CommitLineData
b9b41d25
A
1/** @licstart The following is the entire license notice for the
2 * JavaScript code in this page.
3 *
4 * Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
5 * (c) 2011-2017 The Shaarli Community, see AUTHORS
6 *
7 * This software is provided 'as-is', without any express or implied warranty.
8 * In no event will the authors be held liable for any damages arising from
9 * the use of this software.
10 *
11 * Permission is granted to anyone to use this software for any purpose,
12 * including commercial applications, and to alter it and redistribute it
13 * freely, subject to the following restrictions:
14 *
15 * 1. The origin of this software must not be misrepresented; you must not
16 * claim that you wrote the original software. If you use this software
17 * in a product, an acknowledgment in the product documentation would
18 * be appreciated but is not required.
19 *
20 * 2. Altered source versions must be plainly marked as such, and must
21 * not be misrepresented as being the original software.
22 *
23 * 3. This notice may not be removed or altered from any source distribution.
24 *
25 * @licend The above is the entire license notice
26 * for the JavaScript code in this page.
27 */
28
402b0346
A
29window.onload = function () {
30
31 /**
32 * Retrieve an element up in the tree from its class name.
33 */
34 function getParentByClass(el, className) {
35 var p = el.parentNode;
36 if (p == null || p.classList.contains(className)) {
37 return p;
38 }
39 return getParentByClass(p, className);
40 }
41
42
43 /**
44 * Handle responsive menu.
45 * Source: http://purecss.io/layouts/tucked-menu-vertical/
46 */
47 (function (window, document) {
48 var menu = document.getElementById('shaarli-menu'),
49 WINDOW_CHANGE_EVENT = ('onorientationchange' in window) ? 'orientationchange':'resize';
50
51 function toggleHorizontal() {
52 [].forEach.call(
53 document.getElementById('shaarli-menu').querySelectorAll('.menu-transform'),
54 function(el){
55 el.classList.toggle('pure-menu-horizontal');
56 }
57 );
58 };
59
60 function toggleMenu() {
61 // set timeout so that the panel has a chance to roll up
62 // before the menu switches states
63 if (menu.classList.contains('open')) {
64 setTimeout(toggleHorizontal, 500);
65 }
66 else {
67 toggleHorizontal();
68 }
69 menu.classList.toggle('open');
70 document.getElementById('menu-toggle').classList.toggle('x');
71 };
72
73 function closeMenu() {
74 if (menu.classList.contains('open')) {
75 toggleMenu();
76 }
77 }
78
a0737313
A
79 var menuToggle = document.getElementById('menu-toggle');
80 if (menuToggle != null) {
81 menuToggle.addEventListener('click', function (e) {
82 toggleMenu();
83 });
84 }
402b0346
A
85
86 window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu);
87 })(this, this.document);
88
89 /**
90 * Fold/Expand shaares description and thumbnail.
91 */
92 var foldAllButtons = document.getElementsByClassName('fold-all');
93 var foldButtons = document.getElementsByClassName('fold-button');
94
95 [].forEach.call(foldButtons, function (foldButton) {
96 // Retrieve description
97 var description = null;
98 var thumbnail = null;
99 var linklistItem = getParentByClass(foldButton, 'linklist-item');
100 if (linklistItem != null) {
101 description = linklistItem.querySelector('.linklist-item-description');
102 thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
103 if (description != null || thumbnail != null) {
104 foldButton.style.display = 'inline';
105 }
106 }
107
108 foldButton.addEventListener('click', function (event) {
109 event.preventDefault();
110 toggleFold(event.target, description, thumbnail);
111 });
112 });
113
114 if (foldAllButtons != null) {
115 [].forEach.call(foldAllButtons, function (foldAllButton) {
116 foldAllButton.addEventListener('click', function (event) {
117 event.preventDefault();
70401690 118 var state = foldAllButton.firstElementChild.getAttribute('class').indexOf('down') != -1 ? 'down' : 'up';
402b0346 119 [].forEach.call(foldButtons, function (foldButton) {
70401690
A
120 if (foldButton.firstElementChild.classList.contains('fa-chevron-up') && state == 'down'
121 || foldButton.firstElementChild.classList.contains('fa-chevron-down') && state == 'up'
122 ) {
123 return;
124 }
402b0346
A
125 // Retrieve description
126 var description = null;
127 var thumbnail = null;
128 var linklistItem = getParentByClass(foldButton, 'linklist-item');
129 if (linklistItem != null) {
130 description = linklistItem.querySelector('.linklist-item-description');
131 thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
132 if (description != null || thumbnail != null) {
133 foldButton.style.display = 'inline';
134 }
135 }
136
137 toggleFold(foldButton.firstElementChild, description, thumbnail);
138 });
139 foldAllButton.firstElementChild.classList.toggle('fa-chevron-down');
140 foldAllButton.firstElementChild.classList.toggle('fa-chevron-up');
141 });
142 });
143 }
144
145 function toggleFold(button, description, thumb)
146 {
147 // Switch fold/expand - up = fold
148 if (button.classList.contains('fa-chevron-up')) {
149 button.title = 'Expand';
150 if (description != null) {
151 description.style.display = 'none';
152 }
153 if (thumb != null) {
154 thumb.style.display = 'none';
155 }
156 }
157 else {
158 button.title = 'Fold';
159 if (description != null) {
160 description.style.display = 'block';
161 }
162 if (thumb != null) {
163 thumb.style.display = 'block';
164 }
165 }
166 button.classList.toggle('fa-chevron-down');
167 button.classList.toggle('fa-chevron-up');
168 }
169
170 /**
171 * Confirmation message before deletion.
172 */
173 var deleteLinks = document.querySelectorAll('.confirm-delete');
174 [].forEach.call(deleteLinks, function(deleteLink) {
175 deleteLink.addEventListener('click', function(event) {
176 if(! confirm('Are you sure you want to delete this link ?')) {
177 event.preventDefault();
178 }
179 });
180 });
181
182 /**
183 * Close alerts
184 */
185 var closeLinks = document.querySelectorAll('.pure-alert-close');
186 [].forEach.call(closeLinks, function(closeLink) {
187 closeLink.addEventListener('click', function(event) {
188 var alert = getParentByClass(event.target, 'pure-alert-closable');
189 alert.style.display = 'none';
190 });
191 });
192
193 /**
194 * New version dismiss.
195 * Hide the message for one week using localStorage.
196 */
197 var newVersionDismiss = document.getElementById('new-version-dismiss');
198 var newVersionMessage = document.querySelector('.new-version-message');
199 if (newVersionMessage != null
200 && localStorage.getItem('newVersionDismiss') != null
201 && parseInt(localStorage.getItem('newVersionDismiss')) + 7*24*60*60*1000 > (new Date()).getTime()
202 ) {
203 newVersionMessage.style.display = 'none';
204 }
205 if (newVersionDismiss != null) {
206 newVersionDismiss.addEventListener('click', function () {
207 localStorage.setItem('newVersionDismiss', (new Date()).getTime());
208 });
209 }
210
211 var hiddenReturnurl = document.getElementsByName('returnurl');
212 if (hiddenReturnurl != null) {
213 hiddenReturnurl.value = window.location.href;
214 }
215
216 /**
217 * Autofocus text fields
218 */
b9b41d25
A
219 // ES6 syntax
220 let autofocusElements = document.querySelectorAll('.autofocus');
221 for (let autofocusElement of autofocusElements) {
222 if (autofocusElement.value == '') {
223 autofocusElement.focus();
224 break;
225 }
402b0346
A
226 }
227
228 /**
229 * Handle sub menus/forms
230 */
231 var openers = document.getElementsByClassName('subheader-opener');
232 if (openers != null) {
233 [].forEach.call(openers, function(opener) {
234 opener.addEventListener('click', function(event) {
235 event.preventDefault();
236
237 var id = opener.getAttribute('data-open-id');
238 var sub = document.getElementById(id);
239
240 if (sub != null) {
241 [].forEach.call(document.getElementsByClassName('subheader-form'), function (element) {
242 if (element != sub) {
243 removeClass(element, 'open')
244 }
245 });
246
247 sub.classList.toggle('open');
248 }
249 });
250 });
251 }
252
253 function removeClass(element, classname) {
254 element.className = element.className.replace(new RegExp('(?:^|\\s)'+ classname + '(?:\\s|$)'), ' ');
255 }
256
257 /**
258 * Remove CSS target padding (for fixed bar)
259 */
260 if (location.hash != '') {
0040058d 261 var anchor = document.getElementById(location.hash.substr(1));
402b0346
A
262 if (anchor != null) {
263 var padsize = anchor.clientHeight;
402b0346
A
264 this.window.scroll(0, this.window.scrollY - padsize);
265 anchor.style.paddingTop = 0;
266 }
267 }
70401690
A
268
269 /**
270 * Text area resizer
271 */
272 var description = document.getElementById('lf_description');
273 var observe = function (element, event, handler) {
274 element.addEventListener(event, handler, false);
275 };
276 function init () {
277 function resize () {
278 description.style.height = 'auto';
279 description.style.height = description.scrollHeight+10+'px';
280 }
281 /* 0-timeout to get the already changed text */
282 function delayedResize () {
283 window.setTimeout(resize, 0);
284 }
285 observe(description, 'change', resize);
286 observe(description, 'cut', delayedResize);
287 observe(description, 'paste', delayedResize);
288 observe(description, 'drop', delayedResize);
289 observe(description, 'keydown', delayedResize);
290
291 resize();
292 }
2dd698fd 293
70401690
A
294 if (description != null) {
295 init();
2dd698fd
A
296 // Submit editlink form with CTRL + Enter in the text area.
297 description.addEventListener('keydown', function (event) {
298 if (event.ctrlKey && event.keyCode === 13) {
299 document.getElementById('button-save-edit').click();
300 }
301 });
70401690 302 }
b9b41d25 303
b9b41d25
A
304 /**
305 * Awesomplete trigger.
306 */
307 var tags = document.getElementById('lf_tags');
308 if (tags != null) {
309 awesompleteUniqueTag('#lf_tags');
310 }
311
312 /**
313 * bLazy trigger
314 */
315 var picwall = document.getElementById('picwall_container');
316 if (picwall != null) {
317 var bLazy = new Blazy();
318 }
319
320 /**
321 * Bookmarklet alert
322 */
323 var bookmarkletLinks = document.querySelectorAll('.bookmarklet-link');
324 var bkmMessage = document.getElementById('bookmarklet-alert');
325 [].forEach.call(bookmarkletLinks, function(link) {
326 link.addEventListener('click', function(event) {
327 event.preventDefault();
328 alert(bkmMessage.value);
329 });
330 });
331
332 /**
333 * Firefox Social
334 */
335 var ffButton = document.getElementById('ff-social-button');
336 if (ffButton != null) {
337 ffButton.addEventListener('click', function(event) {
338 activateFirefoxSocial(event.target);
339 });
340 }
341
342 /**
343 * Plugin admin order
344 */
345 var orderPA = document.querySelectorAll('.order');
346 [].forEach.call(orderPA, function(link) {
347 link.addEventListener('click', function(event) {
348 event.preventDefault();
349 if (event.target.classList.contains('order-up')) {
350 return orderUp(event.target.parentNode.parentNode.getAttribute('data-order'));
351 } else if (event.target.classList.contains('order-down')) {
352 return orderDown(event.target.parentNode.parentNode.getAttribute('data-order'));
353 }
354 });
355 });
a0737313
A
356
357 var continent = document.getElementById('continent');
358 var city = document.getElementById('city');
359 if (continent != null && city != null) {
29a837f3 360 continent.addEventListener('change', function (event) {
a0737313
A
361 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, true);
362 });
363 hideTimezoneCities(city, continent.options[continent.selectedIndex].value, false);
364 }
29a837f3
A
365
366 /**
367 * Bulk actions
368 *
369 * Note: Requires a modern browser.
370 */
371 if (testEs6Compatibility()) {
372 let linkCheckboxes = document.querySelectorAll('.delete-checkbox');
373 for(let checkbox of linkCheckboxes) {
374 checkbox.style.display = 'block';
375 checkbox.addEventListener('click', function(event) {
376 let count = 0;
377 for(let checkbox of linkCheckboxes) {
378 count = checkbox.checked ? count + 1 : count;
379 }
380 let bar = document.getElementById('actions');
381 if (count == 0 && bar.classList.contains('open')) {
382 bar.classList.toggle('open');
383 } else if (count > 0 && ! bar.classList.contains('open')) {
384 bar.classList.toggle('open');
385 }
386 });
387 }
388
389 let deleteButton = document.getElementById('actions-delete');
390 let token = document.querySelector('input[type="hidden"][name="token"]');
391 if (deleteButton != null && token != null) {
392 deleteButton.addEventListener('click', function(event) {
393 event.preventDefault();
394
395 let links = [];
396 for(let checkbox of linkCheckboxes) {
397 if (checkbox.checked) {
398 links.push({
399 'id': checkbox.value,
400 'title': document.querySelector('.linklist-item[data-id="'+ checkbox.value +'"] .linklist-link').innerHTML
401 });
402 }
403 }
404
405 let message = 'Are you sure you want to delete '+ links.length +' links?\n';
406 message += 'This action is IRREVERSIBLE!\n\nTitles:\n';
407 let ids = '';
408 for (let item of links) {
409 message += ' - '+ item['title'] +'\n';
410 ids += item['id'] +'+';
411 }
412 if (window.confirm(message)) {
413 window.location = '?delete_link&lf_linkdate='+ ids +'&token='+ token.value;
414 }
415 });
416 }
417 }
402b0346 418};
b9b41d25
A
419
420function activateFirefoxSocial(node) {
421 var loc = location.href;
422 var baseURL = loc.substring(0, loc.lastIndexOf("/"));
423
424 // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable.
425 var data = {
426 name: "{$shaarlititle}",
427 description: "The personal, minimalist, super-fast, database free, bookmarking service by the Shaarli community.",
428 author: "Shaarli",
429 version: "1.0.0",
430
431 iconURL: baseURL + "/images/favicon.ico",
432 icon32URL: baseURL + "/images/favicon.ico",
433 icon64URL: baseURL + "/images/favicon.ico",
434
435 shareURL: baseURL + "{noparse}?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi{/noparse}",
436 homepageURL: baseURL
437 };
438 node.setAttribute("data-service", JSON.stringify(data));
439
440 var activate = new CustomEvent("ActivateSocialFeature");
441 node.dispatchEvent(activate);
442}
a0737313
A
443
444/**
445 * Add the class 'hidden' to city options not attached to the current selected continent.
446 *
447 * @param cities List of <option> elements
448 * @param currentContinent Current selected continent
449 * @param reset Set to true to reset the selected value
450 */
451function hideTimezoneCities(cities, currentContinent, reset = false) {
452 var first = true;
29a837f3 453 [].forEach.call(cities, function (option) {
a0737313
A
454 if (option.getAttribute('data-continent') != currentContinent) {
455 option.className = 'hidden';
456 } else {
457 option.className = '';
458 if (reset === true && first === true) {
459 option.setAttribute('selected', 'selected');
460 first = false;
461 }
462 }
463 });
464}
29a837f3
A
465
466/**
467 * Check if the browser is compatible with ECMAScript 6 syntax
468 *
469 * Source: http://stackoverflow.com/a/29046739/1484919
470 *
471 * @returns {boolean}
472 */
473function testEs6Compatibility()
474{
475 "use strict";
476
477 try { eval("var foo = (x)=>x+1"); }
478 catch (e) { return false; }
479 return true;
480}