suite/chatzilla/content/output-window.js (257 lines of code) (raw):

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ var initialized = false; var view; var client; var mainWindow; var clickHandler; var dd; var getMsg; var getObjectDetails; var header = null; var headers = { IRCClient: { prefix: "cli-", fields: [ "container", "netcount", "version-container", "version", "connectcount", ], update: updateClient, }, IRCNetwork: { prefix: "net-", fields: ["container", "url-anchor", "status", "lag"], update: updateNetwork, }, IRCChannel: { prefix: "ch-", fields: [ "container", "url-anchor", "modestr", "usercount", "topicnodes", "topicinput", "topiccancel", ], update: updateChannel, }, IRCUser: { prefix: "usr-", fields: ["container", "url-anchor", "serverstr", "title", "descnodes"], update: updateUser, }, IRCDCCChat: { prefix: "dcc-chat-", fields: ["container", "remotestr", "title"], update: updateDCCChat, }, IRCDCCFileTransfer: { prefix: "dcc-file-", fields: ["container", "file", "progress", "progressbar"], update: updateDCCFile, }, }; var initOutputWindow = stock_initOutputWindow; function stock_initOutputWindow(newClient, newView, newClickHandler) { function initHeader() { /* it's better if we wait a half a second before poking at these * dom nodes. */ setHeaderState(view.prefs.displayHeader); updateHeader(); var div = document.getElementById("messages-outer"); div.removeAttribute("hidden"); window.scrollTo(0, window.document.body.clientHeight); } client = newClient; view = newView; clickHandler = newClickHandler; mainWindow = client.mainWindow; client.messageManager.importBundle(client.defaultBundle, window); getMsg = mainWindow.getMsg; getObjectDetails = mainWindow.getObjectDetails; dd = mainWindow.dd; changeCSS(view.prefs["motif.current"]); updateMotifSettings(); var output = document.getElementById("output"); output.appendChild(adoptNode(view.messages)); if (view.TYPE in headers) { header = cacheNodes(headers[view.TYPE].prefix, headers[view.TYPE].fields); // Turn off accessibility announcements: they're useless as all these // changes are in the "log" as well, normally. // We're setting the attribute here instead of in the HTML to cope with // custom output windows and so we set it only on the Right header // for this view. header.container.setAttribute("aria-live", "off"); header.update = headers[view.TYPE].update; } var name; if ("unicodeName" in view) { name = view.unicodeName; } else { name = view.name; } updateSplash(name); setTimeout(initHeader, 500); initialized = true; } function onTopicNodesClick(e) { if (!clickHandler(e)) { if (e.which != 1) { return; } startTopicEdit(); } e.stopPropagation(); } function onTopicKeypress(e) { switch (e.keyCode) { case 13 /* enter */: var topic = header.topicinput.value; topic = mainWindow.replaceColorCodes(topic); view.setTopic(topic); cancelTopicEdit(true); view.dispatch("focus-input"); break; case 27 /* esc */: cancelTopicEdit(true); view.dispatch("focus-input"); break; default: client.mainWindow.onInputKeyPress(e); } } function startTopicEdit() { var me = view.getUser(view.parent.me.unicodeName); if ( !me || (!view.mode.publicTopic && !me.isOp && !me.isHalfOp) || !hasAttribute("topicinput", "hidden") ) { return; } header.topicinput.value = mainWindow.decodeColorCodes(view.topic); header.topicnodes.setAttribute("hidden", "true"); header.topicinput.removeAttribute("hidden"); header.topiccancel.removeAttribute("hidden"); header.topicinput.focus(); header.topicinput.selectionStart = 0; } function cancelTopicEdit(force) { var originalTopic = mainWindow.decodeColorCodes(view.topic); if ( !hasAttribute("topicnodes", "hidden") || (!force && header.topicinput.value != originalTopic) ) { return; } header.topicinput.setAttribute("hidden", "true"); header.topiccancel.setAttribute("hidden", "true"); header.topicnodes.removeAttribute("hidden"); } function cacheNodes(pfx, ary, nodes) { if (!nodes) { nodes = {}; } for (var i = 0; i < ary.length; ++i) { nodes[ary[i]] = document.getElementById(pfx + ary[i]); } return nodes; } function changeCSS(url, id) { if (!id) { id = "main-css"; } var node = document.getElementById(id); if (!node) { node = document.createElement("link"); node.setAttribute("id", id); node.setAttribute("rel", "stylesheet"); node.setAttribute("type", "text/css"); var head = document.getElementsByTagName("head")[0]; head.appendChild(node); } else if (node.getAttribute("href") == url) { return; } node.setAttribute("href", url); window.scrollTo(0, window.document.body.clientHeight); } function scrollToElement(element, position) { /* The following values can be used for element: * selection - current selected text. * marker - the activity marker. * [any DOM node] - anything :) * * The following values can be used for position: * top - scroll so it is at the top. * center - scroll so it is in the middle. * bottom - scroll so it is at the bottom. * inview - scroll so it is in view. */ switch (element) { case "selection": var sel = window.getSelection(); if (sel) { element = sel.anchorNode; } else { element = null; } break; case "marker": if ("getActivityMarker" in view) { element = view.getActivityMarker(); } else { element = null; } break; } if (!element) { return; } // Calculate element's position in document. var pos = { top: 0, center: 0, bottom: 0 }; // Find first parent with offset data. while (element && !("offsetParent" in element)) { element = element.parentNode; } var elt = element; // Calc total offset data. while (elt) { pos.top += 0 + elt.offsetTop; elt = elt.offsetParent; } pos.center = pos.top + element.offsetHeight / 2; pos.bottom = pos.top + element.offsetHeight; // Store the positions to align the element with. var cont = { top: 0, center: window.innerHeight / 2, bottom: window.innerHeight, }; if (!hasAttribute("container", "hidden")) { /* Offset height doesn't include the margins, so we get to do that * ourselves via getComputedStyle(). We're assuming that will return * a px value, which is all but guaranteed. */ var headerHeight = header.container.offsetHeight; var css = getComputedStyle(header.container, null); headerHeight += parseInt(css.marginTop) + parseInt(css.marginBottom); cont.top += headerHeight; cont.center += headerHeight / 2; } // Pick between 'top' and 'bottom' for 'inview' position. if (position == "inview") { if (pos.top - window.scrollY < cont.top) { position = "top"; } else if (pos.bottom - window.scrollY > cont.bottom) { position = "bottom"; } else { return; } } window.scrollTo(0, pos[position] - cont[position]); } function updateMotifSettings(existingTimeout) { // Try... catch with a repeat to cope with the style sheet not being loaded const TIMEOUT = 100; try { existingTimeout += TIMEOUT; view.motifSettings = getMotifSettings(); } catch (ex) { if (existingTimeout >= 30000) { // Stop after trying for 30 seconds return; } if (ex.name == "NS_ERROR_DOM_INVALID_ACCESS_ERR") { //not ready, try again setTimeout(updateMotifSettings, TIMEOUT, existingTimeout); } // something else, panic! else { dd(ex); } } } function getMotifSettings() { var re = new RegExp("czsettings\\.(\\w*)", "i"); var rules = document.getElementById("main-css").sheet.cssRules; var rv = {}; var ary; // Copy any settings, which are available in the motif using the // "CZSETTINGS" selector. We only store the regexp match after checking // the rule type because selectorText is not defined on other rule types. for (var i = 0; i < rules.length; i++) { if ( rules[i].type == CSSRule.STYLE_RULE && (ary = rules[i].selectorText.match(re)) != null ) { rv[ary[1]] = true; } } return rv; } function adoptNode(node) { return client.adoptNode(node, document); } function setText(field, text, checkCondition) { if (!header[field].firstChild) { header[field].appendChild(document.createTextNode("")); } if (typeof text != "string") { text = MSG_UNKNOWN; if (checkCondition) { setAttribute(field, "condition", "red"); } } else if (checkCondition) { setAttribute(field, "condition", "green"); } header[field].firstChild.data = text; } function setAttribute(field, name, value) { if (!value) { value = "true"; } header[field].setAttribute(name, value); } function removeAttribute(field, name) { header[field].removeAttribute(name); } function hasAttribute(field, name) { return header[field].hasAttribute(name); } function setHeaderState(state) { if (header) { if (state) { removeAttribute("container", "hidden"); updateHeader(); } else { setAttribute("container", "hidden"); } } } function updateHeader() { document.title = view.getURL(); if (!header || hasAttribute("container", "hidden")) { return; } for (var id in header) { var value; if (id == "url-anchor") { value = view.getURL(); setAttribute("url-anchor", "href", value); setText("url-anchor", value); } else if (id in view) { setText(id, view[id]); } } if (header.update) { header.update(); } } function updateClient() { var n = 0, c = 0; for (name in client.networks) { ++n; if (client.networks[name].isConnected()) { ++c; } } setAttribute("version-container", "title", client.userAgent); setText("version", client.version); setText("netcount", String(n)); setText("connectcount", String(c)); } function updateNetwork() { if (view.state == mainWindow.NET_CONNECTING) { setText("status", MSG_CONNECTING); setAttribute("status", "condition", "yellow"); removeAttribute("status", "title"); setText("lag", MSG_UNKNOWN); } else if (view.isConnected()) { setText("status", MSG_CONNECTED); setAttribute("status", "condition", "green"); setAttribute( "status", "title", getMsg(MSG_CONNECT_VIA, view.primServ.unicodeName) ); var lag = view.primServ.lag; if (lag != -1) { setText("lag", getMsg(MSG_FMT_SECONDS, lag.toFixed(2))); } else { setText("lag", MSG_UNKNOWN); } } else { setText("status", MSG_DISCONNECTED); setAttribute("status", "condition", "red"); removeAttribute("status", "title"); setText("lag", MSG_UNKNOWN); } } function updateChannel() { if (header.topicnodes.firstChild) { header.topicnodes.firstChild.remove(); } if (view.active) { var str = view.mode.getModeStr(); if (!str) { str = MSG_NO_MODE; } setText("modestr", str); setAttribute("modestr", "condition", "green"); setText( "usercount", getMsg(MSG_FMT_USERCOUNT, [ view.getUsersLength(), view.opCount, view.halfopCount, view.voiceCount, ]) ); setAttribute("usercount", "condition", "green"); if (view.topic) { var data = getObjectDetails(view); data.dontLogURLs = true; var mailto = client.prefs["munger.mailto"]; client.munger.getRule(".mailto").enabled = mailto; var nodes = client.munger.munge(view.topic, null, data); client.munger.getRule(".mailto").enabled = false; header.topicnodes.appendChild(adoptNode(nodes)); } else { setText("topicnodes", MSG_NONE); } } else { setText("modestr", MSG_UNKNOWN); setAttribute("modestr", "condition", "red"); setText("usercount", MSG_UNKNOWN); setAttribute("usercount", "condition", "red"); setText("topicnodes", MSG_UNKNOWN); } } function updateUser() { var source; if (view.name) { source = "<" + view.name + "@" + view.host + ">"; } else { source = MSG_UNKNOWN; } if (view.parent.isConnected) { setText("serverstr", view.connectionHost, true); } else { setText("serverstr", null, true); } setText("title", getMsg(MSG_TITLE_USER, [view.unicodeName, source])); if (header.descnodes.firstChild) { header.descnodes.firstChild.remove(); } if (typeof view.desc != "undefined") { var data = getObjectDetails(view); data.dontLogURLs = true; var nodes = client.munger.munge(view.desc, null, data); header.descnodes.appendChild(adoptNode(nodes)); } else { setText("descnodes", ""); } } function updateDCCChat() { if (view.state.state == 4) { setText("remotestr", view.remoteIP + ":" + view.port, true); } else { setText("remotestr", null, true); } setText("title", getMsg(MSG_TITLE_DCCCHAT, view.user.unicodeName)); } function updateDCCFile() { var pcent = view.progress; setText("file", view.filename); setText( "progress", getMsg(MSG_DCCFILE_PROGRESS, [ pcent, mainWindow.getSISize(view.position), mainWindow.getSISize(view.size), mainWindow.getSISpeed(view.speed), ]) ); setAttribute("progressbar", "width", pcent + "%"); } function updateSplash(content) { var splash = document.getElementById("splash"); splash.appendChild(document.createTextNode(content)); }