REPORT de la branche 1.0
[euphorik.git] / js / euphorik.js
index d040dc3..825e4c3 100755 (executable)
@@ -1,4 +1,20 @@
-// coding: utf-8\r
+// coding: utf-8\r
+// Copyright 2008 Grégory Burri\r
+//\r
+// This file is part of Euphorik.\r
+//\r
+// Euphorik is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 3 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// Euphorik is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with Euphorik.  If not, see <http://www.gnu.org/licenses/>.\r
 \r
 /**\r
   * Contient la base javascript pour le site euphorik.ch.\r
   * Normalement 'const' à la place de 'var' mais non supporté par IE7.
   */\r
 var conf = {\r
-   nbMessageAffiche : 10, // (par page)
+   nbMessageAffiche : 40, // (par page)
    pseudoDefaut : "<nick>",\r
-   tempsAffichageMessageDialogue : 4000, // en ms\r
+   tempsAffichageMessageDialogue : 4000, // en ms
+   tempsKick : 15, // en minute
+   tempsBan : 60 * 24 * 3, // en minutes (3jours)\r
    smiles : {   \r
       "smile" : [/:\)/g, /:-\)/g],  \r
       "bigsmile" : [/:D/g, /:-D/g],\r
@@ -65,13 +83,19 @@ String.prototype.rtrim = function()
 ///////////////////////////////////////////////////////////////////////////////////////////////////\r
 \r
 /**\r
-  * Cette classe regroupe des fonctions utilitaires (helpers).\r
+  * Cette classe regroupe des fonctions utilitaires (helpers).
+  * @formateur est permet de formater les messages affichés à l'aide de messageDialogue (facultatif)\r
   */
-function Util()
+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}
@@ -82,11 +106,17 @@ var messageType = {informatif: 0, question: 1, erreur: 2}
   * @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
   *        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)
@@ -94,7 +124,7 @@ Util.prototype.messageDialogue = function(message, type, boutons)
    var fermer = function(){$("#info").slideUp(100)}
    fermer()   
    
-   $("#info .message").html(message)
+   $("#info .message").html(thisUtil.formateur == undefined || !formate ? message : thisUtil.formateur.traitementComplet(message))
    switch(type)
    {
       case messageType.informatif : $("#info #icone").attr("class", "information"); break
@@ -109,6 +139,55 @@ Util.prototype.messageDialogue = function(message, type, boutons)
    this.timeoutMessageDialogue = setTimeout(fermer, conf.tempsAffichageMessageDialogue)   
 }\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")
+         
+         $("p", m).html(message)
+      
+         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)
+}
+
 /**
   * Utilisé pour l'envoie de donnée avec la méthode ajax de jQuery.
   */
@@ -173,19 +252,16 @@ Util.prototype.replaceSelection = function(input, replaceString) {
          this.setCaretToPos(input, selectionStart + replaceString.length)\r
    }\r
    else if (document.selection)
-   {\r
-      var range = document.selection.createRange();\r
+   {
+      input.focus()\r
+      var range = document.selection.createRange()\r
       if (range.parentElement() == input)
       {\r
          var isCollapsed = range.text == ''\r
          range.text = replaceString\r
          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
+         {\r
             range.moveStart('character', -replaceString.length);\r
-            range.select();\r
          }\r
       }\r
    }\r
@@ -222,10 +298,21 @@ 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)
@@ -238,11 +325,16 @@ Pages.prototype.afficherPage = function(nomPage, forcerChargement)
    if (this.pageCourante != null && this.pageCourante.decharger)
       this.pageCourante.decharger()
   
-   $("#menu div").removeClass("courante")
-   $("#menu div." + nomPage).addClass("courante")
+   $("#menu li").removeClass("courante")
+   $("#menu li." + nomPage).addClass("courante")
       
    this.pageCourante = page
