Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1

This commit is contained in:
Matt A. Tobin 2019-11-03 00:17:46 -04:00 • committed by Roy Tam
commit e400f4130a
1564 changed files with 510348 additions and 0 deletions

View file

@ -0,0 +1,203 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://gre/modules/Services.jsm");
var gAddButton;
var gRemoveButton;
var gHeaderInputElement;
var gArrayHdrs;
var gHdrsList;
var gContainer;
var gFilterBundle=null;
var gCustomBundle=null;
function onLoad()
{
let hdrs = Services.prefs.getCharPref("mailnews.customHeaders");
gHeaderInputElement = document.getElementById("headerInput");
gHeaderInputElement.focus();
gHdrsList = document.getElementById("headerList");
gArrayHdrs = new Array();
gAddButton = document.getElementById("addButton");
gRemoveButton = document.getElementById("removeButton");
initializeDialog(hdrs);
updateAddButton(true);
updateRemoveButton();
}
function initializeDialog(hdrs)
{
if (hdrs)
{
hdrs = hdrs.replace(/\s+/g,''); //remove white spaces before splitting
gArrayHdrs = hdrs.split(":");
for (var i = 0; i < gArrayHdrs.length; i++)
if (!gArrayHdrs[i])
gArrayHdrs.splice(i,1); //remove any null elements
initializeRows();
}
}
function initializeRows()
{
for (var i = 0; i < gArrayHdrs.length; i++)
addRow(TrimString(gArrayHdrs[i]));
}
function onTextInput()
{
// enable the add button if the user has started to type text
updateAddButton( (gHeaderInputElement.value == "") );
}
function onOk()
{
if (gArrayHdrs.length)
{
var hdrs;
if (gArrayHdrs.length == 1)
hdrs = gArrayHdrs;
else
hdrs = gArrayHdrs.join(": ");
Services.prefs.setCharPref("mailnews.customHeaders", hdrs);
// flush prefs to disk, in case we crash, to avoid dataloss and problems with filters that use the custom headers
Services.prefs.savePrefFile(null);
}
else
{
Services.prefs.clearUserPref("mailnews.customHeaders"); //clear the pref, no custom headers
}
window.arguments[0].selectedVal = gHdrsList.selectedItem ? gHdrsList.selectedItem.label : null;
return true;
}
function customHeaderOverflow()
{
var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
if (gArrayHdrs.length >= (nsMsgSearchAttrib.kNumMsgSearchAttributes - nsMsgSearchAttrib.OtherHeader - 1))
{
if (!gFilterBundle)
gFilterBundle = document.getElementById("bundle_filter");
var alertText = gFilterBundle.getString("customHeaderOverflow");
Services.prompt.alert(window, null, alertText);
return true;
}
return false;
}
function onAddHeader()
{
var newHdr = TrimString(gHeaderInputElement.value);
if (!isRFC2822Header(newHdr)) // if user entered an invalid rfc822 header field name, bail out.
{
if (!gCustomBundle)
gCustomBundle = document.getElementById("bundle_custom");
var alertText = gCustomBundle.getString("colonInHeaderName");
Services.prompt.alert(window, null, alertText);
return;
}
gHeaderInputElement.value = "";
if (!newHdr || customHeaderOverflow())
return;
if (!duplicateHdrExists(newHdr))
{
gArrayHdrs[gArrayHdrs.length] = newHdr;
var newItem = addRow(newHdr);
gHdrsList.selectItem (newItem); // make sure the new entry is selected in the tree
// now disable the add button
updateAddButton(true);
gHeaderInputElement.focus(); // refocus the input field for the next custom header
}
}
function isRFC2822Header(hdr)
{
var charCode;
for (var i = 0; i < hdr.length; i++)
{
charCode = hdr.charCodeAt(i);
//58 is for colon and 33 and 126 are us-ascii bounds that should be used for header field name, as per rfc2822
if (charCode < 33 || charCode == 58 || charCode > 126)
return false;
}
return true;
}
function duplicateHdrExists(hdr)
{
for (var i = 0;i < gArrayHdrs.length; i++)
{
if (gArrayHdrs[i] == hdr)
return true;
}
return false;
}
function onRemoveHeader()
{
var listitem = gHdrsList.selectedItems[0]
if (!listitem) return;
listitem.remove();
var selectedHdr = GetListItemAttributeStr(listitem);
var j=0;
for (var i = 0; i < gArrayHdrs.length; i++)
{
if (gArrayHdrs[i] == selectedHdr)
{
gArrayHdrs.splice(i,1);
break;
}
}
}
function GetListItemAttributeStr(listitem)
{
if (listitem)
return TrimString(listitem.getAttribute("label"));
return "";
}
function addRow(newHdr)
{
var listitem = document.createElement("listitem");
listitem.setAttribute("label", newHdr);
gHdrsList.appendChild(listitem);
return listitem;
}
function updateAddButton(aDisable)
{
// only update the button if the disabled state changed
if (aDisable == gAddButton.disabled)
return;
gAddButton.disabled = aDisable;
document.documentElement.defaultButton = aDisable ? "accept" : "extra1";
}
function updateRemoveButton()
{
var headerSelected = (gHdrsList.selectedItems.length > 0);
gRemoveButton.disabled = !headerSelected;
if (gRemoveButton.disabled)
gHeaderInputElement.focus();
}
//Remove whitespace from both ends of a string
function TrimString(string)
{
if (!string) return "";
return string.trim();
}

View file

@ -0,0 +1,57 @@
<?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/. -->
<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/CustomHeaders.dtd">
<dialog id="customHeadersDialog"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="onLoad();"
ondialogaccept="return onOk();"
ondialogextra1="onAddHeader();"
ondialogextra2="onRemoveHeader();"
style="width: 30em; height: 25em;"
persist="width height screenX screenY"
title="&window.title;"
buttons="accept,cancel,extra1,extra2">
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
<stringbundle id="bundle_custom" src="chrome://messenger/locale/custom.properties"/>
</stringbundleset>
<script type="application/javascript" src="chrome://messenger/content/CustomHeaders.js"/>
<grid flex="1">
<columns>
<column flex="1"/>
<column/>
</columns>
<rows>
<row>
<label accesskey="&newMsgHeader.accesskey;" control="headerInput" value="&newMsgHeader.label;"/>
</row>
<row>
<textbox id="headerInput" onfocus="this.select();" oninput="onTextInput();"/>
</row>
<row flex="1">
<vbox>
<listbox id="headerList" flex="1" onselect="updateRemoveButton();" />
</vbox>
<vbox>
<button id="addButton"
label="&addButton.label;"
accesskey="&addButton.accesskey;"
dlgtype="extra1"/>
<button id="removeButton"
label="&removeButton.label;"
accesskey="&removeButton.accesskey;"
dlgtype="extra2"/>
</vbox>
</row>
</rows>
</grid>
</dialog>

View file

