UserChrome.js/Mail

From MozillaZine Knowledge Base
Jump to navigationJump to search

This page contains sample userChrome.js scripts for mail.

Unless otherwise stated, they are self-contained scripts that work in both Thunderbird and SeaMonkey.

Note:  Version 0.8 of the userChrome.js extension may create a default userChrome.js file that is only appropriate for browsers. Add scripts for mail above the default content of the file. For Thunderbird, delete the default content.

Account settings list

This script collapses the list of accounts in account settings, leaving only one account expanded. It also removes the hard-coded width from the list, so that you can set your preferred width in userChrome.css.

Note:  The Mail Tweak extension also provides this feature, and it does not require any script.

When you copy the script from this web page, make sure that you scroll to get all of it:

AccountSelect = {

  load: function (win) {
    const tgt = "/content/AccountManager.xul"
    if (win.location.pathname != tgt) return

    with (win) {
      document.getElementById("accounttree")
        .parentNode
        .removeAttribute("style")

      selectServer = function (id, pg) {
        var svr = null, elem = null
        if (id) svr = document.getElementById(id)
        if (!svr) svr = getFirstAccount()

        if (id && svr && pg) elem = findSelectPage(svr, pg)
        if (!elem) elem = svr

        var view = accounttree.view
        var box = accounttree.treeBoxObject

        for (var i = 0; i < view.rowCount; ++i) {
          if (!view.isContainer(i)) continue
          if (view.isContainerOpen(i)) view.toggleOpenState(i)
          }

        var n = view.getIndexOfItem(elem)
        view.selection.select(n)
        view.toggleOpenState(n)
        box.ensureRowIsVisible(n)

        elem = svr.lastChild.lastChild
        if (elem.localName == "treeitem") n = view.getIndexOfItem(elem)
        box.ensureRowIsVisible(n)
        }
      }
    },

  observe: function (win, act) {
    if (act == "domwindowopened") win.addEventListener(
      "load",
      function () {AccountSelect.load(win)},
      false
      )
    }

  }

Components
  .classes["@mozilla.org/embedcomp/window-watcher;1"]
  .getService(Components.interfaces.nsIWindowWatcher)
  .registerNotification(AccountSelect)

To set the width, add a rule like this to your userChrome.css file, and adjust the number depending on your font. For example, you could adjust it so that Composition & Addressing is completely visible:

#accounttree {min-width: 18em !important;}

See also: Dialog too small or too large

Auto-hide folders

This script provides a simple way to make the folders sidebar auto-hide. To reveal the sidebar, move your mouse over the splitter at the left edge of the mail window. To hide it, move your mouse over the main part of the mail window.

Note:  The Mail Tweak extension also provides this feature, and it does not require any script.

AutoHideFolders = document.getElementById("folderpane_splitter")
  || document.getElementById("gray_vertical_splitter")

AutoHideFolders.addEventListener(
  "mouseover",
  function () {AutoHideFolders.setAttribute("state", "open")},
  false)

AutoHideFolders.nextSibling.addEventListener(
  "mouseover",
  function () {AutoHideFolders.setAttribute("state", "collapsed")},
  false)

Display name from address book

This Thunderbird-only script uses your personal address book to look up addresses in message headers, and displays the name from your address book instead of the name in the header. It places the name and address from the header in a tooltip, which you can see by positioning your mouse pointer over the header.

To activate this feature, choose Options (Preferences) – Advanced – General and check the box: "Show only display name for people in my address book"   This is a standard Thunderbird feature, but it only displays names found in messages. The script modifies it to display names found in your address book. The modification is the subject of bug 243631.

You can customize the script by specifying a different address book field as the cardColumn to display—for example: nickName or aimScreenName   For the full list, see: nsIAbCard

To enable the display name feature for From or Reply-To addresses, change abFrom or abReplyTo to true.

When you copy the script from this web page, make sure that you scroll to get all of it:

