/* -*- Mode: C++; tab-width: 8; 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/. */ /* This file contains the munger functions and rules used by ChatZilla. * It's generally a bad idea to call munger functions inside ChatZilla for * anything but munging (chat) output. */ function initMunger() { /* linkRE: the general URL linkifier regular expression: * * - start with whitespace, non-word, or begining-of-line * - then match: * - EITHER scheme (word + hyphen), colon, then lots of non-whitespace * - OR "www" followed by at least 2 sets of: * - "." plus some non-whitespace, non-"." characters * - must end match with a word-break * - include a "/" or "=" beyond break if present * - end with whitespace, non-word, or end-of-line */ client.linkRE = /(?:\W|^)((?:(\w[\w-]+):[^\s]+|www(\.[^.\s]+){2,})\b[\/=\)]?)(?=\s|\W|$)/; // Colours: \x03, with optional foreground and background colours client.colorRE = /(\x03((\d{1,2})(,\d{1,2}|)|))/; client.whitespaceRE = new RegExp("(\\S{" + client.MAX_WORD_DISPLAY + ",})"); const LOW_PRIORITY = 5; const NORMAL_PRIORITY = 10; const HIGH_PRIORITY = 15; const HIGHER_PRIORITY = 20; var munger = client.munger = new CMunger(insertText); // Special internal munger! munger.addRule(".inline-buttons", /(\[\[.*?\]\])/, insertInlineButton, HIGH_PRIORITY, LOW_PRIORITY, false); munger.addRule("quote", /(``|'')/, insertQuote, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("bold", /(?:[\s(\[]|^)(\*[^*()]*\*)(?:[\s\]).,;!\?]|$)/, "chatzilla-bold", NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("underline", /(?:[\s(\[]|^)(\_[^_()]*\_)(?:[\s\]).,;!\?]|$)/, "chatzilla-underline", NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("italic", /(?:\s|^)(\/[^\/()]*\/)(?:[\s.,]|$)/, "chatzilla-italic", NORMAL_PRIORITY, NORMAL_PRIORITY); /* allow () chars inside |code()| blocks */ munger.addRule("teletype", /(?:\s|^)(\|[^|]*\|)(?:[\s.,]|$)/, "chatzilla-teletype", NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule(".mirc-colors", client.colorRE, mircChangeColor, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule(".mirc-bold", /(\x02)/, mircToggleBold, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule(".mirc-underline", /(\x1f)/, mircToggleUnder, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule(".mirc-color-reset", /(\x0f)/, mircResetColor, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule(".mirc-reverse", /(\x16)/, mircReverseColor, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule(".ansi-escape-sgr", /(\x1b\[([\d;]*)m)/, ansiEscapeSGR, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("ctrl-char", /([\x01-\x1f])/, showCtrlChar, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("link", client.linkRE, insertLink, NORMAL_PRIORITY, HIGH_PRIORITY); // This has a higher starting priority so as to get it to match before the // normal link, which won't know about mailto and then fail. munger.addRule(".mailto", /(?:\W|^)((mailto:)?[^:;\\<>\[\]()\'\"\s\u201d]+@[^.<>\[\]()\'\"\s\u201d]+\.[^<>\[\]()\'\"\s\u201d]+)/i, insertMailToLink, NORMAL_PRIORITY, HIGHER_PRIORITY, false); addBugzillaLinkMungerRule(client.prefs["bugKeyword"], NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("channel-link", /(?:[^\w#]|^)[@%+]?(#[^<>,\[\](){}\"\s\u201d]*[^:,.<>\[\](){}\'\"\s\u201d])/i, insertChannelLink, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("talkback-link", /(?:\W|^)(TB\d{8,}[A-Z]?)(?:\W|$)/, insertTalkbackLink, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("face", /((^|\s)(?:[>O]?[B8=:;(xX%][~']?[-^v"]?(?:[)|(PpSs0oO#\?\*\[\]\/\\]|D+)|>[-^v]?\)|[oO9][._][oO9])(\s|$))/, insertSmiley, NORMAL_PRIORITY, NORMAL_PRIORITY); munger.addRule("rheet", /(?:\W|^)(rhee+t\!*)(?:\s|$)/i, insertRheet, 10, 10); munger.addRule("word-hyphenator", client.whitespaceRE, insertHyphenatedWord, LOW_PRIORITY, NORMAL_PRIORITY); client.enableColors = client.prefs["munger.colorCodes"]; var branch = client.prefManager.prefBranch; for (var entry in munger.entries) { if (!isinstance(munger.entries[entry], Object)) continue; for (var rule in munger.entries[entry]) { if (rule[0] == ".") continue; try { munger.entries[entry][rule].enabled = branch.getBoolPref("munger." + rule); } catch (ex) { // nada } } } } function addBugzillaLinkMungerRule(keywords, priority, startPriority) { client.munger.addRule("bugzilla-link", new RegExp("(?:\\W|^)(("+keywords+")\\s+(?:#?\\d+|#[^\\s,]{1,20})(?:\\s+comment\\s+#?\\d+)?)","i"), insertBugzillaLink, priority, startPriority); } function insertLink(matchText, containerTag, data, mungerEntry) { var href; var linkText; var trailing; ary = matchText.match(/([.,?\)]+)$/); if (ary) { linkText = RegExp.leftContext; trailing = ary[1]; // We special-case links that end with (something), often found on wikis // if "trailing" starts with ) and there's an unclosed ( in the // "linkText"; then we put the final ) back in if ((trailing.indexOf(")") == 0) && (linkText.match(/\([^\)]*$/))) { linkText += ")"; trailing = trailing.substr(1); } } else { linkText = matchText; } var ary = linkText.match(/^(\w[\w-]+):/); if (ary) { if (!client.checkURLScheme(ary[1])) { mungerEntry.enabled = false; client.munger.munge(matchText, containerTag, data); mungerEntry.enabled = true; return; } href = linkText; } else { href = "http://" + linkText; } /* This gives callers to the munger control over URLs being logged; the * channel topic munger uses this, as well as the "is important" checker. * If either of |dontLogURLs| or |noStateChange| is present and true, we * don't log. */ if ((!("dontLogURLs" in data) || !data.dontLogURLs) && (!("noStateChange" in data) || !data.noStateChange) && client.urlLogger) { client.urlLogger.append(href); } var anchor = document.createElementNS(XHTML_NS, "html:a"); var mircRE = /\x1f|\x02|\x0f|\x16|\x03([0-9]{1,2}(,[0-9]{1,2})?)?/g; anchor.setAttribute("href", href.replace(mircRE, "")); // Carry over formatting. var otherFormatting = calcClass(data); if (otherFormatting) anchor.setAttribute("class", "chatzilla-link " + otherFormatting); else anchor.setAttribute("class", "chatzilla-link"); anchor.setAttribute("target", "_content"); mungerEntry.enabled = false; data.inLink = true; client.munger.munge(linkText, anchor, data); mungerEntry.enabled = true; delete data.inLink; containerTag.appendChild(anchor); if (trailing) insertText(trailing, containerTag, data); } function insertMailToLink(matchText, containerTag, eventData, mungerEntry) { if (("inLink" in eventData) && eventData.inLink) { mungerEntry.enabled = false; client.munger.munge(matchText, containerTag, eventData); mungerEntry.enabled = true; return; } var href; if (matchText.toLowerCase().indexOf("mailto:") != 0) href = "mailto:" + matchText; else href = matchText; var anchor = document.createElementNS(XHTML_NS, "html:a"); var mircRE = /\x1f|\x02|\x0f|\x16|\x03([0-9]{1,2}(,[0-9]{1,2})?)?/g; anchor.setAttribute("href", href.replace(mircRE, "")); // Carry over formatting. var otherFormatting = calcClass(eventData); if (otherFormatting) anchor.setAttribute("class", "chatzilla-link " + otherFormatting); else anchor.setAttribute("class", "chatzilla-link"); //anchor.setAttribute ("target", "_content"); mungerEntry.enabled = false; eventData.inLink = true; client.munger.munge(matchText, anchor, eventData); mungerEntry.enabled = true; delete eventData.inLink; containerTag.appendChild(anchor); } function insertChannelLink(matchText, containerTag, eventData, mungerEntry) { if (("inLink" in eventData) && eventData.inLink) { mungerEntry.enabled = false; client.munger.munge(matchText, containerTag, eventData); mungerEntry.enabled = true; return; } var bogusChannels = /^#(include|error|define|if|ifdef|else|elsif|endif)$/i; if (!("network" in eventData) || !eventData.network || matchText.search(bogusChannels) != -1) { containerTag.appendChild(document.createTextNode(matchText)); return; } var linkText = removeColorCodes(matchText); var encodedLinkText = fromUnicode(linkText, eventData.sourceObject); var anchor = document.createElementNS(XHTML_NS, "html:a"); anchor.setAttribute("href", eventData.network.getURL(encodedLinkText)); // Carry over formatting. var otherFormatting = calcClass(eventData); if (otherFormatting) anchor.setAttribute("class", "chatzilla-link " + otherFormatting); else anchor.setAttribute("class", "chatzilla-link"); mungerEntry.enabled = false; eventData.inLink = true; client.munger.munge(matchText, anchor, eventData); mungerEntry.enabled = true; delete eventData.inLink; containerTag.appendChild(anchor); } function insertTalkbackLink(matchText, containerTag, eventData, mungerEntry) { if (("inLink" in eventData) && eventData.inLink) { mungerEntry.enabled = false; client.munger.munge(matchText, containerTag, eventData); mungerEntry.enabled = true; return; } var anchor = document.createElementNS(XHTML_NS, "html:a"); anchor.setAttribute("href", "http://talkback-public.mozilla.org/" + "search/start.jsp?search=2&type=iid&id=" + matchText); // Carry over formatting. var otherFormatting = calcClass(eventData); if (otherFormatting) anchor.setAttribute("class", "chatzilla-link " + otherFormatting); else anchor.setAttribute("class", "chatzilla-link"); mungerEntry.enabled = false; client.munger.munge(matchText, anchor, eventData); mungerEntry.enabled = true; containerTag.appendChild(anchor); } function insertBugzillaLink (matchText, containerTag, eventData, mungerEntry) { if (("inLink" in eventData) && eventData.inLink) { mungerEntry.enabled = false; client.munger.munge(matchText, containerTag, eventData); mungerEntry.enabled = true; return; } var prefs = client.prefs; if (eventData.channel) prefs = eventData.channel.prefs; else if (eventData.network) prefs = eventData.network.prefs; var bugURL = prefs["bugURL"]; var bugURLcomment = prefs["bugURL.comment"]; if (bugURL.length > 0) { var idOrAlias = matchText.match(new RegExp("(?:"+client.prefs["bugKeyword"]+")\\s+#?(\\d+|[^\\s,]{1,20})","i"))[1]; bugURL = bugURL.replace("%s", idOrAlias); var commentNum = matchText.match(/comment\s+#?(\d+)/i); if (commentNum) { /* If the comment is a complete URL, use only that, replacing %1$s * and %2$s with the bug number and comment number, respectively. * Otherwise, append the comment preference to the main one, * replacing just %s in each. */ if (bugURLcomment.match(/^\w+:/)) { bugURL = bugURLcomment; bugURL = bugURL.replace("%1$s", idOrAlias); bugURL = bugURL.replace("%2$s", commentNum[1]); } else { bugURL += bugURLcomment.replace("%s", commentNum[1]); } } var anchor = document.createElementNS(XHTML_NS, "html:a"); anchor.setAttribute("href", bugURL); // Carry over formatting. var otherFormatting = calcClass(eventData); if (otherFormatting) anchor.setAttribute("class", "chatzilla-link " + otherFormatting); else anchor.setAttribute("class", "chatzilla-link"); anchor.setAttribute("target", "_content"); mungerEntry.enabled = false; eventData.inLink = true; client.munger.munge(matchText, anchor, eventData); mungerEntry.enabled = true; delete eventData.inLink; containerTag.appendChild(anchor); } else { mungerEntry.enabled = false; client.munger.munge(matchText, containerTag, eventData); mungerEntry.enabled = true; } } function insertRheet(matchText, containerTag, eventData, mungerEntry) { if (("inLink" in eventData) && eventData.inLink) { mungerEntry.enabled = false; client.munger.munge(matchText, containerTag, eventData); mungerEntry.enabled = true; return; } var anchor = document.createElementNS(XHTML_NS, "html:a"); anchor.setAttribute("href", "http://ftp.mozilla.org/pub/mozilla.org/mozilla/libraries/bonus-tracks/rheet.wav"); anchor.setAttribute("class", "chatzilla-rheet chatzilla-link"); //anchor.setAttribute ("target", "_content"); insertText(matchText, anchor, eventData); containerTag.appendChild(anchor); } function insertQuote (matchText, containerTag) { if (matchText == "``") containerTag.appendChild(document.createTextNode("\u201c")); else containerTag.appendChild(document.createTextNode("\u201d")); containerTag.appendChild(document.createElementNS(XHTML_NS, "html:wbr")); } function insertSmiley(emoticon, containerTag, eventData, mungerEntry) { let smilies = { "face-alien": "\uD83D\uDC7D", "face-lol": "\uD83D\uDE02", "face-laugh": "\uD83D\uDE04", "face-sweat_smile": "\uD83D\uDE05", "face-innocent": "\uD83D\uDE07", "face-evil": "\uD83D\uDE08", "face-wink": "\uD83D\uDE09", "face-smile": "\uD83D\uDE0A", "face-cool": "\uD83D\uDE0E", "face-neutral": "\uD83D\uDE10", "face-thinking": "\uD83D\uDE14", "face-confused": "\uD83D\uDE15", "face-kissing": "\uD83D\uDE17", "face-tongue": "\uD83D\uDE1B", "face-worried": "\uD83D\uDE1F", "face-angry": "\uD83D\uDE20", "face-cry": "\uD83D\uDE22", "face-surprised": "\uD83D\uDE2D", "face-eek": "\uD83D\uDE31", "face-red": "\uD83D\uDE33", "face-dizzy": "\uD83D\uDE35", "face-sad": "\uD83D\uDE41", "face-rolleyes": "\uD83D\uDE44", "face-zipped": "\uD83E\uDD10", "face-rofl": "\uD83E\uDD23", "face-woozy": "\uD83E\uDD74", }; let type; if (emoticon.search(/\>[-^v]?\)/) != -1) type = "face-alien"; else if (emoticon.search(/\>[=:;][-^v]?[(|]|[Xx][-^v]?[(\[]/) != -1) type = "face-angry"; else if (emoticon.search(/[=:;][-^v]?[Ss]/) != -1) type = "face-confused"; else if (emoticon.search(/[B8][-^v]?[)\]]/) != -1) type = "face-cool"; else if (emoticon.search(/[=:;][~'][-^v]?\(/) != -1) type = "face-cry"; else if (emoticon.search(/o[._]O|O[._]o/) != -1) type = "face-dizzy"; else if (emoticon.search(/o[._]o|O[._]O/) != -1) type = "face-eek"; else if (emoticon.search(/\>[=:;][-^v]?D/) != -1) type = "face-evil"; else if (emoticon.search(/O[=:][-^v]?[)]/) != -1) type = "face-innocent"; else if (emoticon.search(/[=:;][-^v]?[*]/) != -1) type = "face-kissing"; else if (emoticon.search(/[=:;][-^v]?DD/) != -1) type = "face-lol"; else if (emoticon.search(/[=:;][-^v]?D/) != -1) type = "face-laugh"; else if (emoticon.search(/\([-^v]?D|[xX][-^v]?D/) != -1) type = "face-rofl"; else if (emoticon.search(/[=:;][-^v]?\|/) != -1) type = "face-neutral"; else if (emoticon.search(/[=:;][-^v]?\?/) != -1) type = "face-thinking"; else if (emoticon.search(/[=:;]"[)\]]/) != -1) type = "face-red"; else if (emoticon.search(/9[._]9/) != -1) type = "face-rolleyes"; else if (emoticon.search(/[=:;][-^v]?[(\[]/) != -1) type = "face-sad"; else if (emoticon.search(/[=:][-^v]?[)]/) != -1) type = "face-smile"; else if (emoticon.search(/[=:;][-^v]?[0oO]/) != -1) type = "face-surprised"; else if (emoticon.search(/[=:][-^v]?[\]]/) != -1) type = "face-sweat_smile"; else if (emoticon.search(/[=:;][-^v]?[pP]/) != -1) type = "face-tongue"; else if (emoticon.search(/;[-^v]?[)\]]/) != -1) type = "face-wink"; else if (emoticon.search(/%[-^v][)\]]/) != -1) type = "face-woozy"; else if (emoticon.search(/[=:;][-^v]?[\/\\]/) != -1) type = "face-worried"; else if (emoticon.search(/[=:;][-^v]?[#]/) != -1) type = "face-zipped"; let glyph = smilies[type]; if (!glyph) { // We didn't actually match anything, so it'll be a too-generic match // from the munger RegExp. mungerEntry.enabled = false; client.munger.munge(emoticon, containerTag, eventData); mungerEntry.enabled = true; return; } // Add spaces to beginning / end where appropriate. if (emoticon.search(/^\s/) != -1) glyph = " " + glyph; if (emoticon.search(/\s$/) != -1) glyph = glyph + " "; // Create a span to hold the emoticon. let span = document.createElementNS(XHTML_NS, "html:span"); span.appendChild(document.createTextNode(glyph)); span.setAttribute("class", "chatzilla-emote-txt"); // Add the title attribute (to show the original text in a tooltip) in case // the replacement was done incorrectly. span.setAttribute("title", emoticon); span.setAttribute("type", type); containerTag.appendChild(span); } function mircChangeColor (colorInfo, containerTag, data) { /* If colors are disabled, the caller doesn't want colors specifically, or * the caller doesn't want any state-changing effects, we drop out. */ if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } // Entry 0 will contain all colors specified, // entry 1 will have any specified foreground color or be undefined, // entry 2 will have any specified background color or be undefined. // Valid color codes are 0-99 with 99 having special meaning. let ary = colorInfo.match(/^\x03(?:(\d\d?)(?:,(\d\d?))?)?/); // If no foreground color specified or somehow the array does not have 3 // entries then it has invalid syntax. if (ary.length != 3 || !ary[1]) { delete data.currFgColor; delete data.currBgColor; return; } let fgColor = Number(ary[1]); if (fgColor != 99) { data.currFgColor = (fgColor % 16).toString().padStart(2, "0"); } else { delete data.currFgColor; } // If no background color then default to 99. let bgColor = Number(ary[2] || "99"); if (bgColor != 99) { data.currBgColor = (bgColor % 16).toString().padStart(2, "0"); } else { delete data.currBgColor; } // Only set hasColorInfo if we have something set. if (fgColor != 99 || bgColor != 99) { data.hasColorInfo = true; } } function mircToggleBold (colorInfo, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } if ("isBold" in data) delete data.isBold; else data.isBold = true; data.hasColorInfo = true; } function mircToggleUnder (colorInfo, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } if ("isUnderline" in data) delete data.isUnderline; else data.isUnderline = true; data.hasColorInfo = true; } function mircResetColor (text, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange) || !("hasColorInfo" in data)) { return; } removeColorInfo(data); } function mircReverseColor (text, containerTag, data) { if (!client.enableColors || (("noMircColors" in data) && data.noMircColors) || (("noStateChange" in data) && data.noStateChange)) { return; } var tempColor = ("currFgColor" in data ? data.currFgColor : ""); if ("currBgColor" in data) data.currFgColor = data.currBgColor; else delete data.currFgColor; if (tempColor) data.currBgColor = tempColor; else delete data.currBgColor; data.hasColorInfo = true; } function ansiEscapeSGR(text, containerTag, data) { if (!client.enableColors || (("noANSIColors" in data) && data.noANSIColors) || (("noStateChange" in data) && data.noStateChange)) { return; } /* ANSI SGR (Select Graphic Rendition) escape support. Matched text may * have any number of effects, each a number separated by a semicolon. If * there are no effects listed, it is treated as effect "0" (reset/normal). */ text = text.substr(2, text.length - 3) || "0"; const ansiToMircColor = [ "01", "05", "03", "07", "02", "06", "10", "15", "14", "04", "09", "08", "12", "13", "11", "00" ]; var effects = text.split(";"); for (var i = 0; i < effects.length; i++) { data.hasColorInfo = true; switch (Number(effects[i])) { case 0: // Reset/normal. removeColorInfo(data); break; case 1: // Intensity: bold. data.isBold = true; break; case 3: // Italic: on. data.isItalic = true; break; case 4: // Underline: single. data.isUnderline = true; break; case 9: // Strikethrough: on. data.isStrikethrough = true; break; case 22: // Intensity: normal. delete data.isBold; break; case 23: // Italic: off. delete data.isItalic; break; case 24: // Underline: off. delete data.isUnderline; break; case 29: // Strikethrough: off. delete data.isStrikethrough; break; case 53: // Overline: on. data.isOverline = true; break; case 55: // Overline: off. delete data.isOverline; break; case 30: // FG: Black. case 31: // FG: Red. case 32: // FG: Green. case 33: // FG: Yellow. case 34: // FG: Blue. case 35: // FG: Magenta. case 36: // FG: Cyan. case 37: // FG: While (light grey). data.currFgColor = ansiToMircColor[effects[i] - 30]; break; case 39: // FG: default. delete data.currFgColor; break; case 40: // BG: Black. case 41: // BG: Red. case 42: // BG: Green. case 43: // BG: Yellow. case 44: // BG: Blue. case 45: // BG: Magenta. case 46: // BG: Cyan. case 47: // BG: While (light grey). data.currBgColor = ansiToMircColor[effects[i] - 40]; break; case 49: // BG: default. delete data.currBgColor; break; case 90: // FG: Bright Black (dark grey). case 91: // FG: Bright Red. case 92: // FG: Bright Green. case 93: // FG: Bright Yellow. case 94: // FG: Bright Blue. case 95: // FG: Bright Magenta. case 96: // FG: Bright Cyan. case 97: // FG: Bright While. data.currFgColor = ansiToMircColor[effects[i] - 90 + 8]; break; case 100: // BG: Bright Black (dark grey). case 101: // BG: Bright Red. case 102: // BG: Bright Green. case 103: // BG: Bright Yellow. case 104: // BG: Bright Blue. case 105: // BG: Bright Magenta. case 106: // BG: Bright Cyan. case 107: // BG: Bright While. data.currBgColor = ansiToMircColor[effects[i] - 100 + 8]; break; } } } function removeColorInfo(data) { delete data.currFgColor; delete data.currBgColor; delete data.isBold; delete data.isItalic; delete data.isOverline; delete data.isStrikethrough; delete data.isUnderline; delete data.hasColorInfo; } function showCtrlChar(c, containerTag) { var span = document.createElementNS(XHTML_NS, "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); containerTag.appendChild(document.createElementNS(XHTML_NS, "html:wbr")); } function insertText(text, containerTag, data) { var newClass = ""; if (data && ("hasColorInfo" in data)) newClass = calcClass(data); if (!newClass) delete data.hasColorInfo; if (newClass) { var spanTag = document.createElementNS(XHTML_NS, "html:span"); spanTag.setAttribute("class", newClass); containerTag.appendChild(spanTag); containerTag = spanTag; } var arg; while ((arg = text.match(client.whitespaceRE))) { // Find the start of the match so we can insert the preceding text. var start = text.indexOf(arg[0]); if (start > 0) containerTag.appendChild(document.createTextNode(text.substr(0, start))); // Process the long word itself. insertHyphenatedWord(arg[1], containerTag, { dontStyleText: true }); // Continue with the rest of the text. text = text.substr(start + arg[0].length); } // Insert any left-over text on the end. if (text) containerTag.appendChild(document.createTextNode(text)); } function insertHyphenatedWord(longWord, containerTag, data) { var wordParts = splitLongWord(longWord, client.MAX_WORD_DISPLAY); if (!data || !("dontStyleText" in data)) { var newClass = ""; if (data && ("hasColorInfo" in data)) newClass = calcClass(data); if (!newClass) delete data.hasColorInfo; if (newClass) { var spanTag = document.createElementNS(XHTML_NS, "html:span"); spanTag.setAttribute("class", newClass); containerTag.appendChild(spanTag); containerTag = spanTag; } } var wbr = document.createElementNS(XHTML_NS, "html:wbr"); for (var i = 0; i < wordParts.length; ++i) { containerTag.appendChild(document.createTextNode(wordParts[i])); containerTag.appendChild(wbr.cloneNode(true)); } } function insertInlineButton(text, containerTag, data) { var ary = text.match(/\[\[([^\]]+)\]\[([^\]]+)\]\[([^\]]+)\]\]/); if (!ary) { containerTag.appendChild(document.createTextNode(text)); return; } var label = ary[1]; var title = ary[2]; var command = ary[3]; var link = document.createElementNS(XHTML_NS, "html:a"); link.setAttribute("href", "x-cz-command:" + encodeURI(command)); link.setAttribute("title", title); link.setAttribute("class", "chatzilla-link"); link.appendChild(document.createTextNode(label)); containerTag.appendChild(document.createTextNode("[")); containerTag.appendChild(link); containerTag.appendChild(document.createTextNode("]")); } function calcClass(data) { var className = ""; if ("hasColorInfo" in data) { if ("currFgColor" in data) className += " chatzilla-fg" + data.currFgColor; if ("currBgColor" in data) className += " chatzilla-bg" + data.currBgColor; if ("isBold" in data) className += " chatzilla-bold"; if ("isItalic" in data) className += " chatzilla-italic"; if ("isOverline" in data) className += " chatzilla-overline"; if ("isStrikethrough" in data) className += " chatzilla-strikethrough"; if ("isUnderline" in data) className += " chatzilla-underline"; } return className; }