-   $("#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()
@@ -250,6 +342,11 @@ 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
@@ -277,7 +374,7 @@ Formateur.prototype.getSmilesHTML = function()
    var XHTML = ""\r
    for (var sNom in this.smiles)\r
    {\r
-      XHTML += "<img class=\"" + sNom + "\" src=\"img/smileys/" + sNom + ".gif\" />"\r
+      XHTML += "<img class=\"" + sNom + "\" src=\"img/smileys/" + sNom + ".gif\" alt =\"" + sNom + "\" />"\r
    }\r
    return XHTML\r
 }\r
@@ -289,7 +386,7 @@ Formateur.prototype.getSmilesHTML = function()
   */
 Formateur.prototype.traitementComplet = function(M, pseudo)
 {
-   return this.traiterLiensConv(this.traiterSmiles(this.traiterURL(this.remplacerBalisesHTML(M), pseudo)))
+   return this.traiterLiensConv(this.traiterSmiles(this.traiterURL(this.traiterWikiSyntaxe(this.remplacerBalisesHTML(M)), pseudo)))
 }
 
 /**
@@ -311,7 +408,7 @@ Formateur.prototype.traiterLiensConv = function(M)
 \r
 /**\r
   * FIXME : Cette méthode est attrocement lourde ! A optimiser.
-  * moyenne su échantillon : 234ms\r
+  * moyenne sur échantillon : 234ms\r
   */
 Formateur.prototype.traiterSmiles = function(M)
 {  
@@ -319,14 +416,14 @@ 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)
@@ -343,6 +440,27 @@ Formateur.prototype.traiterURL = function(M, pseudo)
    }
    return M.replace(this.regexUrl, traitementUrl)
 }
