diff --git a/src/messenger/webim/js/164/chat.js b/src/messenger/webim/js/164/chat.js
index 6548b009..0a8d21c8 100644
--- a/src/messenger/webim/js/164/chat.js
+++ b/src/messenger/webim/js/164/chat.js
@@ -5,20 +5,21 @@
Copyright (c) 2005-2011 Mibew Messenger Community
License: http://mibew.org/license.php
*/
-var FrameUtils={getDocument:function(a){return a.contentDocument?a.contentDocument:a.contentWindow?a.contentWindow.document:a.document?a.document:null},initFrame:function(a){var b=this.getDocument(a);b.open();b.write("
");b.write('');b.write("");b.write("");
+var FrameUtils={options:{},getDocument:function(a){return a.contentDocument?a.contentDocument:a.contentWindow?a.contentWindow.document:a.document?a.document:null},initFrame:function(a){var b=this.getDocument(a);b.open();b.write("");this.options.cssfile&&b.write('');b.write("");b.write("");
b.write("");b.close();a.onload=function(){a.myHtml&&(FrameUtils.getDocument(a).getElementById("content").innerHTML+=a.myHtml,FrameUtils.scrollDown(a))}},insertIntoFrame:function(a,b){var c=this.getDocument(a).getElementById("content");null==c?(a.myHtml||(a.myHtml=""),a.myHtml+=b):c.innerHTML+=b},scrollDown:function(a){var b=this.getDocument(a).getElementById("bottom");if("opera"==myAgent)try{a.contentWindow.scrollTo(0,this.getDocument(a).getElementById("content").clientHeight)}catch(c){}b&&
-b.scrollIntoView(!1)}};ChatThreadUpdater=Class.create();
-ChatThreadUpdater.prototype={initialize:function(a,b,c){this._options=c;this.thread=b;this.chatServer=a;this.focused=this.skipNextsound=this.cansend=!0;this.ownThread=null!=this._options.message;FrameUtils.initFrame(this._options.container);this._options.message&&(this._options.message.onkeydown=this.handleKeyDown.bind(this),this._options.message.onfocus=function(){this.focused=!0}.bind(this),this._options.message.onblur=function(){this.focused=!1}.bind(this));this.chatServer.callFunctionsPeriodically(this.updateFunctionBuilder.bind(this),
-this.updateChatState.bind(this));this.chatServer.registerFunction("updateMessages",this.updateMessages.bind(this));this.chatServer.registerFunction("setupAvatar",this.setupAvatar.bind(this));this.chatServer.runUpdater()},handleException:function(){this.setStatus("offline, reconnecting");this.enableInput(!0)},handleTimeout:function(){this.setStatus("timeout, reconnecting");this.enableInput(!0)},enableInput:function(a){this._options.message&&(this._options.message.disabled=!a)},refresh:function(){this.chatServer.restartUpdater()},
-postMessage:function(a){""!=a&&this.cansend&&(this.cansend=!1,this.skipNextsound=!0,"opera"!=myRealAgent&&this.enableInput(!1),this.chatServer.callFunctions([{"function":"post",arguments:{references:{},"return":{},message:a,threadId:this.thread.threadid,token:this.thread.token,user:this.thread.user}}],function(){this.enableInput(!0);this.cansend=!0;this.skipNextsound=!1;this._options.message&&(this._options.message.value="",this._options.message.focus())}.bind(this),!0))},changeName:function(a){this.skipNextsound=
-!0;this.chatServer.callFunctions([{"function":"rename",arguments:{references:{},"return":{},threadId:this.thread.threadid,token:this.thread.token,name:a}}],function(a){a.errorCode&&this.handleError(a,"cannot rename")}.bind(this),!0)},closeThread:function(){(!this._options.localizedStrings.closeConfirmation||confirm(this._options.localizedStrings.closeConfirmation))&&this.chatServer.callFunctions([{"function":"close",arguments:{references:{},"return":{closed:"closed"},threadId:this.thread.threadid,
-token:this.thread.token,lastId:this.thread.lastid,user:this.thread.user}}],this.onThreadClosed.bind(this),!0)},onThreadClosed:function(a){a.closed?window.close():this.handleError(a,"cannot close")},processMessage:function(a,b){FrameUtils.insertIntoFrame(a,b)},showTyping:function(a){$("typingdiv")&&($("typingdiv").style.display=a?"inline":"none")},setupAvatar:function(a){this._options.avatar&&this.thread.user&&(this._options.avatar.innerHTML=""!=a.imageLink?'':"")},updateMessages:function(a){a.lastId&&(this.thread.lastid=a.lastId);for(var b=0;b');$("avatarwnd").innerHTML=c},displayMessages:function(a){for(var b=0;b");
- doc.write("");
- doc.write("");
- doc.write("");
- doc.write("");
- doc.close();
- frm.onload = function() {
- if( frm.myHtml ) {
- FrameUtils.getDocument(frm).getElementById('content').innerHTML += frm.myHtml;
- FrameUtils.scrollDown(frm);
- }
- };
- },
+ options: {},
- insertIntoFrame: function(frm, htmlcontent) {
- var vcontent = this.getDocument(frm).getElementById('content');
- if( vcontent == null ) {
- if( !frm.myHtml ) frm.myHtml = "";
- frm.myHtml += htmlcontent;
- } else {
- vcontent.innerHTML += htmlcontent;
- }
- },
+ getDocument: function(frm) {
+ if (frm.contentDocument) {
+ return frm.contentDocument;
+ } else if (frm.contentWindow) {
+ return frm.contentWindow.document;
+ } else if (frm.document) {
+ return frm.document;
+ } else {
+ return null;
+ }
+ },
- scrollDown: function(frm) {
- var vbottom = this.getDocument(frm).getElementById('bottom');
- if( myAgent == 'opera' ) {
- try {
- frm.contentWindow.scrollTo(0,this.getDocument(frm).getElementById('content').clientHeight);
- } catch(e) {}
- }
- if( vbottom ) {
- vbottom.scrollIntoView(false);
- }
- }
+ initFrame: function(frm) {
+ var doc = this.getDocument(frm);
+ doc.open();
+ doc.write("");
+ if (this.options.cssfile) {
+ doc.write("");
+ }
+ doc.write("");
+ doc.write("");
+ doc.write("");
+ doc.close();
+ frm.onload = function() {
+ if (frm.myHtml) {
+ FrameUtils.getDocument(frm).getElementById('content').innerHTML += frm.myHtml;
+ FrameUtils.scrollDown(frm);
+ }
+ };
+ },
+
+ insertIntoFrame: function(frm, htmlcontent) {
+ var vcontent = this.getDocument(frm).getElementById('content');
+ if (vcontent == null) {
+ if (!frm.myHtml) {
+ frm.myHtml = "";
+ }
+ frm.myHtml += htmlcontent;
+ } else {
+ vcontent.innerHTML += htmlcontent;
+ }
+ },
+
+ scrollDown: function(frm) {
+ var vbottom = this.getDocument(frm).getElementById('bottom');
+ if (myAgent == 'opera') {
+ try {
+ frm.contentWindow.scrollTo(0,this.getDocument(frm).getElementById('content').clientHeight);
+ } catch(e) {}
+ }
+ if (vbottom) {
+ vbottom.scrollIntoView(false);
+ }
+ }
};
-ChatThreadUpdater = Class.create();
-ChatThreadUpdater.prototype = {
- /**
- * Create an instance of ChatThreadUpdater
- * @constructor
- * @param {ChatServer} chatServer An instance of ChatServer class
- * @param {Thread} thread Thread object
- * @param {Object} options Additional configuration options
- * @todo Add error handlers to chatServer
- * @todo Think about code format
- */
- initialize: function(chatServer, thread, options) {
+ChatView = Class.create();
+ChatView.prototype = {
+ /**
+ * Status timeout identifier
+ * @type Number
+ * @private
+ */
+ statusTimeout: null,
+
+ /**
+ * Contains localized strings. Properties names are language key and
+ * properties values are localized strings
+ * @type Object
+ */
+ localizedStrings: {},
+
+ /**
+ * Contains predefined answers configurable from administrative interface
+ * @type Array
+ */
+ predefinedAnswers: [],
+
+ /**
+ * Messages container DOM element
+ * @type Object
+ */
+ messageContainer: null,
+
+ /**
+ * Create an instance of ChatView
+ * @constructor
+ */
+ initialize: function(localizedStrings, predefinedAnswers) {
+ this.localizedStrings = localizedStrings || {};
+ this.predefinedAnswers = predefinedAnswers || [];
+
+ this.messageContainer = (myRealAgent == 'safari')
+ ? self.frames[0]
+ : $("chatwnd");
+ FrameUtils.initFrame(this.messageContainer);
+ },
+
+ /**
+ * Get localized string by language key
+ * @param {String} key Language key
+ * @returns {Boolean|String} Returns boolean FALSE if string with specified
+ * key is undefined and localized string otherwise
+ */
+ getLocaleString: function(key) {
+ if (typeof this.localizedStrings[key] == 'undefined') {
+ return false;
+ }
+ return this.localizedStrings[key];
+ },
+
+ /**
+ * Enables or disables input field
+ * @param {Boolean} val Use boolean true for enable input and false
+ * otherwise
+ */
+ enableInput: function(val) {
+ var message = $('msgwnd');
+ if (message) {
+ message.disabled = !val;
+ }
+ },
+
+ /**
+ * Clear message input element and set focus to it
+ */
+ clearInput: function() {
+ var message = $('msgwnd');
+ if(message) {
+ message.value = '';
+ message.focus();
+ }
+ },
+
+ /**
+ * Displays status div and sets the status string into it
+ * @param {String} k Status string
+ */
+ showStatusDiv: function(k) {
+ if ($("engineinfo")) {
+ $("engineinfo").style.display = 'inline';
+ $("engineinfo").innerHTML = k;
+ }
+ },
+
+ /**
+ * Sets the status
+ * @param {String} k Status string
+ */
+ setStatus: function(k) {
+ if (this.statusTimeout) {
+ clearTimeout(this.statusTimeout);
+ }
+ this.showStatusDiv(k);
+ this.statusTimeout = setTimeout(this.clearStatus.bind(this), 4000);
+ },
+
+ /**
+ * Hide the status string
+ */
+ clearStatus: function() {
+ $("engineinfo").style.display='none';
+ },
+
+ /**
+ * Displays typing status
+ * @param {Boolean} istyping Indicates the other side of conversation is
+ * typing a message or not
+ */
+ showTyping: function(istyping) {
+ if( $("typingdiv") ) {
+ $("typingdiv").style.display = istyping ? 'inline' : 'none';
+ }
+ },
+
+ /**
+ * Updates operator's avatar
+ * @param {String} root Base path
+ * @param {String} imageLink New avatar URL
+ */
+ updateAvatar: function(root, imageLink) {
+ var avatar = "";
+ if (imageLink != "") {
+ avatar = '' +
+ '';
+ }
+ $("avatarwnd").innerHTML = avatar;
+ },
+
+ /**
+ * Display all messages at the message window
+ * @param {Array} messages Messages array
+ */
+ displayMessages: function(messages) {
+ // Output messages
+ for (var i = 0; i < messages.length; i++) {
+ this.outputMessage(messages[i]);
+ }
+ // There are some new messages
+ if (messages.length > 0) {
+ FrameUtils.scrollDown(this.messageContainer);
+ }
+ },
+
+ /**
+ * Add message to the message window
+ * @param {String} message HTML message to insert
+ * @private
+ */
+ outputMessage: function(message) {
+ FrameUtils.insertIntoFrame(this.messageContainer, message);
+ },
+
+ /**
+ * Show new user name input
+ */
+ showNameField: function() {
+ $('changename1').style.display='inline';
+ $('changename2').style.display='none';
+ },
+
+ /**
+ * Hide new user name input
+ */
+ hideNameField: function() {
+ $('changename1').style.display='none';
+ $('changename2').style.display='inline';
+ },
+
+ /**
+ * Update user name in chat window
+ * @param {String} name New user's name
+ */
+ updateUserName: function(name) {
+ $('unamelink').innerHTML = htmlescape(name);
+ },
+
+ /**
+ * Change sound button state.
+ *
+ * @param {Boolean} enable TRUE if sound enabled and FALSE otherwise
+ */
+ changeSoundButtonState: function(enable) {
+ var tsound = $('soundimg');
+ if (enable) {
+ tsound.className = "tplimage isound";
+ } else {
+ tsound.className = "tplimage inosound";
+ }
+ var messagePane = $('msgwnd');
+ if(messagePane) {
+ messagePane.focus();
+ }
+ },
+
+ /**
+ * Add predefined answer to message input element and set focus to it.
+ *
+ * @param {Number} answerIndex Index of predefined answer
+ */
+ displayPredefinedAnswer: function(answerIndex) {
+ var message = $('msgwnd');
+ message.value = this.predefinedAnswers[answerIndex];
+ message.focus();
+ },
+
+ /**
+ * Set selectedIndex property of the select box DOM element passed as
+ * argument to zero.
+ * @param {Object} elem Select box DOM element
+ */
+ resetSelectedIndex: function(elem) {
+ elem.selectedIndex = 0;
+ }
+}
+
+ChatController = Class.create();
+ChatController.prototype = {
/**
* Additional options
* @type Object
* @private
*/
- this._options = options;
+ options: {},
/**
* An instance of the Thread class
* @type Thread
*/
- this.thread = thread;
+ thread: null,
/**
* An instance of the ChatServer class
* @type ChatServer
*/
- this.chatServer = chatServer;
+ server: null,
+
+ /**
+ * An instance of the ChatView class
+ * @type ChatView
+ */
+ view: null,
/**
* Indicates if user can post messages
* @type Boolean
*/
- this.cansend = true;
+ cansend: true,
/**
* Indicates if next message's sound must be skipped
* @type Boolean
*/
- this.skipNextsound = true;
+ skipNextSound: true,
/**
* Indicates if message input area ihn focus
* @type Boolean
*/
- this.focused = true;
+ focused: true,
+
+ /**
+ * Message input DOM element
+ * @type Object
+ */
+ message: null,
/**
* Indicates the thread belong to this operator
* @type Boolean
*/
- this.ownThread = this._options.message != null;
+ ownThread: null,
- FrameUtils.initFrame(this._options.container);
- if (this._options.message) {
- this._options.message.onkeydown = this.handleKeyDown.bind(this);
- this._options.message.onfocus = (function() {this.focused = true;}).bind(this);
- this._options.message.onblur = (function() {this.focused = false;}).bind(this);
+ /**
+ * Create an instance of ChatController
+ * @constructor
+ * @param {ChatServer} chatServer An instance of ChatServer class
+ * @param {Thread} thread Thread object
+ * @param {ChatView} chatView A Chat view object
+ * @param {Object} options Additional configuration options
+ * @todo Add error handlers to chatServer
+ * @todo Think about code format
+ */
+ initialize: function(chatServer, chatView, thread, options) {
+
+ this.options = options;
+ this.thread = thread;
+ this.server = chatServer;
+ this.view = chatView;
+
+ this.message = $('msgwnd');
+
+ this.ownThread = this.message != null;
+
+ if (this.message) {
+ this.message.onkeydown = this.handleKeyDown.bind(this);
+ this.message.onfocus = (function() {this.focused = true;}).bind(this);
+ this.message.onblur = (function() {this.focused = false;}).bind(this);
+ }
+
+ // Add periodic functions
+ this.server.callFunctionsPeriodically(
+ this.updateFunctionBuilder.bind(this),
+ this.updateChatState.bind(this)
+ );
+
+ // Register functions
+ this.server.registerFunction(
+ 'updateMessages',
+ this.updateMessages.bind(this)
+ );
+ this.server.registerFunction(
+ 'setupAvatar',
+ this.setupAvatar.bind(this)
+ );
+
+ this.server.runUpdater();
+ },
+
+ /**
+ * Exception handler. Updates status message
+ */
+ handleException: function(e) {
+ this.view.setStatus("offline, reconnecting");
+ this.view.enableInput(true);
+ },
+
+ /**
+ * Timeout handler. Updates status message
+ */
+ handleTimeout: function() {
+ this.view.setStatus("timeout, reconnecting");
+ this.view.enableInput(true);
+ },
+
+ /**
+ * Load new messages by restarting thread updater.
+ */
+ refresh: function() {
+ this.server.restartUpdater();
+ },
+
+ /**
+ * Sends message to the chat server
+ * @param {String} msg Message for send
+ */
+ postMessage: function(msg) {
+ // Check if message can be sent
+ if(msg == "" || !this.cansend) {
+ return;
+ }
+ // Disable message sending
+ this.cansend = false;
+ // Disable next sound
+ this.skipNextSound = true;
+ // Disable input
+ if(myRealAgent != 'opera') {
+ this.view.enableInput(false);
+ }
+ // Post message
+ this.server.callFunctions(
+ [{
+ "function": "post",
+ "arguments": {
+ "references": {},
+ "return": {},
+ "message": msg,
+ "threadId": this.thread.threadid,
+ "token": this.thread.token,
+ "user": this.thread.user
+ }
+ }],
+ (function(){
+ this.view.enableInput(true);
+ this.cansend = true;
+ this.view.clearInput();
+ }).bind(this),
+ true
+ );
+ },
+
+ /**
+ * Change user name
+ * @param {String} newname A new user name
+ */
+ changeName: function(newname) {
+ this.skipNextSound = true;
+ this.server.callFunctions(
+ [{
+ "function": "rename",
+ "arguments": {
+ "references": {},
+ "return": {},
+ "threadId": this.thread.threadid,
+ "token": this.thread.token,
+ "name": newname
+ }
+ }],
+ (function(args){
+ if (args.errorCode) {
+ this.handleError(args, 'cannot rename');
+ }
+ }).bind(this),
+ true
+ );
+ },
+
+ /**
+ * Send request for close chat to the core
+ */
+ closeThread: function() {
+ // Show confirmation message if can
+ if (this.view.getLocaleString('closeConfirmation')) {
+ if (! confirm(this.view.getLocaleString('closeConfirmation'))) {
+ return;
+ }
+ }
+ // Send request
+ this.server.callFunctions(
+ [{
+ "function": "close",
+ "arguments": {
+ "references": {},
+ "return": {"closed": "closed"},
+ "threadId": this.thread.threadid,
+ "token": this.thread.token,
+ "lastId": this.thread.lastid,
+ "user": this.thread.user
+ }
+ }],
+ this.onThreadClosed.bind(this),
+ true
+ );
+ },
+
+ /**
+ * Callback function for close chat request.
+ *
+ * Close chat window if closing success or warn on fail
+ */
+ onThreadClosed: function(args) {
+ if (args.closed) {
+ window.close();
+ } else {
+ this.handleError(args, 'cannot close');
+ }
+ },
+
+ /**
+ * Update operator's avatar
+ * @param {Array} args Array of arguments passed from the core
+ */
+ setupAvatar: function(args) {
+ if ($("avatarwnd") && this.thread.user) {
+ this.view.updateAvatar(this.options.webimRoot, args.imageLink);
+ }
+ },
+
+ /**
+ * Add new messages to chat window
+ * @param {Object} args object of function arguments passed from the server
+ */
+ updateMessages: function(args){
+ // Update last message id
+ if (args.lastId) {
+ this.thread.lastid = args.lastId;
+ }
+ // Add messages
+ this.view.displayMessages(args.messages);
+ // Clear status string
+ this.view.clearStatus();
+ // There are some new messages
+ if (args.messages.length > 0) {
+ if (!this.skipNextSound) {
+ var tsound = $('soundimg');
+ if (tsound == null || tsound.className.match(new RegExp("\\bisound\\b"))) {
+ playSound(this.options.webimRoot+'/sounds/new_message.wav');
+ }
+ }
+ if (!this.focused) {
+ window.focus();
+ }
+ }
+ this.skipNextSound = false;
+ },
+
+ /**
+ * Build update function to call at the core
+ */
+ updateFunctionBuilder: function() {
+ return [
+ {
+ "function": "update",
+ "arguments": {
+ "return": {'typing': 'typing', 'canPost': 'canPost'},
+ "references": {},
+ "threadId": this.thread.threadid,
+ "token": this.thread.token,
+ "lastId": this.thread.lastid,
+ "typed": (this.message && this.message.value != ''),
+ "user": this.thread.user
+ }
+ }
+ ];
+ },
+
+ /**
+ * Set current chat state message
+ * @param {Array} args Array of arguments passed from the core
+ */
+ updateChatState: function(args) {
+ if (args.errorCode) {
+ // Something went wrong
+ this.handleError(args, 'refresh failed');
+ return;
+ }
+ // Update typing indicator
+ if (typeof args.typing != 'undefined') {
+ this.view.showTyping(args.typing);
+ }
+
+ // Check if user can post messages
+ if (typeof args.canPost != 'undefined') {
+ if ((args.canPost && !this.ownThread) || (this.ownThread && ! args.canPost)) {
+ // Refresh the page
+ window.location.href = window.location.href;
+ }
+ }
+ },
+
+ /**
+ * Check if send key (Enter or Ctrl+Enter) pressed
+ * @param {Boolean} ctrlpressed Indicates ctrl key is pressed or not
+ * @param {Number} key Key code
+ */
+ isSendkey: function(ctrlpressed, key) {
+ return ((key==13 && (ctrlpressed || this.options.ignorectrl)) || (key==10));
+ },
+
+ /**
+ * Key down handler
+ *
+ * @param {Object} k Event object
+ */
+ handleKeyDown: function(k) {
+ if (k) {
+ ctrl=k.ctrlKey;
+ k=k.which;
+ } else {
+ k=event.keyCode;
+ ctrl=event.ctrlKey;
+ }
+ if (this.message && this.isSendkey(ctrl, k)) {
+ var mmsg = this.message.value;
+ if (this.options.ignorectrl) {
+ mmsg = mmsg.replace(/[\r\n]+$/,'');
+ }
+ this.postMessage(mmsg);
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Update status message
+ *
+ * @param {Object} args Array of arguments. Must contain 'errorCode' and
+ * 'errorMessage' keys
+ * @param {String} descr Error description
+ */
+ handleError: function(args, descr) {
+ if (args.errorCode) {
+ this.view.setStatus(args.errorMessage);
+ } else {
+ this.view.setStatus('reconnecting');
+ }
+ },
+
+ /**
+ * Apply new user's name
+ */
+ applyName: function() {
+ this.changeName($('uname').value);
+ this.view.hideNameField();
+ this.view.updateUserName($('uname').value);
+ },
+
+ /**
+ * Displays field for new user's name
+ */
+ showNameField: function() {
+ this.view.showNameField();
+ },
+
+ /**
+ * Predefined Answer select event handler.
+ *
+ * Add selected predefined answer to message input and reset predefined
+ * answers select box.
+ *
+ * @param {Object} answerSelect Predefined answer DOM element
+ */
+ selectPredefinedAnswer: function(answerSelect) {
+ var index = answerSelect.selectedIndex;
+ if(index != 0) {
+ this.view.displayPredefinedAnswer(index-1);
+ this.view.resetSelect(answerSelect);
+ }
+ },
+
+ /**
+ * Toggle sound button
+ */
+ toggleSound: function() {
+ var tsound = $('soundimg');
+ if(!tsound) {
+ return;
+ }
+ if(tsound.className.match(new RegExp("\\bisound\\b"))) {
+ this.view.changeSoundButtonState(false);
+ } else {
+ this.view.changeSoundButtonState(true);
+ }
}
-
- // Add periodic functions
- this.chatServer.callFunctionsPeriodically(
- this.updateFunctionBuilder.bind(this),
- this.updateChatState.bind(this)
- );
-
- // Register functions
- this.chatServer.registerFunction(
- 'updateMessages',
- this.updateMessages.bind(this)
- );
- this.chatServer.registerFunction(
- 'setupAvatar',
- this.setupAvatar.bind(this)
- );
-
- this.chatServer.runUpdater();
- },
-
- /**
- * Exception handler. Updates status message
- */
- handleException: function(e) {
- this.setStatus("offline, reconnecting");
- this.enableInput(true);
- },
-
- /**
- * Timeout handler. Updates status message
- */
- handleTimeout: function() {
- this.setStatus("timeout, reconnecting");
- this.enableInput(true);
- },
-
- /**
- * Enables or disables input field
- * @param {Boolean} val Use boolean true for enable input and false otherwise
- */
- enableInput: function(val) {
- if( this._options.message ) {
- this._options.message.disabled = !val;
- }
- },
-
- /**
- * Load new messages by restarting thread updater.
- */
- refresh: function() {
- this.chatServer.restartUpdater();
- },
-
- /**
- * Sends message to the chat server
- * @param {String} msg Message for send
- */
- postMessage: function(msg) {
- // Check if message can be sent
- if(msg == "" || !this.cansend) {
- return;
- }
- // Disable message sending
- this.cansend = false;
- // Disable next sound
- this.skipNextsound = true;
- // Disable input
- if(myRealAgent != 'opera') {
- this.enableInput(false);
- }
- // Post message
- this.chatServer.callFunctions(
- [{
- "function": "post",
- "arguments": {
- "references": {},
- "return": {},
- "message": msg,
- "threadId": this.thread.threadid,
- "token": this.thread.token,
- "user": this.thread.user
- }
- }],
- (function(){
- this.enableInput(true);
- this.cansend = true;
- this.skipNextsound = false;
- if(this._options.message) {
- this._options.message.value = '';
- this._options.message.focus();
- }
- }).bind(this),
- true
- );
- },
-
- /**
- * Change user name
- * @param {String} newname A new user name
- */
- changeName: function(newname) {
- this.skipNextsound = true;
- this.chatServer.callFunctions(
- [{
- "function": "rename",
- "arguments": {
- "references": {},
- "return": {},
- "threadId": this.thread.threadid,
- "token": this.thread.token,
- "name": newname
- }
- }],
- (function(args){
- if (args.errorCode) {
- this.handleError(args, 'cannot rename');
- }
- }).bind(this),
- true
- );
- },
-
- /**
- * Send request for close chat to the core
- */
- closeThread: function() {
- // Show confirmation message if can
- if(this._options.localizedStrings.closeConfirmation){
- if(! confirm(this._options.localizedStrings.closeConfirmation)){
- return;
- }
- }
- // Send request
- this.chatServer.callFunctions(
- [{
- "function": "close",
- "arguments": {
- "references": {},
- "return": {"closed": "closed"},
- "threadId": this.thread.threadid,
- "token": this.thread.token,
- "lastId": this.thread.lastid,
- "user": this.thread.user
- }
- }],
- this.onThreadClosed.bind(this),
- true
- );
- },
-
- /**
- * Callback function for close chat request.
- *
- * Close chat window if closing success or warn on fail
- */
- onThreadClosed: function(args) {
- if (args.closed) {
- window.close();
- } else {
- this.handleError(args, 'cannot close');
- }
- },
-
- /**
- * Add message to the message window
- * @param {Object} _target Target DOM element
- * @param {String} message HTML message to insert
- */
- processMessage: function(_target, message) {
- FrameUtils.insertIntoFrame(_target, message);
- },
-
- /**
- * Displays typing status
- * @param {Boolean} istyping Indicates the other side of conversation is
- * typing a message or not
- */
- showTyping: function(istyping) {
- if( $("typingdiv") ) {
- $("typingdiv").style.display=istyping ? 'inline' : 'none';
- }
- },
-
- /**
- * Update operator's avatar
- * @param {Array} args Array of arguments passed from the core
- */
- setupAvatar: function(args) {
- if (this._options.avatar && this.thread.user) {
- this._options.avatar.innerHTML = args.imageLink != ""
- ? ""
- : "";
- }
- },
-
- /**
- * Add new messages to chat window
- * @param {Object} args object of function arguments passed from the server
- * @todo Fix skipNextSound
- */
- updateMessages: function(args){
- // Update last message id
- if (args.lastId) {
- this.thread.lastid = args.lastId;
- }
- // Add messages
- for (var i = 0; i < args.messages.length; i++) {
- // TODO: Add template engine
- this.processMessage(this._options.container, args.messages[i]);
- }
- // Clear status string
- this.clearStatus();
- // There are some new messages
- if (args.messages.length > 0) {
- FrameUtils.scrollDown(this._options.container);
- if (!this.skipNextsound) {
- var tsound = $('soundimg');
- if (tsound == null || tsound.className.match(new RegExp("\\bisound\\b"))) {
- playSound(this._options.webimRoot+'/sounds/new_message.wav');
- }
- }
- if (!this.focused) {
- window.focus();
- }
- }
- this.skipNextsound = false;
- },
-
- /**
- * Build update function to call at the core
- */
- updateFunctionBuilder: function() {
- return [
- {
- "function": "update",
- "arguments": {
- "return": {'typing': 'typing', 'canPost': 'canPost'},
- "references": {},
- "threadId": this.thread.threadid,
- "token": this.thread.token,
- "lastId": this.thread.lastid,
- "typed": (this._options.message && this._options.message.value != ''),
- "user": this.thread.user
- }
- }
- ];
- },
-
- /**
- * Set current chat state message
- * @param {Array} args Array of arguments passed from the core
- */
- updateChatState: function(args) {
- if (args.errorCode) {
- // Something went wrong
- this.handleError(args, 'refresh failed');
- return;
- }
- // Update typing indicator
- if (typeof args.typing != 'undefined') {
- this.showTyping(args.typing);
- }
-
- // Check if user can post messages
- if (typeof args.canPost != 'undefined') {
- if ((args.canPost && !this.ownThread) || (this.ownThread && ! args.canPost)) {
- // Refresh the page
- window.location.href = window.location.href;
- }
- }
- },
-
- /**
- * Check if send key (Enter or Ctrl+Enter) pressed
- * @param {Boolean} ctrlpressed Indicates ctrl key is pressed or not
- * @param {Number} key Key code
- */
- isSendkey: function(ctrlpressed, key) {
- return ((key==13 && (ctrlpressed || this._options.ignorectrl)) || (key==10));
- },
-
- /**
- * Key down handler
- *
- * @param {Object} k Event object
- */
- handleKeyDown: function(k) {
- if( k ){ctrl=k.ctrlKey;k=k.which;} else {k=event.keyCode;ctrl=event.ctrlKey;}
- if( this._options.message && this.isSendkey(ctrl, k) ) {
- var mmsg = this._options.message.value;
- if( this._options.ignorectrl ) {
- mmsg = mmsg.replace(/[\r\n]+$/,'');
- }
- this.postMessage( mmsg );
- return false;
- }
- return true;
- },
-
- /**
- * Update status message
- *
- * @param {Array} args Array of arguments. Must contain 'errorCode' and
- * 'errorMessage' keys
- * @param {String} descr Error description
- */
- handleError: function(args, descr) {
- if (args.errorCode) {
- this.setStatus(args.errorMessage);
- } else {
- this.setStatus('reconnecting');
- }
- },
-
- /**
- * Displays status div and sets the status string into it
- *
- * @param {String} k Status string
- */
- showStatusDiv: function(k) {
- if( $("engineinfo") ) {
- $("engineinfo").style.display='inline';
- $("engineinfo").innerHTML = k;
- }
- },
-
- /**
- * Sets the status
- *
- * @param {String} k Status string
- */
- setStatus: function(k) {
- if( this.statusTimeout )
- clearTimeout(this.statusTimeout);
- this.showStatusDiv(k);
- this.statusTimeout = setTimeout(this.clearStatus.bind(this), 4000);
- },
-
- /**
- * Hide the status string
- */
- clearStatus: function() {
- $("engineinfo").style.display='none';
- }
}
-var Chat = {
- threadUpdater : {},
-
- applyName: function() {
- Chat.threadUpdater.changeName($('uname').value);
- $('changename1').style.display='none';
- $('changename2').style.display='inline';
- $('unamelink').innerHTML = htmlescape($('uname').value);
- },
-
- showNameField: function() {
- $('changename1').style.display='inline';
- $('changename2').style.display='none';
- }
-};
-
Behaviour.register({
- '#postmessage a' : function(el) {
- el.onclick = function() {
- var message = $('msgwnd');
- if( message )
- Chat.threadUpdater.postMessage(message.value);
- };
- },
- 'select#predefined' : function(el) {
- el.onchange = function() {
- var message = $('msgwnd');
- if(this.selectedIndex!=0) {
- message.value = Chat.predefinedAnswers[this.selectedIndex-1];
- }
- this.selectedIndex = 0;
- message.focus();
- };
- },
- 'div#changename2 a' : function(el) {
- el.onclick = function() {
- Chat.showNameField();
- return false;
- };
- },
- 'div#changename1 a' : function(el) {
- el.onclick = function() {
- Chat.applyName();
- return false;
- };
- },
- 'div#changename1 input#uname' : function(el) {
- el.onkeydown = function(e) {
- var ev = e || event;
- if( ev.keyCode == 13 ) {
- Chat.applyName();
- }
- };
- },
- 'a#refresh' : function(el) {
- el.onclick = function() {
- Chat.threadUpdater.refresh();
- };
- },
- 'a#togglesound' : function(el) {
- el.onclick = function() {
- var tsound = $('soundimg');
- if(!tsound) {
- return;
- }
- if(tsound.className.match(new RegExp("\\bisound\\b"))) {
- tsound.className = "tplimage inosound";
- } else {
- tsound.className = "tplimage isound";
- }
- var messagePane = $('msgwnd');
- if(messagePane)
- messagePane.focus();
- };
- },
- 'a.closethread' : function(el) {
- el.onclick = function() {
- Chat.threadUpdater.closeThread();
- };
- }
+ '#postmessage a' : function(el) {
+ el.onclick = function() {
+ var message = $('msgwnd');
+ if (message) {
+ chatController.postMessage(message.value);
+ }
+ };
+ },
+
+ 'select#predefined' : function(el) {
+ el.onchange = function() {
+ chatController.selectPredefinedAnswer(this);
+ };
+ },
+
+ 'div#changename2 a' : function(el) {
+ el.onclick = function() {
+ chatController.showNameField();
+ return false;
+ };
+ },
+
+ 'div#changename1 a' : function(el) {
+ el.onclick = function() {
+ chatController.applyName();
+ return false;
+ };
+ },
+
+ 'div#changename1 input#uname' : function(el) {
+ el.onkeydown = function(e) {
+ var ev = e || event;
+ if( ev.keyCode == 13 ) {
+ chatController.applyName();
+ }
+ };
+ },
+
+ 'a#refresh' : function(el) {
+ el.onclick = function() {
+ chatController.refresh();
+ };
+ },
+
+ 'a#togglesound' : function(el) {
+ el.onclick = function() {
+ chatController.toggleSound();
+ };
+ },
+
+ 'a.closethread' : function(el) {
+ el.onclick = function() {
+ chatController.closeThread();
+ };
+ }
});
var pluginManager = new PluginManager();
+var chatController;
EventHelper.register(window, 'onload', function(){
- var chatServer = new ChatServer(chatParams.serverParams);
- var thread = new Thread(chatParams.threadParams);
- chatParams.initPlugins(pluginManager, thread, chatServer);
- Chat.cssfile = chatParams.cssfile;
- Chat.predefinedAnswers = chatParams.predefinedAnswers || [];
- Chat.localizedStrings = chatParams.localizedStrings;
- Chat.threadUpdater = new ChatThreadUpdater(
- chatServer,
- thread,
- {
- ignorectrl: -1,
- container: myRealAgent=='safari'?self.frames[0]:$("chatwnd"),
- avatar: $("avatarwnd"),
- message: $("msgwnd")
- }.extend(chatParams.threadUpdaterParams || {})
- );
+ FrameUtils.options.cssfile = chatParams.cssfile;
+ var chatServer = new ChatServer(chatParams.serverParams);
+ var thread = new Thread(chatParams.threadParams);
+ chatParams.initPlugins(pluginManager, thread, chatServer);
+ var chatView = new ChatView(
+ chatParams.localizedStrings,
+ chatParams.predefinedAnswers || []
+ );
+ chatController = new ChatController(
+ chatServer,
+ chatView,
+ thread,
+ {ignorectrl: -1}.extend(chatParams.controllerParams || {})
+ );
});
\ No newline at end of file
diff --git a/src/messenger/webim/styles/dialogs/default/templates/chat.tpl b/src/messenger/webim/styles/dialogs/default/templates/chat.tpl
index f48cc0d5..c405d264 100644
--- a/src/messenger/webim/styles/dialogs/default/templates/chat.tpl
+++ b/src/messenger/webim/styles/dialogs/default/templates/chat.tpl
@@ -15,6 +15,7 @@ ${page:additional_js}