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