ABDisplayName = {

  // customize:
  cardColumn: "displayName",
  abFrom: false,
  abReplyTo: false,

  DB: Components
    .classes["@mozilla.org/addressbook;1"]
    .createInstance(Components.interfaces.nsIAddressBook)
    .getAbDatabaseFromURI("moz-abmdbdirectory://abook.mab"),

  card: function (addr, col) {
    return this.DB.getCardFromAttribute(null, col, addr, true)
    },

  load: function (win) {
    if (!win.AddExtraAddressProcessing) return
    win.AddExtraAddressProcessing = function (addr, node) {
      var label = "", tip = "", ok = gShowCondensedEmailAddresses
      var parent = node.parentNode, id = parent.id
      while (!id) parent = parent.parentNode, id = parent.id
      if (ok && id == "expandedfromBox") ok = ABDisplayName.abFrom
      else if (ok && id == "expandedreply-toBox") ok = ABDisplayName.abReplyTo
      if (ok) {
        var card = ABDisplayName.card(addr, "LowercasePrimaryEmail")
        if (!card) card = ABDisplayName.card(addr, "SecondEmail")
        var cardName = card? card[ABDisplayName.cardColumn] : ""
        var mailName = node.getAttribute("displayName")
        label = cardName? cardName : mailName? mailName : ""
        tip = cardName? mailName : mailName? cardName : ""
        }
      if (label) {
        node.setAttribute("label", label)
        node.setAttribute("tooltiptext", tip? tip + " <" + addr + ">" : addr)
        node.setAttribute("tooltip", "emailAddressTooltip")
        }
      else node.removeAttribute("tooltiptext")
      }
    },

  observe: function (win, act) {
    if (act == "domwindowopened") win.addEventListener(
      "load",
      function () {ABDisplayName.load(win)},
      false
      )
    }

  }

ABDisplayName.load(self)
Components
  .classes["@mozilla.org/embedcomp/window-watcher;1"]
  .getService(Components.interfaces.nsIWindowWatcher)
  .registerNotification(ABDisplayName)

Folder icons (1)

This script provides a simple way to specify icons, colors or other styles for particular accounts or folders in the folder pane. The script is only an enabler—you must specify style rules in a userChrome.css file in the usual way.

This script identifies accounts and folders by name, so all accounts and folders with the same name get the same style. For a method that identifies accounts and folders individually, see the next section.

Notes: 

  • Spaces or other special characters in account or folder names might cause problems with this script.
  • The Mail Tweak extension also provides a similar feature, and it does not require any script.
// make account and folder names available to userChrome.css
with (document.getElementById("folderNameCell"))
   setAttribute("properties", getAttribute("properties") + " name-?folderTreeName")

For example, if you want a special icon for an account or folder named Haddock, you can use code like this in userChrome.css:

treechildren::-moz-tree-image(folderNameCol, name-Haddock)
  {list-style-image: url("fish.png") !important;}

In this example, the image file fish.png should be 16×16 pixels with a transparent background, stored in the same place as your userChrome files.

For another example, if you want all folders named ToDo to appear in bold red, you can use code like this in userChrome.css:

treechildren::-moz-tree-cell-text(folderNameCol, name-ToDo)
  {color: red !important; font-weight: bold !important;}

Folder icons (2)

This script provides another way to specify icons, colors or other styles for particular accounts or folders in the folder pane. The script is only an enabler—you must specify style rules in a userChrome.css file in the usual way.

Note:  The Mail Tweak extension also provides a similar feature.

This script identifies accounts and folders individually by using a mailbox URI. In a mailbox URI, spaces and other special characters are encoded so that they do not cause any problems.

To use this script, customize the list of mailbox URIs near the top so that it lists the accounts and folders that you want to style. Ensure that every mailbox URI in the list is enclosed in double-quote characters. Ensure that every mailbox URI except the last is followed by a comma.

In this example, the first mailbox URI specifies an account, and the second mailbox URI specifies that account's Inbox:

FolderStyle = {

  // customize...
  folder: [
    "mailbox://someone@pop3.example.net",
    "mailbox://someone@pop3.example.net/Inbox"
    ],

  load: function () {
    var ds = Components
      .classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
      .createInstance(Components.interfaces.nsIRDFDataSource)

    var styleURI = "http://home.netscape.com/NC-rdf#Style"
    var p = RDF.GetResource(styleURI)
    for (var i = 0; i < this.folder.length; ++i)
      ds.Assert(RDF.GetResource(this.folder[i]), p, RDF.GetLiteral(i), true)

    document.getElementById("folderTree").database.AddDataSource(ds)

    var bindings = document.getElementById("folderTree")
      .firstChild.firstChild.firstChild.nextSibling
    with (bindings.appendChild(document.createElement("binding")))
      setAttribute("subject", "?member"),
      setAttribute("predicate", styleURI),
      setAttribute("object", "?style")

    with (document.getElementById("folderNameCell"))
      setAttribute("properties", getAttribute("properties") + " style-?style")
    }

  }