+
+/**
+  * 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>"
+      }
+   )
+}
 \r
 /**\r
   * Renvoie une version courte de l'url.\r
@@ -384,7 +502,7 @@ 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)
@@ -413,10 +531,10 @@ function Client(util)
    // données personnels\r
    this.resetDonneesPersonnelles()
    
-   this.setStatut(statutType.deconnected)
-   
-   // le dernier message d'erreur recut du serveur (par exemple une connexion foireuse : "login impossible")
-   this.dernierMessageErreur = ""\r
+   this.setStatut(statutType.deconnected)\r
+   \r
+   // si true alors chaque modification du client est mémorisé sur le serveur\r
+   this.autoflush = $.browser["opera"]\r
 }
 \r
 Client.prototype.resetDonneesPersonnelles = function()\r
@@ -428,6 +546,9 @@ Client.prototype.resetDonneesPersonnelles = function()
    this.email = ""\r
    this.css = $("link#cssPrincipale").attr("href")
    this.nickFormat = "nick"
+   this.viewTimes = true
+   this.viewTooltips = true
+   this.cookie = undefined
    
    this.pagePrincipale = 1
    this.ekMaster = false
@@ -445,7 +566,9 @@ Client.prototype.setCss = function(css)
 
    this.css = css
    $("link#cssPrincipale").attr("href", this.css)
-   this.majMenu()
+   this.majMenu()\r
+   \r
+   if (this.autoflush) this.flush(true)
 }
 
 Client.prototype.pageSuivante = function(numConv)
@@ -498,7 +621,10 @@ Client.prototype.ajouterConversation = function(racine)
       if (this.conversations[i].root == racine)
          return false
          
-   this.conversations.push({root : racine, page : 1})
+   this.conversations.push({root : racine, page : 1})\r
+   \r
+   if (this.autoflush) this.flush(true)\r
+   
    return true
 }
 
@@ -509,7 +635,9 @@ Client.prototype.supprimerConversation = function(num)
    // décalage TODO : supprimer le dernier élément 
    for (var i = num; i < this.conversations.length - 1; i++)
       this.conversations[i] = this.conversations[i+1]
-   this.conversations.pop()
+   this.conversations.pop()\r
+   \r
+   if (this.autoflush) this.flush(true)
 }
 
 Client.prototype.getJSONLogin = function(login, password)
@@ -565,6 +693,8 @@ Client.prototype.getJSONProfile = function()
       "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()
    }
@@ -585,14 +715,16 @@ Client.prototype.delCookie = function()
    document.cookie = "cookie=; max-age=0"\r
 }
 
-Client.prototype.setCookie = function(cookie)
+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.authentifie = function()
@@ -611,7 +743,7 @@ Client.prototype.setStatut = function(statut)
          (statut == "auth_not_registered" ? statutType.auth_not_registered : statutType.deconnected)\r
    }   \r
    \r
-   if (statut == this.statut) return   \r
+   if (statut == this.statut) return\r
    \r
    this.statut = statut   \r
    this.majMenu()
@@ -642,8 +774,11 @@ Client.prototype.enregistrement = function(login, password)
       this.login = login
       this.password = password
       if(this.flush())
+      {
          this.setStatut(statutType.auth_registered)
-      return true
+         return true
+      }
+      return false
    }
    else\r
    {
@@ -653,7 +788,7 @@ Client.prototype.enregistrement = function(login, password)
 
 Client.prototype.connexion = function(messageJson)
 {
-   ;;; dumpObj(messageJson)
+   ;; dumpObj(messageJson)
    thisClient = this
    jQuery.ajax(
       {
@@ -665,8 +800,11 @@ Client.prototype.connexion = function(messageJson)
          success:
             function(data)
             {
-               ;;; dumpObj(data)
-               thisClient.chargerDonnees(data)
+               ;; dumpObj(data)
+               if (data["reply"] == "error")
+                  thisClient.util.messageDialogue(data["error_message"])
+               else
+                  thisClient.chargerDonnees(data)
             }
       }
    )
@@ -675,10 +813,10 @@ Client.prototype.connexion = function(messageJson)
 \r
 Client.prototype.deconnexion = function()\r
 {
-   this.flush()
-   this.delCookie()\r
-   this.setStatut(statutType.deconnected) // deconnexion\r
+   this.flush(true)
+   this.delCookie()
    this.resetDonneesPersonnelles()\r
+   this.setStatut(statutType.deconnected) // deconnexion\r
 }
 
 Client.prototype.chargerDonnees = function(data)
@@ -700,14 +838,17 @@ Client.prototype.chargerDonnees = function(data)
       this.email = data["email"]\r
       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
       this.pagePrincipale = data["main_page"] == undefined ? 1 : data["main_page"]
       
       // les conversations
       this.conversations = data["conversations"]
+      
+      this.majBulle()
    }
-   this.dernierMessageErreur = data["error_message"]
 }
 
 /**
@@ -718,13 +859,15 @@ Client.prototype.chargerDonnees = function(data)
 Client.prototype.flush = function(async)
 {
    if (async == undefined)
-      async = true
+      async = false
       
    if (!this.authentifie())
       return false
 
-   thisClient = this
-   ;;; dumpObj(this.getJSONProfile())
+   var thisClient = this
+   var ok = true
+   
+   ;; dumpObj(this.getJSONProfile())
    jQuery.ajax(
       {
          async: async,
@@ -735,20 +878,30 @@ Client.prototype.flush = function(async)
          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()
 {
    // TODO : à virer : ne plus changer de style de display ... spa beau .. ou trouver une autre méthode
-   var displayType = this.css == "css/3/euphorik.css" ? "block" : "inline" //this.client
+   // var displayType = this.css == "css/3/euphorik.css" ? "block" : "inline" //this.client
+   displayType = "block"
 
-   $("#menu .admin").css("display", this.ekMaster ? "inline" : "none")
+   $("#menu .admin").css("display", this.ekMaster ? displayType : "none")
 
    // met à jour le menu   
    if (this.statut == statutType.auth_registered)
@@ -771,6 +924,14 @@ Client.prototype.majMenu = function()
    }
 }
 
+/**
+  * Met à jour l'affichage des infos bulles en fonction du profile.
+  */
+Client.prototype.majBulle = function()
+{
+   this.util.bulleActive = this.viewTooltips
+}
+
 Client.prototype.slap = function(userId, raison)
 {
    var thisClient = this
@@ -801,7 +962,7 @@ Client.prototype.ban = function(userId, raison, minutes)
 
    // par défaut un ban correspond à 3 jours
    if (typeof(minutes) == "undefined")
-      minutes = 60 * 24 * 3
+      minutes = conf.tempsBan;
       
    jQuery.ajax({
       type: "POST",
@@ -826,7 +987,7 @@ Client.prototype.ban = function(userId, raison, minutes)
 
 Client.prototype.kick = function(userId, raison)
 {
-   this.ban(userId, raison, 15)
+   this.ban(userId, raison, conf.tempsKick)
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -842,6 +1003,9 @@ function PageEvent(page, 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
 }
 
 /**
@@ -849,8 +1013,12 @@ function PageEvent(page, util)
   */
 PageEvent.prototype.stopAttenteCourante = function()
 {
+   this.stop = true
+         
    if (this.attenteCourante != null)
+   {
       this.attenteCourante.abort()   
+   }
 }
 
 /**
@@ -860,11 +1028,13 @@ PageEvent.prototype.stopAttenteCourante = function()
   */
 PageEvent.prototype.waitEvent = function(funSend, funReceive)
 {
-   var thisPageEvent = this
-      
    this.stopAttenteCourante()
+   
+   this.stop = false
+   
+   var thisPageEvent = this
       
-   // on doit conserver l'ordre des valeurs de l'objet JSON (le serveur les veux dans l'ordre définit dans le protocole)
+   // 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 = 
    {
@@ -875,7 +1045,8 @@ PageEvent.prototype.waitEvent = function(funSend, funReceive)
    for (v in poulpe)
       dataToSend[v] = poulpe[v]
    
-   ;;; dumpObj(dataToSend)
+   ;; dumpObj(dataToSend)
+   
    this.attenteCourante = jQuery.ajax({
       type: "POST",
       url: "request",
@@ -884,20 +1055,29 @@ PageEvent.prototype.waitEvent = function(funSend, funReceive)
       success:
          function(data)
          {            
-            ;;; dumpObj(data)
+            ;; dumpObj(data)
             
             funReceive(data)
             
             // rappel de la fonction dans 100 ms
-            setTimeout(function(){ thisPageEvent.waitEvent(funSend, funReceive) }, 100);
+            setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funReceive) }, 100)
          },
       error:
          function(XMLHttpRequest, textStatus, errorThrown)
          {
-            setTimeout(function(){ thisPageEvent.rafraichirMessages(funSend, funReceive) }, 1000);
+            setTimeout(function(){ thisPageEvent.waitEvent2(funSend, funReceive) }, 1000)
          }
    })
+}
 
+/**
+  * Si un stopAttenteCourante survient un peu n'importe quand il faut imédiatement arreter de boucler.
+  */
+PageEvent.prototype.waitEvent2 = function(funSend, funReceive)
+{
+   if (this.stop)
+      return
+   this.waitEvent(funSend, funReceive)
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -913,24 +1093,25 @@ function initialiserListeStyles(client)
 }
             
 // charge dynamiquement le script de debug
-;;; jQuery.ajax({async : false, url : "js/debug.js", dataType : "script"})
+;; jQuery.ajax({async : false, url : "js/debug.js", dataType : "script"})
       \r
 // le main
 $(document).ready(
    function()
    {  
-      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
+      var pages = new Pages()\r
       \r
       // connexion vers le serveur (utilise un cookie qui traine)\r
       client.connexionCookie()
       
       initialiserListeStyles(client)
 
-      // TODO : pourquoi $(document).unload ne fonctionne pas ?
-      $(window).unload(function(){client.flush(false)})
+      // FIXME : ne fonctionne pas sous opera
+      // voir : http://dev.jquery.com/ticket/2892#preview
+      $(window).unload(function(){client.flush()})
       
       $("#menu .minichat").click(function(){ pages.afficherPage("minichat") })
       $("#menu .admin").click(function(){ pages.afficherPage("admin") })
@@ -948,12 +1129,17 @@ $(document).ready(
       })
       $("#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")
    }
-)
\ No newline at end of file
+)