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