FolderStyle.load()

In your |userChrome.css file, add rules to specify the style for each account or folder. The property style-0 identifies the first mailbox URI in your list, the property style-1 identifies the next, and so on. This example gives the account name a green background, and makes the name of its Inbox folder green:

treechildren::-moz-tree-cell-text(folderNameCol, style-0)
 {background-color: palegreen !important;}

treechildren::-moz-tree-cell-text(folderNameCol, style-1)
 {color: limegreen !important;}

Message view menu

This Thunderbird-only script modifies the View – Messages menu and the Mail Views control on the toolbar so that certain tag views and custom views that you use most often are easier to select.

Note:  The View – Messages menu is only available when you customize the toolbar so that it includes the Mail Views control. This is bug 332802. To work around it, customize the toolbar then hide the Mail Views control with userChrome.css: #mailviews-container {visibility: collapse;}

Customize this script by naming the views that you want to promote. You can name tag views or custom views. Ensure that you preserve the punctuation in the script. Each view name must be quoted, and every view name except the last must be followed by a comma.

Promoted views appear after All and Unread in the menu, in the order that you name them in the script.

A limitation of this script is that it does not monitor changes that you make to tags or custom views. To refresh the menus, restart Thunderbird.

When you copy the script from this web page, make sure that you scroll to get all of it:

MessageViewMenu = {

  // customize...
  promote: [
    "Important",
    "People I Know",
    "Personal",
    "Last 5 Days"
    ],

  // translate...
  title: "Message View Menu",
  error: "No view named: %",

  MVL: Components
    .classes["@mozilla.org/messenger/mailviewlist;1"]
    .getService(Components.interfaces.nsIMsgMailViewList),

  MTS: Components
    .classes["@mozilla.org/messenger/tagservice;1"]
    .getService(Components.interfaces.nsIMsgTagService),

  PS: Components
    .classes["@mozilla.org/embedcomp/prompt-service;1"]
    .getService(Components.interfaces.nsIPromptService),

  customize: function (id) {
    var tgt = document.getElementById(id)
    if (!tgt || tgt.parentNode.hasAttribute("messageviewmenu")) return

    var radio = tgt.parentNode.parentNode.localName == "menu"

    for each (var name in this.promote) {
      var v = this.tag(name) || this.view(name)
      if (!v && radio) this.PS.alert(self, this.title, this.error.replace(/%/, name))
      else {
        with (tgt.parentNode.insertBefore(document.createElement("menuitem"), tgt.nextSibling)) {
          className = "messageviewmenu" + (v.color? " lc-" + v.color.substr(1) : "")
          setAttribute("value", v.key? ":" + v.key : v)
          setAttribute("label", name)
          if (radio) setAttribute("accesskey", name.charAt(0)), setAttribute("type", "radio")
          setAttribute("oncommand", "ViewChangeByMenuitem(this)")
          }
        tgt = tgt.nextSibling
        }
      }

    tgt.parentNode.setAttribute("messageviewmenu", "true")
    if (radio) tgt.parentNode.addEventListener("popupshowing", this.refresh, false)
    },

  load: function (win) {
    this.customize("viewMessageUnread")
    this.customize("viewPickerUnread")
    },

  observe: function (win, act) {
    if (act == "domwindowopened") win.addEventListener(
      "load",
      function () {MessageViewMenu.load(win)},
      false
      )
    },

  refresh: function (event) {
    var item = event.target.firstChild
    while (item) with (item) {
      if (className.indexOf("messageviewmenu") >= 0)
        setAttribute("checked", getAttribute("value") == gCurrentViewValue)
      item = nextSibling
      }
    },

  tag: function (name) {
    if (!this._tags) this._tags = this.MTS.getAllTags({})
    for each (var t in this._tags) if (t.tag == name) return t
    return null
    },

  view: function (name) {
    for (var i = 0; i < this.MVL.mailViewCount; ++i) {
      var v = this.MVL.getMailViewAt(i)
      if (v.prettyName == name) return i + 9
      }
    return ""
    }

  }

