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