land 0.9.49, adds halfop support, initial rplisupport support, and properly masks password dialogs

a=asa, b=227334
This commit is contained in:
rginda%hacksrus.com 2003-12-04 15:35:22 +00:00
parent 782916cdec
commit a18a81d561
8 changed files with 386 additions and 79 deletions

View File

@ -72,6 +72,8 @@ chatzilla.jar:
skin/modern/chatzilla/images/no-graphic.png (xul/skin/images/no-graphic.png)
skin/modern/chatzilla/images/op-symbol.png (xul/skin/images/op-symbol.png)
skin/modern/chatzilla/images/op-graphic.png (xul/skin/images/op-graphic.png)
skin/modern/chatzilla/images/halfop-symbol.png (xul/skin/images/halfop-symbol.png)
skin/modern/chatzilla/images/halfop-graphic.png (xul/skin/images/halfop-graphic.png)
skin/modern/chatzilla/images/voice-symbol.png (xul/skin/images/voice-symbol.png)
skin/modern/chatzilla/images/voice-graphic.png (xul/skin/images/voice-graphic.png)
skin/modern/chatzilla/images/taskbar-irc.gif (xul/skin/images/taskbar-irc.gif)

View File

@ -273,6 +273,10 @@ function CIRCServer (parent, hostname, port, password)
s.savedLine = "";
s.lag = -1;
s.usersStable = true;
s.supports = null;
s.channelTypes = null;
s.channelModes = null;
s.userModes = null;
parent.servers[serverName] = s;
if ("onInit" in s)
@ -883,6 +887,39 @@ function serv_001 (e)
e.server.me.changeNick(e.params[1]);
}
/* Set up supports defaults here.
* This is so that we don't waste /huge/ amounts of RAM for the network's
* servers just because we know about them. Until we connect, that is.
* These defaults are taken from the draft 005 RPL_ISUPPORTS here:
* http://www.ietf.org/internet-drafts/draft-brocklesby-irc-isupport-02.txt
*/
this.supports = new Object();
this.supports.modes = 3;
this.supports.maxchannels = 10;
this.supports.nicklen = 9;
this.supports.casemapping = "rfc1459";
this.supports.channellen = 200;
this.supports.chidlen = 5;
/* Make sure it's possible to tell if we've actually got a 005 message. */
this.supports.rpl_isupport = false;
this.channelTypes = { '#': true, '&': true };
/* This next one isn't in the isupport draft, but instead is defaulting to
* the codes we understand. It should be noted, some servers include the
* mode characters (o, h, v) in the 'a' list, although the draft spec says
* they should be treated as type 'b'. Luckly, in practise this doesn't
* matter, since both 'a' and 'b' types always take a parameter in the
* MODE message, and parsing is not affected. */
this.channelModes = {
a: ['b'],
b: ['k'],
c: ['l'],
d: ['i', 'm', 'n', 'p', 's', 't']
};
this.userModes = [
{ mode: 'o', symbol: '@' },
{ mode: 'v', symbol: '+' }
];
if (this.parent.INITIAL_UMODE)
{
e.server.sendData("mode " + e.server.me.nick + " :" +
@ -901,6 +938,97 @@ function serv_001 (e)
}
/* server features */
CIRCServer.prototype.on005 =
function serv_005 (e)
{
/* Drop params 0 and 1. */
for (var i = 2; i < e.params.length; i++) {
var itemStr = e.params[i];
/* Items may be of the forms:
* NAME
* -NAME
* NAME=value
* Value may be empty on occasion.
* No value allowed for -NAME items.
*/
var item = itemStr.match(/^(-?)([A-Z]+)(=(.*))?$/i);
if (! item)
continue;
var name = item[2].toLowerCase();
if (("3" in item) && item[3])
{
// And other items are stored as-is, though numeric items
// get special treatment to make our life easier later.
if (("4" in item) && item[4].match(/^\d+$/))
this.supports[name] = Number(item[4]);
else
this.supports[name] = item[4];
}
else
{
// Boolean-type items stored as 'true'.
this.supports[name] = !(("1" in item) && item[1] == "-");
}
}
// Supported 'special' items:
// CHANTYPES (--> channelTypes{}),
// PREFIX (--> userModes[{mode,symbol}]),
// CHANMODES (--> channelModes{a:[], b:[], c:[], d:[]}).
if ("chantypes" in this.supports)
{
this.channelTypes = [];
for (var m = 0; m < this.supports.chantypes.length; m++)
this.channelTypes[this.supports.chantypes[m]] = true;
}
if ("prefix" in this.supports)
{
var mlist = this.supports.prefix.match(/^\((.*)\)(.*)$/i);
if ((! mlist) || (mlist[1].length != mlist[2].length))
{
dd ("** Malformed PREFIX entry in 005 SUPPORTS message **");
}
else
{
this.userModes = [];
for (var m = 0; m < mlist[1].length; m++)
this.userModes.push( { mode: mlist[1][m],
symbol: mlist[2][m] } );
}
}
if ("chanmodes" in this.supports)
{
var cmlist = this.supports.chanmodes.split(/,/);
if ((!cmlist) || (cmlist.length < 4))
{
dd ("** Malformed CHANMODES entry in 005 SUPPORTS message **");
}
else
{
// 4 types - list, set-unset-param, set-only-param, flag.
this.channelModes = {
a: cmlist[0].split(''),
b: cmlist[1].split(''),
c: cmlist[2].split(''),
d: cmlist[3].split('')
};
}
}
this.supports.rpl_isupport = true;
e.destObject = this.parent;
e.set = "network";
return true;
}
/* TOPIC reply */
CIRCServer.prototype.on332 =
function serv_332 (e)
@ -1002,6 +1130,7 @@ function serv_353 (e)
e.set = "channel";
var nicks = e.params[4].split (" ");
var mList = this.userModes;
for (var n in nicks)
{
@ -1009,24 +1138,20 @@ function serv_353 (e)
if (nick == "")
break;
switch (nick[0])
var found = false;
for (var m in mList)
{
case "@":
if (nick[0] == mList[m].symbol)
{
e.user = new CIRCChanUser (e.channel,
nick.substr(1, nick.length),
true, (void 0));
break;
case "+":
e.user = new CIRCChanUser (e.channel,
nick.substr(1, nick.length),
(void 0), true);
break;
default:
e.user = new CIRCChanUser (e.channel, nick);
[ mList[m].mode ]);
found = true;
break;
}
}
if (!found)
e.user = new CIRCChanUser (e.channel, nick, [ ]);
}
@ -1134,54 +1259,37 @@ function serv_chanmode (e)
var nick;
var user;
var mList = this.userModes;
for (var i = 0; i < mode_str.length ; i++)
{
/* Take care of modifier first. */
if ((mode_str[i] == '+') || (mode_str[i] == '-'))
{
modifier = mode_str[i];
continue;
}
var done = false;
for (var m in mList)
{
if ((mode_str[i] == mList[m].mode) && (modifier != ""))
{
nick = e.params[BASE_PARAM + params_eaten];
user = new CIRCChanUser (e.channel, nick,
[ modifier + mList[m].mode ]);
params_eaten++;
e.usersAffected.push (user);
done = true;
break;
}
}
if (done)
continue;
switch (mode_str[i])
{
case "+":
case "-":
modifier = mode_str[i];
break;
/* user modes */
case "o": /* operator */
if (modifier == "+")
{
nick = e.params[BASE_PARAM + params_eaten];
user = new CIRCChanUser (e.channel, nick, true);
params_eaten++;
e.usersAffected.push (user);
}
else
if (modifier == "-")
{
nick = e.params[BASE_PARAM + params_eaten];
user = new CIRCChanUser (e.channel, nick, false);
params_eaten++;
e.usersAffected.push (user);
}
break;
case "v": /* voice */
if (modifier == "+")
{
nick = e.params[BASE_PARAM + params_eaten];
user = new CIRCChanUser (e.channel, nick, (void 0), true);
params_eaten++;
e.usersAffected.push (user);
}
else
if (modifier == "-")
{
nick = e.params[BASE_PARAM + params_eaten];
user = new CIRCChanUser (e.channel, nick, (void 0),
false);
params_eaten++;
e.usersAffected.push (user);
}
break;
case "b": /* ban */
var ban = e.params[BASE_PARAM + params_eaten];
params_eaten++;
@ -1747,9 +1855,9 @@ function chan_geturl ()
}
CIRCChannel.prototype.addUser =
function chan_adduser (nick, isOp, isVoice)
function chan_adduser (nick, modes)
{
return new CIRCChanUser (this, nick, isOp, isVoice);
return new CIRCChanUser (this, nick, modes);
}
CIRCChannel.prototype.getUser =
@ -1770,21 +1878,34 @@ function chan_removeuser (nick)
}
CIRCChannel.prototype.getUsersLength =
function chan_userslen ()
function chan_userslen (mode)
{
var i = 0;
var p;
this.opCount = 0;
this.halfopCount = 0;
this.voiceCount = 0;
for (var p in this.users)
if (typeof mode == "undefined")
{
if (this.users[p].isOp)
++this.opCount;
else if (this.users[p].isVoice)
++this.voiceCount;
i++;
for (p in this.users)
{
if (this.users[p].isOp)
this.opCount++;
if (this.users[p].isHalfOp)
this.halfopCount++;
if (this.users[p].isVoice)
this.voiceCount++;
i++;
}
}
else
{
for (p in this.users)
if (arrayContains(this.users[p].modes, mode))
i++;
}
return i;
}
@ -2145,7 +2266,7 @@ function usr_whois ()
/*
* channel user
*/
function CIRCChanUser (parent, nick, isOp, isVoice)
function CIRCChanUser (parent, nick, modes)
{
var properNick = nick;
nick = nick.toLowerCase();
@ -2153,8 +2274,46 @@ function CIRCChanUser (parent, nick, isOp, isVoice)
if (nick in parent.users)
{
var existingUser = parent.users[nick];
if (typeof isOp != "undefined") existingUser.isOp = isOp;
if (typeof isVoice != "undefined") existingUser.isVoice = isVoice;
if (modes)
{
// If we start with a single character mode, assume we're replacing
// the list. (i.e. the list is either all +/- modes, or all normal)
if ((modes.length >= 1) && (modes[0].search(/^[-+]/) == -1))
{
// Modes, but no +/- prefixes, so *replace* mode list.
existingUser.modes = modes;
}
else
{
// We have a +/- mode list, so carefully update the mode list.
for (var m in modes)
{
// This will remove '-' modes, and all other modes will be
// added.
var mode = modes[m][1];
if (modes[m][0] == "-")
{
if (arrayContains(existingUser.modes, mode))
{
var i = arrayIndexOf(existingUser.modes, mode);
arrayRemoveAt(existingUser.modes, i);
}
}
else
{
if (!arrayContains(existingUser.modes, mode))
existingUser.modes.push(mode);
}
}
}
}
existingUser.isOp = (arrayContains(existingUser.modes, "o")) ?
true : false;
existingUser.isHalfOp = (arrayContains(existingUser.modes, "h")) ?
true : false;
existingUser.isVoice = (arrayContains(existingUser.modes, "v")) ?
true : false;
return existingUser;
}
@ -2163,6 +2322,7 @@ function CIRCChanUser (parent, nick, isOp, isVoice)
this.__proto__ = protoUser;
this.getURL = cusr_geturl;
this.setOp = cusr_setop;
this.setHalfOp = cusr_sethalfop;
this.setVoice = cusr_setvoice;
this.setBan = cusr_setban;
this.kick = cusr_kick;
@ -2172,10 +2332,15 @@ function CIRCChanUser (parent, nick, isOp, isVoice)
this.act = cusr_act;
this.whois = cusr_whois;
this.parent = parent;
this.isOp = (typeof isOp != "undefined") ? isOp : false;
this.isVoice = (typeof isVoice != "undefined") ? isVoice : false;
this.TYPE = "IRCChanUser";
this.modes = new Array();
if (typeof modes != "undefined")
this.modes = modes;
this.isOp = (arrayContains(this.modes, "o")) ? true : false;
this.isHalfOp = (arrayContains(this.modes, "h")) ? true : false;
this.isVoice = (arrayContains(this.modes, "v")) ? true : false;
parent.users[nick] = this;
return this;
@ -2200,6 +2365,20 @@ function cusr_setop (f)
return true;
}
function cusr_sethalfop (f)
{
var server = this.parent.parent;
var me = server.me;
if (!this.parent.users[me.nick].isOp)
return false;
var modifier = (f) ? " +h " : " -h ";
server.sendData("MODE " + this.parent.name + modifier + this.nick + "\n");
return true;
}
function cusr_setvoice (f)
{
var server = this.parent.parent;

View File

@ -63,6 +63,7 @@
<bindings>
<binding subject="?member" predicate="http://home.netscape.com/NC-irc#sortname" object="?sortname"/>
<binding subject="?member" predicate="http://home.netscape.com/NC-irc#op" object="?op"/>
<binding subject="?member" predicate="http://home.netscape.com/NC-irc#halfop" object="?halfop"/>
<binding subject="?member" predicate="http://home.netscape.com/NC-irc#voice" object="?voice"/>
<binding subject="?member" predicate="http://home.netscape.com/NC-irc#nick" object="?nick"/>
</bindings>
@ -70,9 +71,9 @@
<action>
<treechildren>
<treeitem uri="?member" flex="1"
properties="op-?op voice-?voice">
properties="op-?op halfop-?halfop voice-?voice">
<treerow>
<treecell properties="op-?op voice-?voice"
<treecell properties="op-?op halfop-?halfop voice-?voice"
label="?nick"/>
</treerow>
</treeitem>
@ -83,7 +84,7 @@
<treecols>
<treecol id="usercol" hideheader="true" flex="1"
properties="op-?op voice-?voice"/>
properties="op-?op halfop-?halfop voice-?voice"/>
</treecols>
</tree>

View File

@ -55,6 +55,8 @@ function initCommands()
["channel-pref", cmdPref, CMD_NEED_CHAN | CMD_CONSOLE],
["op", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE],
["deop", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE],
["hop", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE],
["dehop", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE],
["voice", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE],
["devoice", cmdChanUserMode, CMD_NEED_CHAN | CMD_CONSOLE],
["clear-view", cmdClearView, CMD_CONSOLE],
@ -106,6 +108,7 @@ function initCommands()
["server", cmdServer, CMD_CONSOLE],
["squery", cmdSquery, CMD_NEED_SRV | CMD_CONSOLE],
["stalk", cmdStalk, CMD_CONSOLE],
["supports", cmdSupports, CMD_NEED_SRV | CMD_CONSOLE],
["sync-headers", cmdSync, 0],
["sync-logs", cmdSync, 0],
["sync-motifs", cmdSync, 0],
@ -543,6 +546,14 @@ function cmdChanUserMode(e)
modestr = "-oooo";
break;
case "hop":
modestr = "+hhhh";
break;
case "dehop":
modestr = "-hhhh";
break;
case "voice":
modestr = "+vvvv";
break;
@ -2030,3 +2041,61 @@ function cmdLog(e)
display(MSG_LOGGING_OFF);
}
}
function cmdSupports(e)
{
var server = e.server;
var data = server.supports;
if ("channelTypes" in server)
display(getMsg(MSG_SUPPORTS_CHANTYPES,
keys(server.channelTypes).join(MSG_COMMASP)));
if ("channelModes" in server)
{
display(getMsg(MSG_SUPPORTS_CHANMODESA,
server.channelModes.a.join(MSG_COMMASP)));
display(getMsg(MSG_SUPPORTS_CHANMODESB,
server.channelModes.b.join(MSG_COMMASP)));
display(getMsg(MSG_SUPPORTS_CHANMODESC,
server.channelModes.c.join(MSG_COMMASP)));
display(getMsg(MSG_SUPPORTS_CHANMODESD,
server.channelModes.d.join(MSG_COMMASP)));
}
if ("userModes" in server)
{
var list = new Array();
for (var m in server.userModes)
{
list.push(getMsg(MSG_SUPPORTS_USERMODE, [
server.userModes[m].mode,
server.userModes[m].symbol
]));
}
display(getMsg(MSG_SUPPORTS_USERMODES, list.join(MSG_COMMASP)));
}
var listB1 = new Array();
var listB2 = new Array();
var listN = new Array();
for (var k in data)
{
if (typeof data[k] == "boolean")
{
if (data[k])
listB1.push(k);
else
listB2.push(k);
}
else
{
listN.push(getMsg(MSG_SUPPORTS_MISCOPTION, [ k, data[k] ] ));
}
}
listB1.sort();
listB2.sort();
listN.sort();
display(getMsg(MSG_SUPPORTS_FLAGSON, listB1.join(MSG_COMMASP)));
display(getMsg(MSG_SUPPORTS_FLAGSOFF, listB2.join(MSG_COMMASP)));
display(getMsg(MSG_SUPPORTS_MISCOPTIONS, listN.join(MSG_COMMASP)));
}

