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