MOD avancement dans la Grande Restructuration
[euphorik.git] / js / pageMinichat / conversation.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
20 /**
21 * Représente une conversation.
22 * Une conversation, au niveau XHTML, est formé de deux partie, le titre et les messages.
23 * Le titre comprend la navigation par page, un bouton pour la fermer, un bouton pour la plier, un bouton
24 * pour créer un lien ainsi que le premier message.
25 * @param conversations l'ensemble des conversations
26 * @param num le numéro de la conversation
27 */
28 euphorik.Conversation = function(conversations, num) {
29 this.conversations = conversations;
30
31 // peut changer au cours de la vie de la conversation, n'est pas un id !
32 this.num = num;
33
34 this.id = Math.floor(Math.random() * 1000000).toString(36);
35
36 this.util = this.conversations.util;
37 this.formateur = this.conversations.formateur;
38 this.client = this.conversations.client;
39
40 this.idDernierMessageAffiche = 0;
41 this.racine = undefined;
42
43 this.messages = [];
44 this.messagesParId = {};
45
46 this.nbMessageMax = euphorik.conf.nbMessageAffiche; // Le nombre de message affiché par page
47
48 var messagesXHTML = '<div class="messages"></div>';
49 var messageRacineXHTML = '<div class="messageRacine"></div>';
50 var reverse = this.client.chatOrder === "reverse";
51
52 var XHTML =
53 '<td class="conversation" id="' + this.getId() + '">' +
54 (reverse ? messagesXHTML : "") +
55 '<div class="titre">' +
56 (reverse ? messageRacineXHTML : "") +
57 '<div class="nav">' +
58 (num === 0 ? '' : '<div class="fermer"></div><div class="lien"></div><div class="reduire"></div>') +
59 '<span class="next">&lt;</span><span class="numPage">1</span><span class="prev">&gt;</span>' +
60 '</div>' +
61 (reverse ? "" : messageRacineXHTML) +
62 '</div>' +
63 (reverse ? "" : messagesXHTML) +
64 '</td>';
65
66 $("#conversations tr").append(XHTML);
67
68 this.util.infoBulle("Aller à la première page", $("#" + this.getId() + " .numPage"), euphorik.Util.positionBulleType.haut);
69 if (num !== 0) {
70 this.util.infoBulle("Créer un lien vers la conversation", $("#" + this.getId() + " .lien"));
71 this.util.infoBulle("Fermer la conversation", $("#" + this.getId() + " .fermer"));
72 }
73 };
74
75 /**
76 * @racine un message représentant la racine de la conversation, vaut undefined pour la conversation générale
77 */
78 euphorik.Conversation.prototype.setRacine = function(racineElement) {
79 this.racine = new euphorik.Message(this.client, this.formateur, racineElement);
80 };
81
82 /**
83 * Met à jour la racine, décide de l'afficher ou non.
84 * On l'affiche uniquement si le message racine n'est pas déjà affiché sur la liste des messages.
85 */
86 euphorik.Conversation.prototype.majRacine = function() {
87 if (!this.racine) {
88 return;
89 }
90
91 if (!(this.racine.id in this.messagesParId)) {
92 this.messagesParId[this.racine.id] = this.racine;
93 var element = $(this.racine.XHTML(true, this.getId()));
94 this.attacherEventsSurMessage(element);
95 $("#" + this.getId() + " .titre .messageRacine").html(element);
96 }
97 };
98
99 euphorik.Conversation.prototype.enleverMiseEnEvidence = function() {
100 $("#" + this.getId() + " .message").removeClass("cache");
101 };
102
103 euphorik.Conversation.prototype.colorerEntetes = function() {
104 var thisConversation = this;
105
106 var messagesReponse = "";
107 var messagesRepondu = "";
108 var messagesProprietaire = "";
109 this.messages.each(function(i, mess) {
110 if (mess.appartientAuClient) {
111 messagesProprietaire += "#" + mess.getId(thisConversation.getId()) + ",";
112 } else if (mess.clientARepondu) {
113 messagesRepondu += "#" + mess.getId(thisConversation.getId()) + ",";
114 } else if (mess.estUneReponse) {
115 messagesReponse += "#" + mess.getId(thisConversation.getId()) + ",";
116 }
117 });
118 $(messagesReponse).addClass("reponse");
119 $(messagesRepondu).addClass("repondu");
120 $(messagesProprietaire).addClass("proprietaire");
121 };
122
123 euphorik.Conversation.prototype.decolorerEntetes = function() {
124 $("#" + this.getId() + " .messages .message")
125 .removeClass("reponse")
126 .removeClass("repondu")
127 .removeClass("proprietaire");
128 };
129
130 /**
131 * Défini la page courante et s'il l'on se trouve sur la dernière page.
132 * @pageCourante la page courante
133 * @dernierePage true si c'est la dernière page sinon false
134 */
135 euphorik.Conversation.prototype.setPage = function(pageCourante, dernierePage) {
136 $("#" + this.getId() + " .numPage").text(pageCourante);
137 $("#" + this.getId() + " .next").css("display", pageCourante === 1 ? "none" : "inline");
138 $("#" + this.getId() + " .prev").css("display", dernierePage ? "none" : "inline");
139 };
140
141 /**
142 * Evenement déclanché lors de l'insertion du lien de la conversation dans le message courant.
143 */
144 euphorik.Conversation.prototype.eventLien = function(fun) {
145 var thisConversation = this;
146
147 $("#" + this.getId() + " .titre .lien").click(
148 function() {
149 fun(thisConversation.num);
150 }
151 );
152 };
153
154 /**
155 * Evenement déclanché lors de la fermeture de la conversation.
156 */
157 euphorik.Conversation.prototype.eventFermer = function(fun) {
158 var thisConversation = this;
159
160 $("#" + this.getId() + " .titre .fermer").click(
161 function() {
162 fun(thisConversation.num);
163 }
164 );
165 };
166
167 /**
168 * @funNext appelé lorsque l'on passe à la page suivante (de 2 à 1 par exemple)
169 * @funPrev appelé lorsque l'on passe à la page précédente (de 1 à 2 par exemple)
170 * @funReset appelé lorsque l'on souhaite revenir à la page une
171 */
172 euphorik.Conversation.prototype.setFunPage = function(funNext, funPrev, funReset) {
173 var thisConversation = this;
174
175 $("#" + this.getId() + " .next").click(
176 function() { funNext(thisConversation.num); }
177 );
178 $("#" + this.getId() + " .prev").click(
179 function() { funPrev(thisConversation.num); }
180 );
181 $("#" + this.getId() + " .numPage").click(
182 function() { funReset(thisConversation.num); }
183 );
184 };
185
186 /**
187 * Retourne l'id de la conversation sous la forme (par exemple) "conv3".
188 */
189 euphorik.Conversation.prototype.getId = function() {
190 return "conv" + this.id;
191 };
192
193 euphorik.Conversation.prototype.ajouterMessage = function(message) {
194 this.messages.push(message);
195 this.messagesParId[message.id] = message;
196
197 // enlève les messages exedentaires
198 if (this.messages.length > this.nbMessageMax) {
199 delete this.messagesParId[this.messages.shift().id];
200 }
201 };
202
203 /**
204 * 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 ?
205 */
206 euphorik.Conversation.prototype.viderMessages = function() {
207 this.messages = [];
208 this.messagesParId = {};
209 this.idDernierMessageAffiche = 0;
210 $("#" + this.getId() + " .messages .message").remove();
211
212 // enlève également la racine
213 $("#" + this.getId() + " .titre .messageRacine").empty();
214 };
215
216 euphorik.Conversation.prototype.idMessageFromString = function(idString) {
217 return parseInt(idString.substr(4 + this.getId().length), 36);
218 };
219
220 /**
221 * Après l'ajout d'un ou plusieurs message cette méthode est appelée afin
222 * d'afficher les messages non-affichés.
223 * FIXME : méthode super lourde, à optimiser.
224 */
225 euphorik.Conversation.prototype.flush = function() {
226 var thisConversation = this;
227 var reverse = this.client.chatOrder === "reverse";
228
229 // est-ce que le prochain message est pair ? (permet d'alterner le style des messages)
230 var messagePair = (this.idDernierMessageAffiche === 0 ? true :
231 ($("#" + this.getId() + " .messages div:" + (reverse ? "first" : "last")).attr("class").search("messagePair") === -1)
232 );
233
234 // construction de l'XHTML des messages
235 var XHTML = "";
236 this.messages.each(function(i, mess) {
237 if (mess.id > thisConversation.idDernierMessageAffiche) {
238 XHTML += mess.XHTML(messagePair, thisConversation.getId());
239 messagePair = !messagePair;
240 }
241 });
242
243 var DOM = $(XHTML);
244
245 // pour chaque nouveau message au niveau du document on lui assigne ses événements
246 DOM.each(function() { thisConversation.attacherEventsSurMessage(this); });
247
248 if (reverse) {
249 DOM.prependTo("#" + this.getId() + " .messages");
250 } else {
251 DOM.appendTo("#" + this.getId() + " .messages");
252 }
253
254 // enlève les messages exedentaires au niveau du document
255 var nbMessagesAffiche = $("#" + this.getId() + " .messages .message").size();
256 if (nbMessagesAffiche > this.nbMessageMax) {
257 if (reverse) {
258 $("#" + this.getId() + " .messages .message").slice(this.nbMessageMax, nbMessagesAffiche).remove();
259 } else {
260 $("#" + this.getId() + " .messages .message").slice(0, nbMessagesAffiche - this.nbMessageMax).remove();
261 }
262 }
263
264 if (this.messages.length > 0) {
265 this.idDernierMessageAffiche = this.messages[this.messages.length-1].id;
266 }
267
268 // met à jour la racine de la conversation
269 this.majRacine();
270 };
271
272 /**
273 * Attache des événements à un message donné.
274 * @element le message du DOM
275 */
276 euphorik.Conversation.prototype.attacherEventsSurMessage = function(element) {
277 // l'id du message
278 var idMess = this.idMessageFromString($(element).attr("id"));
279
280 this.util.infoBulle("Extraction de la conversation à partir de ce message", $(".extraire", element));
281 this.util.infoBulle("Extraction de la conversation complète", $(".extraireCompletement", element));
282
283 var thisConversation = this;
284 $(".lienConv", element).click(
285 function(event) {
286 // FIXME : ya pas mieux ?
287 var racine = $(event.target).text();
288 thisConversation.conversations.ouvrirConversation(parseInt(idString.substring(1, racine.length - 1), 36));
289 return false;
290 }
291 );
292
293 $(element).click(
294 function(event) {
295 if ($(event.target).is("a") || $(event.target).parents("#outilsBan").length > 0) {
296 return;
297 }
298
299 // extraction d'une conversation
300 if ($(event.target).is(".extraire")) {
301 thisConversation.conversations.ouvrirConversation(idMess);
302 return;
303 }
304
305 if ($(event.target).is(".extraireCompletement")) {
306 thisConversation.conversations.ouvrirConversation(thisConversation.messagesParId[idMess].racineId);
307 return;
308 }
309
310 // met ou enlève la mise en evidence du message
311 thisConversation.conversations.toggleMessageRepond(thisConversation.messagesParId[idMess]);
312
313 // donne le focus à la ligne de saisie
314 $("form input.message").focus();
315 }
316 );
317
318 // mise en évidence de la conversation
319 $(".entete", element).hover(
320 function() {
321 thisConversation.decolorerEntetes();
322 thisConversation.afficherConversation(idMess);
323 },
324 // quand on sort de l'entête du message la mise en évidence est enlevée
325 function() {
326 thisConversation.enleverMiseEnEvidence();
327 thisConversation.decolorerEntetes();
328 thisConversation.colorerEntetes();
329 }
330 );
331
332 // est-ce que l'on affichage la date du message ?
333 if (thisConversation.client.viewTimes) {
334 $(".dateComplete", element).show();
335 } else {
336 $(".dateComplete", element).hide();
337 }
338
339 $("a[@rel*=lightbox]", element).lightBox();
340
341 // les outils de bannissement (uniquement pour les ekMaster)
342 if (thisConversation.client.ekMaster) {
343 $(".pseudo", element).hover(
344 function(e) {
345 var userId = parseInt($(".id", this).text(), 10);
346 var pseudo = $(this);
347 var h = pseudo.outerHeight();
348 var offset = pseudo.offset();
349 // TODO : calculer automatiquement la largeur plutôt que d'inscrire des valeurs en brut'
350 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();
351 $("img", thisConversation.util.outilsBan).unbind("click");
352 $("#slap", thisConversation.util.outilsBan).click(
353 function() {
354 thisConversation.client.slap(userId, $("#outilsBan input").val());
355 $("#outilsBan input").val("");
356 $("#outilsBan").hide();
357 }
358 );
359 $("#kick", thisConversation.util.outilsBan).click(
360 function() {
361 thisConversation.client.kick(userId, $("#outilsBan input").val());
362 $("#outilsBan input").val("");
363 $("#outilsBan").hide();
364 }
365 );
366 $("#ban", thisConversation.util.outilsBan).click(
367 function() {
368 thisConversation.client.ban(userId, $("#outilsBan input").val());
369 $("#outilsBan input").val("");
370 $("#outilsBan").hide();
371 }
372 );
373 },
374 function() {
375 $("#outilsBan", this).hide();
376 }
377 );
378 }
379 };
380
381 /**
382 * Etablit une liste des messages à mettre en evidence et des messages à cacher.
383 * Puis applique un plan diabolique.
384 * @param id l'id du message
385 */
386 euphorik.Conversation.prototype.afficherConversation = function(id) {
387 var thisConversation = this;
388
389 var message = this.messagesParId[id];
390 if (!message) {
391 return;
392 }
393
394 var mess = message.getConversation(this);
395
396 // FIXME : cet appel est très lent
397 $("#" + this.getId() + " .messages .message").each(
398 function() {
399 var jq = $(this);
400 var statut = mess[thisConversation.idMessageFromString(jq.attr("id"))];
401 if (!statut) {
402 jq.addClass("cache");
403 } else {
404 jq.removeClass("cache");
405 switch (statut) {
406 // "repondu" et "reponse" sont prioritaitres à "proprietaire"
407 // contrairement à la vue normale (sans mise en évidence d'une conversation)
408 case 3 :
409 jq.addClass("repondu");
410 break;
411 case 2 :
412 jq.addClass("reponse");
413 break;
414 case 1 :
415 jq.addClass("proprietaire");
416 break;
417 }
418 }
419 }
420 );
421 };
422
423 /**
424 * Supprime une conversation.
425 * Ne l'enlève pas du DOM.
426 */
427 euphorik.Conversation.prototype.supprimer = function() {
428 $("#" + this.getId()).remove();
429 };