ADD ajout d'une version de protocole, chaque message du client est taggé avec la...
[euphorik.git] / js / pageMinichat.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 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
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 function PageMinichat(client, formateur, util)
20 {
21 this.nom = "minichat"
22
23 this.client = client
24 this.formateur = formateur
25 this.util = util
26
27 // permet d'éviter d'envoyer plusieurs messages simultanément en pressant
28 // rapidement sur "enter" par exemple
29 this.envoieMessageEnCours = false
30
31 this.regexMessageTagMatch = /\{.*?\}>/g
32 this.regexMessageTagReplace = /^(.*?\{.*?\}>)*/
33 }
34
35 PageMinichat.prototype.contenu = function()
36 {
37 // le fait que tout soit collé est fait exprès, permet d'éviter d'avoir des espaces supplémentaires entre les spans'
38 return '\
39 <div id="trollCourant">Troll de la semaine : <span class="troll"></span></div>\
40 <form method="post" action="" id ="posterMessage">\
41 <p>\
42 <input class="captcha" name="captcha" type="text" size="8" maxlength="8"></input>\
43 <input class="pseudo" name="pseudo" type="text" maxlength="50" value="' + encodeURI(conf.nickDefaut) + '"></input>\
44 <span id="repondA"><span class="nb">0</span><span class="messages"></span></span>\
45 <input class="message" name="message" type="text" maxlength="500" value=""></input>\
46 <button class="smiles"></button>\
47 <button class="return"></button>\
48 </p>\
49 </form>\
50 <div id="conversations"></div>'
51 }
52
53 PageMinichat.prototype.charger = function()
54 {
55 thisPage = this
56
57 $("form input.pseudo").val(this.client.pseudo)
58
59 // cet appel ne doit pas être fait avant l'appel à 'charger'
60 this.messages = new Messages(this.client, this.formateur, this.util)
61
62 this.messages.rafraichirMessages(true)
63
64 this.util.setCaretToEnd($("form input.message")[0])
65
66 // les outils de bannissement (uniquement pour les ekMaster)
67 if (this.client.ekMaster)
68 {
69 this.util.outilsBan = $(
70 '<span id="outilsBan">' +
71 '<form action=""><p><input id="raison" name="raison" type="text" size="10" maxlength="200"></input></p></form>' +
72 '<img id="ban" src="img/ban.gif" alt="Ban de 3 jours" />' +
73 '<img id="kick" src="img/kick.gif" alt="Ban de 15min" />' +
74 '<img id="slap" src="img/slap.gif" alt="Avertissement" />' +
75 '</span>'
76 )
77
78 this.util.infoBulle("Slap", $("#slap", this.util.outilsBan))
79 this.util.infoBulle("Kick (" + conf.tempsKick + "min)", $("#kick", this.util.outilsBan))
80 this.util.infoBulle("Ban (" + conf.tempsBan / 24 / 60 + " jours)", $("#ban", this.util.outilsBan))
81 this.util.infoBulle("La raison", $("input", this.util.outilsBan))
82 }
83
84 this.util.infoBulle("Ouvrir la conversation liée au troll de la semaine", $("#trollCourant .troll"))
85
86 this.util.infoBulle("Cliquer sur les messages pour les enlevers de la liste",
87 $("form#posterMessage #repondA").hover(
88 function() { thisPage.util.afficherBoite($(".messages", this), $(this), positionTypeX.centre, positionTypeY.bas) },
89 function() { $(".messages", this).hide() }
90 )
91 )
92
93 // <smiles>
94 $("body").append('<div id="smiles"></div>')
95 // affichage des smiles
96 $("#smiles").append(this.formateur.getSmilesHTML()).children().each(
97 function(i)
98 {
99 var opacityBase = $(this).css("opacity")
100 $(this).click(
101 function(event)
102 {
103 thisPage.util.replaceSelection($("form#posterMessage input.message")[0], thisPage.formateur.smiles[$(this).attr("class")][0].source.replace(/\\/g, ""))
104 }
105 ).hover(
106 function() { $(this).animate({opacity: 1}, 200) },
107 function() { $(this).animate({opacity: opacityBase}, 200) }
108 )
109 }
110 )
111 $("form button.smiles").hover(
112 // affichage de la boite présentant les smiles
113 function(e){ thisPage.util.afficherBoite($("#smiles"), $(e.target), positionTypeX.centre, positionTypeY.basRecouvrement) },
114 function(){}
115 )
116 $("#smiles").hover(
117 function(){},
118 function()
119 {
120 $("#smiles").hide()
121 }
122 )
123 // </smiles>
124
125 // événements
126 var nouveauMessage =
127 function()
128 {
129 // captcha anti bot
130 if ($("form input.captcha").val() != "") return
131
132 thisPage.envoyerMessage(
133 $("form input.pseudo").val(),
134 $("form input.message").val()
135 )
136
137 $("form input.message").focus()
138 }
139
140 $("form").keypress(
141 function(e)
142 {
143 if (e.which == 13) // return
144 nouveauMessage()
145 }
146 )
147
148 $("form button.return").click(nouveauMessage)
149
150 // interdiction de submiter le formulaire
151 $("form").submit(function(){ return false})
152
153 $("input.pseudo").click(
154 function()
155 {
156 var input = $("input.pseudo")[0]
157 if (input.value == conf.pseudoDefaut)
158 input.value = ""
159 }
160 )
161 }
162
163 PageMinichat.prototype.decharger = function()
164 {
165 this.messages.pageEvent.stopAttenteCourante()
166
167 $("body #smiles").remove()
168 }
169
170 PageMinichat.prototype.getJSONMessage = function(pseudo, message)
171 {
172 var repondA = []
173 for (var id in this.messages.messagesRepond)
174 repondA.push(parseInt(id)) // FIXME : une propriété ne peut pas être de type int ?
175
176 return {
177 "header" : { "action" : "put_message", "version" : conf.versionProtocole },
178 "cookie" : this.client.cookie,
179 "nick" : pseudo,
180 "content" : message,
181 "answer_to" : repondA
182 }
183 }
184
185 PageMinichat.prototype.envoyerMessage = function(pseudo, message)
186 {
187 var thisPageMinichat = this
188
189 // (un pseudo vide est autorisé)
190 pseudo = this.formateur.filtrerInputPseudo(pseudo)
191
192 if (pseudo == conf.nickDefaut)
193 {
194 this.util.messageDialogue("Le pseudo ne peut pas être " + conf.nickDefaut)
195 return
196 }
197
198 message = message.trim()
199 if (message == "")
200 {
201 this.util.messageDialogue("Le message est vide")
202 return
203 }
204
205 if (!this.client.authentifie())
206 if (!this.client.enregistrement())
207 {
208 this.util.messageDialogue("login impossible")
209 return
210 }
211
212 this.client.pseudo = pseudo
213
214 // évite le double post
215 if (this.envoieMessageEnCours)
216 {
217 this.util.messageDialogue("Message en cours d'envoie...")
218 return
219 }
220 this.envoieMessageEnCours = true
221
222 jQuery.ajax(
223 {
224 url : "request",
225 type: "POST",
226 data : this.util.jsonVersAction(this.getJSONMessage(pseudo, message)),
227 dataType : "json",
228 beforeSend : function(xmlHttpRequest)
229 {
230 // TODO : ça marche ça ??
231 xmlHttpRequest.setRequestHeader("X-Requested-With", "")
232 },
233 success : function(data, textStatus)
234 {
235 if(data["reply"] == "ok")
236 {
237 // met à jour la classe des messages auquel repond celui ci (c'est un peu de la triche) TODO : ya mieux ?
238 for (var messId in thisPageMinichat.messages.messagesRepond)
239 {
240 for (var j = 0; j < thisPageMinichat.messages.conversations.length; j++)
241 {
242 var mess = thisPageMinichat.messages.conversations[j].messagesParId[messId]
243 if (mess != undefined)
244 mess.clientARepondu = true
245 }
246 $("#conversations div#" + thisPageMinichat.messages.messagesRepond[messId].getId()).addClass("repondu")
247 }
248
249 $("form input.message").val("")
250 thisPageMinichat.messages.enleverMessagesRepond()
251 }
252 else if (data["reply"] == "error")
253 {
254 thisPageMinichat.util.messageDialogue(data["error_message"])
255 }
256 thisPageMinichat.envoieMessageEnCours = false
257 },
258 error : function()
259 {
260 thisPageMinichat.envoieMessageEnCours = false
261 }
262 }
263 )
264 }
265
266 ///////////////////////////////////////////////////////////////////////////////////////////////////
267
268 function Reponse(id, pseudo, login)
269 {
270 this.id = id
271 this.pseudo = pseudo
272 this.login = login
273
274 if (this.pseudo == undefined)
275 this.pseudo = ""
276
277 if (this.login == undefined)
278 this.login = ""
279 }
280
281 ///////////////////////////////////////////////////////////////////////////////////////////////////
282
283 /**
284 * Représente un message.
285 * @param id (string)
286 * @param date (string)
287 * @param pseudo
288 * @param contenu
289 */
290 function Message(client, formateur, id, auteurId, racineId, date, pseudo, login, contenu)
291 {
292 this.client = client
293 this.formateur = formateur
294 this.id = id
295 this.auteurId = auteurId
296 this.racineId = racineId
297 this.date = date
298 this.pseudo = pseudo
299 this.login = login
300 this.contenu = contenu
301
302 this.appartientAuClient = false
303 this.clientARepondu = false
304 this.estUneReponse = false
305 this.ekMaster = false
306
307 this.systeme = false // est-ce un message 'système' ?
308
309 this.repondA = {} // un ensemble de reponse (voir Reponse) indexé par l'id du message de la reponses
310 }
311
312 /**
313 * @param pre est un prefix permettant de créer un Id différent pour deux mêmes messages.
314 * Cela est utile pour afficher plusieurs mêmes messages au sein d'un document XHTML.
315 * voir également la fonction 'XHTML()'.
316 */
317 Message.prototype.getId = function(pre)
318 {
319 if (pre == undefined)
320 pre = ""
321 return pre + "mess" + this.id.toString(36)
322 }
323
324 /**
325 *
326 */
327 Message.prototype.setRepondA = function(repondAJSON)
328 {
329 var thisMessage = this
330 this.repondA = {}
331
332 for(var i = 0; i < repondAJSON.length; i++)
333 {
334 thisMessage.repondA[repondAJSON[i]["id"]] = new Reponse(
335 repondAJSON[i]["id"],
336 repondAJSON[i]["nick"],
337 repondAJSON[i]["login"]
338 )
339 }
340 }
341
342 /**
343 * Renvoie les messages faisant partie d'une conversation.
344 * @param messages l'ensemble des messages de la conversation
345 * @return les id des messages qui ont été mis en evidence sous la forme
346 * d'un hash (object) {id => 0 | 1 | 2 | 3}. 1 : proprietaire, 2 : reponse directe, 3 : message repondu
347 */
348 Message.prototype.getConversation = function(messages)
349 {
350 var thisMessage = this
351
352 // les messages faisant partie de la conversation
353 var messagesEnEvidence = {}
354
355 messagesEnEvidence[this.id] = 1
356
357 // recherche les réponses (O(n))
358 for (var i = 0; i < messages.messages.length; i++)
359 if (messages.messages[i].repondA.hasOwnProperty(this.id))
360 messagesEnEvidence[messages.messages[i].id] = 2
361
362 var premierNiveau = true
363 var f = function(tabIds)
364 {
365 for(var id in tabIds)
366 {
367 var message = messages.messagesParId[id]
368 if (message != undefined)
369 {
370 messagesEnEvidence[id] = premierNiveau ? 3 : (message.auteurId == thisMessage.auteurId ? 1 : 0)
371 premierNiveau = false
372 f (message.repondA)
373 }
374 }
375 }
376 f(this.repondA)
377
378 return messagesEnEvidence
379 }
380
381 /**
382 * Renvoie le message sous la forme de code XHTML (string) prêt à être inséré dans un document.
383 * Aucun callback n'est affecté.
384 */
385 Message.prototype.XHTML = function(messagePair, pre)
386 {
387 if (messagePair == undefined)
388 messagePair = true
389 if (pre == undefined)
390 pre = ""
391
392 // construit l'identifiant de la personne
393 var identifiant =
394 this.client.nickFormat == "nick" || this.login == "" ? this.formateur.traitementComplet(this.pseudo) :
395 (this.client.nickFormat == "login" ? this.formateur.traitementComplet(message.login) :
396 this.formateur.traitementComplet(this.pseudo) + "<span class=\"login\">(" + this.formateur.traitementComplet(message.login) +")</span>" )
397
398 var XHTMLrepondA = ""
399 var debut = true
400 for (var id in this.repondA)
401 {
402 if (!debut) XHTMLrepondA += ", "
403 XHTMLrepondA += this.formateur.traitementComplet(this.repondA[id].pseudo)
404 debut = false
405 }
406 if (XHTMLrepondA != "")
407 XHTMLrepondA = "<span class=\"repondA\">" + XHTMLrepondA + "</span><span class=\"delimitationRepondA\"></span>"
408
409 return "<div id=\"" + this.getId(pre) + "\" class=\"" + (messagePair ? "messagePair" : "messageImpair") + " message" +
410 (this.appartientAuClient ? " proprietaire" : "") +
411 (this.clientARepondu ? " repondu" : "") +
412 (this.estUneReponse ? " reponse" : "") +
413 (this.systeme ? " systeme" : "") +
414 (this.ekMaster ? " ekMaster" : "") +
415 "\">" +
416 "<div class=\"outilsMess\"><div class=\"extraire\"></div><div class=\"extraireCompletement\"></div></div><span class=\"entete\">" +
417 "<span class=\"dateComplete\">[<span class=\"date\">" + this.date + "</span>]</span>" +
418 "<span class=\"pseudo\"><span class=\"id\" style=\"display: none\">" + this.auteurId + "</span class=\"ident\">" + identifiant + "</span></span><span class=\"delimitationEntete\"></span>" +
419 XHTMLrepondA +
420 "<span class=\"contenu\">" + this.formateur.traitementComplet(this.contenu, this.pseudo) + "</span>" +
421 "</div>"
422 }
423
424 ///////////////////////////////////////////////////////////////////////////////////////////////////
425
426 /**
427 * Représente une conversation.
428 * @param numConv le numéro (appelé id) de la conversation
429 * @param formateur outil permettant la mise en forme du texte des messages
430 */
431 function Conversation(num, util, formateur, client)
432 {
433 this.num = num // peut changer au cours de la vie de la conversation
434 this.id = Math.floor(Math.random() * 1000000).toString(36)
435 this.util = util
436 this.formateur = formateur
437 this.client = client
438 this.idDernierMessageAffiche = 0
439
440 this.messages = []
441 this.messagesParId = {}
442
443 this.nbMessageMax = conf.nbMessageAffiche // Le nombre de message affiché par page
444
445 $("#conversations").append(
446 '<div id="' + this.getId() + '" class="conversation">\
447 <div class="titre">' +
448 (num == 0 ? '' : '<div class="fermer"></div><div class="lien"></div>') +
449 '<span class="next">&lt;</span><span class="numPage">1</span><span class="prev">&gt;</span>\
450 </div>\
451 </div>'
452 )
453
454 this.util.infoBulle("Aller à la première page", $("#conversations #" + this.getId() + " .numPage"))
455 if (num != 0)
456 {
457 this.util.infoBulle("Créer un lien vers la conversation", $("#conversations #" + this.getId() + " .lien"))
458 this.util.infoBulle("Fermer la conversation", $("#conversations #" + this.getId() + " .fermer"))
459 }
460 }
461
462 Conversation.prototype.enleverMiseEnEvidence = function()
463 {
464 $("#conversations .message").removeClass("cache")
465 }
466
467 Conversation.prototype.colorerEntetes = function()
468 {
469 var messagesReponse = ""
470 var messagesRepondu = ""
471 var messagesProprietaire = ""
472 for (var i = 0; i < this.messages.length; i++)
473 {
474 if (this.messages[i].appartientAuClient)
475 messagesProprietaire += "#" + this.messages[i].getId(this.getId()) + ","
476 else if (this.messages[i].clientARepondu)
477 messagesRepondu += "#" + this.messages[i].getId(this.getId()) + ","
478 else if (this.messages[i].estUneReponse)
479 messagesReponse += "#" + this.messages[i].getId(this.getId()) + ","
480 }
481 $(messagesReponse).addClass("reponse")
482 $(messagesRepondu).addClass("repondu")
483 $(messagesProprietaire).addClass("proprietaire")
484 }
485
486 Conversation.prototype.decolorerEntetes = function()
487 {
488 $("#" + this.getId() + " .message")
489 .removeClass("reponse")
490 .removeClass("repondu")
491 .removeClass("proprietaire")
492 }
493
494 /**
495 * Défini la page courante et s'il l'on se trouve sur la dernière page.
496 * @pageCourante la page courante
497 * @dernierePage true si c'est la dernière page sinon false
498 */
499 Conversation.prototype.setPage = function(pageCourante, dernierePage)
500 {
501 $("#conversations #" + this.getId() + " .numPage").text(pageCourante)
502 $("#conversations #" + this.getId() + " .next").css("display", pageCourante == 1 ? "none" : "inline")
503 $("#conversations #" + this.getId() + " .prev").css("display", dernierePage ? "none" : "inline")
504 }
505
506 /**
507 * Evenement déclanché lors de l'insertion du lien de la conversation dans le message courant.
508 */
509 Conversation.prototype.eventLien = function(fun)
510 {
511 var thisConversation = this
512
513 $("#conversations #" + this.getId() + " .titre .lien").click(
514 function()
515 {
516 fun(thisConversation.num)
517 }
518 )
519 }
520
521 /**
522 * Evenement déclanché lors de la fermeture de la conversation.
523 */
524 Conversation.prototype.eventFermer = function(fun)
525 {
526 var thisConversation = this
527
528 $("#conversations #" + this.getId() + " .titre .fermer").click(
529 function()
530 {
531 fun(thisConversation.num)
532 }
533 )
534 }
535
536 /**
537 * @funNext appelé lorsque l'on passe à la page suivante (de 2 à 1 par exemple)
538 * @funPrev appelé lorsque l'on passe à la page précédente (de 1 à 2 par exemple)
539 * @funReset appelé lorsque l'on souhaite revenir à la page une
540 */
541 Conversation.prototype.setFunPage = function(funNext, funPrev, funReset)
542 {
543 var thisConversation = this
544
545 $("#conversations #" + this.getId() + " .next").click(
546 function() { funNext(thisConversation.num) }
547 )
548 $("#conversations #" + this.getId() + " .prev").click(
549 function() { funPrev(thisConversation.num) }
550 )
551 $("#conversations #" + this.getId() + " .numPage").click(
552 function() { funReset(thisConversation.num) }
553 )
554 }
555
556 /**
557 * Retourne l'id de la conversation sous la forme (par exemple) "conv3".
558 */
559 Conversation.prototype.getId = function()
560 {
561 return "conv" + this.id
562 }
563
564 Conversation.prototype.ajouterMessage = function(message)
565 {
566 this.messages.push(message)
567 this.messagesParId[message.id] = message
568 if (this.messages.length > this.nbMessageMax)
569 delete this.messagesParId[this.messages.shift().id]
570 }
571
572 /**
573 * FIXME : méthode très lourde. ne serait-ce pas mieux de virer d'un coup l'élément conversation et d'en remettre un vide ?
574 */
575 Conversation.prototype.viderMessages = function()
576 {
577 this.messages = new Array()
578 this.idDernierMessageAffiche = 0
579 $("#conversations #" + this.getId() + " .message").remove()
580 }
581
582 Conversation.prototype.idMessageFromString = function(idString)
583 {
584 return parseInt(idString.substr(4 + this.getId().length), 36)
585 }
586
587 /**
588 * Après l'ajout d'un ou plusieurs message cette méthode est appelée afin
589 * d'afficher les messages non-affichés.
590 * FIXME : méthode super lourde, à optimiser.
591 * @param funClickExtract fonction (fun(numMess)) appelée lors du clic sur un bouton "extraire"
592 * @param funClickMessage fonction (fun(numMess)) appelée lors du clic sur un message
593 */
594 Conversation.prototype.flush = function(funClickOuvrirConv, funClickMessage)
595 {
596 var thisConversation = this
597
598 // est-ce que le prochain message est pair ? (permet d'alterner le style des messages)
599 var messagePair = (this.idDernierMessageAffiche == 0 ? true :
600 ($("#conversations #" + this.getId() + " div:first").attr("class").search("messagePair") == -1)
601 )
602
603 // construction de l'XHTML des messages
604 var XHTML = ""
605 for (var i = 0; i < this.messages.length; i++)
606 if (this.messages[i].id > this.idDernierMessageAffiche)
607 {
608 XHTML += this.messages[i].XHTML(messagePair, this.getId())
609 messagePair = !messagePair
610 }
611
612 var DOM = $(XHTML)
613 DOM.each(
614 function()
615 {
616 $(".lienConv", this).click(
617 function(event)
618 {
619 // FIXME : ya pas mieux ?
620 var racine = $(event.target).text()
621 funClickOuvrirConv(parseInt(idString.substring(1, racine.length - 1), 36))
622 return false
623 }
624 )
625
626 thisConversation.util.infoBulle("Extraction de la conversation à partir de ce message", $(".extraire", this))
627 thisConversation.util.infoBulle("Extraction de la conversation complète", $(".extraireCompletement", this))
628
629 // l'id du message
630 var idMess = thisConversation.idMessageFromString($(this).attr("id"))
631
632 $(this).click(
633 function(event)
634 {
635 if ($(event.target).is("a") || $(event.target).parents("#outilsBan").length > 0) return
636
637 // extraction d'une conversation
638 if ($(event.target).is(".extraire"))
639 {
640 funClickOuvrirConv(idMess)
641 return
642 }
643
644 if ($(event.target).is(".extraireCompletement"))
645 {
646 funClickOuvrirConv(thisConversation.messagesParId[idMess].racineId)
647 return
648 }
649
650 funClickMessage(thisConversation.messagesParId[idMess])
651 }
652 )
653
654 // mise en évidence de la conversation
655 $(".entete", this).hover(
656 function()
657 {
658 thisConversation.decolorerEntetes()
659 thisConversation.afficherConversation(idMess)
660 },
661 // quand on sort de l'entête du message la mise en évidence est enlevée
662 function()
663 {
664 thisConversation.enleverMiseEnEvidence()
665 thisConversation.decolorerEntetes()
666 thisConversation.colorerEntetes()
667 }
668 )
669
670 if (thisConversation.client.viewTimes)
671 $(".dateComplete", this).show()
672 else
673 $(".dateComplete", this).hide()
674
675 $("a[@rel*=lightbox]", this).lightBox()
676
677 // les outils de bannissement (uniquement pour les ekMaster)
678 if (thisConversation.client.ekMaster)
679 $(".pseudo", this).hover(
680 function(e)
681 {
682 var userId = parseInt($(".id", this).text())
683 var element = $(this)
684 var h = element.height()
685 var offset = element.offset()
686 thisConversation.util.outilsBan.css("top", offset.top - 2).css("left", offset.left - 2).height(h < 16 ? 16 : h).width(element.width() + 16 * 3 + 4 + 64).prependTo(this).show()
687 $("img", thisConversation.util.outilsBan).unbind("click")
688 $("#slap", thisConversation.util.outilsBan).click(
689 function(e)
690 {
691 thisConversation.client.slap(userId, $("#outilsBan input").val())
692 $("#outilsBan input").val("")
693 $("#outilsBan").hide()
694 }
695 )
696 $("#kick", thisConversation.util.outilsBan).click(
697 function(e)
698 {
699 thisConversation.client.kick(userId, $("#outilsBan input").val())
700 $("#outilsBan input").val("")
701 $("#outilsBan").hide()
702 }
703 )
704 $("#ban", thisConversation.util.outilsBan).click(
705 function(e)
706 {
707 thisConversation.client.ban(userId, $("#outilsBan input").val())
708 $("#outilsBan input").val("")
709 $("#outilsBan").hide()
710 }
711 )
712 },
713 function(e)
714 {
715 $("#outilsBan", this).hide()
716 }
717 )
718 }
719 )
720 DOM.prependTo("#conversations #" + this.getId())
721
722 // enlève les messages exedentaires
723 var nbMessagesAffiche = $("#conversations #" + this.getId() + " .message").size()
724 if (nbMessagesAffiche > this.nbMessageMax)
725 $("#conversations #" + this.getId() + " .message").slice(this.nbMessageMax, nbMessagesAffiche).remove()
726
727 if (this.messages.length > 0)
728 this.idDernierMessageAffiche = this.messages[this.messages.length-1].id
729 }
730
731 /**
732 * Etablit une liste des messages à mettre en evidence et des messages à cacher.
733 * Puis applique un plan diabolique.
734 * @param id l'id du message
735 */
736 Conversation.prototype.afficherConversation = function(id)
737 {
738 var thisConversation = this
739
740 var message = this.messagesParId[id]
741 if (message == undefined) return
742
743 var mess = message.getConversation(this)
744
745 // FIXME : cet appel est très lent
746 $("#conversations #" + this.getId() + " .message").each(
747 function()
748 {
749 var jq = $(this)
750 var statut = mess[thisConversation.idMessageFromString(jq.attr("id"))]
751 if (statut == undefined)
752 jq.addClass("cache")
753 else
754 {
755 jq.removeClass("cache")
756 switch (statut)
757 {
758 // "repondu" et "reponse" sont prioritaitres à "proprietaire"
759 // contrairement à la vue normale (sans mise en évidence d'une conversation)
760 case 3 :
761 jq.addClass("repondu")
762 break;
763 case 2 :
764 jq.addClass("reponse")
765 break;
766 case 1 :
767 jq.addClass("proprietaire")
768 break;
769 }
770 }
771 }
772 )
773 }
774
775 /**
776 * Supprime une conversation.
777 */
778 Conversation.prototype.supprimer = function()
779 {
780 $("#conversations #" + this.getId()).remove()
781 }
782
783 ///////////////////////////////////////////////////////////////////////////////////////////////////
784
785 /**
786 * Représente l'ensemble des messages affichés.
787 * TODO : renommer cette "classe" en 'Conversations'
788 */
789 function Messages(client, formateur, util)
790 {
791 this.client = client
792 this.formateur = formateur
793 this.util = util
794
795 // un ensemble des messages (id) auquel l'utilisateur répond (vider après l'envoie du message courant)
796 this.messagesRepond = {}
797
798 this.conversations = new Array() // les conversations, la première représente la conversation principale
799
800 this.nouvelleConversation(0)
801
802 this.trollIdCourant = 0
803
804 this.pageEvent = new PageEvent("chat", this.util)
805 }
806
807 // les messages insérés dans le document XHTML on leur id prefixé par cette valeur
808 // cela permet de les distinguer des "vrais" messages
809 Messages.prototype.prefixIdMessage = "rep"
810
811 /**
812 * Permet de définir un message comme étant ou n'étant plus un message auquel l'utilisateur
813 * répond.
814 */
815 Messages.prototype.toggleMessageRepond = function(mess)
816 {
817 // est-ce que l'on répond déjà à ce message ? si oui alors on l'enlève de la liste
818 if (mess.id in this.messagesRepond)
819 {
820 this.enleverMessageRepond(mess)
821 return
822 }
823
824 this.ajouterMessageRepond(mess)
825 }
826
827 /**
828 * Enlève tous les messages auquel l'utilisateur souhaite répond.
829 */
830 Messages.prototype.enleverMessagesRepond = function()
831 {
832 for (var messId in this.messagesRepond)
833 this.enleverMessageRepond(this.messagesRepond[messId])
834
835 // on réinitialise pour être sur que tout est bien enlevé
836 this.messagesRepond = {}
837 $("#conversations div.message").removeClass("repondEnEvidence")
838 $("form#posterMessage #repondA .messages").empty()
839 }
840
841 /**
842 * Définit un message comme n'y répondant plus.
843 */
844 Messages.prototype.enleverMessageRepond = function(mess)
845 {
846 $("#" + mess.getId()).removeClass("repondEnEvidence")
847 $("#" + mess.getId(this.prefixIdMessage)).remove()
848 delete this.messagesRepond[mess.id]
849 this.rafraichireNombreMessagesRepond()
850 }
851
852 /**
853 * Définit un message comme y répondant.
854 */
855 Messages.prototype.ajouterMessageRepond = function(mess)
856 {
857 var thisMessages = this
858
859 // est-ce que le message fait partie de la même conversation que les autres messages ?
860 // TODO : solution plus élégante pour prendre un mess parmis messagesRepond !?
861 var mess2
862 for(mess2 in this.messagesRepond){ break; }
863 mess2 = this.messagesRepond[mess2]
864
865 if (mess2 != undefined && mess2.racineId != mess.racineId)
866 {
867 this.util.messageDialogue("Impossible de répondre à deux messages ne faisant pas partie de la même conversation")
868 return
869 }
870
871 $("form#posterMessage #repondA .messages").append(mess.XHTML(undefined, this.prefixIdMessage))
872 this.messagesRepond[mess.id] = mess
873 $("#" + mess.getId(this.prefixIdMessage) + ", #" + mess.getId()).addClass("repondEnEvidence")
874 $("#" + mess.getId(this.prefixIdMessage)).click(
875 function()
876 {
877 $(this).fadeOut("normal", function(){
878 thisMessages.enleverMessageRepond(mess)
879 $("form#posterMessage #repondA .messages").hide()
880 })
881 }
882 )
883 this.rafraichireNombreMessagesRepond()
884 }
885
886 /**
887 * Met à jour le nombre qui indique à l'utilisateur à combien de messages il répond.
888 */
889 Messages.prototype.rafraichireNombreMessagesRepond = function()
890 {
891 // TODO : ya pas mieux pour trouver le nombre d'objet ?
892 var nb = 0
893 for (m in this.messagesRepond)
894 nb += 1
895 $("#posterMessage #repondA .nb").text(nb)
896
897 var boite = $("#posterMessage #repondA")
898 if (nb > 0) boite.show()
899 else boite.hide()
900 }
901
902 /**
903 * Affiche les messages auquel l'utilisateur souhaite répondre au sein des messages des conversations.
904 * Utilisé lorsqu'une conversation est extraite.
905 */
906 Messages.prototype.afficherMessagesRepondConversations = function()
907 {
908 var expr = ""
909 for(var messId in this.messagesRepond)
910 expr += "#" + this.messagesRepond[messId].getId() + ","
911 $(expr).addClass("repondEnEvidence")
912 }
913
914 /**
915 * Crée un message JSON contenant le message demandant un rafraichissement.
916 */
917 Messages.prototype.getJSONrafraichirMessages = function()
918 {
919 var mess = {
920 "message_count" : conf.nbMessageAffiche,
921 "main_page" : this.client.pagePrincipale,
922 "conversations" : this.getJSONConversations(),
923 "troll_id" : this.trollIdCourant
924 }
925
926 if (this.client.cookie != null) mess["cookie"] = this.client.cookie
927 mess["last_message_id"] = this.conversations[0].idDernierMessageAffiche
928
929 return mess
930 }
931
932 Messages.prototype.getJSONConversations = function()
933 {
934 var clientConv = []
935
936 for (var i = 0; i < this.client.conversations.length; i++)
937 {
938 clientConv.push(
939 {
940 root : this.client.conversations[i].root,
941 page : this.client.conversations[i].page,
942 last_message_id : this.conversations[i + 1] == undefined ? 0 : this.conversations[i + 1].idDernierMessageAffiche
943 }
944 )
945 }
946 return clientConv
947 }
948
949 /**
950 * Ajoute un ensemble de messages puis les affiches.
951 * @param elements un tableau d'éléments JSON représentant les messages, voir protocole.txt
952 * @param numConversation le numéro de la conversation auquel appartiennent les messages
953 * @return true si les messages on été ajoutés, false si la conversation n'existe pas et qu'il n'y a pas de message
954 */
955 Messages.prototype.ajouterMessages = function(elements, numConversation)
956 {
957 if (elements["messages"].length == 0)
958 return this.conversations[numConversation] != undefined
959
960 for (var i = 0; i < elements["messages"].length; i++)
961 this.ajouterMessage(elements["messages"][i], numConversation)
962 this.flush(numConversation)
963
964 // renseigne la conversation sur la page courante et si c'est la dernière
965 this.conversations[numConversation].setPage(
966 numConversation == 0 ? this.client.pagePrincipale : this.client.conversations[numConversation - 1].page,
967 elements["last_page"]
968 )
969
970 return true
971 }
972
973 /**
974 * Création d'un nouveau message.
975 * Les message sont données dans l'ordre de leur id.
976 * @param element un element JSON représentant le message
977 * @param numConversation le numéro de la conversation, 0 = principale
978 */
979 Messages.prototype.ajouterMessage = function(element, numConversation)
980 {
981 var thisMessages = this
982
983 // pas d'utilisation de jquery pour des raisons de performance
984 var id = element["id"]
985
986 var message = new Message(
987 this.client,
988 this.formateur,
989 id,
990 element["user_id"],
991 element["root"],
992 element["date"],
993 element["nick"],
994 element["login"],
995 element["content"]
996 )
997
998 message.appartientAuClient = element["owner"]
999 message.clientARepondu = element["answered"]
1000 message.estUneReponse = element["is_a_reply"]
1001 message.systeme = element["system"]
1002 message.setRepondA(element["answer_to"])
1003 message.ekMaster = element["ek_master"]
1004
1005 if (this.conversations[numConversation] == null)
1006 {
1007 this.nouvelleConversation(
1008 numConversation,
1009 function(num) // fermeture de la conversation
1010 {
1011 thisMessages.supprimerConversation(num)
1012 },
1013 function(num) // insertion du lien vers la conversation
1014 {
1015 thisPage.util.replaceSelection(
1016 $("form#posterMessage input.message")[0],
1017 "{" + thisMessages.client.conversations[num-1].root.toString(36) + "}"
1018 )
1019 }
1020 )
1021 }
1022
1023 this.conversations[numConversation].ajouterMessage(message)
1024 }
1025
1026 Messages.prototype.nouvelleConversation = function(num, funFermer, funLien)
1027 {
1028 var thisMessages = this
1029
1030 this.conversations[num] = new Conversation(num, this.util, this.formateur, this.client)
1031
1032 if (funFermer != undefined)
1033 this.conversations[num].eventFermer(funFermer)
1034 if (funLien != undefined)
1035 this.conversations[num].eventLien(funLien)
1036
1037 this.conversations[num].setFunPage(
1038 function(num) // page suivante
1039 {
1040 thisMessages.client.pageSuivante(num - 1)
1041 thisMessages.rafraichirMessages(true)
1042 },
1043 function(num) // page précédente
1044 {
1045 thisMessages.client.pagePrecedente(num - 1)
1046 thisMessages.rafraichirMessages(true)
1047 },
1048 function(num) // retour à la page une
1049 {
1050 if (thisMessages.client.goPremierePage(num - 1))
1051 thisMessages.rafraichirMessages(true)
1052 }
1053 )
1054
1055 this.ajusterLargeurConversations()
1056 }
1057
1058 /**
1059 * Enlève une conversation.
1060 */
1061 Messages.prototype.supprimerConversation = function(num)
1062 {
1063 if (num <= 0 || num >= this.conversations.length) return // la numéro 0 ne peut être supprimé
1064 this.conversations[num].supprimer()
1065
1066 // décalage TODO : supprimer le dernier élément
1067 for (var i = num; i < this.conversations.length - 1; i++)
1068 {
1069 this.conversations[i] = this.conversations[i+1]
1070 this.conversations[i].num -= 1
1071 }
1072 this.conversations.pop()
1073 this.ajusterLargeurConversations()
1074
1075 this.client.supprimerConversation(num-1)
1076
1077 this.rafraichirMessages(true)
1078 }
1079
1080 /**
1081 * Ajuste la largeur des conversations en fonction de leur nombre. modifie l'attribut CSS 'width'.
1082 */
1083 Messages.prototype.ajusterLargeurConversations = function()
1084 {
1085 var largeurPourcent = (100 / this.conversations.length)
1086 // le "- 0.01" evite que IE se chie dessus lamentablement et affiche les conversations les unes au dessus des autres
1087 if($.browser["msie"])
1088 largeurPourcent -= 0.05
1089 $("#conversations .conversation").css("width", largeurPourcent + "%")
1090 }
1091
1092 /**
1093 * Demande à toutes les conversations de se flusher (afficher les messages non-affichés).
1094 */
1095 Messages.prototype.flushAll = function()
1096 {
1097 for (var i = 0; i < this.conversations.length; i++)
1098 this.flush(i)
1099 }
1100
1101 /**
1102 * Demande à une conversation de se flusher.
1103 */
1104 Messages.prototype.flush = function(numConv)
1105 {
1106 var thisMessages = this
1107
1108 this.conversations[numConv].flush(
1109 function(racine) // appelé lors d'un clic sur un lien vers une conversation
1110 {
1111 thisMessages.ouvrirConversation(racine)
1112 },
1113 // 'Mess' correspond à un objet de type 'Message'
1114 function(Mess) // appelé lors d'un clic sur un message
1115 {
1116 // met ou enlève la mise en evidence du message
1117 thisMessages.toggleMessageRepond(Mess)
1118 // donne le focus à la ligne de saisie
1119 $("form input.message").focus()
1120 }
1121 )
1122 }
1123
1124 Messages.prototype.ouvrirConversation = function(racine)
1125 {
1126 if (this.client.ajouterConversation(racine))
1127 this.rafraichirMessages(true)
1128 else
1129 this.util.messageDialogue("Cette conversation est déjà ouverte")
1130 }
1131
1132 Messages.prototype.viderMessages = function()
1133 {
1134 for (var i = 0; i < this.conversations.length; i++)
1135 this.conversations[i].viderMessages()
1136 }
1137
1138 /**
1139 * Met à jour les messages de manière continue.
1140 * (AJAX-Comet-style proof)
1141 * @param vider vide tous les messages avant d'afficher les nouveaux
1142 */
1143 Messages.prototype.rafraichirMessages = function(vider)
1144 {
1145 var thisMessages = this
1146
1147 if (vider == undefined)
1148 vider = false
1149
1150 if (vider)
1151 for (var i = 0; i < this.conversations.length; i++)
1152 this.conversations[i].idDernierMessageAffiche = 0
1153
1154 this.pageEvent.waitEvent(
1155 function() { return thisMessages.getJSONrafraichirMessages() },
1156 {
1157 "new_troll" :
1158 function(data)
1159 {
1160 thisMessages.trollIdCourant = data["troll_id"]
1161 $("#trollCourant .troll").html(thisMessages.formateur.traitementComplet(data["content"])).unbind("click").click(
1162 function()
1163 {
1164 thisMessages.ouvrirConversation(data["message_id"])
1165 }
1166 )
1167
1168 $("#trollCourant .troll a[@rel*=lightbox]").lightBox()
1169 },
1170 "new_messages" :
1171 function(data)
1172 {
1173 if (vider)
1174 thisMessages.viderMessages()
1175 // ajoute les messages reçus à leur conversation respective
1176 for (var numConv = 0; numConv < data["conversations"].length; numConv++)
1177 {
1178 if (!thisMessages.ajouterMessages(data["conversations"][numConv], numConv))
1179 {
1180 thisMessages.util.messageDialogue("La conversation {" + thisMessages.client.conversations[numConv -1].root.toString(36) + "} n'existe pas")
1181 thisMessages.client.supprimerConversation(numConv - 1)
1182 }
1183 }
1184 if (vider)
1185 thisMessages.afficherMessagesRepondConversations()
1186 vider = false
1187 }
1188 }
1189 )
1190 }