View File

@ -60,6 +60,7 @@ function RDFHelper()
this.resChanUser = this.svc.GetResource (RES_PFX + "chanuser");
this.resSortName = this.svc.GetResource (RES_PFX + "sortname");
this.resOp = this.svc.GetResource (RES_PFX + "op");
this.resHalfOp = this.svc.GetResource (RES_PFX + "halfop");
this.resVoice = this.svc.GetResource (RES_PFX + "voice");
this.resNick = this.svc.GetResource (RES_PFX + "nick");
this.resUser = this.svc.GetResource (RES_PFX + "user");
@ -71,6 +72,7 @@ function RDFHelper()
this.litUnk = this.svc.GetLiteral ("");
this.ds.Assert (this.resNullUser, this.resOp, this.litFalse, true);
this.ds.Assert (this.resNullUser, this.resHalfOp, this.litFalse, true);
this.ds.Assert (this.resNullUser, this.resVoice, this.litFalse, true);
this.ds.Assert (this.resNullUser, this.resNick, this.litUnk, true);
this.ds.Assert (this.resNullUser, this.resUser, this.litUnk, true);

View File

@ -34,7 +34,7 @@
* Samuel Sieb, samuel@sieb.net, MIRC color codes, munger menu, and various
*/
const __cz_version = "0.9.47";
const __cz_version = "0.9.49";
const __cz_condition = "green";
var warn;
@ -624,19 +624,23 @@ function mircChangeColor (colorInfo, containerTag, data)
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 (arrayHasElementAt(ary, 4))
@ -644,13 +648,16 @@ function mircChangeColor (colorInfo, containerTag, data)
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;
}
@ -3154,6 +3161,7 @@ function usr_graphres()
rdf.Assert (this.rdfRes, rdf.resHost, rdf.litUnk);
rdf.Assert (this.rdfRes, rdf.resSortName, rdf.litUnk);
rdf.Assert (this.rdfRes, rdf.resOp, rdf.litUnk);
rdf.Assert (this.rdfRes, rdf.resHalfOp, rdf.litUnk);
rdf.Assert (this.rdfRes, rdf.resVoice, rdf.litUnk);
this.updateGraphResource();
}
@ -3191,16 +3199,20 @@ function usr_updres()
var sortname;
if (this.isOp)
sortname = "o-";
sortname = "a-";
else if (this.isHalfOp)
sortname = "b-";
else if (this.isVoice)
sortname = "v-";
sortname = "c-";
else
sortname = "x-";
sortname = "d-";
sortname += this.nick;
rdf.Change (this.rdfRes, rdf.resSortName, rdf.GetLiteral(sortname));
rdf.Change (this.rdfRes, rdf.resOp,
this.isOp ? rdf.litTrue : rdf.litFalse);
rdf.Change (this.rdfRes, rdf.resHalfOp,
this.isHalfOp ? rdf.litTrue : rdf.litFalse);
rdf.Change (this.rdfRes, rdf.resVoice,
this.isVoice ? rdf.litTrue : rdf.litFalse);
}