MessageViewMenu.load(self)
Components
  .classes["@mozilla.org/embedcomp/window-watcher;1"]
  .getService(Components.interfaces.nsIWindowWatcher)
  .registerNotification(MessageViewMenu)

Permanent pen

This script adds a "permanent pen" for adding notes to HTML replies and drafts.

Note:  The Mail Tweak extension also provides this feature, and it does not require any script.

Normally, when you type in existing text, the characters that you type inherit the style of the surrounding text. When the permanent pen is active, the characters that you type have a style that is permanently set. For example, you reply to a message quoting the original text. You press F9 to activate the permanent pen, and type some comments in various places in the original text. All your comments appear in red.

An indicator in the formatting toolbar shows when permanent pen is active. To see the formatting toolbar, you must be using the HTML editor. If you do not see the formatting toolbar, choose: View – Toolbars – Formatting Toolbar

As supplied, this script sets the permanent pen color to red. To use some other color, change the value of color near the top of the script. To set other text style attributes, change styleSet to list the attributes. To clear text style attributes, change styleNot to list the attributes. The available attributes are listed in the HTML section here, and they match the choices in the Format – Text Style menu. In this script, enclose them in quotes and separate them with commas. For example, to specify a green, bold, italic permanent pen that clears underlining:

color: "green",
styleSet: ["bold", "italic"],
styleNot: ["underline"],

As supplied, this script uses the F9 key to toggle the permanent pen. To use some other key, change the value of keyCode near the top of the script. To specify a key combination, set one or more of altKey, ctrlKey, shiftKey to true.

When you copy the script from this web page, make sure that you scroll to get all of it:

PermanentPen = {

  // customize...
  color: "red",
  styleSet: [],
  styleNot: [],

  keyCode: "F9",
  altKey: false,
  ctrlKey: false,
  shiftKey: false,

  key: function (win, event) {
    if (!win.IsHTMLEditor()) return

    var my = PermanentPen
    if (event.keyCode == event["DOM_VK_" + my.keyCode]
        && event.altKey === my.altKey
        && event.ctrlKey === my.ctrlKey
        && event.shiftKey === my.shiftKey
        ) {
      my._active = !my._active
      win.document.getElementById("ColorButtons").style
        .borderLeftColor = my._active? my.color : "transparent"
      return
      }
    if (!my._active) return

    with (win) {
      gColorObj.TextColor = my.color
      GetCurrentEditor().setInlineProperty(my._font, "color", my.color)
      for each (var cmd in my.styleSet) {
        cmd = "cmd_" + cmd
        var on = top.document.getElementById(cmd).getAttribute("state") == "true"
        if (!on) doStyleUICommand(cmd)
        }
      for each (var cmd in my.styleNot) {
        cmd = "cmd_" + cmd
        var on = top.document.getElementById(cmd).getAttribute("state") == "true"
        if (on) doStyleUICommand(cmd)
        }
      }
    },

  load: function (win) {
    const tgt = "/content/messengercompose/messengercompose.xul"
    if (win.location.pathname != tgt) return

    PermanentPen._active = false
    with (win.document.getElementById("ColorButtons").style)
      borderStyle = "solid",
      borderWidth = "0 0 0 1px",
      borderLeftColor = "transparent"

    if (!win.gAtomService) win.GetAtomService()
    PermanentPen._font = win.gAtomService.getAtom("font")

    win.addEventListener("keypress",
      function (event) {PermanentPen.key(win, event)},
      true)
    },

  observe: function (win, act) {
     if (act == "domwindowopened") win.addEventListener(
      "load",
      function () {PermanentPen.load(win)},
      false
      )
    }

  }

Components
  .classes["@mozilla.org/embedcomp/window-watcher;1"]
  .getService(Components.interfaces.nsIWindowWatcher)
  .registerNotification(PermanentPen)

Print preview focus

This script works around a bug that leaves the print preview window without focus, making it difficult to access with the keyboard.