@ -0,0 +1,854 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource:///modules/mailServices.js");
Components.utils.import("resource:///modules/MailUtils.js");
// 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 gSessionFolderListenerAdded = false;
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;
// A nsIArray of the currently defined actions in the order they will be run.
var gActionListOrdered = null;
var gFilterEditorMsgWindow = null;
var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
var nsMsgFilterType = Components.interfaces.nsMsgFilterType;
var nsIMsgRuleAction = Components.interfaces.nsIMsgRuleAction;
var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
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 = Components.interfaces.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 == Components.interfaces.nsMsgSearchAttrib.Default)
term.attrib = Components.interfaces.nsMsgSearchAttrib.Sender;
term.op = Components.interfaces.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) ==
nsMsgSearchScope.newsFilter) ?
nsMsgFilterAction.Delete : nsMsgFilterAction.MoveToFolder;
gFilter.appendAction(filterAction);
initializeDialog(gFilter);
}
else if ("copiedFilter" in args)
{
// we are copying a filter
var copiedFilter = args.copiedFilter;
var 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 i = 0; i < copiedFilter.searchTerms.Count(); i++)
{
var searchTerm = copiedFilter.searchTerms.QueryElementAt(i,
Components.interfaces.nsIMsgSearchTerm);
var newTerm = newFilter.createTerm();
newTerm.attrib = searchTerm.attrib;
newTerm.op = searchTerm.op;
newTerm.booleanAnd = searchTerm.booleanAnd;
newTerm.value = searchTerm.value;
newFilter.appendTerm(newTerm);
};
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(nsMsgFilterType.Incoming | 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 filterEditorOnUnload()
{
if (gSessionFolderListenerAdded)
MailServices.mailSession.RemoveFolderListener(gFolderListener);
}
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.getElementById("FilterEditor").acceptDialog();
} else {
// If only plain Enter was pressed, add a new rule line.
onMore(event);
}
}
function onAccept()
{
try {
if (!saveFilter())
return false;
} catch(e) {Components.utils.reportError(e); return false;}
// 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;
return true;
}
// the folderListener object
var gFolderListener = {
OnItemAdded: function(parentItem, item) {},
OnItemRemoved: function(parentItem, item){},
OnItemPropertyChanged: function(item, property, oldValue, newValue) {},
OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {},
OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue){},
OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
OnItemEvent: function(folder, event)
{
var eventType = event.toString();
if (eventType == "FolderCreateCompleted")
{
gActionTargetElement.selectFolder(folder);
SetBusyCursor(window, false);
}
else if (eventType == "FolderCreateFailed")
SetBusyCursor(window, false);
}
}
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 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"),
/**
* Returns the currently set filter type (checkboxes) in terms
* of a Components.interfaces.nsMsgFilterType value.
*/
getType: function()
{
let type = nsMsgFilterType.None;
if (this.checkBoxManual.checked)
type |= nsMsgFilterType.Manual;
if (this.checkBoxIncoming.checked) {
if (this.menulistIncoming.selectedItem == this.menuitemAfterPlugins) {
type |= nsMsgFilterType.PostPlugin;
} else {
// this.menuitemBeforePlugins selected
if (getScopeFromFilterList(gFilterList) ==
nsMsgSearchScope.newsFilter)
type |= nsMsgFilterType.NewsRule;
else
type |= nsMsgFilterType.InboxRule;
}
}
if (this.checkBoxArchive.checked)
type |= nsMsgFilterType.Archive;
if (this.checkBoxOutgoing.checked)
type |= nsMsgFilterType.PostOutgoing;
return type;
},
/**
* Sets the checkboxes to represent the filter type passed in.
*
* @param aType the filter type to set in terms
* of Components.interfaces.nsMsgFilterType values.
*/
setType: function(aType)
{
// If there is no type (event) requested, force "when manually run"
if (aType == nsMsgFilterType.None)
aType = nsMsgFilterType.Manual;
this.checkBoxManual.checked = aType & nsMsgFilterType.Manual;
this.checkBoxIncoming.checked = aType & (nsMsgFilterType.PostPlugin |
nsMsgFilterType.Incoming);
this.menulistIncoming.selectedItem = aType & nsMsgFilterType.PostPlugin ?
this.menuitemAfterPlugins : this.menuitemBeforePlugins;
this.checkBoxArchive.checked = aType & nsMsgFilterType.Archive;
this.checkBoxOutgoing.checked = aType & nsMsgFilterType.PostOutgoing;
this.updateClassificationMenu();
},
/**
* Enable the "before/after classification" menulist depending on
* whether "run when incoming mail" is selected.
*/
updateClassificationMenu: function()
{
this.menulistIncoming.disabled = !this.checkBoxIncoming.checked;
updateFilterType();
},
/**
* Disable the options unsuitable for deferred accounts.
*/
disableDeferredAccount: function()
{
this.menuitemAfterPlugins.disabled = true;
this.checkBoxOutgoing.disabled = true;
}
};
}
function initializeDialog(filter)
{
gFilterNameElement.value = filter.filterName;
let filterType = filter.filterType;
gFilterTypeSelector.setType(filter.filterType);
let numActions = filter.actionCount;
for (let actionIndex = 0; actionIndex < numActions; actionIndex++)
{
let filterAction = filter.getActionAt(actionIndex);
var newActionRow = document.createElement('listitem');
newActionRow.setAttribute('initialActionIndex', actionIndex);
newActionRow.className = 'ruleaction';
gFilterActionList.appendChild(newActionRow);
newActionRow.setAttribute('value',
filterAction.type == 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())
{
var newActionRow = document.createElement('listitem');
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 == 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;
Components.utils.reportError("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;
Components.utils.reportError("Filter not saved because custom search term '" +
customTerm.name + "' in rule " + rule_desc(index, obj) + " not available");
}
}
}
else
{
let otherHeader = Components.interfaces.nsMsgSearchAttrib.OtherHeader;
let attribValue = (obj.searchattribute.value > otherHeader) ?
otherHeader : obj.searchattribute.value;
if (!obj.searchattribute
.validityTable
.getAvailable(attribValue, obj.searchoperator.value))
{
invalidRule = true;
Components.utils.reportError("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;
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.queryElementAt(index, nsIMsgRuleAction)))
{
// 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 index = 0; index < gFilterActionList.itemCount; index++)
gFilterActionList.getItemAtIndex(index).getActionStrings(actionStrings);
// 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.queryElementAt(i, nsIMsgRuleAction));
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 searchValue = termRoot.searchvalue;
let selIndex = searchValue.getAttribute( "selectedIndex" );
let children = document.getAnonymousNodes(searchValue);
let activeItem = children[selIndex];
let attribs = Components.interfaces.nsMsgSearchAttrib;
// Term, Operator and Value are the three parts of a filter match
// Term and Operator are easy to retrieve
let term = termRoot.searchattribute.label;
let operator = termRoot.searchoperator.label;
// Values are either popup menu items or edit fields.
// For popup menus use activeItem.label; for
// edit fields, activeItem.value
let value;
switch (Number(termRoot.searchattribute.value))
{
case attribs.Priority:
case attribs.MsgStatus:
case attribs.Keywords:
case attribs.HasAttachmentStatus:
case attribs.JunkStatus:
case attribs.JunkScoreOrigin:
if (activeItem)
value = activeItem.label;
else
value = "";
break;
default:
try
{
value = activeItem.value;
}
catch (ex)
{
// We should never get here, but for safety's sake,
// let's name the filter "Untitled Filter".
stub = gFilterBundle.getString( "untitledFilterName" );
// Do not 'Return'. Instead fall through and deal with the untitled filter below.
}
break;
}
// We are now ready to name the filter.
// If at this point stub is empty, we know that this is not a Match All Filter
// and is not an "untitledFilterName" Filter, so assign it a name using
// a string format from the Filter Bundle.
if (!stub)
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 GetFirstSelectedMsgFolder()
{
var selectedFolder = gActionTargetElement.getAttribute("uri");
if (!selectedFolder)
return null;
var msgFolder = MailUtils.getFolderForURI(selectedFolder, true);
return msgFolder;
}
function SearchNewFolderOkCallback(name, uri)
{
var msgFolder = MailUtils.getFolderForURI(uri, true);
var imapFolder = null;
try
{
imapFolder = msgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
}
catch(ex) {}
if (imapFolder) //imapFolder creation is asynchronous.
{
if (!gSessionFolderListenerAdded) {
try
{
let notifyFlags = Components.interfaces.nsIFolderListener.event;
MailServices.mailSession.AddFolderListener(gFolderListener, notifyFlags);
gSessionFolderListenerAdded = true;
}
catch (ex)
{
Components.utils.reportError("Error adding to session: " + ex + "\n");
}
}
}
var msgWindow = GetFilterEditorMsgWindow();
if (imapFolder)
SetBusyCursor(window, true);
msgFolder.createSubfolder(name, msgWindow);
if (!imapFolder)
{
var curFolder = uri+"/"+encodeURIComponent(name);
let folder = MailUtils.getFolderForURI(curFolder);
gActionTargetElement.selectFolder(folder);
}
}
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 = Components.interfaces.nsIMsgWindow;
gFilterEditorMsgWindow = Components.classes[msgWindowContractID].createInstance(nsIMsgWindow);
gFilterEditorMsgWindow.domWindow = window;
gFilterEditorMsgWindow.rootDocShell.appType = Components.interfaces.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");
}
}
function doHelpButton()
{
openHelp("mail-filters");
}
function getCustomActions()
{
if (!gCustomActions)
{
gCustomActions = [];
let customActionsEnum = MailServices.filters.getCustomActions();
while (customActionsEnum.hasMoreElements())
gCustomActions.push(customActionsEnum.getNext().QueryInterface(
Components.interfaces.nsIMsgFilterCustomAction));
}
}
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 & nsMsgFilterType.Incoming)
return aServerFilterScope;
// Manual or PostPlugin
// local mail allows body and junk types
if (aServerFilterScope == nsMsgSearchScope.offlineMailFilter)
return nsMsgSearchScope.offlineMail;
// IMAP and NEWS online don't allow body
return 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.menulist.focus();
}

