function()

in suite/chatzilla/content/static.js [3900:4353]


    function (message, msgtype, sourceObj, destObj, tags) {
      // We need a message type, assume "INFO".
      if (!msgtype) {
        msgtype = MT_INFO;
      }

      var msgprefix = "";
      if (msgtype.includes("/")) {
        var ary = msgtype.match(/^(.*)\/(.*)$/);
        msgtype = ary[1];
        msgprefix = ary[2];
      }

      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|     */

      // Get the 'me' object, so we can be sure to get the attributes right.
      var me;
      if ("me" in this) {
        me = this.me;
      } else if (o.server && "me" in o.server) {
        me = o.server.me;
      }

      /* Allow for matching (but not identical) user objects here. This tends to
       * happen with bouncers and proxies, when they send channel messages
       * pretending to be from the user; the sourceObj is a CIRCChanUser
       * instead of a CIRCUser so doesn't == 'me'.
       */
      if (me) {
        if (sourceObj && sourceObj.canonicalName == me.canonicalName) {
          sourceObj = me;
        }
        if (destObj && destObj.canonicalName == me.canonicalName) {
          destObj = me;
        }
      }

      // Let callers get away with "ME!" and we have to substitute here.
      if (sourceObj == "ME!") {
        sourceObj = me;
      }
      if (destObj == "ME!") {
        destObj = me;
      }

      // Get the TYPE of the source object.
      var fromType = sourceObj && sourceObj.TYPE ? sourceObj.TYPE : "unk";
      // Is the source a user?
      var fromUser = fromType.search(/IRC.*User/) != -1;
      // Get some sort of "name" for the source.
      var fromAttr = "";
      if (sourceObj) {
        if ("canonicalName" in sourceObj) {
          fromAttr = sourceObj.canonicalName;
        } else if ("name" in sourceObj) {
          fromAttr = sourceObj.name;
        } else {
          fromAttr = sourceObj.viewName;
        }
      }

      // Get the dest TYPE too...
      var toType = destObj ? destObj.TYPE : "unk";
      // Is the dest a user?
      var toUser = toType.search(/IRC.*User/) != -1;
      // Get a dest name too...
      var toAttr = "";
      if (destObj) {
        if ("canonicalName" in destObj) {
          toAttr = destObj.canonicalName;
        } else if ("name" in destObj) {
          toAttr = destObj.name;
        } else {
          toAttr = destObj.viewName;
        }
      }

      // Is the message 'to' or 'from' somewhere other than this view
      var toOther = sourceObj == me && destObj && destObj != this;
      var fromOther =
        toUser &&
        destObj == me &&
        sourceObj != this &&
        // Need extra check for DCC users:
        !(this.TYPE == "IRCDCCChat" && this.user == sourceObj);

      // Attach "ME!" if appropriate, so motifs can style differently.
      if (sourceObj == me && !toOther) {
        fromAttr = fromAttr + " ME!";
      }
      if (destObj && destObj == me) {
        toAttr = me.canonicalName + " ME!";
      }

      /* 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 time;
      if (tags && "time" in tags) {
        time = new Date(tags.time);
      } else {
        time = new Date();
      }

      var timeStamp = strftime(this.prefs["timestamps.log"], time);

      // Statusbar text, and the line that gets saved to the log.
      var statusString;
      var logStringPfx = timeStamp + " ";
      var logStrings = [];

      if (fromUser) {
        statusString = getMsg(MSG_FMT_STATUS, [
          timeStamp,
          sourceObj.unicodeName + "!" + sourceObj.name + "@" + sourceObj.host,
        ]);
      } else {
        var name;
        if (sourceObj) {
          name = sourceObj.viewName;
        } else {
          name = this.viewName;
        }

        statusString = getMsg(MSG_FMT_STATUS, [timeStamp, name]);
      }

      // The table row, and it's attributes.
      var msgRow = document.createElementNS(XHTML_NS, "html:tr");
      msgRow.setAttribute("class", "msg");
      if (this.msgGroups) {
        msgRow.setAttribute("msg-groups", this.msgGroups.join(", "));
      }
      msgRow.setAttribute("msg-type", msgtype);
      msgRow.setAttribute("msg-prefix", msgprefix);
      msgRow.setAttribute("msg-dest", toAttr);
      msgRow.setAttribute("dest-type", toType);
      msgRow.setAttribute("view-type", viewType);
      msgRow.setAttribute("status-text", statusString);
      msgRow.setAttribute("timestamp", Number(time));
      if (fromAttr) {
        if (fromUser) {
          msgRow.setAttribute("msg-user", fromAttr);
          // Set some mode information for channel users
          if (fromType == "IRCChanUser") {
            msgRow.setAttribute("msg-user-mode", sourceObj.modes.join(" "));
          }
        } else {
          msgRow.setAttribute("msg-source", fromAttr);
        }
      }
      if (toOther) {
        msgRow.setAttribute("to-other", toOther);
      }
      if (fromOther) {
        msgRow.setAttribute("from-other", fromOther);
      }

      // Timestamp cell.
      var msgRowTimestamp = document.createElementNS(XHTML_NS, "html:td");
      msgRowTimestamp.setAttribute("class", "msg-timestamp");

      var canMergeData;
      var msgRowSource, msgRowType, msgRowData;
      if (fromUser && msgtype.match(/^(PRIVMSG|ACTION|NOTICE|WALLOPS)$/)) {
        var nick = sourceObj.unicodeName;
        var decorSt = "";
        var decorEn = "";

        // Set default decorations.
        if (msgtype == "ACTION") {
          decorSt = "* ";
        } else {
          decorSt = "<";
          decorEn = ">";
        }

        var nickURL;
        if (sourceObj != me && "getURL" in sourceObj) {
          nickURL = sourceObj.getURL();
        }
        if (toOther && "getURL" in destObj) {
          nickURL = destObj.getURL();
        }

        if (sourceObj != me) {
          // Not from us...
          if (destObj == me) {
            // ...but to us. Messages from someone else to us.

            getAttention = true;
            this.defaultCompletion = "/msg " + nick + " ";

            // If this is a private message, and it's not in a query view,
            // use *nick* instead of <nick>.
            if (msgtype != "ACTION" && this.TYPE != "IRCUser") {
              decorSt = "*";
              decorEn = "*";
            }
          } else {
            // ...or to us. Messages from someone else to channel or similar.

            if (typeof message == "string" && me) {
              isImportant = msgIsImportant(message, nick, o.network);
            } else if (message.hasAttribute("isImportant") && me) {
              isImportant = true;
            }

            if (isImportant) {
              this.defaultCompletion =
                nick + client.prefs.nickCompleteStr + " ";
            }
          }
        } else {
          // Messages from us, to somewhere other than this view
          if (toOther) {
            nick = destObj.unicodeName;
            decorSt = ">";
            decorEn = "<";
          }
        }

        // Log the nickname in the same format as we'll let the user copy.
        // If the message has a prefix, show it after a "/".
        if (msgprefix) {
          logStringPfx += decorSt + nick + "/" + msgprefix + decorEn + " ";
        } else {
          logStringPfx += decorSt + nick + decorEn + " ";
        }

        if (!("lastNickDisplayed" in this) || this.lastNickDisplayed != nick) {
          this.lastNickDisplayed = nick;
          this.mark = "mark" in this && this.mark == "even" ? "odd" : "even";
        }

        msgRowSource = document.createElementNS(XHTML_NS, "html:td");
        msgRowSource.setAttribute("class", "msg-user");

        // Make excessive nicks get shunted.
        if (nick && nick.length > client.MAX_NICK_DISPLAY) {
          blockLevel = true;
        }

        if (decorSt) {
          msgRowSource.appendChild(newInlineText(decorSt, "chatzilla-decor"));
        }
        if (nickURL) {
          var nick_anchor = document.createElementNS(XHTML_NS, "html:a");
          nick_anchor.setAttribute("class", "chatzilla-link");
          nick_anchor.setAttribute("href", nickURL);
          nick_anchor.appendChild(newInlineText(nick));
          msgRowSource.appendChild(nick_anchor);
        } else {
          msgRowSource.appendChild(newInlineText(nick));
        }
        if (msgprefix) {
          /* We don't style the "/" with chatzilla-decor because the one
           * thing we don't want is it disappearing!
           */
          msgRowSource.appendChild(newInlineText("/", ""));
          msgRowSource.appendChild(
            newInlineText(msgprefix, "chatzilla-prefix")
          );
        }
        if (decorEn) {
          msgRowSource.appendChild(newInlineText(decorEn, "chatzilla-decor"));
        }
        canMergeData = this.prefs.collapseMsgs;
      } else if (msgprefix) {
        decorSt = "<";
        decorEn = ">";

        logStringPfx += decorSt + "/" + msgprefix + decorEn + " ";

        msgRowSource = document.createElementNS(XHTML_NS, "html:td");
        msgRowSource.setAttribute("class", "msg-user");

        msgRowSource.appendChild(newInlineText(decorSt, "chatzilla-decor"));
        msgRowSource.appendChild(newInlineText("/", ""));
        msgRowSource.appendChild(newInlineText(msgprefix, "chatzilla-prefix"));
        msgRowSource.appendChild(newInlineText(decorEn, "chatzilla-decor"));
        canMergeData = this.prefs.collapseMsgs;
      } 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 */
        msgRowType = document.createElementNS(XHTML_NS, "html:td");
        msgRowType.setAttribute("class", "msg-type");

        msgRowType.appendChild(newInlineText(code));
        logStringPfx += code + " ";
      }

      if (message) {
        msgRowData = document.createElementNS(XHTML_NS, "html:td");
        msgRowData.setAttribute("class", "msg-data");

        var tmpMsgs = message;
        if (typeof message == "string") {
          msgRowData.appendChild(stringToMsg(message, this));
        } else {
          msgRowData.appendChild(message);
          tmpMsgs = tmpMsgs.innerHTML.replace(/<[^<]*>/g, "");
        }
        tmpMsgs = tmpMsgs.split(/\r?\n/);
        for (var l = 0; l < tmpMsgs.length; l++) {
          logStrings[l] = logStringPfx + tmpMsgs[l];
        }
      }

      if ("mark" in this) {
        msgRow.setAttribute("mark", this.mark);
      }

      if (isImportant) {
        if ("importantMessages" in this) {
          var importantId = "important" + this.importantMessages++;
          msgRow.setAttribute("id", importantId);
        }
        msgRow.setAttribute("important", "true");
        msgRow.setAttribute("aria-live", "assertive");
      }

      // Timestamps first...
      msgRow.appendChild(msgRowTimestamp);
      // Now do the rest of the row, after block-level stuff.
      if (msgRowSource) {
        msgRow.appendChild(msgRowSource);
      } else {
        msgRow.appendChild(msgRowType);
      }
      if (msgRowData) {
        msgRow.appendChild(msgRowData);
      }
      updateTimestampFor(this, msgRow);

      if (blockLevel) {
        /* putting a div here crashes mozilla, so fake it with nested tables
         * for now */
        var tr = document.createElementNS(XHTML_NS, "html:tr");
        tr.setAttribute("class", "msg-nested-tr");
        var td = document.createElementNS(XHTML_NS, "html:td");
        td.setAttribute("class", "msg-nested-td");
        td.setAttribute("colspan", "3");

        tr.appendChild(td);
        var table = document.createElementNS(XHTML_NS, "html:table");
        table.setAttribute("class", "msg-nested-table");
        table.setAttribute("role", "presentation");

        td.appendChild(table);
        var tbody = document.createElementNS(XHTML_NS, "html:tbody");

        tbody.appendChild(msgRow);
        table.appendChild(tbody);
        msgRow = tr;
      }

      // Actually add the item.
      addHistory(this, msgRow, canMergeData);

      // Update attention states...
      if (isImportant || getAttention) {
        setTabState(this, "attention");
        if (client.prefs["notify.aggressive"]) {
          window.getAttention();
        }
      } else if (isSuperfluous) {
        setTabState(this, "superfluous");
      } else {
        setTabState(this, "activity");
      }

      // Copy Important Messages [to network view].
      if (isImportant && client.prefs.copyMessages && o.network != this) {
        if (importantId) {
          // Create the linked inline button
          var msgspan = document.createElementNS(XHTML_NS, "html:span");
          msgspan.setAttribute("isImportant", "true");

          var cmd = "jump-to-anchor " + importantId + " " + this.unicodeName;
          var prefix = getMsg(MSG_JUMPTO_BUTTON, [this.unicodeName, cmd]);

          // Munge prefix as a button
          client.munger.getRule(".inline-buttons").enabled = true;
          client.munger.munge(prefix + " ", msgspan, o);

          // Munge rest of message normally
          client.munger.getRule(".inline-buttons").enabled = false;
          client.munger.munge(message, msgspan, o);

          o.network.displayHere(msgspan, msgtype, sourceObj, destObj);
        } else {
          o.network.displayHere(message, msgtype, sourceObj, destObj);
        }
      }

      // Log file time!
      if (this.prefs.log) {
        if (!this.logFile) {
          client.openLogFile(this);
        }

        try {
          var LE = client.lineEnd;
          for (var l = 0; l < logStrings.length; l++) {
            this.logFile.write(fromUnicode(logStrings[l] + LE, "utf-8"));
          }
        } catch (ex) {
          // Stop logging before showing any messages!
          this.prefs.log = false;
          dd("Log file write error: " + formatException(ex));
          this.displayHere(
            getMsg(MSG_LOGFILE_WRITE_ERROR, getLogPath(this)),
            "ERROR"
          );
        }
      }

      /* We want to show alerts if they're from a non-current view (optional),
       * or we don't have focus at all.
       */
      if (
        client.prefs["alert.globalEnabled"] &&
        this.prefs["alert.enabled"] &&
        client.alert &&
        (!window.isFocused ||
          (!client.prefs["alert.nonFocusedOnly"] &&
            !("currentObject" in client && client.currentObject == this)))
      ) {
        if (isImportant) {
          showEventAlerts(this.TYPE, "stalk", message, nick, o, this, msgtype);
        } else if (isSuperfluous) {
          showEventAlerts(this.TYPE, "event", message, nick, o, this, msgtype);
        } else {
          showEventAlerts(this.TYPE, "chat", message, nick, o, this, msgtype);
        }
      }
    };