]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - tpl/default/js/shaarli.js
Theme: JS - Fix a bug preventing edit margin suppression to work
[github/shaarli/Shaarli.git] / tpl / default / js / shaarli.js
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
29 window.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
79 document.getElementById('menu-toggle').addEventListener('click', function (e) {
80 toggleMenu();
81 });
82
83 window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu);
84 })(this, this.document);
85
86 /**
87 * Fold/Expand shaares description and thumbnail.
88 */
89 var foldAllButtons = document.getElementsByClassName('fold-all');
90 var foldButtons = document.getElementsByClassName('fold-button');
91
92 [].forEach.call(foldButtons, function (foldButton) {
93 // Retrieve description
94 var description = null;
95 var thumbnail = null;
96 var linklistItem = getParentByClass(foldButton, 'linklist-item');
97 if (linklistItem != null) {
98 description = linklistItem.querySelector('.linklist-item-description');
99 thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
100 if (description != null || thumbnail != null) {
101 foldButton.style.display = 'inline';
102 }
103 }
104
105 foldButton.addEventListener('click', function (event) {
106 event.preventDefault();
107 toggleFold(event.target, description, thumbnail);
108 });
109 });
110
111 if (foldAllButtons != null) {
112 [].forEach.call(foldAllButtons, function (foldAllButton) {
113 foldAllButton.addEventListener('click', function (event) {
114 event.preventDefault();
115 var state = foldAllButton.firstElementChild.getAttribute('class').indexOf('down') != -1 ? 'down' : 'up';
116 [].forEach.call(foldButtons, function (foldButton) {
117 if (foldButton.firstElementChild.classList.contains('fa-chevron-up') && state == 'down'
118 || foldButton.firstElementChild.classList.contains('fa-chevron-down') && state == 'up'
119 ) {
120 return;
121 }
122 // Retrieve description
123 var description = null;
124 var thumbnail = null;
125 var linklistItem = getParentByClass(foldButton, 'linklist-item');
126 if (linklistItem != null) {
127 description = linklistItem.querySelector('.linklist-item-description');
128 thumbnail = linklistItem.querySelector('.linklist-item-thumbnail');
129 if (description != null || thumbnail != null) {
130 foldButton.style.display = 'inline';
131 }
132 }
133
134 toggleFold(foldButton.firstElementChild, description, thumbnail);
135 });
136 foldAllButton.firstElementChild.classList.toggle('fa-chevron-down');
137 foldAllButton.firstElementChild.classList.toggle('fa-chevron-up');
138 });
139 });
140 }
141
142 function toggleFold(button, description, thumb)
143 {
144 // Switch fold/expand - up = fold
145 if (button.classList.contains('fa-chevron-up')) {
146 button.title = 'Expand';
147 if (description != null) {
148 description.style.display = 'none';
149 }
150 if (thumb != null) {
151 thumb.style.display = 'none';
152 }
153 }
154 else {
155 button.title = 'Fold';
156 if (description != null) {
157 description.style.display = 'block';
158 }
159 if (thumb != null) {
160 thumb.style.display = 'block';
161 }
162 }
163 button.classList.toggle('fa-chevron-down');
164 button.classList.toggle('fa-chevron-up');
165 }
166
167 /**
168 * Confirmation message before deletion.
169 */
170 var deleteLinks = document.querySelectorAll('.confirm-delete');
171 [].forEach.call(deleteLinks, function(deleteLink) {
172 deleteLink.addEventListener('click', function(event) {
173 if(! confirm('Are you sure you want to delete this link ?')) {
174 event.preventDefault();
175 }
176 });
177 });
178
179 /**
180 * Close alerts
181 */
182 var closeLinks = document.querySelectorAll('.pure-alert-close');
183 [].forEach.call(closeLinks, function(closeLink) {
184 closeLink.addEventListener('click', function(event) {
185 var alert = getParentByClass(event.target, 'pure-alert-closable');
186 alert.style.display = 'none';
187 });
188 });
189
190 /**
191 * New version dismiss.
192 * Hide the message for one week using localStorage.
193 */
194 var newVersionDismiss = document.getElementById('new-version-dismiss');
195 var newVersionMessage = document.querySelector('.new-version-message');
196 if (newVersionMessage != null
197 && localStorage.getItem('newVersionDismiss') != null
198 && parseInt(localStorage.getItem('newVersionDismiss')) + 7*24*60*60*1000 > (new Date()).getTime()
199 ) {
200 newVersionMessage.style.display = 'none';
201 }
202 if (newVersionDismiss != null) {
203 newVersionDismiss.addEventListener('click', function () {
204 localStorage.setItem('newVersionDismiss', (new Date()).getTime());
205 });
206 }
207
208 var hiddenReturnurl = document.getElementsByName('returnurl');
209 if (hiddenReturnurl != null) {
210 hiddenReturnurl.value = window.location.href;
211 }
212
213 /**
214 * Autofocus text fields
215 */
216 // ES6 syntax
217 let autofocusElements = document.querySelectorAll('.autofocus');
218 for (let autofocusElement of autofocusElements) {
219 if (autofocusElement.value == '') {
220 autofocusElement.focus();
221 break;
222 }
223 }
224
225 /**
226 * Handle sub menus/forms
227 */
228 var openers = document.getElementsByClassName('subheader-opener');
229 if (openers != null) {
230 [].forEach.call(openers, function(opener) {
231 opener.addEventListener('click', function(event) {
232 event.preventDefault();
233
234 var id = opener.getAttribute('data-open-id');
235 var sub = document.getElementById(id);
236
237 if (sub != null) {
238 [].forEach.call(document.getElementsByClassName('subheader-form'), function (element) {
239 if (element != sub) {
240 removeClass(element, 'open')
241 }
242 });
243
244 sub.classList.toggle('open');
245 }
246 });
247 });
248 }
249
250 function removeClass(element, classname) {
251 element.className = element.className.replace(new RegExp('(?:^|\\s)'+ classname + '(?:\\s|$)'), ' ');
252 }
253
254 /**
255 * Remove CSS target padding (for fixed bar)
256 */
257 if (location.hash != '') {
258 var anchor = document.getElementById(location.hash.substr(1));
259 if (anchor != null) {
260 var padsize = anchor.clientHeight;
261 this.window.scroll(0, this.window.scrollY - padsize);
262 anchor.style.paddingTop = 0;
263 }
264 }
265
266 /**
267 * Text area resizer
268 */
269 var description = document.getElementById('lf_description');
270 var observe = function (element, event, handler) {
271 element.addEventListener(event, handler, false);
272 };
273 function init () {
274 function resize () {
275 description.style.height = 'auto';
276 description.style.height = description.scrollHeight+10+'px';
277 }
278 /* 0-timeout to get the already changed text */
279 function delayedResize () {
280 window.setTimeout(resize, 0);
281 }
282 observe(description, 'change', resize);
283 observe(description, 'cut', delayedResize);
284 observe(description, 'paste', delayedResize);
285 observe(description, 'drop', delayedResize);
286 observe(description, 'keydown', delayedResize);
287
288 resize();
289 }
290
291 if (description != null) {
292 init();
293 // Submit editlink form with CTRL + Enter in the text area.
294 description.addEventListener('keydown', function (event) {
295 if (event.ctrlKey && event.keyCode === 13) {
296 document.getElementById('button-save-edit').click();
297 }
298 });
299 }
300
301 /**
302 * TimeZome select
303 * FIXME! way too hackish
304 */
305 var toRemove = document.getElementById('timezone-remove');
306 if (toRemove != null) {
307 var firstSelect = toRemove.getElementsByTagName('select')[0];
308 var secondSelect = toRemove.getElementsByTagName('select')[1];
309 toRemove.parentNode.removeChild(toRemove);
310 var toAdd = document.getElementById('timezone-add');
311 var newTimezone = '<span class="timezone-continent">Continent ' + firstSelect.outerHTML + '</span>';
312 newTimezone += ' <span class="timezone-country">Country ' + secondSelect.outerHTML + '</span>';
313 toAdd.innerHTML = newTimezone;
314 }
315
316 /**
317 * Awesomplete trigger.
318 */
319 var tags = document.getElementById('lf_tags');
320 if (tags != null) {
321 awesompleteUniqueTag('#lf_tags');
322 }
323
324 /**
325 * bLazy trigger
326 */
327 var picwall = document.getElementById('picwall_container');
328 if (picwall != null) {
329 var bLazy = new Blazy();
330 }
331
332 /**
333 * Bookmarklet alert
334 */
335 var bookmarkletLinks = document.querySelectorAll('.bookmarklet-link');
336 var bkmMessage = document.getElementById('bookmarklet-alert');
337 [].forEach.call(bookmarkletLinks, function(link) {
338 link.addEventListener('click', function(event) {
339 event.preventDefault();
340 alert(bkmMessage.value);
341 });
342 });
343
344 /**
345 * Firefox Social
346 */
347 var ffButton = document.getElementById('ff-social-button');
348 if (ffButton != null) {
349 ffButton.addEventListener('click', function(event) {
350 activateFirefoxSocial(event.target);
351 });
352 }
353
354 /**
355 * Plugin admin order
356 */
357 var orderPA = document.querySelectorAll('.order');
358 [].forEach.call(orderPA, function(link) {
359 link.addEventListener('click', function(event) {
360 event.preventDefault();
361 if (event.target.classList.contains('order-up')) {
362 return orderUp(event.target.parentNode.parentNode.getAttribute('data-order'));
363 } else if (event.target.classList.contains('order-down')) {
364 return orderDown(event.target.parentNode.parentNode.getAttribute('data-order'));
365 }
366 });
367 });
368 };
369
370 function activateFirefoxSocial(node) {
371 var loc = location.href;
372 var baseURL = loc.substring(0, loc.lastIndexOf("/"));
373
374 // Keeping the data separated (ie. not in the DOM) so that it's maintainable and diffable.
375 var data = {
376 name: "{$shaarlititle}",
377 description: "The personal, minimalist, super-fast, database free, bookmarking service by the Shaarli community.",
378 author: "Shaarli",
379 version: "1.0.0",
380
381 iconURL: baseURL + "/images/favicon.ico",
382 icon32URL: baseURL + "/images/favicon.ico",
383 icon64URL: baseURL + "/images/favicon.ico",
384
385 shareURL: baseURL + "{noparse}?post=%{url}&title=%{title}&description=%{text}&source=firefoxsocialapi{/noparse}",
386 homepageURL: baseURL
387 };
388 node.setAttribute("data-service", JSON.stringify(data));
389
390 var activate = new CustomEvent("ActivateSocialFeature");
391 node.dispatchEvent(activate);
392 }