PrintPreviewFocus = {

  load: function (win) {
    if (win.document.documentElement.id == "printEngineWin") 
      this.preview = win
    },

  unload: function (win) {
    if (win.location.pathname == "/content/printPreviewProgress.xul")
      this.preview.setTimeout(
        function () {try {PrintPreviewFocus.preview.focus()} catch (ex) {}},
        0
        )
    },

  observe: function (win, act) {
    if (act == "domwindowopened") {
      win.addEventListener(
        "load",
        function () {PrintPreviewFocus.load(win)},
        false
        )
      win.addEventListener(
        "unload",
        function () {PrintPreviewFocus.unload(win)},
        false
        )
      }
    }

  }

Components
  .classes["@mozilla.org/embedcomp/window-watcher;1"]
  .getService(Components.interfaces.nsIWindowWatcher)
  .registerNotification(PrintPreviewFocus)

Priority column icons

This script for Thunderbird changes the Priority column in the thread pane so that it displays icons instead of text. To make this work, there are three steps.

1. Add this script to your userChrome.js file to modify the Priority column:

PriorityIcon = {

  init: function () {
    const p = ["priorityNotSet", "priorityNone", "priorityLowest",
      "priorityLow", "priorityNormal", "priorityHigh", "priorityHighest"]

    this._atom = []
    with (Components
        .classes["@mozilla.org/atom-service;1"]
        .getService(Components.interfaces.nsIAtomService))
      for (var i = 0; i <= 6; ++i) this._atom[i] = getAtom(p[i])

    Components
      .classes["@mozilla.org/observer-service;1"]
      .getService(Components.interfaces.nsIObserverService)
      .addObserver(this, "MsgCreateDBView", false)
    },

  observe: function (subject, topic, data) {
    gDBView.addColumnHandler("priorityCol", this)
    },

  set: function (row, props) {
    with (gDBView) var p = db
      .GetMsgHdrForKey(getKeyAt(row))
      .getUint32Property("priority")
    props.AppendElement(this._atom[p])
    },

  // nsIMsgCustomColumnHandler...
  getCellProperties: function (row, col, props) {this.set(row, props)},
  getRowProperties: function (row, props) {},
  getImageSrc: function (row, col) {},
  getCellText: function (row, col) {},
  getSortStringForRow: function (hdr) {},
  getSortLongForRow: function (hdr) {return hdr.getUint32Property("priority")},
  isString: function () {return false}
  }

PriorityIcon.init()

2. Add this code to your userChrome.css file to specify details of the column display:

#priorityCol, treechildren::-moz-tree-image(priorityCol)
 {list-style-image: url("Priority-icons-tb.png");}

treechildren::-moz-tree-image(priorityCol)
 {-moz-image-region: rect(0px 16px 16px 0px);}
treechildren::-moz-tree-image(priorityCol, priorityNone)
 {-moz-image-region: rect(0px 32px 16px 16px);}
treechildren::-moz-tree-image(priorityCol, priorityLowest)
 {-moz-image-region: rect(0px 48px 16px 32px);}
treechildren::-moz-tree-image(priorityCol, priorityLow)
 {-moz-image-region: rect(0px 64px 16px 48px);}
treechildren::-moz-tree-image(priorityCol, priorityNormal)
 {-moz-image-region: rect(0px 80px 16px 64px);}
treechildren::-moz-tree-image(priorityCol, priorityHigh)
 {-moz-image-region: rect(0px 96px 16px 80px);}
treechildren::-moz-tree-image(priorityCol, priorityHighest)
 {-moz-image-region: rect(0px 112px 16px 96px);}

#priorityCol
 {-moz-image-region: rect(0px 128px 16px 112px);
  -moz-binding: url("chrome://global/content/bindings/tree.xml#treecol-image");
  padding-left: 0 !important;
  width: 23px;
  max-width: 23px;}

3. Download one of these images containing the actual icons. (To download an image, get a context menu for it in your web browser—for example, by right-clicking it. From the context menu choose: Save Image As... ) Save it in the same place as your userChrome.js and userChrome.css files. If necessary, rename it Priority-icons-tb.png to match the name in your userChrome.css file:

The image contains eight icons, for the seven priority levels and the column header. The icons for Not Set, None and Normal are blank.

Word count