View File

@ -82,6 +82,11 @@ cmd.delete-view.label = &Close Tab
cmd.delete-view.params = [<view>]
cmd.delete-view.help = Clear the current view, discarding *all* content, and drop its icon from the tab strip.
cmd.dehop.format = Remove Half-operator Status from $nickname
cmd.dehop.label = Remove Half-operator Status
cmd.dehop.params = <nickname> [<...>]
cmd.dehop.help = Removes half-operator status from <nickname> on current channel. Requires operator status.
cmd.deop.format = Remove Operator Status from $nickname
cmd.deop.label = Remove Operator Status
cmd.deop.params = <nickname> [<...>]
@ -93,7 +98,7 @@ cmd.desc.help = Changes the 'ircname' line returned when someone performs a /w
cmd.devoice.format = Remove Voice Status from $nickname
cmd.devoice.label = Remove Voice Status
cmd.devoice.params = <nickname> [<...>]
cmd.devoice.help = Removes voice status from <nickname> on current channel. Requires operator status.
cmd.devoice.help = Removes voice status from <nickname> on current channel. Requires operator (or half-operator) status.
cmd.disconnect.format = Disconnect From $networkName
cmd.disconnect.label = Disconnect
@ -136,6 +141,11 @@ cmd.help.help = Displays help on all commands matching <pattern>, if <pattern>
cmd.hide-view.params = [<view>]
cmd.hide-view.help = Drop the current view's icon from the tab strip, but save its contents. The icon will reappear the next time there is activity on the view.
cmd.hop.format = Give Half-operator Status to $nickname
cmd.hop.label = Give Half-perator Status
cmd.hop.params = <nickname> [<...>]
cmd.hop.help = Gives half-operator status to <nickname> on current channel. Requires operator status.
cmd.toggle-ui.params = <thing>
cmd.toggle-ui.help = Toggles the visibility of various pieces of the user interface. <thing> must be one of: tabstrip, userlist, header, status.
@ -293,6 +303,8 @@ cmd.status.help = Shows status information for the current view.
cmd.statusbar.help = Toggles the visibility of the status bar.
cmd.supports.help = Lists the capabilities of the current server, as reported by the 005 numeric.
cmd.testdisplay.help = Displays a sample text. Used to preview styles.
cmd.topic.params = [<new-topic>]
@ -326,7 +338,7 @@ cmd.version.help = Asks <nickname> what irc client they're running. Their IRC
cmd.voice.format = Give Voice Status to $nickname
cmd.voice.label = Give Voice Status
cmd.voice.params = <nickname> [<...>]
cmd.voice.help = Gives voice status to <nickname> on current channel. Requires operator status.
cmd.voice.help = Gives voice status to <nickname> on current channel. Requires operator (or half-operator) status.
cmd.who.params = <pattern>
cmd.who.help = List users who have name, host, or description information matching <pattern>.
@ -627,6 +639,19 @@ msg.someone.quit = %S has left %S (%S)
msg.unknown.ctcp = Unknown CTCP %S (%S) from %S
msg.supports.chanTypes = Supported channel types: %S.
msg.supports.chanModesA = Supported channel modes (A: lists): %S.
msg.supports.chanModesB = Supported channel modes (B: param): %S.
msg.supports.chanModesC = Supported channel modes (C: on-param): %S.
msg.supports.chanModesD = Supported channel modes (D: boolean): %S.
msg.supports.userMode = %S (%S)
msg.supports.userModes = Supported channel user modes: %S.
msg.supports.flagsOn = This server DOES support: %S.
msg.supports.flagsOff = This server DOESN'T support: %S.
msg.supports.miscOption = %S=%S
msg.supports.miscOptions = Server settings/limits: %S.
# pref-irc-appearance.js
file_browse_CSS=Choose a Cascading Stylesheet (CSS) file
file_browse_CSS_spec=Cascading Stylesheet files (*.css)

View File

@ -180,6 +180,23 @@ treechildren::-moz-tree-image(voice-true) {
list-style-image: url(chrome://chatzilla/skin/images/voice-graphic.png);
}
/* half-chanop */
treechildren:-moz-tree-image(halfop-true) {
list-style-image: url(chrome://chatzilla/skin/images/halfop-symbol.png);
}
treechildren::-moz-tree-image(halfop-true) {
list-style-image: url(chrome://chatzilla/skin/images/halfop-symbol.png);
}
#user-list[mode="graphic"] treechildren:-moz-tree-image(halfop-true) {
list-style-image: url(chrome://chatzilla/skin/images/halfop-graphic.png);
}
#user-list[mode="graphic"] treechildren::-moz-tree-image(halfop-true) {
list-style-image: url(chrome://chatzilla/skin/images/halfop-graphic.png);
}
/* chanop */
treechildren:-moz-tree-image(op-true) {
list-style-image: url(chrome://chatzilla/skin/images/op-symbol.png);