193246 /list doesn't work on RFC2812 compliant IRC servers 114744 File->Close vs. View-Close 80125 Logging facility in Chatzilla. 198538 Chat views tab titles are lowcase 200188 PgDn/PgUp scroll left and right Chatzilla panes 199201 sound settings blocks stalked messages if WAV sound file ... 114104 messaging from private chat tab does not show the addressee 201324 Userlist not always kept sorted as requested 202644 Column sort behavior should parallel mailnews 184228 Spelling error in irc.js: "function net_conenct()". 206915 /join reports joining new channel in current window 209524 stalk word list cannot have length one 198823 Should be able to set Default Character Coding in Prefere... 200097 lowcase ``<nickname> has changed the topic to...'' 205792 important text notification does not properly differentia... 86526 chatzilla stores default prefs in prefs.js Thanks to ssieb, kazhik ans Silver for the patches. git-svn-id: svn://10.0.0.236/trunk@144030 18797224-902f-48f8-a5cc-f745e15eee43
3250 lines
98 KiB
JavaScript
3250 lines
98 KiB
JavaScript
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is ChatZilla
|
|
*
|
|
* The Initial Developer of the Original Code is New Dimensions Consulting,
|
|
* Inc. Portions created by New Dimensions Consulting, Inc. are
|
|
* Copyright (C) 1999 New Dimenstions Consulting, Inc. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Robert Ginda, rginda@netscape.com, original author
|
|
* Chiaki Koufugata chiaki@mozilla.gr.jp UI i18n
|
|
* Samuel Sieb, samuel@sieb.net, MIRC color codes, munger menu, and various
|
|
*/
|
|
|
|
if (DEBUG)
|
|
{
|
|
dd = function (m) {
|
|
dump ("-*- chatzilla: " + m + "\n");
|
|
// messages that start with ** are warnings, so we'll
|
|
// show a stack trace here too.
|
|
if (m.search(/^\*\*/) == 0)
|
|
dump(getStackTrace() + "\n");
|
|
};
|
|
}
|
|
else
|
|
{
|
|
dd = function (){};
|
|
}
|
|
|
|
var client = new Object();
|
|
|
|
const MSG_CSP = getMsg ("commaSpace", " ");
|
|
const MSG_NONE = getMsg ("none");
|
|
const MSG_UNKNOWN = getMsg ("unknown");
|
|
|
|
client.defaultNick = getMsg("defaultNick");
|
|
|
|
client.version = "0.8.34";
|
|
|
|
client.TYPE = "IRCClient";
|
|
client.COMMAND_CHAR = "/";
|
|
client.STEP_TIMEOUT = 500;
|
|
client.MAX_MESSAGES = 200;
|
|
client.MAX_HISTORY = 50;
|
|
/* longest nick to show in display before forcing the message to a block level
|
|
* element */
|
|
client.MAX_NICK_DISPLAY = 14;
|
|
/* longest word to show in display before abbreviating */
|
|
client.MAX_WORD_DISPLAY = 20;
|
|
client.PRINT_DIRECTION = 1; /*1 => new messages at bottom, -1 => at top */
|
|
client.ADDRESSED_NICK_SEP = ":";
|
|
|
|
client.MAX_MSG_PER_ROW = 3; /* default number of messages to collapse into a
|
|
* single row, max. */
|
|
client.INITIAL_COLSPAN = 5; /* MAX_MSG_PER_ROW cannot grow to greater than
|
|
* one half INITIAL_COLSPAN + 1. */
|
|
client.COLLAPSE_ROWS = false;
|
|
client.NOTIFY_TIMEOUT = 5 * 60 * 1000; /* update notify list every 5 minutes */
|
|
|
|
client.SLOPPY_NETWORKS = true; /* true if msgs from a network can be displayed
|
|
* on the current object if it is related to
|
|
* the network (ie, /whois results will appear
|
|
* on the channel you're viewing, if that channel
|
|
* is on the network that the results came from)
|
|
*/
|
|
client.DOUBLETAB_TIME = 500;
|
|
client.IMAGEDIR = "chrome://chatzilla/skin/images/";
|
|
client.HIDE_CODES = true; /* true if you'd prefer to show numeric response
|
|
* codes as some default value (ie, "===") */
|
|
/* true if the browser widget shouldn't be allowed to take focus. windows, and
|
|
* probably the mac, need to be able to give focus to the browser widget for
|
|
* copy to work properly. */
|
|
client.NO_BROWSER_FOCUS = (navigator.platform.search(/mac|win/i) == -1);
|
|
client.DEFAULT_RESPONSE_CODE = "===";
|
|
|
|
|
|
/* XXX maybe move this into css */
|
|
client.responseCodeMap = new Object();
|
|
client.responseCodeMap["HELLO"] = getMsg("responseCodeMapHello");
|
|
client.responseCodeMap["HELP"] = getMsg("responseCodeMapHelp");
|
|
client.responseCodeMap["USAGE"] = getMsg("responseCodeMapUsage");
|
|
client.responseCodeMap["ERROR"] = getMsg("responseCodeMapError");
|
|
client.responseCodeMap["WARNING"] = getMsg("responseCodeMapWarning");
|
|
client.responseCodeMap["INFO"] = getMsg("responseCodeMapInfo");
|
|
client.responseCodeMap["EVAL-IN"] = getMsg("responseCodeMapEvalIn");
|
|
client.responseCodeMap["EVAL-OUT"] = getMsg("responseCodeMapEvalOut");
|
|
client.responseCodeMap["JOIN"] = "-->|";
|
|
client.responseCodeMap["PART"] = "<--|";
|
|
client.responseCodeMap["QUIT"] = "|<--";
|
|
client.responseCodeMap["NICK"] = "=-=";
|
|
client.responseCodeMap["TOPIC"] = "=-=";
|
|
client.responseCodeMap["KICK"] = "=-=";
|
|
client.responseCodeMap["MODE"] = "=-=";
|
|
client.responseCodeMap["END_STATUS"] = "---";
|
|
client.responseCodeMap["315"] = "---"; /* end of WHO */
|
|
client.responseCodeMap["318"] = "---"; /* end of WHOIS */
|
|
client.responseCodeMap["366"] = "---"; /* end of NAMES */
|
|
client.responseCodeMap["376"] = "---"; /* end of MOTD */
|
|
|
|
client.name = getMsg("clientname");
|
|
client.viewsArray = new Array();
|
|
client.activityList = new Object();
|
|
client.uiState = new Object(); /* state of ui elements (visible/collapsed) */
|
|
client.inputHistory = new Array();
|
|
client.lastHistoryReferenced = -1;
|
|
client.incompleteLine = "";
|
|
client.lastTabUp = new Date();
|
|
client.stalkingVictims = new Array();
|
|
|
|
CIRCNetwork.prototype.INITIAL_NICK = client.defaultNick;
|
|
CIRCNetwork.prototype.INITIAL_NAME = "chatzilla";
|
|
CIRCNetwork.prototype.INITIAL_DESC = getMsg("circnetworkInitialDesc");
|
|
CIRCNetwork.prototype.INITIAL_CHANNEL = "";
|
|
CIRCNetwork.prototype.MAX_MESSAGES = 100;
|
|
CIRCNetwork.prototype.IGNORE_MOTD = false;
|
|
|
|
CIRCServer.prototype.READ_TIMEOUT = 0;
|
|
CIRCServer.prototype.VERSION_RPLY = getMsg("circserverVersionRply",
|
|
[client.version,
|
|
navigator.userAgent]);
|
|
CIRCUser.prototype.MAX_MESSAGES = 200;
|
|
|
|
CIRCChannel.prototype.MAX_MESSAGES = 300;
|
|
|
|
CIRCChanUser.prototype.MAX_MESSAGES = 200;
|
|
|
|
window.onresize =
|
|
function ()
|
|
{
|
|
for (var i = 0; i < client.deck.childNodes.length; i++)
|
|
scrollDown(client.deck.childNodes[i], true);
|
|
}
|
|
|
|
function toUnicode (msg, charset)
|
|
{
|
|
if (!("ucConverter" in client))
|
|
return msg;
|
|
|
|
if (typeof charset == "undefined")
|
|
client.ucConverter.charset = client.CHARSET;
|
|
else
|
|
client.ucConverter.charset = charset;
|
|
|
|
try
|
|
{
|
|
return client.ucConverter.ConvertToUnicode(msg);
|
|
}
|
|
catch (ex)
|
|
{
|
|
dd ("caught exception " + ex + " converting " + msg + " to charset " +
|
|
charset);
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
function fromUnicode (msg, charset)
|
|
{
|
|
if (!("ucConverter" in client))
|
|
return msg;
|
|
|
|
if (typeof charset == "undefined")
|
|
client.ucConverter.charset = client.CHARSET;
|
|
else
|
|
client.ucConverter.charset = charset;
|
|
|
|
return client.ucConverter.ConvertFromUnicode(msg);
|
|
}
|
|
|
|
function checkCharset (charset)
|
|
{
|
|
var ex;
|
|
var converter;
|
|
|
|
try
|
|
{
|
|
|
|
const UC_CTRID = "@mozilla.org/intl/scriptableunicodeconverter";
|
|
const nsIUnicodeConverter =
|
|
Components.interfaces.nsIScriptableUnicodeConverter;
|
|
converter =
|
|
Components.classes[UC_CTRID].getService(nsIUnicodeConverter);
|
|
|
|
converter.charset = charset;
|
|
|
|
}
|
|
catch (ex)
|
|
{
|
|
dd ("Caught exception setting charset to " + charset + "\n" + ex);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
function setCharset (charset)
|
|
{
|
|
client.CHARSET = charset;
|
|
|
|
if (!charset)
|
|
{
|
|
delete client.ucConverter;
|
|
client.eventPump.removeHookByName("uc-hook");
|
|
return true;
|
|
}
|
|
|
|
var ex;
|
|
|
|
try
|
|
{
|
|
|
|
if (!("ucConverter" in client))
|
|
{
|
|
const UC_CTRID = "@mozilla.org/intl/scriptableunicodeconverter";
|
|
const nsIUnicodeConverter =
|
|
Components.interfaces.nsIScriptableUnicodeConverter;
|
|
client.ucConverter =
|
|
Components.classes[UC_CTRID].getService(nsIUnicodeConverter);
|
|
}
|
|
|
|
client.ucConverter.charset = charset;
|
|
|
|
}
|
|
catch (ex)
|
|
{
|
|
dd ("Caught exception setting charset to " + charset + "\n" + ex);
|
|
delete client.ucConverter;
|
|
client.CHARSET = "";
|
|
client.eventPump.removeHookByName("uc-hook");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function initStatic()
|
|
{
|
|
var obj;
|
|
|
|
const nsISound = Components.interfaces.nsISound;
|
|
client.sound =
|
|
Components.classes["@mozilla.org/sound;1"].createInstance(nsISound);
|
|
|
|
if (client.CHARSET)
|
|
setCharset(client.CHARSET);
|
|
|
|
multilineInputMode(client.MULTILINE);
|
|
|
|
var ary = navigator.userAgent.match (/;\s*([^;\s]+\s*)\).*\/(\d+)/);
|
|
if (ary)
|
|
client.userAgent = "ChatZilla " + client.version + " [Mozilla " +
|
|
ary[1] + "/" + ary[2] + "]";
|
|
else
|
|
client.userAgent = "ChatZilla " + client.version + " [" +
|
|
navigator.userAgent + "]";
|
|
|
|
obj = document.getElementById("input");
|
|
obj.addEventListener("keypress", onInputKeyPress, false);
|
|
obj = document.getElementById("multiline-input");
|
|
obj.addEventListener("keypress", onMultilineInputKeyPress, false);
|
|
obj = document.getElementById("channel-topicedit");
|
|
obj.addEventListener("keypress", onTopicKeyPress, false);
|
|
obj.active = false;
|
|
|
|
window.onkeypress = onWindowKeyPress;
|
|
|
|
setMenuCheck ("menu-dmessages",
|
|
client.eventPump.getHook ("event-tracer").enabled);
|
|
setMenuCheck ("menu-munger-global", !client.munger.enabled);
|
|
setMenuCheck ("menu-colors", client.enableColors);
|
|
|
|
setupMungerMenu(client.munger);
|
|
|
|
client.uiState["tabstrip"] =
|
|
setMenuCheck ("menu-view-tabstrip", isVisible("view-tabs"));
|
|
client.uiState["info"] =
|
|
setMenuCheck ("menu-view-info", isVisible("user-list-box"));
|
|
client.uiState["header"] =
|
|
setMenuCheck ("menu-view-header", isVisible("header-bar-tbox"));
|
|
client.uiState["status"] =
|
|
setMenuCheck ("menu-view-status", isVisible("status-bar"));
|
|
|
|
client.statusBar = new Object();
|
|
|
|
client.statusBar["header-url"] =
|
|
document.getElementById ("header-url");
|
|
client.statusBar["server-nick"] =
|
|
document.getElementById ("server-nick");
|
|
client.statusBar["channel-mode"] =
|
|
document.getElementById ("channel-mode");
|
|
client.statusBar["channel-users"] =
|
|
document.getElementById ("channel-users");
|
|
client.statusBar["channel-topic"] =
|
|
document.getElementById ("channel-topic");
|
|
client.statusBar["channel-topicedit"] =
|
|
document.getElementById ("channel-topicedit");
|
|
|
|
client.statusElement = document.getElementById ("status-text");
|
|
client.defaultStatus = getMsg ("defaultStatus");
|
|
|
|
client.display (getMsg("welcome"), "HELLO");
|
|
setCurrentObject (client);
|
|
|
|
var m = document.getElementById ("menu-settings-autosave");
|
|
m.setAttribute ("checked", String(client.SAVE_SETTINGS));
|
|
|
|
setInterval ("onNotifyTimeout()", client.NOTIFY_TIMEOUT);
|
|
|
|
if (getBoolPref("logging", false, client.localPrefs))
|
|
client.startLogging(client);
|
|
}
|
|
|
|
function processStartupURLs()
|
|
{
|
|
var wentSomewhere = false;
|
|
|
|
if ("arguments" in window &&
|
|
0 in window.arguments && typeof window.arguments[0] == "object" &&
|
|
"url" in window.arguments[0])
|
|
{
|
|
var url = window.arguments[0].url;
|
|
if (url.search(/^irc:\/?\/?$/i) == -1)
|
|
{
|
|
/* if the url is not irc: irc:/ or irc://, then go to it. */
|
|
gotoIRCURL (url);
|
|
wentSomewhere = true;
|
|
}
|
|
}
|
|
|
|
if (!wentSomewhere)
|
|
{
|
|
/* if we had nowhere else to go, connect to any default urls */
|
|
var ary = client.INITIAL_URLS.split(/\s*;\s*/);
|
|
for (var i in ary)
|
|
{
|
|
if (ary[i] && ary[i] != "irc://")
|
|
gotoIRCURL (ary[i]);
|
|
}
|
|
}
|
|
|
|
if (client.viewsArray.length > 1 && !isStartupURL("irc://"))
|
|
{
|
|
var tb = getTabForObject (client);
|
|
deleteTab(tb);
|
|
client.deck.removeChild(client.frame);
|
|
client.deck.selectedIndex = 0;
|
|
}
|
|
}
|
|
|
|
function setStatus (str)
|
|
{
|
|
client.statusElement.setAttribute ("label", str);
|
|
return str;
|
|
}
|
|
|
|
client.__defineSetter__ ("status", setStatus);
|
|
|
|
function getStatus ()
|
|
{
|
|
return client.statusElement.getAttribute ("label");
|
|
}
|
|
|
|
client.__defineGetter__ ("status", getStatus);
|
|
|
|
function setMenuCheck (id, state)
|
|
{
|
|
var m = document.getElementById(id);
|
|
m.setAttribute ("checked", String(Boolean(state)));
|
|
return state;
|
|
}
|
|
|
|
function isVisible (id)
|
|
{
|
|
var e = document.getElementById(id);
|
|
|
|
if (!e)
|
|
{
|
|
dd ("** Bogus id ``" + id + "'' passed to isVisible() **");
|
|
return false;
|
|
}
|
|
|
|
return (e.getAttribute ("collapsed") != "true");
|
|
}
|
|
|
|
function initHost(obj)
|
|
{
|
|
obj.commands = new CCommandManager();
|
|
addCommands (obj.commands);
|
|
|
|
obj.networks = new Object();
|
|
obj.eventPump = new CEventPump (200);
|
|
|
|
obj.defaultCompletion = client.COMMAND_CHAR + "help ";
|
|
|
|
obj.networks["efnet"] =
|
|
new CIRCNetwork ("efnet",
|
|
[{name: "irc.mcs.net", port: 6667},
|
|
{name: "irc.prison.net", port: 6667},
|
|
{name: "irc.freei.net", port: 6667},
|
|
{name: "irc.magic.ca", port: 6667}],
|
|
obj.eventPump);
|
|
obj.networks["moznet"] =
|
|
new CIRCNetwork ("moznet", [{name: "irc.mozilla.org", port: 6667}],
|
|
obj.eventPump);
|
|
obj.networks["hybridnet"] =
|
|
new CIRCNetwork ("hybridnet", [{name: "irc.ssc.net", port: 6667}],
|
|
obj.eventPump);
|
|
obj.networks["slashnet"] =
|
|
new CIRCNetwork ("slashnet", [{name: "irc.slashnet.org", port:6667}],
|
|
obj.eventPump);
|
|
obj.networks["dalnet"] =
|
|
new CIRCNetwork ("dalnet", [{name: "irc.dal.net", port:6667}],
|
|
obj.eventPump);
|
|
obj.networks["undernet"] =
|
|
new CIRCNetwork ("undernet", [{name: "irc.undernet.org", port:6667}],
|
|
obj.eventPump);
|
|
obj.networks["webbnet"] =
|
|
new CIRCNetwork ("webbnet", [{name: "irc.webbnet.org", port:6667}],
|
|
obj.eventPump);
|
|
obj.networks["quakenet"] =
|
|
new CIRCNetwork ("quakenet", [{name: "irc.quakenet.org", port:6667}],
|
|
obj.eventPump);
|
|
obj.networks["freenode"] =
|
|
new CIRCNetwork ("freenode",
|
|
[{name: "irc.freenode.net", port:6667}],
|
|
obj.eventPump);
|
|
|
|
obj.primNet = obj.networks["efnet"];
|
|
|
|
if (DEBUG)
|
|
{
|
|
/* hook all events EXCEPT server.poll and *.event-end types
|
|
* (the 4th param inverts the match) */
|
|
obj.debugHook =
|
|
obj.eventPump.addHook ([{type: "poll", set: /^(server|dcc-chat)$/},
|
|
{type: "event-end"}], event_tracer,
|
|
"event-tracer", true /* negate */,
|
|
false /* disable */);
|
|
}
|
|
|
|
obj.linkRE =
|
|
/((\w[\w-]+):[^<>\[\]()\'\"\s\u201d]+|www(\.[^.<>\[\]()\'\"\s\u201d]+){2,})/;
|
|
|
|
obj.munger = new CMunger();
|
|
obj.munger.enabled = true;
|
|
obj.munger.addRule ("quote", /(``|'')/, insertQuote);
|
|
obj.munger.addRule ("bold", /(?:\s|^)(\*[^*()]*\*)(?:[\s.,]|$)/,
|
|
"chatzilla-bold");
|
|
obj.munger.addRule ("underline", /(?:\s|^)(\_[^_()]*\_)(?:[\s.,]|$)/,
|
|
"chatzilla-underline");
|
|
obj.munger.addRule ("italic", /(?:\s|^)(\/[^\/()]*\/)(?:[\s.,]|$)/,
|
|
"chatzilla-italic");
|
|
/* allow () chars inside |code()| blocks */
|
|
obj.munger.addRule ("teletype", /(?:\s|^)(\|[^|]*\|)(?:[\s.,]|$)/,
|
|
"chatzilla-teletype");
|
|
obj.munger.addRule (".mirc-colors", /(\x03((\d{1,2})(,\d{1,2}|)|))/,
|
|
mircChangeColor);
|
|
obj.munger.addRule (".mirc-bold", /(\x02)/, mircToggleBold);
|
|
obj.munger.addRule (".mirc-underline", /(\x1f)/, mircToggleUnder);
|
|
obj.munger.addRule (".mirc-color-reset", /(\x0f)/, mircResetColor);
|
|
obj.munger.addRule (".mirc-reverse", /(\x16)/, mircReverseColor);
|
|
obj.munger.addRule ("ctrl-char", /([\x01-\x1f])/, showCtrlChar);
|
|
obj.munger.addRule ("link", obj.linkRE, insertLink);
|
|
obj.munger.addRule ("mailto",
|
|
/(?:\s|\W|^)((mailto:)?[^<>\[\]()\'\"\s\u201d]+@[^.<>\[\]()\'\"\s\u201d]+\.[^<>\[\]()\'\"\s\u201d]+)/i,
|
|
insertMailToLink);
|
|
obj.munger.addRule ("bugzilla-link", /(?:\s|\W|^)(bug\s+#?\d{3,6})/i,
|
|
insertBugzillaLink);
|
|
obj.munger.addRule ("channel-link",
|
|
/(?:\s|\W|^)[@+]?(#[^<>\[\](){}\"\s\u201d]*[^:,.<>\[\](){}\'\"\s\u201d])/i,
|
|
insertChannelLink);
|
|
|
|
obj.munger.addRule ("face",
|
|
/((^|\s)[\<\>]?[\;\=\:]\~?[\-\^\v]?[\)\|\(pP\<\>oO0\[\]\/\\](\s|$))/,
|
|
insertSmiley);
|
|
obj.munger.addRule ("ear", /(?:\s|^)(\(\*)(?:\s|$)/, insertEar, false);
|
|
obj.munger.addRule ("rheet", /(?:\s|\W|^)(rhee+t\!*)(?:\s|$)/i, insertRheet);
|
|
obj.munger.addRule ("word-hyphenator",
|
|
new RegExp ("(\\S{" + client.MAX_WORD_DISPLAY + ",})"),
|
|
insertHyphenatedWord);
|
|
|
|
obj.rdf = new RDFHelper();
|
|
|
|
obj.rdf.initTree("user-list");
|
|
obj.rdf.setTreeRoot("user-list", obj.rdf.resNullChan);
|
|
|
|
obj.localPrefs =
|
|
client.prefService.getBranch(obj.prefBranch.root +
|
|
"viewList.client.");
|
|
}
|
|
|
|
function insertLink (matchText, containerTag)
|
|
{
|
|
var href;
|
|
var linkText;
|
|
|
|
var trailing;
|
|
ary = matchText.match(/([.,?]+)$/);
|
|
if (ary)
|
|
{
|
|
linkText = RegExp.leftContext;
|
|
trailing = ary[1];
|
|
}
|
|
else
|
|
{
|
|
linkText = matchText;
|
|
}
|
|
|
|
var ary = linkText.match (/^(\w[\w-]+):/);
|
|
if (ary)
|
|
{
|
|
if (!("schemes" in client))
|
|
{
|
|
var pfx = "@mozilla.org/network/protocol;1?name=";
|
|
var len = pfx.length;
|
|
|
|
client.schemes = new Object();
|
|
for (var c in Components.classes)
|
|
{
|
|
if (c.indexOf(pfx) == 0)
|
|
client.schemes[c.substr(len)] = true;
|
|
}
|
|
}
|
|
|
|
if (!(ary[1] in client.schemes))
|
|
{
|
|
insertHyphenatedWord(matchText, containerTag);
|
|
return;
|
|
}
|
|
|
|
href = linkText;
|
|
}
|
|
else
|
|
{
|
|
href = "http://" + linkText;
|
|
}
|
|
|
|
var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:a");
|
|
anchor.setAttribute ("href", href);
|
|
anchor.setAttribute ("class", "chatzilla-link");
|
|
anchor.setAttribute ("target", "_content");
|
|
insertHyphenatedWord (linkText, anchor);
|
|
containerTag.appendChild (anchor);
|
|
if (trailing)
|
|
insertHyphenatedWord (trailing, containerTag);
|
|
|
|
}
|
|
|
|
function insertMailToLink (matchText, containerTag)
|
|
{
|
|
|
|
var href;
|
|
|
|
if (matchText.indexOf ("mailto:") != 0)
|
|
href = "mailto:" + matchText;
|
|
else
|
|
href = matchText;
|
|
|
|
var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:a");
|
|
anchor.setAttribute ("href", href);
|
|
anchor.setAttribute ("class", "chatzilla-link");
|
|
//anchor.setAttribute ("target", "_content");
|
|
insertHyphenatedWord (matchText, anchor);
|
|
containerTag.appendChild (anchor);
|
|
|
|
}
|
|
|
|
function insertChannelLink (matchText, containerTag, eventData)
|
|
{
|
|
var encodedMatchText = fromUnicode(matchText + " ");
|
|
/* bug 114923 */
|
|
encodedMatchText = encodedMatchText.substr(0, encodedMatchText.length - 1);
|
|
|
|
if (!("network" in eventData) ||
|
|
matchText.search
|
|
(/^#(include|error|define|if|ifdef|else|elsif|endif|\d+)$/i) != -1)
|
|
|
|
{
|
|
containerTag.appendChild (document.createTextNode(matchText));
|
|
return;
|
|
}
|
|
|
|
var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:a");
|
|
anchor.setAttribute ("href", eventData.network.getURL() +
|
|
escape (encodedMatchText));
|
|
anchor.setAttribute ("class", "chatzilla-link");
|
|
//anchor.setAttribute ("target", "_content");
|
|
insertHyphenatedWord (matchText, anchor);
|
|
containerTag.appendChild (anchor);
|
|
|
|
}
|
|
|
|
function insertBugzillaLink (matchText, containerTag)
|
|
{
|
|
|
|
var number = matchText.match (/(\d+)/)[1];
|
|
|
|
var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:a");
|
|
anchor.setAttribute ("href", client.BUG_URL.replace("%s", number));
|
|
anchor.setAttribute ("class", "chatzilla-link");
|
|
anchor.setAttribute ("target", "_content");
|
|
insertHyphenatedWord (matchText, anchor);
|
|
containerTag.appendChild (anchor);
|
|
|
|
}
|
|
|
|
function insertRheet (matchText, containerTag)
|
|
{
|
|
|
|
var anchor = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:a");
|
|
anchor.setAttribute ("href",
|
|
"ftp://ftp.mozilla.org/pub/mozilla/libraries/bonus-tracks/rheet.wav");
|
|
anchor.setAttribute ("class", "chatzilla-rheet chatzilla-link");
|
|
//anchor.setAttribute ("target", "_content");
|
|
insertHyphenatedWord (matchText, anchor);
|
|
containerTag.appendChild (anchor);
|
|
}
|
|
|
|
function insertQuote (matchText, containerTag)
|
|
{
|
|
if (matchText == "``")
|
|
containerTag.appendChild(document.createTextNode("\u201c"));
|
|
else
|
|
containerTag.appendChild(document.createTextNode("\u201d"));
|
|
}
|
|
|
|
function insertEar (matchText, containerTag)
|
|
{
|
|
if (client.smileyText)
|
|
containerTag.appendChild (document.createTextNode (matchText));
|
|
|
|
var img = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:img");
|
|
img.setAttribute ("src", client.IMAGEDIR + "face-ear.gif");
|
|
img.setAttribute ("title", matchText);
|
|
containerTag.appendChild (img);
|
|
|
|
}
|
|
|
|
function insertSmiley (emoticon, containerTag)
|
|
{
|
|
var type = "error";
|
|
|
|
if (emoticon.search (/\;[-^v]?[\)>\]]/) != -1)
|
|
type = "face-wink";
|
|
else if (emoticon.search (/[=:;][-^v]?[\)>\]]/) != -1)
|
|
type = "face-smile";
|
|
else if (emoticon.search (/[=:;][-^v]?[\/\\]/) != -1)
|
|
type = "face-screw";
|
|
else if (emoticon.search (/[=:;]\~[-^v]?\(/) != -1)
|
|
type = "face-cry";
|
|
else if (emoticon.search (/[=:;][-^v]?[\(<\[]/) != -1)
|
|
type = "face-frown";
|
|
else if (emoticon.search (/\<?[=:;][-^v]?[0oO]/) != -1)
|
|
type = "face-surprise";
|
|
else if (emoticon.search (/[=:;][-^v]?[pP]/) != -1)
|
|
type = "face-tongue";
|
|
else if (emoticon.search (/\>?[=:;][\-\^\v]?[\(\|]/) != -1)
|
|
type = "face-angry";
|
|
|
|
var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:span");
|
|
|
|
/* create a span to hold the emoticon text */
|
|
span.setAttribute ("class", "chatzilla-emote-txt");
|
|
span.setAttribute ("type", type);
|
|
span.appendChild (document.createTextNode (emoticon));
|
|
containerTag.appendChild (span);
|
|
|
|
/* create an empty span after the text. this span will have an image added
|
|
* after it with a chatzilla-emote:after css rule. using
|
|
* chatzilla-emote-txt:after is not good enough because it does not allow us
|
|
* to turn off the emoticon text, but keep the image. ie.
|
|
* chatzilla-emote-txt { display: none; } turns off chatzilla-emote-txt:after
|
|
* as well.*/
|
|
span = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:span");
|
|
span.setAttribute ("class", "chatzilla-emote");
|
|
span.setAttribute ("type", type);
|
|
span.setAttribute ("title", emoticon);
|
|
containerTag.appendChild (span);
|
|
|
|
}
|
|
|
|
function mircChangeColor (colorInfo, containerTag, data)
|
|
{
|
|
if (!client.enableColors)
|
|
return;
|
|
|
|
var ary = colorInfo.match (/.(\d{1,2}|)(,(\d{1,2})|)/);
|
|
|
|
var fgColor = ary[1];
|
|
if (fgColor > 16)
|
|
fgColor &= 16;
|
|
switch (fgColor.length)
|
|
{
|
|
case 0:
|
|
delete data.currFgColor;
|
|
delete data.currBgColor;
|
|
return;
|
|
case 1:
|
|
data.currFgColor = "0" + fgColor;
|
|
break;
|
|
case 2:
|
|
data.currFgColor = fgColor;
|
|
break;
|
|
}
|
|
if (fgColor == 1)
|
|
delete data.currFgColor;
|
|
if (ary.length >= 4)
|
|
{
|
|
var bgColor = ary[3];
|
|
if (bgColor > 16)
|
|
bgColor &= 16;
|
|
if (bgColor.length == 1)
|
|
data.currBgColor = "0" + bgColor;
|
|
else
|
|
data.currBgColor = bgColor;
|
|
if (bgColor == 0)
|
|
delete data.currBgColor;
|
|
}
|
|
data.hasColorInfo = true;
|
|
}
|
|
|
|
function mircToggleBold (colorInfo, containerTag, data)
|
|
{
|
|
if (!client.enableColors)
|
|
return;
|
|
|
|
if ("isBold" in data)
|
|
delete data.isBold;
|
|
else
|
|
data.isBold = true;
|
|
data.hasColorInfo = true;
|
|
}
|
|
|
|
function mircToggleUnder (colorInfo, containerTag, data)
|
|
{
|
|
if (!client.enableColors)
|
|
return;
|
|
|
|
if ("isUnderline" in data)
|
|
delete data.isUnderline;
|
|
else
|
|
data.isUnderline = true;
|
|
data.hasColorInfo = true;
|
|
}
|
|
|
|
function mircResetColor (text, containerTag, data)
|
|
{
|
|
if (!client.enableColors || !("hasColorInfo" in data))
|
|
return;
|
|
|
|
delete data.currFgColor;
|
|
delete data.currBgColor;
|
|
delete data.isBold;
|
|
delete data.isUnder;
|
|
delete data.hasColorInfo;
|
|
}
|
|
|
|
function mircReverseColor (text, containerTag, data)
|
|
{
|
|
if (!client.enableColors)
|
|
return;
|
|
|
|
var tempColor = ("currFgColor" in data ? data.currFgColor : "01");
|
|
|
|
if ("currBgColor" in data)
|
|
data.currFgColor = data.currBgColor;
|
|
else
|
|
data.currFgColor = "00";
|
|
data.currBgColor = tempColor;
|
|
data.hasColorInfo = true;
|
|
}
|
|
|
|
function showCtrlChar(c, containerTag)
|
|
{
|
|
var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:span");
|
|
span.setAttribute ("class", "chatzilla-control-char");
|
|
if (c == "\t")
|
|
{
|
|
containerTag.appendChild(document.createTextNode(c));
|
|
return;
|
|
}
|
|
|
|
var ctrlStr = c.charCodeAt(0).toString(16);
|
|
if (ctrlStr.length < 2)
|
|
ctrlStr = "0" + ctrlStr;
|
|
span.appendChild (document.createTextNode ("0x" + ctrlStr));
|
|
containerTag.appendChild (span);
|
|
}
|
|
|
|
function insertHyphenatedWord (longWord, containerTag)
|
|
{
|
|
var wordParts = splitLongWord (longWord, client.MAX_WORD_DISPLAY);
|
|
for (var i = 0; i < wordParts.length; ++i)
|
|
{
|
|
containerTag.appendChild (document.createTextNode (wordParts[i]));
|
|
if (i != wordParts.length)
|
|
{
|
|
var wbr = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:wbr");
|
|
containerTag.appendChild (wbr);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateStalkExpression(network)
|
|
{
|
|
function escapeChar(ch)
|
|
{
|
|
return "\\" + ch;
|
|
};
|
|
|
|
list = client.stalkingVictims;
|
|
ary = new Array();
|
|
|
|
ary.push(network.primServ.me.nick.replace(/[^\w\d]/g, escapeChar));
|
|
|
|
for (var i = 0; i < list.length; ++i)
|
|
ary.push(list[i].replace(/[^\w\d]/g, escapeChar));
|
|
|
|
var re;
|
|
if (client.STALK_WHOLE_WORDS)
|
|
re = "(^|[\\W\\s])((" + ary.join(")|(") + "))([\\W\\s]|$)";
|
|
else
|
|
re = "(" + ary.join(")|(") + ")";
|
|
|
|
network.stalkExpression = new RegExp(re, "i");
|
|
}
|
|
|
|
function msgIsImportant (msg, sourceNick, network)
|
|
{
|
|
re = network.stalkExpression;
|
|
if (msg.search(re) != -1 || sourceNick && sourceNick.search(re) == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
function isStartupURL(url)
|
|
{
|
|
var ary = client.INITIAL_URLS.split(/\s*;\s*/);
|
|
return arrayContains(ary, url);
|
|
}
|
|
|
|
function cycleView (amount)
|
|
{
|
|
var len = client.viewsArray.length;
|
|
if (len <= 1)
|
|
return;
|
|
|
|
var tb = getTabForObject (client.currentObject);
|
|
if (!tb)
|
|
return;
|
|
|
|
var vk = Number(tb.getAttribute("viewKey"));
|
|
var destKey = (vk + amount) % len; /* wrap around */
|
|
if (destKey < 0)
|
|
destKey += len;
|
|
|
|
setCurrentObject (client.viewsArray[destKey].source);
|
|
}
|
|
|
|
function playSounds (list)
|
|
{
|
|
var ary = list.split (" ");
|
|
if (ary.length == 0)
|
|
return;
|
|
|
|
playSound (ary[0]);
|
|
for (var i = 1; i < ary.length; ++i)
|
|
setTimeout (playSound, 250 * i, ary[i]);
|
|
}
|
|
|
|
function playSound (file)
|
|
{
|
|
if (!client.sound || !file)
|
|
return;
|
|
|
|
if (file == "beep")
|
|
{
|
|
client.sound.beep();
|
|
}
|
|
else
|
|
{
|
|
var uri = Components.classes["@mozilla.org/network/standard-url;1"];
|
|
uri = uri.createInstance(Components.interfaces.nsIURI);
|
|
uri.spec = file;
|
|
try
|
|
{
|
|
client.sound.play (uri);
|
|
} catch (ex) {
|
|
// ignore exceptions from this pile of code.
|
|
}
|
|
}
|
|
}
|
|
|
|
function fillInTooltip(tipElement, id)
|
|
{
|
|
const XLinkNS = "http://www.w3.org/1999/xlink";
|
|
|
|
var retVal = false;
|
|
|
|
var titleText = null;
|
|
var XLinkTitleText = null;
|
|
|
|
while (!titleText && !XLinkTitleText && tipElement) {
|
|
if (tipElement.nodeType == Node.ELEMENT_NODE) {
|
|
titleText = tipElement.getAttribute("title");
|
|
XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
|
|
}
|
|
tipElement = tipElement.parentNode;
|
|
}
|
|
|
|
var texts = [titleText, XLinkTitleText];
|
|
var tipNode = document.getElementById(id);
|
|
|
|
for (var i = 0; i < texts.length; ++i) {
|
|
var t = texts[i];
|
|
if (t && t.search(/\S/) >= 0) {
|
|
tipNode.setAttribute("label", t);
|
|
retVal = true;
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/* timer-based mainloop */
|
|
function mainStep()
|
|
{
|
|
client.eventPump.stepEvents();
|
|
setTimeout ("mainStep()", client.STEP_TIMEOUT);
|
|
}
|
|
|
|
function getMsg (msgName)
|
|
{
|
|
var restCount = arguments.length - 1;
|
|
|
|
if (!("bundle" in client))
|
|
{
|
|
client.bundle =
|
|
srGetStrBundle("chrome://chatzilla/locale/chatzilla.properties");
|
|
}
|
|
|
|
try
|
|
{
|
|
if (restCount == 1 && arguments[1] instanceof Array)
|
|
{
|
|
return client.bundle.formatStringFromName (msgName, arguments[1],
|
|
arguments[1].length);
|
|
}
|
|
else if (restCount > 0)
|
|
{
|
|
var subPhrases = new Array();
|
|
for (var i = 1; i < arguments.length; ++i)
|
|
subPhrases.push(arguments[i]);
|
|
return client.bundle.formatStringFromName (msgName, subPhrases,
|
|
subPhrases.length);
|
|
}
|
|
|
|
return client.bundle.GetStringFromName (msgName);
|
|
}
|
|
catch (ex)
|
|
{
|
|
dd ("caught exception getting value for ``" + msgName + "''\n" + ex +
|
|
"\n" + getStackTrace());
|
|
return msgName;
|
|
}
|
|
}
|
|
|
|
function openQueryTab (server, nick)
|
|
{
|
|
var usr = server.addUser(nick);
|
|
if (!("messages" in usr))
|
|
usr.displayHere (getMsg("cli_imsgMsg3", usr.properNick), "INFO");
|
|
server.sendData ("WHO " + nick + "\n");
|
|
return usr;
|
|
}
|
|
|
|
function arraySpeak (ary, single, plural)
|
|
{
|
|
var rv = "";
|
|
var and = getMsg ("arraySpeakAnd");
|
|
|
|
switch (ary.length)
|
|
{
|
|
case 0:
|
|
break;
|
|
|
|
case 1:
|
|
rv = ary[0];
|
|
if (single)
|
|
rv += " " + single;
|
|
break;
|
|
|
|
case 2:
|
|
rv = ary[0] + " " + and + " " + ary[1];
|
|
if (plural)
|
|
rv += " " + plural;
|
|
break;
|
|
|
|
default:
|
|
for (var i = 0; i < ary.length - 1; ++i)
|
|
rv += ary[i] + ", ";
|
|
rv += and + " " + ary[ary.length - 1];
|
|
if (plural)
|
|
rv += " " + plural;
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
function quicklistCallback (element, ndx, ary)
|
|
{
|
|
/* Check whether the selected attribute == true */
|
|
if (element.getAttribute("selected") == "true")
|
|
{
|
|
/* extract the nick data from the element */
|
|
/* Hmmm, nice walk ... */
|
|
ary.push(element.childNodes[0].childNodes[2].childNodes[0].nodeValue);
|
|
}
|
|
}
|
|
|
|
function getObjectDetails (obj, rv)
|
|
{
|
|
if (!rv)
|
|
rv = new Object();
|
|
|
|
if (!obj || (typeof obj != "object"))
|
|
{
|
|
dd ("** INVALID OBJECT passed to getObjectDetails (" + obj + "). **");
|
|
dd (getStackTrace());
|
|
}
|
|
|
|
rv.orig = obj;
|
|
rv.parent = ("parent" in obj) ? obj.parent : null;
|
|
|
|
switch (obj.TYPE)
|
|
{
|
|
case "IRCChannel":
|
|
rv.channel = obj;
|
|
rv.server = rv.channel.parent;
|
|
rv.network = rv.server.parent;
|
|
break;
|
|
|
|
case "IRCUser":
|
|
rv.user = obj;
|
|
rv.server = rv.user.parent;
|
|
rv.network = rv.server.parent;
|
|
break;
|
|
|
|
case "IRCChanUser":
|
|
rv.user = obj;
|
|
rv.channel = rv.user.parent;
|
|
rv.server = rv.channel.parent;
|
|
rv.network = rv.server.parent;
|
|
break;
|
|
|
|
case "IRCNetwork":
|
|
rv.network = obj;
|
|
if ("primServ" in rv.network)
|
|
rv.server = rv.network.primServ;
|
|
break;
|
|
|
|
case "IRCClient":
|
|
if ("lastNetwork" in obj)
|
|
{
|
|
rv.network = obj.lastNetwork;
|
|
if (rv.network.isConnected())
|
|
rv.server = rv.network.primServ;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* no setup for unknown object */
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
function findDynamicRule (selector)
|
|
{
|
|
var rules = frames[0].document.styleSheets[1].cssRules;
|
|
|
|
if (selector instanceof RegExp)
|
|
fun = "search";
|
|
else
|
|
fun = "indexOf";
|
|
|
|
for (var i = 0; i < rules.length; ++i)
|
|
{
|
|
var rule = rules.item(i);
|
|
if (rule.selectorText && rule.selectorText[fun](selector) == 0)
|
|
return {sheet: frames[0].document.styleSheets[1], rule: rule,
|
|
index: i};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function addDynamicRule (rule)
|
|
{
|
|
var rules = frames[0].document.styleSheets[1];
|
|
|
|
var pos = rules.cssRules.length;
|
|
rules.insertRule (rule, pos);
|
|
}
|
|
|
|
function setClientOutput(doc)
|
|
{
|
|
client.deck = document.getElementById('output-deck');
|
|
//XXXcreateHighlightMenu();
|
|
}
|
|
|
|
function createHighlightMenu()
|
|
{
|
|
/* Look for "special" highlighting rules int he motif. These special rules
|
|
* are in the format ``.chatzilla-highlight[name="<display-name>"] { ... }''
|
|
* where <display-name> is a textual description to be placed in the
|
|
* Highlight submenu of the message area context menu. The body of
|
|
* these rules can be applied by the user to different irc messages. They
|
|
* are special becaus they do not actually match an element in the content
|
|
* model. The style body is copied into a new rule that matches a pettern
|
|
* determined by the user.
|
|
*/
|
|
function processStyleRules(rules)
|
|
{
|
|
for (var i = 0; i < rules.length; ++i)
|
|
{
|
|
var rule = rules.item(i);
|
|
if (rule instanceof CSSStyleRule)
|
|
{
|
|
var ary = rule.selectorText.
|
|
match(/\.chatzilla-highlight\[name=\"?([^\"]+)\"?\]/i);
|
|
if (ary)
|
|
{
|
|
menuitem = document.createElement("menuitem");
|
|
menuitem.setAttribute ("class", "highlight-menu-item");
|
|
menuitem.setAttribute ("label", ary[1]);
|
|
menuitem.setAttribute ("oncommand", "onPopupHighlight('" +
|
|
rule.style.cssText + "');");
|
|
menuitem.setAttribute ("style", rule.style.cssText);
|
|
menu.appendChild(menuitem);
|
|
}
|
|
}
|
|
else if (rule instanceof CSSImportRule)
|
|
{
|
|
processStyleRules(rule.styleSheet.cssRules);
|
|
}
|
|
}
|
|
}
|
|
|
|
var menu = document.getElementById("highlightMenu");
|
|
while (menu.firstChild)
|
|
menu.removeChild(menu.firstChild);
|
|
|
|
var menuitem = document.createElement("menuitem");
|
|
menuitem.setAttribute ("label", getMsg("noStyle"));
|
|
menuitem.setAttribute ("class", "highlight-menu-item");
|
|
menuitem.setAttribute ("oncommand", "onPopupHighlight('');");
|
|
menu.appendChild(menuitem);
|
|
|
|
processStyleRules(frames[0].document.styleSheets[0].cssRules);
|
|
}
|
|
|
|
function setupMungerMenu(munger)
|
|
{
|
|
var menu = document.getElementById("menu-munger");
|
|
for (var entry in munger.entries)
|
|
{
|
|
if (entry[0] != ".")
|
|
{
|
|
var menuitem = document.createElement("menuitem");
|
|
menuitem.setAttribute ("label", munger.entries[entry].description);
|
|
menuitem.setAttribute ("id", "menu-munger-" + entry);
|
|
menuitem.setAttribute ("type", "checkbox");
|
|
if (munger.entries[entry].enabled)
|
|
menuitem.setAttribute ("checked", "true");
|
|
menuitem.setAttribute ("oncommand", "onToggleMungerEntry('" +
|
|
entry + "');");
|
|
menu.appendChild(menuitem);
|
|
}
|
|
}
|
|
}
|
|
|
|
function joinChannel(e, namelist, key, charset)
|
|
{
|
|
if (!e.network || !e.network.isConnected())
|
|
{
|
|
if (!e.network)
|
|
client.currentObject.display (getMsg("cli_ijoinMsg"), "ERROR");
|
|
else
|
|
client.currentObject.display (getMsg("cli_ijoinMsg2",
|
|
e.network.name1), "ERROR");
|
|
return false;
|
|
}
|
|
|
|
var name;
|
|
for (i in namelist)
|
|
{
|
|
name = namelist[i];
|
|
if (name[0].search(/[#&+!]/) != 0)
|
|
name = "#" + name;
|
|
|
|
e.channel = e.server.addChannel (name, charset);
|
|
e.channel.join(key);
|
|
|
|
if (!("messages" in e.channel))
|
|
{
|
|
e.channel.displayHere (getMsg("cli_ijoinMsg3",
|
|
e.channel.unicodeName),
|
|
"INFO");
|
|
}
|
|
setCurrentObject(e.channel);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
var testURLs =
|
|
["irc:", "irc://", "irc:///", "irc:///help", "irc:///help,needkey",
|
|
"irc://irc.foo.org", "irc://foo:6666",
|
|
"irc://foo", "irc://irc.foo.org/", "irc://foo:6666/", "irc://foo/",
|
|
"irc://irc.foo.org/,needpass", "irc://foo/,isserver",
|
|
"irc://moznet/,isserver", "irc://moznet/",
|
|
"irc://foo/chatzilla", "irc://foo/chatzilla/",
|
|
"irc://irc.foo.org/?msg=hello%20there",
|
|
"irc://irc.foo.org/?msg=hello%20there&ignorethis",
|
|
"irc://irc.foo.org/%23mozilla,needkey?msg=hello%20there&ignorethis",
|
|
"invalids",
|
|
"irc://irc.foo.org/,isnick"];
|
|
|
|
function doURLTest()
|
|
{
|
|
for (var u in testURLs)
|
|
{
|
|
dd ("testing url \"" + testURLs[u] + "\"");
|
|
var o = parseIRCURL(testURLs[u]);
|
|
if (!o)
|
|
dd ("PARSE FAILED!");
|
|
else
|
|
dd (dumpObjectTree(o));
|
|
dd ("---");
|
|
}
|
|
}
|
|
|
|
function parseIRCURL (url)
|
|
{
|
|
var specifiedHost = "";
|
|
|
|
var rv = new Object();
|
|
rv.spec = url;
|
|
rv.host = null;
|
|
rv.target = "";
|
|
rv.port = 6667;
|
|
rv.msg = "";
|
|
rv.needpass = false;
|
|
rv.needkey = false;
|
|
rv.isnick = false;
|
|
rv.isserver = false;
|
|
|
|
if (url.search(/^(irc:\/?\/?)$/i) != -1)
|
|
return rv;
|
|
|
|
rv.host = client.DEFAULT_NETWORK;
|
|
|
|
/* split url into <host>/<everything-else> pieces */
|
|
var ary = url.match (/^irc:\/\/([^\/\s]+)?(\/.*)?\s*$/i);
|
|
if (!ary)
|
|
{
|
|
dd ("parseIRCURL: initial split failed");
|
|
return null;
|
|
}
|
|
var host = ary[1];
|
|
var rest = (2 in ary) ? ary[2] : "";
|
|
|
|
/* split <host> into server (or network) / port */
|
|
ary = host.match (/^([^\s\:]+)?(\:\d+)?$/);
|
|
if (!ary)
|
|
{
|
|
dd ("parseIRCURL: host/port split failed");
|
|
return null;
|
|
}
|
|
|
|
if (2 in ary)
|
|
{
|
|
if (!(1 in ary))
|
|
{
|
|
dd ("parseIRCURL: port with no host");
|
|
return null;
|
|
}
|
|
specifiedHost = rv.host = ary[1].toLowerCase();
|
|
rv.isserver = true;
|
|
rv.port = parseInt(ary[2].substr(1));
|
|
}
|
|
else if (1 in ary)
|
|
{
|
|
specifiedHost = rv.host = ary[1].toLowerCase();
|
|
if (specifiedHost.indexOf(".") != -1)
|
|
rv.isserver = true;
|
|
}
|
|
|
|
if (rest)
|
|
{
|
|
ary = rest.match (/^\/([^\,\?\s\/]*)?\/?(,[^\?]*)?(\?.*)?$/);
|
|
if (!ary)
|
|
{
|
|
dd ("parseIRCURL: rest split failed ``" + rest + "''");
|
|
return null;
|
|
}
|
|
|
|
rv.target = (1 in ary) ?
|
|
unescape(ary[1]).replace("\n", "\\n") : "";
|
|
var i = rv.target.indexOf(" ");
|
|
if (i != -1)
|
|
rv.target = rv.target.substr(0, i);
|
|
var params = (2 in ary) ? ary[2].toLowerCase() : "";
|
|
var query = (3 in ary) ? ary[3] : "";
|
|
|
|
if (params)
|
|
{
|
|
rv.isnick =
|
|
(params.search (/,\s*isnick\s*,|,\s*isnick\s*$/) != -1);
|
|
if (rv.isnick && !rv.target)
|
|
{
|
|
dd ("parseIRCURL: isnick w/o target");
|
|
/* isnick w/o a target is bogus */
|
|
return null;
|
|
}
|
|
|
|
if (!rv.isserver)
|
|
{
|
|
rv.isserver =
|
|
(params.search (/,\s*isserver\s*,|,\s*isserver\s*$/) != -1);
|
|
}
|
|
|
|
if (rv.isserver && !specifiedHost)
|
|
{
|
|
dd ("parseIRCURL: isserver w/o host");
|
|
/* isserver w/o a host is bogus */
|
|
return null;
|
|
}
|
|
|
|
rv.needpass =
|
|
(params.search (/,\s*needpass\s*,|,\s*needpass\s*$/) != -1);
|
|
|
|
rv.needkey =
|
|
(params.search (/,\s*needkey\s*,|,\s*needkey\s*$/) != -1);
|
|
|
|
}
|
|
|
|
if (query)
|
|
{
|
|
ary = query.substr(1).split("&");
|
|
while (ary.length)
|
|
{
|
|
var arg = ary.pop().split("=");
|
|
/*
|
|
* we don't want to accept *any* query, or folks could
|
|
* say things like "target=foo", and overwrite what we've
|
|
* already parsed, so we only use query args we know about.
|
|
*/
|
|
switch (arg[0].toLowerCase())
|
|
{
|
|
case "msg":
|
|
rv.msg = unescape(arg[1]).replace ("\n", "\\n");
|
|
break;
|
|
|
|
case "pass":
|
|
rv.needpass = true;
|
|
rv.pass = unescape(arg[1]).replace ("\n", "\\n");
|
|
break;
|
|
|
|
case "key":
|
|
rv.needkey = true;
|
|
rv.key = unescape(arg[1]).replace ("\n", "\\n");
|
|
break;
|
|
|
|
case "charset":
|
|
rv.charset = unescape(arg[1]).replace ("\n", "\\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
function gotoIRCURL (url)
|
|
{
|
|
if (typeof url == "string")
|
|
url = parseIRCURL(url);
|
|
|
|
if (!url)
|
|
{
|
|
window.alert (getMsg("badIRCURL",url));
|
|
return;
|
|
}
|
|
|
|
if (!url.host)
|
|
{
|
|
/* focus the *client* view for irc:, irc:/, and irc:// (the only irc
|
|
* urls that don't have a host. (irc:/// implies a connect to the
|
|
* default network.)
|
|
*/
|
|
onSimulateCommand("/client");
|
|
return;
|
|
}
|
|
|
|
var net;
|
|
var pass = "";
|
|
|
|
if (url.needpass)
|
|
{
|
|
if ("pass" in url)
|
|
pass = url.pass;
|
|
else
|
|
pass = window.prompt (getMsg("gotoIRCURLMsg2",url.spec));
|
|
}
|
|
|
|
if (url.isserver)
|
|
{
|
|
var alreadyThere = false;
|
|
for (var n in client.networks)
|
|
{
|
|
if ((client.networks[n].isConnected()) &&
|
|
(client.networks[n].primServ.connection.host == url.host) &&
|
|
(client.networks[n].primServ.connection.port == url.port))
|
|
{
|
|
/* already connected to this server/port */
|
|
net = client.networks[n];
|
|
alreadyThere = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!alreadyThere)
|
|
{
|
|
/*
|
|
dd ("gotoIRCURL: not already connected to " +
|
|
"server " + url.host + " trying to connect...");
|
|
*/
|
|
client.onInputServer ({inputData: url.host + " " + url.port +
|
|
" " + pass});
|
|
net = client.networks[url.host];
|
|
if (!("pendingURLs" in net))
|
|
net.pendingURLs = new Array();
|
|
net.pendingURLs.unshift (url);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
/* parsed as a network name */
|
|
{
|
|
net = client.networks[url.host];
|
|
if (!net.isConnected())
|
|
{
|
|
/*
|
|
dd ("gotoIRCURL: not already connected to " +
|
|
"network " + url.host + " trying to connect...");
|
|
*/
|
|
client.connectToNetwork (url.host, pass);
|
|
if (!("pendingURLs" in net))
|
|
net.pendingURLs = new Array();
|
|
net.pendingURLs.unshift (url);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* already connected, do whatever comes next in the url */
|
|
//dd ("gotoIRCURL: connected, time to finish parsing ``" + url + "''");
|
|
if (url.target)
|
|
{
|
|
var targetObject;
|
|
var ev;
|
|
if (url.isnick)
|
|
{
|
|
/* url points to a person. */
|
|
var nick = url.target;
|
|
var ary = url.target.split("!");
|
|
if (ary)
|
|
{
|
|
nick = ary[0];
|
|
}
|
|
|
|
ev = {inputData: nick, network: net, server: net.primServ};
|
|
client.onInputQuery(ev);
|
|
targetObject = ev.user;
|
|
}
|
|
else
|
|
{
|
|
/* url points to a channel */
|
|
var key = "";
|
|
if (url.needkey)
|
|
{
|
|
if ("key" in url)
|
|
key = url.key;
|
|
else
|
|
key = window.prompt (getMsg("gotoIRCURLMsg3", url.spec));
|
|
}
|
|
|
|
var charset;
|
|
|
|
if ("charset" in url)
|
|
{
|
|
if (checkCharset(url.charset))
|
|
charset = url.charset;
|
|
else
|
|
display (getMsg("cli_charsetError", url.charset), "ERROR");
|
|
}
|
|
|
|
ev = {inputData: url.target + " " + key,
|
|
network: net, server: net.primServ};
|
|
joinChannel(ev, [url.target], key, charset)
|
|
targetObject = ev.channel;
|
|
}
|
|
|
|
if (url.msg)
|
|
{
|
|
var msg;
|
|
if (url.msg.indexOf("\01ACTION") == 0)
|
|
{
|
|
msg = filterOutput (url.msg, "ACTION", "ME!");
|
|
targetObject.display (msg, "ACTION", "ME!",
|
|
client.currentObject);
|
|
}
|
|
else
|
|
{
|
|
msg = filterOutput (url.msg, "PRIVMSG", "ME!");
|
|
targetObject.display (msg, "PRIVMSG", "ME!",
|
|
client.currentObject);
|
|
}
|
|
targetObject.say (fromUnicode(msg));
|
|
setCurrentObject (targetObject);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!net.messages)
|
|
net.displayHere (getMsg("gotoIRCURLMsg4",net.name),
|
|
"INFO");
|
|
setCurrentObject (net);
|
|
}
|
|
}
|
|
|
|
function setTopicText (text)
|
|
{
|
|
var topic = client.statusBar["channel-topic"];
|
|
var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:span");
|
|
|
|
span.appendChild(stringToMsg(text, client.currentObject));
|
|
topic.removeChild(topic.firstChild);
|
|
topic.appendChild(span);
|
|
}
|
|
|
|
function updateNetwork(obj)
|
|
{
|
|
var o = getObjectDetails (client.currentObject);
|
|
|
|
var lag = MSG_UNKNOWN;
|
|
var nick = "";
|
|
if ("server" in o)
|
|
{
|
|
if (o.server.me)
|
|
nick = o.server.me.properNick;
|
|
lag = (o.server.lag != -1) ? o.server.lag : MSG_UNKNOWN;
|
|
}
|
|
client.statusBar["header-url"].setAttribute("value",
|
|
client.currentObject.getURL());
|
|
client.statusBar["header-url"].setAttribute("href",
|
|
client.currentObject.getURL());
|
|
client.statusBar["header-url"].setAttribute("name",
|
|
client.currentObject.name);
|
|
client.statusBar["server-nick"].setAttribute("value", nick);
|
|
}
|
|
|
|
function updateChannel (obj)
|
|
{
|
|
if (obj && obj != client.currentObject)
|
|
return;
|
|
|
|
var o = getObjectDetails (client.currentObject);
|
|
|
|
var mode = MSG_NONE, users = MSG_NONE, topic = MSG_UNKNOWN;
|
|
|
|
if ("channel" in o)
|
|
{
|
|
mode = o.channel.mode.getModeStr();
|
|
if (!mode)
|
|
mode = MSG_NONE;
|
|
users = getMsg("userCountDetails",
|
|
[o.channel.getUsersLength(), o.channel.opCount,
|
|
o.channel.voiceCount]);
|
|
topic = o.channel.topic ? o.channel.topic : MSG_NONE;
|
|
}
|
|
|
|
client.statusBar["channel-mode"].setAttribute("value", mode);
|
|
client.statusBar["channel-users"].setAttribute("value", users);
|
|
var regex = new RegExp ("(\\S{" + client.MAX_WORD_DISPLAY + ",})", "g");
|
|
var ary = topic.match(regex);
|
|
if (ary && ary.length)
|
|
{
|
|
for (var i = 0; i < ary.length; ++i)
|
|
{
|
|
var hyphenated = hyphenateWord(ary[i], client.MAX_WORD_DISPLAY);
|
|
topic = topic.replace(ary[i], hyphenated);
|
|
}
|
|
}
|
|
|
|
client.statusBar["channel-topic"].firstChild.data = topic;
|
|
|
|
}
|
|
|
|
function updateTitle (obj)
|
|
{
|
|
if (!("currentObject" in client) || (obj && obj != client.currentObject))
|
|
return;
|
|
|
|
var tstring;
|
|
var o = getObjectDetails (client.currentObject);
|
|
var net = "network" in o ? o.network.name : "";
|
|
var nick = "";
|
|
|
|
switch (client.currentObject.TYPE)
|
|
{
|
|
case "IRCNetwork":
|
|
var serv = "", port = "";
|
|
if ("server" in o)
|
|
{
|
|
serv = o.server.connection.host;
|
|
port = o.server.connection.port;
|
|
if (o.server.me)
|
|
nick = o.server.me.properNick;
|
|
tstring = getMsg("updateTitleNetwork", [nick, net, serv, port]);
|
|
}
|
|
else
|
|
{
|
|
nick = client.currentObject.INITIAL_NICK;
|
|
tstring = getMsg("updateTitleNetwork2", [nick, net]);
|
|
}
|
|
break;
|
|
|
|
case "IRCChannel":
|
|
var chan = "", mode = "", topic = "";
|
|
nick = "me" in o.parent ? o.parent.me.properNick :
|
|
getMsg ("updateTitleNoNick");
|
|
chan = o.channel.unicodeName;
|
|
mode = o.channel.mode.getModeStr();
|
|
if (!mode)
|
|
mode = getMsg("updateTitleNoMode");
|
|
topic = o.channel.topic ? o.channel.topic :
|
|
getMsg("updateTitleNoTopic");
|
|
|
|
tstring = getMsg("updateTitleChannel", [nick, chan, mode, topic]);
|
|
break;
|
|
|
|
case "IRCUser":
|
|
nick = client.currentObject.properNick;
|
|
var source = "";
|
|
if (client.currentObject.name)
|
|
{
|
|
source = client.currentObject.name + "@" +
|
|
client.currentObject.host;
|
|
}
|
|
tstring = getMsg("updateTitleUser", [nick, source]);
|
|
//client.statusBar["channel-topic"].setAttribute("value", tstring);
|
|
client.statusBar["channel-topic"].firstChild.data = tstring;
|
|
break;
|
|
|
|
default:
|
|
tstring = getMsg("updateTitleUnknown");
|
|
break;
|
|
}
|
|
|
|
if (!client.uiState["tabstrip"])
|
|
{
|
|
var actl = new Array();
|
|
for (var i in client.activityList)
|
|
actl.push ((client.activityList[i] == "!") ?
|
|
(Number(i) + 1) + "!" : (Number(i) + 1));
|
|
if (actl.length > 0)
|
|
tstring = getMsg("updateTitleWithActivity",
|
|
[tstring, actl.join (MSG_CSP)]);
|
|
}
|
|
|
|
document.title = tstring;
|
|
|
|
}
|
|
|
|
function multilineInputMode (state)
|
|
{
|
|
var multiInput = document.getElementById("multiline-input");
|
|
var multiInputBox = document.getElementById("multiline-box");
|
|
var singleInput = document.getElementById("input");
|
|
var singleInputBox = document.getElementById("singleline-box");
|
|
var splitter = document.getElementById("input-splitter");
|
|
var iw = document.getElementById("input-widgets");
|
|
var h;
|
|
|
|
client._mlMode = state;
|
|
|
|
if (state) /* turn on multiline input mode */
|
|
{
|
|
|
|
h = iw.getAttribute ("lastHeight");
|
|
if (h)
|
|
iw.setAttribute ("height", h); /* restore the slider position */
|
|
|
|
singleInputBox.setAttribute ("collapsed", "true");
|
|
splitter.setAttribute ("collapsed", "false");
|
|
multiInputBox.setAttribute ("collapsed", "false");
|
|
client.input = multiInput;
|
|
}
|
|
else /* turn off multiline input mode */
|
|
{
|
|
h = iw.getAttribute ("height");
|
|
iw.setAttribute ("lastHeight", h); /* save the slider position */
|
|
iw.removeAttribute ("height"); /* let the slider drop */
|
|
|
|
splitter.setAttribute ("collapsed", "true");
|
|
multiInputBox.setAttribute ("collapsed", "true");
|
|
singleInputBox.setAttribute ("collapsed", "false");
|
|
client.input = singleInput;
|
|
}
|
|
|
|
client.MULTILINE = state;
|
|
client.input.focus();
|
|
}
|
|
|
|
function focusInput ()
|
|
{
|
|
const WWATCHER_CTRID = "@mozilla.org/embedcomp/window-watcher;1";
|
|
const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
|
|
|
|
var watcher =
|
|
Components.classes[WWATCHER_CTRID].getService(nsIWindowWatcher);
|
|
if (watcher.activeWindow == window)
|
|
client.input.focus();
|
|
else
|
|
document.commandDispatcher.focusedElement = client.input;
|
|
}
|
|
|
|
function newInlineText (data, className, tagName)
|
|
{
|
|
if (typeof tagName == "undefined")
|
|
tagName = "html:span";
|
|
|
|
var a = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
tagName);
|
|
if (className)
|
|
a.setAttribute ("class", className);
|
|
|
|
switch (typeof data)
|
|
{
|
|
case "string":
|
|
a.appendChild (document.createTextNode (data));
|
|
break;
|
|
|
|
case "object":
|
|
for (var p in data)
|
|
if (p != "data")
|
|
a.setAttribute (p, data[p]);
|
|
else
|
|
a.appendChild (document.createTextNode (data[p]));
|
|
break;
|
|
|
|
case "undefined":
|
|
break;
|
|
|
|
default:
|
|
dd ("** INVALID TYPE ('" + typeof data + "') passed to " +
|
|
"newInlineText.");
|
|
break;
|
|
|
|
}
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
function stringToMsg (message, obj)
|
|
{
|
|
var ary = message.split ("\n");
|
|
var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:span");
|
|
var data = getObjectDetails(obj);
|
|
|
|
if (ary.length == 1)
|
|
client.munger.munge(ary[0], span, data);
|
|
else
|
|
{
|
|
for (var l = 0; l < ary.length - 1; ++l)
|
|
{
|
|
client.munger.munge(ary[l], span, data);
|
|
span.appendChild
|
|
(document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:br"));
|
|
}
|
|
client.munger.munge(ary[l], span, data);
|
|
}
|
|
|
|
return span;
|
|
}
|
|
|
|
function getFrame()
|
|
{
|
|
if (client.deck.childNodes.length == 0)
|
|
return undefined;
|
|
var panel = client.deck.selectedPanel;
|
|
return ("contentWindow" in panel ? panel.contentWindow : undefined);
|
|
}
|
|
|
|
client.__defineGetter__ ("currentFrame", getFrame);
|
|
|
|
function setCurrentObject (obj)
|
|
{
|
|
if (!obj.messages)
|
|
{
|
|
dd ("** INVALID OBJECT passed to setCurrentObject **");
|
|
return;
|
|
}
|
|
|
|
if ("currentObject" in client && client.currentObject == obj)
|
|
return;
|
|
|
|
var tb, userList;
|
|
|
|
if ("currentObject" in client && client.currentObject)
|
|
{
|
|
tb = getTabForObject(client.currentObject);
|
|
}
|
|
if (tb)
|
|
{
|
|
tb.selected = false;
|
|
tb.setAttribute ("state", "normal");
|
|
}
|
|
|
|
/* Unselect currently selected users. */
|
|
userList = document.getElementById("user-list");
|
|
if (isVisible("user-list-box"))
|
|
{
|
|
/* Remove currently selected items before this tree gets rerooted,
|
|
* because it seems to remember the selections for eternity if not. */
|
|
if (userList.treeBoxObject.selection)
|
|
userList.treeBoxObject.selection.clearSelection ();
|
|
|
|
if (obj.TYPE == "IRCChannel")
|
|
client.rdf.setTreeRoot ("user-list", obj.getGraphResource());
|
|
else
|
|
client.rdf.setTreeRoot ("user-list", client.rdf.resNullChan);
|
|
}
|
|
|
|
client.currentObject = obj;
|
|
tb = getTabForObject(obj, true);
|
|
if (tb)
|
|
{
|
|
tb.selected = true;
|
|
tb.setAttribute ("state", "current");
|
|
}
|
|
|
|
var vk = Number(tb.getAttribute("viewKey"));
|
|
delete client.activityList[vk];
|
|
client.deck.selectedIndex = vk;
|
|
|
|
updateNetwork();
|
|
updateChannel();
|
|
updateTitle ();
|
|
|
|
if (client.PRINT_DIRECTION == 1)
|
|
scrollDown(obj.frame, false);
|
|
|
|
onTopicEditEnd();
|
|
|
|
if (client.currentObject.TYPE == "IRCChannel")
|
|
client.statusBar["channel-topic"].setAttribute ("editable", "true");
|
|
else
|
|
client.statusBar["channel-topic"].removeAttribute ("editable");
|
|
|
|
var status = document.getElementById("offline-status");
|
|
if (client.currentObject == client)
|
|
{
|
|
status.setAttribute ("hidden", "true");
|
|
}
|
|
else
|
|
{
|
|
status.removeAttribute ("hidden");
|
|
var details = getObjectDetails(client.currentObject);
|
|
if ("network" in details && details.network.isConnected())
|
|
status.removeAttribute ("offline");
|
|
else
|
|
status.setAttribute ("offline", "true");
|
|
}
|
|
}
|
|
|
|
function checkScroll(frame)
|
|
{
|
|
if (!frame || !("contentWindow" in frame))
|
|
return false;
|
|
|
|
var w = frame.contentWindow;
|
|
return ((w.document.height - (w.innerHeight + w.pageYOffset)) < 160);
|
|
}
|
|
|
|
function scrollDown(frame, force)
|
|
{
|
|
if (!frame || !("contentWindow" in frame))
|
|
return;
|
|
|
|
var w = frame.contentWindow;
|
|
|
|
if (force || checkScroll(frame))
|
|
w.scrollTo(0, w.document.height);
|
|
}
|
|
|
|
/* valid values for |what| are "superfluous", "activity", and "attention".
|
|
* final value for state is dependant on priority of the current state, and the
|
|
* new state. the priority is: normal < superfluous < activity < attention.
|
|
*/
|
|
function setTabState (source, what)
|
|
{
|
|
if (typeof source != "object")
|
|
source = client.viewsArray[source].source;
|
|
|
|
var tb = getTabForObject (source, true);
|
|
var vk = Number(tb.getAttribute("viewKey"));
|
|
|
|
if ("currentObject" in client && client.currentObject != source)
|
|
{
|
|
var state = tb.getAttribute ("state");
|
|
if (state == what)
|
|
{
|
|
/* if the tab state has an equal priority to what we are setting
|
|
* then blink it */
|
|
tb.setAttribute ("state", "normal");
|
|
setTimeout (setTabState, 200, vk, what);
|
|
}
|
|
else
|
|
{
|
|
if (state == "normal" || state == "superfluous" ||
|
|
(state == "activity" && what == "attention"))
|
|
{
|
|
/* if the tab state has a lower priority than what we are
|
|
* setting, change it to the new state */
|
|
tb.setAttribute ("state", what);
|
|
/* we only change the activity list if priority has increased */
|
|
if (what == "attention")
|
|
client.activityList[vk] = "!";
|
|
else if (what == "activity")
|
|
client.activityList[vk] = "+";
|
|
else
|
|
{
|
|
/* this is functionally equivalent to "+" for now */
|
|
client.activityList[vk] = "-";
|
|
}
|
|
updateTitle();
|
|
}
|
|
else
|
|
{
|
|
/* the current state of the tab has a higher priority than the
|
|
* new state.
|
|
* blink the new lower state quickly, then back to the old */
|
|
tb.setAttribute ("state", what);
|
|
setTimeout (setTabState, 200, vk, state);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function notifyAttention (source)
|
|
{
|
|
if (typeof source != "object")
|
|
source = client.viewsArray[source].source;
|
|
|
|
if (client.currentObject != source)
|
|
{
|
|
var tb = getTabForObject (source, true);
|
|
var vk = Number(tb.getAttribute("viewKey"));
|
|
|
|
tb.setAttribute ("state", "attention");
|
|
client.activityList[vk] = "!";
|
|
updateTitle();
|
|
}
|
|
|
|
if (client.FLASH_WINDOW)
|
|
window.getAttention();
|
|
|
|
}
|
|
|
|
function updateUserList()
|
|
{
|
|
var node;
|
|
var sortDirection;
|
|
|
|
var colArray = ["usercol-op", "usercol-voice", "usercol-nick"];
|
|
|
|
for (var i = 0; i < colArray.length; i++)
|
|
{
|
|
node = document.getElementById(colArray[i]);
|
|
if (!node)
|
|
return false;
|
|
sortDirection = node.getAttribute("sortDirection");
|
|
if (sortDirection != "")
|
|
break;
|
|
}
|
|
|
|
sortUserList(node, sortDirection);
|
|
}
|
|
|
|
function sortUserList(node, sortDirection)
|
|
{
|
|
const nsIXULSortService = Components.interfaces.nsIXULSortService;
|
|
const isupports_uri = "@mozilla.org/xul/xul-sort-service;1";
|
|
|
|
var xulSortService =
|
|
Components.classes[isupports_uri].getService(nsIXULSortService);
|
|
if (!xulSortService)
|
|
return false;
|
|
|
|
var sortResource = node.getAttribute("resource");
|
|
try
|
|
{
|
|
if ("sort" in xulSortService)
|
|
xulSortService.sort(node, sortResource, sortDirection);
|
|
else
|
|
xulSortService.Sort(node, sortResource, sortDirection);
|
|
document.persist("user-list", "sortResource");
|
|
document.persist("user-list", "sortDirection");
|
|
}
|
|
catch(ex)
|
|
{
|
|
//dd("Exception calling xulSortService.sort()");
|
|
}
|
|
}
|
|
|
|
function getFrameForDOMWindow(window)
|
|
{
|
|
var frame;
|
|
for (var i = 0; i < client.deck.childNodes.length; i++)
|
|
{
|
|
frame = client.deck.childNodes[i];
|
|
if (frame.contentWindow == window)
|
|
return frame;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
client.progressListener = new Object();
|
|
|
|
client.progressListener.QueryInterface =
|
|
function qi(iid)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
client.progressListener.onStateChange =
|
|
function client_statechange (webProgress, request, stateFlags, status)
|
|
{
|
|
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; const START = nsIWebProgressListener.STATE_START;
|
|
const STOP = nsIWebProgressListener.STATE_STOP;
|
|
|
|
var frame;
|
|
if (stateFlags & START)
|
|
{
|
|
frame = getFrameForDOMWindow(webProgress.DOMWindow);
|
|
if (!frame)
|
|
{
|
|
dd("can't find frame for window")
|
|
webProgress.removeProgressListener (this);
|
|
return;
|
|
}
|
|
}
|
|
else if (stateFlags == 786448)
|
|
{
|
|
frame = getFrameForDOMWindow(webProgress.DOMWindow);
|
|
if (!frame)
|
|
{
|
|
dd("can't find frame for window")
|
|
webProgress.removeProgressListener (this);
|
|
return;
|
|
}
|
|
frame.contentDocument.body.appendChild(frame.source.messages);
|
|
}
|
|
}
|
|
|
|
client.progressListener.onProgressChange =
|
|
function client_progresschange (webProgress, request, currentSelf, totalSelf,
|
|
currentMax, selfMax)
|
|
{
|
|
}
|
|
|
|
client.progressListener.onLocationChange =
|
|
function client_locationchange (webProgress, request, uri)
|
|
{
|
|
}
|
|
|
|
client.progressListener.onStatusChange =
|
|
function client_statuschange (webProgress, request, status, message)
|
|
{
|
|
}
|
|
|
|
client.progressListener.onSecurityChange =
|
|
function client_securitychange (webProgress, request, state)
|
|
{
|
|
}
|
|
|
|
function syncOutputFrame(iframe)
|
|
{
|
|
const nsIWebProgress = Components.interfaces.nsIWebProgress;
|
|
const ALL = nsIWebProgress.NOTIFY_ALL;
|
|
const DOCUMENT = nsIWebProgress.NOTIFY_STATE_DOCUMENT;
|
|
const WINDOW = nsIWebProgress.NOTIFY_STATE_WINDOW;
|
|
|
|
function tryAgain ()
|
|
{
|
|
syncOutputFrame(iframe);
|
|
};
|
|
|
|
try
|
|
{
|
|
if ("contentDocument" in iframe && "webProgress" in iframe)
|
|
{
|
|
|
|
iframe.addProgressListener (client.progressListener, WINDOW);
|
|
iframe.loadURI ("chrome://chatzilla/content/outputwindow.html?" +
|
|
client.DEFAULT_STYLE);
|
|
}
|
|
else
|
|
{
|
|
setTimeout (tryAgain, 500);
|
|
}
|
|
}
|
|
catch (ex)
|
|
{
|
|
dd ("caught exception showing session view, will try again later.");
|
|
dd (dumpObjectTree(ex)+"\n");
|
|
setTimeout (tryAgain, 500);
|
|
}
|
|
}
|
|
|
|
function createMessages(source)
|
|
{
|
|
source.messages =
|
|
document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:table");
|
|
|
|
source.messages.setAttribute ("class", "msg-table");
|
|
source.messages.setAttribute ("view-type", source.TYPE);
|
|
var tbody =
|
|
document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:tbody");
|
|
source.messages.appendChild (tbody);
|
|
source.messageCount = 0;
|
|
}
|
|
|
|
/* gets the toolbutton associated with an object
|
|
* if |create| is present, and true, create if not found */
|
|
function getTabForObject (source, create)
|
|
{
|
|
var name;
|
|
|
|
if (!source)
|
|
{
|
|
dd ("** UNDEFINED passed to getTabForObject **");
|
|
dd (getStackTrace());
|
|
return null;
|
|
}
|
|
|
|
switch (source.TYPE)
|
|
{
|
|
case "IRCChanUser":
|
|
case "IRCUser":
|
|
name = source.properNick;
|
|
break;
|
|
|
|
case "IRCNetwork":
|
|
case "IRCClient":
|
|
name = source.name;
|
|
break;
|
|
case "IRCChannel":
|
|
name = source.unicodeName;
|
|
break;
|
|
|
|
default:
|
|
dd ("** INVALID OBJECT passed to getTabForObject **");
|
|
return null;
|
|
}
|
|
|
|
var tb, id = "tb[" + name + "]";
|
|
var matches = 1;
|
|
|
|
for (var i in client.viewsArray)
|
|
{
|
|
if (client.viewsArray[i].source == source)
|
|
{
|
|
tb = client.viewsArray[i].tb;
|
|
break;
|
|
}
|
|
else
|
|
if (client.viewsArray[i].tb.getAttribute("id") == id)
|
|
id = "tb[" + name + "<" + (++matches) + ">]";
|
|
}
|
|
|
|
if (!tb && create) /* not found, create one */
|
|
{
|
|
if (!("messages" in source) || source.messages == null)
|
|
createMessages(source);
|
|
var views = document.getElementById ("views-tbar-inner");
|
|
tb = document.createElement ("tab");
|
|
tb.setAttribute ("ondraggesture",
|
|
"nsDragAndDrop.startDrag(event, tabDNDObserver);");
|
|
tb.setAttribute ("href", source.getURL());
|
|
tb.setAttribute ("name", source.name);
|
|
tb.setAttribute ("onclick", "onTabClick(" + id.quote() + ");");
|
|
tb.setAttribute ("crop", "right");
|
|
|
|
tb.setAttribute ("class", "tab-bottom view-button");
|
|
tb.setAttribute ("id", id);
|
|
tb.setAttribute ("state", "normal");
|
|
|
|
client.viewsArray.push ({source: source, tb: tb});
|
|
tb.setAttribute ("viewKey", client.viewsArray.length - 1);
|
|
if (matches > 1)
|
|
tb.setAttribute("label", name + "<" + matches + ">");
|
|
else
|
|
tb.setAttribute("label", name);
|
|
|
|
views.appendChild (tb);
|
|
|
|
var browser = document.createElement ("browser");
|
|
browser.setAttribute ("class", "output-container");
|
|
browser.setAttribute ("type", "content");
|
|
browser.setAttribute ("flex", "1");
|
|
browser.setAttribute ("tooltip", "aHTMLTooltip");
|
|
browser.setAttribute ("context", "outputContext");
|
|
//browser.setAttribute ("onload", "scrollDown(true);");
|
|
if (client.NO_BROWSER_FOCUS)
|
|
browser.setAttribute ("onclick", "focusInput()");
|
|
browser.setAttribute ("ondragover", "nsDragAndDrop.dragOver(event, contentDropObserver);");
|
|
browser.setAttribute ("ondragdrop", "nsDragAndDrop.drop(event, contentDropObserver);");
|
|
browser.setAttribute ("ondraggesture", "nsDragAndDrop.startDrag(event, contentAreaDNDObserver);");
|
|
browser.source = source;
|
|
source.frame = browser;
|
|
client.deck.appendChild (browser);
|
|
syncOutputFrame (browser);
|
|
}
|
|
|
|
return tb;
|
|
|
|
}
|
|
|
|
var contentDropObserver = new Object();
|
|
|
|
contentDropObserver.onDragOver =
|
|
function tabdnd_dover (aEvent, aFlavour, aDragSession)
|
|
{
|
|
if (aEvent.getPreventDefault())
|
|
return;
|
|
|
|
if (aDragSession.sourceDocument == aEvent.view.document)
|
|
{
|
|
aDragSession.canDrop = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
contentDropObserver.onDrop =
|
|
function tabdnd_drop (aEvent, aXferData, aDragSession)
|
|
{
|
|
var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);
|
|
if (!url || url.search(client.linkRE) == -1)
|
|
return;
|
|
|
|
if (url.search(/\.css$/i) != -1 && confirm (getMsg("tabdnd_drop", url)))
|
|
{
|
|
onSimulateCommand("/css " + url);
|
|
}
|
|
else if (url.search(/^irc:\/\//i) != -1)
|
|
{
|
|
gotoIRCURL (url);
|
|
}
|
|
}
|
|
|
|
contentDropObserver.getSupportedFlavours =
|
|
function tabdnd_gsf ()
|
|
{
|
|
var flavourSet = new FlavourSet();
|
|
flavourSet.appendFlavour("text/x-moz-url");
|
|
flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
|
|
flavourSet.appendFlavour("text/unicode");
|
|
return flavourSet;
|
|
}
|
|
|
|
var tabDNDObserver = new Object();
|
|
|
|
tabDNDObserver.onDragStart =
|
|
function tabdnd_dstart (aEvent, aXferData, aDragAction)
|
|
{
|
|
var tb = aEvent.currentTarget;
|
|
var href = tb.getAttribute("href");
|
|
var name = tb.getAttribute("name");
|
|
|
|
aXferData.data = new TransferData();
|
|
/* x-moz-url has the format "<url>\n<name>", goodie */
|
|
aXferData.data.addDataForFlavour("text/x-moz-url", href + "\n" + name);
|
|
aXferData.data.addDataForFlavour("text/unicode", href);
|
|
aXferData.data.addDataForFlavour("text/html", "<a href='" + href + "'>" +
|
|
name + "</a>");
|
|
}
|
|
|
|
function deleteTab (tb)
|
|
{
|
|
var i, key = Number(tb.getAttribute("viewKey"));
|
|
|
|
if (!isNaN(key))
|
|
{
|
|
if ("isPermanent" in client.viewsArray[key].source &&
|
|
client.viewsArray[key].source.isPermanent)
|
|
{
|
|
window.alert (getMsg("deleteTabMsg"));
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
/* re-index higher toolbuttons */
|
|
for (i = key + 1; i < client.viewsArray.length; i++)
|
|
{
|
|
client.viewsArray[i].tb.setAttribute ("viewKey", i - 1);
|
|
}
|
|
arrayRemoveAt(client.viewsArray, key);
|
|
var tbinner = document.getElementById("views-tbar-inner");
|
|
tbinner.removeChild(tb);
|
|
}
|
|
}
|
|
else
|
|
dd ("*** INVALID OBJECT passed to deleteTab (" + tb + ") " +
|
|
"no viewKey attribute. (" + key + ")");
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
function filterOutput (msg, msgtype)
|
|
{
|
|
if ("outputFilters" in client)
|
|
{
|
|
for (var f in client.outputFilters)
|
|
{
|
|
if (client.outputFilters[f].enabled)
|
|
msg = client.outputFilters[f].func(msg, msgtype);
|
|
}
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
client.connectToNetwork =
|
|
function cli_connet (netname, pass)
|
|
{
|
|
if (!(netname in client.networks))
|
|
{
|
|
display (getMsg("cli_attachNoNet", netname), "ERROR");
|
|
return false;
|
|
}
|
|
|
|
var netobj = client.networks[netname];
|
|
|
|
if (getBoolPref("logging", false, netobj.localPrefs))
|
|
client.startLogging(netobj);
|
|
|
|
if (!("messages" in netobj))
|
|
netobj.displayHere (getMsg("cli_attachOpened", netname), "INFO");
|
|
setCurrentObject(netobj);
|
|
|
|
if (netobj.isConnected())
|
|
{
|
|
netobj.display (getMsg("cli_attachAlreadyThere", netname), "ERROR");
|
|
return true;
|
|
}
|
|
|
|
if (CIRCNetwork.prototype.INITIAL_NICK == client.defaultNick)
|
|
CIRCNetwork.prototype.INITIAL_NICK =
|
|
prompt (getMsg("cli_attachGetNick"), client.defaultNick);
|
|
|
|
if (!("connecting" in netobj))
|
|
netobj.display (getMsg("cli_attachWorking",netobj.name), "INFO");
|
|
netobj.connecting = true;
|
|
netobj.connect(pass);
|
|
return true;
|
|
}
|
|
|
|
|
|
client.getURL =
|
|
function cli_geturl ()
|
|
{
|
|
return "irc://";
|
|
}
|
|
|
|
client.load =
|
|
function cli_load(url, obj)
|
|
{
|
|
if (!client._loader)
|
|
{
|
|
const LOADER_CTRID = "@mozilla.org/moz/jssubscript-loader;1";
|
|
const mozIJSSubScriptLoader =
|
|
Components.interfaces.mozIJSSubScriptLoader;
|
|
|
|
var cls;
|
|
if ((cls = Components.classes[LOADER_CTRID]))
|
|
client._loader = cls.createInstance (mozIJSSubScriptLoader);
|
|
}
|
|
|
|
try {
|
|
client.currentObject.display (getMsg("cli_loadLoading", url));
|
|
client._loader.loadSubScript (url, obj);
|
|
}
|
|
catch (ex)
|
|
{
|
|
var msg = getMsg("cli_loadError", ex.lineNumber, ex.fileName, ex);
|
|
client.currentObject.display (msg, "ERROR");
|
|
}
|
|
}
|
|
|
|
|
|
client.sayToCurrentTarget =
|
|
function cli_say(msg)
|
|
{
|
|
switch (client.currentObject.TYPE)
|
|
{
|
|
case "IRCChannel":
|
|
case "IRCUser":
|
|
case "IRCChanUser":
|
|
msg = filterOutput (msg, "PRIVMSG");
|
|
client.currentObject.display (msg, "PRIVMSG", "ME!",
|
|
client.currentObject);
|
|
if (client.currentObject.TYPE == "IRCChannel") {
|
|
var charset = client.currentObject.charset;
|
|
client.currentObject.say (fromUnicode(msg, charset));
|
|
} else {
|
|
client.currentObject.say (fromUnicode(msg));
|
|
}
|
|
break;
|
|
|
|
case "IRCClient":
|
|
client.onInputEval ({inputData: msg});
|
|
break;
|
|
|
|
default:
|
|
if (msg != "")
|
|
client.currentObject.display
|
|
(getMsg("cli_sayMsg", client.currentObject.TYPE), "ERROR");
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
CIRCNetwork.prototype.display =
|
|
function n_display (message, msgtype, sourceObj, destObj)
|
|
{
|
|
var o = getObjectDetails(client.currentObject);
|
|
|
|
if (client.SLOPPY_NETWORKS && client.currentObject != client &&
|
|
client.currentObject != this && o.network == this &&
|
|
o.server.connection.isConnected)
|
|
client.currentObject.display (message, msgtype, sourceObj, destObj);
|
|
else
|
|
this.displayHere (message, msgtype, sourceObj, destObj);
|
|
}
|
|
|
|
CIRCUser.prototype.display =
|
|
function u_display(message, msgtype, sourceObj, destObj)
|
|
{
|
|
if ("messages" in this)
|
|
this.displayHere (message, msgtype, sourceObj, destObj);
|
|
else
|
|
{
|
|
var o = getObjectDetails(client.currentObject);
|
|
if (o.server.connection.isConnected &&
|
|
o.network == this.parent.parent &&
|
|
client.currentObject.TYPE != "IRCUser")
|
|
client.currentObject.display (message, msgtype, sourceObj, destObj);
|
|
else
|
|
this.parent.parent.displayHere (message, msgtype, sourceObj,
|
|
destObj);
|
|
}
|
|
}
|
|
|
|
function display (message, msgtype, sourceObj, destObj)
|
|
{
|
|
client.currentObject.display (message, msgtype, sourceObj, destObj);
|
|
}
|
|
|
|
client.display =
|
|
client.displayHere =
|
|
CIRCNetwork.prototype.displayHere =
|
|
CIRCChannel.prototype.display =
|
|
CIRCChannel.prototype.displayHere =
|
|
CIRCUser.prototype.displayHere =
|
|
function __display(message, msgtype, sourceObj, destObj)
|
|
{
|
|
var canMergeData = false;
|
|
var canCollapseRow = false;
|
|
var logText = "";
|
|
|
|
function setAttribs (obj, c, attrs)
|
|
{
|
|
if (attrs)
|
|
{
|
|
for (var a in attrs)
|
|
obj.setAttribute (a, attrs[a]);
|
|
}
|
|
obj.setAttribute ("class", c);
|
|
obj.setAttribute ("msg-type", msgtype);
|
|
obj.setAttribute ("msg-dest", toAttr);
|
|
obj.setAttribute ("dest-type", toType);
|
|
obj.setAttribute ("view-type", viewType);
|
|
if (fromAttr)
|
|
if (fromUser)
|
|
obj.setAttribute ("msg-user", fromAttr);
|
|
else
|
|
obj.setAttribute ("msg-source", fromAttr);
|
|
}
|
|
|
|
var blockLevel = false; /* true if this row should be rendered at block
|
|
* level, (like, if it has a really long nickname
|
|
* that might disturb the rest of the layout) */
|
|
var o = getObjectDetails (this); /* get the skinny on |this| */
|
|
var me;
|
|
if ("server" in o && "me" in o.server)
|
|
{
|
|
me = o.server.me; /* get the object representing the user */
|
|
}
|
|
if (sourceObj == "ME!") sourceObj = me; /* if the caller passes "ME!" */
|
|
if (destObj == "ME!") destObj = me; /* substitute the actual object */
|
|
|
|
var fromType = (sourceObj && sourceObj.TYPE) ? sourceObj.TYPE : "unk";
|
|
var fromUser = (fromType.search(/IRC.*User/) != -1);
|
|
var fromAttr;
|
|
if (fromUser && sourceObj == me) fromAttr = me.nick + " ME!";
|
|
else if (fromUser) fromAttr = sourceObj.nick;
|
|
else if (typeof sourceObj == "object") fromAttr = sourceObj.name;
|
|
|
|
var toType = (destObj) ? destObj.TYPE : "unk";
|
|
var toAttr;
|
|
|
|
if (destObj && destObj == me)
|
|
toAttr = me.nick + " ME!";
|
|
else if (toType == "IRCUser")
|
|
toAttr = destObj.nick;
|
|
else if (typeof destObj == "object")
|
|
toAttr = destObj.name;
|
|
|
|
/* isImportant means to style the messages as important, and flash the
|
|
* window, getAttention means just flash the window. */
|
|
var isImportant = false, getAttention = false, isSuperfluous = false;
|
|
var viewType = this.TYPE;
|
|
var code;
|
|
var msgRow = document.createElementNS("http://www.w3.org/1999/xhtml",
|
|
"html:tr");
|
|
setAttribs(msgRow, "msg");
|
|
|
|
//dd ("fromType is " + fromType + ", fromAttr is " + fromAttr);
|
|
var d = new Date();
|
|
var mins = d.getMinutes();
|
|
if (mins < 10)
|
|
mins = "0" + mins;
|
|
var statusString;
|
|
|
|
var timeStamp = getMsg("cli_dateString", [d.getMonth() + 1, d.getDate(),
|
|
d.getHours(), mins]);
|
|
logText = "[" + timeStamp + "] ";
|
|
|
|
if (fromUser)
|
|
{
|
|
statusString =
|
|
getMsg("cli_statusString", [d.getMonth() + 1, d.getDate(),
|
|
d.getHours(), mins,
|
|
sourceObj.nick + "!" +
|
|
sourceObj.name + "@" + sourceObj.host]);
|
|
}
|
|
else
|
|
{
|
|
var name;
|
|
if (sourceObj)
|
|
{
|
|
name = (sourceObj.TYPE == "CIRCChannel") ?
|
|
sourceObj.unicodeName : sourceObj.name;
|
|
}
|
|
else
|
|
{
|
|
name = (this.TYPE == "CIRCChannel") ?
|
|
this.unicodeName : this.name;
|
|
}
|
|
|
|
statusString =
|
|
getMsg("cli_statusString", [d.getMonth() + 1, d.getDate(),
|
|
d.getHours(), mins, name]);
|
|
}
|
|
|
|
if (fromType.search(/IRC.*User/) != -1 &&
|
|
msgtype.search(/PRIVMSG|ACTION|NOTICE/) != -1)
|
|
{
|
|
/* do nick things here */
|
|
var nick;
|
|
var nickURL;
|
|
|
|
if (sourceObj != me)
|
|
{
|
|
nick = sourceObj.properNick;
|
|
if (!nick)
|
|
nick = sourceObj.name + "@" + sourceObj.host;
|
|
else if ("getURL" in sourceObj)
|
|
nickURL = sourceObj.getURL();
|
|
|
|
if (toType == "IRCUser") /* msg from user to me */
|
|
{
|
|
getAttention = true;
|
|
this.defaultCompletion = "/msg " + nick + " ";
|
|
if (msgtype == "ACTION")
|
|
{
|
|
logText += "*" + nick + " ";
|
|
}
|
|
else
|
|
{
|
|
if (this.TYPE == "IRCUser")
|
|
logText += "<" + nick + "> ";
|
|
else
|
|
logText += "*" + nick + "* ";
|
|
}
|
|
}
|
|
else /* msg from user to channel */
|
|
{
|
|
if (typeof (message == "string") && me)
|
|
{
|
|
isImportant = msgIsImportant (message, nick, o.network);
|
|
if (isImportant)
|
|
{
|
|
this.defaultCompletion = nick +
|
|
client.ADDRESSED_NICK_SEP + " ";
|
|
if (this.TYPE != "IRCNetwork")
|
|
playSounds(client.STALK_BEEP);
|
|
}
|
|
}
|
|
if (msgtype == "ACTION")
|
|
logText += "*" + nick + " ";
|
|
else
|
|
logText += "<" + nick + "> ";
|
|
}
|
|
}
|
|
else if (toType == "IRCUser") /* msg from me to user */
|
|
{
|
|
if (this.TYPE == "IRCUser")
|
|
{
|
|
nick = sourceObj.properNick;
|
|
if (msgtype == "ACTION")
|
|
logText += "*" + nick + " ";
|
|
else
|
|
logText += "<" + nick + "> ";
|
|
}
|
|
else
|
|
{
|
|
nick = destObj.properNick;
|
|
logText += ">" + nick + "< ";
|
|
}
|
|
}
|
|
else /* msg from me to channel */
|
|
{
|
|
nick = sourceObj.properNick;
|
|
if (msgtype == "ACTION")
|
|
logText += "*" + nick + " ";
|
|
else
|
|
logText += "<" + nick + "> ";
|
|
}
|
|
|
|
if (!("mark" in this))
|
|
this.mark = "odd";
|
|
|
|
if (!("lastNickDisplayed" in this) ||
|
|
this.lastNickDisplayed != nick)
|
|
{
|
|
this.lastNickDisplayed = nick;
|
|
this.mark = (this.mark == "even") ? "odd" : "even";
|
|
}
|
|
|
|
var msgSource = document.createElementNS("http://www.w3.org/1999/xhtml",
|
|
"html:td");
|
|
setAttribs (msgSource, "msg-user", {statusText: statusString});
|
|
if (isImportant)
|
|
msgSource.setAttribute ("important", "true");
|
|
if (nick.length > client.MAX_NICK_DISPLAY)
|
|
blockLevel = true;
|
|
if (nickURL)
|
|
{
|
|
var nick_anchor =
|
|
document.createElementNS("http://www.w3.org/1999/xhtml",
|
|
"html:a");
|
|
nick_anchor.setAttribute ("class", "chatzilla-link");
|
|
nick_anchor.setAttribute ("href", nickURL);
|
|
nick_anchor.appendChild (newInlineText (nick));
|
|
msgSource.appendChild (nick_anchor);
|
|
}
|
|
else
|
|
{
|
|
msgSource.appendChild (newInlineText (nick));
|
|
}
|
|
msgRow.appendChild (msgSource);
|
|
canMergeData = client.COLLAPSE_MSGS;
|
|
canCollapseRow = client.COLLAPSE_ROWS;
|
|
}
|
|
else
|
|
{
|
|
isSuperfluous = true;
|
|
if (!client.debugHook.enabled && msgtype in client.responseCodeMap)
|
|
{
|
|
code = client.responseCodeMap[msgtype];
|
|
}
|
|
else
|
|
{
|
|
if (!client.debugHook.enabled && client.HIDE_CODES)
|
|
code = client.DEFAULT_RESPONSE_CODE;
|
|
else
|
|
code = "[" + msgtype + "]";
|
|
}
|
|
|
|
/* Display the message code */
|
|
var msgType = document.createElementNS("http://www.w3.org/1999/xhtml",
|
|
"html:td");
|
|
setAttribs (msgType, "msg-type", {statusText: statusString});
|
|
|
|
msgType.appendChild (newInlineText (code));
|
|
msgRow.appendChild (msgType);
|
|
logText += code + " ";
|
|
}
|
|
|
|
if (message)
|
|
{
|
|
var msgData = document.createElementNS("http://www.w3.org/1999/xhtml",
|
|
"html:td");
|
|
setAttribs (msgData, "msg-data", {statusText: statusString,
|
|
colspan: client.INITIAL_COLSPAN,
|
|
timeStamp: timeStamp});
|
|
if (isImportant)
|
|
msgData.setAttribute ("important", "true");
|
|
|
|
if ("mark" in this)
|
|
msgData.setAttribute ("mark", this.mark);
|
|
|
|
if (typeof message == "string")
|
|
{
|
|
msgData.appendChild (stringToMsg (message, this));
|
|
logText += message;
|
|
}
|
|
else
|
|
{
|
|
msgData.appendChild (message);
|
|
logText += message.innerHTML.replace(/<[^<]*>/g, "");
|
|
}
|
|
|
|
msgRow.appendChild (msgData);
|
|
}
|
|
|
|
if (isImportant)
|
|
msgRow.setAttribute ("important", "true");
|
|
|
|
if (blockLevel)
|
|
{
|
|
/* putting a div here crashes mozilla, so fake it with nested tables
|
|
* for now */
|
|
var tr = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:tr");
|
|
tr.setAttribute ("class", "msg-nested-tr");
|
|
var td = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:td");
|
|
td.setAttribute ("class", "msg-nested-td");
|
|
td.setAttribute ("colspan", "2");
|
|
|
|
tr.appendChild(td);
|
|
var table = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:table");
|
|
table.setAttribute ("class", "msg-nested-table");
|
|
table.setAttribute ("cellpadding", "0");
|
|
|
|
td.appendChild (table);
|
|
var tbody = document.createElementNS ("http://www.w3.org/1999/xhtml",
|
|
"html:tbody");
|
|
|
|
tbody.appendChild (msgRow);
|
|
table.appendChild (tbody);
|
|
msgRow = tr;
|
|
canMergeData = false;
|
|
canCollapseRow = false;
|
|
}
|
|
|
|
addHistory (this, msgRow, canMergeData, canCollapseRow);
|
|
if (isImportant || getAttention)
|
|
{
|
|
setTabState(this, "attention");
|
|
if (client.FLASH_WINDOW)
|
|
window.getAttention();
|
|
}
|
|
else
|
|
{
|
|
if (isSuperfluous)
|
|
setTabState(this, "superfluous");
|
|
else
|
|
setTabState(this, "activity");
|
|
}
|
|
|
|
if (isImportant && client.COPY_MESSAGES)
|
|
{
|
|
if ("network" in o && o.network != this)
|
|
o.network.displayHere("{" + this.name + "} " + message, msgtype,
|
|
sourceObj, destObj);
|
|
}
|
|
|
|
if (("logging" in this) && this.logging)
|
|
{
|
|
try
|
|
{
|
|
this.logFile.write(logText + "\n");
|
|
}
|
|
catch (ex)
|
|
{
|
|
this.logging = false;
|
|
this.localPrefs.clearUserPref("logging");
|
|
this.displayHere(getMsg("cli_ilogMsg7", this.logFile.path),
|
|
"ERROR");
|
|
this.logFile.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
function addHistory (source, obj, mergeData, collapseRow)
|
|
{
|
|
if (!("messages" in source) || (source.messages == null))
|
|
createMessages(source);
|
|
|
|
var tbody = source.messages.firstChild;
|
|
|
|
var needScroll = false;
|
|
|
|
if (client.PRINT_DIRECTION == 1)
|
|
{
|
|
if (mergeData || collapseRow)
|
|
{
|
|
var thisUserCol = obj.firstChild;
|
|
var thisMessageCol = thisUserCol.nextSibling;
|
|
var ci = findPreviousColumnInfo(source.messages);
|
|
var nickColumns = ci.nickColumns;
|
|
var rowExtents = ci.extents;
|
|
var nickColumnCount = nickColumns.length;
|
|
var sameNick = (nickColumnCount > 0 &&
|
|
nickColumns[nickColumnCount - 1].
|
|
getAttribute("msg-user") ==
|
|
thisUserCol.getAttribute("msg-user"));
|
|
var lastRowSpan = (nickColumnCount > 0) ?
|
|
Number(nickColumns[0].getAttribute("rowspan")) : 0;
|
|
if (sameNick && mergeData)
|
|
{
|
|
if (obj.getAttribute("important"))
|
|
{
|
|
nickColumns[nickColumnCount - 1].setAttribute ("important",
|
|
true);
|
|
}
|
|
/* message is from the same person as last time,
|
|
* strip the nick first... */
|
|
obj.removeChild(obj.firstChild);
|
|
/* Adjust height of previous cells, maybe. */
|
|
for (i = 0; i < rowExtents.length - 1; ++i)
|
|
{
|
|
var myLastData =
|
|
rowExtents[i].childNodes[nickColumnCount - 1];
|
|
var myLastRowSpan = (myLastData) ?
|
|
myLastData.getAttribute("rowspan") : 0;
|
|
if (myLastData && myLastRowSpan > 1)
|
|
{
|
|
myLastData.removeAttribute("rowspan");
|
|
}
|
|
}
|
|
/* then add one to the colspan for the previous user columns */
|
|
if (!lastRowSpan)
|
|
lastRowSpan = 1;
|
|
for (var i = 0; i < nickColumns.length; ++i)
|
|
nickColumns[i].setAttribute ("rowspan", lastRowSpan + 1);
|
|
}
|
|
else if (!sameNick && collapseRow && nickColumnCount > 0 &&
|
|
nickColumnCount < client.MAX_MSG_PER_ROW)
|
|
{
|
|
/* message is from a different person, but is elegible to
|
|
* be contained by the previous row. */
|
|
var tr = nickColumns[0].parentNode;
|
|
for (i = 0; i < rowExtents.length; ++i)
|
|
rowExtents[i].lastChild.removeAttribute("colspan");
|
|
obj.firstChild.setAttribute ("rowspan", lastRowSpan);
|
|
tr.appendChild (obj.firstChild);
|
|
var lastColSpan =
|
|
Number(rowExtents[0].lastChild.getAttribute("colspan"));
|
|
obj.lastChild.setAttribute ("colspan", lastColSpan - 2);
|
|
obj.lastChild.setAttribute ("rowspan", lastRowSpan);
|
|
tr.appendChild (obj.lastChild);
|
|
obj = null;
|
|
}
|
|
}
|
|
|
|
if ("frame" in source)
|
|
needScroll = checkScroll (source.frame);
|
|
if (obj)
|
|
tbody.appendChild (obj);
|
|
}
|
|
else
|
|
tbody.insertBefore (obj, source.messages.firstChild);
|
|
|
|
if (source.MAX_MESSAGES)
|
|
{
|
|
if (typeof source.messageCount != "number")
|
|
source.messageCount = 1;
|
|
else
|
|
source.messageCount++;
|
|
|
|
if (source.messageCount > source.MAX_MESSAGES)
|
|
if (client.PRINT_DIRECTION == 1)
|
|
{
|
|
var height = tbody.firstChild.scrollHeight;
|
|
var x = source.frame.contentWindow.pageXOffset;
|
|
var y = source.frame.contentWindow.pageYOffset;
|
|
tbody.removeChild (tbody.firstChild);
|
|
--source.messageCount;
|
|
while (tbody.firstChild &&
|
|
tbody.firstChild.firstChild.getAttribute("class") ==
|
|
"msg-data")
|
|
{
|
|
--source.messageCount;
|
|
tbody.removeChild (tbody.firstChild);
|
|
}
|
|
if (!checkScroll(source.frame) && (y > height))
|
|
source.frame.contentWindow.scrollTo(x, y - height);
|
|
}
|
|
else
|
|
{
|
|
tbody.removeChild (tbody.lastChild);
|
|
--source.messageCount;
|
|
while (tbody.lastChild &&
|
|
tbody.lastChild.firstChild.getAttribute("class") ==
|
|
"msg-data")
|
|
{
|
|
--source.messageCount;
|
|
tbody.removeChild (tbody.lastChild);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needScroll)
|
|
{
|
|
scrollDown(source.frame, true);
|
|
setTimeout (scrollDown, 500, source.frame, false);
|
|
setTimeout (scrollDown, 1000, source.frame, false);
|
|
setTimeout (scrollDown, 2000, source.frame, false);
|
|
}
|
|
}
|
|
|
|
function findPreviousColumnInfo (table)
|
|
{
|
|
var extents = new Array();
|
|
var tr = table.firstChild.lastChild;
|
|
var className = tr ? tr.firstChild.getAttribute("class") : "";
|
|
while (tr && className.search(/msg-user|msg-type|msg-nested-td/) == -1)
|
|
{
|
|
extents.push(tr);
|
|
tr = tr.previousSibling;
|
|
if (tr)
|
|
className = tr.firstChild.getAttribute("class");
|
|
}
|
|
|
|
if (!tr || className != "msg-user")
|
|
return {extents: [], nickColumns: []};
|
|
|
|
extents.push(tr);
|
|
var nickCol = tr.firstChild;
|
|
var nickCols = new Array();
|
|
while (nickCol)
|
|
{
|
|
if (nickCol.getAttribute("class") == "msg-user")
|
|
nickCols.push (nickCol);
|
|
nickCol = nickCol.nextSibling.nextSibling;
|
|
}
|
|
|
|
return {extents: extents, nickColumns: nickCols};
|
|
}
|
|
|
|
client.getConnectionCount =
|
|
function cli_gccount ()
|
|
{
|
|
var count = 0;
|
|
|
|
for (var n in client.networks)
|
|
if (client.networks[n].isConnected())
|
|
++count;
|
|
|
|
return count;
|
|
}
|
|
|
|
client.quit =
|
|
function cli_quit (reason)
|
|
{
|
|
for (var n in client.networks)
|
|
if ("primServ" in client.networks[n])
|
|
client.networks[n].quit (reason);
|
|
}
|
|
|
|
/* gets a tab-complete match for the line of text specified by |line|. wordStart
|
|
* is the position within |line| that starts the word being matched, wordEnd
|
|
* marks the end position. |cursorPos| marks the position of the caret in the
|
|
* textbox.
|
|
*/
|
|
client.performTabMatch =
|
|
function gettabmatch_usr (line, wordStart, wordEnd, word, cursorPos)
|
|
{
|
|
if (wordStart != 0 || line[0] != client.COMMAND_CHAR)
|
|
return null;
|
|
|
|
var matches = client.commands.listNames(word.substr(1));
|
|
if (matches.length == 1 && wordEnd == line.length)
|
|
{
|
|
matches[0] = client.COMMAND_CHAR + matches[0] + " ";
|
|
}
|
|
else
|
|
{
|
|
for (var i in matches)
|
|
matches[i] = client.COMMAND_CHAR + matches[i];
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
CIRCChannel.prototype.performTabMatch =
|
|
CIRCNetwork.prototype.performTabMatch =
|
|
CIRCUser.prototype.performTabMatch =
|
|
function gettabmatch_other (line, wordStart, wordEnd, word, cursorpos)
|
|
{
|
|
if (wordStart == 0 && line[0] == client.COMMAND_CHAR)
|
|
{
|
|
return client.performTabMatch (line, wordStart, wordEnd, word,
|
|
cursorpos);
|
|
}
|
|
|
|
var matchList = new Array();
|
|
var users;
|
|
var channels;
|
|
|
|
var details = getObjectDetails(this);
|
|
|
|
if ("channel" in details && word == details.channel.name[0])
|
|
{
|
|
/* When we have #<tab>, we just want the current channel, if possible. */
|
|
matchList.push (details.channel.unicodeName);
|
|
}
|
|
else
|
|
{
|
|
/* Ok, not #<tab> or no current channel, so get the full list. */
|
|
|
|
if ("users" in details.orig)
|
|
{
|
|
users = details.orig.users;
|
|
for (var n in users)
|
|
matchList.push (users[n].nick);
|
|
}
|
|
|
|
if ("server" in details)
|
|
{
|
|
channels = details.server.channels;
|
|
for (var c in channels)
|
|
matchList.push (channels[c].name);
|
|
}
|
|
}
|
|
|
|
var matches = matchEntry (word, matchList);
|
|
|
|
if (matches.length == 1)
|
|
{
|
|
if (users && matches[0] in users)
|
|
{
|
|
matches[0] = users[matches[0]].properNick;
|
|
if (wordStart == 0)
|
|
matches[0] += client.ADDRESSED_NICK_SEP;
|
|
}
|
|
else if (channels && matches[0] in channels)
|
|
{
|
|
matches[0] = channels[matches[0]].unicodeName;
|
|
}
|
|
|
|
if (wordEnd == line.length)
|
|
{
|
|
/* add a space if the word is at the end of the line. */
|
|
matches[0] += " ";
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the selected nicks from the user-list
|
|
* tree object. This grabs the tree element's
|
|
* selected items, extracts the appropriate text
|
|
* for the nick, promotes each nick to a CIRCChanUser
|
|
* instance and returns an array of these objects.
|
|
*/
|
|
CIRCChannel.prototype.getSelectedUsers =
|
|
function my_getselectedusers ()
|
|
{
|
|
var tree = document.getElementById("user-list");
|
|
var cell; /* reference to each selected cell of the tree object */
|
|
var rv_ary = new Array; /* return value arrray for CIRCChanUser objects */
|
|
|
|
var rangeCount = tree.view.selection.getRangeCount();
|
|
for (var i = 0; i < rangeCount; ++i)
|
|
{
|
|
var start = {}, end = {};
|
|
tree.view.selection.getRangeAt(i, start, end);
|
|
for (var k = start.value; k <= end.value; ++k)
|
|
{
|
|
var item = tree.contentView.getItemAtIndex(k);
|
|
|
|
/* First, set the reference to the XUL element. */
|
|
cell = item.firstChild.childNodes[2];
|
|
|
|
/* Now, create an instance of CIRCChaneUser by passing the text
|
|
* of the cell to the getUser function of this CIRCChannel instance.
|
|
*/
|
|
rv_ary[i] = this.getUser( cell.getAttribute("label") );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* USAGE NOTE: If the return value is non-null, the caller
|
|
* can assume the array is valid, and NOT
|
|
* need to check the length, and vice versa.
|
|
*/
|
|
|
|
return rv_ary.length > 0 ? rv_ary : null;
|
|
|
|
}
|
|
|
|
CIRCChannel.prototype.getGraphResource =
|
|
function my_graphres ()
|
|
{
|
|
if (!("rdfRes" in this))
|
|
{
|
|
this.rdfRes =
|
|
client.rdf.GetResource(RES_PFX + "CHANNEL:" +
|
|
this.parent.parent.name +
|
|
":" + escape(this.name));
|
|
//dd ("created channel resource " + this.rdfRes.Value);
|
|
|
|
}
|
|
|
|
return this.rdfRes;
|
|
}
|
|
|
|
CIRCUser.prototype.getGraphResource =
|
|
function usr_graphres()
|
|
{
|
|
if (this.TYPE != "IRCChanUser")
|
|
dd ("** WARNING: cuser.getGraphResource called on wrong object **");
|
|
|
|
var rdf = client.rdf;
|
|
|
|
if (!("rdfRes" in this))
|
|
{
|
|
if (!("nextResID" in CIRCUser))
|
|
CIRCUser.nextResID = 0;
|
|
|
|
this.rdfRes = rdf.GetResource (RES_PFX + "CUSER:" +
|
|
this.parent.parent.parent.name + ":" +
|
|
escape(this.parent.name) + ":" +
|
|
CIRCUser.nextResID++);
|
|
|
|
//dd ("created cuser resource " + this.rdfRes.Value);
|
|
|
|
rdf.Assert (this.rdfRes, rdf.resNick, rdf.GetLiteral(this.properNick));
|
|
if (this.name)
|
|
rdf.Assert (this.rdfRes, rdf.resUser, rdf.GetLiteral(this.name));
|
|
else
|
|
rdf.Assert (this.rdfRes, rdf.resUser, rdf.litUnk);
|
|
if (this.host)
|
|
rdf.Assert (this.rdfRes, rdf.resHost, rdf.GetLiteral(this.host));
|
|
else
|
|
rdf.Assert (this.rdfRes, rdf.resHost, rdf.litUnk);
|
|
|
|
rdf.Assert (this.rdfRes, rdf.resOp,
|
|
this.isOp ? rdf.litTrue : rdf.litFalse);
|
|
rdf.Assert (this.rdfRes, rdf.resVoice,
|
|
this.isVoice ? rdf.litTrue : rdf.litFalse);
|
|
}
|
|
|
|
return this.rdfRes;
|
|
|
|
}
|
|
|
|
CIRCUser.prototype.updateGraphResource =
|
|
function usr_updres()
|
|
{
|
|
if (this.TYPE != "IRCChanUser")
|
|
dd ("** WARNING: cuser.updateGraphResource called on wrong object **");
|
|
|
|
if (!("rdfRes" in this))
|
|
this.getGraphResource();
|
|
|
|
var rdf = client.rdf;
|
|
|
|
rdf.Change (this.rdfRes, rdf.resNick, rdf.GetLiteral(this.properNick));
|
|
if (this.name)
|
|
rdf.Change (this.rdfRes, rdf.resUser, rdf.GetLiteral(this.name));
|
|
else
|
|
rdf.Change (this.rdfRes, rdf.resUser, rdf.litUnk);
|
|
if (this.host)
|
|
rdf.Change (this.rdfRes, rdf.resHost, rdf.GetLiteral(this.host));
|
|
else
|
|
rdf.Change (this.rdfRes, rdf.resHost, rdf.litUnk);
|
|
|
|
rdf.Change (this.rdfRes, rdf.resOp,
|
|
this.isOp ? rdf.litTrue : rdf.litFalse);
|
|
rdf.Change (this.rdfRes, rdf.resVoice,
|
|
this.isVoice ? rdf.litTrue : rdf.litFalse);
|
|
}
|