summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/search/content/FilterEditor.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/search/content/FilterEditor.js')
-rw-r--r--comm/mailnews/search/content/FilterEditor.js809
1 files changed, 809 insertions, 0 deletions
diff --git a/comm/mailnews/search/content/FilterEditor.js b/comm/mailnews/search/content/FilterEditor.js
new file mode 100644
index 0000000000..3b93773192
--- /dev/null
+++ b/comm/mailnews/search/content/FilterEditor.js
@@ -0,0 +1,809 @@
+/* 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/. */
+
+/* import-globals-from searchTerm.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+// The actual filter that we're editing if it is a _saved_ filter or prefill;
+// void otherwise.
+var gFilter;
+// cache the key elements we need
+var gFilterList;
+// The filter name as it appears in the "Filter Name" field of dialog.
+var gFilterNameElement;
+var gFilterTypeSelector;
+var gFilterBundle;
+var gPreFillName;
+var gFilterActionList;
+var gCustomActions = null;
+var gFilterType;
+var gFilterPosition = 0;
+
+var gFilterActionStrings = [
+ "none",
+ "movemessage",
+ "setpriorityto",
+ "deletemessage",
+ "markasread",
+ "ignorethread",
+ "watchthread",
+ "markasflagged",
+ "label",
+ "replytomessage",
+ "forwardmessage",
+ "stopexecution",
+ "deletefrompopserver",
+ "leaveonpopserver",
+ "setjunkscore",
+ "fetchfrompopserver",
+ "copymessage",
+ "addtagtomessage",
+ "ignoresubthread",
+ "markasunread",
+];
+
+// A temporary filter with the current state of actions in the UI.
+var gTempFilter = null;
+// nsIMsgRuleAction[] - the currently defined actions in the order they will be run.
+var gActionListOrdered = null;
+
+var gFilterEditorMsgWindow = null;
+
+window.addEventListener("DOMContentLoaded", filterEditorOnLoad, { once: true });
+document.addEventListener("dialogaccept", onAccept);
+
+function filterEditorOnLoad() {
+ getCustomActions();
+ initializeSearchWidgets();
+ initializeFilterWidgets();
+
+ gFilterBundle = document.getElementById("bundle_filter");
+
+ if ("arguments" in window && window.arguments[0]) {
+ var args = window.arguments[0];
+
+ if ("filterList" in args) {
+ gFilterList = args.filterList;
+ // the postPlugin filters cannot be applied to servers that are
+ // deferred, (you must define them on the deferredTo server instead).
+ let server = gFilterList.folder.server;
+ if (server.rootFolder != server.rootMsgFolder) {
+ gFilterTypeSelector.disableDeferredAccount();
+ }
+ }
+
+ if ("filterPosition" in args) {
+ gFilterPosition = args.filterPosition;
+ }
+
+ if ("filter" in args) {
+ // editing a filter
+ gFilter = window.arguments[0].filter;
+ initializeDialog(gFilter);
+ } else {
+ if (gFilterList) {
+ setSearchScope(getScopeFromFilterList(gFilterList));
+ }
+ // if doing prefill filter create a new filter and populate it.
+ if ("filterName" in args) {
+ gPreFillName = args.filterName;
+
+ // Passing null as the parameter to createFilter to keep the name empty
+ // until later where we assign the name.
+ gFilter = gFilterList.createFilter(null);
+
+ var term = gFilter.createTerm();
+
+ term.attrib = Ci.nsMsgSearchAttrib.Default;
+ if ("fieldName" in args && args.fieldName) {
+ // fieldName should contain the name of the field in which to search,
+ // from nsMsgSearchTerm.cpp::SearchAttribEntryTable, e.g. "to" or "cc"
+ try {
+ term.attrib = term.getAttributeFromString(args.fieldName);
+ } catch (e) {
+ /* Invalid string is fine, just ignore it. */
+ }
+ }
+ if (term.attrib == Ci.nsMsgSearchAttrib.Default) {
+ term.attrib = Ci.nsMsgSearchAttrib.Sender;
+ }
+
+ term.op = Ci.nsMsgSearchOp.Is;
+ term.booleanAnd = gSearchBooleanRadiogroup.value == "and";
+
+ var termValue = term.value;
+ termValue.attrib = term.attrib;
+ termValue.str = gPreFillName;
+
+ term.value = termValue;
+
+ gFilter.appendTerm(term);
+
+ // the default action for news filters is Delete
+ // for everything else, it's MoveToFolder
+ var filterAction = gFilter.createAction();
+ filterAction.type =
+ getScopeFromFilterList(gFilterList) == Ci.nsMsgSearchScope.newsFilter
+ ? Ci.nsMsgFilterAction.Delete
+ : Ci.nsMsgFilterAction.MoveToFolder;
+ gFilter.appendAction(filterAction);
+ initializeDialog(gFilter);
+ } else if ("copiedFilter" in args) {
+ // we are copying a filter
+ let copiedFilter = args.copiedFilter;
+ let copiedName = gFilterBundle.getFormattedString(
+ "copyToNewFilterName",
+ [copiedFilter.filterName]
+ );
+ let newFilter = gFilterList.createFilter(copiedName);
+
+ // copy the actions
+ for (let i = 0; i < copiedFilter.actionCount; i++) {
+ let filterAction = copiedFilter.getActionAt(i);
+ newFilter.appendAction(filterAction);
+ }
+
+ // copy the search terms
+ for (let searchTerm of copiedFilter.searchTerms) {
+ let newTerm = newFilter.createTerm();
+ newTerm.attrib = searchTerm.attrib;
+ newTerm.op = searchTerm.op;
+ newTerm.booleanAnd = searchTerm.booleanAnd;
+ newTerm.value = searchTerm.value;
+ newFilter.appendTerm(newTerm);
+ }
+
+ newFilter.filterType = copiedFilter.filterType;
+
+ gPreFillName = copiedName;
+ gFilter = newFilter;
+
+ initializeDialog(gFilter);
+
+ // We reset the filter name, because otherwise the saveFilter()
+ // function thinks we are editing a filter, and will thus skip the name
+ // uniqueness check.
+ gFilter.filterName = "";
+ } else {
+ // fake the first more button press
+ onMore(null);
+ }
+ }
+ }
+
+ if (!gFilter) {
+ // This is a new filter. Set to both Incoming and Manual contexts.
+ gFilterTypeSelector.setType(
+ Ci.nsMsgFilterType.Incoming | Ci.nsMsgFilterType.Manual
+ );
+ }
+
+ // in the case of a new filter, we may not have an action row yet.
+ ensureActionRow();
+ gFilterType = gFilterTypeSelector.getType();
+
+ gFilterNameElement.select();
+ // This call is required on mac and linux. It has no effect under win32. See bug 94800.
+ gFilterNameElement.focus();
+}
+
+function onEnterInSearchTerm(event) {
+ if (event.ctrlKey || (Services.appinfo.OS == "Darwin" && event.metaKey)) {
+ // If accel key (Ctrl on Win/Linux, Cmd on Mac) was held too, accept the dialog.
+ document.querySelector("dialog").acceptDialog();
+ } else {
+ // If only plain Enter was pressed, add a new rule line.
+ onMore(event);
+ }
+}
+
+function onAccept(event) {
+ try {
+ if (!saveFilter()) {
+ event.preventDefault();
+ return;
+ }
+ } catch (e) {
+ console.error(e);
+ event.preventDefault();
+ return;
+ }
+
+ // parent should refresh filter list..
+ // this should REALLY only happen when some criteria changes that
+ // are displayed in the filter dialog, like the filter name
+ window.arguments[0].refresh = true;
+ window.arguments[0].newFilter = gFilter;
+}
+
+function duplicateFilterNameExists(filterName) {
+ if (gFilterList) {
+ for (var i = 0; i < gFilterList.filterCount; i++) {
+ if (filterName == gFilterList.getFilterAt(i).filterName) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function getScopeFromFilterList(filterList) {
+ if (!filterList) {
+ dump("yikes, null filterList\n");
+ return Ci.nsMsgSearchScope.offlineMail;
+ }
+ return filterList.folder.server.filterScope;
+}
+
+function getScope(filter) {
+ return getScopeFromFilterList(filter.filterList);
+}
+
+function initializeFilterWidgets() {
+ gFilterNameElement = document.getElementById("filterName");
+ gFilterActionList = document.getElementById("filterActionList");
+ initializeFilterTypeSelector();
+}
+
+function initializeFilterTypeSelector() {
+ /**
+ * This object controls code interaction with the widget allowing specifying
+ * the filter type (event when the filter is run).
+ */
+ gFilterTypeSelector = {
+ checkBoxManual: document.getElementById("runManual"),
+ checkBoxIncoming: document.getElementById("runIncoming"),
+
+ menulistIncoming: document.getElementById("pluginsRunOrder"),
+
+ menuitemBeforePlugins: document.getElementById("runBeforePlugins"),
+ menuitemAfterPlugins: document.getElementById("runAfterPlugins"),
+
+ checkBoxArchive: document.getElementById("runArchive"),
+ checkBoxOutgoing: document.getElementById("runOutgoing"),
+ checkBoxPeriodic: document.getElementById("runPeriodic"),
+
+ /**
+ * Returns the currently set filter type (checkboxes) in terms
+ * of a Ci.Ci.nsMsgFilterType value.
+ */
+ getType() {
+ let type = Ci.nsMsgFilterType.None;
+
+ if (this.checkBoxManual.checked) {
+ type |= Ci.nsMsgFilterType.Manual;
+ }
+
+ if (this.checkBoxIncoming.checked) {
+ if (this.menulistIncoming.selectedItem == this.menuitemAfterPlugins) {
+ type |= Ci.nsMsgFilterType.PostPlugin;
+ } else if (
+ getScopeFromFilterList(gFilterList) == Ci.nsMsgSearchScope.newsFilter
+ ) {
+ type |= Ci.nsMsgFilterType.NewsRule;
+ } else {
+ type |= Ci.nsMsgFilterType.InboxRule;
+ }
+ }
+
+ if (this.checkBoxArchive.checked) {
+ type |= Ci.nsMsgFilterType.Archive;
+ }
+
+ if (this.checkBoxOutgoing.checked) {
+ type |= Ci.nsMsgFilterType.PostOutgoing;
+ }
+
+ if (this.checkBoxPeriodic.checked) {
+ type |= Ci.nsMsgFilterType.Periodic;
+ }
+
+ return type;
+ },
+
+ /**
+ * Sets the checkboxes to represent the filter type passed in.
+ *
+ * @param aType the filter type to set in terms
+ * of Ci.Ci.nsMsgFilterType values.
+ */
+ setType(aType) {
+ // If there is no type (event) requested, force "when manually run"
+ if (aType == Ci.nsMsgFilterType.None) {
+ aType = Ci.nsMsgFilterType.Manual;
+ }
+
+ this.checkBoxManual.checked = aType & Ci.nsMsgFilterType.Manual;
+
+ this.checkBoxIncoming.checked =
+ aType & (Ci.nsMsgFilterType.PostPlugin | Ci.nsMsgFilterType.Incoming);
+
+ this.menulistIncoming.selectedItem =
+ aType & Ci.nsMsgFilterType.PostPlugin
+ ? this.menuitemAfterPlugins
+ : this.menuitemBeforePlugins;
+
+ this.checkBoxArchive.checked = aType & Ci.nsMsgFilterType.Archive;
+
+ this.checkBoxOutgoing.checked = aType & Ci.nsMsgFilterType.PostOutgoing;
+
+ this.checkBoxPeriodic.checked = aType & Ci.nsMsgFilterType.Periodic;
+ const periodMinutes = gFilterList.folder.server.getIntValue(
+ "periodicFilterRateMinutes"
+ );
+ document.getElementById("runPeriodic").label = PluralForm.get(
+ periodMinutes,
+ gFilterBundle.getString("contextPeriodic.label")
+ ).replace("#1", periodMinutes);
+
+ this.updateClassificationMenu();
+ },
+
+ /**
+ * Enable the "before/after classification" menulist depending on
+ * whether "run when incoming mail" is selected.
+ */
+ updateClassificationMenu() {
+ this.menulistIncoming.disabled = !this.checkBoxIncoming.checked;
+ updateFilterType();
+ },
+
+ /**
+ * Disable the options unsuitable for deferred accounts.
+ */
+ disableDeferredAccount() {
+ this.menuitemAfterPlugins.disabled = true;
+ this.checkBoxOutgoing.disabled = true;
+ },
+ };
+}
+
+function initializeDialog(filter) {
+ gFilterNameElement.value = filter.filterName;
+ gFilterTypeSelector.setType(filter.filterType);
+
+ let numActions = filter.actionCount;
+ for (let actionIndex = 0; actionIndex < numActions; actionIndex++) {
+ let filterAction = filter.getActionAt(actionIndex);
+
+ let newActionRow = document.createXULElement("richlistitem", {
+ is: "ruleaction-richlistitem",
+ });
+ newActionRow.setAttribute("initialActionIndex", actionIndex);
+ newActionRow.className = "ruleaction";
+ gFilterActionList.appendChild(newActionRow);
+ newActionRow.setAttribute(
+ "value",
+ filterAction.type == Ci.nsMsgFilterAction.Custom
+ ? filterAction.customId
+ : gFilterActionStrings[filterAction.type]
+ );
+ newActionRow.setAttribute("onfocus", "this.storeFocus();");
+ }
+
+ var gSearchScope = getFilterScope(
+ getScope(filter),
+ filter.filterType,
+ filter.filterList
+ );
+ initializeSearchRows(gSearchScope, filter.searchTerms);
+ setFilterScope(filter.filterType, filter.filterList);
+}
+
+function ensureActionRow() {
+ // make sure we have at least one action row visible to the user
+ if (!gFilterActionList.getRowCount()) {
+ let newActionRow = document.createXULElement("richlistitem", {
+ is: "ruleaction-richlistitem",
+ });
+ newActionRow.className = "ruleaction";
+ gFilterActionList.appendChild(newActionRow);
+ newActionRow.mRemoveButton.disabled = true;
+ }
+}
+
+// move to overlay
+function saveFilter() {
+ // See if at least one filter type (activation event) is selected.
+ if (gFilterType == Ci.nsMsgFilterType.None) {
+ Services.prompt.alert(
+ window,
+ gFilterBundle.getString("mustHaveFilterTypeTitle"),
+ gFilterBundle.getString("mustHaveFilterTypeMessage")
+ );
+ return false;
+ }
+
+ let filterName = gFilterNameElement.value;
+ // If we think have a duplicate, then we need to check that if we
+ // have an original filter name (i.e. we are editing a filter), then
+ // we must check that the original is not the current as that is what
+ // the duplicateFilterNameExists function will have picked up.
+ if (
+ (!gFilter || gFilter.filterName != filterName) &&
+ duplicateFilterNameExists(filterName)
+ ) {
+ Services.prompt.alert(
+ window,
+ gFilterBundle.getString("cannotHaveDuplicateFilterTitle"),
+ gFilterBundle.getString("cannotHaveDuplicateFilterMessage")
+ );
+ return false;
+ }
+
+ // Check that all of the search attributes and operators are valid.
+ function rule_desc(index, obj) {
+ return (
+ index +
+ 1 +
+ " (" +
+ obj.searchattribute.label +
+ ", " +
+ obj.searchoperator.label +
+ ")"
+ );
+ }
+
+ let invalidRule = false;
+ for (let index = 0; index < gSearchTerms.length; index++) {
+ let obj = gSearchTerms[index].obj;
+ // We don't need to check validity of matchAll terms
+ if (obj.matchAll) {
+ continue;
+ }
+
+ // the term might be an offscreen one that we haven't initialized yet
+ let searchTerm = obj.searchTerm;
+ if (!searchTerm && !gSearchTerms[index].initialized) {
+ continue;
+ }
+
+ if (isNaN(obj.searchattribute.value)) {
+ // is this a custom term?
+ let customTerm = MailServices.filters.getCustomTerm(
+ obj.searchattribute.value
+ );
+ if (!customTerm) {
+ invalidRule = true;
+ console.error(
+ "Filter not saved because custom search term '" +
+ obj.searchattribute.value +
+ "' in rule " +
+ rule_desc(index, obj) +
+ " not found"
+ );
+ } else if (
+ !customTerm.getAvailable(obj.searchScope, obj.searchattribute.value)
+ ) {
+ invalidRule = true;
+ console.error(
+ "Filter not saved because custom search term '" +
+ customTerm.name +
+ "' in rule " +
+ rule_desc(index, obj) +
+ " not available"
+ );
+ }
+ } else {
+ let otherHeader = Ci.nsMsgSearchAttrib.OtherHeader;
+ let attribValue =
+ obj.searchattribute.value > otherHeader
+ ? otherHeader
+ : obj.searchattribute.value;
+ if (
+ !obj.searchattribute.validityTable.getAvailable(
+ attribValue,
+ obj.searchoperator.value
+ )
+ ) {
+ invalidRule = true;
+ console.error(
+ "Filter not saved because standard search term '" +
+ attribValue +
+ "' in rule " +
+ rule_desc(index, obj) +
+ " not available in this context"
+ );
+ }
+ }
+
+ if (invalidRule) {
+ Services.prompt.alert(
+ window,
+ gFilterBundle.getString("searchTermsInvalidTitle"),
+ gFilterBundle.getFormattedString("searchTermsInvalidRule", [
+ obj.searchattribute.label,
+ obj.searchoperator.label,
+ ])
+ );
+ return false;
+ }
+ }
+
+ // before we go any further, validate each specified filter action, abort the save
+ // if any of the actions is invalid...
+ for (let index = 0; index < gFilterActionList.itemCount; index++) {
+ var listItem = gFilterActionList.getItemAtIndex(index);
+ if (!listItem.validateAction()) {
+ return false;
+ }
+ }
+
+ // if we made it here, all of the actions are valid, so go ahead and save the filter
+ let isNewFilter;
+ if (!gFilter) {
+ // This is a new filter
+ gFilter = gFilterList.createFilter(filterName);
+ isNewFilter = true;
+ gFilter.enabled = true;
+ } else {
+ // We are working with an existing filter object,
+ // either editing or using prefill
+ gFilter.filterName = filterName;
+ // Prefilter is treated as a new filter.
+ if (gPreFillName) {
+ isNewFilter = true;
+ gFilter.enabled = true;
+ } else {
+ isNewFilter = false;
+ }
+
+ gFilter.clearActionList();
+ }
+
+ // add each filteraction to the filter
+ for (let index = 0; index < gFilterActionList.itemCount; index++) {
+ gFilterActionList.getItemAtIndex(index).saveToFilter(gFilter);
+ }
+
+ // If we do not have a filter name at this point, generate one.
+ if (!gFilter.filterName) {
+ AssignMeaningfulName();
+ }
+
+ gFilter.filterType = gFilterType;
+ gFilter.searchTerms = saveSearchTerms(gFilter.searchTerms, gFilter);
+
+ if (isNewFilter) {
+ // new filter - insert into gFilterList
+ gFilterList.insertFilterAt(gFilterPosition, gFilter);
+ }
+
+ // success!
+ return true;
+}
+
+/**
+ * Check if the list of actions the user created will be executed in a different order.
+ * Exposes a note to the user if that is the case.
+ */
+function checkActionsReorder() {
+ setTimeout(_checkActionsReorder, 0);
+}
+
+/**
+ * This should be called from setTimeout otherwise some of the elements calling
+ * may not be fully initialized yet (e.g. we get ".saveToFilter is not a function").
+ * It is OK to schedule multiple timeouts with this function.
+ */
+function _checkActionsReorder() {
+ // Create a temporary disposable filter and add current actions to it.
+ if (!gTempFilter) {
+ gTempFilter = gFilterList.createFilter("");
+ } else {
+ gTempFilter.clearActionList();
+ }
+
+ for (let index = 0; index < gFilterActionList.itemCount; index++) {
+ gFilterActionList.getItemAtIndex(index).saveToFilter(gTempFilter);
+ }
+
+ // Now get the actions out of the filter in the order they will be executed in.
+ gActionListOrdered = gTempFilter.sortedActionList;
+
+ // Compare the two lists.
+ let statusBar = document.getElementById("statusbar");
+ for (let index = 0; index < gActionListOrdered.length; index++) {
+ if (index != gTempFilter.getActionIndex(gActionListOrdered[index])) {
+ // If the lists are not the same unhide the status bar and show warning.
+ statusBar.style.visibility = "visible";
+ return;
+ }
+ }
+
+ statusBar.style.visibility = "hidden";
+}
+
+/**
+ * Show a dialog with the ordered list of actions.
+ * The fetching of action label and argument is separated from checkActionsReorder
+ * function to make that one more lightweight. The list is built only upon
+ * user request.
+ */
+function showActionsOrder() {
+ // Fetch the actions and arguments as a string.
+ let actionStrings = [];
+ for (let i = 0; i < gFilterActionList.itemCount; i++) {
+ let ruleAction = gFilterActionList.getItemAtIndex(i);
+ let actionTarget = ruleAction.children[1];
+ let actionItem = actionTarget.ruleactiontargetElement;
+ let actionItemLabel = actionItem && actionItem.children[0].label;
+
+ let actionString = {
+ label: ruleAction.mRuleActionType.label,
+ argument: "",
+ };
+ if (actionItem) {
+ if (actionItemLabel) {
+ actionString.argument = actionItemLabel;
+ } else {
+ actionString.argument = actionItem.children[0].value;
+ }
+ }
+ actionStrings.push(actionString);
+ }
+
+ // Present a nicely formatted list of action names and arguments.
+ let actionList = gFilterBundle.getString("filterActionOrderExplanation");
+ for (let i = 0; i < gActionListOrdered.length; i++) {
+ let actionIndex = gTempFilter.getActionIndex(gActionListOrdered[i]);
+ let action = actionStrings[actionIndex];
+ actionList += gFilterBundle.getFormattedString("filterActionItem", [
+ i + 1,
+ action.label,
+ action.argument,
+ ]);
+ }
+
+ Services.prompt.confirmEx(
+ window,
+ gFilterBundle.getString("filterActionOrderTitle"),
+ actionList,
+ Services.prompt.BUTTON_TITLE_OK,
+ null,
+ null,
+ null,
+ null,
+ { value: false }
+ );
+}
+
+function AssignMeaningfulName() {
+ // termRoot points to the first search object, which is the one we care about.
+ let termRoot = gSearchTerms[0].obj;
+ // stub is used as the base name for a filter.
+ let stub;
+
+ // If this is a Match All Messages Filter, we already know the name to assign.
+ if (termRoot.matchAll) {
+ stub = gFilterBundle.getString("matchAllFilterName");
+ } else {
+ // Assign a name based on the first search term.
+ let term = termRoot.searchattribute.label;
+ let operator = termRoot.searchoperator.label;
+ let value = termRoot.searchvalue.getReadableValue();
+ stub = gFilterBundle.getFormattedString("filterAutoNameStr", [
+ term,
+ operator,
+ value,
+ ]);
+ }
+
+ // Whatever name we have used, 'uniquify' it.
+ let tempName = stub;
+ let count = 1;
+ while (duplicateFilterNameExists(tempName)) {
+ count++;
+ tempName = `${stub} ${count}`;
+ }
+ gFilter.filterName = tempName;
+}
+
+function UpdateAfterCustomHeaderChange() {
+ updateSearchAttributes();
+}
+
+// if you use msgWindow, please make sure that destructor gets called when you close the "window"
+function GetFilterEditorMsgWindow() {
+ if (!gFilterEditorMsgWindow) {
+ var msgWindowContractID = "@mozilla.org/messenger/msgwindow;1";
+ var nsIMsgWindow = Ci.nsIMsgWindow;
+ gFilterEditorMsgWindow =
+ Cc[msgWindowContractID].createInstance(nsIMsgWindow);
+ gFilterEditorMsgWindow.domWindow = window;
+ gFilterEditorMsgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
+ }
+ return gFilterEditorMsgWindow;
+}
+
+function SetBusyCursor(window, enable) {
+ // setCursor() is only available for chrome windows.
+ // However one of our frames is the start page which
+ // is a non-chrome window, so check if this window has a
+ // setCursor method
+ if ("setCursor" in window) {
+ if (enable) {
+ window.setCursor("wait");
+ } else {
+ window.setCursor("auto");
+ }
+ }
+}
+
+/* globals openHelp */
+// suite/components/helpviewer/content/contextHelp.js
+function doHelpButton() {
+ openHelp("mail-filters");
+}
+
+function getCustomActions() {
+ if (!gCustomActions) {
+ gCustomActions = MailServices.filters.getCustomActions();
+ }
+}
+
+function updateFilterType() {
+ gFilterType = gFilterTypeSelector.getType();
+ setFilterScope(gFilterType, gFilterList);
+
+ // set valid actions
+ var ruleActions = gFilterActionList.getElementsByAttribute(
+ "class",
+ "ruleaction"
+ );
+ for (var i = 0; i < ruleActions.length; i++) {
+ ruleActions[i].mRuleActionType.hideInvalidActions();
+ }
+}
+
+// Given a filter type, set the global search scope to the filter scope
+function setFilterScope(aFilterType, aFilterList) {
+ let filterScope = getFilterScope(
+ getScopeFromFilterList(aFilterList),
+ aFilterType,
+ aFilterList
+ );
+ setSearchScope(filterScope);
+}
+
+//
+// Given the base filter scope for a server, and the filter
+// type, return the scope used for filter. This assumes a
+// hierarchy of contexts, with incoming the most restrictive,
+// followed by manual and post-plugin.
+function getFilterScope(aServerFilterScope, aFilterType, aFilterList) {
+ if (aFilterType & Ci.nsMsgFilterType.Incoming) {
+ return aServerFilterScope;
+ }
+
+ // Manual or PostPlugin
+ // local mail allows body and junk types
+ if (aServerFilterScope == Ci.nsMsgSearchScope.offlineMailFilter) {
+ return Ci.nsMsgSearchScope.offlineMail;
+ }
+ // IMAP and NEWS online don't allow body
+ return Ci.nsMsgSearchScope.onlineManual;
+}
+
+/**
+ * Re-focus the action that was focused before focus was lost.
+ */
+function setLastActionFocus() {
+ let lastAction = gFilterActionList.getAttribute("focusedAction");
+ if (!lastAction || lastAction < 0) {
+ lastAction = 0;
+ }
+ if (lastAction >= gFilterActionList.itemCount) {
+ lastAction = gFilterActionList.itemCount - 1;
+ }
+
+ gFilterActionList.getItemAtIndex(lastAction).mRuleActionType.focus();
+}