7cb04c8c7c9dc9220c90095299a85c86c8b656e8
[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.loginsub.domain.value = document.forms.login.domain.value;
666 document.forms.login.password.value = "";
667 document.forms.loginsub.submit();
668 }
669
670 function doChallengeResponseLogged() {
671 var str = document.forms.loginsub.username.value + ":" +
672 hash_encrypt(document.forms.login.password.value) + ":" +
673 document.forms.loginsub.challenge.value;
674
675 document.forms.loginsub.response.value = hash_encrypt(str);
676 document.forms.loginsub.remember.value = document.forms.login.remember.checked;
677 document.forms.login.password.value = "";
678 document.forms.loginsub.submit();
679 }
680
681 // }}}
682 // {{{ send test email
683
684 function sendTestEmail(token, hruid)
685 {
686 var url = 'emails/test';
687 var msg = "Un email a été envoyé avec succès";
688 if (hruid) {
689 url += '/' + hruid;
690 msg += " sur l'adresse de " + hruid + ".";
691 } else {
692 msg += " sur ton addresse.";
693 }
694 $('#mail_sent').successMessage(url + '?token=' + token, msg);
695 return false;
696 }
697
698 // }}}
699 // {{{ jQuery object extension
700
701 (function($) {
702 /* Add new functions to jQuery namesapce */
703 $.extend({
704 /* The goal of the following functions is to provide an AJAX API that
705 * take a different callback in case of HTTP success code (2XX) and in
706 * other cases.
707 */
708
709 xajax: function(source, method, data, onSuccess, onError, type) {
710 /* Shift argument */
711 if ($.isFunction(data)) {
712 type = type || onError;
713 onError = onSuccess;
714 onSuccess = data;
715 data = null;
716 }
717 if (onError != null && !$.isFunction(onError)) {
718 type = type || onError;
719 onError = null;
720 }
721
722 function ajaxHandler(data, textStatus, xhr) {
723 if (textStatus == 'success') {
724 if (onSuccess) {
725 onSuccess(data, textStatus, xhr);
726 }
727 } else if (textStatus == 'error') {
728 if (onError) {
729 onError(data, textStatus, xhr);
730 } else {
731 alert("Une error s'est produite lors du traitement de la requête.\n"
732 + "Ta session a peut-être expiré");
733 }
734 }
735 }
736 return $.ajax({
737 url: source,
738 type: method,
739 success: ajaxHandler,
740 data : data,
741 dataType: type
742 });
743 },
744
745 xget: function(source, data, onSuccess, onError, type) {
746 return $.xajax(source, 'GET', data, onSuccess, onError, type);
747 },
748
749 xgetJSON: function(source, data, onSuccess, onError) {
750 return $.xget(source, data, onSuccess, onError, 'json');
751 },
752
753 xgetScript: function(source, onSuccess, onError) {
754 return $.xget(source, null, onSuccess, onError, 'script');
755 },
756
757 xgetText: function(source, data, onSuccess, onError) {
758 return $.xget(source, data, onSuccess, onError, 'text');
759 },
760
761 xpost: function(source, data, onSuccess, onError, type) {
762 return $.xajax(source, 'POST', data, onSuccess, onError, type);
763 }
764 });
765
766 /* Add new functions to jQuery objects */
767 $.fn.extend({
768 tmpMessage: function(message, success) {
769 if (success) {
770 this.html("<img src='images/icons/wand.gif' alt='' /> " + message)
771 .css('color', 'green');
772 } else {
773 this.html("<img src='images/icons/error.gif' alt='' /> " + message)
774 .css('color', 'red');
775 }
776 return this.css('fontWeight', 'bold')
777 .show()
778 .delay(1000)
779 .fadeOut(500);
780 },
781
782 updateHtml: function(source, callback) {
783 var elements = this;
784 function handler(data) {
785 elements.html(data);
786 if (callback) {
787 callback(data);
788 }
789 }
790 $.xget(source, handler, 'text');
791 return this;
792 },
793
794 successMessage: function(source, message) {
795 var elements = this;
796 $.xget(source, function() {
797 elements.tmpMessage(message, true);
798 });
799 return this;
800 },
801
802 wiki: function(text, withTitle) {
803 if (text == '') {
804 return this.html('');
805 }
806 var url = 'wiki_preview';
807 if (!withTitle) {
808 url += '/notitile';
809 }
810 var $this = this;
811 $.post(url, { text: text },
812 function (data) {
813 $this.html(data);
814 }, 'text');
815 return this;
816 },
817
818 popWin: function(w, h) {
819 return this.click(function() {
820 window.open(this.href, '_blank',
821 'toolbar=0,location=0,directories=0,status=0,'
822 +'menubar=0,scrollbars=1,resizable=1,'
823 +'width='+w+',height='+h);
824 return false;
825 });
826 }
827 });
828 })(jQuery);
829
830 // }}}
831 // {{{ preview wiki
832
833 function previewWiki(idFrom, idTo, withTitle, idShow)
834 {
835 $('#' + idTo).wiki($('#' + idFrom).val(), withTitle);
836 if (idShow != null) {
837 $('#' + idShow).show();
838 }
839 }
840
841 // }}}
842
843 /***************************************************************************
844 * Quick search
845 */
846
847 /* quick search {{{ */
848 (function($) {
849 function findPos(obj) {
850 var curleft = obj.offsetLeft || 0;
851 var curtop = obj.offsetTop || 0;
852 while (obj = obj.offsetParent) {
853 curleft += obj.offsetLeft
854 curtop += obj.offsetTop
855 }
856 return {x:curleft,y:curtop};
857 }
858
859 $.template('quickMinifiche',
860 '<div class="contact grayed" style="clear: both">' +
861 '<div class="identity">' +
862 '<div class="photo"><img src="photo/${hrpid}" alt="${directory_name}" /></div>' +
863 '<div class="nom">' +
864 '{{if is_female}}&bull;{{/if}}<a>${directory_name}</a>' +
865 '</div>' +
866 '<div class="edu">${promo}</div>' +
867 '</div>' +
868 '<div class="noprint bits"></div>' +
869 '<div class="long"></div>' +
870 '</div>');
871
872
873 function buildPopup(input, destination, linkBindFunction)
874 {
875 var $popup = destination;
876 var selected = null;
877 var hovered = 0;
878
879 function updateSelection()
880 {
881 var sel = $popup.children('.contact').addClass('grayed');
882 if (selected !== null) {
883 while (selected < 0) {
884 selected += sel.length;
885 }
886 if (selected >= sel.length) {
887 selected -= sel.length;
888 }
889 sel.eq(selected).removeClass('grayed');
890 }
891 }
892
893 function formatProfile(i, profile) {
894 var data = $.tmpl('quickMinifiche', profile)
895 .css('cursor', 'pointer')
896 .hover(function() {
897 selected = i;
898 updateSelection();
899 hovered++;
900 }, function() {
901 if (selected === i) {
902 selected = null;
903 updateSelection();
904 }
905 hovered--;
906 }).mouseup(function() {
907 var sel = $(this).find('a');
908 if (!sel.attr('hovered')) {
909 sel.click();
910 }
911 });
912 data.find('a').hover(function() { $(this).attr('hovered', true) },
913 function() { $(this).attr('hovered', false) });
914 return data;
915 }
916
917 if (!$popup) {
918 $popup = $('<div>').hide()
919 .addClass('contact-list')
920 .css({
921 position: 'absolute',
922 width: '300px',
923 top: input.css('bottom'),
924 clear: 'both',
925 'text-align': 'left'
926 });
927 input.after($popup);
928 }
929
930 return {
931 hide: function(ignoreIfHover) {
932 if (ignoreIfHover && hovered !== 0) {
933 return true;
934 }
935 selected = null;
936 updateSelection();
937 $popup.hide();
938 return true;
939 },
940
941 show: function() {
942 var pos = findPos(input.get(0));
943 $popup.css('left', pos.x - 300 + input.width()).show();
944 return true;
945 },
946
947 selected: function() {
948 return selected !== null;
949 },
950
951 unselect: function() {
952 selected = null;
953 updateSelection();
954 },
955
956 selectNext: function() {
957 if (selected === null) {
958 selected = 0;
959 } else {
960 selected++;
961 }
962 updateSelection();
963 return true;
964 },
965
966 selectPrev: function() {
967 if (selected === null) {
968 selected = -1;
969 } else {
970 selected--;
971 }
972 updateSelection();
973 return true;
974 },
975
976 activeCurrent: function() {
977 var sel = $popup.children('.contact');
978 if (selected !== null) {
979 sel.eq(selected).find('a').click();
980 return false;
981 }
982 return true;
983 },
984
985 updateContent: function(profiles, extra) {
986 var profile;
987 var $this;
988 $popup.empty();
989 for (var i = 0, len = profiles.length; i < len; i++) {
990 (function(elt) {
991 var profile = formatProfile(i, elt);
992 profile.find('a').each(function() {
993 linkBindFunction.call(this, elt, $this, extra);
994 });
995 profile.appendTo($popup);
996 }(profiles[i]));
997 }
998 if (len === 1) {
999 selected = 0;
1000 } else {
1001 selected = null;
1002 }
1003 updateSelection();
1004 if (len > 0) {
1005 this.show();
1006 } else {
1007 this.hide();
1008 }
1009 return true;
1010 }
1011 };
1012 }
1013
1014 $.fn.extend({
1015 quickSearch: function(options) {
1016 return this.each(function() {
1017 var $this = $(this);
1018 var $input = this;
1019 var $popup;
1020 var previous = null;
1021 var pending = false;
1022 var disabled = false;
1023 var updatePopup;
1024
1025 options = options || { };
1026 options = $.extend({
1027 destination: null,
1028 minChars: 3,
1029 shortChars: 5,
1030 shortTimeout: 300,
1031 longTimeout: 100,
1032 queryParams: {
1033 offset: 0,
1034 count: 10,
1035 allow_special: true
1036 },
1037 loadingClassLeft: 'ac_loading',
1038 loadingClassRight: 'ac_loading_left',
1039 selectAction: function(profile, popup, extra) {
1040 var type = extra.link_type || 'profile';
1041 switch (type) {
1042 case 'profile':
1043 $(this).attr('href', 'profile/' + profile.hrpid)
1044 .popWin(840, 600)
1045 .click(function() { $popup.hide(); return false; });
1046 break;
1047 case 'admin':
1048 $(this).attr('href', 'admin/user/' + profile.hrpid)
1049 .click(function() { window.open($(this).attr('href')); return false });
1050 break;
1051 }
1052 }
1053 }, options);
1054 options.loadingClass = $this.css('text-align') === 'right' ? options.loadingClassRight
1055 : options.loadingClassLeft;
1056 $this.attr('autocomplete', 'off');
1057 $popup = buildPopup($this, options.destination, options.selectAction);
1058
1059 function markPending() {
1060 pending = true;
1061 }
1062
1063 function performUpdate(quick)
1064 {
1065 if (updatePopup === markPending) {
1066 return true;
1067 }
1068 updatePopup = markPending;
1069 $this.addClass(options.loadingClass);
1070 $.xapi('search', $.extend({ 'quick': quick }, options.queryParams), function(data) {
1071 if (data.profile_count > options.queryParams.count || data.profile_count < 0) {
1072 return $popup.hide();
1073 }
1074 $popup.updateContent(data.profiles, data);
1075 previous = quick;
1076 }, function(data, text) {
1077 if (text !== 'abort') {
1078 disabled = true;
1079 }
1080 }).complete(function() {
1081 $this.removeClass(options.loadingClass);
1082 updatePopup = doUpdatePopup;
1083 if (pending) {
1084 updatePopup();
1085 }
1086 });
1087 return true;
1088 }
1089
1090 function doUpdatePopup(dontDelay)
1091 {
1092 var quick = $this.val();
1093 if ($.isFunction(quick.trim)) {
1094 quick = quick.trim();
1095 }
1096 pending = false;
1097 if (disabled || quick.length < options.minChars) {
1098 previous = quick;
1099 return $popup.hide();
1100 } else if (!dontDelay) {
1101 var timeout = quick.length < options.shortChars ? options.shortTimeout : options.longTimeout;
1102 setTimeout(function() {
1103 updatePopup(true);
1104 }, timeout);
1105 return true;
1106 } else if (previous === quick) {
1107 return $popup.show();
1108 }
1109 return performUpdate(quick);
1110 }
1111
1112 updatePopup = doUpdatePopup;
1113
1114 return $this.keyup(function(e) {
1115 if (e.keyCode !== 27 /* escape */ && e.keyCode !== 13 /* enter */
1116 && e.keyCode !== 9 /* tab */ && e.keyCode !== 38 /* up */
1117 && e.keyCode !== 40 /* down */) {
1118 return updatePopup();
1119 }
1120 return true;
1121 })
1122 .keydown(function(e) {
1123 switch (e.keyCode) {
1124 case 9: /* Tab */
1125 case 40: /* Down */
1126 $popup.selectNext();
1127 return false;
1128
1129 case 38:
1130 $popup.selectPrev();
1131 return false;
1132
1133 case 13: /* Return */
1134 return $popup.activeCurrent();
1135
1136 case 27: /* Escape */
1137 if ($popup.selected()) {
1138 $popup.unselect();
1139 } else {
1140 $popup.hide();
1141 }
1142 return true;
1143 }
1144 return true;
1145 })
1146 .blur(function() {
1147 return $popup.hide(true);
1148 })
1149 .focus(updatePopup);});
1150 }
1151 });
1152 }(jQuery));
1153
1154 /***************************************************************************
1155 * Overlib made simple
1156 */
1157
1158 (function($) {
1159 $.fn.extend({
1160 overlib: function(text, width, height) {
1161 var args = [ ];
1162 var key;
1163
1164 if (typeof text === 'string') {
1165 args.push(text);
1166 if (width) {
1167 args.push(WIDTH, width);
1168 }
1169 if (height) {
1170 args.push(HEIGHT, height);
1171 }
1172 } else {
1173 for (key in text) {
1174 switch (key) {
1175 case 'text':
1176 args.unshift(text[key]);
1177 break;
1178 case 'caption':
1179 args.push(CAPTION, text[key]);
1180 break;
1181 case 'close_text':
1182 args.push(CLOSETEXT, text[key]);
1183 break;
1184 case 'delay':
1185 args.push(DELAY, text[key]);
1186 break;
1187 case 'sticky':
1188 if (text[key]) {
1189 args.push(STICKY);
1190 }
1191 break;
1192 case 'width':
1193 args.push(WIDTH, text[key]);
1194 break;
1195 case 'height':
1196 args.push(HEIGHT, text[key]);
1197 break;
1198 }
1199 }
1200 }
1201 return this
1202 .mouseover(function () {
1203 return overlib.apply(null, args);
1204 })
1205 .mouseout(nd);
1206 }
1207 });
1208 }(jQuery));
1209 /* }}} */
1210
1211 /***************************************************************************
1212 * The real OnLoad
1213 */
1214
1215 $(function() {
1216 auto_links();
1217 getNow();
1218 setInterval(getNow, 1000);
1219 $("#quick")
1220 .focus(function() {
1221 if ($(this).val() === 'Recherche dans l\'annuaire') {
1222 $(this).val('');
1223 }
1224 $("#quick_button").show();
1225 })
1226 .blur(function() {
1227 $("#quick_button").hide();
1228 })
1229 .quickSearch();
1230 $("#quick_button").click(function() {
1231 if ($("#quick").val() === 'Recherche dans l\'annuaire'
1232 || $("#quick").val() === '') {
1233 return false;
1234 }
1235 return true;
1236 });
1237 });
1238
1239 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: