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