2 // Copyright 2008 Grégory Burri
4 // This file is part of Euphorik.
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.
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.
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/>.
19 /*jslint laxbreak:true */
22 * Représente une conversation.
23 * Une conversation, au niveau XHTML, est formé de deux partie, le titre et les messages.
24 * Le titre comprend la navigation par page, un bouton pour la fermer, un bouton pour la plier, un bouton
25 * pour créer un lien ainsi que le premier message.
26 * @param conversations l'ensemble des conversations
27 * @param num le numéro de la conversation
29 euphorik
.Conversation = function(conversations
, num
) {
30 this.conversations
= conversations
;
32 // peut changer au cours de la vie de la conversation, n'est pas un id !
35 this.id
= Math
.floor(Math
.random() * 1000000).toString(36);
37 this.util
= this.conversations
.util
;
38 this.formater
= this.conversations
.formater
;
39 this.client
= this.conversations
.client
;
41 this.idDernierMessageAffiche
= 0;
42 this.racine
= undefined;
45 this.messagesParId
= {};
47 this.nbMessageMax
= euphorik
.conf
.nbMessageAffiche
; // Le nombre de message affiché par page
49 var messagesXHTML
= '<div class="messages"></div>';
50 var messageRacineXHTML
= '<div class="messageRacine"></div>';
51 var reverse
= this.client
.chatOrder
=== "reverse";
54 '<td class="conversation" id="' + this.getId() + '">' +
55 (reverse
? messagesXHTML : "") +
56 '<div class="titre">' +
57 (reverse
? messageRacineXHTML : "") +
59 (this.num
=== 0 ? '' : '<div class="fermer"></div><div class="creerLien"></div>') + //</div><div class="reduire">
60 '<span class="next"><</span><span class="numPage">1</span><span class="prev">></span>' +
62 (reverse
? "" : messageRacineXHTML
) +
64 (reverse
? "" : messagesXHTML
) +
65 //'<div class="messageReduit" style="height:200px; width:50px"></div>' +
68 $("#conversations tr").append(XHTML
);
71 this.util
.infoBulle("Aller à la première page", $("#" + this.getId() + " .titre .numPage"), euphorik
.Util
.positionBulleType
.haut
);
73 this.util
.infoBulle("Créer un lien vers la conversation", $("#" + this.getId() + " .titre .creerLien"));
74 this.util
.infoBulle("Fermer la conversation", $("#" + this.getId() + " .titre .fermer"));
77 // les différents événements liés à la conversation
78 var thisConversation
= this;
79 $("#" + this.getId() + " .titre .creerLien").click(function() {
80 thisConversation
.util
.replaceSelection(
81 $("form#posterMessage input.message")[0],
82 "{" + thisConversation
.client
.conversations
[thisConversation
.num
- 1].root
.toString(36) + "}"
85 $("#" + this.getId() + " .titre .fermer").click(function() {
86 thisConversation
.conversations
.supprimerConversation(thisConversation
.num
);
89 $("#" + this.getId() + " .titre .reduire").click(function() {
90 $("#" + thisConversation.getId() + " .titre, #" + thisConversation.getId() + " .messages").hide()
91 var e = $("#" + thisConversation.getId() + " .messageReduit");
92 e.get()[0].innerHTML = thisConversation.getMessageReduit()
96 euphorik.Conversation.prototype.getMessageReduit = function() {
98 '<svg:svg version="1.1" baseProfile="full" width="100px" height="200px">' +
99 '<svg:image x="10" y="10" height="10" width="10" class="fermer" />' +
100 '<svg:text transform="rotate(-90)" y="15" x="-200" >' +
101 'Blabla blablablabla bla blabla ..' +
107 * @racine un message représentant la racine de la conversation, vaut undefined pour la conversation générale
109 euphorik
.Conversation
.prototype.setRacine = function(racineElement
) {
110 this.racine
= new euphorik
.Message(this.client
, this.formater
, racineElement
);
114 * Met à jour la racine, décide de l'afficher ou non.
115 * On l'affiche uniquement si le message racine n'est pas déjà affiché sur la liste des messages.
117 euphorik
.Conversation
.prototype.majRacine = function() {
122 if (!(this.racine
.id
in this.messagesParId
)) {
123 this.messagesParId
[this.racine
.id
] = this.racine
;
124 var element
= $(this.racine
.XHTML(true, this.getId()));
125 this.attacherEventsSurMessage(element
);
126 $("#" + this.getId() + " .titre .messageRacine").html(element
);
130 euphorik
.Conversation
.prototype.enleverMiseEnEvidence = function() {
131 $("#" + this.getId() + " .message").removeClass("cache");
134 euphorik
.Conversation
.prototype.colorerEntetes = function() {
135 var thisConversation
= this;
137 var messagesReponse
= "";
138 var messagesRepondu
= "";
139 var messagesProprietaire
= "";
140 this.messages
.each(function(i
, mess
) {
141 if (mess
.appartientAuClient
) {
142 messagesProprietaire
+= "#" + mess
.getId(thisConversation
.getId()) + ",";
143 } else if (mess
.clientARepondu
) {
144 messagesRepondu
+= "#" + mess
.getId(thisConversation
.getId()) + ",";
145 } else if (mess
.estUneReponse
) {
146 messagesReponse
+= "#" + mess
.getId(thisConversation
.getId()) + ",";
149 $(messagesReponse
).addClass("reponse");
150 $(messagesRepondu
).addClass("repondu");
151 $(messagesProprietaire
).addClass("proprietaire");
154 euphorik
.Conversation
.prototype.decolorerEntetes = function() {
155 $("#" + this.getId() + " .messages .message")
156 .removeClass("reponse")
157 .removeClass("repondu")
158 .removeClass("proprietaire");
162 * Défini la page courante et s'il l'on se trouve sur la dernière page.
163 * @pageCourante la page courante
164 * @dernierePage true si c'est la dernière page sinon false
166 euphorik
.Conversation
.prototype.setPage = function(pageCourante
, dernierePage
) {
167 $("#" + this.getId() + " .numPage").text(pageCourante
);
168 $("#" + this.getId() + " .next").css("display", pageCourante
=== 1 ? "none" : "inline");
169 $("#" + this.getId() + " .prev").css("display", dernierePage
? "none" : "inline");
173 * @funNext appelé lorsque l'on passe à la page suivante (de 2 à 1 par exemple)
174 * @funPrev appelé lorsque l'on passe à la page précédente (de 1 à 2 par exemple)
175 * @funReset appelé lorsque l'on souhaite revenir à la page une
177 euphorik
.Conversation
.prototype.setFunPage = function(funNext
, funPrev
, funReset
) {
178 var thisConversation
= this;
180 $("#" + this.getId() + " .next").click(
181 function() { funNext(thisConversation
.num
); }
183 $("#" + this.getId() + " .prev").click(
184 function() { funPrev(thisConversation
.num
); }
186 $("#" + this.getId() + " .numPage").click(
187 function() { funReset(thisConversation
.num
); }
192 * Retourne l'id de la conversation sous la forme (par exemple) "conv3".
194 euphorik
.Conversation
.prototype.getId = function() {
195 return "conv" + this.id
;
199 * Après avoir créé un message celui ci est ajouté à une conversation via cette méthode.
201 euphorik
.Conversation
.prototype.ajouterMessage = function(message
) {
202 this.messages
.push(message
);
203 this.messagesParId
[message
.id
] = message
;
205 // enlève le message exedentaire si nécessaire
206 if (this.messages
.length
> this.nbMessageMax
) {
207 delete this.messagesParId
[this.messages
.shift().id
];
210 // met à jour le membre 'estReponduPar' des messages de la conversation
211 for (var i
= 0; i
< this.messages
.length
- 1; i
++) {
212 var autreMess
= this.messages
[i
];
213 if (autreMess
.id
in message
.repondA
) {
214 autreMess
.estReponduPar
[message
.id
] = true;
220 * 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 ?
222 euphorik
.Conversation
.prototype.viderMessages = function() {
224 this.messagesParId
= {};
225 this.idDernierMessageAffiche
= 0;
226 $("#" + this.getId() + " .messages .message").remove();
228 // enlève également la racine
229 $("#" + this.getId() + " .titre .messageRacine").empty();
232 euphorik
.Conversation
.prototype.idMessageFromString = function(idString
) {
233 return parseInt(idString
.substr(4 + this.getId().length
), 36);
237 * Après l'ajout d'un ou plusieurs message cette méthode est appelée afin
238 * d'afficher les messages non-affichés.
239 * FIXME : méthode super lourde, à optimiser.
241 euphorik
.Conversation
.prototype.flush = function() {
242 var thisConversation
= this;
243 var reverse
= this.client
.chatOrder
=== "reverse";
245 var messagePair
= (this.idDernierMessageAffiche
=== 0 ? true :
246 ($("#" + this.getId() + " .messages div:" + (reverse
? "first" : "last")).attr("class").search("messagePair") === -1)
249 // permet d'itérer sur les nouveaux messages à afficher
250 var pourChaqueNouveauMessage = function(f
) {
251 thisConversation
.messages
.each(function(i
, mess
) {
252 if (mess
.id
> thisConversation
.idDernierMessageAffiche
) {
258 // construction de l'XHTML des messages
260 pourChaqueNouveauMessage(function(mess
) {
261 XHTML
+= mess
.XHTML(messagePair
, thisConversation
.getId());
262 messagePair
= !messagePair
;
267 // pour chaque nouveau message au niveau du document on lui assigne ses événements
268 DOM
.each(function() { thisConversation
.attacherEventsSurMessage(this); });
271 DOM
.prependTo("#" + this.getId() + " .messages");
273 DOM
.appendTo("#" + this.getId() + " .messages");
276 // enlève les messages exedentaires au niveau du document
277 var nbMessagesAffiche
= $("#" + this.getId() + " .messages .message").size();
278 if (nbMessagesAffiche
> this.nbMessageMax
) {
280 $("#" + this.getId() + " .messages .message").slice(this.nbMessageMax
, nbMessagesAffiche
).remove();
282 $("#" + this.getId() + " .messages .message").slice(0, nbMessagesAffiche
- this.nbMessageMax
).remove();
286 // met à jour la classe des messages auquels repondent les nouveaux messages
287 // dans le cas où ce message appartient au client courant (c'est un peu de la triche) TODO : ya mieux ?
288 pourChaqueNouveauMessage(function(mess
) {
289 if (mess
.auteurId
=== thisConversation
.client
.id
) {
290 objectEach(mess
.repondA
, function(messId
) {
291 var mess
= thisConversation
.messagesParId
[messId
];
293 mess
.clientARepondu
= true;
294 $("#conversations #" + mess
.getId(thisConversation
.getId())).addClass("repondu");
300 if (this.messages
.length
> 0) {
301 this.idDernierMessageAffiche
= this.messages
[this.messages
.length
-1].id
;
304 // met à jour la racine de la conversation
309 * Attache des événements à un message donné.
310 * Utilisé lorsqu'un message est ajouté au document HTML.
311 * @element le message du DOM
313 euphorik
.Conversation
.prototype.attacherEventsSurMessage = function(element
) {
315 var idMess
= this.idMessageFromString($(element
).attr("id"));
317 if (idMess
in this.conversations
.messagesRepond
) {
318 $(element
).addClass("repondEnEvidence");
321 var thisConversation
= this;
322 $(".lienConv", element
).click(
324 // FIXME : ya pas mieux ?
325 var racine
= $(event
.target
).text();
326 thisConversation
.conversations
.ouvrirConversation(parseInt(racine
.substring(1, racine
.length
- 1), 36));
331 $(element
).click(function(event
) {
332 if ($(event
.target
).is("a") || $(event
.target
).parents("#outilsBan").length
> 0) {
334 } else if ($(event
.target
).is(".extraire")) {
335 thisConversation
.util
.outilsMessage
.hide();
336 thisConversation
.conversations
.ouvrirConversation(idMess
);
338 } else if ($(event
.target
).is(".extraireCompletement")) {
339 thisConversation
.util
.outilsMessage
.hide();
340 thisConversation
.conversations
.ouvrirConversation(thisConversation
.messagesParId
[idMess
].racineId
);
344 // met ou enlève la mise en evidence du message
345 thisConversation
.conversations
.toggleMessageRepond(thisConversation
.messagesParId
[idMess
]);
347 // donne le focus à la ligne de saisie
348 $("form input.message").focus();
349 }).hover(function() { // affiche les outils liées au message
350 var top
= $(this).offset().top
;
351 var left
= $(this).offset().left
+ $(this).outerWidth() - thisConversation
.util
.outilsMessage
.outerWidth();
352 $(".extraire", thisConversation
.util
.outilsMessage
).unbind();
353 $(".extraireCompletement", thisConversation
.util
.outilsMessage
).unbind();
354 // TODO : ces deux appels devraient se trouver dans "PageMinichat" lors de la création de thisConversation.util
355 // malheureusement lors de la modification du DOM (suppression des conversations) les événements liés sont supprimés
356 thisConversation
.util
.infoBulle("Extraction de la conversation à partir de ce message", $(".extraire", thisConversation
.util
.outilsMessage
));
357 thisConversation
.util
.infoBulle("Extraction de la conversation complète", $(".extraireCompletement", thisConversation
.util
.outilsMessage
));
358 thisConversation
.util
.outilsMessage
.css("top", top
).css("left", left
).prependTo(this).show();
360 thisConversation
.util
.outilsMessage
.hide();
363 // mise en évidence de la conversation
364 $(".entete", element
).hover(
366 thisConversation
.decolorerEntetes();
367 thisConversation
.afficherConversation(idMess
);
369 // quand on sort de l'entête du message la mise en évidence est enlevée
371 thisConversation
.enleverMiseEnEvidence();
372 thisConversation
.decolorerEntetes();
373 thisConversation
.colorerEntetes();
377 // est-ce que l'on affichage la date du message ?
378 if (thisConversation
.client
.viewTimes
) {
379 $(".dateComplete", element
).show();
381 $(".dateComplete", element
).hide();
384 $("a[@rel*=lightbox]", element
).lightBox();
386 // les outils de bannissement (uniquement pour les ekMaster)
387 if (thisConversation
.client
.ekMaster
) {
388 $(".pseudo", element
).hover(
390 var userId
= parseInt($(".id", this).text(), 10);
391 var pseudo
= $(this);
392 var h
= pseudo
.outerHeight();
393 var offset
= pseudo
.offset();
394 // TODO : calculer automatiquement la largeur plutôt que d'inscrire des valeurs en brut'
395 thisConversation
.util
.outilsBan
.css("top", offset
.top
- 2).css("left", offset
.left
- 2).height(h
< 16 ? 16 : h
).width(pseudo
.outerWidth() + 16 * 3 + 12 + 64).prependTo(this).show();
396 $("img", thisConversation
.util
.outilsBan
).unbind("click");
397 $("#slap", thisConversation
.util
.outilsBan
).click(
399 thisConversation
.client
.slap(userId
, $("#outilsBan input").val());
400 $("#outilsBan input").val("");
401 $("#outilsBan").hide();
404 $("#kick", thisConversation
.util
.outilsBan
).click(
406 thisConversation
.client
.kick(userId
, $("#outilsBan input").val());
407 $("#outilsBan input").val("");
408 $("#outilsBan").hide();
411 $("#ban", thisConversation
.util
.outilsBan
).click(
413 thisConversation
.client
.ban(userId
, $("#outilsBan input").val());
414 $("#outilsBan input").val("");
415 $("#outilsBan").hide();
420 thisConversation
.util
.outilsBan
.hide();
427 * Etablit une liste des messages à mettre en evidence et des messages à cacher.
428 * Puis applique un plan diabolique.
429 * @param id l'id du message
431 euphorik
.Conversation
.prototype.afficherConversation = function(id
) {
432 var thisConversation
= this;
434 var message
= this.messagesParId
[id
];
439 var mess
= message
.getConversation(this);
441 // FIXME : cet appel est très lent
442 $("#" + this.getId() + " .messages .message").each(
445 var statut
= mess
[thisConversation
.idMessageFromString(jq
.attr("id"))];
446 if (statut
=== undefined) {
447 jq
.addClass("cache");
449 jq
.removeClass("cache");
451 // "repondu" et "reponse" sont prioritaitres à "proprietaire"
452 // contrairement à la vue normale (sans mise en évidence d'une conversation)
454 jq
.addClass("repondu");
457 jq
.addClass("reponse");
460 jq
.addClass("proprietaire");
469 * Supprime la conversation du DOM.
471 euphorik
.Conversation
.prototype.supprimer = function() {
472 $("#" + this.getId()).remove();