View file

@ -0,0 +1,125 @@
<?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/. -->
<?xml-stylesheet href="chrome://messenger/skin/filterDialog.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/FilterEditor.dtd">
<dialog id="FilterEditor"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&window.title;"
style="&filterEditorDialog.dimensions;"
windowtype="mailnews:filtereditor"
persist="width height screenX screenY"
buttons="accept,cancel"
onload="filterEditorOnLoad();"
onunload="filterEditorOnUnload();"
ondialogaccept="return onAccept();">
<dummy class="usesMailWidgets"/>
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
<stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
<stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
</stringbundleset>
<script type="application/javascript" src="chrome://messenger/content/mailWindowOverlay.js"/>
<script type="application/javascript" src="chrome://messenger/content/mailCommands.js"/>
<script type="application/javascript" src="chrome://messenger/content/FilterEditor.js"/>
<commandset>
<command id="cmd_updateFilterType" oncommand="updateFilterType();"/>
<command id="cmd_updateClassificationMenu" oncommand="gFilterTypeSelector.updateClassificationMenu();"/>
</commandset>
<vbox>
<hbox align="center">
<label value="&filterName.label;" accesskey="&filterName.accesskey;" control="filterName"/>
<textbox flex="1" id="filterName"/>
<spacer flex="1"/>
</hbox>
</vbox>
<separator class="thin"/>
<vbox flex="1">
<groupbox>
<caption label="&contextDesc.label;"/>
<grid>
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<checkbox id="runManual"
label="&contextManual.label;"
accesskey="&contextManual.accesskey;"
command="cmd_updateFilterType"/>
</row>
<row>
<checkbox id="runIncoming"
label="&contextIncomingMail.label;"
accesskey="&contextIncomingMail.accesskey;"
command="cmd_updateClassificationMenu"/>
<menulist id="pluginsRunOrder"
command="cmd_updateFilterType">
<menupopup>
<menuitem id="runBeforePlugins"
label="&contextBeforeCls.label;"/>
<menuitem id="runAfterPlugins"
label="&contextAfterCls.label;"/>
</menupopup>
</menulist>
</row>
<row>
<checkbox id="runArchive"
label="&contextArchive.label;"
accesskey="&contextArchive.accesskey;"
command="cmd_updateFilterType"/>
</row>
<row>
<checkbox id="runOutgoing"
label="&contextOutgoing.label;"
accesskey="&contextOutgoing.accesskey;"
command="cmd_updateFilterType"/>
</row>
</rows>
</grid>
</groupbox>
<vbox id="searchTermListBox" flex="1"/>
</vbox>
<splitter id="gray_horizontal_splitter" persist="state"/>
<vbox flex="1">
<label value="&filterActionDesc.label;"
accesskey="&filterActionDesc.accesskey;"
control="filterActionList"/>
<listbox id="filterActionList" flex="1" rows="4" minheight="35%"
onfocus="setLastActionFocus();" focusedAction="0">
<listcols>
<listcol flex="&filterActionTypeFlexValue;"/>
<listcol flex="&filterActionTargetFlexValue;"/>
<listcol class="filler"/>
</listcols>
</listbox>
</vbox>
<vbox id="statusbar" style="visibility: hidden;">
<hbox align="center">
<label>
&filterActionOrderWarning.label;
</label>
<label class="text-link" onclick="showActionsOrder();">&filterActionOrder.label;</label>
</hbox>
</vbox>
</dialog>

View file