This script provides a word count in the status bar when you select text while composing a message.

Notes: 

  • Like the spelling checker, this script sees hidden text that may be in an HTML message, and includes it in the count. Thunderbird and SeaMonkey do not normally insert hidden text, but HTML from other sources might contain hidden text.
  • The Mail Tweak extension also provides this feature, and it does not require any script.
WordCount = {

  // translatable text...
  oneWord: "1 word",
  manyWords: "% words",

  load: function (win) {
    const tgt = "/content/messengercompose/messengercompose.xul"
    if (win.location.pathname != tgt) return

    this._status = win.document.getElementById("statusText")

    win.countWords = function () {
      var sel = null
      with (win.document.getElementById("content-frame"))
        try {sel = getEditor(contentWindow).selection} catch (ex) {}
      if (sel) sel
        .QueryInterface(Components.interfaces.nsISelectionPrivate)
        .addSelectionListener(WordCount)
      }

    with (win.document.getElementById("selectEditMenuItems"))
      setAttribute("oncommandupdate",
        getAttribute("oncommandupdate") + ";countWords()")
    },

  notifySelectionChanged: function (doc, sel, why) {
    var wds = sel.toString().match(/(^|\s+)\S/g)
    var n = wds? wds.length : 0
    this._status.label =
      n == 0 ? ""
      : n == 1 ? this.oneWord
      : this.manyWords.replace("%", n)
    },

  observe: function (win, act) {
    if (act == "domwindowopened") win.addEventListener(
      "load",
      function () {WordCount.load(win)},
      false
      )
    }

  }

Components
  .classes["@mozilla.org/embedcomp/window-watcher;1"]
  .getService(Components.interfaces.nsIWindowWatcher)
  .registerNotification(WordCount)

Image defaults

This script changes the defaults in the Image Properties dialog—the dialog that you see when when you are writing a message and you choose Insert – Image... The changes mean that by default Thunderbird does not attach the image file, and does not require alternate text. If you only need one of these features, change the appropriate false to true in the settings near the start of the script.

See also: Creating complex mails with inline images

Note:  The Mail Tweak extension also provides this feature, and it does not require any script.

ImageDefaults = {

  // settings...
  attachLabel:    "Attach",
  defaultAttach:  false,
  requireAltText: false,

  load: function (win) {
    const tgt = "/content/EdImageProps.xul"
    if (win.location.pathname != tgt) return

    var doc = win.document
    var box = doc.getElementById("AdvancedEdit")
    with (box.insertBefore(doc.createElement("checkbox"), box.firstChild)) {
      setAttribute("id", "ImageDefaults-attach")
      setAttribute("label", this.attachLabel)
      setAttribute("disabled", "true")
      }

    win.ImageDefaults_enable = win.doOverallEnabling
    win.doOverallEnabling = function () {
      with (win) {
        var ok = (TrimString(gDialog.srcInput.value) != "")
        var dnsa = document.getElementById("ImageDefaults-attach")
        dnsa.checked = !ok? false
          : gInsertNewImage? ImageDefaults.defaultAttach
          : !globalElement.hasAttribute("moz-do-not-send")
        dnsa.disabled = !ok

        if (!ImageDefaults.requireAltText) with (gDialog) {
          if (altTextRadioGroup.selectedIndex == 0
            && TrimString(altTextInput.value) == "")
              altTextRadioGroup.selectedIndex = 1
          }

        ImageDefaults_enable()
        }
      }

    win.ImageDefaults_validate = win.ValidateData
    win.ValidateData = function () {
      with (win) {
        var src = TrimString(gDialog.srcInput.value)
        var dnsa = document.getElementById("ImageDefaults-attach")
        var mdns = (src && !dnsa.checked)
        globalElement[mdns? "setAttribute" : "removeAttribute"]("moz-do-not-send", "true")
        return ImageDefaults_validate()
        }
      }
    },

  observe: function (win, act) {
    if (act == "domwindowopened") win.addEventListener(
      "load",
      function () {ImageDefaults.load(win)},
      false
      )
    }

  }

Components
  .classes["@mozilla.org/embedcomp/window-watcher;1"]
  .getService(Components.interfaces.nsIWindowWatcher)
  .registerNotification(ImageDefaults)