221 строка
6.8 KiB
JavaScript
221 строка
6.8 KiB
JavaScript
/* 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/. */
|
|
|
|
Components.utils.import("resource:///modules/imServices.jsm");
|
|
|
|
this.EXPORTED_SYMBOLS = [
|
|
"smileImMarkup", // used to add smile:// img tags into IM markup.
|
|
"smileTextNode", // used to add smile:// img tags to the content of a textnode
|
|
"smileString", // used to add smile:// img tags into a string without parsing it as HTML. Be sure the string doesn't contain HTML tags.
|
|
"getSmileRealURI", // used to retrive the chrome URI for a smile:// URI
|
|
"getSmileyList" // used to display a list of smileys in the UI
|
|
];
|
|
|
|
var kEmoticonsThemePref = "messenger.options.emoticonsTheme";
|
|
var kThemeFile = "theme.js";
|
|
|
|
this.__defineGetter__("gTheme", function() {
|
|
delete this.gTheme;
|
|
gPrefObserver.init();
|
|
return this.gTheme = getTheme();
|
|
});
|
|
|
|
var gPrefObserver = {
|
|
init: function po_init() {
|
|
Services.prefs.addObserver(kEmoticonsThemePref, gPrefObserver, false);
|
|
},
|
|
|
|
observe: function so_observe(aObject, aTopic, aMsg) {
|
|
if (aTopic != "nsPref:changed" || aMsg != kEmoticonsThemePref)
|
|
throw "bad notification";
|
|
|
|
gTheme = getTheme();
|
|
}
|
|
};
|
|
|
|
function getSmileRealURI(aSmile)
|
|
{
|
|
aSmile = Components.classes["@mozilla.org/intl/texttosuburi;1"]
|
|
.getService(Components.interfaces.nsITextToSubURI)
|
|
.unEscapeURIForUI("UTF-8", aSmile);
|
|
if (aSmile in gTheme.iconsHash)
|
|
return gTheme.baseUri + gTheme.iconsHash[aSmile].filename;
|
|
|
|
throw "Invalid smile!";
|
|
}
|
|
|
|
function getSmileyList(aThemeName)
|
|
{
|
|
let theme = aThemeName == gTheme.name ? gTheme : getTheme(aThemeName);
|
|
if (!theme.json)
|
|
return null;
|
|
|
|
let addAbsoluteUrls = function(aSmiley) {
|
|
return {filename: aSmiley.filename,
|
|
src: theme.baseUri + aSmiley.filename,
|
|
textCodes: aSmiley.textCodes};
|
|
};
|
|
return theme.json.smileys.map(addAbsoluteUrls);
|
|
}
|
|
|
|
function getTheme(aName)
|
|
{
|
|
let name = aName || Services.prefs.getCharPref(kEmoticonsThemePref);
|
|
|
|
let theme = {
|
|
name: name,
|
|
iconsHash: null,
|
|
json: null,
|
|
regExp: null
|
|
};
|
|
|
|
if (name == "none")
|
|
return theme;
|
|
|
|
if (name == "default")
|
|
theme.baseUri = "chrome://instantbird-emoticons/skin/";
|
|
else
|
|
theme.baseUri = "chrome://" + theme.name + "/skin/";
|
|
try {
|
|
let channel = Services.io.newChannel2(theme.baseUri + kThemeFile, null, null, null,
|
|
Services.scriptSecurityManager.getSystemPrincipal(),
|
|
null,
|
|
Components.interfaces.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
Components.interfaces.nsIContentPolicy.TYPE_IMAGE);
|
|
let stream = channel.open();
|
|
let json = Components.classes["@mozilla.org/dom/json;1"]
|
|
.createInstance(Components.interfaces.nsIJSON);
|
|
theme.json = json.decodeFromStream(stream, stream.available());
|
|
stream.close();
|
|
theme.iconsHash = {};
|
|
for (let smiley of theme.json.smileys) {
|
|
for (let textCode of smiley.textCodes)
|
|
theme.iconsHash[textCode] = smiley;
|
|
}
|
|
} catch(e) {
|
|
Components.utils.reportError(e);
|
|
}
|
|
return theme;
|
|
}
|
|
|
|
function getRegexp()
|
|
{
|
|
if (gTheme.regExp) {
|
|
gTheme.regExp.lastIndex = 0;
|
|
return gTheme.regExp;
|
|
}
|
|
|
|
// return null if smileys are disabled
|
|
if (!gTheme.iconsHash)
|
|
return null;
|
|
|
|
if ("" in gTheme.iconsHash) {
|
|
Components.utils.reportError("Emoticon " +
|
|
gTheme.iconsHash[""].filename +
|
|
" matches the empty string!");
|
|
delete gTheme.iconsHash[""];
|
|
}
|
|
|
|
let emoticonList = [];
|
|
for (let emoticon in gTheme.iconsHash)
|
|
emoticonList.push(emoticon);
|
|
|
|
let exp = /[[\]{}()*+?.\\^$|]/g;
|
|
emoticonList = emoticonList.sort()
|
|
.reverse()
|
|
.map(x => x.replace(exp, "\\$&"));
|
|
|
|
if (!emoticonList.length) {
|
|
// the theme contains no valid emoticon, make sure we will return
|
|
// early next time
|
|
gTheme.iconsHash = null;
|
|
return null;
|
|
}
|
|
|
|
gTheme.regExp = new RegExp(emoticonList.join('|'), 'g');
|
|
return gTheme.regExp;
|
|
}
|
|
|
|
// unused. May be useful later to process a string instead of an HTML node
|
|
function smileString(aString)
|
|
{
|
|
const kSmileFormat = '<img class="ib-img-smile" src="smile://$&" alt="$&" title="$&"/>';
|
|
|
|
let exp = getRegexp();
|
|
return exp ? aString.replace(exp, kSmileFormat) : aString;
|
|
}
|
|
|
|
function smileTextNode(aNode)
|
|
{
|
|
/*
|
|
* Skip text nodes that contain the href in the child text node.
|
|
* We must check both the testNode.textContent and the aNode.data since they
|
|
* cover different cases:
|
|
* textContent: The URL is split over multiple nodes for some reason
|
|
* data: The URL is not the only content in the link, skip only the one node
|
|
* Check the class name to skip any autolinked nodes from mozTXTToHTMLConv.
|
|
*/
|
|
let testNode = aNode;
|
|
while ((testNode = testNode.parentNode)) {
|
|
if (testNode instanceof Components.interfaces.nsIDOMHTMLAnchorElement &&
|
|
(testNode.getAttribute("href") == testNode.textContent.trim() ||
|
|
testNode.getAttribute("href") == aNode.data.trim() ||
|
|
testNode.className.includes("moz-txt-link-")))
|
|
return 0;
|
|
}
|
|
|
|
let result = 0;
|
|
let exp = getRegexp();
|
|
if (!exp)
|
|
return result;
|
|
|
|
let match;
|
|
while ((match = exp.exec(aNode.data))) {
|
|
let smileNode = aNode.splitText(match.index);
|
|
aNode = smileNode.splitText(exp.lastIndex - match.index);
|
|
// at this point, smileNode is a text node with only the text
|
|
// of the smiley and aNode is a text node with the text after
|
|
// the smiley. The text in aNode hasn't been processed yet.
|
|
let smile = smileNode.data;
|
|
let elt = aNode.ownerDocument.createElement("img");
|
|
elt.setAttribute("src", "smile://" + smile);
|
|
elt.setAttribute("title", smile);
|
|
elt.setAttribute("alt", smile);
|
|
elt.setAttribute("class", "ib-img-smile");
|
|
smileNode.parentNode.replaceChild(elt, smileNode);
|
|
result += 2;
|
|
exp.lastIndex = 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function smileNode(aNode)
|
|
{
|
|
for (let i = 0; i < aNode.childNodes.length; ++i) {
|
|
let node = aNode.childNodes[i];
|
|
if (node instanceof Components.interfaces.nsIDOMHTMLElement) {
|
|
// we are on a tag, recurse to process its children
|
|
smileNode(node);
|
|
} else if (node instanceof Components.interfaces.nsIDOMText) {
|
|
// we are on a text node, process it
|
|
smileTextNode(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
function smileImMarkup(aDocument, aText)
|
|
{
|
|
if (!aDocument)
|
|
throw "providing an HTML document is required";
|
|
|
|
// return early if smileys are disabled
|
|
if (!gTheme.iconsHash)
|
|
return aText;
|
|
|
|
let div = aDocument.createElement("div");
|
|
div.innerHTML = aText;
|
|
smileNode(div);
|
|
return div.innerHTML;
|
|
}
|