mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-27 10:57:34 +09:00
Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1
This commit is contained in:
parent
23e0d82436
commit
e400f4130a
1564 changed files with 510348 additions and 0 deletions
203
mailnews/base/search/content/CustomHeaders.js
Normal file
203
mailnews/base/search/content/CustomHeaders.js
Normal 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();
|
||||
}
|
||||
57
mailnews/base/search/content/CustomHeaders.xul
Normal file
57
mailnews/base/search/content/CustomHeaders.xul
Normal 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>
|
||||
854
mailnews/base/search/content/FilterEditor.js
Normal file
854
mailnews/base/search/content/FilterEditor.js
Normal 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();
|
||||
}
|
||||
125
mailnews/base/search/content/FilterEditor.xul
Normal file
125
mailnews/base/search/content/FilterEditor.xul
Normal 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>
|
||||
536
mailnews/base/search/content/searchTermOverlay.js
Normal file
536
mailnews/base/search/content/searchTermOverlay.js
Normal 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;
|
||||
}
|
||||
66
mailnews/base/search/content/searchTermOverlay.xul
Normal file
66
mailnews/base/search/content/searchTermOverlay.xul
Normal 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>
|
||||
738
mailnews/base/search/content/searchWidgets.xml
Normal file
738
mailnews/base/search/content/searchWidgets.xml
Normal 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="©Message.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="−"
|
||||
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="¬Junk.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>
|
||||
36
mailnews/base/search/content/viewLog.js
Normal file
36
mailnews/base/search/content/viewLog.js
Normal 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();
|
||||
}
|
||||
|
||||
49
mailnews/base/search/content/viewLog.xul
Normal file
49
mailnews/base/search/content/viewLog.xul
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue