MOD avancement dans l'affichage des conversations (pas fini)
[euphorik.git] / js / pageMinichat.js
1 // coding: utf-8
2
3 function PageMinichat(client, formateur, util)
4 {
5 this.nom = "minichat"
6
7 this.client = client
8 this.formateur = formateur
9 this.util = util
10
11 this.regexMessageTagMatch = /\{.*?\}>/g
12 this.regexMessageTagReplace = /^(.*?\{.*?\}>)*/
13
14 this.messages = new Messages(this.client, this.formateur, this.util)
15 }
16
17 PageMinichat.prototype.contenu = function()
18 {
19 return '\
20 <div id="smiles"></div>\
21 <form method="post" action="">\
22 <p>\
23 <input class="captcha" name="captcha" type="text" size="12"></input>\
24 <input class="pseudo" name="pseudo" type="text" size="12" maxlength="50" value="&lt;nick&gt;"></input>\
25 <input class="message" name="message" type="text" size="80" maxlength="500" value=""></input>\
26 <button class="return"></button>\
27 </p>\
28 </form>\
29 <div id="conversations"></div>'
30 }
31
32 PageMinichat.prototype.charger = function()
33 {
34 thisPage = this
35
36 jQuery("form input.pseudo").val(this.client.pseudo)
37
38 this.messages.rafraichirMessages(true)
39
40 this.util.setCaretToEnd(jQuery("form input.message")[0])
41
42 // affichage des smiles
43 jQuery("#smiles").html(this.formateur.getSmilesHTML()).children().each(
44 function(i)
45 {
46 var opacityBase = jQuery(this).css("opacity")
47 jQuery(this).click(
48 function(event)
49 {
50 thisPage.util.replaceSelection(jQuery("form input.message")[0], thisPage.formateur.smiles[jQuery(this).attr("class")][0].source.replace(/\\/g, ""))
51 }
52 )
53 .hover(
54 function()
55 {
56 jQuery(this).animate(
57 {
58 opacity: 1
59 }, 200
60 )
61 },
62 function()
63 {
64 jQuery(this).animate(
65 {
66 opacity: opacityBase
67 }, 200
68 )
69 }
70 )
71 }
72 )
73
74 /// événements
75 jQuery("form button.return").click(
76 function()
77 {
78 // captcha anti bot
79 if (jQuery("form input.captcha").val() != "") return
80
81 thisPage.envoyerMessage(
82 jQuery("form input.pseudo").val(),
83 jQuery("form input.message").val()
84 )
85
86 jQuery("form input.message")[0].focus()
87 }
88 )
89 // interdiction de submiter le formulaire
90 jQuery("form").submit(function(){return false})
91
92 jQuery("input.pseudo").click(
93 function()
94 {
95 var input = jQuery("input.pseudo")[0]
96 if (input.value == conf.pseudoDefaut)
97 input.value = ""
98 }
99 )
100 }
101
102 PageMinichat.prototype.decharger = function()
103 {
104 if (this.attenteCourante != null)
105 this.attenteCourante.abort()
106 }
107
108 PageMinichat.prototype.getXMLMessage = function(pseudo, message, repondA)
109 {
110 var XMLDocument = this.util.creerDocumentXMLAction()
111 XMLDocument.documentElement.setAttribute("name", "message")
112
113 var nodeCookie = XMLDocument.createElement("cookie")
114 nodeCookie.appendChild(XMLDocument.createTextNode(this.client.cookie))
115 XMLDocument.documentElement.appendChild(nodeCookie)
116
117 var nodePseudo = XMLDocument.createElement("pseudo")
118 nodePseudo.appendChild(XMLDocument.createTextNode(pseudo))
119 XMLDocument.documentElement.appendChild(nodePseudo)
120
121 var nodeContenu = XMLDocument.createElement("contenu")
122 nodeContenu.appendChild(XMLDocument.createTextNode(message))
123 XMLDocument.documentElement.appendChild(nodeContenu)
124
125 if (repondA.length > 0)
126 {
127 var nodeReponses = XMLDocument.createElement("reponses")
128 XMLDocument.documentElement.appendChild(nodeReponses)
129 for (var i = 0; i < repondA.length; i++)
130 {
131 var nodeReponse = XMLDocument.createElement("reponse")
132 nodeReponse.setAttribute("id", repondA[i])
133 nodeReponses.appendChild(nodeReponse)
134 }
135 }
136
137 return XMLDocument
138 }
139
140 PageMinichat.prototype.envoyerMessage = function(pseudo, message)
141 {
142 // (un pseudo vide est autorisé)
143 pseudo = this.formateur.filtrerInputPseudo(pseudo)
144
145 // extraction des id des messages (en base 36 évidemment) auquels le user répond
146 var repondA = []
147 var tags = message.match(this.regexMessageTagMatch)
148 if (tags != null)
149 {
150 for(var i = 0; i < tags.length; i++)
151 repondA.push(/\{(.*?)\}>/.exec(tags[i])[1])
152 message = message.replace(this.regexMessageTagReplace, "")
153
154 // met à jour la classe des messages auquel repond celui ci (c'est un peu de la triche)
155 for (var i = 0; i < repondA.length; i++)
156 {
157 jQuery("#conversation div#" + repondA[i]).addClass("repondu")
158 for (var m = 0; m < this.messages.messages.length; m++)
159 this.messages.messages[m].clientARepondu = true
160 }
161 }
162
163 message = message.trim()
164 if (message == "")
165 {
166 this.util.messageDialogue("Le message est vide")
167 return
168 }
169
170 if (!this.client.identifie())
171 if (!this.client.enregistrement())
172 {
173 this.util.messageDialogue("login impossible")
174 return
175 }
176
177 //this.util.log(this.util.xmlVersAction(this.getXMLmessage(pseudo, message, repondA)).action)
178 //alert(this.util.xmlVersAction(this.getXMLMessage(pseudo, message, repondA)).action)
179 /* Obsolète
180 jQuery.post("request", this.util.xmlVersAction(this.getXMLMessage(pseudo, message, repondA)),
181 function(data, textStatus)
182 {
183 // TODO : traiter les erreurs
184 //alert(data)
185 jQuery("form input.message").val("")
186 }
187 )*/
188
189 jQuery.ajax(
190 {
191 url : "request",
192 type: "POST",
193 data : this.util.xmlVersAction(this.getXMLMessage(pseudo, message, repondA)),
194 dataType : "xml",
195 success : function(data, textStatus)
196 {
197 if(jQuery("statut", data.documentElement).text() == "ok")
198 jQuery("form input.message").val("")
199 }
200 }
201 )
202 }
203
204 ///////////////////////////////////////////////////////////////////////////////////////////////////
205
206 function Reponse(id, pseudo, login)
207 {
208 this.id = id
209 this.pseudo = pseudo
210 this.login = login
211
212 if (this.pseudo == undefined)
213 this.pseudo = ""
214
215 if (this.login == undefined)
216 this.login = ""
217 }
218
219 ///////////////////////////////////////////////////////////////////////////////////////////////////
220
221 /**
222 * Représente un message.
223 * @param id (string)
224 * @param date (string)
225 * @param pseudo
226 * @param contenu
227 */
228 function Message(id, date, pseudo, contenu)
229 {
230 this.id = id
231 this.date = date
232 this.pseudo = pseudo
233 this.contenu = contenu
234
235 this.appartientAuClient = false
236 this.clientARepondu = false
237 this.estUneReponse = false
238
239 this.systeme = false // est-ce un message 'système' ?
240
241 this.repondA = {} // un ensemble de reponse (voir Reponse) indexé par l'id du message de la reponses
242 }
243
244 /**
245 *
246 */
247 Message.prototype.setRepondA = function(element)
248 {
249 this.repondA = {}
250
251 var thisMessage = this
252
253 jQuery("id", element).each (
254 function()
255 {
256 var reponse = new Reponse(jQuery(this).attr("id"), jQuery(this).attr("pseudo"), jQuery(this).attr("login"))
257 thisMessage.repondA[reponse.id] = reponse
258 }
259 )
260 }
261
262 /**
263 * @return les id des messages qui ont été mis en evidence sous la forme d'un objet
264 */
265 Message.prototype.afficherConversation = function(messages)
266 {
267 // les messages faisant partie de la conversation
268 var messagesEnEvidence = {}
269
270 messagesEnEvidence[this.id] = true
271
272 // recherche les réponses (O(n))
273 for (var i = 0; i < messages.messages.length; i++)
274 if (messages.messages[i].repondA.hasOwnProperty(this.id))
275 messagesEnEvidence[messages.messages[i].id] = true
276
277 var f = function(tabIds)
278 {
279 for(var id in tabIds)
280 {
281 var message = messages.messagesParId[id]
282 if (message != undefined)
283 {
284 messagesEnEvidence[id] = true
285 f (message.repondA)
286 }
287 }
288 }
289 f(this.repondA)
290
291 return messagesEnEvidence
292 }
293
294 ///////////////////////////////////////////////////////////////////////////////////////////////////
295
296 /**
297 * Représente une conversation.
298 * @param numConv le numéro (appelé id) de la conversation
299 * @param formateur outil permettant la mise en forme du texte des messages
300 */
301 function Conversation(numConv, formateur)
302 {
303 var thisConversation = this
304
305 this.id = numConv
306 this.messageOver = null // le message sur lequel se trouve le curseur
307 this.formateur = formateur
308 this.messages = new Array()
309 this.messagesParId = new Object()
310
311 this.idDernierMesssage = null // la racine de la conversation (string), null = conversation principale
312
313 this.page = 1 // par défaut on se trouve sur la première page
314
315 jQuery("#conversations").append(
316 "<div id = \"" + this.getId() + "\" />"
317 )
318
319 // enlève la mise en évidence pour la conversation
320 jQuery(this.getId()).hover(
321 function(){},
322 function(event)
323 {
324 jQuery("#conversations .message").removeClass("cache")
325 thisConversation.messageOver = null
326 }
327 )
328 }
329
330 /**
331 * Retourne l'id de la conversation sous la forme (par exemple) "#conv453"
332 */
333 Conversation.prototype.getId = function()
334 {
335 return "#" + this.id
336 }
337
338 Conversation.prototype.ajouterMessage = function(message)
339 {
340 this.messages.push(message)
341 this.messagesParId[message.id] = message
342 if (this.messages.length > this.nbMessageMax)
343 delete this.messagesParId[this.messages.shift().id]
344 }
345
346 Conversation.prototype.viderMessages = function()
347 {
348 this.messages = new Array()
349 jQuery("#conversations " + this.getId()).empty()
350 }
351
352 /**
353 * Après l'ajout d'un ou plusieurs message cette méthode est appelée afin
354 * d'afficher les messages non-affichés.
355 */
356 Conversation.prototype.flush = function()
357 {
358 var thisConversation = this
359
360 var idDernierMessageAffiche = jQuery("#conversations " + this.getId() + " div:first").attr("id")
361 if (idDernierMessageAffiche == undefined) idDernierMessageAffiche = "0"
362
363 var XHTML = ""
364 for (var i = this.messages.length - 1; i >= 0; i--)
365 if (parseInt(this.messages[i].id, 36) > parseInt(idDernierMessageAffiche, 36))
366 {
367 var message = this.messages[i]
368 var XHTMLrepondA = ""
369 for (var id in message.repondA)
370 XHTMLrepondA += this.formateur.traitementComplet(message.repondA[id].pseudo) + "> "
371 XHTMLrepondA = "<div class=\"repondA\">" + XHTMLrepondA + "</div>"
372
373 XHTML +=
374 "<div id=\"" + message.id + "\" class=\"" + (parseInt(message.id, 36) % 2 == 0 ? "messagePair" : "messageImpair") + " message" +
375 (this.messages[i].appartientAuClient ? " proprietaire" : "") + (this.messages[i].clientARepondu ? " repondu" : "") + (this.messages[i].estUneReponse ? " reponse" : "") + (this.messages[i].systeme ? " systeme" : "") +
376 "\" >" +
377 "[<div class=\"date\">" + message.date + "</div>]" +
378 "<div class=\"pseudo\">" + this.formateur.traitementComplet(message.pseudo) + "</div>:" +
379 XHTMLrepondA +
380 "<div class=\"contenu\">" + (message.systeme ? this.formateur.remplacerBalisesHTML(message.contenu) : this.formateur.traitementComplet(message.contenu, message.pseudo))+ "</div>" +
381 "</div>"
382 }
383 jQuery("#conversations " + this.getId()).prepend(XHTML)
384
385 if (myLightbox != null)
386 myLightbox.updateImageList()
387
388 var nbMessagesAffiche = jQuery("#conversations " + this.getId() + " .message").size()
389 if (nbMessagesAffiche > this.nbMessageMax)
390 jQuery("#conversations " + this.getId() + " .message").slice(this.nbMessageMax, nbMessagesAffiche).empty();
391
392 jQuery("#conversations " + this.getId() + " .message").filter(function(){return parseInt(jQuery(this).attr("id"), 36) > parseInt(idDernierMessageAffiche, 36)}).each(
393 function()
394 {
395 jQuery(this).click(
396 function(event)
397 {
398 if (jQuery(event.target).is("a")) return
399
400 var valCourant = jQuery("input.message").val()
401 if (valCourant == undefined) valCourant = ""
402 var tag = jQuery(".pseudo", this).text() + "{" + jQuery(this).attr("id") + "}" + ">"
403 if (valCourant.indexOf(tag, 0) == -1)
404 jQuery("input.message").val(tag + " " + valCourant)
405 thisConversation.util.setCaretToEnd(jQuery("form input.message")[0])
406 }
407 )
408 // Q : pourquoi pas un .hover ?
409 // R : simplement pour éviter que lorsqu'un message arrive cela n'affecte la conversation actuellement mise en évidence (uniquement pour Firefox)
410 .mousemove(
411 function(e)
412 {
413 if (this !== thisConversation.messageOver)
414 {
415 thisConversation.afficherConversation(this)
416 thisConversation.messageOver = this
417 }
418 }
419 )
420 }
421 )
422 }
423
424 /**
425 * Etablit une liste des messages à mettre en evidence et des messages à cacher.
426 * Puis applique un plan diabolique.
427 * @param element un message de la liste des messages
428 */
429 Conversation.prototype.afficherConversation = function(element)
430 {
431 // cherche le message selectionné
432 var id = jQuery(element).attr("id")
433 var message = this.messagesParId[id]
434 if (message == undefined) return
435
436 mess = message.afficherConversation(this)
437
438 // FIXME : cet appel est très lent
439 jQuery("#conversations " + this.getId() + " .message").each(
440 function()
441 {
442 var jq = jQuery(this)
443 if (!mess.hasOwnProperty(jq.attr("id")))
444 jq.addClass("cache")
445 else
446 jq.removeClass("cache")
447 }
448 )
449 }
450
451 ///////////////////////////////////////////////////////////////////////////////////////////////////
452
453 /**
454 * Représente l'ensemble des messages affichés.
455 */
456 function Messages(client, formateur, util)
457 {
458 this.client = client
459 this.formateur = formateur
460 this.util = util
461
462 this.conversations = new Array() // les conversations, la première représente la conversation principale
463 this.conversations[0] = new Conversation(0, this.formateur)
464
465 this.idDernierMesssage = null // l'id du dernier message connu
466
467 // l'objet XMLHttpRequest représentant la connexion d'attente
468 this.attenteCourante = null
469 }
470
471 /**
472 * Crée un document XML contenant le message demandant un rafraichissement.
473 */
474 Messages.prototype.getXMLrafraichirMessages = function()
475 {
476 var XMLDocument = this.util.creerDocumentXMLAction()
477 XMLDocument.documentElement.setAttribute("name", "refreshMessages")
478
479 //alert(this.util.serializer.serializeToString(XMLDocument))
480
481 if (this.client.identifie())
482 {
483 var nodeCookie= XMLDocument.createElement("cookie")
484 nodeCookie.appendChild(XMLDocument.createTextNode(this.client.cookie))
485 XMLDocument.documentElement.appendChild(nodeCookie)
486 }
487
488 if (this.idDernierMesssage != null)
489 {
490 var nodeDernierMessageId = XMLDocument.createElement("dernierMessageId")
491 nodeDernierMessageId.appendChild(XMLDocument.createTextNode(this.idDernierMesssage))
492 XMLDocument.documentElement.appendChild(nodeDernierMessageId)
493 }
494
495 var nodeNombreMessage = XMLDocument.createElement("nombreMessage")
496 nodeNombreMessage.appendChild(XMLDocument.createTextNode(conf.nbMessageAffiche))
497 XMLDocument.documentElement.appendChild(nodeNombreMessage)
498
499 var nodePage = XMLDocument.createElement("page")
500 nodePage.appendChild(XMLDocument.createTextNode(this.conversations[0].page))
501 XMLDocument.documentElement.appendChild(nodePage)
502
503 // les conversations
504 for (var i = 1; i < this.conversations.length; i++)
505 {
506 var nodeConversation = XMLDocument.createElement("conversation")
507 XMLDocument.documentElement.appendChild(nodeConversation)
508
509 var nodeRacine = XMLDocument.createElement("racine")
510 nodeRacine.appendChild(XMLDocument.createTextNode(this.conversation.racine))
511 nodeConversation.appendChild(nodeRacine)
512
513 var nodePageConv = XMLDocument.createElement("page")
514 nodePageConv.appendChild(XMLDocument.createTextNode(this.conversation.page))
515 nodeConversation.appendChild(nodePageConv)
516 }
517
518 return XMLDocument;
519 }
520
521 /**
522 * Ajoute un ensemble de messages puis les affiches.
523 * @param elements un tableau d'éléments représentant les messages, voir protocole.txt
524 * @param numConversation le numéro de la conversation auquel appartiennent les messages
525 */
526 Messages.prototype.ajouterMessages = function(elements, numConversation)
527 {
528 for (var i = 0; i < elements.length; i++)
529 this.ajouterMessage(elements[i], numConversation)
530 this.flush(numConversation)
531 }
532
533 /**
534 * Création d'un nouveau message.
535 * Les message sont données dans l'ordre de leur id.
536 * @param element un element xml représentant le message
537 * @param numConversation le numéro de la conversation, 0 = principale
538 */
539 Messages.prototype.ajouterMessage = function(element, numConversation)
540 {
541 // pas d'utilisation de jquery pour des raisons de performance
542 this.idDernierMesssage = element.getAttribute("id")
543
544 var message = new Message(
545 this.idDernierMesssage,
546 jQuery("date", element).text(),
547 jQuery("pseudo", element).text(),
548 jQuery("contenu", element).text()
549 )
550
551 message.appartientAuClient = jQuery("proprietaire", element).text() == "true"
552 message.clientARepondu = jQuery("repondu", element).text() == "true"
553 message.estUneReponse = jQuery("reponse", element).text() == "true"
554 message.systeme = jQuery("systeme", element).text() == "true"
555 message.setRepondA(jQuery("repondA", element))
556
557 if (this.conversations[numConversation] == null)
558 this.conversations[numConversation] = new Conversation(numConversation, this.formateur)
559 this.conversations[numConversation].ajouterMessage(message)
560 }
561
562 /**
563 * Demande à toutes les conversations de se flusher (afficher les messages non-affichés).
564 */
565 Messages.prototype.flushAll = function()
566 {
567 for (var i = 0; i < this.conversations.length; i++)
568 this.flush(i)
569 }
570
571 /**
572 * Demande à une conversation de se flusher.
573 */
574 Messages.prototype.flush = function(numConv)
575 {
576 this.conversations[numConv].flush()
577 }
578
579 Messages.prototype.viderMessages = function()
580 {
581 this.idDernierMesssage = null
582
583 for (var i = 0; i < this.conversations.length; i++)
584 this.conversations[i].viderMessages()
585 }
586
587 /**
588 * Met à jour les messages de manière continue.
589 * (AJAX-Comet-style proof)
590 * @param vider vide tous les messages avant d'afficher les nouveaux
591 */
592 Messages.prototype.rafraichirMessages = function(vider)
593 {
594 if (this.attenteCourante != null)
595 this.attenteCourante.abort()
596
597 if (vider == undefined)
598 vider = false
599
600 if (vider)
601 {
602 this.idDernierMesssage = null
603 this.messages = new Array()
604 }
605
606 var thisMessages = this // caisupair javacrypte
607
608 //this.util.log(this.util.serializer.serializeToString(this.getXMLrafraichirMessages()))
609 //alert(this.util.xmlVersAction(this.getXMLrafraichirMessages()).action)
610 this.attenteCourante = jQuery.ajax({
611 type: "POST",
612 url: "request",
613 dataType: "xml",
614 data: this.util.xmlVersAction(this.getXMLrafraichirMessages()),
615 success:
616 function(data)
617 {
618 //thisMessages.util.log(thisMessages.util.serializer.serializeToString(data))
619
620 if (vider)
621 thisMessages.viderMessages()
622
623 //thisMessages.MAJPages(parseInt(jQuery("nbPage", data.documentElement).text()))
624
625 // ajoute les messages reçu à leur conversation respective
626 var numConv = 0
627 jQuery("conversation", data.documentElement).each(
628 function(i)
629 {
630 var messages = []
631 jQuery("message", this).each(
632 function(j)
633 {
634 // thisMessages.ajouterMessage(this, numConv)
635 messages.push(this)
636 }
637 )
638 thisMessages.ajouterMessages(messages, numConv)
639 numConv += 1
640 }
641 )
642
643 // demande à toutes les conversations d'afficher les messages non-affichés
644 //thisMessages.flush()
645
646 // rappel de la fonction dans 100 ms
647 setTimeout(function(){ thisMessages.rafraichirMessages() }, 100);
648 },
649 error:
650 function(XMLHttpRequest, textStatus, errorThrown)
651 {
652 setTimeout(function(){ thisMessages.rafraichirMessages() }, 1000);
653 }
654 }
655 )
656 }
657
658 /* Osbolète
659 Messages.prototype.MAJPages = function(nbPage)
660 {
661 //alert(nbPage)
662
663 var thisMessages = this
664
665 var nbPageActuel = jQuery("#pages span").size()
666
667 for(var p = nbPageActuel + 1; p <= nbPage && p <= 3; p++)
668 {
669 jQuery("#pages").append("<span " + (this.page == p ? "class=\"pageCourante\"" : "" ) + ">" + p + "</span>").click(
670 function(event)
671 {
672 var target = jQuery(event.target)
673
674 if(!target.is("span"))
675 return
676
677 thisMessages.allerSurLaPage(parseInt(jQuery(event.target).text()))
678 }
679 )
680 }
681 }*/
682
683 /* Obsolète
684 Messages.prototype.allerSurLaPage = function(page)
685 {
686 if (page == this.page)
687 return
688 this.page = page
689
690 var thisMessages = this
691
692 //jQuery("#pages span").removeClass("pageCourante")
693 jQuery("#pages span").each(
694 function(i)
695 {
696 if (jQuery(this).text() == thisMessages.page)
697 jQuery(this).addClass("pageCourante")
698 else
699 jQuery(this).removeClass("pageCourante")
700 }
701 )
702
703 this.rafraichirMessages(true)
704 }*/
705