mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-22 20:33:10 +09:00
829 lines
34 KiB
XML
829 lines
34 KiB
XML
<?xml version="1.0"?>
|
|
<!-- 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/.
|
|
-->
|
|
|
|
<bindings id="mailFolderBindings"
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
|
|
<binding id="folder-menupopup"
|
|
extends="chrome://global/content/bindings/popup.xml#popup">
|
|
<implementation>
|
|
<constructor><![CDATA[
|
|
Components.utils.import("resource:///modules/FeedUtils.jsm", this);
|
|
Components.utils.import("resource:///modules/folderUtils.jsm", this);
|
|
Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
|
|
Components.utils.import("resource:///modules/mailServices.js", this);
|
|
Components.utils.import("resource:///modules/MailUtils.js", this);
|
|
Components.utils.import("resource:///modules/StringBundle.js", this);
|
|
this._stringBundle = new this
|
|
.StringBundle("chrome://messenger/locale/folderWidgets.properties");
|
|
|
|
// Get the displayformat if set.
|
|
if (this.parentNode && this.parentNode.localName == "menulist")
|
|
this._displayformat = this.parentNode.getAttribute("displayformat");
|
|
|
|
// Find out if we are in a wrapper (customize toolbars mode is active).
|
|
let inWrapper = false;
|
|
let node = this;
|
|
while (node instanceof XULElement) {
|
|
if (node.id.startsWith("wrapper-")) {
|
|
inWrapper = true;
|
|
break;
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
|
|
if (!inWrapper) {
|
|
if (this.hasAttribute("original-width")) {
|
|
// If we were in a wrapper before and have a width stored, restore it now.
|
|
if (this.getAttribute("original-width") == "none")
|
|
this.removeAttribute("width");
|
|
else
|
|
this.setAttribute("width", this.getAttribute("original-width"));
|
|
|
|
this.removeAttribute("original-width");
|
|
}
|
|
|
|
// If we are a child of a menulist, and we aren't in a wrapper, we
|
|
// need to build our content right away, otherwise the menulist
|
|
// won't have proper sizing.
|
|
if (this.parentNode && this.parentNode.localName == "menulist")
|
|
this._ensureInitialized();
|
|
} else {
|
|
// But if we're in a wrapper, remove our children, because we're
|
|
// getting re-created when the toolbar customization closes.
|
|
this._teardown();
|
|
|
|
// Store our current width and set a safe small width when we show
|
|
// in a wrapper.
|
|
if (!this.hasAttribute("original-width")) {
|
|
this.setAttribute("original-width", this.hasAttribute("width") ?
|
|
this.getAttribute("width") : "none");
|
|
this.setAttribute("width", "100");
|
|
}
|
|
}
|
|
]]></constructor>
|
|
<destructor><![CDATA[
|
|
// Clean up when being destroyed.
|
|
this._removeListener();
|
|
]]></destructor>
|
|
<!--
|
|
- Make sure we remove our listener when the window is being destroyed
|
|
- or the widget teared down.
|
|
-->
|
|
<method name="_removeListener">
|
|
<body><![CDATA[
|
|
if (!this._initialized)
|
|
return;
|
|
|
|
this.MailServices.mailSession.RemoveFolderListener(this._listener);
|
|
]]></body>
|
|
</method>
|
|
|
|
<field name="_stringBundle">null</field>
|
|
|
|
<!--
|
|
- If non-null, the subFolders of this nsIMsgFolder will be used to
|
|
- populate this menu. If this is null, the menu will be populated
|
|
- using the root-folders for all accounts
|
|
-->
|
|
<field name="_parentFolder">null</field>
|
|
<property name="parentFolder"
|
|
onget="return this._parentFolder;"
|
|
onset="return this._parentFolder = val;"/>
|
|
|
|
<!--
|
|
- Various filtering modes can be used with this menu-binding. To use
|
|
- one of them, append the mode="foo" attribute to the element. When
|
|
- building the menu, we will then use this._filters[mode] as a filter
|
|
- function to eliminate folders that should not be shown.
|
|
-
|
|
- Note that extensions should feel free to plug in here!
|
|
-->
|
|
<field name="_filters"><![CDATA[({
|
|
// Returns true if messages can be filed in the folder
|
|
filing: function filter_filing(aFolder) {
|
|
if (!aFolder.server.canFileMessagesOnServer)
|
|
return false;
|
|
|
|
return (aFolder.canFileMessages || aFolder.hasSubFolders);
|
|
},
|
|
|
|
// Returns true if we can get mail for this folder. (usually this just
|
|
// means the "root" fake folder)
|
|
getMail: function filter_getMail(aFolder) {
|
|
if (aFolder.isServer && aFolder.server.type != "none")
|
|
return true;
|
|
if (aFolder.server.type == "nntp" || aFolder.server.type == "rss")
|
|
return true;
|
|
return false;
|
|
},
|
|
|
|
// Returns true if we can add filters to this folder/account
|
|
filters: function filter_filter(aFolder) {
|
|
// We can always filter news
|
|
if (aFolder.server.type == "nntp")
|
|
return true;
|
|
|
|
return aFolder.server.canHaveFilters;
|
|
},
|
|
|
|
subscribe: function filter_subscribe(aFolder) {
|
|
return aFolder.canSubscribe;
|
|
},
|
|
|
|
newFolder: function filter_newFolder(aFolder) {
|
|
return aFolder.canCreateSubfolders &&
|
|
aFolder.server.canCreateFoldersOnServer;
|
|
},
|
|
|
|
deferred: function filter_defered(aFolder) {
|
|
return aFolder.server.canCreateFoldersOnServer &&
|
|
!aFolder.supportsOffline;
|
|
},
|
|
|
|
// Folders that are not in a deferred account
|
|
notDeferred: function(aFolder) {
|
|
let server = aFolder.server;
|
|
return !(server instanceof Components.interfaces.nsIPop3IncomingServer &&
|
|
server.deferredToAccount);
|
|
},
|
|
|
|
// Folders that can be searched.
|
|
search: function filter_search(aFolder) {
|
|
if (!aFolder.server.canSearchMessages ||
|
|
aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
|
|
return false;
|
|
return true;
|
|
},
|
|
|
|
// Folders that can subscribe feeds.
|
|
feeds: function filter_feeds(aFolder) {
|
|
if (aFolder.server.type != "rss" ||
|
|
aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Trash) ||
|
|
aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
|
|
return false;
|
|
return true;
|
|
},
|
|
|
|
junk: function filter_junk(aFolder) {
|
|
// Don't show servers (nntp & any others) which do not allow search or filing
|
|
// I don't really understand why canSearchMessages is needed, but it was included in
|
|
// earlier code, so I include it as well.
|
|
if (!aFolder.server.canFileMessagesOnServer || !aFolder.server.canSearchMessages)
|
|
return false;
|
|
// show parents that might have usable subfolders, or usable folders
|
|
return aFolder.hasSubFolders || aFolder.canFileMessages;
|
|
}
|
|
})]]></field>
|
|
|
|
<!--
|
|
- The maximum number of entries in the "Recent" menu
|
|
-->
|
|
<field name="_MAXRECENT">15</field>
|
|
|
|
<!--
|
|
- Is this list containing only servers (accounts) and no real folders?
|
|
-->
|
|
<field name="_serversOnly">true</field>
|
|
|
|
<!--
|
|
- Our listener to let us know when folders change/appear/disappear so
|
|
- we can know to rebuild ourselves.
|
|
-->
|
|
<field name="_listener">
|
|
<![CDATA[({
|
|
_menu: this,
|
|
_clearMenu: function(aMenu) {
|
|
if (aMenu._teardown)
|
|
aMenu._teardown();
|
|
},
|
|
OnItemAdded: function act_add(aRDFParentItem, aItem) {
|
|
if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
|
|
return;
|
|
if (this._filterFunction && !this._filterFunction(aItem)) {
|
|
return;
|
|
}
|
|
//xxx we can optimize this later
|
|
//xxx I'm not quite sure why this isn't always a function
|
|
if (this._menu._teardown)
|
|
this._menu._teardown();
|
|
},
|
|
|
|
OnItemRemoved: function act_remove(aRDFParentItem, aItem) {
|
|
if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
|
|
return;
|
|
if (this._filterFunction && !this._filterFunction(aItem)) {
|
|
return;
|
|
}
|
|
//xxx we can optimize this later
|
|
if (this._menu._teardown)
|
|
this._menu._teardown();
|
|
},
|
|
|
|
//xxx I stole this listener list from nsMsgFolderDatasource.cpp, but
|
|
// someone should really document what events are fired when, so that
|
|
// we make sure we're updating at the right times.
|
|
OnItemPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
|
|
OnItemIntPropertyChanged: function(aItem, aProperty, aOld, aNew) {
|
|
var child = this._getChildForItem(aItem);
|
|
if (child)
|
|
this._menu._setCssSelectors(child._folder, child);
|
|
},
|
|
OnItemBoolPropertyChanged: function(aItem, aProperty, aOld, aNew) {
|
|
var child = this._getChildForItem(aItem);
|
|
if (child)
|
|
this._menu._setCssSelectors(child._folder, child);
|
|
},
|
|
OnItemUnicharPropertyChanged: function(aItem, aProperty, aOld, aNew) {
|
|
var child = this._getChildForItem(aItem);
|
|
if (child)
|
|
this._menu._setCssSelectors(child._folder, child);
|
|
},
|
|
OnItemPropertyFlagChanged: function(aItem, aProperty, aOld, aNew) {},
|
|
OnItemEvent: function(aFolder, aEvent) {
|
|
if (aEvent.toString() == "MRMTimeChanged") {
|
|
if (this._menu.getAttribute("showRecent") != "true" ||
|
|
!this._menu.firstChild || !this._menu.firstChild.firstChild)
|
|
return;
|
|
// if this folder is already in the recent menu, return.
|
|
if (this._getChildForItem(aFolder,
|
|
this._menu.firstChild.firstChild))
|
|
return;
|
|
}
|
|
// Special casing folder renames here, since they require more work
|
|
// since sort-order may have changed.
|
|
else if (aEvent.toString() == "RenameCompleted") {
|
|
if (!this._getChildForItem(aFolder))
|
|
return;
|
|
}
|
|
else
|
|
return;
|
|
// folder renamed, or new recent folder, so rebuild.
|
|
setTimeout(this._clearMenu, 0, this._menu);
|
|
},
|
|
|
|
/**
|
|
* Helper function to check and see whether we have a menuitem for this
|
|
* particular nsIMsgFolder
|
|
*
|
|
* @param aItem the nsIMsgFolder to check
|
|
* @param aMenu (optional) menu to look in, defaults to this._menu.
|
|
* @returns null if no child for that folder exists, otherwise the
|
|
* menuitem for that child
|
|
*/
|
|
_getChildForItem: function act__itemIsChild(aItem, aMenu) {
|
|
aMenu = aMenu || this._menu;
|
|
if (!aMenu || !aMenu.childNodes)
|
|
return null;
|
|
|
|
if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
|
|
return null;
|
|
for (let i = 0; i < aMenu.childNodes.length; i++) {
|
|
let folder = aMenu.childNodes[i]._folder;
|
|
if (folder && folder.URI == aItem.URI)
|
|
return aMenu.childNodes[i];
|
|
}
|
|
return null;
|
|
}
|
|
})]]></field>
|
|
|
|
<!--
|
|
- True if we have already built our menu-items and are now just
|
|
- listening for changes
|
|
-->
|
|
<field name="_initialized">false</field>
|
|
|
|
<!--
|
|
- Call this if you are unsure whether the menu-items have been built,
|
|
- but know that they need to be built now if they haven't.
|
|
-->
|
|
<method name="_ensureInitialized">
|
|
<body><![CDATA[
|
|
if (this._initialized)
|
|
return;
|
|
|
|
// I really wish they'd just make this global...
|
|
const Ci = Components.interfaces;
|
|
|
|
let folders;
|
|
|
|
// Figure out which folders to build. If we don't have a parent, then
|
|
// we assume we should build the top-level accounts. (Actually we
|
|
// build the fake root folders for those accounts.)
|
|
if (!this._parentFolder) {
|
|
let accounts = this.allAccountsSorted(true);
|
|
|
|
// Now generate our folder-list. Note that we'll special case this
|
|
// situation below, to avoid destroying the sort order we just made
|
|
folders = accounts.map(acct => acct.incomingServer.rootFolder);
|
|
} else {
|
|
// If we do have a parent folder, then we just build based on those
|
|
// subFolders for that parent.
|
|
folders = this.toArray(this.fixIterator(this._parentFolder.subFolders,
|
|
Ci.nsIMsgFolder));
|
|
}
|
|
|
|
this._build(folders);
|
|
|
|
// Lastly, we add a listener to get notified of changes in the folder
|
|
// structure.
|
|
this.MailServices.mailSession.AddFolderListener(this._listener,
|
|
Ci.nsIFolderListener.all);
|
|
|
|
this._initialized = true;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
- Actually constructs the menu-items based on the folders given.
|
|
-
|
|
- @param aFolders An array of nsIMsgFolders to use for building.
|
|
-->
|
|
<method name="_build">
|
|
<parameter name="aFolders"/>
|
|
<body><![CDATA[
|
|
let folders;
|
|
let excludeServers = [];
|
|
let disableServers = [];
|
|
|
|
// excludeServers attribute is a comma separated list of server keys.
|
|
if (this.hasAttribute("excludeServers"))
|
|
excludeServers = this.getAttribute("excludeServers").split(",");
|
|
|
|
// disableServers attribute is a comma separated list of server keys.
|
|
if (this.hasAttribute("disableServers"))
|
|
disableServers = this.getAttribute("disableServers").split(",");
|
|
|
|
// Extensions and other consumers can add to these modes too, see the
|
|
// above note on the _filters field.
|
|
var mode = this.getAttribute("mode");
|
|
if (mode && mode != "") {
|
|
var filterFunction = this._filters[mode];
|
|
folders = aFolders.filter(filterFunction);
|
|
this._listener._filterFunction = filterFunction;
|
|
} else {
|
|
folders = aFolders;
|
|
}
|
|
|
|
if (excludeServers.length > 0) {
|
|
folders = folders.filter(function(aFolder) {
|
|
return !(excludeServers.indexOf(aFolder.server.key) != -1); });
|
|
}
|
|
|
|
/* This code block will do the following: Add a menu item that refers
|
|
back to the parent folder when there is a showFileHereLabel
|
|
attribute or no mode attribute. However the code won't add such a
|
|
menu item if one of the following conditions is met:
|
|
(*) There is no parent folder
|
|
(*) Folder is server and showAccountsFileHere is explicitly false
|
|
(*) Current folder has a mode, the parent folder can be selected,
|
|
no messages can be filed into the parent folder (e.g. when the
|
|
parent folder is a news group or news server) and the folder
|
|
mode is not equal to newFolder
|
|
|
|
The menu item will have the value of the fileHereLabel attribute as
|
|
label or if the attribute does not exist the name of the parent
|
|
folder instead.
|
|
*/
|
|
let parent = this._parentFolder;
|
|
if (parent && (this.getAttribute("showFileHereLabel") == "true" || !mode)) {
|
|
let showAccountsFileHere = this.getAttribute("showAccountsFileHere");
|
|
if ((!parent.isServer || showAccountsFileHere != "false") &&
|
|
(!mode || mode == "newFolder" || parent.noSelect ||
|
|
parent.canFileMessages || showAccountsFileHere == "true")) {
|
|
var menuitem = document.createElement("menuitem");
|
|
menuitem._folder = this._parentFolder;
|
|
menuitem.setAttribute("generated", "true");
|
|
if (this.hasAttribute("fileHereLabel")) {
|
|
menuitem.setAttribute("label", this.getAttribute("fileHereLabel"));
|
|
menuitem.setAttribute("accesskey", this.getAttribute("fileHereAccessKey"));
|
|
} else {
|
|
menuitem.setAttribute("label", this._parentFolder.prettyName);
|
|
menuitem.setAttribute("class", "folderMenuItem menuitem-iconic");
|
|
this._setCssSelectors(this._parentFolder, menuitem);
|
|
}
|
|
// Eww. have to support some legacy code here...
|
|
menuitem.setAttribute("id", this._parentFolder.URI);
|
|
this.appendChild(menuitem);
|
|
|
|
if (this._parentFolder.noSelect)
|
|
menuitem.setAttribute("disabled", "true");
|
|
|
|
var sep= document.createElement("menuseparator");
|
|
sep.setAttribute("generated", "true");
|
|
this.appendChild(sep);
|
|
}
|
|
}
|
|
|
|
let globalInboxFolder = null;
|
|
// See if this is the toplevel menu (usually with accounts).
|
|
if (!this._parentFolder) {
|
|
// Some menus want a "Recent" option, but that should only be on our
|
|
// top-level menu
|
|
if (this.getAttribute("showRecent") == "true")
|
|
this._buildRecentMenu();
|
|
// If we are showing the accounts for deferring, move Local Folders to the top.
|
|
if (mode == "deferred") {
|
|
globalInboxFolder = this.MailServices.accounts.localFoldersServer
|
|
.rootFolder;
|
|
let localFoldersIndex = folders.indexOf(globalInboxFolder);
|
|
if (localFoldersIndex != -1) {
|
|
folders.splice(localFoldersIndex, 1);
|
|
folders.unshift(globalInboxFolder);
|
|
}
|
|
}
|
|
// If we're the root of the folder hierarchy, then we actually don't
|
|
// want to sort the folders, but rather the accounts to which the
|
|
// folders belong. Since that sorting was already done, we don't need
|
|
// to do anything for that case here.
|
|
} else {
|
|
// Sorts the list of folders. We give first priority to the sortKey
|
|
// property if it is available, otherwise a case-insensitive
|
|
// comparison of names.
|
|
folders = folders.sort(function nameCompare(a, b) {
|
|
return a.compareSortKeys(b);
|
|
});
|
|
}
|
|
|
|
/* In some cases, the user wants to have a list of subfolders for only
|
|
* some account types (or maybe all of them). So we use this to
|
|
* determine what the user wanted.
|
|
*/
|
|
var shouldExpand;
|
|
var labels = null;
|
|
if (this.getAttribute("expandFolders") == "true" ||
|
|
!this.hasAttribute("expandFolders")) {
|
|
shouldExpand = function (e) { return true; };
|
|
} else if (this.getAttribute("expandFolders") == "false") {
|
|
shouldExpand = function (e) { return false; };
|
|
} else {
|
|
/* We want a subfolder list for only some servers. We also may need
|
|
* to create headers to select the servers. If so, then headlabels
|
|
* is a comma-delimited list of labels corresponding to the server
|
|
* types specified in expandFolders.
|
|
*/
|
|
var types = this.getAttribute("expandFolders").split(/ *, */);
|
|
// Set the labels. labels[type] = label
|
|
if (this.hasAttribute("headlabels")) {
|
|
var labelNames = this.getAttribute("headlabels").split(/ *, */);
|
|
labels = {};
|
|
// If the length isn't equal, don't give them any of the labels,
|
|
// since any combination will probably be wrong.
|
|
if (labelNames.length == types.length) {
|
|
for (var index in types)
|
|
labels[types[index]] = labelNames[index];
|
|
}
|
|
}
|
|
shouldExpand = function (e) { return types.indexOf(e) != -1; };
|
|
}
|
|
|
|
// We need to call this, or hasSubFolders will always return false.
|
|
// Remove this workaround when Bug 502900 is fixed.
|
|
this.MailUtils.discoverFolders();
|
|
this._serversOnly = true;
|
|
|
|
for (let folder of folders) {
|
|
let node;
|
|
if (!folder.isServer)
|
|
this._serversOnly = false;
|
|
|
|
// If we're going to add subFolders, we need to make menus, not
|
|
// menuitems.
|
|
if (!folder.hasSubFolders || !shouldExpand(folder.server.type)) {
|
|
node = document.createElement("menuitem");
|
|
// Grumble, grumble, legacy code support
|
|
node.setAttribute("id", folder.URI);
|
|
node.setAttribute("class", "folderMenuItem menuitem-iconic");
|
|
node.setAttribute("generated", "true");
|
|
this.appendChild(node);
|
|
} else {
|
|
this._serversOnly = false;
|
|
//xxx this is slightly problematic in that we haven't confirmed
|
|
// whether any of the subfolders will pass the filter
|
|
node = document.createElement("menu");
|
|
node.setAttribute("class", "folderMenuItem menu-iconic");
|
|
node.setAttribute("generated", "true");
|
|
this.appendChild(node);
|
|
|
|
// Create the submenu
|
|
// (We must use cloneNode here because on OS X the native menu
|
|
// functionality and very sad limitations of XBL1 cause the bindings
|
|
// to never get created for popup if we create a new element. We
|
|
// perform a shallow clone to avoid picking up any of our children.)
|
|
var popup = this.cloneNode(false);
|
|
popup._parentFolder = folder;
|
|
popup.setAttribute("class", this.getAttribute("class"));
|
|
popup.setAttribute("type", this.getAttribute("type"));
|
|
if (this.hasAttribute("fileHereLabel"))
|
|
popup.setAttribute("fileHereLabel",
|
|
this.getAttribute("fileHereLabel"));
|
|
popup.setAttribute("showFileHereLabel",
|
|
this.getAttribute("showFileHereLabel"));
|
|
popup.setAttribute("oncommand",
|
|
this.getAttribute("oncommand"));
|
|
popup.setAttribute("mode",
|
|
this.getAttribute("mode"));
|
|
if (this.hasAttribute("disableServers"))
|
|
popup.setAttribute("disableServers",
|
|
this.getAttribute("disableServers"));
|
|
if (this.hasAttribute("position"))
|
|
popup.setAttribute("position",
|
|
this.getAttribute("position"));
|
|
|
|
// If there are labels, add the labels now
|
|
if (labels) {
|
|
var serverNode = document.createElement("menuitem");
|
|
serverNode.setAttribute("label", labels[folder.server.type]);
|
|
serverNode._folder = folder;
|
|
serverNode.setAttribute("generated", "true");
|
|
popup.appendChild(serverNode);
|
|
var sep = document.createElement("menuseparator");
|
|
sep.setAttribute("generated", "true");
|
|
popup.appendChild(sep);
|
|
}
|
|
|
|
popup.setAttribute("generated", "true");
|
|
node.appendChild(popup);
|
|
}
|
|
|
|
if (disableServers.indexOf(folder.server.key) != -1)
|
|
node.setAttribute("disabled", "true");
|
|
|
|
node._folder = folder;
|
|
let label = "";
|
|
if (mode == "deferred" && folder.isServer &&
|
|
folder.server.rootFolder == globalInboxFolder) {
|
|
label = this._stringBundle.get("globalInbox", [folder.prettyName]);
|
|
} else {
|
|
label = folder.prettyName;
|
|
}
|
|
node.setAttribute("label", label);
|
|
this._setCssSelectors(folder, node);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
- Builds a submenu with all of the recently used folders in it, to
|
|
- allow for easy access.
|
|
-->
|
|
<method name="_buildRecentMenu">
|
|
<body><![CDATA[
|
|
const Ci = Components.interfaces;
|
|
|
|
// Iterate through all folders in all accounts, and find 15 (_MAXRECENT)
|
|
// of most recently modified ones.
|
|
let allFolders = this.toArray(
|
|
this.fixIterator(this.MailServices.accounts.allFolders, Ci.nsIMsgFolder));
|
|
|
|
allFolders = allFolders.filter(f => f.canFileMessages);
|
|
|
|
let recentFolders = this.getMostRecentFolders(allFolders,
|
|
this._MAXRECENT,
|
|
"MRMTime");
|
|
|
|
// Cache the pretty names so that they do not need to be fetched
|
|
// _MAXRECENT^2 times later.
|
|
recentFolders = recentFolders.map(
|
|
function (f) { return { folder: f, name: f.prettyName } });
|
|
|
|
// Because we're scanning across multiple accounts, we can end up with
|
|
// several folders with the same name. Find those dupes.
|
|
let dupeNames = new Set();
|
|
for (let i = 0; i < recentFolders.length; i++) {
|
|
for (let j = i + 1; j < recentFolders.length; j++) {
|
|
if (recentFolders[i].name == recentFolders[j].name)
|
|
dupeNames.add(recentFolders[i].name);
|
|
}
|
|
}
|
|
|
|
for (let folderItem of recentFolders) {
|
|
// If this folder name appears multiple times in the recent list,
|
|
// append the server name to disambiguate.
|
|
// TODO:
|
|
// - maybe this could use verboseFolderFormat from messenger.properties
|
|
// instead of hardcoded " - ".
|
|
// - disambiguate folders with same name in same account
|
|
// (in different subtrees).
|
|
let label = folderItem.name;
|
|
if (dupeNames.has(label))
|
|
label += " - " + folderItem.folder.server.prettyName;
|
|
|
|
folderItem.label = label;
|
|
}
|
|
|
|
// Make sure the entries are sorted alphabetically.
|
|
recentFolders.sort((a, b) => this.folderNameCompare(a.label, b.label));
|
|
|
|
// Now create the Recent folder and its children
|
|
var menu = document.createElement("menu");
|
|
menu.setAttribute("label", this.getAttribute("recentLabel"));
|
|
menu.setAttribute("accesskey", this.getAttribute("recentAccessKey"));
|
|
var popup = document.createElement("menupopup");
|
|
popup.setAttribute("class", this.getAttribute("class"));
|
|
popup.setAttribute("generated", "true");
|
|
menu.appendChild(popup);
|
|
|
|
// Create entries for each of the recent folders.
|
|
for (let folderItem of recentFolders) {
|
|
let node = document.createElement("menuitem");
|
|
|
|
node.setAttribute("label", folderItem.label);
|
|
node._folder = folderItem.folder;
|
|
|
|
node.setAttribute("class", "folderMenuItem menuitem-iconic");
|
|
this._setCssSelectors(folderItem.folder, node);
|
|
node.setAttribute("generated", "true");
|
|
popup.appendChild(node);
|
|
}
|
|
menu.setAttribute("generated", "true");
|
|
this.appendChild(menu);
|
|
if (!recentFolders.length)
|
|
menu.setAttribute("disabled", "true");
|
|
|
|
var sep = document.createElement("menuseparator");
|
|
sep.setAttribute("generated", "true");
|
|
this.appendChild(sep);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
- This function adds attributes on menu/menuitems to make it easier for
|
|
- css to style them.
|
|
-
|
|
- @param aFolder the folder that corresponds to the menu/menuitem
|
|
- @param aMenuNode the actual DOM node to set attributes on
|
|
-->
|
|
<method name="_setCssSelectors">
|
|
<parameter name="aFolder"/>
|
|
<parameter name="aMenuNode"/>
|
|
<body><![CDATA[
|
|
|
|
// First set the SpecialFolder attribute
|
|
aMenuNode.setAttribute("SpecialFolder", this.getSpecialFolderString(aFolder));
|
|
|
|
// Now set the biffState
|
|
let biffStates = ["NewMail", "NoMail", "UnknownMail"];
|
|
for (let state of biffStates) {
|
|
if (aFolder.biffState ==
|
|
Components.interfaces.nsIMsgFolder["nsMsgBiffState_" + state]) {
|
|
aMenuNode.setAttribute("BiffState", state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
aMenuNode.setAttribute("IsServer", aFolder.isServer);
|
|
aMenuNode.setAttribute("IsSecure", aFolder.server.isSecure);
|
|
aMenuNode.setAttribute("ServerType", aFolder.server.type);
|
|
aMenuNode.setAttribute("IsFeedFolder",
|
|
(this.FeedUtils.getFeedUrlsInFolder(aFolder) ? true : false));
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
- This function returns a formatted display name for a menulist
|
|
- selected folder. The desired format is set as the 'displayformat'
|
|
- attribute of the folderpicker's <menulist>, one of:
|
|
- 'name' (default) - Folder
|
|
- 'verbose' - Folder on Account
|
|
- 'path' - Account/Folder/Subfolder
|
|
-
|
|
- @param aFolder the folder that corresponds to the menu/menuitem
|
|
- @return string display name
|
|
-->
|
|
<field name="_displayformat">null</field>
|
|
<method name="getDisplayName">
|
|
<parameter name="aFolder"/>
|
|
<body><![CDATA[
|
|
if (aFolder.isServer)
|
|
return aFolder.prettyName;
|
|
|
|
if (this._displayformat == "verbose")
|
|
return this._stringBundle.getFormattedString("verboseFolderFormat",
|
|
[aFolder.prettyName, aFolder.server.prettyName]);
|
|
|
|
if (this._displayformat == "path")
|
|
return this.FeedUtils.getFolderPrettyPath(aFolder) || aFolder.name;
|
|
|
|
return aFolder.name;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
- Makes a given folder selected.
|
|
-
|
|
- @param aFolder the folder to select (if none, then Choose Folder)
|
|
- @note If aFolder is not in this popup, but is instead a descendant of
|
|
- a member of the popup, that ancestor will be selected.
|
|
- @return true if any usable folder was found, otherwise false.
|
|
-->
|
|
<method name="selectFolder">
|
|
<parameter name="aFolder"/>
|
|
<body><![CDATA[
|
|
// Set the label of the menulist element as if aFolder had been selected.
|
|
function setupParent(aFolder, aMenulist, aNoFolders) {
|
|
let menupopup = aMenulist.menupopup;
|
|
if (aFolder) {
|
|
aMenulist.setAttribute("label", menupopup.getDisplayName(aFolder));
|
|
} else {
|
|
aMenulist.setAttribute("label", menupopup._stringBundle.getString(
|
|
aNoFolders ? "noFolders" :
|
|
(menupopup._serversOnly ? "chooseAccount" : "chooseFolder")));
|
|
}
|
|
aMenulist.setAttribute("value",
|
|
aFolder ? aFolder.URI : "");
|
|
aMenulist.setAttribute("IsServer",
|
|
aFolder ? aFolder.isServer : false);
|
|
aMenulist.setAttribute("IsSecure",
|
|
aFolder ? aFolder.server.isSecure : false);
|
|
aMenulist.setAttribute("ServerType",
|
|
aFolder ? aFolder.server.type : "none");
|
|
aMenulist.setAttribute("SpecialFolder",
|
|
aFolder ? menupopup.getSpecialFolderString(aFolder) : "none");
|
|
aMenulist.setAttribute("IsFeedFolder", Boolean(
|
|
aFolder && menupopup.FeedUtils.getFeedUrlsInFolder(aFolder)));
|
|
}
|
|
|
|
let folder;
|
|
if (aFolder) {
|
|
for (let child of this.childNodes) {
|
|
if (child && child._folder && !child.disabled &&
|
|
(child._folder.URI == aFolder.URI ||
|
|
(child.tagName == "menu" &&
|
|
child._folder.isAncestorOf(aFolder)))) {
|
|
if (child._folder.URI == aFolder.URI)
|
|
this.parentNode.selectedItem = child;
|
|
folder = aFolder;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the caller specified a folder to select and it was not
|
|
// found, or if the caller didn't pass a folder (meaning a logical
|
|
// and valid folder wasn't determined), don't blow up but reset
|
|
// attributes and set a nice Choose Folder label so the user may
|
|
// select a valid folder per the filter for this picker. If there are
|
|
// no children, then no folder passed the filter; disable the menulist
|
|
// as there's nothing to choose from.
|
|
let noFolders;
|
|
if (!this.childElementCount)
|
|
{
|
|
this.parentNode.setAttribute("disabled", true);
|
|
noFolders = true;
|
|
}
|
|
else
|
|
{
|
|
this.parentNode.removeAttribute("disabled");
|
|
noFolders = false;
|
|
}
|
|
|
|
setupParent(folder, this.parentNode, noFolders);
|
|
return folder ? true : false;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
- Removes all menu-items for this popup, resets all fields, and
|
|
- removes the listener. This function is invoked when a change
|
|
- that affects this menu is detected by our listener.
|
|
-->
|
|
<method name="_teardown">
|
|
<body><![CDATA[
|
|
for (let i = this.childNodes.length - 1; i >= 0; i--) {
|
|
let child = this.childNodes[i];
|
|
if (child.getAttribute("generated") != "true")
|
|
continue;
|
|
if ("_teardown" in child)
|
|
child._teardown();
|
|
child.remove();
|
|
}
|
|
|
|
this._removeListener();
|
|
|
|
this._initialized = false;
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<!--
|
|
- In order to improve performance, we're not going to build any of the
|
|
- menu until we're shown (unless we're the child of a menulist, see
|
|
- note in the constructor).
|
|
-
|
|
- @note _ensureInitialized can be called repeatedly without issue, so
|
|
- don't worry about it here.
|
|
-->
|
|
<handler event="popupshowing" phase="capturing">
|
|
this._ensureInitialized();
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
</bindings>
|