@ -0,0 +1,536 @@
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var gTotalSearchTerms=0;
var gSearchTermList;
var gSearchTerms = new Array;
var gSearchRemovedTerms = new Array;
var gSearchScope;
var gSearchBooleanRadiogroup;
var gUniqueSearchTermCounter = 0; // gets bumped every time we add a search term so we can always
// dynamically generate unique IDs for the terms.
// cache these so we don't have to hit the string bundle for them
var gMoreButtonTooltipText;
var gLessButtonTooltipText;
var gLoading = true;
function searchTermContainer() {}
searchTermContainer.prototype = {
internalSearchTerm : '',
internalBooleanAnd : '',
// this.searchTerm: the actual nsIMsgSearchTerm object
get searchTerm() { return this.internalSearchTerm; },
set searchTerm(val) {
this.internalSearchTerm = val;
var term = val;
// val is a nsIMsgSearchTerm
var searchAttribute=this.searchattribute;
var searchOperator=this.searchoperator;
var searchValue=this.searchvalue;
// now reflect all attributes of the searchterm into the widgets
if (searchAttribute)
{
// for custom, the value is the custom id, not the integer attribute
if (term.attrib == Components.interfaces.nsMsgSearchAttrib.Custom)
searchAttribute.value = term.customId;
else
searchAttribute.value = term.attrib;
}
if (searchOperator) searchOperator.value = val.op;
if (searchValue) searchValue.value = term.value;
this.booleanAnd = val.booleanAnd;
this.matchAll = val.matchAll;
return val;
},
// searchscope - just forward to the searchattribute
get searchScope() {
if (this.searchattribute)
return this.searchattribute.searchScope;
return undefined;
},
set searchScope(val) {
var searchAttribute = this.searchattribute;
if (searchAttribute) searchAttribute.searchScope=val;
return val;
},
saveId: function (element, slot) {
this[slot] = element.id;
},
getElement: function (slot) {
return document.getElementById(this[slot]);
},
// three well-defined properties:
// searchattribute, searchoperator, searchvalue
// the trick going on here is that we're storing the Element's Id,
// not the element itself, because the XBL object may change out
// from underneath us
get searchattribute() { return this.getElement("internalSearchAttributeId"); },
set searchattribute(val) {
this.saveId(val, "internalSearchAttributeId");
return val;
},
get searchoperator() { return this.getElement("internalSearchOperatorId"); },
set searchoperator(val) {
this.saveId(val, "internalSearchOperatorId");
return val;
},
get searchvalue() { return this.getElement("internalSearchValueId"); },
set searchvalue(val) {
this.saveId(val, "internalSearchValueId");
return val;
},
booleanNodes: null,
get booleanAnd() { return this.internalBooleanAnd; },
set booleanAnd(val) {
this.internalBooleanAnd = val;
return val;
},
save: function () {
var searchTerm = this.searchTerm;
var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
if (isNaN(this.searchattribute.value)) // is this a custom term?
{
searchTerm.attrib = nsMsgSearchAttrib.Custom;
searchTerm.customId = this.searchattribute.value;
}
else
{
searchTerm.attrib = this.searchattribute.value;
}
if (this.searchattribute.value > nsMsgSearchAttrib.OtherHeader && this.searchattribute.value < nsMsgSearchAttrib.kNumMsgSearchAttributes)
searchTerm.arbitraryHeader = this.searchattribute.label;
searchTerm.op = this.searchoperator.value;
if (this.searchvalue.value)
this.searchvalue.save();
else
this.searchvalue.saveTo(searchTerm.value);
searchTerm.value = this.searchvalue.value;
searchTerm.booleanAnd = this.booleanAnd;
searchTerm.matchAll = this.matchAll;
},
// if you have a search term element with no search term
saveTo: function(searchTerm) {
this.internalSearchTerm = searchTerm;
this.save();
}
}
var nsIMsgSearchTerm = Components.interfaces.nsIMsgSearchTerm;
function initializeSearchWidgets()
{
gSearchBooleanRadiogroup = document.getElementById("booleanAndGroup");
gSearchTermList = document.getElementById("searchTermList");
// initialize some strings
var bundle = document.getElementById('bundle_search');
gMoreButtonTooltipText = bundle.getString('moreButtonTooltipText');
gLessButtonTooltipText = bundle.getString('lessButtonTooltipText');
}
function initializeBooleanWidgets()
{
var booleanAnd = true;
var matchAll = false;
// get the boolean value from the first term
var firstTerm = gSearchTerms[0].searchTerm;
if (firstTerm)
{
// If there is a second term, it should actually define whether we're
// using 'and' or not. Note that our UI is not as rich as the
// underlying search model, so there's the potential to lose here when
// grouping is involved.
booleanAnd = (gSearchTerms.length > 1) ?
gSearchTerms[1].searchTerm.booleanAnd : firstTerm.booleanAnd;
matchAll = firstTerm.matchAll;
}
// target radio items have value="and" or value="or" or "all"
gSearchBooleanRadiogroup.value = matchAll
? "matchAll"
: (booleanAnd ? "and" : "or")
var searchTerms = document.getElementById("searchTermList");
if (searchTerms)
updateSearchTermsListbox(matchAll);
}
function initializeSearchRows(scope, searchTerms)
{
for (var i = 0; i < searchTerms.Count(); i++) {
var searchTerm = searchTerms.QueryElementAt(i, nsIMsgSearchTerm);
createSearchRow(i, scope, searchTerm, false);
gTotalSearchTerms++;
}
initializeBooleanWidgets();
updateRemoveRowButton();
}
/**
* Enables/disables all the visible elements inside the search terms listbox.
*
* @param matchAllValue boolean value from the first search term
*/
function updateSearchTermsListbox(matchAllValue)
{
var searchTerms = document.getElementById("searchTermList");
searchTerms.setAttribute("disabled", matchAllValue);
var searchAttributeList = searchTerms.getElementsByTagName("searchattribute");
var searchOperatorList = searchTerms.getElementsByTagName("searchoperator");
var searchValueList = searchTerms.getElementsByTagName("searchvalue");
for (var i = 0; i < searchAttributeList.length; i++) {
searchAttributeList[i].setAttribute("disabled", matchAllValue);
searchOperatorList[i].setAttribute("disabled", matchAllValue);
searchValueList[i].setAttribute("disabled", matchAllValue);
if (!matchAllValue)
searchValueList[i].removeAttribute("disabled");
}
var moreOrLessButtonsList = searchTerms.getElementsByTagName("button");
for (var i = 0; i < moreOrLessButtonsList.length; i++) {
moreOrLessButtonsList[i].setAttribute("disabled", matchAllValue);
}
if (!matchAllValue)
updateRemoveRowButton();
}
// enables/disables the less button for the first row of search terms.
function updateRemoveRowButton()
{
var firstListItem = gSearchTermList.getItemAtIndex(0);
if (firstListItem)
firstListItem.lastChild.lastChild.setAttribute("disabled", gTotalSearchTerms == 1);
}
// Returns the actual list item row index in the list of search rows
// that contains the passed in element id.
function getSearchRowIndexForElement(aElement)
{
var listItem = aElement;
while (listItem && listItem.localName != "listitem")
listItem = listItem.parentNode;
return gSearchTermList.getIndexOfItem(listItem);
}
function onMore(event)
{
// if we have an event, extract the list row index and use that as the row number
// for our insertion point. If there is no event, append to the end....
var rowIndex;
if (event)
rowIndex = getSearchRowIndexForElement(event.target) + 1;
else
rowIndex = gSearchTermList.getRowCount();
createSearchRow(rowIndex, gSearchScope, null, event != null);
gTotalSearchTerms++;
updateRemoveRowButton();
// the user just added a term, so scroll to it
gSearchTermList.ensureIndexIsVisible(rowIndex);
}
function onLess(event)
{
if (event && gTotalSearchTerms > 1)
{
removeSearchRow(getSearchRowIndexForElement(event.target));
--gTotalSearchTerms;
}
updateRemoveRowButton();
}
// set scope on all visible searchattribute tags
function setSearchScope(scope)
{
gSearchScope = scope;
for (var i = 0; i < gSearchTerms.length; i++)
{
// don't set element attributes if XBL hasn't loaded
if (!(gSearchTerms[i].obj.searchattribute.searchScope === undefined))
{
gSearchTerms[i].obj.searchattribute.searchScope = scope;
// act like the user "selected" this, see bug #202848
gSearchTerms[i].obj.searchattribute.onSelect(null /* no event */);
}
gSearchTerms[i].scope = scope;
}
}
function updateSearchAttributes()
{
for (var i=0; i<gSearchTerms.length; i++)
gSearchTerms[i].obj.searchattribute.refreshList();
}
function booleanChanged(event) {
// when boolean changes, we have to update all the attributes on the search terms
var newBoolValue = (event.target.getAttribute("value") == "and");
var matchAllValue = (event.target.getAttribute("value") == "matchAll");
if (document.getElementById("abPopup")) {
var selectedAB = document.getElementById("abPopup").selectedItem.value;
setSearchScope(GetScopeForDirectoryURI(selectedAB));
}
for (var i=0; i<gSearchTerms.length; i++) {
let searchTerm = gSearchTerms[i].obj;
// If term is not yet initialized in the UI, change the original object.
if (!searchTerm || !gSearchTerms[i].initialized)
searchTerm = gSearchTerms[i].searchTerm;
searchTerm.booleanAnd = newBoolValue;
searchTerm.matchAll = matchAllValue;
}
var searchTerms = document.getElementById("searchTermList");
if (searchTerms)
{
if (!matchAllValue && searchTerms.hidden && !gTotalSearchTerms)
onMore(null); // fake to get empty row.
updateSearchTermsListbox(matchAllValue);
}
}
/**
* Create a new search row with all the needed elements.
*
* @param index index of the position in the menulist where to add the row
* @param scope a nsMsgSearchScope constant indicating scope of this search rule
* @param searchTerm nsIMsgSearchTerm object to hold the search term
* @param aUserAdded boolean indicating if the row addition was initiated by the user
* (e.g. via the '+' button)
*/
function createSearchRow(index, scope, searchTerm, aUserAdded)
{
var searchAttr = document.createElement("searchattribute");
var searchOp = document.createElement("searchoperator");
var searchVal = document.createElement("searchvalue");
var moreButton = document.createElement("button");
var lessButton = document.createElement("button");
moreButton.setAttribute("class", "small-button");
moreButton.setAttribute("oncommand", "onMore(event);");
moreButton.setAttribute('label', '+');
moreButton.setAttribute('tooltiptext', gMoreButtonTooltipText);
lessButton.setAttribute("class", "small-button");
lessButton.setAttribute("oncommand", "onLess(event);");
lessButton.setAttribute('label', '\u2212');
lessButton.setAttribute('tooltiptext', gLessButtonTooltipText);
// now set up ids:
searchAttr.id = "searchAttr" + gUniqueSearchTermCounter;
searchOp.id = "searchOp" + gUniqueSearchTermCounter;
searchVal.id = "searchVal" + gUniqueSearchTermCounter;
searchAttr.setAttribute("for", searchOp.id + "," + searchVal.id);
searchOp.setAttribute("opfor", searchVal.id);
var rowdata = [searchAttr, searchOp, searchVal,
[moreButton, lessButton] ];
var searchrow = constructRow(rowdata);
searchrow.id = "searchRow" + gUniqueSearchTermCounter;
var searchTermObj = new searchTermContainer;
searchTermObj.searchattribute = searchAttr;
searchTermObj.searchoperator = searchOp;
searchTermObj.searchvalue = searchVal;
// now insert the new search term into our list of terms
gSearchTerms.splice(index, 0, {obj:searchTermObj, scope:scope, searchTerm:searchTerm, initialized:false});
var editFilter = null;
try { editFilter = gFilter; } catch(e) { }
var editMailView = null;
try { editMailView = gMailView; } catch(e) { }
if ((!editFilter && !editMailView) ||
(editFilter && index == gTotalSearchTerms) ||
(editMailView && index == gTotalSearchTerms))
gLoading = false;
// index is index of new row
// gTotalSearchTerms has not been updated yet
if (gLoading || index == gTotalSearchTerms) {
gSearchTermList.appendChild(searchrow);
}
else {
var currentItem = gSearchTermList.getItemAtIndex(index);
gSearchTermList.insertBefore(searchrow, currentItem);
}
// If this row was added by user action, focus the value field.
if (aUserAdded) {
document.commandDispatcher.advanceFocusIntoSubtree(searchVal);
searchrow.setAttribute("highlight", "true");
}
// bump our unique search term counter
gUniqueSearchTermCounter++;
}
function initializeTermFromId(id)
{
initializeTermFromIndex(getSearchRowIndexForElement(document.getElementById(id)));
}
function initializeTermFromIndex(index)
{
var searchTermObj = gSearchTerms[index].obj;
searchTermObj.searchScope = gSearchTerms[index].scope;
// the search term will initialize the searchTerm element, including
// .booleanAnd
if (gSearchTerms[index].searchTerm)
searchTermObj.searchTerm = gSearchTerms[index].searchTerm;
// here, we don't have a searchTerm, so it's probably a new element -
// we'll initialize the .booleanAnd from the existing setting in
// the UI
else
{
searchTermObj.booleanAnd = (gSearchBooleanRadiogroup.value == "and");
if (index)
{
// If we weren't pre-initialized with a searchTerm then steal the
// search attribute and operator from the previous row.
searchTermObj.searchattribute.value = gSearchTerms[index - 1].obj.searchattribute.value;
searchTermObj.searchoperator.value = gSearchTerms[index - 1].obj.searchoperator.value;
}
}
gSearchTerms[index].initialized = true;
}
/**
* Creates a <listitem> using the array children as the children
* of each listcell.
* @param aChildren An array of XUL elements to put into the listitem.
* Each array member is put into a separate listcell.
* If the member itself is an array of elements,
* all of them are put into the same listcell.
*/
function constructRow(aChildren)
{
let listitem = document.createElement("listitem");
listitem.setAttribute("allowevents", "true");
for (let i = 0; i < aChildren.length; i++) {
let listcell = document.createElement("listcell");
let child = aChildren[i];
if (child instanceof Array) {
for (let j = 0; j < child.length; j++)
listcell.appendChild(child[j]);
} else {
child.setAttribute("flex", "1");
listcell.appendChild(child);
}
listitem.appendChild(listcell);
}
return listitem;
}
function removeSearchRow(index)
{
var searchTermObj = gSearchTerms[index].obj;
if (!searchTermObj) {
return;
}
// if it is an existing (but offscreen) term,
// make sure it is initialized before we remove it.
if (!gSearchTerms[index].searchTerm && !gSearchTerms[index].initialized)
initializeTermFromIndex(index);
// need to remove row from list, so walk upwards from the
// searchattribute to find the first <listitem>
var listitem = searchTermObj.searchattribute;
while (listitem) {
if (listitem.localName == "listitem") break;
listitem = listitem.parentNode;
}
if (!listitem) {
dump("Error: couldn't find parent listitem!\n");
return;
}
if (searchTermObj.searchTerm) {
gSearchRemovedTerms[gSearchRemovedTerms.length] = searchTermObj.searchTerm;
} else {
//dump("That wasn't real. ignoring \n");
}
listitem.remove();
// now remove the item from our list of terms
gSearchTerms.splice(index, 1);
}
// save the search terms from the UI back to the actual search terms
// searchTerms: nsISupportsArray of terms
// termOwner: object which can contain and create the terms
// (will be unnecessary if we just make terms creatable
// via XPCOM)
function saveSearchTerms(searchTerms, termOwner)
{
var matchAll = gSearchBooleanRadiogroup.value == 'matchAll';
var i;
for (i = 0; i < gSearchRemovedTerms.length; i++)
searchTerms.RemoveElement(gSearchRemovedTerms[i]);
for (i = 0; i < gSearchTerms.length; i++) {
try {
gSearchTerms[i].obj.matchAll = matchAll;
var searchTerm = gSearchTerms[i].obj.searchTerm;
if (searchTerm) {
gSearchTerms[i].obj.save();
} else if (!gSearchTerms[i].initialized) {
// the term might be an offscreen one we haven't initialized yet
searchTerm = gSearchTerms[i].searchTerm;
} else {
// need to create a new searchTerm, and somehow save it to that
searchTerm = termOwner.createTerm();
gSearchTerms[i].obj.saveTo(searchTerm);
// this might not be the right place for the term,
// but we need to make the array longer anyway
termOwner.appendTerm(searchTerm);
}
searchTerms.SetElementAt(i, searchTerm);
} catch (ex) {
dump("** Error saving element " + i + ": " + ex + "\n");
}
}
}
function onReset(event)
{
while (gTotalSearchTerms>0)
removeSearchRow(--gTotalSearchTerms);
onMore(null);
}
function hideMatchAllItem()
{
var allItems = document.getElementById('matchAllItem');
if (allItems)
allItems.hidden = true;
}

