Don't use the domain part in login anymore.
[platal.git] / htdocs / javascript / xorg.js
1 /***************************************************************************
2 * Copyright (C) 2003-2011 Polytechnique.org *
3 * http://opensource.polytechnique.org/ *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software *
17 * Foundation, Inc., *
18 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
19 ***************************************************************************/
20
21 // {{{ function getNow()
22 var days = ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'];
23 var months = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
24 'août', 'septembre', 'octobre', 'novembre', 'décembre'];
25
26 function getNow() {
27 var dt = new Date();
28 var dy = dt.getDay();
29 var mh = dt.getMonth();
30 var wd = dt.getDate();
31 var yr = dt.getYear();
32 var hr = dt.getHours();
33 var mi = dt.getMinutes();
34 var se = dt.getSeconds();
35
36 if (yr<1000) {
37 yr += 1900;
38 }
39 if (mi < 10) {
40 mi = '0' + mi;
41 }
42 if (se < 10) {
43 se = '0' + se;
44 }
45
46 $(".date-heure").html(days[dy] + ' ' + wd + ' ' + months[mh] + ' ' + yr + '<br />'
47 + hr + ':' + mi + ':' + se);
48 }
49
50 // }}}
51 // {{{ Search Engine
52
53 function canAddSearchEngine()
54 {
55 if (((typeof window.sidebar === "object") && $.isFunction(window.sidebar.addSearchEngine))
56 || ((typeof window.external === "object") && $.isFunction(window.external.AddSearchProvider))) {
57 return true;
58 }
59 return false;
60 }
61
62 function addSearchEngine()
63 {
64 var searchURI = "http://www.polytechnique.org/xorg.opensearch.xml";
65 if ((typeof window.sidebar === "object") && $.isFunction(window.sidebar.addSearchEngine)) {
66 window.sidebar.addSearchEngine(
67 searchURI,
68 "http://www.polytechnique.org/images/xorg.png",
69 "Annuaire Polytechnique.org",
70 "Academic");
71 } else {
72 try {
73 window.external.AddSearchProvider(searchURI);
74 } catch(e) {
75 alert("Impossible d'installer la barre de recherche");
76 }
77 }
78 }
79
80 // }}}
81
82 /***************************************************************************
83 * POPUP THINGS
84 */
85
86 // {{{ function goodiesPopup()
87
88 (function($) {
89 var goodies = {
90 ical: {
91 default_title: 'Calendrier iCal',
92 sites: [
93 {url_prefix: '',
94 img: 'images/icons/calendar_view_day.gif',
95 title: 'Calendrier iCal'},
96 {url_prefix: 'http://www.google.com/calendar/render?cid=',
97 img: 'images/goodies/add-google-calendar.gif',
98 title: 'Ajouter à Google Calendar'},
99 {url_prefix: 'https://www.google.com/calendar/hosted/polytechnique.org/render?cid=',
100 img: 'images/goodies/add-google-calendar.gif',
101 title: 'Ajouter à Google Apps / Calendar'}
102 ]
103 },
104
105 rss: {
106 default_title: 'Fils RSS',
107 sites: [
108 {url_prefix: '',
109 img: 'images/icons/feed.gif',
110 title: 'Fil rss'},
111 {url_prefix: 'http://fusion.google.com/add?feedurl=',
112 img: 'images/goodies/add-google.gif',
113 alt: 'Add to Google',
114 title: 'Ajouter à iGoogle/Google Reader'},
115 {url_prefix: 'http://www.netvibes.com/subscribe.php?url=',
116 img: 'images/goodies/add-netvibes.gif',
117 title: 'Ajouter à Netvibes'},
118 {url_prefix: 'http://add.my.yahoo.com/content?.intl=fr&url=',
119 img: 'images/goodies/add-yahoo.gif',
120 alt: 'Add to My Yahoo!',
121 title: 'Ajouter à My Yahoo!'}
122 ]
123 }
124 };
125
126 $.fn.extend({
127 goodiesPopup: function goodiesPopup(type) {
128 var text = '<div style="text-align: center; line-height: 2.2">';
129 var site;
130 var entry;
131 var s_alt;
132 var s_img;
133 var s_title;
134 var s_url;
135 var href = this.attr('href');
136
137 for (site in goodies[type].sites) {
138 entry = goodies[type].sites[site];
139 s_alt = entry.alt || "";
140 s_img = entry.img;
141 s_title = entry.title || "";
142 s_url = entry.url_prefix.length > 0 ? entry.url_prefix + escape(href) : href;
143
144 text += '<a href="' + s_url + '"><img src="' + s_img + '" title="' + s_title + '" alt="' + s_alt + '"></a><br />';
145 }
146 text += '<a href="https://www.polytechnique.org/Xorg/Goodies">Plus de bonus</a> ...</div>';
147
148 return this.overlib({
149 text: text,
150 caption: this.attr('title') || goodies.default_title,
151 close_text: 'Fermer',
152 delay: 800,
153 sticky: true,
154 width: 150
155 });
156 }
157 });
158 }(jQuery));
159
160 // }}}
161 // {{{ function auto_links()
162
163 function auto_links() {
164 var url = document.URL;
165 var fqdn = url.replace(/^https?:\/\/([^\/]*)\/.*$/,'$1');
166 var light = url.indexOf('display=light') > url.indexOf('?');
167 var resource_page = url.contains('rss') || url.contains('ical');
168
169 $("a,link").each(function(i) {
170 var node = $(this);
171 var href = this.href;
172 var matches;
173 var rss;
174 var ical;
175
176 if(!href || node.hasClass('xdx')
177 || href.startsWith('mailto:') || href.startsWith('javascript:')) {
178 return;
179 }
180 if ((!href.contains(fqdn) && !this.className.contains('popup')) || node.hasClass('popup')) {
181 node.click(function () {
182 window.open(this.href);
183 return false;
184 });
185 }
186 if (href.contains(fqdn) && light) {
187 href = href.replace(/([^\#\?]*)\??([^\#]*)(\#.*)?/, "$1?display=light&$2$3");
188 this.href = href;
189 }
190 rss = href.contains('rss');
191 ical = href.contains('ical');
192 if (rss || ical) {
193 if (!href.startsWith('http')) {
194 href = 'http://' + fqdn + '/' + href;
195 }
196 }
197 if (this.nodeName.toLowerCase() === 'a' && !resource_page) {
198 if (rss && !href.contains('prefs/rss') && (href.contains('xml') || href.contains('hash'))) {
199 node.goodiesPopup('rss');
200 } else if (ical) {
201 node.goodiesPopup('ical');
202 }
203 }
204 matches = /^popup_([0-9]*)x([0-9]*)$/.exec(this.className);
205 if (matches) {
206 node.popWin(matches[1], matches[2]);
207 }
208 });
209 $('.popup2').popWin(840, 600);
210 $('.popup3').popWin(640, 800);
211 }
212
213
214 // }}}
215
216 /***************************************************************************
217 * Password check
218 */
219
220 // {{{ function checkPassword
221
222 /* {{{ SHA1 Implementation */
223
224 /*
225 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
226 * in FIPS PUB 180-1
227 * Version 2.1a Copyright Paul Johnston 2000 - 2002.
228 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
229 * Distributed under the BSD License
230 * See http://pajhome.org.uk/crypt/md5 for details.
231 */
232
233 /*
234 * Configurable variables. You may need to tweak these to be compatible with
235 * the server-side, but the defaults work in most cases.
236 */
237 var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
238 var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
239 var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
240
241 /*
242 * These are the functions you'll usually want to call
243 * They take string arguments and return either hex or base-64 encoded strings
244 */
245 function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
246 function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
247 function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
248 function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
249 function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
250 function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
251
252 /*
253 * Perform a simple self-test to see if the VM is working
254 */
255 function sha1_vm_test()
256 {
257 return hex_sha1("abc") === "a9993e364706816aba3e25717850c26c9cd0d89d";
258 }
259
260 /*
261 * Calculate the SHA-1 of an array of big-endian words, and a bit length
262 */
263 function core_sha1(x, len)
264 {
265 var w, a, b, c, d, e;
266 var olda, oldb, oldc, oldd, olde;
267 var i, j, t;
268
269 /* append padding */
270 x[len >> 5] |= 0x80 << (24 - len % 32);
271 x[((len + 64 >> 9) << 4) + 15] = len;
272
273 w = Array(80);
274 a = 1732584193;
275 b = -271733879;
276 c = -1732584194;
277 d = 271733878;
278 e = -1009589776;
279
280 for(i = 0; i < x.length; i += 16)
281 {
282 olda = a;
283 oldb = b;
284 oldc = c;
285 oldd = d;
286 olde = e;
287
288 for(j = 0; j < 80; j++)
289 {
290 if(j < 16) w[j] = x[i + j];
291 else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
292 t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
293 safe_add(safe_add(e, w[j]), sha1_kt(j)));
294 e = d;
295 d = c;
296 c = rol(b, 30);
297 b = a;
298 a = t;
299 }
300
301 a = safe_add(a, olda);
302 b = safe_add(b, oldb);
303 c = safe_add(c, oldc);
304 d = safe_add(d, oldd);
305 e = safe_add(e, olde);
306 }
307 return Array(a, b, c, d, e);
308
309 }
310
311 /*
312 * Perform the appropriate triplet combination function for the current
313 * iteration
314 */
315 function sha1_ft(t, b, c, d)
316 {
317 if(t < 20) return (b & c) | ((~b) & d);
318 if(t < 40) return b ^ c ^ d;
319 if(t < 60) return (b & c) | (b & d) | (c & d);
320 return b ^ c ^ d;
321 }
322
323 /*
324 * Determine the appropriate additive constant for the current iteration
325 */
326 function sha1_kt(t)
327 {
328 return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
329 (t < 60) ? -1894007588 : -899497514;
330 }
331
332 /*
333 * Calculate the HMAC-SHA1 of a key and some data
334 */
335 function core_hmac_sha1(key, data)
336 {
337 var bkey = str2binb(key);
338 var i, ipad, opad;
339 var hash;
340
341 if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
342
343 ipad = Array(16);
344 opad = Array(16);
345 for(i = 0; i < 16; i++)
346 {
347 ipad[i] = bkey[i] ^ 0x36363636;
348 opad[i] = bkey[i] ^ 0x5C5C5C5C;
349 }
350
351 hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
352 return core_sha1(opad.concat(hash), 512 + 160);
353 }
354
355 /*
356 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
357 * to work around bugs in some JS interpreters.
358 */
359 function safe_add(x, y)
360 {
361 var lsw = (x & 0xFFFF) + (y & 0xFFFF);
362 var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
363 return (msw << 16) | (lsw & 0xFFFF);
364 }
365
366 /*
367 * Bitwise rotate a 32-bit number to the left.
368 */
369 function rol(num, cnt)
370 {
371 return (num << cnt) | (num >>> (32 - cnt));
372 }
373
374 /*
375 * Convert an 8-bit or 16-bit string to an array of big-endian words
376 * In 8-bit function, characters >255 have their hi-byte silently ignored.
377 */
378 function str2binb(str)
379 {
380 var bin = Array();
381 var mask = (1 << chrsz) - 1;
382 var i;
383 for(i = 0; i < str.length * chrsz; i += chrsz)
384 bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
385 return bin;
386 }
387
388 /*
389 * Convert an array of big-endian words to a string
390 */
391 function binb2str(bin)
392 {
393 var str = "";
394 var mask = (1 << chrsz) - 1;
395 var i;
396 for(i = 0; i < bin.length * 32; i += chrsz)
397 str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
398 return str;
399 }
400
401 /*
402 * Convert an array of big-endian words to a hex string.
403 */
404 function binb2hex(binarray)
405 {
406 var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
407 var str = "";
408 var i;
409 for(i = 0; i < binarray.length * 4; i++)
410 {
411 str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
412 hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
413 }
414 return str;
415 }
416
417 /*
418 * Convert an array of big-endian words to a base-64 string
419 */
420 function binb2b64(binarray)
421 {
422 var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
423 var str = "";
424 var i, j, triplet;
425 for(i = 0; i < binarray.length * 4; i += 3)
426 {
427 triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
428 | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
429 | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
430 for(j = 0; j < 4; j++)
431 {
432 if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
433 else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
434 }
435 }
436 return str;
437 }
438
439 /* }}} */
440
441 function hash_encrypt(a) {
442 return hex_sha1(a);
443 }
444
445 var hexa_h = "0123456789abcdef";
446
447 function dechex(a) {
448 return hexa_h.charAt(a);
449 }
450
451 function hexdec(a) {
452 return hexa_h.indexOf(a);
453 }
454
455 function hash_xor(a, b) {
456 var c,i,j,k,d;
457 c = "";
458 i = a.length;
459 j = b.length;
460 if (i < j) {
461 d = a; a = b; b = d;
462 k = i; i = j; j = k;
463 }
464 for (k = 0; k < j; k++) {
465 c += dechex(hexdec(a.charAt(k)) ^ hexdec(b.charAt(k)));
466 }
467 for (; k < i; k++) {
468 c += a.charAt(k);
469 }
470 return c;
471 }
472
473 function getType(c) {
474 if (c >= 'a' && c <= 'z') {
475 return 1;
476 } else if (c >= 'A' && c <= 'Z') {
477 return 2;
478 } else if (c >= '0' && c <= '9') {
479 return 3;
480 } else {
481 return 4;
482 }
483 }
484
485 function differentTypes(password) {
486 var prev = 0;
487 var type;
488
489 for (i = 0 ; i < password.length ; ++i) {
490 type = getType(password.charAt(i));
491 if (prev !== 0 && prev !== type) {
492 return true;
493 }
494 prev = type;
495 }
496 return false;
497 }
498
499 function passwordStrength(password) {
500 var prop = 0;
501 var prev = 0;
502 var firstType = true;
503 var types = Array(0, 0, 0, 0, 0);
504 var type;
505
506 for (i = 0 ; i < password.length ; ++i) {
507 type = getType(password.charAt(i));
508 if (prev !== 0 && prev !== type) {
509 prop += 5;
510 firstType = false;
511 }
512 prop += i;
513 if (types[type] === 0 && !firstType) {
514 prop += 15;
515 }
516 types[type]++;
517 prev = type;
518 }
519 if (password.length < 6) {
520 prop *= 0.75;
521 }
522 if (firstType) {
523 prop *= 0.75;
524 }
525 if (prop > 100) {
526 prop = 100;
527 } else if (prop < 0) {
528 prop = 0;
529 }
530
531 return prop;
532 }
533
534 function checkPassword(box, okLabel) {
535 var password = box.value;
536 var prop = passwordStrength(password);
537 var submitButton;
538
539 if (prop >= 60) {
540 color = "#4f4";
541 bgcolor = "#050";
542 ok = true;
543 } else if (prop >= 35) {
544 color = "#ff4";
545 bgcolor = "#750";
546 ok = true;
547 } else {
548 color = "#f20";
549 bgcolor = "#700";
550 ok = false;
551 }
552 $("#passwords_measure")
553 .stop()
554 .animate({ width: prop + "%",
555 backgroundColor: color
556 }, 750)
557 .parent().stop()
558 .animate({ backgroundColor: bgcolor }, 750);
559 submitButton = $(":submit[name='" + passwordprompt_submit + "']");
560 if (ok && password.length >= 6 && differentTypes(password)) {
561 submitButton.attr("value", okLabel);
562 submitButton.removeAttr("disabled");
563 } else {
564 submitButton.attr("value", "Mot de passe trop faible");
565 submitButton.attr("disabled", "disabled");
566 }
567 }
568
569 function hashResponse(password1, password2, hasConfirmation, doAuth) {
570 var pw1 = $('[name=' + password1 + ']').val();
571 var pw2;
572
573 if (hasConfirmation) {
574 pw2 = $('[name=' + password2 + ']').val();
575 if (pw1 !== pw2) {
576 alert("\nErreur : les deux champs ne sont pas identiques !");
577 return false;
578 }
579 $('[name=' + password2 + ']').val('');
580 } else if (pw1 === '********') {
581 return true;
582 }
583
584 if (pw1.length < 6) {
585 alert("\nErreur : le nouveau mot de passe doit faire au moins 6 caractères !");
586 return false;
587 }
588 if (!differentTypes(pw1)) {
589 alert ("\nErreur : le nouveau mot de passe doit comporter au moins deux types de caractères parmi les suivants : lettres minuscules, lettres majuscules, chiffres, caractères spéciaux.");
590 return false;
591 }
592
593 alert("Le mot de passe va être chiffré avant de nous parvenir par Internet ! Ainsi il ne circulera pas en clair.");
594 $('[name=' + password1 + ']').val('');
595 $('[name=pwhash]').val(hash_encrypt(pw1));
596
597 if (doAuth) {
598 $('[name=password]').val(pw1);
599 doChallengeResponse();
600 }
601
602 return true;
603 }
604
605 function correctUserName() {
606 var u = document.forms.login.username;
607 var mots;
608
609 // login with no space
610 if (!u.value.contains(' ')) {
611 return true;
612 }
613 mots = u.value.split(' ');
614 // jean paul.du pont -> jean-paul.du-pont
615 if (u.value.contains('.')) {
616 u.value = mots.join('-');
617 return true;
618 }
619 // jean dupont -> jean.dupont
620 if (mots.length === 2) {
621 u.value = mots[0] + "." + mots[1];
622 return true;
623 }
624 // jean dupont 2001 -> jean.dupont.2001
625 if (mots.length === 3 && mots[2] > 1920 && mots[2] < 3000) {
626 u.value = mots.join('.');
627 return true;
628 }
629 // jean de la vallee -> jean.de-la-vallee
630 if (mots[1].toUpperCase() === 'DE') {
631 u.value = mots[0] + "." + mots.join('-').substr(mots[0].length+1);
632 return true;
633 }
634 // jean paul dupont -> jean-paul.dupont
635 if (mots.length === 3 && mots[0].toUpperCase() === 'JEAN') {
636 u.value = mots[0] + "-" + mots[1] + "." + mots[2];
637 return true;
638 }
639
640 alert('Ton email ne doit pas contenir de blanc.\nLe format standard est\n\nprenom.nom.promotion\n\nSi ton nom ou ton prenom est composé,\nsépare les mots par des -');
641
642 return false;
643 }
644
645 function doChallengeResponse() {
646 var new_pass, old_pass, str;
647
648 if (!correctUserName()) {
649 return false;
650 }
651
652 new_pass = hash_encrypt(document.forms.login.password.value);
653 old_pass = hash_encrypt(document.forms.login.password.value.substr(0, 10));
654
655 str = document.forms.login.username.value + ":" +
656 new_pass + ":" +
657 document.forms.loginsub.challenge.value;
658
659 document.forms.loginsub.response.value = hash_encrypt(str);
660 if (new_pass !== old_pass) {
661 document.forms.loginsub.xorpass.value = hash_xor(new_pass, old_pass);
662 }
663 document.forms.loginsub.username.value = document.forms.login.username.value;
664 document.forms.loginsub.remember.value = document.forms.login.remember.checked;
665 document.forms.login.password.value = "";
666 document.forms.loginsub.submit();
667 }
668
669 function doChallengeResponseLogged() {
670 var str = document.forms.loginsub.username.value + ":" +
671 hash_encrypt(document.forms.login.password.value) + ":" +
672 document.forms.loginsub.challenge.value;
673
674 document.forms.loginsub.response.value = hash_encrypt(str);
675 document.forms.loginsub.remember.value = document.forms.login.remember.checked;
676 document.forms.login.password.value = "";
677 document.forms.loginsub.submit();
678 }
679
680 // }}}
681 // {{{ send test email
682
683 function sendTestEmail(token, hruid)
684 {
685 var url = 'emails/test';
686 var msg = "Un email a été envoyé avec succès";
687 if (hruid) {
688 url += '/' + hruid;
689 msg += " sur l'adresse de " + hruid + ".";
690 } else {
691 msg += " sur ton addresse.";
692 }
693 $('#mail_sent').successMessage(url + '?token=' + token, msg);
694 return false;
695 }
696
697 // }}}
698 // {{{ jQuery object extension
699
700 (function($) {
701 /* Add new functions to jQuery namesapce */
702 $.extend({
703 /* The goal of the following functions is to provide an AJAX API that
704 * take a different callback in case of HTTP success code (2XX) and in
705 * other cases.
706 */
707
708 xajax: function(source, method, data, onSuccess, onError, type) {
709 /* Shift argument */
710 if ($.isFunction(data)) {
711 type = type || onError;
712 onError = onSuccess;
713 onSuccess = data;
714 data = null;
715 }
716 if (onError != null && !$.isFunction(onError)) {
717 type = type || onError;
718 onError = null;
719 }
720
721 function ajaxHandler(data, textStatus, xhr) {
722 if (textStatus == 'success') {
723 if (onSuccess) {
724 onSuccess(data, textStatus, xhr);
725 }
726 } else if (textStatus == 'error') {
727 if (onError) {
728 onError(data, textStatus, xhr);
729 } else {
730 alert("Une error s'est produite lors du traitement de la requête.\n"
731 + "Ta session a peut-être expiré");
732 }
733 }
734 }
735 return $.ajax({
736 url: source,
737 type: method,
738 success: ajaxHandler,
739 data : data,
740 dataType: type
741 });
742 },
743
744 xget: function(source, data, onSuccess, onError, type) {
745 return $.xajax(source, 'GET', data, onSuccess, onError, type);
746 },
747
748 xgetJSON: function(source, data, onSuccess, onError) {
749 return $.xget(source, data, onSuccess, onError, 'json');
750 },
751
752 xgetScript: function(source, onSuccess, onError) {
753 return $.xget(source, null, onSuccess, onError, 'script');
754 },
755
756 xgetText: function(source, data, onSuccess, onError) {
757 return $.xget(source, data, onSuccess, onError, 'text');
758 },
759
760 xpost: function(source, data, onSuccess, onError, type) {
761 return $.xajax(source, 'POST', data, onSuccess, onError, type);
762 }
763 });
764
765 /* Add new functions to jQuery objects */
766 $.fn.extend({
767 tmpMessage: function(message, success) {
768 if (success) {
769 this.html("<img src='images/icons/wand.gif' alt='' /> " + message)
770 .css('color', 'green');
771 } else {
772 this.html("<img src='images/icons/error.gif' alt='' /> " + message)
773 .css('color', 'red');
774 }
775 return this.css('fontWeight', 'bold')
776 .show()
777 .delay(1000)
778 .fadeOut(500);
779 },
780
781 updateHtml: function(source, callback) {
782 var elements = this;
783 function handler(data) {
784 elements.html(data);
785 if (callback) {
786 callback(data);
787 }
788 }
789 $.xget(source, handler, 'text');
790 return this;
791 },
792
793 successMessage: function(source, message) {
794 var elements = this;
795 $.xget(source, function() {
796 elements.tmpMessage(message, true);
797 });
798 return this;
799 },
800
801 wiki: function(text, withTitle) {
802 if (text == '') {
803 return this.html('');
804 }
805 var url = 'wiki_preview';
806 if (!withTitle) {
807 url += '/notitile';
808 }
809 var $this = this;
810 $.post(url, { text: text },
811 function (data) {
812 $this.html(data);
813 }, 'text');
814 return this;
815 },
816
817 popWin: function(w, h) {
818 return this.click(function() {
819 window.open(this.href, '_blank',
820 'toolbar=0,location=0,directories=0,status=0,'
821 +'menubar=0,scrollbars=1,resizable=1,'
822 +'width='+w+',height='+h);
823 return false;
824 });
825 }
826 });
827 })(jQuery);
828
829 // }}}
830 // {{{ preview wiki
831
832 function previewWiki(idFrom, idTo, withTitle, idShow)
833 {
834 $('#' + idTo).wiki($('#' + idFrom).val(), withTitle);
835 if (idShow != null) {
836 $('#' + idShow).show();
837 }
838 }
839
840 // }}}
841 // {{{ updatepromofields
842
843 function updatepromofields(egal1, egal2, promo2) {
844 var comparator = egal1.val();
845
846 if (comparator == '=') {
847 egal2.attr('disabled', 'disabled');
848 promo2.attr('disabled', 'disabled');
849 } else if (comparator == '<=' || comparator == '>=') {
850 egal2.removeAttr('disabled');
851 promo2.removeAttr('disabled');
852 if (comparator == '<=') {
853 egal2.val('>=');
854 } else {
855 egal2.val('<=');
856 }
857 }
858 }
859
860 // }}}
861
862 /***************************************************************************
863 * Quick search
864 */
865
866 /* quick search {{{ */
867 (function($) {
868 function findPos(obj) {
869 var curleft = obj.offsetLeft || 0;
870 var curtop = obj.offsetTop || 0;
871 while (obj = obj.offsetParent) {
872 curleft += obj.offsetLeft
873 curtop += obj.offsetTop
874 }
875 return {x:curleft,y:curtop};
876 }
877
878 $.template('quickMinifiche',
879 '<div class="contact grayed" style="clear: both">' +
880 '<div class="identity">' +
881 '<div class="photo"><img src="photo/${hrpid}" alt="${directory_name}" /></div>' +
882 '<div class="nom">' +
883 '{{if is_female}}&bull;{{/if}}<a>${directory_name}</a>' +
884 '</div>' +
885 '<div class="edu">${promo}</div>' +
886 '</div>' +
887 '<div class="noprint bits"></div>' +
888 '<div class="long"></div>' +
889 '</div>');
890
891
892 function buildPopup(input, destination, linkBindFunction)
893 {
894 var $popup = destination;
895 var selected = null;
896 var hovered = 0;
897
898 function updateSelection()
899 {
900 var sel = $popup.children('.contact').addClass('grayed');
901 if (selected !== null) {
902 while (selected < 0) {
903 selected += sel.length;
904 }
905 if (selected >= sel.length) {
906 selected -= sel.length;
907 }
908 sel.eq(selected).removeClass('grayed');
909 }
910 }
911
912 function formatProfile(i, profile) {
913 var data = $.tmpl('quickMinifiche', profile)
914 .css('cursor', 'pointer')
915 .hover(function() {
916 selected = i;
917 updateSelection();
918 hovered++;
919 }, function() {
920 if (selected === i) {
921 selected = null;
922 updateSelection();
923 }
924 hovered--;
925 }).mouseup(function() {
926 var sel = $(this).find('a');
927 if (!sel.attr('hovered')) {
928 sel.click();
929 }
930 });
931 data.find('a').hover(function() { $(this).attr('hovered', true) },
932 function() { $(this).attr('hovered', false) });
933 return data;
934 }
935
936 if (!$popup) {
937 $popup = $('<div>').hide()
938 .addClass('contact-list')
939 .css({
940 position: 'absolute',
941 width: '300px',
942 top: input.css('bottom'),
943 clear: 'both',
944 'text-align': 'left'
945 });
946 input.after($popup);
947 }
948
949 return {
950 hide: function(ignoreIfHover) {
951 if (ignoreIfHover && hovered !== 0) {
952 return true;
953 }
954 selected = null;
955 updateSelection();
956 $popup.hide();
957 return true;
958 },
959
960 show: function() {
961 var pos = findPos(input.get(0));
962 $popup.css('left', pos.x - 300 + input.width()).show();
963 return true;
964 },
965
966 selected: function() {
967 return selected !== null;
968 },
969
970 unselect: function() {
971 selected = null;
972 updateSelection();
973 },
974
975 selectNext: function() {
976 if (selected === null) {
977 selected = 0;
978 } else {
979 selected++;
980 }
981 updateSelection();
982 return true;
983 },
984
985 selectPrev: function() {
986 if (selected === null) {
987 selected = -1;
988 } else {
989 selected--;
990 }
991 updateSelection();
992 return true;
993 },
994
995 activeCurrent: function() {
996 var sel = $popup.children('.contact');
997 if (selected !== null) {
998 sel.eq(selected).find('a').click();
999 return false;
1000 }
1001 return true;
1002 },
1003
1004 updateContent: function(profiles, extra) {
1005 var profile;
1006 var $this;
1007 $popup.empty();
1008 for (var i = 0, len = profiles.length; i < len; i++) {
1009 (function(elt) {
1010 var profile = formatProfile(i, elt);
1011 profile.find('a').each(function() {
1012 linkBindFunction.call(this, elt, $this, extra);
1013 });
1014 profile.appendTo($popup);
1015 }(profiles[i]));
1016 }
1017 if (len === 1) {
1018 selected = 0;
1019 } else {
1020 selected = null;
1021 }
1022 updateSelection();
1023 if (len > 0) {
1024 this.show();
1025 } else {
1026 this.hide();
1027 }
1028 return true;
1029 }
1030 };
1031 }
1032
1033 $.fn.extend({
1034 quickSearch: function(options) {
1035 return this.each(function() {
1036 var $this = $(this);
1037 var $input = this;
1038 var $popup;
1039 var previous = null;
1040 var pending = false;
1041 var disabled = false;
1042 var updatePopup;
1043
1044 options = options || { };
1045 options = $.extend({
1046 destination: null,
1047 minChars: 3,
1048 shortChars: 5,
1049 shortTimeout: 300,
1050 longTimeout: 100,
1051 queryParams: {
1052 offset: 0,
1053 count: 10,
1054 allow_special: true
1055 },
1056 loadingClassLeft: 'ac_loading',
1057 loadingClassRight: 'ac_loading_left',
1058 selectAction: function(profile, popup, extra) {
1059 var type = extra.link_type || 'profile';
1060 switch (type) {
1061 case 'profile':
1062 $(this).attr('href', 'profile/' + profile.hrpid)
1063 .popWin(840, 600)
1064 .click(function() { $popup.hide(); return false; });
1065 break;
1066 case 'admin':
1067 $(this).attr('href', 'admin/user/' + profile.hrpid)
1068 .click(function() { window.open($(this).attr('href')); return false });
1069 break;
1070 }
1071 }
1072 }, options);
1073 options.loadingClass = $this.css('text-align') === 'right' ? options.loadingClassRight
1074 : options.loadingClassLeft;
1075 $this.attr('autocomplete', 'off');
1076 $popup = buildPopup($this, options.destination, options.selectAction);
1077
1078 function markPending() {
1079 pending = true;
1080 }
1081
1082 function performUpdate(quick)
1083 {
1084 if (updatePopup === markPending) {
1085 return true;
1086 }
1087 updatePopup = markPending;
1088 $this.addClass(options.loadingClass);
1089 $.xapi('search', $.extend({ 'quick': quick }, options.queryParams), function(data) {
1090 if (data.profile_count > options.queryParams.count || data.profile_count < 0) {
1091 return $popup.hide();
1092 }
1093 $popup.updateContent(data.profiles, data);
1094 previous = quick;
1095 }, function(data, text) {
1096 if (text !== 'abort') {
1097 disabled = true;
1098 }
1099 }).complete(function() {
1100 $this.removeClass(options.loadingClass);
1101 updatePopup = doUpdatePopup;
1102 if (pending) {
1103 updatePopup();
1104 }
1105 });
1106 return true;
1107 }
1108
1109 function doUpdatePopup(dontDelay)
1110 {
1111 var quick = $this.val();
1112 if ($.isFunction(quick.trim)) {
1113 quick = quick.trim();
1114 }
1115 pending = false;
1116 if (disabled || quick.length < options.minChars) {
1117 previous = quick;
1118 return $popup.hide();
1119 } else if (!dontDelay) {
1120 var timeout = quick.length < options.shortChars ? options.shortTimeout : options.longTimeout;
1121 setTimeout(function() {
1122 updatePopup(true);
1123 }, timeout);
1124 return true;
1125 } else if (previous === quick) {
1126 return $popup.show();
1127 }
1128 return performUpdate(quick);
1129 }
1130
1131 updatePopup = doUpdatePopup;
1132
1133 return $this.keyup(function(e) {
1134 if (e.keyCode !== 27 /* escape */ && e.keyCode !== 13 /* enter */
1135 && e.keyCode !== 9 /* tab */ && e.keyCode !== 38 /* up */
1136 && e.keyCode !== 40 /* down */) {
1137 return updatePopup();
1138 }
1139 return true;
1140 })
1141 .keydown(function(e) {
1142 switch (e.keyCode) {
1143 case 9: /* Tab */
1144 case 40: /* Down */
1145 $popup.selectNext();
1146 return false;
1147
1148 case 38:
1149 $popup.selectPrev();
1150 return false;
1151
1152 case 13: /* Return */
1153 return $popup.activeCurrent();
1154
1155 case 27: /* Escape */
1156 if ($popup.selected()) {
1157 $popup.unselect();
1158 } else {
1159 $popup.hide();
1160 }
1161 return true;
1162 }
1163 return true;
1164 })
1165 .blur(function() {
1166 return $popup.hide(true);
1167 })
1168 .focus(updatePopup);});
1169 }
1170 });
1171 }(jQuery));
1172
1173 /***************************************************************************
1174 * Overlib made simple
1175 */
1176
1177 (function($) {
1178 $.fn.extend({
1179 overlib: function(text, width, height) {
1180 var args = [ ];
1181 var key;
1182
1183 if (typeof text === 'string') {
1184 args.push(text);
1185 if (width) {
1186 args.push(WIDTH, width);
1187 }
1188 if (height) {
1189 args.push(HEIGHT, height);
1190 }
1191 } else {
1192 for (key in text) {
1193 switch (key) {
1194 case 'text':
1195 args.unshift(text[key]);
1196 break;
1197 case 'caption':
1198 args.push(CAPTION, text[key]);
1199 break;
1200 case 'close_text':
1201 args.push(CLOSETEXT, text[key]);
1202 break;
1203 case 'delay':
1204 args.push(DELAY, text[key]);
1205 break;
1206 case 'sticky':
1207 if (text[key]) {
1208 args.push(STICKY);
1209 }
1210 break;
1211 case 'width':
1212 args.push(WIDTH, text[key]);
1213 break;
1214 case 'height':
1215 args.push(HEIGHT, text[key]);
1216 break;
1217 }
1218 }
1219 }
1220 return this
1221 .mouseover(function () {
1222 return overlib.apply(null, args);
1223 })
1224 .mouseout(nd);
1225 }
1226 });
1227 }(jQuery));
1228 /* }}} */
1229
1230 /***************************************************************************
1231 * The real OnLoad
1232 */
1233
1234 $(function() {
1235 auto_links();
1236 getNow();
1237 setInterval(getNow, 1000);
1238 $("#quick")
1239 .focus(function() {
1240 if ($(this).val() === 'Recherche dans l\'annuaire') {
1241 $(this).val('');
1242 }
1243 $("#quick_button").show();
1244 })
1245 .blur(function() {
1246 $("#quick_button").hide();
1247 })
1248 .quickSearch();
1249 $("#quick_button").click(function() {
1250 if ($("#quick").val() === 'Recherche dans l\'annuaire'
1251 || $("#quick").val() === '') {
1252 return false;
1253 }
1254 return true;
1255 });
1256 });
1257
1258 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: