Skip to content

Instantly share code, notes, and snippets.

@arlolra
Created July 2, 2014 18:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arlolra/14d38c35c65b555677ec to your computer and use it in GitHub Desktop.
Save arlolra/14d38c35c65b555677ec to your computer and use it in GitHub Desktop.
diff --git a/chat/components/public/prplIConversation.idl b/chat/components/public/prplIConversation.idl
--- a/chat/components/public/prplIConversation.idl
+++ b/chat/components/public/prplIConversation.idl
@@ -6,16 +6,17 @@
#include "nsISupports.idl"
#include "nsISimpleEnumerator.idl"
#include "nsIObserver.idl"
interface imIAccountBuddy;
interface imIAccount;
interface nsIURI;
interface nsIDOMDocument;
+interface cancellableOutgoingMessage;
/*
* This is the XPCOM purple conversation component, a proxy for PurpleConversation.
*/
[scriptable, uuid(e40dc3e5-c9ff-457b-a6cc-655cce81042c)]
interface prplIConversation: nsISupports {
@@ -39,16 +40,18 @@ interface prplIConversation: nsISupports
readonly attribute PRTime startDate;
/* Unique identifier of the conversation */
/* Setable only once by purpleCoreService while calling addConversation. */
attribute unsigned long id;
/* Send a message in the conversation */
void sendMsg(in AUTF8String aMsg);
+ void prepareForSending(in cancellableOutgoingMessage aOm);
+
/* Send information about the current typing state to the server.
aString should contain the content currently in the text field. The
protocol should return the number of characters that can still be typed. */
long sendTyping(in AUTF8String aString);
const long NO_TYPING_LIMIT = 2147483647; // max int = 2 ^ 31 - 1
/* Un-initialize the conversation. Will be called by
purpleCoreService::RemoveConversation when the conversation is
diff --git a/chat/components/public/prplIMessage.idl b/chat/components/public/prplIMessage.idl
--- a/chat/components/public/prplIMessage.idl
+++ b/chat/components/public/prplIMessage.idl
@@ -1,14 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsIRunnable.idl"
+#include "nsIMutableArray.idl"
#include "prplIConversation.idl"
/*
* An action that the user may perform in relation to a particular message.
*/
[scriptable, uuid(7e470f0e-d948-4d9a-b8dc-4beecf6554b9)]
interface prplIMessageAction: nsIRunnable
{
@@ -71,8 +72,17 @@ interface prplIMessage: nsISupports {
/* An array of actions the user may perform on this message.
The first action will be the 'default' and may be performed
automatically when the message is double clicked.
'Reply' is usually a good default action. */
void getActions([optional] out unsigned long actionCount,
[retval, array, size_is(actionCount)] out prplIMessageAction actions);
};
+
+[scriptable, uuid(a7eb9e1A-6bcf-4668-986f-a59822ed3e19)]
+interface cancellableOutgoingMessage: nsISupports {
+ attribute nsIMutableArray sendableMessages; // array of AUTF8String
+ attribute AUTF8String displayableMessage;
+ attribute cancellableOutgoingMessage nextDisplayableMessage;
+ attribute boolean cancelled;
+ attribute prplIConversation target;
+};
diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConversations.js
--- a/chat/components/src/imConversations.js
+++ b/chat/components/src/imConversations.js
@@ -284,17 +284,34 @@ UIConversation.prototype = {
// prplIConversation
get isChat() this.target.isChat,
get account() this.target.account,
get name() this.target.name,
get normalizedName() this.target.normalizedName,
get title() this.target.title,
get startDate() this.target.startDate,
- sendMsg: function (aMsg) { this.target.sendMsg(aMsg); },
+ sendMsg: function(aMsg) {
+ let om = new CancellableOutgoingMessage(aMsg, this.target);
+
+ this.notifyObservers(om, "preparing-message", null);
+ this.target.prepareForSending(om);
+ this.notifyObservers(om, "sending-message", null);
+
+ while (om) {
+ if (!om.cancelled) {
+ let count = om.sendableMessages.length;
+ for (let i = 0; i < count; ++i) {
+ let msg = om.sendableMessages.queryElementAt(i, Ci.nsISupportsString);
+ this.target.sendMsg(msg.toString());
+ }
+ }
+ om = om.nextDisplayableMessage;
+ }
+ },
unInit: function() {
for each (let conv in this._prplConv)
gConversationsService.forgetConversation(conv);
if (this._observedContact) {
this._observedContact.removeObserver(this);
delete this._observedContact;
}
this._prplConv = {}; // Prevent .close from failing.
diff --git a/chat/modules/jsProtoHelper.jsm b/chat/modules/jsProtoHelper.jsm
--- a/chat/modules/jsProtoHelper.jsm
+++ b/chat/modules/jsProtoHelper.jsm
@@ -6,16 +6,17 @@ const EXPORTED_SYMBOLS = [
"GenericAccountPrototype",
"GenericAccountBuddyPrototype",
"GenericConvIMPrototype",
"GenericConvChatPrototype",
"GenericConvChatBuddyPrototype",
"GenericMessagePrototype",
"GenericProtocolPrototype",
"Message",
+ "CancellableOutgoingMessage",
"TooltipInfo"
];
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
Cu.import("resource:///modules/imXPCOMUtils.jsm");
Cu.import("resource:///modules/imServices.jsm");
@@ -424,16 +425,26 @@ const GenericMessagePrototype = {
}
};
function Message(aWho, aMessage, aObject) {
this._init(aWho, aMessage, aObject);
}
Message.prototype = GenericMessagePrototype;
+function CancellableOutgoingMessage(aMsg, aTarget) {
+ this.sendableMessages = [aMsg];
+ this.displayableMessage = aMsg;
+ this.nextDisplayableMessage = null;
+ this.target = aTarget;
+}
+CancellableOutgoingMessage.prototype = {
+ constructor: CancellableOutgoingMessage,
+ cancelled: false
+};
const GenericConversationPrototype = {
__proto__: ClassInfo("prplIConversation", "generic conversation object"),
get wrappedJSObject() this,
get DEBUG() this._account.DEBUG,
get LOG() this._account.LOG,
get WARN() this._account.WARN,
@@ -467,31 +478,46 @@ const GenericConversationPrototype = {
try {
observer.observe(aSubject, aTopic, aData);
} catch(e) {
this.ERROR(e);
}
}
},
- sendMsg: function (aMsg) {
+ prepareForSending: function(aOm) {},
+ sendMsg: function(aMsg) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
sendTyping: function(aString) Ci.prplIConversation.NO_TYPING_LIMIT,
close: function() {
Services.obs.notifyObservers(this, "closing-conversation", null);
Services.conversations.removeConversation(this);
},
unInit: function() {
delete this._account;
delete this._observers;
},
writeMessage: function(aWho, aText, aProperties) {
+ if (!aProperties.system) {
+ let om = new CancellableOutgoingMessage(aText, this);
+
+ if (aProperties.outgoing) {
+ Services.obs.notifyObservers(om, "displaying-message", null);
+ } else if (aProperties.incoming) {
+ Services.obs.notifyObservers(om, "receiving-message", null);
+ }
+
+ if (om.cancelled)
+ return;
+
+ aText = om.displayableMessage;
+ }
(new Message(aWho, aText, aProperties)).conversation = this;
},
get account() this._account.imAccount,
get name() this._name,
get normalizedName() this._account.normalize(this.name),
get title() this.name,
get startDate() this._date
diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js
--- a/chat/protocols/irc/irc.js
+++ b/chat/protocols/irc/irc.js
@@ -129,59 +129,67 @@ const GenericIRCConversation = {
getMaxMessageLength: function() {
// Build the shortest possible message that could be sent to other users.
let baseMessage = ":" + this._account._nickname + this._account.prefix +
" " + this._account.buildMessage("PRIVMSG", this.name) +
" :\r\n";
return this._account.maxMessageLength -
this._account.countBytes(baseMessage);
},
+ prepareForSending: function(aOm) {
+ let sendableMessages = [];
+ let count = aOm.sendableMessages.length;
+ for (let i = 0; i < count; ++i) {
+ let aMessage = aOm.sendableMessages.queryElementAt(i, Ci.nsISupportsString).toString();
+
+ // Split the message by line breaks and send each one individually.
+ let messages = aMessage.split(/[\r\n]+/);
+
+ let maxLength = this.getMaxMessageLength();
+
+ // Attempt to smartly split a string into multiple lines (based on the
+ // maximum number of characters the message can contain).
+ for (let i = 0; i < messages.length; ++i) {
+ let message = messages[i];
+ let length = this._account.countBytes(message);
+ // The message is short enough.
+ if (length <= maxLength)
+ continue;
+
+ // Find the location of a space before the maximum length.
+ let index = message.lastIndexOf(" ", maxLength);
+
+ // Remove the current message and insert the two new ones. If no space was
+ // found, cut the first message to the maximum length and start the second
+ // message one character after that. If a space was found, exclude it.
+ messages.splice(i, 1, message.substr(0, index == -1 ? maxLength : index),
+ message.substr((index + 1) || maxLength));
+ }
+ sendableMessages.concat(messages);
+ }
+ aOm.sendableMessages = sendableMessages;
+ if (aOm.nextDisplayableMessage)
+ this.prepareForSending(aOm.nextDisplayableMessage);
+ },
sendMsg: function(aMessage) {
- // Split the message by line breaks and send each one individually.
- let messages = aMessage.split(/[\r\n]+/);
+ if (!aMessage.length)
+ return;
- let maxLength = this.getMaxMessageLength();
-
- // Attempt to smartly split a string into multiple lines (based on the
- // maximum number of characters the message can contain).
- for (let i = 0; i < messages.length; ++i) {
- let message = messages[i];
- let length = this._account.countBytes(message);
- // The message is short enough.
- if (length <= maxLength)
- continue;
-
- // Find the location of a space before the maximum length.
- let index = message.lastIndexOf(" ", maxLength);
-
- // Remove the current message and insert the two new ones. If no space was
- // found, cut the first message to the maximum length and start the second
- // message one character after that. If a space was found, exclude it.
- messages.splice(i, 1, message.substr(0, index == -1 ? maxLength : index),
- message.substr((index + 1) || maxLength));
+ if (!this._account.sendMessage("PRIVMSG", [this.name, message])) {
+ this.writeMessage(this._account._currentServerName,
+ _("error.sendMessageFailed"),
+ {error: true, system: true});
+ return;
}
- // Send each message and display it in the conversation.
- for (let message of messages) {
- if (!message.length)
- return;
+ // Since the server doesn't send us a message back, just assume the
+ // message was received and immediately show it.
+ this.writeMessage(this._account._nickname, message, {outgoing: true});
- if (!this._account.sendMessage("PRIVMSG", [this.name, message])) {
- this.writeMessage(this._account._currentServerName,
- _("error.sendMessageFailed"),
- {error: true, system: true});
- break;
- }
-
- // Since the server doesn't send us a message back, just assume the
- // message was received and immediately show it.
- this.writeMessage(this._account._nickname, message, {outgoing: true});
-
- this._pendingMessage = true;
- }
+ this._pendingMessage = true;
},
// IRC doesn't support typing notifications, but it does have a maximum
// message length.
sendTyping: function(aString) {
let longestLineLength =
Math.max.apply(null, aString.split("\n").map(this._account.countBytes,
this._account));
return this.getMaxMessageLength() - longestLineLength;
@@ -603,21 +611,21 @@ function ircConversation(aAccount, aName
this.requestBuddyInfo(aName);
}
ircConversation.prototype = {
__proto__: GenericConvIMPrototype,
get buddy() this._account.buddies.get(this.name),
// Overwrite the writeMessage function to apply CTCP formatting before
// display.
- writeMessage: function(aWho, aText, aProperties) {
- GenericConvIMPrototype.writeMessage.call(this, aWho,
- ctcpFormatToHTML(aText),
- aProperties);
- },
+ // writeMessage: function(aWho, aText, aProperties) {
+ // GenericConvIMPrototype.writeMessage.call(this, aWho,
+ // ctcpFormatToHTML(aText),
+ // aProperties);
+ // },
unInit: function() {
this.unInitIRCConversation();
GenericConvIMPrototype.unInit.call(this);
},
updateNick: function(aNewNick) {
this._name = aNewNick;
diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -28,16 +28,21 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
"@mozilla.org/image/tools;1",
"imgITools");
XPCOMUtils.defineLazyGetter(this, "_", function()
l10nHelper("chrome://chat/locale/xmpp.properties")
);
+XPCOMUtils.defineLazyGetter(this, "TXTToHTML", function() {
+ let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(Ci.mozITXTToHTMLConv);
+ return function(aTXT) cs.scanTXT(aTXT, cs.kEntities);
+});
+
/* This is an ordered list, used to determine chat buddy flags:
* index < member -> noFlags
* index = member -> voiced
* moderator -> halfOp
* admin -> op
* owner -> founder
*/
const kRoles = ["outcast", "visitor", "participant", "member", "moderator",
@@ -233,32 +238,41 @@ const XMPPConversationPrototype = {
_targetResource: "",
get to() {
let to = this.buddy.userName;
if (this._targetResource)
to += "/" + this._targetResource;
return to;
},
+ prepareForSending: function(aOm) {
+ let sendableMessages = [];
+ let count = aOm.sendableMessages.length;
+ for (let i = 0; i < count; ++i) {
+ let aMessage = aOm.sendableMessages.queryElementAt(i, Ci.nsISupportsString).toString();
+ sendableMessages.push(TXTToHTML(aMessage));
+ }
+ aOm.sendableMessages = sendableMessages;
+ if (aOm.nextDisplayableMessage)
+ this.prepareForSending(aOm.nextDisplayableMessage);
+ },
+
/* Called when the user enters a chat message */
- sendMsg: function (aMsg) {
+ sendMsg: function(aMsg) {
this._cancelTypingTimer();
let cs = this.shouldSendTypingNotifications ? "active" : null;
let s = Stanza.message(this.to, aMsg, cs);
this._account.sendStanza(s);
let who;
if (this._account._connection)
who = this._account._connection._jid.jid;
if (!who)
who = this._account.name;
let alias = this.account.alias || this.account.statusInfo.displayName;
- let msg = Cc["@mozilla.org/txttohtmlconv;1"]
- .getService(Ci.mozITXTToHTMLConv)
- .scanTXT(aMsg, Ci.mozITXTToHTMLConv.kEntities);
- this.writeMessage(who, msg, {outgoing: true, _alias: alias});
+ this.writeMessage(who, aMsg, {outgoing: true, _alias: alias});
delete this._typingState;
},
/* Called by the account when a messsage is received from the buddy */
incomingMessage: function(aMsg, aStanza, aDate) {
let from = aStanza.attributes["from"];
this._targetResource = this._account._parseJID(from).resource;
let flags = {};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment