MOD modification de la manière d'afficher les messages auquels l'utilisateur souhaite...
[euphorik.git] / js / euphorik.js
1 // coding: utf-8
2 // Copyright 2008 Grégory Burri
3 //
4 // This file is part of Euphorik.
5 //
6 // Euphorik is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // Euphorik is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with Euphorik. If not, see <http://www.gnu.org/licenses/>.
18
19 /**
20 * Contient la base javascript pour le site euphorik.ch.
21 * Chaque page possède son propre fichier js nommé "page<nom de la page>.js".
22 * Auteur : GBurri
23 * Date : 6.11.2007
24 */
25
26
27 /**
28 * La configuration.
29 * Normalement 'const' à la place de 'var' mais non supporté par IE7.
30 */
31 var conf = {
32 nickDefaut : "<nick>",
33 nbMessageAffiche : 40, // (par page)
34 pseudoDefaut : "<nick>",
35 tempsAffichageMessageDialogue : 4000, // en ms
36 tempsKick : 15, // en minute
37 tempsBan : 60 * 24 * 3, // en minutes (3jours)
38 smiles : {
39 "smile" : [/:\)/g, /:-\)/g],
40 "bigsmile" : [/:D/g, /:-D/g],
41 "clin" : [/;\)/g, /;-\)/g],
42 "cool" : [/8\)/g, /8-\)/g],
43 "eheheh" : [/:P/g, /:-P/g],
44 "lol" : [/\[-lol\]/g],
45 "spliff" : [/\[-spliff\]/g],
46 "oh" : [/:o/g, /:O/g],
47 "heink" : [/\[-heink\]/g],
48 "hum" : [/\[-hum\]/g],
49 "boh" : [/\[-boh\]/g],
50 "sniff" : [/:\(/g, /:-\(/g],
51 "triste" : [/\[-triste\]/g],
52 "pascontent" : [/>\(/g, /&gt;\(/g],
53 "argn" : [/\[-argn\]/g],
54 "redface" : [/\[-redface\]/g],
55 "bunny" : [/\[-lapin\]/g],
56 "chat" : [/\[-chat\]/g],
57 "renne" : [/\[-renne\]/g],
58 "star" : [/\[-star\]/g],
59 "kirby" : [/\[-kirby\]/g],
60 "slurp" : [/\[-slurp\]/g],
61 "agreed" : [/\[-agreed\]/g],
62 "dodo" : [/\[-dodo\]/g],
63 "bn" : [/\[-bn\]/g]
64 }
65 }
66
67 ///////////////////////////////////////////////////////////////////////////////////////////////////
68
69 String.prototype.trim = function()
70 {
71 return jQuery.trim(this) // anciennement : this.replace(/^\s+|\s+$/g, "");
72 }
73
74 String.prototype.ltrim = function()
75 {
76 return this.replace(/^\s+/, "");
77 }
78
79 String.prototype.rtrim = function()
80 {
81 return this.replace(/\s+$/, "");
82 }
83
84 ///////////////////////////////////////////////////////////////////////////////////////////////////
85
86 /**
87 * Cette classe regroupe des fonctions utilitaires (helpers).
88 * @formateur est permet de formater les messages affichés à l'aide de messageDialogue (facultatif)
89 */
90 function Util(formateur)
91 {
92 $("#info .fermer").click(function(){
93 $("#info").slideUp(50)
94 })
95
96 $("body").append('<div id="flecheBulle"></div>').append('<div id="messageBulle"><p></p></div>')
97
98 this.formateur = formateur
99 this.bulleActive = true
100 }
101
102 var messageType = {informatif: 0, question: 1, erreur: 2}
103
104 /**
105 * Affiche une boite de dialogue avec un message à l'intérieur.
106 * @param message le message (string)
107 * @param type voir 'messageType'. par défaut messageType.informatif
108 * @param les boutons sous la forme d'un objet ou les clefs sont les labels des boutons
109 * et les valeurs les fonctions executées lorsqu'un bouton est activé.
110 * @param formate faut-il formaté le message ? true par défaut
111 */
112 Util.prototype.messageDialogue = function(message, type, boutons, formate)
113 {
114 var thisUtil = this
115
116 if (type == undefined)
117 type = messageType.informatif
118
119 if (formate == undefined)
120 formate = true
121
122 if (this.timeoutMessageDialogue != undefined)
123 clearTimeout(this.timeoutMessageDialogue)
124
125 var fermer = function(){$("#info").slideUp(100)}
126 fermer()
127
128 $("#info .message").html(thisUtil.formateur == undefined || !formate ? message : thisUtil.formateur.traitementComplet(message))
129 switch(type)
130 {
131 case messageType.informatif : $("#info #icone").attr("class", "information"); break
132 case messageType.question : $("#info #icone").attr("class", "interrogation"); break
133 case messageType.erreur : $("#info #icone").attr("class", "exclamation"); break
134 }
135 $("#info .boutons").html("")
136 for (var b in boutons)
137 $("#info .boutons").append("<div>" + b + "</div>").find("div:last").click(boutons[b]).click(fermer)
138
139 $("#info").slideDown(200)
140 this.timeoutMessageDialogue = setTimeout(fermer, conf.tempsAffichageMessageDialogue)
141 }
142
143 /**
144 * Affiche un info bulle lorsque le curseur survole l'élément donné.
145 * FIXME : le width de element ne tient pas compte du padding !?
146 */
147 Util.prototype.infoBulle = function(message, element)
148 {
149 var thisUtil = this
150
151 var cacherBulle = function()
152 {
153 $("#flecheBulle").hide()
154 $("#messageBulle").hide()
155 }
156
157 element.hover(
158 function(e)
159 {
160 if (!thisUtil.bulleActive)
161 return
162
163 var m = $("#messageBulle")
164 var f = $("#flecheBulle")
165
166 // remplie le paragraphe de la bulle avec le message
167 $("p", m).html(message)
168
169 // réinitialise la position, évite le cas ou la boite est collé à droite et remplie avec un texte la faisant dépassé
170 // dans ce cas la hauteur n'est pas calculé correctement
171 m.css("top", 0).css("left", 0)
172
173 var positionFleche = {
174 left : element.offset().left + element.width() / 2 - f.width() / 2,
175 top : element.offset().top - f.height()
176 }
177 var positionMessage = {
178 left : element.offset().left + element.width() / 2 - m.width() / 2,
179 top : element.offset().top - f.height() - m.height()
180 }
181 var depassementDroit = (positionMessage.left + m.width()) - $("body").width()
182 if (depassementDroit > 0)
183 positionMessage.left -= depassementDroit
184 else
185 {
186 if (positionMessage.left < 0)
187 positionMessage.left = 0
188 }
189
190 m.css("top", positionMessage.top).css("left", positionMessage.left).show()
191 f.css("top", positionFleche.top).css("left", positionFleche.left).show()
192 },
193 cacherBulle
194 ).click(cacherBulle)
195 }
196
197 /**
198 * Utilisé pour l'envoie de donnée avec la méthode ajax de jQuery.
199 */
200 Util.prototype.jsonVersAction = function(json)
201 {
202 return {action : JSON.stringify(json) }
203 }
204
205 Util.prototype.md5 = function(chaine)
206 {
207 return hex_md5(chaine)
208 }
209
210 // pompé de http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130
211 Util.prototype.setSelectionRange = function(input, selectionStart, selectionEnd)
212 {
213 if (input.setSelectionRange)
214 {
215 input.focus()
216 input.setSelectionRange(selectionStart, selectionEnd)
217 }
218 else if (input.createTextRange)
219 {
220 var range = input.createTextRange()
221 range.collapse(true)
222 range.moveEnd('character', selectionEnd)
223 range.moveStart('character', selectionStart)
224 range.select()
225 }
226 }
227
228 Util.prototype.setCaretToEnd = function(input)
229 {
230 this.setSelectionRange(input, input.value.length, input.value.length)
231 }
232 Util.prototype.setCaretToBegin = function(input)
233 {
234 this.setSelectionRange(input, 0, 0)
235 }
236 Util.prototype.setCaretToPos = function(input, pos)
237 {
238 this.setSelectionRange(input, pos, pos)
239 }
240 Util.prototype.selectString = function(input, string)
241 {
242 var match = new RegExp(string, "i").exec(input.value)
243 if (match)
244 {
245 this.setSelectionRange (input, match.index, match.index + match[0].length)
246 }
247 }
248 Util.prototype.replaceSelection = function(input, replaceString) {
249 if (input.setSelectionRange)
250 {
251 var selectionStart = input.selectionStart
252 var selectionEnd = input.selectionEnd
253 input.value = input.value.substring(0, selectionStart) + replaceString + input.value.substring(selectionEnd)
254
255 if (selectionStart != selectionEnd) // has there been a selection
256 this.setSelectionRange(input, selectionStart, selectionStart + replaceString.length)
257 else // set caret
258 this.setCaretToPos(input, selectionStart + replaceString.length)
259 }
260 else if (document.selection)
261 {
262 input.focus()
263 var range = document.selection.createRange()
264 if (range.parentElement() == input)
265 {
266 var isCollapsed = range.text == ''
267 range.text = replaceString
268 if (!isCollapsed)
269 {
270 range.moveStart('character', -replaceString.length);
271 }
272 }
273 }
274 }
275
276 Util.prototype.rot13 = function(chaine)
277 {
278 var ACode = 'A'.charCodeAt(0)
279 var aCode = 'a'.charCodeAt(0)
280 var MCode = 'M'.charCodeAt(0)
281 var mCode = 'm'.charCodeAt(0)
282 var ZCode = 'Z'.charCodeAt(0)
283 var zCode = 'z'.charCodeAt(0)
284
285 var f = function(ch, pos) {
286 if (pos == ch.length)
287 return ""
288
289 var c = ch.charCodeAt(pos);
290 return String.fromCharCode(
291 c +
292 (c >= ACode && c <= MCode || c >= aCode && c <= mCode ? 13 :
293 (c > MCode && c <= ZCode || c > mCode && c <= zCode ? -13 : 0))
294 ) + f(ch, pos + 1)
295 }
296 return f(chaine, 0)
297 }
298
299 ///////////////////////////////////////////////////////////////////////////////////////////////////
300
301 function Pages()
302 {
303 this.pageCourante = null
304 this.pages = {}
305 }
306
307 /**
308 * Accepte soit un objet soit un string.
309 * un string correspond au nom de la page, par exemple : "page" -> "page.html"
310 */
311 Pages.prototype.ajouterPage = function(page)
312 {
313 if (typeof page == "string")
314 {
315 this.pages[page] = page
316 }
317 else
318 {
319 page.pages = this // la magie des langages dynamiques : le foutoire
320 this.pages[page.nom] = page
321 }
322 }
323
324 Pages.prototype.afficherPage = function(nomPage, forcerChargement)
325 {
326 if (forcerChargement == undefined) forcerChargement = false
327
328 var page = this.pages[nomPage]
329 if (page == undefined || (!forcerChargement && page == this.pageCourante)) return
330
331 if (this.pageCourante != null && this.pageCourante.decharger)
332 this.pageCourante.decharger()
333
334 $("#menu li").removeClass("courante")
335 $("#menu li." + nomPage).addClass("courante")
336
337 this.pageCourante = page
338 var contenu = ""
339 if (typeof page == "string")
340 $.ajax({async: false, url: "pages/" + page + ".html", success : function(page) { contenu += page }})
341 else
342 contenu += this.pageCourante.contenu()
343 $("#page").html(contenu).removeClass().addClass(this.pageCourante.nom)
344
345 if (this.pageCourante.charger)
346 this.pageCourante.charger()
347 }
348
349 ///////////////////////////////////////////////////////////////////////////////////////////////////
350
351 /**
352 * Classe permettant de formater du texte par exemple pour la substitution des liens dans les
353 * message par "[url]".
354 * TODO : améliorer l'efficacité des méthods notamment lié au smiles.
355 */
356 function Formateur()
357 {
358 this.smiles = conf.smiles
359 this.protocoles = "http|https|ed2k"
360
361 this.regexUrl = new RegExp("(?:(?:" + this.protocoles + ")://|www\\.)[^ ]*", "gi")
362 this.regexImg = new RegExp("^.*?\\.(gif|jpg|png|jpeg|bmp|tiff)$", "i")
363 this.regexDomaine = new RegExp("^(?:(?:" + this.protocoles + ")://|www\\.).*?([^/.]+\\.[^/.]+)(?:$|/).*$", "i")
364 this.regexTestProtocoleExiste = new RegExp("^(?:" + this.protocoles + ")://.*$", "i")
365 this.regexNomProtocole = new RegExp("^(.*?)://")
366 }
367
368 /**
369 * Formate un pseudo saise par l'utilisateur.
370 * @param pseudo le pseudo brut
371 * @return le pseudo filtré
372 */
373 Formateur.prototype.filtrerInputPseudo = function(pseudo)
374 {
375 return pseudo.replace(/{|}/g, "").trim()
376 }
377
378 Formateur.prototype.getSmilesHTML = function()
379 {
380 var XHTML = ""
381 for (var sNom in this.smiles)
382 {
383 XHTML += "<img class=\"" + sNom + "\" src=\"img/smileys/" + sNom + ".gif\" alt =\"" + sNom + "\" />"
384 }
385 return XHTML
386 }
387
388 /**
389 * Formatage complet d'un texte.
390 * @M le message
391 * @pseudo facultatif, permet de contruire le label des images sous la forme : "<Pseudo> : <Message>"
392 */
393 Formateur.prototype.traitementComplet = function(M, pseudo)
394 {
395 return this.traiterLiensConv(this.traiterSmiles(this.traiterURL(this.traiterWikiSyntaxe(this.remplacerBalisesHTML(M)), pseudo)))
396 }
397
398 /**
399 * Transforme les liens en entités clickables.
400 * Un lien vers une conversation permet d'ouvrire celle ci, elle se marque comme ceci dans un message :
401 * "{5F}" ou 5F est la racine de la conversation.
402 * Ce lien sera transformer en <span class="lienConv">{5F}</span> pouvant être clické pour créer la conv 5F.
403 */
404 Formateur.prototype.traiterLiensConv = function(M)
405 {
406 return M.replace(
407 /\{\w+\}/g,
408 function(lien)
409 {
410 return "<span class=\"lienConv\">" + lien + "</span>"
411 }
412 )
413 }
414
415 /**
416 * FIXME : Cette méthode est attrocement lourde ! A optimiser.
417 * moyenne sur échantillon : 234ms
418 */
419 Formateur.prototype.traiterSmiles = function(M)
420 {
421 for (var sNom in this.smiles)
422 {
423 ss = this.smiles[sNom]
424 for (var i = 0; i < ss.length; i++)
425 M = M.replace(ss[i], "<img src=\"img/smileys/" + sNom + ".gif\" alt =\"" + sNom + "\" />")
426 }
427 return M
428 }
429
430 Formateur.prototype.remplacerBalisesHTML = function(M)
431 {
432 return M.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;")
433 }
434
435 Formateur.prototype.traiterURL = function(M, pseudo)
436 {
437 thisFormateur = this
438
439 var traitementUrl = function(url)
440 {
441 // si ya pas de protocole on rajoute "http://"
442 if (!thisFormateur.regexTestProtocoleExiste.test(url))
443 url = "http://" + url
444 var extension = thisFormateur.getShort(url)
445 return "<a " + (extension[1] ? "title=\"" + (pseudo == undefined ? "" : thisFormateur.traiterPourFenetreLightBox(pseudo, url) + ": ") + thisFormateur.traiterPourFenetreLightBox(M, url) + "\"" + " rel=\"lightbox\"" : "") + " href=\"" + url + "\" >[" + extension[0] + "]</a>"
446 }
447 return M.replace(this.regexUrl, traitementUrl)
448 }
449
450 /**
451 * Formatage en utilisant un sous-ensemble des règles de mediwiki.
452 * par exemple ''italic'' devient <i>italic</i>
453 */
454 Formateur.prototype.traiterWikiSyntaxe = function(M)
455 {
456 return M.replace(
457 /'''(.*?)'''/g,
458 function(texte, capture)
459 {
460 return "<b>" + capture + "</b>"
461 }
462 ).replace(
463 /''(.*?)''/g,
464 function(texte, capture)
465 {
466 return "<i>" + capture + "</i>"
467 }
468 )
469 }
470
471 /**
472 * Renvoie une version courte de l'url.
473 * par exemple : http://en.wikipedia.org/wiki/Yakov_Smirnoff devient wikipedia.org
474 */
475 Formateur.prototype.getShort = function(url)
476 {
477 var estUneImage = false
478 var versionShort = null
479 var rechercheImg = this.regexImg.exec(url)
480
481 if (rechercheImg != null)
482 {
483 versionShort = rechercheImg[1].toLowerCase()
484 if (versionShort == "jpeg") versionShort = "jpg" // jpeg -> jpg
485 estUneImage = true
486 }
487 else
488 {
489 var rechercheDomaine = this.regexDomaine.exec(url)
490 if (rechercheDomaine != null && rechercheDomaine.length >= 2)
491 versionShort = rechercheDomaine[1]
492 else
493 {
494 var nomProtocole = this.regexNomProtocole.exec(url)
495 if (nomProtocole != null && nomProtocole.length >= 2)
496 versionShort = nomProtocole[1]
497 }
498 }
499
500 return [versionShort == null ? "url" : versionShort, estUneImage]
501 }
502
503 /**
504 * Traite les pseudo et messages à être affiché dans le titre d'une image visualisé avec lightbox.
505 */
506 Formateur.prototype.traiterPourFenetreLightBox = function(M, urlCourante)
507 {
508 thisFormateur = this
509 var traitementUrl = function(url)
510 {
511 return "[" + thisFormateur.getShort(url)[0] + (urlCourante == url ? "*" : "") + "]"
512 }
513
514 return this.remplacerBalisesHTML(M).replace(this.regexUrl, traitementUrl)
515 }
516
517
518 ///////////////////////////////////////////////////////////////////////////////////////////////////
519
520 // les statuts possibes du client
521 var statutType = {
522 // mode enregistré, peut poster des messages et modifier son profile
523 auth_registered : 0,
524 // mode identifié, peut poster des messages mais n'a pas accès au profile
525 auth_not_registered : 1,
526 // mode déconnecté, ne peut pas poster de message
527 deconnected : 2
528 }
529
530 function Client(util)
531 {
532 this.util = util
533
534 this.cookie = null
535 this.regexCookie = new RegExp("^cookie=([^;]*)")
536
537 // données personnels
538 this.resetDonneesPersonnelles()
539
540 this.setStatut(statutType.deconnected)
541
542 // si true alors chaque modification du client est mémorisé sur le serveur
543 this.autoflush = $.browser["opera"]
544 }
545
546 Client.prototype.resetDonneesPersonnelles = function()
547 {
548 this.id = 0
549 this.pseudo = conf.pseudoDefaut
550 this.login = ""
551 this.password = ""
552 this.email = ""
553 this.css = $("link#cssPrincipale").attr("href")
554 this.nickFormat = "nick"
555 this.viewTimes = true
556 this.viewTooltips = true
557 this.cookie = undefined
558
559 this.pagePrincipale = 1
560 this.ekMaster = false
561
562 // les conversations, une conversation est un objet possédant les attributs suivants :
563 // - root (entier)
564 // - page (entier)
565 this.conversations = new Array()
566 }
567
568 Client.prototype.setCss = function(css)
569 {
570 if (this.css == css || css == "")
571 return
572
573 this.css = css
574 $("link#cssPrincipale").attr("href", this.css)
575 if (this.autoflush) this.flush(true)
576 }
577
578 Client.prototype.pageSuivante = function(numConv)
579 {
580 if (numConv < 0 && this.pagePrincipale > 1)
581 this.pagePrincipale -= 1
582 else if (this.conversations[numConv].page > 1)
583 this.conversations[numConv].page -= 1
584 }
585
586 Client.prototype.pagePrecedente = function(numConv)
587 {
588 if (numConv < 0)
589 this.pagePrincipale += 1
590 else
591 this.conversations[numConv].page += 1
592 }
593
594 /**
595 * Définit la première page pour la conversation donnée.
596 * @return true si la page a changé sinon false
597 */
598 Client.prototype.goPremierePage = function(numConv)
599 {
600 if (numConv < 0)
601 {
602 if (this.pagePrincipale == 1)
603 return false
604 this.pagePrincipale = 1
605 }
606 else
607 {
608 if (this.conversations[numConv].page == 1)
609 return false
610 this.conversations[numConv].page = 1
611 }
612 return true
613 }
614
615 /**
616 * Ajoute une conversation à la vue de l'utilisateur.
617 * Le profile de l'utilisateur est directement sauvegardé sur le serveur.
618 * @param racines la racine de la conversation (integer)
619 * @return true si la conversation a été créée sinon false (par exemple si la conv existe déjà)
620 */
621 Client.prototype.ajouterConversation = function(racine)
622 {
623 // vérification s'il elle n'existe pas déjà
624 for (var i = 0; i < this.conversations.length; i++)
625 if (this.conversations[i].root == racine)
626 return false
627
628 this.conversations.push({root : racine, page : 1})
629 if (this.autoflush) this.flush(true)
630
631 return true
632 }
633
634 Client.prototype.supprimerConversation = function(num)
635 {
636 if (num < 0 || num >= this.conversations.length) return
637
638 // décalage TODO : supprimer le dernier élément
639 for (var i = num; i < this.conversations.length - 1; i++)
640 this.conversations[i] = this.conversations[i+1]
641 this.conversations.pop()
642
643 if (this.autoflush) this.flush(true)
644 }
645
646 Client.prototype.getJSONLogin = function(login, password)
647 {
648 return {
649 "action" : "authentification",
650 "login" : login,
651 "password" : password
652 }
653 }
654
655 Client.prototype.getJSONLoginCookie = function()
656 {
657 return {
658 "action" : "authentification",
659 "cookie" : this.cookie
660 }
661 }
662
663 /**
664 * le couple (login, password) est facultatif. S'il n'est pas fournit alors il ne sera pas possible
665 * de s'autentifier avec (login, password).
666 */
667 Client.prototype.getJSONEnregistrement = function(login, password)
668 {
669 var mess = { "action" : "register" }
670
671 if (login != undefined && password != undefined)
672 {
673 mess["login"] = login
674 mess["password"] = password
675 }
676
677 return mess;
678 }
679
680 Client.prototype.getJSONConversations = function()
681 {
682 var conversations = new Array()
683 for (var i = 0; i < this.conversations.length; i++)
684 conversations.push(this.conversations[i].root)
685 return conversations
686 }
687
688 Client.prototype.getJSONProfile = function()
689 {
690 return {
691 "action" : "set_profile",
692 "cookie" : this.cookie,
693 "login" : this.login,
694 "password" : this.password,
695 "nick" : this.pseudo,
696 "email" : this.email,
697 "css" : this.css,
698 "nick_format" : this.nickFormat,
699 "view_times" : this.viewTimes,
700 "view_tooltips" : this.viewTooltips,
701 "conversations" : this.getJSONConversations()
702 }
703 }
704
705 /**
706 * Renvoie null si pas définit.
707 */
708 Client.prototype.getCookie = function()
709 {
710 var cookie = this.regexCookie.exec(document.cookie)
711 if (cookie == null) this.cookie = null
712 else this.cookie = cookie[1]
713 }
714
715 Client.prototype.delCookie = function()
716 {
717 document.cookie = "cookie=; max-age=0"
718 }
719
720 Client.prototype.setCookie = function()
721 {
722 if (this.cookie == null || this.cookie == undefined)
723 return
724
725 // ne fonctionne pas sous IE....
726 /*document.cookie = "cookie=" + this.cookie + "; max-age=" + (60 * 60 * 24 * 365) */
727
728 document.cookie =
729 "cookie="+this.cookie+"; expires=" + new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 365).toUTCString()
730 }
731
732 Client.prototype.authentifie = function()
733 {
734 return this.statut == statutType.auth_registered || this.statut == statutType.auth_not_registered
735 }
736
737 Client.prototype.setStatut = function(statut)
738 {
739 // conversation en "enum" si en "string"
740 if (typeof(statut) == "string")
741 {
742 statut =
743 statut == "auth_registered" ?
744 statutType.auth_registered :
745 (statut == "auth_not_registered" ? statutType.auth_not_registered : statutType.deconnected)
746 }
747
748 if (statut == this.statut) return
749
750 this.statut = statut
751 this.majMenu()
752 }
753
754 /**
755 * Effectue la connexion vers le serveur.
756 * Cette fonction est bloquante tant que la connexion n'a pas été établie.
757 * S'il existe un cookie en local on s'authentifie directement avec lui.
758 * Si il n'est pas possible de s'authentifier alors on affiche un captcha anti-bot.
759 */
760 Client.prototype.connexionCookie = function()
761 {
762 this.getCookie()
763 if (this.cookie == null) return false;
764 return this.connexion(this.getJSONLoginCookie())
765 }
766
767 Client.prototype.connexionLogin = function(login, password)
768 {
769 return this.connexion(this.getJSONLogin(login, password))
770 }
771
772 Client.prototype.enregistrement = function(login, password)
773 {
774 if (this.authentifie())
775 {
776 this.login = login
777 this.password = password
778 if(this.flush())
779 {
780 this.setStatut(statutType.auth_registered)
781 return true
782 }
783 return false
784 }
785 else
786 {
787 return this.connexion(this.getJSONEnregistrement(login, password))
788 }
789 }
790
791 Client.prototype.connexion = function(messageJson)
792 {
793 ;; dumpObj(messageJson)
794 thisClient = this
795 jQuery.ajax(
796 {
797 async: false,
798 type: "POST",
799 url: "request",
800 dataType: "json",
801 data: this.util.jsonVersAction(messageJson),
802 success:
803 function(data)
804 {
805 ;; dumpObj(data)
806 if (data["reply"] == "error")
807 thisClient.util.messageDialogue(data["error_message"])
808 else
809 thisClient.chargerDonnees(data)
810 }
811 }
812 )
813 return this.authentifie()
814 }
815
816 Client.prototype.deconnexion = function()
817 {
818 this.flush(true)
819 this.delCookie()
820 this.resetDonneesPersonnelles()
821 this.setStatut(statutType.deconnected) // deconnexion
822 }
823
824 Client.prototype.chargerDonnees = function(data)
825 {
826 // la modification du statut qui suit met à jour le menu, le menu dépend (page admin)
827 // de l'état ekMaster
828 this.ekMaster = data["ek_master"] != undefined ? data["ek_master"] : false
829
830 this.setStatut(data["status"])
831
832 if (this.authentifie())
833 {
834 this.cookie = data["cookie"]
835 this.setCookie()
836
837 this.id = data["id"]
838 this.login = data["login"]
839 this.pseudo = data["nick"]
840 this.email = data["email"]
841 this.setCss(data["css"])
842 this.nickFormat = data["nick_format"]
843 this.viewTimes = data["view_times"]
844 this.viewTooltips = data["view_tooltips"]
845
846 // la page de la conversation principale
847 this.pagePrincipale = 1
848
849 // les conversations
850 this.conversations = data["conversations"]
851 for (var i = 0; i < this.conversations.length; i++)
852 this.conversations[i] = {root : this.conversations[i], page : 1}
853
854 this.majBulle()
855 this.majCssSelectionee()
856 }
857 }
858
859 /**
860 * Met à jour les données personne sur serveur.
861 * @param async de manière asynchrone ? défaut = true
862 * @return false si le flush n'a pas pû se faire sinon true
863 */
864 Client.prototype.flush = function(async)
865 {
866 if (async == undefined)
867 async = false
868
869 if (!this.authentifie())
870 return false
871
872 var thisClient = this
873 var ok = true
874
875 ;; dumpObj(this.getJSONProfile())
876 jQuery.ajax(
877 {
878 async: async,
879 type: "POST",
880 url: "request",
881 dataType: "json",
882 data: this.util.jsonVersAction(this.getJSONProfile()),
883 success:
884 function(data)
885 {
886 ;; dumpObj(data)
887 if (data["reply"] == "error")
888 {
889 thisClient.util.messageDialogue(data["error_message"])
890 ok = false
891 }
892 else
893 {
894 thisClient.majBulle()
895 }
896 }
897 }
898 )
899
900 return ok
901 }
902
903 Client.prototype.majMenu = function()
904 {
905 displayType = "block"
906
907 $("#menu .admin").css("display", this.ekMaster ? displayType : "none")
908
909 // met à jour le menu
910 if (this.statut == statutType.auth_registered)
911 {
912 $("#menu .profile").css("display", displayType).text("profile")
913 $("#menu .logout").css("display", displayType)
914 $("#menu .register").css("display", "none")
915 }
916 else if (this.statut == statutType.auth_not_registered)
917 {
918 $("#menu .profile").css("display", "none")
919 $("#menu .logout").css("display", displayType)
920 $("#menu .register").css("display", displayType)
921 }
922 else
923 {
924 $("#menu .profile").css("display", displayType).text("login")
925 $("#menu .logout").css("display", "none")
926 $("#menu .register").css("display", displayType)
927 }
928 }
929
930 /**
931 * Met à jour l'affichage des infos bulles en fonction du profile.
932 */
933 Client.prototype.majBulle = function()
934 {
935 this.util.bulleActive = this.viewTooltips
936 }
937
938 /**
939 * Met à jour la css sélectionnée, lors du chargement des données.
940 */
941 Client.prototype.majCssSelectionee = function()
942 {
943 // extraction du numéro de la css courante
944 var numCssCourante = this.css.match(/^.*?\/(\d)\/.*$/)
945 if (numCssCourante != null && numCssCourante[1] != undefined)
946 {
947 $("#menuCss option").removeAttr("selected")
948 $("#menuCss option[value=" + numCssCourante[1]+ "]").attr("selected", "selected")
949 }
950 }
951
952 Client.prototype.slap = function(userId, raison)
953 {
954 var thisClient = this
955
956 jQuery.ajax({
957 type: "POST",
958 url: "request",
959 dataType: "json",
960 data: this.util.jsonVersAction(
961 {
962 "action" : "slap",
963 "cookie" : thisClient.cookie,
964 "user_id" : userId,
965 "reason" : raison
966 }),
967 success:
968 function(data)
969 {
970 if (data["reply"] == "error")
971 thisClient.util.messageDialogue(data["error_message"])
972 }
973 })
974 }
975
976 Client.prototype.ban = function(userId, raison, minutes)
977 {
978 var thisClient = this
979
980 // par défaut un ban correspond à 3 jours
981 if (typeof(minutes) == "undefined")
982 minutes = conf.tempsBan;
983
984 jQuery.ajax({
985 type: "POST",
986 url: "request",
987 dataType: "json",
988 data: this.util.jsonVersAction(
989 {
990 "action" : "ban",
991 "cookie" : thisClient.cookie,
992 "duration" : minutes,
993 "user_id" : userId,
994 "reason" : raison
995 }),
996 success:
997 function(data)
998 {
999 if (data["reply"] == "error")
1000 thisClient.util.messageDialogue(data["error_message"])
1001 }
1002 })
1003 }
1004
1005 Client.prototype.kick = function(userId, raison)
1006 {
1007 this.ban(userId, raison, conf.tempsKick)
1008 }
1009
1010 ///////////////////////////////////////////////////////////////////////////////////////////////////
1011
1012 /**
1013 * classe permettant de gérer les événements (push serveur).
1014 * l'information envoyé est sous la forme :
1015 * {
1016 * "action" : "wait_event"
1017 * "page" : <page>
1018 * [..]
1019 * }
1020 * l'information reçu est sous la forme :
1021 * {
1022 * "reply" : <reply>
1023 * }
1024 * @page la page
1025 */
1026 function PageEvent(page, util)
1027 {
1028 this.page = page
1029 this.util = util
1030
1031 // l'objet JSONHttpRequest représentant la connexion d'attente
1032 this.attenteCourante = null
1033
1034 // le multhreading du pauvre, merci javascript de m'offrire autant de primitives pour la gestion de la concurrence...
1035 this.stop = false
1036 }
1037
1038 /**
1039 * Arrête l'attente courante s'il y en a une.
1040 */
1041 PageEvent.prototype.stopAttenteCourante = function()
1042 {
1043 this.stop = true
1044
1045 if (this.attenteCourante != null)
1046 {
1047 this.attenteCourante.abort()
1048 }
1049 }
1050
1051 /**
1052 * Attend un événement lié à la page.
1053 * @funSend une fonction renvoyant les données json à envoyer
1054 * @funsReceive est un objet comprenant les fonctions à appeler en fonction du "reply"
1055 * les fonctions acceptent un paramètre correspondant au données reçues.
1056 * exemple : {"new_message" : function(data){ ... }}
1057 */
1058 PageEvent.prototype.waitEvent = function(funSend, funsReceive)
1059 {
1060 this.stopAttenteCourante()
1061
1062 this.stop = false
1063
1064 var thisPageEvent = this
1065
1066 // on doit conserver l'ordre des valeurs de l'objet JSON (le serveur les veut dans l'ordre définit dans le protocole)
1067 // TODO : ya pas mieux ?
1068 var dataToSend =
1069 {
1070 "action" : "wait_event",
1071 "page" : this.page
1072 }
1073 var poulpe = funSend()
1074 for (v in poulpe)
1075 dataToSend[v] = poulpe[v]
1076
1077 ;; dumpObj(dataToSend)
1078
1079 this.attenteCourante = jQuery.ajax({
1080 type: "POST",
1081 url: "request",
1082 dataType: "json",
1083 // TODO : doit disparaitre
1084 timeout: 180000, // timeout de 3min. Gros HACK pas beau. FIXME problème décrit ici : http://groups.google.com/group/jquery-en/browse_thread/thread/8724e64af3333a76
1085 data: this.util.jsonVersAction(dataToSend),
1086 success:
1087 function(data)
1088 {
1089 ;; dumpObj(data)
1090
1091 funsReceive[data["reply"]](data)
1092
1093 // rappel de la fonction dans 100 ms
1094 setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funsReceive) }, 100)
1095 },
1096 error:
1097 function(XMLHttpRequest, textStatus, errorThrown)
1098 {
1099 ;; console.log("Connexion perdue dans waitEvent")
1100 setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funsReceive) }, 1000)
1101 }
1102 })
1103 }
1104
1105 /**
1106 * Si un stopAttenteCourante survient un peu n'importe quand il faut imédiatement arreter de boucler.
1107 */
1108 PageEvent.prototype.waitEvent2 = function(funSend, funsReceive)
1109 {
1110 if (this.stop)
1111 return
1112 this.waitEvent(funSend, funsReceive)
1113 }
1114
1115 ///////////////////////////////////////////////////////////////////////////////////////////////////
1116
1117 function initialiserListeStyles(client)
1118 {
1119 $("#menuCss").change(
1120 function()
1121 {
1122 client.setCss("css/" + $("option:selected", this).attr("value") + "/euphorik.css")
1123 }
1124 )
1125 }
1126
1127 // charge dynamiquement le script de debug
1128 ;; jQuery.ajax({async : false, url : "js/debug.js", dataType : "script"})
1129
1130 // le main
1131 $(document).ready(
1132 function()
1133 {
1134 var formateur = new Formateur()
1135 var util = new Util(formateur)
1136 var client = new Client(util)
1137 var pages = new Pages()
1138
1139 // connexion vers le serveur (utilise un cookie qui traine)
1140 client.connexionCookie()
1141
1142 initialiserListeStyles(client)
1143
1144 // FIXME : ne fonctionne pas sous opera
1145 // voir : http://dev.jquery.com/ticket/2892#preview
1146 $(window).unload(function(){client.flush()})
1147
1148 $("#menu .minichat").click(function(){ pages.afficherPage("minichat") })
1149 $("#menu .admin").click(function(){ pages.afficherPage("admin") })
1150 $("#menu .profile").click(function(){ pages.afficherPage("profile") })
1151 $("#menu .logout").click(function(){
1152 util.messageDialogue("Êtes-vous sur de vouloir vous délogger ?", messageType.question,
1153 {"Oui" : function()
1154 {
1155 client.deconnexion();
1156 pages.afficherPage("minichat", true)
1157 },
1158 "Non" : function(){}
1159 }
1160 )
1161 })
1162 $("#menu .register").click(function(){ pages.afficherPage("register") })
1163 $("#menu .about").click(function(){ pages.afficherPage("about") })
1164
1165 // TODO : simplifier et pouvoir créer des liens par exemple : <span class="lien" href="conditions">Conditions d'utilisation</span>
1166 $("#footer .conditions").click(function(){ pages.afficherPage("conditions_utilisation") })
1167
1168 pages.ajouterPage(new PageMinichat(client, formateur, util))
1169 pages.ajouterPage(new PageAdmin(client, formateur, util))
1170 pages.ajouterPage(new PageProfile(client, formateur, util))
1171 pages.ajouterPage(new PageRegister(client, formateur, util))
1172 pages.ajouterPage(new PageAbout(client, formateur, util))
1173 pages.ajouterPage("conditions_utilisation")
1174
1175 pages.afficherPage("minichat")
1176 }
1177 )