MOD mise en forme de util.js et conf.js, à faire pour tous les autres js
[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 // tout euphorik est contenu dans cet objet
27 var euphorik = {}
28 // ;; euphorik.include =
29 // ;; euphorik.include = function(f) { var s = document.createElement('script'); s.type = 'text/javascript'; s.src = "js/" + f + ".js"; document.getElementsByTagName('head')[0].appendChild(s); }
30
31
32 // version jQuery : function(f) { jQuery.ajax({async : false, url : "js/" + f + ".js", dataType : "script"}) }
33 // mais comme il n'est pas encore chargé...
34 ;; euphorik.include = function(f) {
35 ;; var req, url = 'js/' + f + '.js'
36 ;; if (window.XMLHttpRequest) {
37 ;; req = new XMLHttpRequest(); req.open("GET", url, false /* async */); req.send(null);
38 ;; } else if (window.ActiveXObject) {
39 ;; req = new ActiveXObject((navigator.userAgent.toLowerCase().indexOf('msie 5') != -1) ? "Microsoft.XMLHTTP" : "Msxml2.XMLHTTP");
40 ;; if (req) { req.open("GET", url, false); req.send(); }
41 ;; }
42 ;; if (req!==false) { if (req.status==200) { window.eval(req.responseText); } else if (req.status==404) { alert("erreur de chargement (404) de : " + url) } }
43 ;; }
44
45 ;; euphorik.include("jquery")
46 ;; euphorik.include("jquery.lightbox")
47 ;; euphorik.include("md5")
48 ;; euphorik.include("json2")
49
50 ;; euphorik.include("conf")
51 ;; euphorik.include("util")
52
53 ;; euphorik.include("pageMinichat")
54 ;; euphorik.include("pageAdmin")
55 ;; euphorik.include("pageProfile")
56 ;; euphorik.include("pageRegister")
57 ;; euphorik.include("pageAbout")
58
59
60 // tout un tas d'améliorations des objets javascript ;)
61 /**
62 * Pour chaque propriété de l'objet execute f(p, v) ou p est le nom de la propriété et v sa valeur.
63 * Ne parcours pas les propriétés des prototypes.
64 */
65 object.prototype.each = function(f) {
66 for (var b in boutons) {
67 if (boutons.hasOwnProperty(b)) {
68 f(b, boutons[b])
69 }
70 }
71 }
72
73 String.prototype.trim = function() {
74 return jQuery.trim(this) // anciennement : this.replace(/^\s+|\s+$/g, "");
75 }
76
77 String.prototype.ltrim = function() {
78 return this.replace(/^\s+/, "");
79 }
80
81 String.prototype.rtrim = function() {
82 return this.replace(/\s+$/, "");
83 }
84
85
86 ///////////////////////////////////////////////////////////////////////////////////////////////////
87
88 function Pages()
89 {
90 this.pageCourante = null
91 this.pages = {}
92 }
93
94 /**
95 * Accepte soit un objet soit un string.
96 * un string correspond au nom de la page, par exemple : "page" -> "page.html"
97 */
98 Pages.prototype.ajouterPage = function(page)
99 {
100 if (typeof page == "string")
101 {
102 this.pages[page] = page
103 }
104 else
105 {
106 page.pages = this // la magie des langages dynamiques : le foutoire
107 this.pages[page.nom] = page
108 }
109 }
110
111 Pages.prototype.afficherPage = function(nomPage, forcerChargement)
112 {
113 if (forcerChargement == undefined) forcerChargement = false
114
115 var page = this.pages[nomPage]
116 if (page == undefined || (!forcerChargement && page == this.pageCourante)) return
117
118 if (this.pageCourante != null && this.pageCourante.decharger)
119 this.pageCourante.decharger()
120
121 $("#menu li").removeClass("courante")
122 $("#menu li." + nomPage).addClass("courante")
123
124 this.pageCourante = page
125 var contenu = ""
126 if (typeof page == "string")
127 $.ajax({async: false, url: "pages/" + page + ".html", success : function(page) { contenu += page }})
128 else
129 {
130 contenu += this.pageCourante.contenu()
131 }
132 $("#page").html(contenu).removeClass().addClass(this.pageCourante.nom +
133 (this.pageCourante.classes != undefined ? " " + this.pageCourante.classes() : "") // l'objet peut fournire des classes css supplémentaires sous la forme d'un string
134 )
135
136 if (this.pageCourante.charger)
137 this.pageCourante.charger()
138 }
139
140 ///////////////////////////////////////////////////////////////////////////////////////////////////
141
142 /**
143 * Classe permettant de formater du texte par exemple pour la substitution des liens dans les
144 * message par "[url]".
145 * TODO : améliorer l'efficacité des méthods notamment lié au smiles.
146 */
147 function Formateur()
148 {
149 this.smiles = euphorik.conf.smiles
150 this.protocoles = "http|https|ed2k"
151
152 this.regexUrl = new RegExp("(?:(?:" + this.protocoles + ")://|www\\.)[^ ]*", "gi")
153 this.regexImg = new RegExp("^.*?\\.(gif|jpg|png|jpeg|bmp|tiff)$", "i")
154 this.regexDomaine = new RegExp("^(?:(?:" + this.protocoles + ")://|www\\.).*?([^/.]+\\.[^/.]+)(?:$|/).*$", "i")
155 this.regexTestProtocoleExiste = new RegExp("^(?:" + this.protocoles + ")://.*$", "i")
156 this.regexNomProtocole = new RegExp("^(.*?)://")
157 }
158
159 /**
160 * Formate un pseudo saise par l'utilisateur.
161 * @param pseudo le pseudo brut
162 * @return le pseudo filtré
163 */
164 Formateur.prototype.filtrerInputPseudo = function(pseudo)
165 {
166 return pseudo.replace(/{|}/g, "").trim()
167 }
168
169 Formateur.prototype.getSmilesHTML = function()
170 {
171 var XHTML = ""
172 for (var sNom in this.smiles)
173 {
174 XHTML += "<img class=\"" + sNom + "\" src=\"img/smileys/" + sNom + ".gif\" alt =\"" + sNom + "\" />"
175 }
176 return XHTML
177 }
178
179 /**
180 * Formatage complet d'un texte.
181 * @M le message
182 * @pseudo facultatif, permet de contruire le label des images sous la forme : "<Pseudo> : <Message>"
183 */
184 Formateur.prototype.traitementComplet = function(M, pseudo)
185 {
186 return this.traiterLiensConv(this.traiterSmiles(this.traiterURL(this.traiterWikiSyntaxe(this.remplacerBalisesHTML(M)), pseudo)))
187 }
188
189 /**
190 * Transforme les liens en entités clickables.
191 * Un lien vers une conversation permet d'ouvrire celle ci, elle se marque comme ceci dans un message :
192 * "{5F}" ou 5F est la racine de la conversation.
193 * Ce lien sera transformer en <span class="lienConv">{5F}</span> pouvant être clické pour créer la conv 5F.
194 */
195 Formateur.prototype.traiterLiensConv = function(M)
196 {
197 return M.replace(
198 /\{\w+\}/g,
199 function(lien)
200 {
201 return "<span class=\"lienConv\">" + lien + "</span>"
202 }
203 )
204 }
205
206 /**
207 * FIXME : Cette méthode est attrocement lourde ! A optimiser.
208 * moyenne sur échantillon : 234ms
209 */
210 Formateur.prototype.traiterSmiles = function(M)
211 {
212 for (var sNom in this.smiles)
213 {
214 var ss = this.smiles[sNom]
215 for (var i = 0; i < ss.length; i++)
216 M = M.replace(ss[i], "<img src=\"img/smileys/" + sNom + ".gif\" alt =\"" + sNom + "\" />")
217 }
218 return M
219 }
220
221 Formateur.prototype.remplacerBalisesHTML = function(M)
222 {
223 return M.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;")
224 }
225
226 Formateur.prototype.traiterURL = function(M, pseudo)
227 {
228 var thisFormateur = this
229
230 var traitementUrl = function(url)
231 {
232 // si ya pas de protocole on rajoute "http://"
233 if (!thisFormateur.regexTestProtocoleExiste.test(url))
234 url = "http://" + url
235 var extension = thisFormateur.getShort(url)
236 return "<a " + (extension[1] ? "title=\"" + (pseudo == undefined ? "" : thisFormateur.traiterPourFenetreLightBox(pseudo, url) + ": ") + thisFormateur.traiterPourFenetreLightBox(M, url) + "\"" + " rel=\"lightbox\"" : "") + " href=\"" + url + "\" >[" + extension[0] + "]</a>"
237 }
238 return M.replace(this.regexUrl, traitementUrl)
239 }
240
241 /**
242 * Formatage en utilisant un sous-ensemble des règles de mediwiki.
243 * par exemple ''italic'' devient <i>italic</i>
244 */
245 Formateur.prototype.traiterWikiSyntaxe = function(M)
246 {
247 return M.replace(
248 /'''(.*?)'''/g,
249 function(texte, capture)
250 {
251 return "<b>" + capture + "</b>"
252 }
253 ).replace(
254 /''(.*?)''/g,
255 function(texte, capture)
256 {
257 return "<i>" + capture + "</i>"
258 }
259 )
260 }
261
262 /**
263 * Renvoie une version courte de l'url.
264 * par exemple : http://en.wikipedia.org/wiki/Yakov_Smirnoff devient wikipedia.org
265 */
266 Formateur.prototype.getShort = function(url)
267 {
268 var estUneImage = false
269 var versionShort = null
270 var rechercheImg = this.regexImg.exec(url)
271
272 if (rechercheImg != null)
273 {
274 versionShort = rechercheImg[1].toLowerCase()
275 if (versionShort == "jpeg") versionShort = "jpg" // jpeg -> jpg
276 estUneImage = true
277 }
278 else
279 {
280 var rechercheDomaine = this.regexDomaine.exec(url)
281 if (rechercheDomaine != null && rechercheDomaine.length >= 2)
282 versionShort = rechercheDomaine[1]
283 else
284 {
285 var nomProtocole = this.regexNomProtocole.exec(url)
286 if (nomProtocole != null && nomProtocole.length >= 2)
287 versionShort = nomProtocole[1]
288 }
289 }
290
291 return [versionShort == null ? "url" : versionShort, estUneImage]
292 }
293
294 /**
295 * Traite les pseudo et messages à être affiché dans le titre d'une image visualisé avec lightbox.
296 */
297 Formateur.prototype.traiterPourFenetreLightBox = function(M, urlCourante)
298 {
299 var thisFormateur = this
300 var traitementUrl = function(url)
301 {
302 return "[" + thisFormateur.getShort(url)[0] + (urlCourante == url ? "*" : "") + "]"
303 }
304
305 return this.remplacerBalisesHTML(M).replace(this.regexUrl, traitementUrl)
306 }
307
308
309 ///////////////////////////////////////////////////////////////////////////////////////////////////
310
311 // les statuts possibes du client
312 var statutType = {
313 // mode enregistré, peut poster des messages et modifier son profile
314 auth_registered : 0,
315 // mode identifié, peut poster des messages mais n'a pas accès au profile
316 auth_not_registered : 1,
317 // mode déconnecté, ne peut pas poster de message
318 deconnected : 2
319 }
320
321 function Client(util)
322 {
323 this.util = util
324
325 this.cookie = null
326 this.regexCookie = new RegExp("^cookie=([^;]*)")
327
328 // données personnels
329 this.resetDonneesPersonnelles()
330
331 this.setStatut(statutType.deconnected)
332
333 // si true alors chaque modification du client est mémorisé sur le serveur
334 this.autoflush = $.browser["opera"]
335 }
336
337 Client.prototype.resetDonneesPersonnelles = function()
338 {
339 this.id = 0
340 this.pseudo = euphorik.conf.pseudoDefaut
341 this.login = ""
342 this.password = ""
343 this.email = ""
344 this.css = $("link#cssPrincipale").attr("href")
345 this.chatOrder = "reverse"
346 this.nickFormat = "nick"
347 this.viewTimes = true
348 this.viewTooltips = true
349 this.cookie = undefined
350
351 this.pagePrincipale = 1
352 this.ekMaster = false
353 this.ostentatiousMaster = "light"
354
355 // les conversations, une conversation est un objet possédant les attributs suivants :
356 // - root (entier)
357 // - page (entier)
358 // - reduit (bool)
359 this.conversations = []
360 }
361
362 Client.prototype.setCss = function(css)
363 {
364 if (this.css == css || css == "")
365 return
366
367 this.css = css
368 $("link#cssPrincipale").attr("href", this.css)
369 if (this.autoflush) this.flush(true)
370 }
371
372 Client.prototype.pageSuivante = function(numConv)
373 {
374 if (numConv < 0 && this.pagePrincipale > 1)
375 this.pagePrincipale -= 1
376 else if (this.conversations[numConv].page > 1)
377 this.conversations[numConv].page -= 1
378 }
379
380 Client.prototype.pagePrecedente = function(numConv)
381 {
382 if (numConv < 0)
383 this.pagePrincipale += 1
384 else
385 this.conversations[numConv].page += 1
386 }
387
388 /**
389 * Définit la première page pour la conversation donnée.
390 * @return true si la page a changé sinon false
391 */
392 Client.prototype.goPremierePage = function(numConv)
393 {
394 if (numConv < 0)
395 {
396 if (this.pagePrincipale == 1)
397 return false
398 this.pagePrincipale = 1
399 }
400 else
401 {
402 if (this.conversations[numConv].page == 1)
403 return false
404 this.conversations[numConv].page = 1
405 }
406 return true
407 }
408
409 /**
410 * Ajoute une conversation à la vue de l'utilisateur.
411 * Le profile de l'utilisateur est directement sauvegardé sur le serveur.
412 * @param racines la racine de la conversation (integer)
413 * @return true si la conversation a été créée sinon false (par exemple si la conv existe déjà)
414 */
415 Client.prototype.ajouterConversation = function(racine)
416 {
417 // vérification s'il elle n'existe pas déjà
418 for (var i = 0; i < this.conversations.length; i++)
419 if (this.conversations[i].root == racine)
420 return false
421
422 this.conversations.push({root : racine, page : 1, reduit : false})
423 if (this.autoflush) this.flush(true)
424
425 return true
426 }
427
428 Client.prototype.supprimerConversation = function(num)
429 {
430 if (num < 0 || num >= this.conversations.length) return
431
432 // décalage TODO : supprimer le dernier élément
433 for (var i = num; i < this.conversations.length - 1; i++)
434 this.conversations[i] = this.conversations[i+1]
435 this.conversations.pop()
436
437 if (this.autoflush) this.flush(true)
438 }
439
440 Client.prototype.getJSONLogin = function(login, password)
441 {
442 return {
443 "header" : { "action" : "authentification", "version" : euphorik.conf.versionProtocole },
444 "login" : login,
445 "password" : password
446 }
447 }
448
449 Client.prototype.getJSONLoginCookie = function()
450 {
451 return {
452 "header" : { "action" : "authentification", "version" : euphorik.conf.versionProtocole },
453 "cookie" : this.cookie
454 }
455 }
456
457 /**
458 * le couple (login, password) est facultatif. S'il n'est pas fournit alors il ne sera pas possible
459 * de s'autentifier avec (login, password).
460 */
461 Client.prototype.getJSONEnregistrement = function(login, password)
462 {
463 var mess = { "header" : { "action" : "register", "version" : euphorik.conf.versionProtocole }}
464
465 if (login != undefined && password != undefined)
466 {
467 mess["login"] = login
468 mess["password"] = password
469 }
470
471 return mess;
472 }
473
474 Client.prototype.getJSONConversations = function()
475 {
476 var conversations = new Array()
477 for (var i = 0; i < this.conversations.length; i++)
478 conversations.push({root : this.conversations[i].root, minimized : this.conversations[i].reduit})
479 return conversations
480 }
481
482 Client.prototype.getJSONProfile = function()
483 {
484 return {
485 "header" : { "action" : "set_profile", "version" : euphorik.conf.versionProtocole },
486 "cookie" : this.cookie,
487 "login" : this.login,
488 "password" : this.password,
489 "nick" : this.pseudo,
490 "email" : this.email,
491 "css" : this.css,
492 "chat_order" : this.chatOrder,
493 "nick_format" : this.nickFormat,
494 "view_times" : this.viewTimes,
495 "view_tooltips" : this.viewTooltips,
496 "conversations" : this.getJSONConversations(),
497 "ostentatious_master" : this.ostentatiousMaster
498 }
499 }
500
501 /**
502 * Renvoie null si pas définit.
503 */
504 Client.prototype.getCookie = function()
505 {
506 var cookie = this.regexCookie.exec(document.cookie)
507 if (cookie == null) this.cookie = null
508 else this.cookie = cookie[1]
509 }
510
511 Client.prototype.delCookie = function()
512 {
513 document.cookie = "cookie=; max-age=0"
514 }
515
516 Client.prototype.setCookie = function()
517 {
518 if (this.cookie == null || this.cookie == undefined)
519 return
520
521 // ne fonctionne pas sous IE....
522 /*document.cookie = "cookie=" + this.cookie + "; max-age=" + (60 * 60 * 24 * 365) */
523
524 document.cookie =
525 "cookie="+this.cookie+"; expires=" + new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 365).toUTCString()
526 }
527
528 Client.prototype.authentifie = function()
529 {
530 return this.statut == statutType.auth_registered || this.statut == statutType.auth_not_registered
531 }
532
533 Client.prototype.setStatut = function(statut)
534 {
535 // conversation en "enum" si en "string"
536 if (typeof(statut) == "string")
537 {
538 statut =
539 statut == "auth_registered" ?
540 statutType.auth_registered :
541 (statut == "auth_not_registered" ? statutType.auth_not_registered : statutType.deconnected)
542 }
543
544 if (statut == this.statut) return
545
546 this.statut = statut
547 this.majMenu()
548 this.majLogo()
549 }
550
551 /**
552 * Effectue la connexion vers le serveur.
553 * Cette fonction est bloquante tant que la connexion n'a pas été établie.
554 * S'il existe un cookie en local on s'authentifie directement avec lui.
555 * Si il n'est pas possible de s'authentifier alors on affiche un captcha anti-bot.
556 */
557 Client.prototype.connexionCookie = function()
558 {
559 this.getCookie()
560 if (this.cookie == null) return false;
561 return this.connexion(this.getJSONLoginCookie())
562 }
563
564 Client.prototype.connexionLogin = function(login, password)
565 {
566 return this.connexion(this.getJSONLogin(login, password))
567 }
568
569 Client.prototype.enregistrement = function(login, password)
570 {
571 if (this.authentifie())
572 {
573 this.login = login
574 this.password = password
575 if(this.flush())
576 {
577 this.setStatut(statutType.auth_registered)
578 return true
579 }
580 return false
581 }
582 else
583 {
584 return this.connexion(this.getJSONEnregistrement(login, password))
585 }
586 }
587
588 /**
589 * Connexion. Réalisé de manière synchrone.
590 */
591 Client.prototype.connexion = function(messageJson)
592 {
593 var thisClient = this
594 jQuery.ajax(
595 {
596 async: false,
597 type: "POST",
598 url: "request",
599 dataType: "json",
600 data: this.util.jsonVersAction(messageJson),
601 success:
602 function(data)
603 {
604 if (data["reply"] == "error")
605 {
606 thisClient.util.messageDialogue(data["error_message"])
607 // suppression du cookie actuel, cas où le cookie du client ne permet pas une authentification
608 thisClient.delCookie()
609 }
610 else
611 thisClient.chargerDonnees(data)
612 }
613 }
614 )
615 return this.authentifie()
616 }
617
618 Client.prototype.deconnexion = function()
619 {
620 this.flush(true)
621 this.delCookie()
622 this.resetDonneesPersonnelles()
623 this.setStatut(statutType.deconnected) // deconnexion
624 }
625
626 Client.prototype.chargerDonnees = function(data)
627 {
628 // la modification du statut qui suit met à jour le menu, le menu dépend (page admin)
629 // de l'état ekMaster
630 this.ekMaster = data["ek_master"] != undefined ? data["ek_master"] : false
631
632 this.setStatut(data["status"])
633
634 if (this.authentifie())
635 {
636 this.cookie = data["cookie"]
637 this.setCookie()
638
639 this.id = data["id"]
640 this.login = data["login"]
641 this.pseudo = data["nick"]
642 this.email = data["email"]
643 this.setCss(data["css"])
644 this.chatOrder = data["chat_order"]
645 this.nickFormat = data["nick_format"]
646 this.viewTimes = data["view_times"]
647 this.viewTooltips = data["view_tooltips"]
648 this.ostentatiousMaster = data["ostentatious_master"]
649
650 // la page de la conversation principale
651 this.pagePrincipale = 1
652
653 // les conversations
654 this.conversations = data["conversations"]
655 for (var i = 0; i < this.conversations.length; i++)
656 this.conversations[i] = {root : this.conversations[i].root, page : 1, reduit : this.conversations[i].minimized}
657
658 this.majBulle()
659 this.majCssSelectionee()
660 //this.majLogo()
661 }
662 }
663
664 /**
665 * Met à jour les données personne sur serveur.
666 * @param async de manière asynchrone ? défaut = true
667 * @return false si le flush n'a pas pû se faire sinon true
668 */
669 Client.prototype.flush = function(async)
670 {
671 if (async == undefined)
672 async = false
673
674 if (!this.authentifie())
675 return false
676
677 var thisClient = this
678 var ok = true
679 jQuery.ajax(
680 {
681 async: async,
682 type: "POST",
683 url: "request",
684 dataType: "json",
685 data: this.util.jsonVersAction(this.getJSONProfile()),
686 success:
687 function(data)
688 {
689 if (data["reply"] == "error")
690 {
691 thisClient.util.messageDialogue(data["error_message"])
692 ok = false
693 }
694 else
695 {
696 thisClient.majBulle()
697 }
698 }
699 }
700 )
701
702 return ok
703 }
704
705 Client.prototype.majMenu = function()
706 {
707 var displayType = "block"
708
709 $("#menu .admin").css("display", this.ekMaster ? displayType : "none")
710
711 // met à jour le menu
712 if (this.statut == statutType.auth_registered)
713 {
714 $("#menu .profile").css("display", displayType).text("profile")
715 $("#menu .logout").css("display", displayType)
716 $("#menu .register").css("display", "none")
717 }
718 else if (this.statut == statutType.auth_not_registered)
719 {
720 $("#menu .profile").css("display", "none")
721 $("#menu .logout").css("display", displayType)
722 $("#menu .register").css("display", displayType)
723 }
724 else
725 {
726 $("#menu .profile").css("display", displayType).text("login")
727 $("#menu .logout").css("display", "none")
728 $("#menu .register").css("display", displayType)
729 }
730 }
731
732 /**
733 * Met à jour l'affichage des infos bulles en fonction du profile.
734 */
735 Client.prototype.majBulle = function()
736 {
737 this.util.bulleActive = this.viewTooltips
738 }
739
740 /**
741 * Met à jour la css sélectionnée, lors du chargement des données.
742 */
743 Client.prototype.majCssSelectionee = function()
744 {
745 // extraction du numéro de la css courante
746 var numCssCourante = this.css.match(/^.*?\/(\d)\/.*$/)
747 if (numCssCourante != null && numCssCourante[1] != undefined)
748 {
749 $("#menuCss option").removeAttr("selected")
750 $("#menuCss option[value=" + numCssCourante[1]+ "]").attr("selected", "selected")
751 }
752 }
753
754 /**
755 * Change la "class" du logo en fonction du statut de ekMaster.
756 */
757 Client.prototype.majLogo = function()
758 {
759 if (this.ekMaster)
760 $("#logo").addClass("ekMaster")
761 else
762 $("#logo").removeClass("ekMaster")
763 }
764
765
766 Client.prototype.slap = function(userId, raison)
767 {
768 var thisClient = this
769
770 jQuery.ajax({
771 type: "POST",
772 url: "request",
773 dataType: "json",
774 data: this.util.jsonVersAction(
775 {
776 "header" : { "action" : "slap", "version" : euphorik.conf.versionProtocole },
777 "cookie" : thisClient.cookie,
778 "user_id" : userId,
779 "reason" : raison
780 }),
781 success:
782 function(data)
783 {
784 if (data["reply"] == "error")
785 thisClient.util.messageDialogue(data["error_message"])
786 }
787 })
788 }
789
790 Client.prototype.ban = function(userId, raison, minutes)
791 {
792 var thisClient = this
793
794 // par défaut un ban correspond à 3 jours
795 if (typeof(minutes) == "undefined")
796 minutes = euphorik.conf.tempsBan;
797
798 jQuery.ajax({
799 type: "POST",
800 url: "request",
801 dataType: "json",
802 data: this.util.jsonVersAction(
803 {
804 "header" : { "action" : "ban", "version" : euphorik.conf.versionProtocole },
805 "cookie" : thisClient.cookie,
806 "duration" : minutes,
807 "user_id" : userId,
808 "reason" : raison
809 }),
810 success:
811 function(data)
812 {
813 if (data["reply"] == "error")
814 thisClient.util.messageDialogue(data["error_message"])
815 }
816 })
817 }
818
819 Client.prototype.kick = function(userId, raison)
820 {
821 this.ban(userId, raison, euphorik.conf.tempsKick)
822 }
823
824 ///////////////////////////////////////////////////////////////////////////////////////////////////
825
826 /**
827 * classe permettant de gérer les événements (push serveur).
828 * l'information envoyé est sous la forme :
829 * {
830 * "header" : {"action" : "wait_event", "version" : <v> },
831 * "page" : <page>
832 * [..]
833 * }
834 * l'information reçu est sous la forme :
835 * {
836 * "reply" : <reply>
837 * }
838 * @page la page
839 */
840 function PageEvent(page, util)
841 {
842 this.page = page
843 this.util = util
844
845 // l'objet JSONHttpRequest représentant la connexion d'attente
846 this.attenteCourante = null
847
848 // le multhreading du pauvre, merci javascript de m'offrire autant de primitives pour la gestion de la concurrence...
849 this.stop = false
850 }
851
852 /**
853 * Arrête l'attente courante s'il y en a une.
854 */
855 PageEvent.prototype.stopAttenteCourante = function()
856 {
857 this.stop = true
858
859 if (this.attenteCourante != null)
860 {
861 this.attenteCourante.abort()
862 }
863 }
864
865 /**
866 * Attend un événement lié à la page.
867 * @funSend une fonction renvoyant les données json à envoyer
868 * @funsReceive est un objet comprenant les fonctions à appeler en fonction du "reply"
869 * les fonctions acceptent un paramètre correspondant au données reçues.
870 * exemple : {"new_message" : function(data){ ... }}
871 */
872 PageEvent.prototype.waitEvent = function(funSend, funsReceive)
873 {
874 this.stopAttenteCourante()
875
876 this.stop = false
877
878 var thisPageEvent = this
879
880 // on doit conserver l'ordre des valeurs de l'objet JSON (le serveur les veut dans l'ordre définit dans le protocole)
881 // TODO : ya pas mieux ?
882 var dataToSend =
883 {
884 "header" : { "action" : "wait_event", "version" : euphorik.conf.versionProtocole },
885 "page" : this.page
886 }
887 var poulpe = funSend()
888 for (var v in poulpe)
889 dataToSend[v] = poulpe[v]
890
891 this.attenteCourante = jQuery.ajax({
892 type: "POST",
893 url: "request",
894 dataType: "json",
895 // TODO : doit disparaitre
896 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
897 data: this.util.jsonVersAction(dataToSend),
898 success:
899 function(data)
900 {
901 funsReceive[data["reply"]](data)
902
903 // rappel de la fonction dans 100 ms
904 setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funsReceive) }, 100)
905 },
906 error:
907 function(XMLHttpRequest, textStatus, errorThrown)
908 {
909 ;; console.log("Connexion perdue dans waitEvent")
910 setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funsReceive) }, 1000)
911 }
912 })
913 }
914
915 /**
916 * Si un stopAttenteCourante survient un peu n'importe quand il faut imédiatement arreter de boucler.
917 */
918 PageEvent.prototype.waitEvent2 = function(funSend, funsReceive)
919 {
920 if (this.stop)
921 return
922 this.waitEvent(funSend, funsReceive)
923 }
924
925 ///////////////////////////////////////////////////////////////////////////////////////////////////
926
927
928 // le main
929 $(document).ready(
930 function()
931 {
932 var formateur = new Formateur()
933 var util = new euphorik.Util(formateur)
934 var client = new Client(util)
935 var pages = new Pages()
936
937 // connexion vers le serveur (utilise un cookie qui traine)
938 client.connexionCookie()
939
940 $("#menuCss").change(function(){ client.setCss("styles/" + $("option:selected", this).attr("value") + "/euphorik.css")})
941
942 // FIXME : ne fonctionne pas sous opera
943 // voir : http://dev.jquery.com/ticket/2892#preview
944 $(window).unload(function(){client.flush()})
945
946 $("#menu .minichat").click(function(){ pages.afficherPage("minichat") })
947 $("#menu .admin").click(function(){ pages.afficherPage("admin") })
948 $("#menu .profile").click(function(){ pages.afficherPage("profile") })
949 $("#menu .logout").click(function(){
950 util.messageDialogue("Êtes-vous sur de vouloir vous délogger ?", euphorik.Util.messageType.question,
951 {"Oui" : function()
952 {
953 client.deconnexion();
954 pages.afficherPage("minichat", true)
955 },
956 "Non" : function(){}
957 }
958 )
959 })
960 $("#menu .register").click(function(){ pages.afficherPage("register") })
961 $("#menu .about").click(function(){ pages.afficherPage("about") })
962
963 // TODO : simplifier et pouvoir créer des liens par exemple : <span class="lien" href="conditions">Conditions d'utilisation</span>
964 $("#footer .conditions").click(function(){ pages.afficherPage("conditions_utilisation") })
965
966 pages.ajouterPage(new PageMinichat(client, formateur, util))
967 pages.ajouterPage(new PageAdmin(client, formateur, util))
968 pages.ajouterPage(new PageProfile(client, formateur, util))
969 pages.ajouterPage(new PageRegister(client, formateur, util))
970 pages.ajouterPage(new PageAbout(client, formateur, util))
971 pages.ajouterPage("conditions_utilisation")
972
973 pages.afficherPage("minichat")
974 }
975 )