MOD on ne peut plus avec le nick par défaut : <nick>
[euphorik.git] / js / euphorik.js
index a20ee37..b2024ad 100755 (executable)
-// coding: utf-8\r
-\r
-/**\r
-  * Contient la base javascript pour le site euphorik.ch.\r
+// coding: utf-8
+// Copyright 2008 Grégory Burri
+//
+// This file is part of Euphorik.
+//
+// Euphorik is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Euphorik is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Euphorik.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+  * Contient la base javascript pour le site euphorik.ch.
   * Chaque page possède son propre fichier js nommé "page<nom de la page>.js".
   * Auteur : GBurri
   * Date : 6.11.2007
   */
+  
 
 /**
-  * La configuration.\r
+  * La configuration.
   * Normalement 'const' à la place de 'var' mais non supporté par IE7.
-  */\r
-var conf = {\r
-   nbMessageAffiche : 80, // (par page)
-   pseudoDefaut : "<nick>",\r
-   tempsAffichageMessageDialogue : 4000, // en ms\r
-   smiles : {   \r
-      "smile" : [/:\)/g, /:-\)/g],  \r
-      "bigsmile" : [/:D/g, /:-D/g],\r
-      "clin" : [/;\)/g, /;-\)/g],\r
-      "cool" : [/8\)/g, /8-\)/g],\r
-      "eheheh" : [/:P/g, /:-P/g],\r
-      "oh" : [/:o/g, /:O/g],\r
-      "pascontent" : [/>\(/g, /&gt;\(/g],\r
-      "sniff" : [/:\(/g, /:-\(/g],\r
-      "argn" : [/\[:argn\]/g],\r
-      "bunny" : [/\[:lapin\]/g],\r
-      "chat" : [/\[:chat\]/g],\r
-      "renne" : [/\[:renne\]/g],\r
-      "lol" : [/\[:lol\]/g],\r
-      "spliff" : [/\[:spliff\]/g],\r
-      "star" : [/\[:star\]/g],\r
-      "triste" : [/\[:triste\]/g],\r
-      "kirby" : [/\[:kirby\]/g]\r
-   }\r
-}\r
+  */
+var conf = {
+   nickDefaut : "<nick>",
+   nbMessageAffiche : 40, // (par page)
+   pseudoDefaut : "<nick>",
+   tempsAffichageMessageDialogue : 4000, // en ms
+   tempsKick : 15, // en minute
+   tempsBan : 60 * 24 * 3, // en minutes (3jours)
+   smiles : {   
+      "smile" : [/:\)/g, /:-\)/g],  
+      "bigsmile" : [/:D/g, /:-D/g],
+      "clin" : [/;\)/g, /;-\)/g],
+      "cool" : [/8\)/g, /8-\)/g],
+      "eheheh" : [/:P/g, /:-P/g],
+      "lol" : [/\[-lol\]/g],
+      "spliff" : [/\[-spliff\]/g],
+      "oh" : [/:o/g, /:O/g],
+      "heink" : [/\[-heink\]/g],
+      "hum" : [/\[-hum\]/g],
+      "boh" : [/\[-boh\]/g],
+      "sniff" : [/:\(/g, /:-\(/g],
+      "triste" : [/\[-triste\]/g],
+      "pascontent" : [/>\(/g, /&gt;\(/g],
+      "argn" : [/\[-argn\]/g],
+      "redface" : [/\[-redface\]/g],
+      "bunny" : [/\[-lapin\]/g],
+      "chat" : [/\[-chat\]/g],
+      "renne" : [/\[-renne\]/g],
+      "star" : [/\[-star\]/g],
+      "kirby" : [/\[-kirby\]/g],
+      "slurp" : [/\[-slurp\]/g],
+      "agreed" : [/\[-agreed\]/g],
+      "dodo" : [/\[-dodo\]/g],
+      "bn" : [/\[-bn\]/g]
+   }
+}
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-\r
-String.prototype.trim = function()\r
-{\r
-       return this.replace(/^\s+|\s+$/g, "");\r
-}\r
-\r
-String.prototype.ltrim = function()\r
-{\r
-       return this.replace(/^\s+/, "");\r
-}\r
-\r
-String.prototype.rtrim = function()\r
-{\r
-       return this.replace(/\s+$/, "");\r
-}\r
-\r
-String.prototype.dump = function()\r
-{\r
-   if (typeof dump != "undefined")\r
-   {\r
-      dump("\n--- EUPHORIK.CH ---\n")\r
-      dump(this)\r
-      dump("\n------\n")\r
-   }\r
-}\r
-\r
-///////////////////////////////////////////////////////////////////////////////////////////////////\r
-\r
-/**\r
-  * Cette classe regroupe des fonctions utilitaires (helpers).\r
+
+String.prototype.trim = function()
+{
+       return jQuery.trim(this) // anciennement : this.replace(/^\s+|\s+$/g, "");
+}
+
+String.prototype.ltrim = function()
+{
+       return this.replace(/^\s+/, "");
+}
+
+String.prototype.rtrim = function()
+{
+       return this.replace(/\s+$/, "");
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+  * Cette classe regroupe des fonctions utilitaires (helpers).
+  * @formateur est permet de formater les messages affichés à l'aide de messageDialogue (facultatif)
   */
-function Util()
-{\r
-   if(typeof XMLSerializer != "undefined")
-      this.serializer = new XMLSerializer()
-      
-   jQuery("#info .fermer").click(function(){
-      jQuery("#info").slideUp(50) 
+function Util(formateur)
+{
+   $("#info .fermer").click(function(){
+      $("#info").slideUp(50) 
    })
+   
+   $("body").append('<div id="flecheBulle"></div>').append('<div id="messageBulle"><p></p></div>')
+   
+   this.formateur = formateur
+   this.bulleActive = true
 }
 
+var messageType = {informatif: 0, question: 1, erreur: 2}
+
 /**
   * Affiche une boite de dialogue avec un message à l'intérieur.
-  * @param message le message (string)\r
-  * @param type voir 'messageType'. par défaut messageType.informatif\r
-  * @param les boutons sous la forme d'un objet ou les clefs sont les labels des boutons\r
+  * @param message le message (string)
+  * @param type voir 'messageType'. par défaut messageType.informatif
+  * @param les boutons sous la forme d'un objet ou les clefs sont les labels des boutons
   *        et les valeurs les fonctions executées lorsqu'un bouton est activé.
+  * @param formate faut-il formaté le message ? true par défaut
   */
-Util.prototype.messageDialogue = function(message, type, boutons)
+Util.prototype.messageDialogue = function(message, type, boutons, formate)
 {
+   var thisUtil = this
+
    if (type == undefined)
       type = messageType.informatif
+      
+   if (formate == undefined)
+      formate = true
 
    if (this.timeoutMessageDialogue != undefined)
       clearTimeout(this.timeoutMessageDialogue)
       
-   var fermer = function(){jQuery("#info").slideUp(100)}
+   var fermer = function(){$("#info").slideUp(100)}
    fermer()   
    
-   jQuery("#info .message").html(message)
+   $("#info .message").html(thisUtil.formateur == undefined || !formate ? message : thisUtil.formateur.traitementComplet(message))
    switch(type)
    {
-      case messageType.informatif : jQuery("#info #icone").attr("class", "information"); break
-      case messageType.question : jQuery("#info #icone").attr("class", "interrogation"); break
-      case messageType.erreur : jQuery("#info #icone").attr("class", "exclamation"); break
+      case messageType.informatif : $("#info #icone").attr("class", "information"); break
+      case messageType.question : $("#info #icone").attr("class", "interrogation"); break
+      case messageType.erreur : $("#info #icone").attr("class", "exclamation"); break
    }   
-   jQuery("#info .boutons").html("")
+   $("#info .boutons").html("")
    for (var b in boutons)
-      jQuery("#info .boutons").append("<div>" + b + "</div>").find("div:last").click(boutons[b]).click(fermer)
+      $("#info .boutons").append("<div>" + b + "</div>").find("div:last").click(boutons[b]).click(fermer)
    
-   jQuery("#info").slideDown(200)
+   $("#info").slideDown(200)
    this.timeoutMessageDialogue = setTimeout(fermer, conf.tempsAffichageMessageDialogue)   
 }
-var messageType = {informatif: 0, question: 1, erreur: 2}
-\r
-/**\r
-  * Transforme un document XML en string.\r
-  */\r
-Util.prototype.serializeXML = function(documentXML)\r
-{\r
-   if (this.serializer)\r
-      return this.serializer.serializeToString(documentXML)\r
-   else\r
-      return documentXML.xml\r
-}\r
-
-Util.prototype.creerDocumentXMLAction = function()
-{\r
-   if (document.implementation && document.implementation.createDocument)\r
-   {\r
-      // var doc = document.implementation.createDocument("", "action", null)
-      var parser = new DOMParser();
-      var doc =  parser.parseFromString("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<action/>", "text/xml")
-      //alert(this.serializeXML(doc))
-      return doc\r
-   }\r
-   else if (window.ActiveXObject)\r
-   {\r
-      var doc = new ActiveXObject("MSXML2.DOMDocument") //("Microsoft.XMLDOM")\r
-      doc.appendChild(doc.createElement("action"));\r
-      //doc.loadXML("<action></action>")\r
-      //alert(doc.documentElement)\r
-      //doc.createElement("action")\r
-      return doc\r
-   }   
+
+/**
+  * Affiche un info bulle lorsque le curseur survole l'élément donné.
+  * FIXME : le width de element ne tient pas compte du padding !?
+  */
+Util.prototype.infoBulle = function(message, element)
+{
+   var thisUtil = this
+
+   var cacherBulle = function()
+      {   
+         $("#flecheBulle").hide()
+         $("#messageBulle").hide()
+      }
+
+   element.hover(
+      function(e)
+      {
+         if (!thisUtil.bulleActive)
+            return
+                  
+         var m = $("#messageBulle")
+         var f = $("#flecheBulle")
+         
+         // remplie le paragraphe de la bulle avec le message
+         $("p", m).html(message)
+         
+         // réinitialise la position, évite le cas ou la boite est collé à droite et remplie avec un texte la faisant dépassé
+         // dans ce cas la hauteur n'est pas calculé correctement
+         m.css("top", 0).css("left", 0)
+         
+         var positionFleche = {
+            left : element.offset().left + element.width() / 2 - f.width() / 2,
+            top : element.offset().top - f.height()
+         }
+         var positionMessage = {
+            left : element.offset().left + element.width() / 2 - m.width() / 2,
+            top : element.offset().top - f.height() - m.height()
+         }
+         var depassementDroit = (positionMessage.left + m.width()) - $("body").width()
+         if (depassementDroit > 0)
+            positionMessage.left -= depassementDroit
+         else
+         {
+            if (positionMessage.left < 0)
+               positionMessage.left = 0
+         }
+         
+         m.css("top", positionMessage.top).css("left", positionMessage.left).show()
+         f.css("top", positionFleche.top).css("left", positionFleche.left).show()
+      },
+      cacherBulle
+   ).click(cacherBulle)
 }
 
-Util.prototype.xmlVersAction = function(xml)
+/**
+  * Utilisé pour l'envoie de donnée avec la méthode ajax de jQuery.
+  */
+Util.prototype.jsonVersAction = function(json)
 {
-   //return {action: this.to_utf8(this.serializeXML(xml /*, "UTF-8"*/))}
-   return {action: this.serializeXML(xml)}
+   return {action : JSON.stringify(json) }
 }
-\r
-Util.prototype.md5 = function(chaine)\r
-{\r
-   return hex_md5(chaine)\r
+
+Util.prototype.md5 = function(chaine)
+{
+   return hex_md5(chaine)
 }
-\r
-// pompé de http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130\r
+
+// pompé de http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130
 Util.prototype.setSelectionRange = function(input, selectionStart, selectionEnd)
-{\r
+{
    if (input.setSelectionRange)
-   {\r
-      input.focus()\r
-      input.setSelectionRange(selectionStart, selectionEnd)\r
-   }\r
+   {
+      input.focus()
+      input.setSelectionRange(selectionStart, selectionEnd)
+   }
    else if (input.createTextRange)
-   {\r
-      var range = input.createTextRange()\r
-      range.collapse(true)\r
-      range.moveEnd('character', selectionEnd)\r
-      range.moveStart('character', selectionStart)\r
-      range.select()\r
-   }\r
-}
-\r
+   {
+      var range = input.createTextRange()
+      range.collapse(true)
+      range.moveEnd('character', selectionEnd)
+      range.moveStart('character', selectionStart)
+      range.select()
+   }
+}
+
 Util.prototype.setCaretToEnd = function(input)
-{\r
-   this.setSelectionRange(input, input.value.length, input.value.length)\r
-}\r
+{
+   this.setSelectionRange(input, input.value.length, input.value.length)
+}
 Util.prototype.setCaretToBegin = function(input)
-{\r
-   this.setSelectionRange(input, 0, 0)\r
-}\r
+{
+   this.setSelectionRange(input, 0, 0)
+}
 Util.prototype.setCaretToPos = function(input, pos)
-{\r
-   this.setSelectionRange(input, pos, pos)\r
-}\r
+{
+   this.setSelectionRange(input, pos, pos)
+}
 Util.prototype.selectString = function(input, string)
-{\r
-   var match = new RegExp(string, "i").exec(input.value)\r
+{
+   var match = new RegExp(string, "i").exec(input.value)
    if (match)
-   {\r
-      this.setSelectionRange (input, match.index, match.index + match[0].length)\r
-   }\r
-}\r
-Util.prototype.replaceSelection = function(input, replaceString) {\r
+   {
+      this.setSelectionRange (input, match.index, match.index + match[0].length)
+   }
+}
+Util.prototype.replaceSelection = function(input, replaceString) {
    if (input.setSelectionRange)
-   {\r
-      var selectionStart = input.selectionStart\r
-      var selectionEnd = input.selectionEnd\r
+   {
+      var selectionStart = input.selectionStart
+      var selectionEnd = input.selectionEnd
       input.value = input.value.substring(0, selectionStart) + replaceString + input.value.substring(selectionEnd)
-      \r
-      if (selectionStart != selectionEnd) // has there been a selection\r
-         this.setSelectionRange(input, selectionStart, selectionStart + replaceString.length)\r
-      else // set caret\r
-         this.setCaretToPos(input, selectionStart + replaceString.length)\r
-   }\r
+      
+      if (selectionStart != selectionEnd) // has there been a selection
+         this.setSelectionRange(input, selectionStart, selectionStart + replaceString.length)
+      else // set caret
+         this.setCaretToPos(input, selectionStart + replaceString.length)
+   }
    else if (document.selection)
-   {\r
-      var range = document.selection.createRange();\r
+   {
+      input.focus()
+      var range = document.selection.createRange()
       if (range.parentElement() == input)
-      {\r
-         var isCollapsed = range.text == ''\r
-         range.text = replaceString\r
+      {
+         var isCollapsed = range.text == ''
+         range.text = replaceString
          if (!isCollapsed)
          {
-            // there has been a selection\r
-            // it appears range.select() should select the newly \r
-            // inserted text but that fails with IE\r
-            range.moveStart('character', -replaceString.length);\r
-            range.select();\r
-         }\r
-      }\r
-   }\r
+            range.moveStart('character', -replaceString.length);
+         }
+      }
+   }
+}
+
+Util.prototype.rot13 = function(chaine)
+{
+   var ACode = 'A'.charCodeAt(0)
+   var aCode = 'a'.charCodeAt(0)
+   var MCode = 'M'.charCodeAt(0)
+   var mCode = 'm'.charCodeAt(0)
+   var ZCode = 'Z'.charCodeAt(0)
+   var zCode = 'z'.charCodeAt(0)
+
+   var f = function(ch, pos) {
+      if (pos == ch.length)
+         return ""
+      
+      var c = ch.charCodeAt(pos);
+      return String.fromCharCode(
+         c +
+         (c >= ACode && c <= MCode || c >= aCode && c <= mCode ? 13 :
+         (c > MCode && c <= ZCode || c > mCode && c <= zCode ? -13 : 0))
+      ) + f(ch, pos + 1)
+   }
+   return f(chaine, 0)
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -232,15 +304,26 @@ function Pages()
    this.pages = {}
 }
 
+/**
+  * Accepte soit un objet soit un string.
+  * un string correspond au nom de la page, par exemple : "page" -> "page.html"
+  */
 Pages.prototype.ajouterPage = function(page)
 {
-   page.pages = this // la magie des langages dynamiques : le foutoire
-   this.pages[page.nom] = page
+   if (typeof page == "string")
+   {
+      this.pages[page] = page
+   }
+   else
+   {
+      page.pages = this // la magie des langages dynamiques : le foutoire
+      this.pages[page.nom] = page
+   }
 }
 
 Pages.prototype.afficherPage = function(nomPage, forcerChargement)
-{\r
-   if (forcerChargement == undefined) forcerChargement = false\r
+{
+   if (forcerChargement == undefined) forcerChargement = false
 
    var page = this.pages[nomPage]
    if (page == undefined || (!forcerChargement && page == this.pageCourante)) return
@@ -248,11 +331,16 @@ Pages.prototype.afficherPage = function(nomPage, forcerChargement)
    if (this.pageCourante != null && this.pageCourante.decharger)
       this.pageCourante.decharger()
   
-   jQuery("#menu div").removeClass("courante")
-   jQuery("#menu div." + nomPage).addClass("courante")
+   $("#menu li").removeClass("courante")
+   $("#menu li." + nomPage).addClass("courante")
       
    this.pageCourante = page
-   jQuery("#page").html(this.pageCourante.contenu()).removeClass().addClass(this.pageCourante.nom)
+   var contenu = ""
+   if (typeof page == "string")
+      $.ajax({async: false, url: "pages/" + page + ".html", success : function(page) { contenu += page }})
+   else
+      contenu += this.pageCourante.contenu()
+   $("#page").html(contenu).removeClass().addClass(this.pageCourante.nom)
    
    if (this.pageCourante.charger)
       this.pageCourante.charger()
@@ -260,15 +348,20 @@ Pages.prototype.afficherPage = function(nomPage, forcerChargement)
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+/**
+  * Classe permettant de formater du texte par exemple pour la substitution des liens dans les
+  * message par "[url]".
+  * TODO : améliorer l'efficacité des méthods notamment lié au smiles.
+  */
 function Formateur()
 {
-   this.smiles = conf.smiles\r
-   this.protocoles = "http|https|ed2k"\r
-   \r
-   this.regexUrl = new RegExp("(?:(?:" + this.protocoles + ")://|www\\.)[^ ]*", "gi")\r
-   this.regexImg = new RegExp("^.*?\\.(gif|jpg|png|jpeg|bmp|tiff)$", "i")\r
-   this.regexDomaine = new RegExp("^(?:(?:" + this.protocoles + ")://|www\\.).*?([^/.]+\\.[^/.]+)(?:$|/).*$", "i")\r
-   this.regexTestProtocoleExiste = new RegExp("^(?:" + this.protocoles + ")://.*$", "i")\r
+   this.smiles = conf.smiles
+   this.protocoles = "http|https|ed2k"
+   
+   this.regexUrl = new RegExp("(?:(?:" + this.protocoles + ")://|www\\.)[^ ]*", "gi")
+   this.regexImg = new RegExp("^.*?\\.(gif|jpg|png|jpeg|bmp|tiff)$", "i")
+   this.regexDomaine = new RegExp("^(?:(?:" + this.protocoles + ")://|www\\.).*?([^/.]+\\.[^/.]+)(?:$|/).*$", "i")
+   this.regexTestProtocoleExiste = new RegExp("^(?:" + this.protocoles + ")://.*$", "i")
    this.regexNomProtocole = new RegExp("^(.*?)://")
 }
 
@@ -281,24 +374,47 @@ Formateur.prototype.filtrerInputPseudo = function(pseudo)
 {
    return pseudo.replace(/{|}/g, "").trim()
 }
-\r
-Formateur.prototype.getSmilesHTML = function()\r
-{\r
-   var XHTML = ""\r
-   for (var sNom in this.smiles)\r
-   {\r
-      XHTML += "<img class=\"" + sNom + "\" src=\"img/smileys/" + sNom + ".gif\" />"\r
-   }\r
-   return XHTML\r
-}\r
 
+Formateur.prototype.getSmilesHTML = function()
+{
+   var XHTML = ""
+   for (var sNom in this.smiles)
+   {
+      XHTML += "<img class=\"" + sNom + "\" src=\"img/smileys/" + sNom + ".gif\" alt =\"" + sNom + "\" />"
+   }
+   return XHTML
+}
+
+/**
+  * Formatage complet d'un texte.
+  * @M le message
+  * @pseudo facultatif, permet de contruire le label des images sous la forme : "<Pseudo> : <Message>"
+  */
 Formateur.prototype.traitementComplet = function(M, pseudo)
 {
-   return this.traiterSmiles(this.traiterURL(this.remplacerBalisesHTML(M), pseudo))
+   return this.traiterLiensConv(this.traiterSmiles(this.traiterURL(this.traiterWikiSyntaxe(this.remplacerBalisesHTML(M)), pseudo)))
 }
-\r
-/**\r
-  * FIXME : Cette méthode est attrocement lourde !!\r
+
+/**
+  * Transforme les liens en entités clickables.
+  * Un lien vers une conversation permet d'ouvrire celle ci, elle se marque comme ceci dans un message :
+  * "{5F}" ou 5F est la racine de la conversation.
+  * Ce lien sera transformer en <span class="lienConv">{5F}</span> pouvant être clické pour créer la conv 5F.
+  */
+Formateur.prototype.traiterLiensConv = function(M)
+{
+   return M.replace(
+      /\{\w+\}/g,
+      function(lien)
+      {
+         return "<span class=\"lienConv\">" + lien + "</span>"
+      }
+   )
+}
+
+/**
+  * FIXME : Cette méthode est attrocement lourde ! A optimiser.
+  * moyenne sur échantillon : 234ms
   */
 Formateur.prototype.traiterSmiles = function(M)
 {  
@@ -306,62 +422,80 @@ Formateur.prototype.traiterSmiles = function(M)
    {
       ss = this.smiles[sNom]
       for (var i = 0; i < ss.length; i++)       
-         M = M.replace(ss[i], "<img src=\"img/smileys/" + sNom + ".gif\" />")
+         M = M.replace(ss[i], "<img src=\"img/smileys/" + sNom + ".gif\" alt =\"" + sNom + "\"  />")
    }
    return M
 }
 
 Formateur.prototype.remplacerBalisesHTML = function(M)
 {
-   return M.replace(/</g, "&lt;").replace(/>/g, "&gt;")
+   return M.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;")
 }
 
 Formateur.prototype.traiterURL = function(M, pseudo)
 {
    thisFormateur = this
-   
-   if (pseudo == undefined)
-      pseudo = ""
          
    var traitementUrl = function(url)
-   {    \r
-      // si ya pas de protocole on rajoute "http://"\r
-      if (!thisFormateur.regexTestProtocoleExiste.test(url))\r
+   {    
+      // si ya pas de protocole on rajoute "http://"
+      if (!thisFormateur.regexTestProtocoleExiste.test(url))
          url = "http://" + url
       var extension = thisFormateur.getShort(url)
-      return "<a " + (extension[1] ? "title=\"" + thisFormateur.traiterPourFenetreLightBox(pseudo, url) + ": " +  thisFormateur.traiterPourFenetreLightBox(M, url) + "\"" + " rel=\"lightbox[groupe]\"" : "") + " href=\"" + url + "\" >[" + extension[0] + "]</a>"
+      return "<a " + (extension[1] ? "title=\"" + (pseudo == undefined ? "" : thisFormateur.traiterPourFenetreLightBox(pseudo, url) + ": ") +  thisFormateur.traiterPourFenetreLightBox(M, url) + "\"" + " rel=\"lightbox\"" : "") + " href=\"" + url + "\" >[" + extension[0] + "]</a>"
    }
    return M.replace(this.regexUrl, traitementUrl)
 }
-\r
-/**\r
-  * Renvoie une version courte de l'url.\r
-  * par exemple : http://en.wikipedia.org/wiki/Yakov_Smirnoff devient wikipedia.org\r
+
+/**
+  * Formatage en utilisant un sous-ensemble des règles de mediwiki.
+  * par exemple ''italic'' devient <i>italic</i>
+  */
+Formateur.prototype.traiterWikiSyntaxe = function(M)
+{
+   return M.replace(
+      /'''(.*?)'''/g,
+      function(texte, capture)
+      {
+         return "<b>" + capture + "</b>"
+      }
+   ).replace(
+      /''(.*?)''/g,
+      function(texte, capture)
+      {
+         return "<i>" + capture + "</i>"
+      }
+   )
+}
+
+/**
+  * Renvoie une version courte de l'url.
+  * par exemple : http://en.wikipedia.org/wiki/Yakov_Smirnoff devient wikipedia.org
   */
 Formateur.prototype.getShort = function(url)
-{\r
+{
       var estUneImage = false
       var versionShort = null
-      var rechercheImg = this.regexImg.exec(url)\r
-      //alert(url)
-      if (rechercheImg != null)\r
+      var rechercheImg = this.regexImg.exec(url)
+      
+      if (rechercheImg != null)
+      {
+         versionShort = rechercheImg[1].toLowerCase()
+         if (versionShort == "jpeg") versionShort = "jpg" // jpeg -> jpg
+         estUneImage = true
+      }
+      else
       {
-         versionShort = rechercheImg[1].toLowerCase()\r
-         if (versionShort == "jpeg") versionShort = "jpg" // jpeg -> jpg\r
-         estUneImage = true\r
-      }\r
-      else\r
-      {\r
-         var rechercheDomaine = this.regexDomaine.exec(url)\r
-         if (rechercheDomaine != null && rechercheDomaine.length >= 2)\r
-            versionShort = rechercheDomaine[1]\r
-         else\r
-         {\r
-            var nomProtocole = this.regexNomProtocole.exec(url)\r
-            if (nomProtocole != null && nomProtocole.length >= 2)\r
-               versionShort = nomProtocole[1]\r
-         }\r
-      }\r
+         var rechercheDomaine = this.regexDomaine.exec(url)
+         if (rechercheDomaine != null && rechercheDomaine.length >= 2)
+            versionShort = rechercheDomaine[1]
+         else
+         {
+            var nomProtocole = this.regexNomProtocole.exec(url)
+            if (nomProtocole != null && nomProtocole.length >= 2)
+               versionShort = nomProtocole[1]
+         }
+      }
       
       return [versionShort == null ? "url" : versionShort, estUneImage]
  }
@@ -374,80 +508,127 @@ Formateur.prototype.traiterPourFenetreLightBox = function(M, urlCourante)
    thisFormateur = this
    var traitementUrl = function(url)
    {
-      return "[" + thisFormateur.getShort(url)[0] + (urlCourante == url ? ": image courante" : "") + "]"
+      return "[" + thisFormateur.getShort(url)[0] + (urlCourante == url ? "*" : "") + "]"
    }
-   \r
+   
    return this.remplacerBalisesHTML(M).replace(this.regexUrl, traitementUrl)
 }
 
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-var statutType = {enregistre: 0, identifie: 1, non_identifie: 2}
-\r
-function Client(util)\r
+// les statuts possibes du client
+var statutType = {
+   // mode enregistré, peut poster des messages et modifier son profile
+   auth_registered : 0,
+   // mode identifié, peut poster des messages mais n'a pas accès au profile
+   auth_not_registered : 1,
+   // mode déconnecté, ne peut pas poster de message
+   deconnected : 2
+}
+
+function Client(util)
 {
    this.util = util
-   \r
+   
    this.cookie = null
    this.regexCookie = new RegExp("^cookie=([^;]*)")
-   \r
-   // Obsolète
-   //this.captchaCrypt = null
    
-   // données personnels\r
+   // données personnels
    this.resetDonneesPersonnelles()
    
-   this.setStatut(statutType.non_identifie)
+   this.setStatut(statutType.deconnected)
    
-   // le dernier message d'erreur recut du serveur (par exemple une connexion foireuse : "login impossible")
-   this.dernierMessageErreur = ""\r
+   // si true alors chaque modification du client est mémorisé sur le serveur
+   this.autoflush = $.browser["opera"]
 }
-\r
-Client.prototype.resetDonneesPersonnelles = function()\r
-{\r
-   this.pseudo = conf.pseudoDefaut\r
-   this.login = ""\r
-   this.password = ""\r
-   this.email = ""\r
-   this.css = jQuery("link#cssPrincipale").attr("href")
+
+Client.prototype.resetDonneesPersonnelles = function()
+{
+   this.id = 0
+   this.pseudo = conf.pseudoDefaut
+   this.login = ""
+   this.password = ""
+   this.email = ""
+   this.css = $("link#cssPrincipale").attr("href")
+   this.nickFormat = "nick"
+   this.viewTimes = true
+   this.viewTooltips = true
+   this.cookie = undefined
    
    this.pagePrincipale = 1
+   this.ekMaster = false
    
    // les conversations, une conversation est un objet possédant les attributs suivants :
    // - racine (entier)
    // - page (entier)
-   this.conversations = new Array()\r
+   this.conversations = new Array()
 }
 
 Client.prototype.setCss = function(css)
 {
-   if (this.css == css)
+   if (this.css == css || css == "")
       return
 
    this.css = css
-   jQuery("link#cssPrincipale").attr("href", this.css)
-   this.majMenu()
+   $("link#cssPrincipale").attr("href", this.css)
+   if (this.autoflush) this.flush(true)
+}
+
+Client.prototype.pageSuivante = function(numConv)
+{
+   if (numConv < 0 && this.pagePrincipale > 1)
+      this.pagePrincipale -= 1
+   else if (this.conversations[numConv].page > 1)
+      this.conversations[numConv].page -= 1
+}
+
+Client.prototype.pagePrecedente = function(numConv)
+{
+   if (numConv < 0)
+      this.pagePrincipale += 1
+   else 
+      this.conversations[numConv].page += 1
+}
 
-   if (this.identifie())
-      this.flush()   
+/**
+  * Définit la première page pour la conversation donnée.
+  * @return true si la page a changé sinon false
+  */
+Client.prototype.goPremierePage = function(numConv)
+{
+   if (numConv < 0)
+   {
+      if (this.pagePrincipale == 1)
+         return false
+      this.pagePrincipale = 1
+   }
+   else
+   {
+      if (this.conversations[numConv].page == 1)
+         return false
+      this.conversations[numConv].page = 1
+   }
+   return true
 }
 
 /**
   * Ajoute une conversation à la vue de l'utilisateur.
   * Le profile de l'utilisateur est directement sauvegardé sur le serveur.
-  * @param racines la racine de la conversation
+  * @param racines la racine de la conversation (integer)
   * @return true si la conversation a été créée sinon false (par exemple si la conv existe déjà)
   */
 Client.prototype.ajouterConversation = function(racine)
 {
    // vérification s'il elle n'existe pas déjà
    for (var i = 0; i < this.conversations.length; i++)
-      if (this.conversations[i].racine == racine)
+      if (this.conversations[i].root == racine)
          return false
          
-   this.conversations.push({racine : racine, page : 1})
-   this.flush(false)
+   this.conversations.push({root : racine, page : 1})
+   
+   if (this.autoflush) this.flush(true)
+   
    return true
 }
 
@@ -460,128 +641,67 @@ Client.prototype.supprimerConversation = function(num)
       this.conversations[i] = this.conversations[i+1]
    this.conversations.pop()
    
-   this.flush(false)
-}\r
-
-Client.prototype.getXMLlogin = function(login, password)
-{
-   var XMLDocument = this.util.creerDocumentXMLAction()
-   XMLDocument.documentElement.setAttribute("name", "login")
-   
-   var nodeLogin = XMLDocument.createElement("login")
-   nodeLogin.appendChild(XMLDocument.createTextNode(login))
-   XMLDocument.documentElement.appendChild(nodeLogin)
-   
-   var nodePassword = XMLDocument.createElement("password")
-   nodePassword.appendChild(XMLDocument.createTextNode(password))
-   XMLDocument.documentElement.appendChild(nodePassword)
-   
-   return XMLDocument   
+   if (this.autoflush) this.flush(true)
 }
 
-Client.prototype.getXMLloginCookie = function()
+Client.prototype.getJSONLogin = function(login, password)
 {
-   var XMLDocument = this.util.creerDocumentXMLAction()
-   XMLDocument.documentElement.setAttribute("name", "login")
-   
-   var nodeCookie = XMLDocument.createElement("cookie")
-   nodeCookie.appendChild(XMLDocument.createTextNode(this.cookie))
-   XMLDocument.documentElement.appendChild(nodeCookie)
-   
-   return XMLDocument
+   return {
+      "action" : "authentification",
+      "login" : login,
+      "password" : password
+   }
 }
-\r
-/* Obsolète
-Client.prototype.getXMLloginCaptcha = function(captchaCrypt, captchaInput)
-{
-   var XMLDocument = this.util.creerDocumentXMLAction()
-   XMLDocument.documentElement.setAttribute("name", "loginCaptcha")
-   
-   var nodecaptchaCrypt = XMLDocument.createElement("captchaCrypt")
-   nodecaptchaCrypt.appendChild(XMLDocument.createTextNode(captchaCrypt))
-   XMLDocument.documentElement.appendChild(nodecaptchaCrypt)
-   
-   var nodecaptchaInput = XMLDocument.createElement("captchaInput")
-   nodecaptchaInput.appendChild(XMLDocument.createTextNode(captchaInput))
-   XMLDocument.documentElement.appendChild(nodecaptchaInput)
-   
-   return XMLDocument
-}*/
-\r
-/* Obsolète
-Client.prototype.getXMLgenerationCaptcha = function()
+
+Client.prototype.getJSONLoginCookie = function()
 {
-   var XMLDocument = this.util.creerDocumentXMLAction()
-   XMLDocument.documentElement.setAttribute("name", "generationCaptcha")
-   
-   return XMLDocument
-}*/
+   return {
+      "action" : "authentification",
+      "cookie" : this.cookie
+   }
+}  
 
-Client.prototype.getXMLEnregistrement = function(login, password)
+/**
+  * le couple (login, password) est facultatif. S'il n'est pas fournit alors il ne sera pas possible
+  * de s'autentifier avec (login, password).
+  */
+Client.prototype.getJSONEnregistrement = function(login, password)
 {
-   var XMLDocument = this.util.creerDocumentXMLAction()
-   XMLDocument.documentElement.setAttribute("name", "register")
-   
-   var nodeLogin = XMLDocument.createElement("login")
-   nodeLogin.appendChild(XMLDocument.createTextNode(login))
-   XMLDocument.documentElement.appendChild(nodeLogin)
+   var mess = { "action" : "register" }
    
-   var nodePassword = XMLDocument.createElement("password")
-   nodePassword.appendChild(XMLDocument.createTextNode(password))
-   XMLDocument.documentElement.appendChild(nodePassword)
+   if (login != undefined && password != undefined)
+   {
+      mess["login"] = login
+      mess["password"] = password
+   }
    
-   return XMLDocument   
+   return mess;
 }
 
-Client.prototype.getXMLProfile = function()
+Client.prototype.getJSONConversations = function()
 {
-   var XMLDocument = this.util.creerDocumentXMLAction()
-   XMLDocument.documentElement.setAttribute("name", "profile")
-   
-   var nodeCookie = XMLDocument.createElement("cookie")
-   nodeCookie.appendChild(XMLDocument.createTextNode(this.cookie))
-   XMLDocument.documentElement.appendChild(nodeCookie)
-   
-   var nodeLogin = XMLDocument.createElement("login")
-   nodeLogin.appendChild(XMLDocument.createTextNode(this.login))
-   XMLDocument.documentElement.appendChild(nodeLogin)
-   
-   var nodePassword = XMLDocument.createElement("password")
-   nodePassword.appendChild(XMLDocument.createTextNode(this.password))
-   XMLDocument.documentElement.appendChild(nodePassword)
-   
-   var nodePseudo = XMLDocument.createElement("pseudo")
-   nodePseudo.appendChild(XMLDocument.createTextNode(this.pseudo))
-   XMLDocument.documentElement.appendChild(nodePseudo)
-   
-   var nodeEmail = XMLDocument.createElement("email")
-   nodeEmail.appendChild(XMLDocument.createTextNode(this.email))
-   XMLDocument.documentElement.appendChild(nodeEmail)
-   
-   var nodeCSS = XMLDocument.createElement("css")
-   nodeCSS.appendChild(XMLDocument.createTextNode(this.css))
-   XMLDocument.documentElement.appendChild(nodeCSS)
-   
-   var nodePagePrincipale = XMLDocument.createElement("pagePrincipale")
-   nodePagePrincipale.appendChild(XMLDocument.createTextNode(this.pagePrincipale))
-   XMLDocument.documentElement.appendChild(nodePagePrincipale)
-   
-   // mémorise les conversations affichées
+   var conversations = new Array()
    for (var i = 0; i < this.conversations.length; i++)
-   {
-      var nodeConv = XMLDocument.createElement("conversation")
-      XMLDocument.documentElement.appendChild(nodeConv)
-      
-      var nodeRacine = XMLDocument.createElement("racine")
-      nodeRacine.appendChild(XMLDocument.createTextNode(this.conversations[i].racine))
-      nodeConv.appendChild(nodeRacine)
-      
-      var nodePage = XMLDocument.createElement("page")
-      nodePage.appendChild(XMLDocument.createTextNode(this.conversations[i].page))
-      nodeConv.appendChild(nodePage)
+      conversations.push({ "root" : this.conversations[i].root, "page" : this.conversations[i].page})
+   return conversations
+}
+
+Client.prototype.getJSONProfile = function()
+{
+   return {
+      "action" : "set_profile",
+      "cookie" : this.cookie,
+      "login" : this.login,
+      "password" : this.password,
+      "nick" : this.pseudo,
+      "email" : this.email,
+      "css" : this.css,
+      "nick_format" : this.nickFormat,
+      "view_times" : this.viewTimes,
+      "view_tooltips" : this.viewTooltips,
+      "main_page" : this.pagePrincipale < 1 ? 1 : this.pagePrincipale,
+      "conversations" : this.getJSONConversations()
    }
-   
-   return XMLDocument    
 }
 
 /**
@@ -592,266 +712,444 @@ Client.prototype.getCookie = function()
    var cookie = this.regexCookie.exec(document.cookie)
    if (cookie == null) this.cookie = null
    else this.cookie = cookie[1]
-}\r
-\r
-Client.prototype.delCookie = function()\r
-{\r
-   document.cookie = "cookie=; max-age=0"\r
 }
 
-Client.prototype.setCookie = function(cookie)
+Client.prototype.delCookie = function()
+{
+   document.cookie = "cookie=; max-age=0"
+}
+
+Client.prototype.setCookie = function()
 {
-   if (this.cookie == null)
+   if (this.cookie == null || this.cookie == undefined)
       return
       
-   document.cookie =
-      "cookie="+this.cookie+
-      "; max-age="  + (60 * 60 * 24 * 365)
+   // ne fonctionne pas sous IE....
+   /*document.cookie = "cookie=" + this.cookie + "; max-age="  + (60 * 60 * 24 * 365) */
+   
+   document.cookie = 
+      "cookie="+this.cookie+"; expires=" + new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 365).toUTCString()
 }
 
-Client.prototype.identifie = function()
+Client.prototype.authentifie = function()
 {
-   return this.statut == statutType.enregistre || this.statut == statutType.identifie
+   return this.statut == statutType.auth_registered || this.statut == statutType.auth_not_registered
 }
 
 Client.prototype.setStatut = function(statut)
-{  \r
-   if(typeof(statut) == "string")\r
+{  
+   // conversation en "enum" si en "string"
+   if (typeof(statut) == "string")
    {
       statut =
-         statut == "enregistre" ?
-            statutType.enregistre : (statut == "identifie" ? statutType.identifie : statutType.non_identifie)   \r
-   }   \r
-   \r
-   if (statut == this.statut) return   \r
-   \r
-   this.statut = statut   \r
+         statut == "auth_registered" ?
+            statutType.auth_registered :
+         (statut == "auth_not_registered" ? statutType.auth_not_registered : statutType.deconnected)
+   }   
+   
+   if (statut == this.statut) return
+   
+   this.statut = statut   
    this.majMenu()
-}\r
+}
 
 /**
-  * Demande la génération d'un captcha au serveur et l'affiche.
-  */\r
-  /* Obsolète
-Client.prototype.afficherCaptcha = function(query)
+  * Effectue la connexion vers le serveur.
+  * Cette fonction est bloquante tant que la connexion n'a pas été établie.
+  * S'il existe un cookie en local on s'authentifie directement avec lui.
+  * Si il n'est pas possible de s'authentifier alors on affiche un captcha anti-bot.
+  */
+Client.prototype.connexionCookie = function()
 {
-   var thisClient = this
-
-   $.post("request", this.util.xmlVersAction(this.getXMLgenerationCaptcha()),
-      function(data, textStatus)
-      {
-         var chemin = jQuery("chemin", data.documentElement).text()
-         thisClient.captchaCrypt = jQuery("captchaCrypt", data.documentElement).text()
-         jQuery(query).prepend(
-            "<p id=\"captcha\" >Es-tu un bot ? <img class=\"captchaImg\" src=\"" + chemin + "\" />" +
-            "<input name=\"captchaInput\" type=\"text\" size=\"5\" max_length=\"5\" ></p>"
-         )
-      }
-   )
-}
-
-Client.prototype.cacherCaptcha = function()
-{
-   jQuery("#captcha").remove()
-}*/
-\r
-/**\r
-  * Effectue la connexion vers le serveur.\r
-  * Cette fonction est bloquante tant que la connexion n'a pas été établie.\r
-  * S'il existe un cookie en local on s'authentifie directement avec lui.\r
-  * Si il n'est pas possible de s'authentifier alors on affiche un captcha anti-bot.\r
-  */\r
-Client.prototype.connexionCookie = function()\r
-{\r
-   this.getCookie()\r
+   this.getCookie()
    if (this.cookie == null) return false;
-   return this.connexion(this.util.xmlVersAction(this.getXMLloginCookie()))\r
+   return this.connexion(this.getJSONLoginCookie())
 }
 
 Client.prototype.connexionLogin = function(login, password)
 {
-   return this.connexion(this.util.xmlVersAction(this.getXMLlogin(login, password)))
+   return this.connexion(this.getJSONLogin(login, password))
 }
-\r
-/* Obsolète\r
-Client.prototype.connexionCaptcha = function()
-{   
-   return this.connexion(this.util.xmlVersAction(this.getXMLloginCaptcha(this.captchaCrypt, jQuery("#captcha input").val())))
-}*/
 
 Client.prototype.enregistrement = function(login, password)
 { 
-   if (this.identifie())
+   if (this.authentifie())
    {
       this.login = login
       this.password = password
       if(this.flush())
-         this.setStatut(statutType.enregistre)
-      return true
+      {
+         this.setStatut(statutType.auth_registered)
+         return true
+      }
+      return false
    }
-   else\r
-   {\r
-      if (login == undefined) login = ""\r
-      if (password == undefined) password = ""
-      return this.connexion(this.util.xmlVersAction(this.getXMLEnregistrement(login, password)))\r
+   else
+   {
+      return this.connexion(this.getJSONEnregistrement(login, password))
    }
 }
 
-Client.prototype.connexion = function(action)
+Client.prototype.connexion = function(messageJson)
 {
-   //action.action.dump()
+   ;; dumpObj(messageJson)
    thisClient = this
    jQuery.ajax(
       {
          async: false,
          type: "POST",
          url: "request",
-         dataType: "xml",
-         data: action,
+         dataType: "json",
+         data: this.util.jsonVersAction(messageJson),
          success:
             function(data)
             {
-               //thisClient.util.serializer.serializeToString(data).dump()
-               thisClient.chargerDonnees(data)
+               ;; dumpObj(data)
+               if (data["reply"] == "error")
+                  thisClient.util.messageDialogue(data["error_message"])
+               else
+                  thisClient.chargerDonnees(data)
             }
       }
    )
-   return this.identifie()
-}\r
-\r
-Client.prototype.deconnexion = function()\r
-{\r
-   this.setStatut(statutType.non_identifie) // deconnexion\r
-   this.resetDonneesPersonnelles()\r
-   this.delCookie ()\r
+   return this.authentifie()
 }
 
-Client.prototype.chargerDonnees = function(data)
+Client.prototype.deconnexion = function()
 {
-   var thisClient = this
+   this.flush(true)
+   this.delCookie()
+   this.resetDonneesPersonnelles()
+   this.setStatut(statutType.deconnected) // deconnexion
+}
 
-   this.setStatut(jQuery("statut", data.documentElement).text())       
+Client.prototype.chargerDonnees = function(data)
+{
+   // la modification du statut qui suit met à jour le menu, le menu dépend (page admin)
+   // de l'état ekMaster
+   this.ekMaster = data["ek_master"] != undefined ? data["ek_master"] : false
+   
+   this.setStatut(data["status"]) 
    
-   if (this.identifie())
+   if (this.authentifie())
    {
-      this.cookie = jQuery("cookie", data.documentElement).text()
+      this.cookie = data["cookie"]
       this.setCookie()
-      \r
-      this.login = jQuery("login", data.documentElement).text()
-      this.pseudo = jQuery("pseudo", data.documentElement).text()\r
-      this.email = jQuery("email", data.documentElement).text()\r
-      this.css = jQuery("css", data.documentElement).text()
+      
+      this.id = data["id"]
+      this.login = data["login"]
+      this.pseudo = data["nick"]
+      this.email = data["email"]
+      this.setCss(data["css"])
+      this.nickFormat = data["nick_format"]
+      this.viewTimes = data["view_times"]
+      this.viewTooltips = data["view_tooltips"]
       
       // la page de la conversation principale
-      var tmp = jQuery("pagePrincipale", data.documentElement)
-      this.pagePrincipale = tmp.length < 1 ? 1 : tmp.text()
+      this.pagePrincipale = data["main_page"] == undefined ? 1 : data["main_page"]
       
-      // met à jour la css
-      if (this.css != "")
-      {
-         jQuery("link#cssPrincipale").attr("href", this.css)
-         this.majMenu()
-      }
       // les conversations
-      this.conversations = new Array()
-      jQuery("conversation", data.documentElement).each(
-         function(i)
-         {
-            thisClient.conversations.push( { racine : jQuery("racine", this).text(), page : jQuery("page", this).text() } )
-         }
-      )
+      this.conversations = data["conversations"]
+      
+      this.majBulle()
+      this.majCssSelectionee()
    }
-   this.dernierMessageErreur = jQuery("information", data.documentElement).text()
 }
 
 /**
   * Met à jour les données personne sur serveur.
   * @param async de manière asynchrone ? défaut = true
+  * @return false si le flush n'a pas pû se faire sinon true
   */
 Client.prototype.flush = function(async)
 {
    if (async == undefined)
-      async = true
+      async = false
+      
+   if (!this.authentifie())
+      return false
 
-   thisClient = this
-   //thisClient.util.log(this.util.xmlVersAction(this.getXMLProfile()).action)      
+   var thisClient = this
+   var ok = true
+   
+   ;; dumpObj(this.getJSONProfile())
    jQuery.ajax(
       {
          async: async,
          type: "POST",
          url: "request",
-         dataType: "xml",
-         data: this.util.xmlVersAction(this.getXMLProfile()),
+         dataType: "json",
+         data: this.util.jsonVersAction(this.getJSONProfile()),
          success:
             function(data)
             {
-               //thisClient.util.log(thisClient.util.serializer.serializeToString(data))   
+               ;; dumpObj(data)
+               if (data["reply"] == "error")
+               {
+                  thisClient.util.messageDialogue(data["error_message"])
+                  ok = false
+               }
+               else
+               {
+                  thisClient.majBulle()
+               }
             }
       }
    )
-   // TODO : retourner false si un problème est survenu lors de l'update du profile
-   return true
+   
+   return ok
 }
 
 Client.prototype.majMenu = function()
 {
-   var displayType = this.css == "css/3/euphorik.css" ? "block" : "inline" //this.client
+   displayType = "block"
+
+   $("#menu .admin").css("display", this.ekMaster ? displayType : "none")
 
    // met à jour le menu   
-   if (this.statut == statutType.enregistre)
+   if (this.statut == statutType.auth_registered)
    {
-      jQuery("#menu .profile").css("display", displayType).text("profile")\r
-      jQuery("#menu .logout").css("display", displayType)
-      jQuery("#menu .register").css("display", "none")
+      $("#menu .profile").css("display", displayType).text("profile")
+      $("#menu .logout").css("display", displayType)
+      $("#menu .register").css("display", "none")
    }
-   else if (this.statut == statutType.identifie)
+   else if (this.statut == statutType.auth_not_registered)
    {
-      jQuery("#menu .profile").css("display", "none")\r
-      jQuery("#menu .logout").css("display", displayType)
-      jQuery("#menu .register").css("display", displayType)
+      $("#menu .profile").css("display", "none")
+      $("#menu .logout").css("display", displayType)
+      $("#menu .register").css("display", displayType)
    }
    else
    {
-      jQuery("#menu .profile").css("display", displayType).text("login")\r
-      jQuery("#menu .logout").css("display", "none")
-      jQuery("#menu .register").css("display", displayType)
+      $("#menu .profile").css("display", displayType).text("login")
+      $("#menu .logout").css("display", "none")
+      $("#menu .register").css("display", displayType)
+   }
+}
+
+/**
+  * Met à jour l'affichage des infos bulles en fonction du profile.
+  */
+Client.prototype.majBulle = function()
+{
+   this.util.bulleActive = this.viewTooltips
+}
+
+/**
+  * Met à jour la css sélectionnée, lors du chargement des données.
+  */
+Client.prototype.majCssSelectionee = function()
+{
+   // extraction du numéro de la css courante
+   var numCssCourante = this.css.match(/^.*?\/(\d)\/.*$/)
+   if (numCssCourante[1] != undefined)
+   {
+      $("#menuCss option").removeAttr("selected")
+      $("#menuCss option[value=" + numCssCourante[1]+ "]").attr("selected", "selected")
    }
 }
 
+Client.prototype.slap = function(userId, raison)
+{
+   var thisClient = this
+   
+   jQuery.ajax({
+      type: "POST",
+      url: "request",
+      dataType: "json",
+      data: this.util.jsonVersAction(
+         {
+            "action" : "slap",
+            "cookie" : thisClient.cookie,
+            "user_id" : userId,
+            "reason" : raison
+         }),
+      success: 
+         function(data)
+         {
+            if (data["reply"] == "error")
+               thisClient.util.messageDialogue(data["error_message"])
+         }
+   })
+}
+
+Client.prototype.ban = function(userId, raison, minutes)
+{
+   var thisClient = this
+
+   // par défaut un ban correspond à 3 jours
+   if (typeof(minutes) == "undefined")
+      minutes = conf.tempsBan;
+      
+   jQuery.ajax({
+      type: "POST",
+      url: "request",
+      dataType: "json",
+      data: this.util.jsonVersAction(
+         {
+            "action" : "ban",
+            "cookie" : thisClient.cookie,
+            "duration" : minutes,
+            "user_id" : userId,
+            "reason" : raison
+         }),
+      success: 
+         function(data)
+         {
+            if (data["reply"] == "error")
+               thisClient.util.messageDialogue(data["error_message"])
+         }
+   })
+}
+
+Client.prototype.kick = function(userId, raison)
+{
+   this.ban(userId, raison, conf.tempsKick)
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-jQuery.noConflict()
+/**
+   * classe permettant de gérer les événements (push serveur).
+   * l'information envoyé est sous la forme :
+   *  {
+   *     "action" : "wait_event"
+   *     "page" : <page>
+   *     [..]
+   *  }
+   * l'information reçu est sous la forme :
+   *  {
+   *     "reply" : <reply>
+   *  }
+   * @page la page
+   */
+function PageEvent(page, util)
+{
+   this.page = page
+   this.util = util
+   
+   // l'objet JSONHttpRequest représentant la connexion d'attente
+   this.attenteCourante = null
+   
+   // le multhreading du pauvre, merci javascript de m'offrire autant de primitives pour la gestion de la concurrence...
+   this.stop = false
+}
+
+/**
+  * Arrête l'attente courante s'il y en a une.
+  */
+PageEvent.prototype.stopAttenteCourante = function()
+{
+   this.stop = true
+         
+   if (this.attenteCourante != null)
+   {
+      this.attenteCourante.abort()   
+   }
+}
+
+/**
+  * Attend un événement lié à la page. 
+  * @funSend une fonction renvoyant les données json à envoyer
+  * @funsReceive est un objet comprenant les fonctions à appeler en fonction du "reply"
+  * les fonctions acceptent un paramètre correspondant au données reçues.
+  * exemple : {"new_message" : function(data){ ... }}
+  */
+PageEvent.prototype.waitEvent = function(funSend, funsReceive)
+{
+   this.stopAttenteCourante()
+   
+   this.stop = false
+   
+   var thisPageEvent = this
+      
+   // on doit conserver l'ordre des valeurs de l'objet JSON (le serveur les veut dans l'ordre définit dans le protocole)
+   // TODO : ya pas mieux ?
+   var dataToSend = 
+   {
+      "action" : "wait_event",
+      "page" : this.page
+   }
+   var poulpe = funSend()
+   for (v in poulpe)
+      dataToSend[v] = poulpe[v]
+   
+   ;; dumpObj(dataToSend)
+   
+   this.attenteCourante = jQuery.ajax({
+      type: "POST",
+      url: "request",
+      dataType: "json",\r
+      timeout: 300000, // timeout de 5min. Gros HACK pas beau. FIXME problème décrit ici : http://groups.google.com/group/jquery-en/browse_thread/thread/8724e64af3333a76
+      // Obsolète (voir TODO)
+      //timeout: 300000, // timeout de 5min. Gros HACK pas beau. FIXME problème décrit ici : http://groups.google.com/group/jquery-en/browse_thread/thread/8724e64af3333a76
+      data: this.util.jsonVersAction(dataToSend),
+      success:
+         function(data)
+         {            
+            ;; dumpObj(data)
+            
+            funsReceive[data["reply"]](data)
+            
+            // rappel de la fonction dans 100 ms
+            setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funsReceive) }, 100)
+         },
+      error:
+         function(XMLHttpRequest, textStatus, errorThrown)
+         {
+            ;; console.log("Connexion perdue dans waitEvent")
+            setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funsReceive) }, 1000)
+         }
+   })
+}
+
+/**
+  * Si un stopAttenteCourante survient un peu n'importe quand il faut imédiatement arreter de boucler.
+  */
+PageEvent.prototype.waitEvent2 = function(funSend, funsReceive)
+{
+   if (this.stop)
+      return
+   this.waitEvent(funSend, funsReceive)
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+function initialiserListeStyles(client)
+{
+   $("#menuCss").change(
+      function()
+      {
+         client.setCss("css/" + $("option:selected", this).attr("value") + "/euphorik.css")
+      }
+   )
+}
             
-      \r
+// charge dynamiquement le script de debug
+;; jQuery.ajax({async : false, url : "js/debug.js", dataType : "script"})
+      
 // le main
-jQuery(document).ready(
+$(document).ready(
    function()
    {  
-      /* FIXME : ce code pose problème sur konqueror, voir : http://www.kde-forum.org/thread.php?threadid=17993
-      var p = new DOMParser();
-      var doc =  p.parseFromString("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<action/>", "text/xml")
-      var s = new XMLSerializer()
-      alert(s.serializeToString(doc)) */
-   
-      var util = new Util()
+      var formateur = new Formateur()
+      var util = new Util(formateur)
       var client = new Client(util)
       var pages = new Pages()
-      var formateur = new Formateur()\r
-      \r
-      // connexion vers le serveur (utilise un cookie qui traine)\r
+      
+      // connexion vers le serveur (utilise un cookie qui traine)
       client.connexionCookie()
       
-      // les styles css
-      for (var i = 1; i <= 3; i++)
-      {
-         jQuery("#css"+i).click(function(){
-            client.setCss("css/" + jQuery(this).attr("id").charAt(3) + "/euphorik.css")
-         })
-      }
+      initialiserListeStyles(client)
+
+      // FIXME : ne fonctionne pas sous opera
+      // voir : http://dev.jquery.com/ticket/2892#preview
+      $(window).unload(function(){client.flush()})
       
-      jQuery("#menu .minichat").click(function(){ pages.afficherPage("minichat") })
-      jQuery("#menu .profile").click(function(){ pages.afficherPage("profile") })\r
-      jQuery("#menu .logout").click(function(){
+      $("#menu .minichat").click(function(){ pages.afficherPage("minichat") })
+      $("#menu .admin").click(function(){ pages.afficherPage("admin") })
+      $("#menu .profile").click(function(){ pages.afficherPage("profile") })
+      $("#menu .logout").click(function(){
          util.messageDialogue("Êtes-vous sur de vouloir vous délogger ?", messageType.question,
             {"Oui" : function()
                {
@@ -862,12 +1160,19 @@ jQuery(document).ready(
             }
          )
       })
-      jQuery("#menu .register").click(function(){ pages.afficherPage("register") })
+      $("#menu .register").click(function(){ pages.afficherPage("register") })
+      $("#menu .about").click(function(){ pages.afficherPage("about") })
+      
+      // TODO : simplifier et pouvoir créer des liens par exemple : <span class="lien" href="conditions">Conditions d'utilisation</span>
+      $("#footer .conditions").click(function(){ pages.afficherPage("conditions_utilisation") })
 
       pages.ajouterPage(new PageMinichat(client, formateur, util))
+      pages.ajouterPage(new PageAdmin(client, formateur, util))
       pages.ajouterPage(new PageProfile(client, formateur, util))
       pages.ajouterPage(new PageRegister(client, formateur, util))
+      pages.ajouterPage(new PageAbout(client, formateur, util))
+      pages.ajouterPage("conditions_utilisation")
+      
       pages.afficherPage("minichat")
    }
 )
-