View file

@ -0,0 +1,66 @@
<?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/. -->
<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/searchTermOverlay.dtd">
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://messenger/content/searchTermOverlay.js"/>
<script type="application/javascript"
src="chrome://messenger/content/dateFormat.js"/>
<vbox id="searchTermListBox">
<radiogroup id="booleanAndGroup" orient="horizontal" value="and"
oncommand="booleanChanged(event);">
<radio value="and" label="&matchAll.label;"
accesskey="&matchAll.accesskey;"/>
<radio value="or" label="&matchAny.label;"
accesskey="&matchAny.accesskey;"/>
<radio value="matchAll" id="matchAllItem" label="&matchAllMsgs.label;"
accesskey="&matchAllMsgs.accesskey;"/>
</radiogroup>
<hbox flex="1">
<hbox id="searchterms"/>
<listbox flex="1" id="searchTermList" rows="4" minheight="35%">
<listcols>
<listcol flex="&searchTermListAttributesFlexValue;"/>
<listcol flex="&searchTermListOperatorsFlexValue;"/>
<listcol flex="&searchTermListValueFlexValue;"/>
<listcol class="filler"/>
</listcols>
<!-- this is what the listitems will look like:
<listitem id="searchListItem">
<listcell allowevents="true">
<searchattribute id="searchAttr1" for="searchOp1,searchValue1" flex="1"/>
</listcell>
<listcell allowevents="true">
<searchoperator id="searchOp1" opfor="searchValue1" flex="1"/>
</listcell>
<listcell allowevents="true" >
<searchvalue id="searchValue1" flex="1"/>
</listcell>
<listcell>
<button label="add"/>
<button label="remove"/>
</listcell>
</listitem>
<listitem>
<listcell label="the.."/>
<listcell label="contains.."/>
<listcell label="text here"/>
<listcell label="+/-"/>
</listitem>
-->
</listbox>
</hbox>
</vbox>
</overlay>

View file

