Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 908cd81ccf
Fetching contributors…

Cannot retrieve contributors at this time

2541 lines (2077 sloc) 91.333 kb
<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="chatpane-view">
<resources>
<stylesheet src="chrome://global/skin"/>
<stylesheet src="chrome://oneteam/skin/chatpane/chatpane.css"/>
</resources>
<content>
<xul:tooltip id="mozmill-tooltip" onpopupshowing="return fillTooltip(document.tooltipNode, this)"/>
<xul:hbox flex="1">
<xul:iframe src="data:text/html;base64,PCFET0NUWVBFIEhUTUw+PGh0bWw+PGJvZHk+PC9ib2R5PjwvaHRtbD4K" id="output" flex="1" type="content" tooltip="mozmill-tooltip"/>
<xul:stack id="markersContainer">
<xul:vbox class="marker-arrow"/>
</xul:stack>
</xul:hbox>
</content>
<implementation>
<field name="scrollToBottom">true</field>
<property name="smilesEnabled"
onget="return this._output.contentDocument.body.className == 'smiles-enabled'"
onset="this._output.contentDocument.body.className = val ? 'smiles-enabled' : '';
return val"/>
<property name="model" onget="return this._model">
<setter><![CDATA[
if (val == this._model)
return val;
if (this._model)
this._model.unregisterView(this._regToken);
this._model = val;
if (val) {
this._regToken = val.registerView(this._onMessageQueueChanged, this, "messages");
this._onMessageQueueChanged();
}
this._lastNickMessageId = {};
this._lastNickMessage = {};
return val;
]]></setter>
</property>
<property name="searchString" onget="return this._searchString">
<setter><![CDATA[
this._searchString = val;
if (this._lastMsg)
this._output.contentWindow.find(this._searchString, false, false,
true, false, true)
]]></setter>
</property>
<property name="visible" onget="return this._visible">
<setter><![CDATA[
if (!val == !this._visible)
return this._visible = val;
this._visible = val;
this._updateNonSeenStatus();
this._syncMarkers();
this._updateVisibleAnimations();
if (!val && this._model)
this._model.visible = false;
return val;
]]></setter>
</property>
<property name="editor" readonly="true">
<getter><![CDATA[
var win = this._output.contentWindow.
QueryInterface(Components.interfaces.nsIInterfaceRequestor);
var webNav = win.getInterface(Components.interfaces.nsIWebNavigation).
QueryInterface(Components.interfaces.nsIInterfaceRequestor);
return webNav.getInterface(Components.interfaces.nsIEditingSession).
getEditorForWindow(win);
]]></getter>
</property>
<constructor><![CDATA[
var me = this;
this._output = document.getAnonymousElementByAttribute(this, "id", "output");
this._output.addEventListener("load", function(){ me._init() }, true);
recoverSetters(this);
if (this._visible == null)
this._visible = true;
this._markersContainer = document.getAnonymousElementByAttribute(this, "id", "markersContainer");
this._markers = [];
this.colorMap = {0: 0};
this.colorIndex = 1;
this.colorTable = prefManager.getPref("chat.messages.colors").split(/\s*,\s*/);
this._threadingDriver = new ReplyGroups(new Callback(this._onUpdateThreadsDisplay, this),
this._threadsChangedFun || function(){},
new Callback(this._onThreadDestroyed, this));
this._lastNickMessageId = {};
this._lastNickMessage = {};
this._msgUidToMsg = {};
]]></constructor>
<destructor><![CDATA[
this.model = null;
]]></destructor>
<method name="addMarker">
<parameter name="element"/>
<parameter name="markerColor"/>
<parameter name="tooltip"/>
<parameter name="clickHandler"/>
<parameter name="seenHandler"/>
<parameter name="visibleHandler"/>
<body><![CDATA[
var marker = document.createElementNS(XULNS, "vbox");
marker.setAttribute("class", "marker-arrow");
marker.setAttribute("left", "0");
marker.setAttribute("onclick", "this.token.obj.scrollToElement(this.token.element);"+
"this.token.clickHandler()");
if (tooltip)
marker.setAttribute("tooltiptext", tooltip);
this._markersContainer.appendChild(marker);
var token = {
element: element,
markerElement: marker,
markerColor: markerColor,
clickHandler: clickHandler || function(){},
seenHandler: seenHandler,
visibleHandler: visibleHandler,
obj: this
};
this._syncMarker(token);
this._markers.push(token);
return marker.token = token;
]]></body>
</method>
<method name="removeMarker">
<parameter name="token"/>
<body><![CDATA[
var idx = this._markers.indexOf(token);
if (idx < 0)
return;
this._markers.splice(idx, 1);
token.markerElement.parentNode.removeChild(token.markerElement);
if (token.seenTimeout)
clearTimeout(token.seenTimeout);
]]></body>
</method>
<method name="_syncMarker">
<parameter name="token"/>
<body><![CDATA[
var p = token.element;
var scrEl = token.element.parentNode;
var top = 0;
var visible = token.seenHandler;
while (scrEl && scrEl.scrollHeight == scrEl.clientHeight)
scrEl = scrEl.parentNode;
if (scrEl)
do {
top += p.offsetTop;
p = p.offsetParent;
} while (p);
if (scrEl && (top < scrEl.scrollTop || top >= (scrEl.scrollTop + scrEl.clientHeight))) {
token.markerElement.setAttribute("top", parseInt(top/scrEl.scrollHeight*(scrEl.clientHeight-8)));
token.markerElement.style.borderRightColor = token.markerColor;
visible = false;
} else
token.markerElement.style.borderRightColor = "transparent";
if (visible && this.visible && !token.seenTimeout) {
var timeout;
if (scrEl && top < scrEl.scrollHeight - scrEl.clientHeight) {
timeout = 100;
} else
timeout = 3000;
token.seenTimeout = setTimeout(token.seenHandler, timeout, token, this);
} else if ((!visible || !this.visible) && token.seenTimeout) {
clearTimeout(token.seenTimeout);
token.seenTimeout = null;
}
if (this.visible && !visible && !token.visibleTimeout && token.visibleHandler)
token.visibleTimeout = setTimeout(token.visibleHandler, 3000, token, this);
else if (!this.visible && token.visibleTimeout) {
clearTimeout(token.visibleTimeout);
token.visibleTimeout = null;
}
]]></body>
</method>
<method name="_syncMarkers">
<body><![CDATA[
if (!this._markers)
return;
for (var i = 0; i < this._markers.length; i++)
this._syncMarker(this._markers[i]);
]]></body>
</method>
<method name="_updateVisibleAnimations">
<body><![CDATA[
if (!this.visible && this._visibleAnimationsTimeout) {
clearTimeout(this._visibleAnimationsTimeout);
this._visibleAnimationsTimeout = null;
} else if (this.visible && !this._visibleAnimationsTimeout &&
this._visibleAnimations)
{
setTimeout(function(_this) {
for (var i = 0; i < _this._visibleAnimations.length; i++)
_this._visibleAnimations[i].start();
_this._visibleAnimations = null;
}, 250, this);
}
]]></body>
</method>
<method name="scrollToElement">
<parameter name="element"/>
<body><![CDATA[
if (this._scrollAnimation)
this._scrollAnimation.stop();
return this._scrollAnimation = Animator.animateScrollToElement({
element: element,
}, "absolute:0");
]]></body>
</method>
<method name="scrollPageUp">
<body><![CDATA[
var el = this._output.contentDocument.documentElement;
if (this._scrollAnimation)
this._scrollAnimation.stop();
this._scrollAnimation = Animator.animateScroll({
element: el,
time: 100,
}, 0, el.scrollTop-el.clientHeight+16);
]]></body>
</method>
<method name="scrollPageDown">
<body><![CDATA[
var el = this._output.contentDocument.documentElement;
if (this._scrollAnimation)
this._scrollAnimation.stop();
this._scrollAnimation = Animator.animateScroll({
element: el,
time: 100,
}, 0, el.scrollTop+el.clientHeight+16);
]]></body>
</method>
<method name="_init">
<body>
<![CDATA[
this._initialized = true;
var doc = this._output.contentDocument;
var win = this._output.contentWindow;
var link = doc.createElement("link");
if (!("_frames" in window))
window._frames = [];
window._frames.push(win);
win._frameElement = this._output;
link.setAttribute("rel", "stylesheet");
link.setAttribute("href", "resource://oneteam-skin/chatpane/content.css");
doc.getElementsByTagName("HEAD")[0].appendChild(link);
var colorScheme = ".message { opacity: 1 }\n.message[not-seen=\"true\"] { opacity: 1 }\n";
var colorSchemeTag = doc.createElement("style");
colorSchemeTag.setAttribute("type", "text/css");
colorSchemeTag.appendChild(doc.createTextNode(colorScheme));
doc.getElementsByTagName("HEAD")[0].appendChild(colorSchemeTag);
doc.body.className = "smiles-enabled";
for (var i = 0; i < account.style.smiles.length; i++)
account.style.smiles[i].attachStyles(doc);
var me = this;
this._output.contentDocument.addEventListener("click", function(event) {
if (event.button != 0)
return;
if (event.target.className.indexOf("thread-marker-reply") >= 0)
me._threadMarkerClick(event.target)
else if (event.target.className.indexOf("fetch-more") >= 0)
me._prependMessages();
else if (event.target.className.indexOf("inline-command") >= 0)
me._inlineCommandClick(event.target);
else
linkEventsRedirector(event);
}, true);
if (this._model)
this._onMessageQueueChanged();
doc.addEventListener("contactdblclick", function(event) {
var target = event.target;
if (target && target.contact)
target.contact.onOpenChat();
}, false);
doc.addEventListener("replacewithimage", function(event) {
var target = event.target;
var checked = target.getElementsByTagName("input")[0].checked;
if (checked) {
account.cache.setValue("loadimage-"+me.model.contact.jid.normalizedJID.shortJID, true);
for each (var thread in me.model.contact.threads)
if (thread.chatPane)
try {
thread.chatPane._content._chatpane._output._replaceWithImage();
} catch (ex) {alert(ex)}
for each (var thread in me.model.contact.newThreads)
if (thread.chatPane)
try {
thread.chatPane._content._chatpane._output._replaceWithImage();
} catch (ex) {alert(ex)}
Message.prototype.epoch++;
} else
me._replaceWithImage(event.target);
}, false);
win.addEventListener("resize", function() {
me._syncMarkers();
doc.documentElement.scrollTop = doc.documentElement.scrollHeight;
}, false);
win.addEventListener("scroll", function() {
me._syncMarkers();
}, false);
]]>
</body>
</method>
<method name="_replaceWithImage">
<parameter name="target"/>
<body><![CDATA[
if (!target) {
var repl = this._output.contentDocument.getElementsByClassName("image-replacement");
while (repl.length)
this._replaceWithImage(repl[0]);
return;
}
var img = target.ownerDocument.createElement("img");
for each (var attr in ["width", "height", "src", "alt"])
if (target.getAttribute(attr))
img.setAttribute(attr, target.getAttribute(attr));
target.parentNode.replaceChild(img, target);
]]></body>
</method>
<method name="_onMessageQueueChanged">
<parameter name="model"/>
<parameter name="name"/>
<parameter name="arg"/>
<body><![CDATA[
if (!this._initialized)
return;
var newMessages = arg ? arg.added : this._model.messages;
if (!this._msgs) {
[this.archivedMsgToken, this._msgs, this._hasMoreArchivedMessages] =
this.model.getMessagesFromHistory(10, newMessages && newMessages.length ?
newMessages[0].time.getTime() : null);
}
if (newMessages && newMessages.length) {
this._msgs.push.apply(this._msgs, newMessages);
this._model.removeMessages();
}
if (!this._msgs.length)
return;
if (this._appendTimeout)
window.clearTimeout(this._appendTimeout);
this._appendTimeout = window.setTimeout(function(t, initial){
t._appendMessages(initial)
}, 50, this, !model);
]]></body>
</method>
<method name="_updateNonSeenStatus">
<parameter name="fromScroll"/>
<body><![CDATA[
var doc = this._output.contentDocument;
if (!this._firstNotSeenMsg) {
if (!this.visible) {
this._markMessagesAsSeen();
} else if (this._model)
this._model.visible = true;
return;
}
var rule = doc.styleSheets[1].cssRules[0];
if (!this._model || this._model.styleUnseen)
rule.style.cssText = "opacity: 0.3";
if (this._unseenMarker)
return;
this._unseenMarker = this.addMarker(this._firstNotSeenMsg, "black",
_("Click to scroll to first non-seen message"), null, function(token, el) {
el.removeMarker(token);
el._unseenMarker = null;
el._firstNotSeenMsg = null;
if (!el._model || el._model.styleUnseen)
Animator.animateCssRule({
rule: rule,
style: "opacity",
stopCallback: function(token) {
token.rule.style.opacity = "";
el._markMessagesAsSeen();
if (el._model)
el._model.visible = true;
}
}, 0.3, 1);
else
el._model.visible = true;
}, function(token, el) {
if (el._model)
el._model.visible = true;
});
]]></body>
</method>
<method name="_markMessagesAsSeen">
<body><![CDATA[
var els = this._output.contentDocument.getElementsByTagName("div");
for (var i = els.length-1; i >= 0; i--)
els[i].removeAttribute("not-seen");
]]></body>
</method>
<method name="_prependMessages">
<parameter name="numCalls"/>
<body><![CDATA[
if (this._prependAnimation && this._prependAnimation.running) {
this._prependAnimation.numCalls++;
return;
}
var msgs, hasMore;
[this.archivedMsgToken, msgs, hasMore] =
this.model.getMessagesFromHistory(10, this.archivedMsgToken);
if (!hasMore)
this._fetchMoreArrow.style.display = "none";
var doc = this._output.contentDocument;
var pos = doc.documentElement.clientHeight - doc.documentElement.scrollTop;
var fragment = this._genDocFragmentFromMsgs(msgs, doc, null, null,
this._firstMsg, this._firstMsgEl);
var firstMsgEl = fragment.firstChild;
var d1 = doc.createElement("div");
d1.setAttribute("style", "overflow: hidden; height: 0; position: relative")
var d2 = doc.createElement("div");
d2.setAttribute("style", "left: 0; right: 0; bottom: 0; position: absolute")
d1.appendChild(d2);
d2.appendChild(fragment);
this._content.insertBefore(d1, this._firstMsgEl);
// force relayout
d1.clientHeight;
this._prependAnimation = Animator.animateDimensions({
element: d1,
numCalls: (numCalls||1) - 1,
chatpane: this,
setValue: function(value, token) {
token.element.style.height = value[0]+"px";
},
stopCallback: function(token) {
var el = token.element;
while (el.firstChild.firstChild)
el.parentNode.insertBefore(el.firstChild.firstChild, el);
el.parentNode.removeChild(el);
if (token.numCalls)
token.chatpane._prependMessages(token.numCalls);
}
}, 0, d2.scrollHeight);
// this._content.insertBefore(fragment, this._firstMsgEl);
this._firstMsg = msgs[0];
this._firstMsgEl = firstMsgEl;
doc.documentElement.scrollTop = 0;
]]></body>
</method>
<method name="_appendMessages">
<parameter name="initial"/>
<body><![CDATA[
var doc = this._output.contentDocument;
if (!doc) {
this._appendTimeout = window.setTimeout(function(t, initial) {
t._appendMessages(initial)
}, 50, this, initial);
return
}
var scrEl = doc.documentElement;
if (!this._fetchMoreArrow && this._hasMoreArchivedMessages) {
this._fetchMoreArrow = doc.createElement("div");
this._fetchMoreArrow.setAttribute("class", "fetch-more");
this._fetchMoreArrow.textContent = _("Load more messages from history");
this._fetchMoreArrow.setAttribute("title", "fetch-more");
doc.body.appendChild(this._fetchMoreArrow);
}
var scrollToBottom = scrEl.scrollTop + scrEl.clientHeight >= scrEl.scrollHeight - 8 ||
(this._scrollToBottomAnimation && this._scrollToBottomAnimation.running);
this._appendTimeout = null;
if (!this._content) {
this._content = doc.createElement("div");
this._content.setAttribute("class", "mainContainer");
if (!this._gradient) {
this._gradient = doc.createElement("div");
this._gradient.setAttribute("class", "gradient");
doc.body.appendChild(this._gradient);
}
doc.body.appendChild(this._content);
}
var msgs = this._msgs;
this._msgs = [];
var notSeenIndex = -1;
if (!this._visible && !this._firstNotSeenMsg)
for (var i = 0; notSeenIndex < 0 && i < msgs.length; i++)
if (!msgs[i].archived && !msgs[i].isSystemMessage)
notSeenIndex = i;
var fragment = this._genDocFragmentFromMsgs(msgs, doc, this._lastMsg, this._lastMsgEl);
if (!fragment.childNodes.length) {
this._model.removeMessages();
return;
}
var msgEl = fragment.lastChild;
if (notSeenIndex >= 0)
this._firstNotSeenMsg = fragment.childNodes[notSeenIndex];
if (!this._firstMsg) {
this._firstMsg = msgs[0];
this._firstMsgEl = fragment.firstChild;
}
this._content.appendChild(fragment);
this._model.removeMessages();
if (this.scrollToBottom && scrollToBottom && msgEl)
if (initial)
doc.documentElement.scrollTop = doc.documentElement.scrollHeight;
else
this._scrollToBottomAnimation = this.scrollToElement(msgEl);
if (!this.lastMsg && this._searchString) {
this._output.contentWindow.find(this._searchString, false, false,
true, false, true)
}
this._lastMsg = msgs[msgs.length-1];
this._lastMsgEl = msgEl;
this._updateNonSeenStatus();
this._syncMarkers();
]]></body>
</method>
<method name="_genDocFragmentFromMsgs">
<parameter name="msgs"/>
<parameter name="doc"/>
<parameter name="beforeMsg"/>
<parameter name="beforeMsgEl"/>
<parameter name="afterMsg"/>
<parameter name="afterMsgEl"/>
<body><![CDATA[
var fragment = doc.createDocumentFragment();
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i].wrapper();
var animRequired = false;
if (!afterMsg) {
var lastMsg = this._lastNickMessage[msg.nick];
if (msg.replaceMessageId && (!lastMsg || msg.replaceMessageId != lastMsg.messageId))
continue; // an invalid editMessage (i.e. with invalid replaceMessageId) is ignored
tryToConvertOldFormatEditMessage(msg, lastMsg);
if (msg.replaceMessageId) {
msg.editCounter = lastMsg.editCounter ? lastMsg.editCounter+1 : 1;
if (msg.editCounter <= 3) {
lastMsg.editMessage = msg;
animRequired = !msg.archived;
msg = lastMsg;
} else
msg.editCounter = 0; // 4th edition of a message displayed as an original message
}
}
if (!msg.editCounter && !msg.msgEl) {
var msgEl = doc.createElement("div");
msgEl.setAttribute("class", "message "+msg.getClasses(this.model.isFromArchive));
msgEl.setAttribute("not-seen", "true");
if (msg.isSystemMessage || !beforeMsg ||
beforeMsg.isSystemMessage ||
beforeMsg.contactId != msg.contactId ||
msg.dontGroup || beforeMsg.dontGroup ||
msg.time - beforeMsg.time > 5*60*1000 ||
(beforeMsg.offline && !msg.offline))
{
if (beforeMsgEl)
beforeMsgEl.setAttribute("ends-chunk", "true");
msgEl.setAttribute("starts-chunk", "true");
}
if (msg.contact) {
var e = doc.createElement("div");
e.setAttribute("class", "avatar-image")
e.setAttribute("style", "background-image: url("+
(msg.contact.avatar ||
"resource://oneteam-skin/avatar/imgs/default-avatar.png")+
")");
var e2 = doc.createElement("div");
e2.setAttribute("class", "avatar")
e2.appendChild(e);
msgEl.appendChild(e2);
}
var h = doc.createElement("span");
h.setAttribute("class", "header");
msgEl.appendChild(h);
var e = doc.createElement("span");
e.setAttribute("class", "to-copy-paste");
e.appendChild(doc.createTextNode("["));
h.appendChild(e);
e = doc.createElement("span");
e.setAttribute("class", "date");
e.appendChild(doc.createTextNode(readableTimestamp(msg.time)));
h.appendChild(e);
e = doc.createElement("span");
e.setAttribute("class", "to-copy-paste");
e.appendChild(doc.createTextNode("] "));
h.appendChild(e);
if (!msg.isSystemMessage) {
e = doc.createElement("span");
e.setAttribute("class", "author");
e.appendChild(doc.createTextNode(msg.nick));
h.appendChild(e);
e = doc.createElement("span");
e.setAttribute("class", "to-copy-paste");
e.appendChild(doc.createTextNode(": "));
h.appendChild(e);
}
e = doc.createElement("span");
e.setAttribute("class", "body");
msg.contentNode = doc.createElement("div");
msg.contentNode.innerHTML = msg.formatedHtml || "<br/>";
e.appendChild(msg.contentNode);
msgEl.appendChild(e);
if (!msg.isSystemMessage) {
msg.rightDiv = doc.createElement("div");
msg.rightDiv.setAttribute("class", "right-div");
e.insertBefore(msg.rightDiv, e.firstChild);
var xReplyTo = "xReplyTo" in msg ? msg.xReplyTo[0] : null;
var xMessageId = "xMessageId" in msg ? msg.xMessageId : null;
if (xMessageId) {
if (!afterMsg)
this._lastNickMessageId[msg.nick] = xMessageId;
msgEl.twitterNick = msg.xTwitterNick;
msgEl.msgId = xMessageId;
}
if (xReplyTo || xMessageId) {
var im = doc.createElement("div");
im.setAttribute("class", "message-action thread-marker" +
( msg.archived != true // the threading feature is temporary disabled for
// archived message, until release of beta2
&& xMessageId && this._input ? " thread-marker-reply" : ""));
if (this._input)
im.title = _("Refer to this Message");
var handler = {
_this: this,
handleEvent: function(event) {
if (!event.target.hasAttribute("threadId"))
return;
if (event.type == "mouseover") {
if (!this._inside) {
this._inside = true;
this._this._onThreadMarkerOver(event.target);
}
} else if (this._inside) {
this._inside = false;
this._this._onThreadMarkerOut(event.target);
}
}
}
im.addEventListener("mouseover", handler, false);
im.addEventListener("mouseout", handler, false);
e.insertBefore(im, e.firstChild);
this._threadingDriver.addMessage(msg, msgEl);
if (xMessageId && this._input && this._input.thread &&
this._threadingDriver.msgIdMap[xMessageId][2] == this._input.thread)
this._input.msgId = xMessageId;
}
}
msg.msgEl = msgEl;
msgEl.msgUid = msg.uid;
fragment.appendChild(msgEl);
msg.wrappedValue.addRepresentation(msgEl);
msg.wrappedValue.markAsSeen();
beforeMsg = msg;
beforeMsgEl = msgEl;
this._msgUidToMsg[msg.uid] = msg;
}
if (!msg.editCounter && msg.editMessage) {
var body = msg.msgEl.lastChild;
if (!msg.displayEditButton) {
msg.displayEditButton = displayEditButton(body, msg, this._output);
}
}
while (msg.editMessage) {
msg.editMessage.editCounter =
msg.editCounter ? msg.editCounter +1 : 1;
msg.editMessage.rightDiv = msg.rightDiv;
msg.editMessage.displayEditButton = msg.displayEditButton;
msg.editMessage.msgEl = msg.msgEl;
msg.editMessage.previous = msg;
msg = msg.editMessage;
msg.contentNode = doc.createElement("div");
msg.contentNode.innerHTML = msg.formatedHtml || "<br/>";
} // now, msg is the last edit message, or the original message if not edited
if (msg.displayEditButton)
msg.displayEditButton.reload(msg);
if (!afterMsg && !msg.isSystemMessage) {
this._lastNickMessage[msg.nick] = msg;
if (this._input && msg.contact.representsMe)
this._input.lastMessage = msg;
}
if (animRequired) {
if (!this.visible && !this._visibleAnimations)
this._visibleAnimations = [];
var iterator = doc.createNodeIterator(msg.msgEl.lastChild.lastChild, 1, {
acceptNode: function(node) {
return /span/i.test(node.tagName) && node.className == "edit" ? 1 : 0;
}
}, true);
var n;
while ((n = iterator.nextNode())) {
var animation = Animator.animateStyle({
element: n,
style: "background",
stopValue: "",
paused: !this.visible
}, "#fd0", "parent.transparent");
if (!this.visible)
this._visibleAnimations.push(animation);
}
}
}
if (this._input && !afterMsg)
this.parentNode.parentNode.parentNode.parentNode.sthgToEdit =
this._input.lastMessage
&& !this._input.lastMessage.isOldFormatEditMessage
&& (!this._input.lastMessage.editCounter
|| this._input.lastMessage.editCounter < 3);
if (afterMsg) {
if (!msg.isSystemMessage && afterMsg.contactId == msg.contactId &&
afterMsg.time - msg.time < 5*60*1000 &&
afterMsg.offline == msg.offline)
{
afterMsgEl.removeAttribute("starts-chunk");
msgEl.removeAttribute("ends-chunk");
} else
msgEl.setAttribute("ends-chunk", "true");
}
return fragment;
]]></body>
</method>
<method name="syncEditMode">
<body><![CDATA[
var chatpane = this.parentNode.parentNode.parentNode.parentNode;
if (chatpane.editModeAvailable) {
this._attachEditButton();
var messageDiv = this._editButton.parentNode.parentNode.parentNode;
if (chatpane.editModeEnabled) {
this._editButton.setAttribute("editing", true);
this._editButton.title = _("Cancel Edition");
messageDiv.setAttribute("editing", true);
} else {
this._editButton.removeAttribute("editing");
this._editButton.title = _("Edit Last Message");
messageDiv.removeAttribute("editing");
}
} else
if (this._editButton) {
this._editButton.parentNode.parentNode.removeAttribute("editing");
this._removeEditButton();
}
]]></body>
</method>
<method name="_removeEditButton">
<body><![CDATA[
if (this._editButton) {
this._editButton.parentNode.removeChild(this._editButton);
delete this._editButton;
}
]]></body>
</method>
<method name="_attachEditButton">
<body><![CDATA[
if (this._input.lastMessage) {
var rightDiv = this._input.lastMessage.rightDiv;
if (!this._editButton || rightDiv != this._editButton.parentNode) {
this._removeEditButton();
var doc = this._output.contentDocument;
var im = doc.createElement("div");
im.setAttribute("id", "editButton");
im.addEventListener("click", new Callback (function() {
this.parentNode.parentNode.parentNode.parentNode.switchEditMode();
}, this), true);
im.title= _("Edit Last Message");
this._editButton = im;
rightDiv.appendChild(im);
}
}
]]></body>
</method>
<method name="_onUpdateThreadsDisplay">
<parameter name="thread"/>
<parameter name="msgToken"/>
<body><![CDATA[
if (this._selectedThread)
if (this._selectedThread.age == thread.age && msgToken) {
msgToken[1].setAttribute("in-selected-thread", "true");
msgToken[1].setAttribute("ends-selected-chunk", "true");
lastThreadMsgToken = thread.tokens[thread.tokens.length-2];
if (msgToken[1].previousSibling == lastThreadMsgToken[1])
msgEl.setAttribute("starts-selected-chunk", "true");
else
lastThreadMsgToken[1].removeAttribute("ends-selected-chunk");
} else
this._selectThread(this._selectedThread);
if (!msgToken) {
for (var token in thread.iterator()) {
var marker = token[1].lastChild.firstChild;
marker.setAttribute("threadId", thread.age+1);
if (this._input)
marker.title = _("Refer to this Conversation");
}
} else {
var marker = msgToken[1].lastChild.firstChild;
marker.setAttribute("threadId", thread.age+1);
if (this._input)
marker.title = _("Refer to this Conversation");
}
]]></body>
</method>
<method name="_onThreadDestroyed">
<parameter name="thread"/>
<body><![CDATA[
for (var token in thread.iterator()) {
var marker = token[1].lastChild.firstChild;
marker.removeAttribute("threadId");
if (this._input)
marker.title = _("Refer to this Message");
}
]]></body>
</method>
<method name="_setInputReplyMsgId">
<parameter name="msgId"/>
<body><![CDATA[
this._input.thread = null;
var thread = this._threadingDriver.getThreadForMsgId(msgId);
var msgEl = this._threadingDriver.msgIdMap[msgId][1];
this._input.thread = thread;
this._input.msgId = msgId;
if (msgEl.twitterNick)
this._input._append("@"+msgEl.twitterNick);
]]></body>
</method>
<method name="_threadMarkerClick">
<parameter name="el"/>
<body><![CDATA[
if (this._input && !this._input.disabled)
this._setInputReplyMsgId(el.parentNode.parentNode.msgId);
]]></body>
</method>
<method name="_inlineCommandClick">
<parameter name="el"/>
<body><![CDATA[
var msgEl = el.parentNode.parentNode.parentNode;
var msg = this._msgUidToMsg[msgEl.msgUid]
if (msg)
msg.wrappedValue.processInlineCommand(el, window);
]]></body>
</method>
<method name="_onThreadMarkerOver">
<parameter name="el"/>
<body><![CDATA[
this.highlightThread(this._threadingDriver
.getThreadForMsgId(el.parentNode.parentNode.msgId));
]]></body>
</method>
<method name="_onThreadMarkerOut">
<parameter name="el"/>
<body><![CDATA[
this.unhighlightThread(this._threadingDriver
.getThreadForMsgId(el.parentNode.parentNode.msgId));
]]></body>
</method>
<method name="highlightThread">
<parameter name="thread"/>
<body><![CDATA[
for (var token in thread.iterator())
token[1].setAttribute("in-marked-thread", "true");
this._output.contentDocument.body.setAttribute("marked-thread", "true");
]]></body>
</method>
<method name="unhighlightThread">
<parameter name="thread"/>
<body><![CDATA[
this._output.contentDocument.body.removeAttribute("marked-thread");
for (var token in thread.iterator())
token[1].removeAttribute("in-marked-thread");
]]></body>
</method>
<method name="_selectThread">
<parameter name="thread"/>
<body><![CDATA[
var msgs = this._content.childNodes;
if (!thread)
this._output.contentDocument.body.removeAttribute("hide-not-marked");
if (this._selectedThread && (!thread || this._selectedThread.age != thread.age)) {
for (var token in this._selectedThread.iterator()) {
token[1].removeAttribute("in-selected-thread");
token[1].removeAttribute("starts-selected-chunk");
token[1].removeAttribute("ends-selected-chunk");
}
}
if (thread) {
var prevToken;
for (var token in thread.iterator()) {
token[1].setAttribute("in-selected-thread", "true");
if (!prevToken || prevToken[1] != token[1].previousSibling) {
if (prevToken)
prevToken[1].setAttribute("ends-selected-chunk", true);
token[1].setAttribute("starts-selected-chunk", true);
} else {
if (prevToken)
prevToken[1].removeAttribute("ends-selected-chunk");
token[1].removeAttribute("starts-selected-chunk");
}
prevToken = token;
}
}
if (token) {
token[1].setAttribute("ends-selected-chunk", true);
}
this._selectedThread = thread;
if (thread)
this._output.contentDocument.body.setAttribute("hide-not-marked", "true");
]]></body>
</method>
<method name="clear">
<body><![CDATA[
this._threadingDriver.reset();
if (this._content)
this._content.parentNode.removeChild(this._content);
this._content = this._msgsContainer = this.archivedMsgToken =
this._msgs = this._firstNotSeenMsg =
this._firstMsg = this._firstMsgEl =
this._lastMsg = this._lastMsgEl = null;
]]></body>
</method>
</implementation>
</binding>
<binding id="richtext-toolbar" extends="chrome://global/content/bindings/toolbar.xml#toolbar">
<resources>
<stylesheet src="chrome://global/skin"/>
<stylesheet src="chrome://oneteam/skin/chatpane/textbox.css"/>
</resources>
<content tbalign="center">
<xul:toolbarbutton id="boldButton" type="checkbox" cmd="bold" tooltiptext="_('Bold')"/>
<xul:toolbarbutton id="italicButton" type="checkbox" cmd="italic" tooltiptext="_('Italic')"/>
<xul:toolbarbutton id="underlineButton" type="checkbox" cmd="underline" tooltiptext="_('Underline')"/>
<xul:toolbarseparator/>
<xul:toolbarbutton id="biggerFontButton" cmd="increaseFontSize" tooltiptext="_('Increase font size')"/>
<xul:toolbarbutton id="smallerFontButton" cmd="decreaseFontSize" tooltiptext="_('Decrease font size')"/>
<xul:toolbarseparator/>
<xul:toolbarbutton id="fontColorButton" cmd="foreColor" tooltiptext="_('Font color')"/>
<xul:toolbarbutton id="fontBackgroundButton" cmd="hiliteColor" tooltiptext="_('Background color')"/>
<children/>
</content>
<implementation>
<constructor><![CDATA[
var el = this.nextSibling;
if (el && el.localName != "textbox")
el = el.firstChild;
if (el && el.localName == "textbox" && el.getAttribute("type") == "resizable-richtext")
this._textbox = el;
]]></constructor>
<method name="_repairColor">
<parameter name="color"/>
<body>
<![CDATA[
var match;
if (!color)
return "#000000";
else if (color == "transparent")
return "#ffffff";
if (match = color.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/))
return "#"+(+match[1]+256).toString(16).substr(1)+
(+match[2]+256).toString(16).substr(1)+
(+match[3]+256).toString(16).substr(1);
return color;
]]>
</body>
</method>
<method name="_updateState">
<body><![CDATA[
if (!this._textbox || this._textbox._disabled)
return;
var doc = this._textbox._input.contentDocument;
for each (var cmd in "bold italic underline".split(" ")) {
document.getAnonymousElementByAttribute(this, "cmd", cmd).checked =
doc.queryCommandState(cmd);
}
for each (var cmd in "foreColor hiliteColor".split(" ")) {
document.getAnonymousElementByAttribute(this, "cmd", cmd).color =
this._repairColor(doc.queryCommandValue(cmd));
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="select"><![CDATA[
var el = event.originalTarget;
if (!this._textbox || !el || el.localName != "colorpicker")
return;
this._textbox._input.contentDocument.execCommand(
el.parentNode.parentNode.getAttribute("cmd"), false,
this._repairColor(el.color));
]]></handler>
<handler event="command"><![CDATA[
if (!this._textbox)
return;
var cmd = event.originalTarget.getAttribute("cmd");
if (cmd == "foreColor" || cmd == "hiliteColor") {
} else if (cmd) {
this._textbox._input.contentDocument.execCommand(cmd, false, null);
this._textbox.maybeResize();
}
]]></handler>
</handlers>
</binding>
<binding id="resizable-richtextbox">
<resources>
<stylesheet src="chrome://global/skin"/>
<stylesheet src="chrome://oneteam/skin/chatpane/textbox.css"/>
</resources>
<content>
<xul:stack flex="1">
<xul:hbox align="end" pack="end">
<xul:label id="_counter" value="0"/>
</xul:hbox>
<xul:vbox class="inputBox textbox-input-box" flex="1" xbl:inherits="context,spellcheck">
<html:iframe id="_input"/>
</xul:vbox>
</xul:stack>
</content>
<implementation implements="nsIDOMXULTextBoxElement">
<property name="value" onget="return this._input.contentDocument.body"
onset="return this._input.contentDocument.body.innerHTML = val"/>
<property name="editor" readonly="true">
<getter><![CDATA[
var win = this._input.contentWindow.
QueryInterface(Components.interfaces.nsIInterfaceRequestor);
var webNav = win.getInterface(Components.interfaces.nsIWebNavigation).
QueryInterface(Components.interfaces.nsIInterfaceRequestor);
return webNav.getInterface(Components.interfaces.nsIEditingSession).
getEditorForWindow(win);
]]></getter>
</property>
<property name="isEmpty" readonly="true">
<getter><![CDATA[
var brCount = 0;
function empty(node, firstLevel) {
if (node.nodeType == node.TEXT_NODE)
return node.nodeValue == "";
if (node.nodeName == "BR")
return !firstLevel || brCount++ > 0;
for (var i = 0; i < node.childNodes.length; i++)
if (!arguments.callee(node.childNodes[i]))
return false;
return true;
}
return empty(this._input.contentDocument.body, true);
]]></getter>
</property>
<property name="thread" onget="return this._thread">
<setter><![CDATA[
if (this._thread) {
//this._output.unhighlightThread(this._thread);
this._thread.destroy();
}
this._thread = val;
var doc = this._input.contentDocument;
if (val) {
//this._output.highlightThread(this._thread);
doc.body.setAttribute("threadId", val.age+1);
}
else
doc.body.removeAttribute("threadId")
]]></setter>
</property>
<property name="disabled" onget="return !!this._disabled">
<setter><![CDATA[
if (this._initialized && !this.disabled != !val) {
this._disabled = val;
this._input.contentDocument.body.setAttribute("disabled", !!val);
if (val)
this._disableInput();
else
this._enableInput();
} else
this._disabled = val;
this.parentNode.setAttribute("disabled", !!val);
]]></setter>
</property>
<property name="visible" onget="return this._visible">
<setter><![CDATA[
if (!this._visible == !val) {
this._visible = val;
return val;
}
this._visible = val;
if (!val && "_focusTimer" in this) {
clearTimeout(this._focusTimer);
delete this._focusTimer;
}
return val;
]]></setter>
</property>
<constructor><![CDATA[
var me = this;
this._counter = document.getAnonymousElementByAttribute(this, "id", "_counter");
this._input = document.getAnonymousElementByAttribute(this, "id", "_input");
this._input.addEventListener("load", function(){me._init()}, true);
var el = this.previousSibling;
if (!el)
el = this.parentNode.previousSibling;
if (el && el.localName == "toolbar" && el.getAttribute("type") == "chatpane-commands")
this._toolbar = el;
this._history = [];
this._historyPosition = 0;
]]></constructor>
<destructor><![CDATA[
if (this._maybeResizeTimeout)
clearTimeout(this._maybeResizeTimeout);
delete this._maybeResizeTimeout;
]]></destructor>
<method name="syncEditMode">
<body><![CDATA[
if (this.parentNode.parentNode.parentNode.editModeEnabled) {
/* _currentMessage is the message the user has left while he corrects the last one
he has sent: _currentMessage will be displayed again when the user will send
the edited message, or cancel the edition */
this._currentMessage = this.value.innerHTML;
this.clear();
this._input.parentNode.setAttribute("editMode", "true");
this._input.contentDocument.body.setAttribute("editMode", "true");
if (this.lastMessage)
this.value = this.lastMessage.html;
}
else {
if (this._currentMessage) {
this.value = this._currentMessage;
delete this._currentMessage;
}
this._input.parentNode.removeAttribute("editMode");
this._input.contentDocument.body.removeAttribute("editMode");
}
]]></body>
</method>
<method name="_disableInput">
<body><![CDATA[
var doc = this._input.contentDocument;
doc.designMode = "off";
for each (var event in ("mousedown paste keypress resize DOMSubtreeModified "+
"keydown keyup mouseup dragdrop").split(" "))
if (("_on"+event) in this)
doc.removeEventListener(event, this["_on"+event], (event == "keypress"));
delete this.lastMessage;
this.parentNode.parentNode.parentNode.sthgToEdit = false;
]]></body>
</method>
<method name="_enableInput">
<body><![CDATA[
var me = this;
var doc = this._input.contentDocument;
doc.designMode = "on";
try {
var scu = this._input.parentNode.spellCheckerUI;
scu.init(this.editor);
scu.enabled = true;
} catch (ex) {
}
doc.addEventListener("mousedown", this._onmousedown = function(event) {
if (me._toolbar)
me._toolbar._updateState(event);
if (event.button == 0 &&
event.pageX >= event.target.ownerDocument.body.clientWidth-42 &&
event.pageX < event.target.ownerDocument.body.clientWidth)
me._doSend(true);
}, false);
doc.addEventListener("paste", this._onpaste = function(event) {
var body = me._input.contentDocument.body;
setTimeout(sanitizeDOM, 0, body, function(el) {
return el == body || el.nodeName.toLowerCase() == "br" ||
el == body.firstChild && el.hasAttribute("thread-index") ?
"keep" : null;
});
}, false);
doc.addEventListener("keypress", this._onkeypress = function(event) {
event.stopPropagation();
if (me._disabled)
return false;
var doc = me._input.contentDocument;
if (event.keyCode == KeyEvent.DOM_VK_TAB && !event.ctrlKey) {
event.preventDefault();
var selection = me._input.contentWindow.getSelection();
var range = doc.createRange();
range.setStart(doc.body, 0);
range.setEnd(selection.anchorNode, selection.anchorOffset);
var res = me._tryCompletion(me._rangeContent(range), event.shiftKey);
if (res != null) {
range.deleteContents();
doc.execCommand("insertHTML", false, xmlEscape(res));
}
range.detach();
if ((doc.body.lastChild.localName||"").toUpperCase() != "BR")
doc.body.insertBefore(doc.createElement("br"), null);
me._onUserType();
return false;
}
var s;
var upKey = event.keyCode == KeyEvent.DOM_VK_UP;
if ((upKey || event.keyCode == KeyEvent.DOM_VK_DOWN) &&
!event.shiftKey && !event.altKey && !event.ctrlKey &&
!event.metaKey)
{
s = me._input.contentWindow.getSelection();
if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset &&
me._handleHistoryKey(upKey))
{
event.preventDefault();
me._onUserType();
return false;
}
}
if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
if (me._doSend(!event.shiftKey)) {
event.preventDefault();
return true;
} else {
me._onUserType();
return false;
}
}
if (event.keyCode == KeyEvent.DOM_VK_BACK_SPACE && me._thread) {
if (!s)
s = me._input.contentWindow.getSelection();
if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset && s.anchorOffset == 0) {
var n = s.anchorNode;
while (n != doc.body && !n.previousSibling)
n = n.parentNode;
if (n == doc.body)
me.thread = null;
}
me._onUserType();
return false;
}
if ((event.charCode == 69 || event.charCode == 101) // KeyEvent.DOM_VK_E == 69
&& !event.shiftKey && !event.metaKey && (
(!event.altKey && event.ctrlKey) // unix/windows
|| event.cmdKey // apple
)) {
me.parentNode.parentNode.parentNode.switchEditMode();
me._onUserType();
return false;
}
me._onUserType();
return true;
}, true);
window.addEventListener("resize", this._onresize = function(ev) {
if (ev.target == window)
try {
me.maybeResize()
} catch(ex){}
}, false);
doc.addEventListener("DOMSubtreeModified", this._onDOMSubtreeModified = function() {
me._counter.value = me._domToStr(me._input.contentDocument.body).
replace(/\n$/,"").length;
if (me._toolbar)
me._toolbar._updateState(event);
me.maybeResize();
me.modified = true;
}, false);
for each (var event in "keydown keyup mousedown mouseup dragdrop".split(" ")) {
doc.addEventListener(event, this["_on"+event] = function(event) {
if (me._toolbar)
me._toolbar._updateState(event);
}, false);
}
this.focus(true);
if (this._toolbar)
this._toolbar._updateState();
var chatpane = this.parentNode.parentNode.parentNode;
for (var nick in this._output._lastNickMessage) {
if (this._output._lastNickMessage[nick].contact.representsMe) {
this.lastMessage = this._output._lastNickMessage[nick];
chatpane.sthgToEdit =
this.lastMessage
&& !this.lastMessage.isOldFormatEditMessage
&& (!this.lastMessage.editCounter || this.lastMessage.editCounter < 3);
break;
}
}
]]></body>
</method>
<method name="_init">
<body><![CDATA[
var me = this;
var doc = this._input.contentDocument;
this._initialized = true;
var link = doc.createElement("link");
link.setAttribute("href", "chrome://oneteam/skin/chatpane/content.css");
link.setAttribute("rel", "stylesheet");
doc.getElementsByTagName("HEAD")[0].appendChild(link);
// the following line is experiential: else, if nothing was ever typed in the
// textbox, there are odd behaviour with deletion: impossible to delete <br/>
// and messages to edit, until typing sthg. I can't explain why. FX
this.value = "<br/>";
if (this._toolbar)
doc.body.setAttribute("hasSendButton", "true");
if (this.disabled)
doc.body.setAttribute("disabled", true);
else
this._enableInput();
this.maybeResize();
]]></body>
</method>
<method name="focus">
<parameter name="immediatelly"/>
<body><![CDATA[
if (this._input && !this._visible)
if (!this._input || !this._visible)
return;
if (immediatelly)
this._input.contentWindow.focus();
else if (!this._focusTimer)
this._focusTimer = setTimeout(function(el, _this) {
delete _this._focusTimer;
el.focus();
}, 100, this._input.contentWindow, this);
]]></body>
</method>
<method name="clear">
<body><![CDATA[
var thread = this.thread;
this._thread = null;
this._input.contentDocument.body.innerHTML = "<br>";
if (thread)
this.thread = thread;
this.maybeResize();
]]></body>
</method>
<method name="_append">
<parameter name="str"/>
<body><![CDATA[
var doc = this._input.contentDocument;
doc.execCommand("insertHTML", false, xmlEscape(str));
if ((doc.body.lastChild.localName||"").toUpperCase() != "BR")
doc.body.insertBefore(doc.createElement("br"), null);
this._onUserType();
]]></body>
</method>
<method name="_replayEvent">
<parameter name="event"/>
<parameter name="skipNavigation"/>
<body><![CDATA[
if (this._inReplayEvent)
return false;
var navKey = event.keyCode == event.DOM_VK_PAGE_UP ||
event.keyCode == event.DOM_VK_PAGE_DOWN ||
event.keyCode == event.DOM_VK_UP ||
event.keyCode == event.DOM_VK_DOWN ||
event.keyCode == event.DOM_VK_RIGHT ||
event.keyCode == event.DOM_VK_LEFT ||
event.keyCode == event.DOM_VK_HOME ||
event.keyCode == event.DOM_VK_END;
if (skipNavigation && navKey)
return false;
this._inReplayEvent = true;
this.modified = false;
var altKey = navigator.platform.indexOf("Mac") >= 0 ? 0 : event.altKey;
var replayEvent = this._input.contentDocument.createEvent("KeyboardEvent");
replayEvent.initKeyEvent("keypress", event.bubbles, event.cancelable,
this._input.contentWindow, event.ctrlKey,
altKey, event.shiftKey, event.metaKey,
event.keyCode, event.charCode);
this._input.contentDocument.documentElement.dispatchEvent(replayEvent);
this._inReplayEvent = false;
return this.modified || navKey;
]]></body>
</method>
<method name="_onUserType">
<body><![CDATA[
setTimeout(function(_this) {
var event = document.createEvent("Events");
event.initEvent("userTyping", false, true);
_this.dispatchEvent(event);
}, 100, this)
]]></body>
</method>
<method name="_domToStr">
<parameter name="node"/>
<body><![CDATA[
if (node.nodeType == node.TEXT_NODE)
return node.nodeValue;
if (node.nodeName == "BR")
return "\n";
var res = "";
for (var i = 0; i < node.childNodes.length; i++)
res += arguments.callee(node.childNodes[i]);
return res;
]]></body>
</method>
<method name="_rangeContent">
<parameter name="range"/>
<body><![CDATA[
return this._domToStr(range.cloneContents());
]]></body>
</method>
<method name="_tryCompletion">
<body><![CDATA[
]]></body>
</method>
<method name="_doSend">
<parameter name="withEnter"/>
<body><![CDATA[
if (this._disabled)
return false;
var sentMessage = this._send(withEnter);
var value = sentMessage && sentMessage.html;
if (value == null)
return false;
if (value) {
if (!this._history.length || this._history[this._history.length-1] != value)
this._history.push(value);
this._historyPosition = this._history.length;
}
this.clear();
this.parentNode.parentNode.parentNode.editModeEnabled = false;
this.focus();
return true;
]]></body>
</method>
<method name="_send">
<parameter name="withEnter"/>
<body><![CDATA[
return withEnter ? null : this._input.value;
]]></body>
</method>
<method name="_blink">
<body><![CDATA[
if (this._blinkAnimation)
this._blinkAnimation.stop();
this.style.outline = "3px solid transparent";
this._blinkAnimation = Animator.animateStyle({
element: this,
style: "outlineColor",
stopValue: "transparent",
}, "parent.transparent", "borderTopColor", "parent.transparent");
]]></body>
</method>
<method name="maybeResize">
<body><![CDATA[
if (!this._maybeResizeTimeout)
this._maybeResizeTimeout = setTimeout(this._maybeResize, 0, this);
]]></body>
</method>
<method name="_maybeResize">
<parameter name="me"/>
<body><![CDATA[
delete me._maybeResizeTimeout;
me._preMaybeResize();
var body = me._input.contentDocument.body;
var counterBB = me._counter.getBoundingClientRect();
body.style.marginRight = counterBB.width+"px";
var h = me._input.clientHeight + body.offsetHeight - body.clientHeight;
if (counterBB.height > h)
h = counterBB.height;
if (h < 25)
h = 25;
if (h > 0.3*window.innerHeight) {
body.style.overflowY = "auto"
h = 0.3*window.innerHeight;
} else
body.style.overflowY = "hidden";
me._counter.style.marginRight = (50+me._input.clientWidth-body.clientWidth)+"px";
me._input.style.height = (h) + "px";
me._postMaybeResize();
]]></body>
</method>
<method name="_preMaybeResize">
<body> </body>
</method>
<method name="_postMaybeResize">
<body> </body>
</method>
<method name="_handleHistoryKey">
<parameter name="up"/>
<body><![CDATA[
var histPos = this._historyPosition + (up ? -1 : 1);
if (histPos < 0 || histPos > this._history.length)
return false;
var selection = this._input.contentWindow.getSelection();
var range = this._input.contentDocument.createRange();
if (up) {
range.setStart(this._input.contentDocument.body, 0);
range.setEnd(selection.anchorNode, selection.anchorOffset);
} else {
range.setStart(selection.focusNode, selection.focusOffset);
range.setEndAfter(this._input.contentDocument.body);
}
var val = this.value.innerHTML.replace(/<br>$/, "");
var rangeContent = this._rangeContent(range)
if (this._historyPosition == this._history.length)
this._enteredText = val;
if ("getClientRects" in range) {
if (range.getClientRects().length > 1)
return false;
} else if (rangeContent && rangeContent != "\n")
return false;
if (this._historyPosition < this._history.length) {
if (val)
this._history[this._historyPosition] = val;
} else
this._enteredText = val;
this.clear();
var content = histPos < this._history.length ? this._history[histPos] :
this._enteredText;
if (content) {
this._input.contentDocument.execCommand("insertHTML", false, content);
selection = this._input.contentWindow.getSelection();
selection.selectAllChildren(this._input.contentDocument.body);
if (up)
selection.collapseToEnd();
else
selection.collapseToStart();
}
this._historyPosition = histPos;
this.maybeResize();
return true;
]]></body>
</method>
</implementation>
</binding>
<binding id="chatpane-toolbar" extends="chatpane.xml#richtext-toolbar">
<resources>
<stylesheet src="chrome://global/skin"/>
<stylesheet src="chrome://oneteam/skin/chatpane/textbox.css"/>
</resources>
<content tbalign="center">
<xul:toolbarbutton id="editButton" type="disabled" tooltiptext="_('Edit Last Message')"
oncommand="if (this.type != 'disabled')
this.parentNode.parentNode.parentNode.switchEditMode()"/>
<xul:toolbarseparator/>
<xul:toolbarbutton id="vcardButton" oncommand="this.parentNode.model.showVCard()"
tooltiptext="_('Show contact details')"/>
<xul:toolbarseparator id="sendFileSep"/>
<xul:toolbarbutton id="sendFile" oncommand="this.parentNode._model.onSendFile()"
tooltiptext="_('Send file')"/>
<xul:toolbarseparator id="jingleCallSep"/>
<xul:toolbarbutton id="jingleCall" oncommand="this.parentNode._model.onJingleCall()"
tooltiptext="_('Jingle call')"/>
<xul:toolbarseparator/>
<xul:toolbarbutton id="smilesList" type="smiles-list"
tooltiptext="_('Toggle smiles visibility/select smile')"/>
<xul:toolbarseparator/>
<xul:toolbarbutton id="extraCmds" tooltiptext="_('Additional commands')" type="menu">
<xul:menupopup id="extraCmds-popup" onpopupshowing="this.parentNode.parentNode._syncMenu()">
<xul:menuitem label="_('Invite to chat room')"
onmenushowing="this.hidden = contact instanceof ConferenceMember ||
contact instanceof Conference;
this.disabled = account.conferences.length == 0"
oncommand="this.parentNode.parentNode.parentNode._model.onInvite()"/>
<xul:menuitem label="_('Invite contact')"
onmenushowing="this.hidden = !(contact instanceof Conference)"
oncommand="this.parentNode.parentNode.parentNode._model.onInvite()"/>
<xul:menuitem label="_('Invite user by mail')"
onmenushowing="this.hidden = !(contact instanceof Conference)"
oncommand="this.parentNode.parentNode.parentNode._model.onInviteByMail()"/>
<xul:menuseparator/>
<xul:menuitem id="charCounter" label="_('Show characters counter')" type="checkbox"
oncommand="this.parentNode.parentNode.parentNode.showCounter = !this.checked"/>
</xul:menupopup>
</xul:toolbarbutton>
<xul:spacer flex="1"/>
<xul:toolbarbutton id="spellingButton" tooltiptext="_('Autospellchecker')"
type="menu-button"
oncommand="spellcheck.toggle(this);">
<xul:menupopup id="languageMenuList" onpopupshowing="spellcheck.onShowDictionariesMenu(this, event.target);"/>
</xul:toolbarbutton>
<xul:toolbarseparator/>
<xul:toolbarbutton id="boldButton" type="checkbox" cmd="bold" tooltiptext="_('Bold')"/>
<xul:toolbarbutton id="italicButton" type="checkbox" cmd="italic" tooltiptext="_('Italic')"/>
<xul:toolbarbutton id="underlineButton" type="checkbox" cmd="underline" tooltiptext="_('Underline')"/>
<xul:toolbarseparator class="transparent"/>
<xul:toolbarbutton id="biggerFontButton" cmd="increaseFontSize" tooltiptext="_('Increase font size')"/>
<xul:toolbarbutton id="smallerFontButton" cmd="decreaseFontSize" tooltiptext="_('Decrease font size')"/>
<xul:toolbarseparator class="transparent"/>
<xul:toolbarbutton id="fontColorButton" cmd="foreColor" tooltiptext="_('Font color')"/>
<xul:toolbarbutton id="fontBackgroundButton" cmd="hiliteColor" tooltiptext="_('Background color')"/>
</content>
<implementation>
<property name="model" onget="return this._model">
<setter><![CDATA[
this._model = val;
if (val)
this.showCounter = account.cache.getValue("showCounter-"+val.jid.normalizedJID.shortJID);
else
this.showCounter = false;
if (this._token)
this._token.unregisterFromAll()
this._token = null;
if (val)
this._token = val.registerView(this._updateJingleState, this,
"jingleResource", this._token);
this._updateJingleState();
this._sendFile.hidden = this._sendFileSep.hidden =
!val || typeof(socks5Service) == "undefined" ||
!socks5Service.canSendTo(val.contact ? val : val.activeResource);
this._syncMenu();
]]></setter>
</property>
<property name="showCounter" onget="return this._showCounter">
<setter><![CDATA[
this._showCounter = val;
var counter = document.getAnonymousElementByAttribute(this, "id", "charCounter");
counter.setAttribute("checked", !!val);
counter.checked = !!val;
if (this._textbox) {
this._textbox._counter.hidden = !val;
this._textbox.maybeResize();
}
if (this._model)
if (val)
account.cache.setValue("showCounter-"+this.model.jid.normalizedJID.shortJID, true);
else
account.cache.removeValue("showCounter-"+this.model.jid.normalizedJID.shortJID);
]]></setter>
</property>
<constructor><![CDATA[
this._sendFile = document.getAnonymousElementByAttribute(this, "id", "sendFile");
this._sendFileSep = document.getAnonymousElementByAttribute(this, "id", "sendFileSep");
this._jingleCall = document.getAnonymousElementByAttribute(this, "id", "jingleCall");
this._jingleCallSep = document.getAnonymousElementByAttribute(this, "id", "jingleCallSep");
this._extraCmds = document.getAnonymousElementByAttribute(this, "id", "extraCmds");
recoverSetters(this);
]]></constructor>
<method name="syncEditMode">
<body><![CDATA[
var button = document.getAnonymousElementByAttribute(this, "id", "editButton");
button.type =
!this.parentNode.parentNode.editModeAvailable ? "disabled" :
this.parentNode.parentNode.editModeEnabled ? "cancel" : "edit";
button.setAttribute("tooltiptext",
button.type == "cancel" ? _("Cancel Edition") : _("Edit Last Message"));
]]></body>
</method>
<method name="_updateJingleState">
<body><![CDATA[
this._jingleCall.hidden = this._jingleCallSep.hidden =
!this.model || !this.model.jingleResource;
]]></body>
</method>
<method name="_syncMenu">
<body><![CDATA[
var list = this._extraCmds.firstChild.childNodes;
var hasAnyEntry = false;
for (var i = 0; i < list.length; i++) {
if (!list[i].onmenushowing)
list[i].onmenushowing = new Function("contact", list[i].getAttribute("onmenushowing"));
list[i].onmenushowing(this._model);
if (!list[i].hidden)
hasAnyEntry = true;
}
this._extraCmds.hidden = !hasAnyEntry;
]]></body>
</method>
</implementation>
<handlers>
<handler event="selectedSmile">
<![CDATA[
if (this._textbox)
this._textbox._append(event.originalTarget.selectedSmile.texts[0]);
]]>
</handler>
</handlers>
</binding>
<binding id="chatpane">
<resources>
<stylesheet src="chrome://global/skin"/>
<stylesheet src="chrome://oneteam/skin/chatpane/chatpane.css"/>
</resources>
<content>
<xul:vbox flex="1" smiles-enabled="true">
<xul:hbox flex="1">
<xul:vbox flex="1">
<xul:contactinfo class="noCounter" id="contactInfo">
<xul:textbox id="search-field" type="search" hidden="true"
timeout="50" emptytext="_('Search')"/>
<xul:hbox id="thread-picker" flex="1000" hidden="true">
<xul:deck flex="1">
<xul:hbox align="center" pack="end">
<xul:label value="_('THREAD')"/>
<xul:radiogroup id="thread-picker-container" orient="horizontal"/>
</xul:hbox>
<xul:hbox align="center" pack="end">
<xul:label value="_('THREAD')"/>
<xul:menulist id="thread-picker-menulist">
<xul:menupopup/>
</xul:menulist>
</xul:hbox>
</xul:deck>
</xul:hbox>
</xul:contactinfo>
<xul:chatpane-view id="output-pane" flex="1"/>
</xul:vbox>
<xul:splitter class="roster-splitter" hidden="true"/>
<xul:vbox class="roster-container" hidden="true">
<xul:hbox align="baseline" pack="end">
<xul:label value="_('BOOKMARK')"/>
<xul:image class="bookmark-star" onclick="this._controller.onStarClick()"/>
</xul:hbox>
<xul:richlistbox id="roster" flex="1" width="200"/>
</xul:vbox>
</xul:hbox>
<xul:toolbar type="chatpane-commands"/>
<xul:hbox id="input-container">
<xul:textbox type="resizable-richtext" id="input" class="input" flex="1"
multiline="true" spellcheck="true" context="inputContext"/>
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<property name="inputIsEmpty" onget="return this._input.isEmpty" readonly="true"/>
<property name="focusHandler"
onset="this._attachFocusHandler(this, val); return val"
onget="return this._focusHandler" />
<property name="model" onget="return this._model">
<setter><![CDATA[
var isConference = val && val.contact instanceof Conference;
var hasRoster = val && val.contact && val.contact.ViewConstructor;
this._model = val;
this._contactInfo.model = val && val.contact;
this._output.model = val;
this._output._input = this._input;
this._input._output = this._output;
this._chatpaneCommands.model = val && val.contact;
this.completionEngine = val && val.contact.createCompletionEngine();
if (this._regToken) {
this._regToken.unregisterFromAll();
this._regToken = null;
}
if (this._rosterView) {
this._rosterView.destroy();
this._rosterView = null;
}
if (val && val.contact) {
this._contactInfo.parentNode.menuModel = val.contact;
this._contactInfo.parentNode.setAttribute("context",
(isConference ? "conference" : "contact")+"-contextmenu");
if (hasRoster)
this._rosterView = new val.contact.ViewConstructor(val.contact, this._roster);
if (isConference) {
this._regToken = account.bookmarks.registerView(this._onBookmarksUpdated, this,
"bookmarks", this._regToken)
this._onBookmarksUpdated();
}
this._regToken = account.registerView(this._onConnect, this,
"connected", this._regToken);
}
this._searchField.hidden = !val || !("refineSearch" in val);
this._roster.parentNode.hidden = this._roster.parentNode.previousSibling.hidden =
!hasRoster;
this.setAttribute("hasRoster", !!hasRoster);
return val;
]]></setter>
</property>
<property name="visible" onget="return this._visible">
<setter><![CDATA[
this._visible = val;
try {
this._output.visible = val;
} catch (ex) { }
try {
this._input.visible = val;
} catch (ex) { }
return val;
]]></setter>
</property>
<property name="sthgToEdit" onget="return this._sthgToEdit">
<setter><![CDATA[
this._sthgToEdit = !!val;
this.editModeAvailable = !!val && !this._input.disabled;
]]></setter>
</property>
<property name="editModeAvailable" onget="return this._editModeAvailable">
<setter><![CDATA[
this._editModeAvailable = val;
this._chatpaneCommands.syncEditMode();
this._output.syncEditMode();
]]></setter>
</property>
<property name="editModeEnabled" onget="return this._editModeEnabled">
<setter><![CDATA[
this._editModeEnabled = this._editModeAvailable ? val : false;
this._input.syncEditMode();
this._chatpaneCommands.syncEditMode();
this._output.syncEditMode();
]]></setter>
</property>
<constructor><![CDATA[
this._sthgToEdit = this._editModeAvailable = this._editModeEnabled = false;
this.sendOnEnter = this.getAttribute("sendonenter") != "false";
this._input = document.getAnonymousElementByAttribute(this, "id", "input");
this._container = this._input.parentNode.parentNode;
this._output = document.getAnonymousElementByAttribute(this, "id", "output-pane");
this._roster = document.getAnonymousElementByAttribute(this, "id", "roster");
this._contactInfo = document.getAnonymousElementByAttribute(this, "id", "contactInfo");
this._chatpaneCommands = document.getAnonymousElementByAttribute(this, "type", "chatpane-commands");
this._smilesList = document.getAnonymousElementByAttribute(this, "id", "smilesList");
this._bookmarkStar = document.getAnonymousElementByAttribute(this, "class", "bookmark-star");
this._threadPicker = document.getAnonymousElementByAttribute(this, "id", "thread-picker");
this._threadPickerContainer = document.getAnonymousElementByAttribute(this, "id", "thread-picker-container");
this._threadPickerMenulist = document.getAnonymousElementByAttribute(this, "id", "thread-picker-menulist");
this._searchField = document.getAnonymousElementByAttribute(this, "id", "search-field");
this._bookmarkStar._controller = this;
this._input._tryCompletion = new Callback(this._tryCompletion, this);
this._input._send = new Callback(this._send, this);
this._input._preMaybeResize = new Callback(this._preMaybeResize, this);
this._input._postMaybeResize = new Callback(this._postMaybeResize, this);
this._input.disabled = !account.connected;
this._output._threadsChangedFun = new Callback(this._onThreadsChanged, this);
var deck = this._threadPickerContainer.parentNode;
deck.addEventListener("underflow", function(event) {
event.target.parentNode.selectedIndex = 0;
}, false);
deck.addEventListener("overflow", function(event) {
event.target.parentNode.selectedIndex = 1;
}, false);
this.addThread("ALL", "-1");
recoverSetters(this);
var me = this;
this.addEventListener("keypress", function(ev) {
var switchTab = 0;
if ((ev.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
ev.keyCode == KeyEvent.DOM_VK_PAGE_DOWN)) {
if (ev.shiftKey && !ev.altKey && !ev.ctrlKey && !ev.metaKey) {
ev.stopPropagation();
ev.preventDefault();
if (ev.keyCode == KeyEvent.DOM_VK_PAGE_UP)
me._output.scrollPageUp();
else
me._output.scrollPageDown();
} else if (!ev.shiftKey && !ev.altKey && ev.ctrlKey && !ev.metaKey) {
ev.stopPropagation();
ev.preventDefault();
switchTab = ev.keyCode == KeyEvent.DOM_VK_PAGE_UP ? -1 : 1;
}
} else if (ev.keyCode == KeyEvent.DOM_VK_TAB && !ev.altKey && !ev.metaKey &&
ev.ctrlKey)
{
ev.stopPropagation();
ev.preventDefault();
switchTab = ev.shiftKey ? -1 : 1;
}
if (switchTab) {
var tabbox = me.parentNode && me.parentNode._tabbox;
if (tabbox)
tabbox._tabs.advanceSelectedTab(switchTab, true)
}
}, true);
setTimeout(function(me) {
if (!me._output._output) {
setTimeout(arguments.callee, 10, me);
return;
}
me._output._output.contentWindow.addEventListener("keypress", function(event) {
if (me._input._replayEvent(event, true)) {
event.stopPropagation();
event.preventDefault();
me.focus();
}
}, false);
}, 0, this);
]]></constructor>
<method name="switchEditMode">
<body><![CDATA[
if (this.editModeEnabled)
this.editModeEnabled = false;
else if (this.editModeAvailable)
this.editModeEnabled = true;
]]></body>
</method>
<method name="_attachFocusHandler">
<parameter name="_this"/>
<parameter name="handler"/>
<parameter name="internal"/>
<body><![CDATA[
if (!internal) {
if (_this._focusHandler == handler)
return;
_this._focusHandler = handler;
} else
delete _this._focusHandlerAttachTimeout;
if (!_this._output || !_this._input ||
!_this._output._initialized || !_this._input._initialized)
{
if (_this._focusHandlerAttachTimeout == null)
_this._focusHandlerAttachTimeout = window.setTimeout(arguments.callee, 10, _this, null, true);
return;
}
if (_this._focusHandler) {
if (!_this._focusHandlers) {
_this._focusHandlers = [];
_this._focusHandlers[0] = function(event) {
if ("nodeType" in event.target && event.target.nodeType == 9 && _this._focusHandler)
_this._focusHandler._windowFocusHandler(_this._focusHandler, event);
};
_this._focusHandlers[1] = function(event) {
if ("nodeType" in event.target && event.target.nodeType == 9 && _this._focusHandler)
_this._focusHandler._windowBlurHandler(_this._focusHandler, event);
};
}
if (!_this._focusHandlerAttached) {
_this._input._input.contentWindow.addEventListener("focus", _this._focusHandlers[0], true);
_this._input._input.contentWindow.addEventListener("blur", _this._focusHandlers[1], true);
_this._output._output.contentWindow.addEventListener("focus", _this._focusHandlers[0], true);
_this._output._output.contentWindow.addEventListener("blur", _this._focusHandlers[1], true);
_this._focusHandlerAttached = true;
}
} else if (_this._focusHandlerAttached) {
_this._input._input.contentWindow.removeEventListener("focus", _this._focusHandlers[0], true);
_this._input._input.contentWindow.removeEventListener("blur", _this._focusHandlers[1], true);
_this._output._output.contentWindow.removeEventListener("focus", _this._focusHandlers[0], true);
_this._output._output.contentWindow.removeEventListener("blur", _this._focusHandlers[1], true);
_this._focusHandlerAttached = false;
}
]]></body>
</method>
<method name="_onBookmarksUpdated">
<body><![CDATA[
this._bookmarkStar.setAttribute("bookmarked",
account.bookmarks.hasBookmarkForJid(this._model.contact.jid))
]]></body>
</method>
<method name="onStarClick">
<body><![CDATA[
if (account.bookmarks.hasBookmarkForJid(this._model.contact.jid))
account.onManageBookmarks(this._model.contact.jid);
else
this._model.contact.onBookmark();
]]></body>
</method>
<method name="_onConnect">
<body><![CDATA[
this._input.disabled = !account.connected;
]]></body>
</method>
<method name="focus">
<body><![CDATA[
this._input.focus();
]]></body>
</method>
<method name="clear">
<body><![CDATA[
this._input.clear();
]]></body>
</method>
<method name="clearOutput">
<body><![CDATA[
this._output.clear();
]]></body>
</method>
<method name="addThread">
<parameter name="label"/>
<parameter name="value"/>
<body><![CDATA[
var radio = document.createElementNS(XULNS, "radio");
radio.setAttribute("value", value);
radio.setAttribute("label", label);
if (!this._threadPickerContainer.childNodes.length)
radio.setAttribute("selected", "true");
this._threadPickerContainer.appendChild(radio);
var menuitem = document.createElementNS(XULNS, "menuitem");
menuitem.setAttribute("label", label);
menuitem.setAttribute("value", value);
menuitem.setAttribute("class", "menuitem-noaccel");
this._threadPickerMenulist.firstChild.appendChild(menuitem);
if (this._threadPickerMenulist.firstChild.childNodes.length == 1)
this._threadPickerMenulist.selectedIndex = 0;
var ss = document.styleSheets[document.styleSheets.length-1];
if (this._maxThreads >= value)
return;
this._maxThreads = value;
]]></body>
</method>
<method name="_onThreadsChanged">
<parameter name="thread"/>
<body><![CDATA[
if (thread) {
this._threadPicker.hidden = false;
this.addThread(thread.age+1, thread.age);
} else {
var threads = this._output._threadingDriver.threadsByAge;
if (threads.length == 0)
this._threadPicker.hidden = true;
var list = this._threadPickerContainer;
var list2 = this._threadPickerMenulist.firstChild;
while (threads.length < list.childNodes.length-1) {
list.removeChild(list.lastChild);
list2.removeChild(list2.lastChild);
}
var st = this._output._selectedThread;
if (st) {
var thread = st.age < 0 ?
null : this._output._threadingDriver.threadsByAge[st];
if (thread != st) {
this._output._selectThread(thread);
this._threadPickerMenulist.value = this._threadPickerContainer.value =
this._output._selectedThread ?
this._output._selectedThread.age : -1;
}
}
}
]]></body>
</method>
<method name="_tryCompletion">
<parameter name="str"/>
<body><![CDATA[
if (!this.completionEngine)
return;
var res = this.completionEngine.complete(str);
if (res == null)
this._input._blink();
return res;
]]></body>
</method>
<method name="_preMaybeResize">
<body><![CDATA[
try {
var el = this._output._output.contentDocument.documentElement;
this._outputScrollPos = el.scrollTop + el.clientHeight;
} catch (ex) {}
]]></body>
</method>
<method name="_postMaybeResize">
<body><![CDATA[
try {
var el = this._output._output.contentDocument.documentElement;
el.scrollTop = this._outputScrollPos - el.clientHeight;
} catch (ex) {}
]]></body>
</method>
<method name="_doSend">
<parameter name="withEnter"/>
<body><![CDATA[
return this._input._doSend(withEnter);
]]></body>
</method>
<method name="_send">
<parameter name="withEnter"/>
<body><![CDATA[
if (!this.sendOnEnter || !withEnter)
return null;
var text = this._input.value.textContent;
if (~text.search(/\S/) || this.editModeEnabled) {
if (!this.completionEngine || !this.completionEngine.execCommand(text)) {
// the following lines are a fallback for the thread feature
var idx = text.indexOf(":");
var nick = idx > 0 ? text.substr(0, idx) : "";
var msgId = nick in this._output._lastNickMessageId ?
this._output._lastNickMessageId[nick] : null;
if (msgId && !this._input.thread)
this._output._setInputReplyMsgId(msgId);
// end of the fallback
var ev = document.createEvent("Events");
ev.initEvent("chat-send", true, false);
this.dispatchEvent(ev);
var body = this._input.value;
if ((body.lastChild.localName||"").toUpperCase() == "BR")
body.removeChild(body.lastChild);
var myResource = this._model.contact instanceof ConferenceMember ?
this._model.contact.contact.myResource : account.myResource;
var msg = new Message(null, body, myResource,
this._model.contact instanceof Conference ? 1 : 3,
null, this._model);
if (this._input.thread)
msg.xReplyTo = [this._input.msgId];
if (this.editModeEnabled)
msg.replaceMessageId = this._input.lastMessage.messageId;
this._model.sendMessage(msg);
return msg;
}
}
return "";
]]></body>
</method>
</implementation>
<handlers>
<handler event="command"><![CDATA[
if (event.originalTarget.parentNode == this._threadPickerContainer ||
event.originalTarget.parentNode.parentNode == this._threadPickerMenulist)
{
this._output._selectThread(this._output._threadingDriver.
threadsByAge[event.originalTarget.value]);
this._threadPickerMenulist.value = this._threadPickerContainer.value =
event.originalTarget.value;
var doc = this._output._output.contentDocument;
doc.body.scrollTop = doc.body.scrollHeight;
} else if (event.originalTarget == this._searchField) {
this.model.refineSearch(this._searchField.value);
}
]]></handler>
<handler event="smilesToggle"><![CDATA[
var enabled = this._container.getAttribute("smiles-enabled") != "true";
this._container.setAttribute("smiles-enabled", enabled);
this._output.smilesEnabled = enabled;
]]></handler>
</handlers>
</binding>
</bindings>
Jump to Line
Something went wrong with that request. Please try again.