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