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