@ -0,0 +1,738 @@
<?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/. -->
<!--
This file has the following external dependencies:
-gFilterActionStrings from FilterEditor.js
-gFilterList from FilterEditor.js
-gFilter from FilterEditor.js
-gCustomActions from FilterEditor.js
-gFilterType from FilterEditor.js
-checkActionsReorder from FilterEditor.js
-->
<!DOCTYPE dialog [
<!ENTITY % filterEditorDTD SYSTEM "chrome://messenger/locale/FilterEditor.dtd" >
%filterEditorDTD;
<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
%messengerDTD;
]>
<bindings id="filterBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:nc="http://home.netscape.com/NC-rdf#"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="ruleactiontype-menulist">
<content>
<xul:menulist class="ruleaction-type">
<xul:menupopup>
<xul:menuitem label="&moveMessage.label;" value="movemessage" enablefornews="false"/>
<xul:menuitem label="&copyMessage.label;" value="copymessage"/>
<xul:menuseparator enablefornews="false"/>
<xul:menuitem label="&forwardTo.label;" value="forwardmessage" enablefornews="false"/>
<xul:menuitem label="&replyWithTemplate.label;" value="replytomessage" enablefornews="false"/>
<xul:menuseparator/>
<xul:menuitem label="&markMessageRead.label;" value="markasread"/>
<xul:menuitem label="&markMessageUnread.label;" value="markasunread"/>
<xul:menuitem label="&markMessageStarred.label;" value="markasflagged"/>
<xul:menuitem label="&setPriority.label;" value="setpriorityto"/>
<xul:menuitem label="&addTag.label;" value="addtagtomessage"/>
<xul:menuitem label="&setJunkScore.label;" value="setjunkscore" enablefornews="false"/>
<xul:menuseparator enableforpop3="true"/>
<xul:menuitem label="&deleteMessage.label;" value="deletemessage"/>
<xul:menuitem label="&deleteFromPOP.label;" value="deletefrompopserver" enableforpop3="true"/>
<xul:menuitem label="&fetchFromPOP.label;" value="fetchfrompopserver" enableforpop3="true"/>
<xul:menuseparator/>
<xul:menuitem label="&ignoreThread.label;" value="ignorethread"/>
<xul:menuitem label="&ignoreSubthread.label;" value="ignoresubthread"/>
<xul:menuitem label="&watchThread.label;" value="watchthread"/>
<xul:menuseparator/>
<xul:menuitem label="&stopExecution.label;" value="stopexecution"/>
</xul:menupopup>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
this.addCustomActions();
this.hideInvalidActions();
// differentiate between creating a new, next available action,
// and creating a row which will be initialized with an action
if (!this.parentNode.hasAttribute('initialActionIndex'))
{
var unavailableActions = this.usedActionsList();
// select the first one that's not in the list
for (var index = 0; index < this.menuitems.length; index++)
{
var menu = this.menuitems[index];
if (!(menu.value in unavailableActions) && !menu.hidden)
{
this.menulist.value = menu.value;
this.parentNode.setAttribute('value', menu.value);
break;
}
}
}
else
{
this.parentNode.mActionTypeInitialized = true;
this.parentNode.clearInitialActionIndex();
}
]]>
</constructor>
<field name="menulist">document.getAnonymousNodes(this)[0]</field>
<field name="menuitems">this.menulist.getElementsByTagNameNS(this.menulist.namespaceURI, 'menuitem')</field>
<method name="hideInvalidActions">
<body>
<![CDATA[
let menupopup = this.menulist.menupopup;
let scope = getScopeFromFilterList(gFilterList);
// walk through the list of filter actions and hide any actions which aren't valid
// for our given scope (news, imap, pop, etc) and context
let elements, i;
// disable / enable all elements in the "filteractionlist"
// based on the scope and the "enablefornews" attribute
elements = menupopup.getElementsByAttribute("enablefornews", "true");
for (i = 0; i < elements.length; i++)
elements[i].hidden = scope != Components.interfaces.nsMsgSearchScope.newsFilter;
elements = menupopup.getElementsByAttribute("enablefornews", "false");
for (i = 0; i < elements.length; i++)
elements[i].hidden = scope == Components.interfaces.nsMsgSearchScope.newsFilter;
elements = menupopup.getElementsByAttribute("enableforpop3", "true");
for (i = 0; i < elements.length; i++)
elements[i].hidden = !((gFilterList.folder.server.type == "pop3") ||
(gFilterList.folder.server.type == "none"));
elements = menupopup.getElementsByAttribute("isCustom", "true");
// Note there might be an additional element here as a placeholder
// for a missing action, so we iterate over the known actions
// instead of the elements.
for (i = 0; i < gCustomActions.length; i++)
elements[i].hidden = !gCustomActions[i]
.isValidForType(gFilterType, scope);
// Disable "Reply with Template" if there are no templates.
if (!this.getTemplates(false)) {
elements = menupopup.getElementsByAttribute("value", "replytomessage");
if (elements.length == 1)
elements[0].hidden = true;
}
]]>
</body>
</method>
<method name="addCustomActions">
<body>
<![CDATA[
var menupopup = this.menulist.menupopup;
for (var i = 0; i < gCustomActions.length; i++)
{
var customAction = gCustomActions[i];
var menuitem = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"xul:menuitem");
menuitem.setAttribute("label", customAction.name);
menuitem.setAttribute("value", customAction.id);
menuitem.setAttribute("isCustom", "true");
menupopup.appendChild(menuitem);
}
]]>
</body>
</method>
<!-- returns a hash containing all of the filter actions which are currently being used by other filteractionrows -->
<method name="usedActionsList">
<body>
<![CDATA[
var usedActions = {};
var currentFilterActionRow = this.parentNode;
var listBox = currentFilterActionRow.mListBox; // need to account for the list item
// now iterate over each list item in the list box
for (var index = 0; index < listBox.getRowCount(); index++)
{
var filterActionRow = listBox.getItemAtIndex(index);
if (filterActionRow != currentFilterActionRow)
{
var actionValue = filterActionRow.getAttribute('value');
// let custom actions decide if dups are allowed
var isCustom = false;
for (var i = 0; i < gCustomActions.length; i++)
{
if (gCustomActions[i].id == actionValue)
{
isCustom = true;
if (!gCustomActions[i].allowDuplicates)
usedActions[actionValue] = true;
break;
}
}
if (!isCustom) {
// The following actions can appear more than once in a single filter
// so do not set them as already used.
if (actionValue != 'addtagtomessage' &&
actionValue != 'forwardmessage' &&
actionValue != 'copymessage')
usedActions[actionValue] = true;
// If either Delete message or Move message exists, disable the other one.
// It does not make sense to apply both to the same message.
if (actionValue == 'deletemessage')
usedActions['movemessage'] = true;
else if (actionValue == 'movemessage')
usedActions['deletemessage'] = true;
// The same with Mark as read/Mark as Unread.
else if (actionValue == 'markasread')
usedActions['markasunread'] = true;
else if (actionValue == 'markasunread')
usedActions['markasread'] = true;
}
}
}
return usedActions;
]]>
</body>
</method>
<!--
- Check if there exist any templates in this account.
-
- @param populateTemplateList If true, create menuitems representing
- the found templates.
- @param templateMenuList The menulist element to create items in.
-
- @return True if at least one template was found, otherwise false.
-->
<method name="getTemplates">
<parameter name="populateTemplateList"/>
<parameter name="templateMenuList"/>
<body>
<![CDATA[
Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
let identitiesRaw = MailServices.accounts
.getIdentitiesForServer(gFilterList.folder.server);
let identities = Array.from(this.fixIterator(identitiesRaw,
Components.interfaces.nsIMsgIdentity));
if (!identities.length) // typically if this is Local Folders
identities.push(MailServices.accounts.defaultAccount.defaultIdentity);
let templateFound = false;
let foldersScanned = [];
for (let identity of identities) {
let enumerator = null;
let msgFolder;
try {
msgFolder = Components.classes["@mozilla.org/rdf/rdf-service;1"]
.getService(Components.interfaces.nsIRDFService)
.GetResource(identity.stationeryFolder)
.QueryInterface(Components.interfaces.nsIMsgFolder);
// If we already processed this folder, do not set enumerator
// so that we skip this identity.
if (foldersScanned.indexOf(msgFolder) == -1) {
foldersScanned.push(msgFolder);
enumerator = msgFolder.msgDatabase.EnumerateMessages();
}
} catch (e) {
// The Templates folder may not exist, that is OK.
}
if (!enumerator)
continue;
while (enumerator.hasMoreElements()) {
let header = enumerator.getNext();
if (header instanceof Components.interfaces.nsIMsgDBHdr) {
templateFound = true;
if (!populateTemplateList)
return true;
let msgTemplateUri = msgFolder.URI + "?messageId=" +
header.messageId + '&subject=' + header.mime2DecodedSubject;
let newItem = templateMenuList.appendItem(header.mime2DecodedSubject,
msgTemplateUri);
}
}
}
return templateFound;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="command">
<![CDATA[
this.parentNode.setAttribute('value', this.menulist.value);
checkActionsReorder();
]]>
</handler>
<handler event="popupshowing">
<![CDATA[
var unavailableActions = this.usedActionsList();
for (var index = 0; index < this.menuitems.length; index++)
{
var menu = this.menuitems[index];
menu.setAttribute('disabled', menu.value in unavailableActions);
}
]]>
</handler>
</handlers>
</binding>
<!-- This binding exists to disable the default binding of a listitem
in the search terms. -->
<binding id="listitem">
<implementation>
<method name="_fireEvent">
<parameter name="aName"/>
<body>
<![CDATA[
/* This provides a dummy _fireEvent function that
the listbox expects to be able to call.
See bug 202036. */
]]>
</body>
</method>
</implementation>
</binding>
<binding id="ruleaction" extends="#listitem">
<content allowevents="true">
<xul:listcell class="ruleactiontype"
orient="vertical" align="stretch" pack="center"/>
<xul:listcell class="ruleactiontarget" xbl:inherits="type=value"
orient="vertical" align="stretch" pack="center"/>
<xul:listcell>
<xul:button class="small-button"
label="+"
tooltiptext="&addAction.tooltip;"
oncommand="this.parentNode.parentNode.addRow();"/>
<xul:button class="small-button"
label="&#x2212;"
tooltiptext="&removeAction.tooltip;"
oncommand="this.parentNode.parentNode.removeRow();"
anonid="removeButton"/>
</xul:listcell>
</content>
<implementation>
<field name="mListBox">this.parentNode</field>
<field name="mRemoveButton">document.getAnonymousElementByAttribute(this, "anonid", "removeButton")</field>
<field name="mActionTypeInitialized">false</field>
<field name="mRuleActionTargetInitialized">false</field>
<field name="mRuleActionType">document.getAnonymousNodes(this)[0]</field>
<method name="clearInitialActionIndex">
<body>
<![CDATA[
// we should only remove the initialActionIndex after we have been told that
// both the rule action type and the rule action target have both been built since they both need
// this piece of information. This complication arises because both of these child elements are getting
// bound asynchronously after the search row has been constructed
if (this.mActionTypeInitialized && this.mRuleActionTargetInitialized)
this.removeAttribute('initialActionIndex');
]]>
</body>
</method>
<method name="initWithAction">
<parameter name="aFilterAction"/>
<body>
<![CDATA[
var filterActionStr;
var actionTarget = document.getAnonymousNodes(this)[1];
var actionItem = document.getAnonymousNodes(actionTarget);
var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
switch (aFilterAction.type)
{
case nsMsgFilterAction.Custom:
filterActionStr = aFilterAction.customId;
if (actionItem)
actionItem[0].value = aFilterAction.strValue;
// Make sure the custom action has been added. If not, it
// probably was from an extension that has been removed. We'll
// show a dummy menuitem to warn the user.
var needCustomLabel = true;
for (var i = 0; i < gCustomActions.length; i++)
{
if (gCustomActions[i].id == filterActionStr)
{
needCustomLabel = false;
break;
}
}
if (needCustomLabel)
{
var menuitem = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"xul:menuitem");
menuitem.setAttribute("label",
gFilterBundle.getString("filterMissingCustomAction"));
menuitem.setAttribute("value", filterActionStr);
menuitem.disabled = true;
this.mRuleActionType.menulist.menupopup.appendChild(menuitem);
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Components.interfaces.nsIScriptError);
scriptError.init("Missing custom action " + filterActionStr,
null, null, 0, 0,
Components.interfaces.nsIScriptError.errorFlag,
"component javascript");
Services.console.logMessage(scriptError);
}
break;
case nsMsgFilterAction.MoveToFolder:
case nsMsgFilterAction.CopyToFolder:
actionItem[0].value = aFilterAction.targetFolderUri;
break;
case nsMsgFilterAction.Reply:
case nsMsgFilterAction.Forward:
actionItem[0].value = aFilterAction.strValue;
break;
case nsMsgFilterAction.Label:
actionItem[0].value = aFilterAction.label;
break;
case nsMsgFilterAction.ChangePriority:
actionItem[0].value = aFilterAction.priority;
break;
case nsMsgFilterAction.JunkScore:
actionItem[0].value = aFilterAction.junkScore;
break;
case nsMsgFilterAction.AddTag:
actionItem[0].value = aFilterAction.strValue;
break;
default:
break;
}
if (aFilterAction.type != nsMsgFilterAction.Custom)
filterActionStr = gFilterActionStrings[aFilterAction.type];
document.getAnonymousNodes(this.mRuleActionType)[0]
.value = filterActionStr;
this.mRuleActionTargetInitialized = true;
this.clearInitialActionIndex();
checkActionsReorder();
]]>
</body>
</method>
<method name="validateAction">
<body>
<![CDATA[
// returns true if this row represents a valid filter action and false otherwise.
// This routine also prompts the user.
Components.utils.import("resource:///modules/MailUtils.js", this);
var filterActionString = this.getAttribute('value');
var actionTarget = document.getAnonymousNodes(this)[1];
var errorString, customError;
switch (filterActionString)
{
case "movemessage":
case "copymessage":
let msgFolder = document.getAnonymousNodes(actionTarget)[0].value ?
this.MailUtils.getFolderForURI(document.getAnonymousNodes(actionTarget)[0].value) : null;
if (!msgFolder || !msgFolder.canFileMessages)
errorString = "mustSelectFolder";
break;
case "forwardmessage":
if (document.getAnonymousNodes(actionTarget)[0].value.length < 3 ||
document.getAnonymousNodes(actionTarget)[0].value.indexOf('@') < 1)
errorString = "enterValidEmailAddress";
break;
case "replytomessage":
if (!document.getAnonymousNodes(actionTarget)[0].selectedItem)
errorString = "pickTemplateToReplyWith";
break;
default:
// some custom actions have no action value node
if (!document.getAnonymousNodes(actionTarget))
return true;
// locate the correct custom action, and check validity
for (var i = 0; i < gCustomActions.length; i++)
if (gCustomActions[i].id == filterActionString)
{
customError =
gCustomActions[i].validateActionValue(
document.getAnonymousNodes(actionTarget)[0].value,
gFilterList.folder, gFilterType);
break;
}
break;
}
errorString = errorString ?
gFilterBundle.getString(errorString) :
customError;
if (errorString)
Services.prompt.alert(window, null, errorString);
return !errorString;
]]>
</body>
</method>
<method name="saveToFilter">
<parameter name="aFilter"/>
<body>
<![CDATA[
// create a new filter action, fill it in, and then append it to the filter
var filterAction = aFilter.createAction();
var filterActionString = this.getAttribute('value');
filterAction.type = gFilterActionStrings.indexOf(filterActionString);
var actionTarget = document.getAnonymousNodes(this)[1];
var actionItem = document.getAnonymousNodes(actionTarget);
var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
switch (filterAction.type)
{
case nsMsgFilterAction.Label:
filterAction.label = actionItem[0].getAttribute("value");
break;
case nsMsgFilterAction.ChangePriority:
filterAction.priority = actionItem[0].getAttribute("value");
break;
case nsMsgFilterAction.MoveToFolder:
case nsMsgFilterAction.CopyToFolder:
filterAction.targetFolderUri = actionItem[0].value;
break;
case nsMsgFilterAction.JunkScore:
filterAction.junkScore = actionItem[0].value;
break;
case nsMsgFilterAction.Custom:
filterAction.customId = filterActionString;
// fall through to set the value
default:
if (actionItem)
filterAction.strValue = actionItem[0].value;
break;
}
aFilter.appendAction(filterAction);
]]>
</body>
</method>
<method name="getActionStrings">
<parameter name="aActionStrings"/>
<body>
<![CDATA[
// Collect the action names and arguments in a plain string form.
let actionTarget = document.getAnonymousNodes(this)[1];
let actionItem = document.getAnonymousNodes(actionTarget);
aActionStrings.push({
label: document.getAnonymousNodes(this.mRuleActionType)[0].label,
argument: actionItem ?
(actionItem[0].label ?
actionItem[0].label : actionItem[0].value) : ""
});
]]>
</body>
</method>
<method name="updateRemoveButton">
<body>
<![CDATA[
// if we only have one row of actions, then disable the remove button for that row
this.mListBox.getItemAtIndex(0).mRemoveButton.disabled = this.mListBox.getRowCount() == 1;
]]>
</body>
</method>
<method name="addRow">
<body>
<![CDATA[
let listItem = document.createElement('listitem');
listItem.className = 'ruleaction';
listItem.setAttribute('onfocus','this.storeFocus();');
this.mListBox.insertBefore(listItem, this.nextSibling);
this.mListBox.ensureElementIsVisible(listItem);
// make sure the first remove button is enabled
this.updateRemoveButton();
checkActionsReorder();
]]>
</body>
</method>
<method name="removeRow">
<body>
<![CDATA[
// this.mListBox will fail after the row is removed, so save it
let listBox = this.mListBox;
if (listBox.getRowCount() > 1)
this.remove();
// can't use 'this' as it is destroyed now
listBox.getItemAtIndex(0).updateRemoveButton();
checkActionsReorder();
]]>
</body>
</method>
<method name="storeFocus">
<body>
<![CDATA[
// When this action row is focused, store its index in the parent listbox.
this.mListBox.setAttribute("focusedAction", this.mListBox.getIndexOfItem(this));
]]>
</body>
</method>
</implementation>
</binding>
<binding id="ruleactiontarget-base">
<implementation>
<constructor>
<![CDATA[
if (this.parentNode.hasAttribute('initialActionIndex'))
{
let actionIndex = this.parentNode.getAttribute('initialActionIndex');
let filterAction = gFilter.getActionAt(actionIndex);
this.parentNode.initWithAction(filterAction);
}
this.parentNode.updateRemoveButton();
]]>
</constructor>
</implementation>
</binding>
<binding id="ruleactiontarget-tag" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
</xul:menupopup>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
let menuPopup = document.getAnonymousNodes(this)[0].menupopup;
let tagArray = MailServices.tags.getAllTags({});
for (let i = 0; i < tagArray.length; ++i)
{
var taginfo = tagArray[i];
var newMenuItem = document.createElement('menuitem');
newMenuItem.setAttribute('label', taginfo.tag);
newMenuItem.setAttribute('value', taginfo.key);
menuPopup.appendChild(newMenuItem);
}
// propagating a pre-existing hack to make the tag get displayed correctly in the menulist
// now that we've changed the tags for each menu list. We need to use the current selectedIndex
// (if its defined) to handle the case where we were initialized with a filter action already.
var currentItem = document.getAnonymousNodes(this)[0].selectedItem;
document.getAnonymousNodes(this)[0].selectedItem = null;
document.getAnonymousNodes(this)[0].selectedItem = currentItem;
]]>
</constructor>
</implementation>
</binding>
<binding id="ruleactiontarget-priority" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
<xul:menuitem value="6" label="&highestPriorityCmd.label;"/>
<xul:menuitem value="5" label="&highPriorityCmd.label;"/>
<xul:menuitem value="4" label="&normalPriorityCmd.label;"/>
<xul:menuitem value="3" label="&lowPriorityCmd.label;"/>
<xul:menuitem value="2" label="&lowestPriorityCmd.label;"/>
</xul:menupopup>
</xul:menulist>
</content>
</binding>
<binding id="ruleactiontarget-junkscore" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
<xul:menuitem value="100" label="&junk.label;"/>
<xul:menuitem value="0" label="&notJunk.label;"/>
</xul:menupopup>
</xul:menulist>
</content>
</binding>
<binding id="ruleactiontarget-replyto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
</xul:menupopup>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
document.getAnonymousElementByAttribute(
this.parentNode, "class", "ruleactiontype")
.getTemplates(true, document.getAnonymousNodes(this)[0]);
]]>
</constructor>
</implementation>
</binding>
<binding id="ruleactiontarget-forwardto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:textbox class="ruleactionitem" flex="1"/>
</content>
</binding>
<binding id="ruleactiontarget-folder" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem folderMenuItem"
displayformat="verbose"
oncommand="this.parentNode.setPicker(event);">
<xul:menupopup type="folder"
mode="filing"
class="menulist-menupopup"
showRecent="true"
recentLabel="&recentFolders.label;"
showFileHereLabel="true"/>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
Components.utils.import("resource:///modules/MailUtils.js", this);
let folder = this.menulist.value ?
this.MailUtils.getFolderForURI(this.menulist.value) :
gFilterList.folder;
// An account folder is not a move/copy target; show "Choose Folder".
folder = folder.isServer ? null : folder;
let menupopup = this.menulist.menupopup;
// The menupopup constructor needs to finish first.
setTimeout(function() { menupopup.selectFolder(folder); }, 0);
]]>
</constructor>
<field name="menulist">document.getAnonymousNodes(this)[0]</field>
<method name="setPicker">
<parameter name="aEvent"/>
<body>
<![CDATA[
this.menulist.menupopup.selectFolder(aEvent.target._folder);
]]>
</body>
</method>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,36 @@
/* 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/. */
var gFilterList;
var gLogFilters;
var gLogView;
function onLoad()
{
gFilterList = window.arguments[0].filterList;
gLogFilters = document.getElementById("logFilters");
gLogFilters.checked = gFilterList.loggingEnabled;
gLogView = document.getElementById("logView");
// for security, disable JS
gLogView.docShell.allowJavascript = false;
gLogView.setAttribute("src", gFilterList.logURL);
}
function toggleLogFilters()
{
gFilterList.loggingEnabled = gLogFilters.checked;
}
function clearLog()
{
gFilterList.clearLog();
// reload the newly truncated file
gLogView.reload();
}

View file

@ -0,0 +1,49 @@
<?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/. -->
<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/viewLog.dtd">
<dialog id="viewLogWindow"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="onLoad();"
title="&viewLog.title;"
windowtype="mailnews:filterlog"
buttons="accept"
buttonlabelaccept="&closeLog.label;"
buttonaccesskeyaccept="&closeLog.accesskey;"
ondialogaccept="window.close();"
persist="screenX screenY width height"
style="width: 40em; height: 25em;">
<script type="application/javascript" src="chrome://messenger/content/viewLog.js"/>
<vbox flex="1">
<description>&viewLogInfo.text;</description>
<hbox>
<checkbox id="logFilters"
label="&enableLog.label;"
accesskey="&enableLog.accesskey;"
oncommand="toggleLogFilters();"/>
<spacer flex="1"/>
<button label="&clearLog.label;"
accesskey="&clearLog.accesskey;"
oncommand="clearLog();"/>
</hbox>
<separator class="thin"/>
<hbox flex="1">
<browser id="logView"
class="inset"
type="content"
disablehistory="true"
disablesecurity="true"
src="about:blank"
autofind="false"
flex="1"/>
</hbox>
</vbox>
</dialog>