зеркало из https://github.com/mozilla/pjs.git
Bug 416548: tags pref panel migration; r=IanN, sr=neil
This commit is contained in:
Родитель
fec5b42bca
Коммит
d2ba47f299
|
@ -57,12 +57,20 @@
|
|||
|
||||
<!-- category tree entries for mail/news -->
|
||||
<treechildren id="prefsPanelChildren">
|
||||
<treeitem container="true" id="mailnews" label="&mail.label;"
|
||||
prefpane="mail_prefs_general" insertafter="navigator"
|
||||
<treeitem container="true"
|
||||
id="mailnewsItem"
|
||||
insertafter="navigator"
|
||||
label="&mail.label;"
|
||||
prefpane="mailnews_pane"
|
||||
url="chrome://messenger/content/pref-mailnews.xul"
|
||||
helpTopic="mail_prefs_general">
|
||||
<treechildren id="messengerChildren">
|
||||
<!-- XXX Move pref panes from below to here as they are migrated -->
|
||||
<treeitem id="tagsItem"
|
||||
label="&tags.label;"
|
||||
prefpane="tags_pane"
|
||||
url="chrome://messenger/content/pref-tags.xul"
|
||||
helpTopic="mail-prefs-tags"/>
|
||||
</treechildren>
|
||||
</treeitem>
|
||||
</treechildren>
|
||||
|
@ -107,7 +115,7 @@
|
|||
</treeitem>
|
||||
<treeitem id="mailtagspref">
|
||||
<treerow>
|
||||
<treecell url="chrome://messenger/content/pref-labels.xul" label="&tags.label;"/>
|
||||
<treecell label="(Migrated: &tags.label;)"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
]>
|
||||
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<prefpane id="mail_prefs_general"
|
||||
<prefpane id="mailnews_pane"
|
||||
label="&pref.mailnews.title;"
|
||||
script="chrome://messenger/content/pref-mailnews.js">
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
// Each tag entry in our list looks like this, where <key> is tag's unique key:
|
||||
// Each tag entry in our list looks like this:
|
||||
// <listitem>
|
||||
// <listcell>
|
||||
// <textbox/>
|
||||
|
@ -43,104 +43,72 @@
|
|||
// <colorpicker type='button'/>
|
||||
// </listcell>
|
||||
// </listitem>
|
||||
const TAGPANEL_URI = 'chrome://messenger/content/pref-labels.xul';
|
||||
const TAGLIST_ID = 'tagList'; // UI element
|
||||
const ACTIVE_TAGS_ID = TAGLIST_ID + '.active'; // wsm element
|
||||
const DELETED_TAGS_ID = TAGLIST_ID + '.deleted'; // wsm element
|
||||
// For ease of handling, all tag data is stored in <listitem>.tagInfo also.
|
||||
|
||||
const kOrdinalCharLow = "a";
|
||||
const kOrdinalCharHigh = "z";
|
||||
const kOrdinalPadding = String.fromCharCode(kOrdinalCharLow.charCodeAt(0) - 1);
|
||||
|
||||
var gInstantApply = document.documentElement.instantApply; // read only once
|
||||
var gTagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
|
||||
.getService(Components.interfaces.nsIMsgTagService);
|
||||
var gTagList = null; // tagList root element
|
||||
var gAddButton = null;
|
||||
var gDeleteButton = null;
|
||||
var gRaiseButton = null;
|
||||
var gLowerButton = null;
|
||||
|
||||
var gDeletedTags = null; // tags to be deleted by the tagService
|
||||
var gDeletedTags = null; // tags marked for deletion in non-instant apply mode
|
||||
|
||||
// init global stuff before the wsm is used
|
||||
function InitTagPanel()
|
||||
|
||||
function Startup()
|
||||
{
|
||||
gTagList = document.getElementById(TAGLIST_ID);
|
||||
gTagList = document.getElementById('tagList');
|
||||
gAddButton = document.getElementById('addTagButton');
|
||||
gDeleteButton = document.getElementById('deleteTagButton');
|
||||
gRaiseButton = document.getElementById('raiseTagButton');
|
||||
gLowerButton = document.getElementById('lowerTagButton');
|
||||
InitTagList();
|
||||
if (!gInstantApply)
|
||||
window.addEventListener("dialogaccept", this.OnOK, true);
|
||||
UpdateButtonStates();
|
||||
parent.initPanel(TAGPANEL_URI);
|
||||
}
|
||||
|
||||
function Startup()
|
||||
function InitTagList()
|
||||
{
|
||||
parent.hPrefWindow.registerOKCallbackFunc(OnOK);
|
||||
}
|
||||
|
||||
// store pref values in the wsm
|
||||
function GetFields(aPageData)
|
||||
{
|
||||
// collect the tag definitions from the UI and store them in the wsm
|
||||
var tags = [];
|
||||
for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling)
|
||||
if (entry.localName == 'listitem')
|
||||
{
|
||||
// update taginfo with current values from textbox and colorpicker
|
||||
UpdateTagInfo(entry.taginfo, entry);
|
||||
tags.push(entry.taginfo);
|
||||
}
|
||||
aPageData[ACTIVE_TAGS_ID] = tags;
|
||||
|
||||
// store the list of tags to be deleted in the OKHandler
|
||||
aPageData[DELETED_TAGS_ID] = gDeletedTags;
|
||||
|
||||
return aPageData;
|
||||
}
|
||||
|
||||
// read pref values stored in the wsm
|
||||
function SetFields(aPageData)
|
||||
{
|
||||
var i, tags;
|
||||
// If the wsm has no tag data yet, get the list from the tag service.
|
||||
if (!(ACTIVE_TAGS_ID in aPageData))
|
||||
// Read the tags from preferences via the tag service.
|
||||
var tagArray = gTagService.getAllTags({});
|
||||
for (var i = 0; i < tagArray.length; ++i)
|
||||
{
|
||||
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
|
||||
.getService(Components.interfaces.nsIMsgTagService);
|
||||
var tagArray = tagService.getAllTags({});
|
||||
tags = aPageData[ACTIVE_TAGS_ID] = [];
|
||||
for (i = 0; i < tagArray.length; ++i)
|
||||
{
|
||||
// The nsMsgTag items are readonly, but we may need to change them.
|
||||
// And we don't care for the current ordinal strings, we'll create new
|
||||
// ones in the OKHandler if necessary
|
||||
var t = tagArray[i];
|
||||
tags.push({tag: t.tag, key: t.key, color: t.color});
|
||||
}
|
||||
var t = tagArray[i];
|
||||
var tagInfo = {tag: t.tag,
|
||||
key: t.key,
|
||||
color: t.color,
|
||||
ordinal: t.ordinal,
|
||||
new: false, // not added in this run
|
||||
changed: false}; // not changed (yet)
|
||||
AppendTagEntry(tagInfo, null);
|
||||
}
|
||||
|
||||
// now create the dynamic elements
|
||||
tags = aPageData[ACTIVE_TAGS_ID];
|
||||
|
||||
// Listitems we append to the "end" of the listbox and would be rendered
|
||||
// outside the clipping area don't get their text and color set!
|
||||
// (See also 354065.)
|
||||
// So we stuff them in bottom-up... :-|
|
||||
var beforeTag = null;
|
||||
for (i = tags.length - 1; i >= 0; --i)
|
||||
beforeTag = AppendTagEntry(tags[i], beforeTag);
|
||||
|
||||
// grab the list of tags to be deleted in the OKHandler
|
||||
gDeletedTags = (DELETED_TAGS_ID in aPageData) ? aPageData[DELETED_TAGS_ID] : {};
|
||||
}
|
||||
|
||||
// read text and color from the listitem
|
||||
function UpdateTagInfo(aTagInfo, aEntry)
|
||||
{
|
||||
aTagInfo.tag = aEntry.firstChild.firstChild.value;
|
||||
aTagInfo.color = aEntry.lastChild.lastChild.color;
|
||||
var tag = aEntry.firstChild.firstChild.value;
|
||||
var color = aEntry.lastChild.lastChild.color;
|
||||
if (tag != aTagInfo.tag || color != aTagInfo.color)
|
||||
{
|
||||
aTagInfo.changed = true; // never unset changed flag here!
|
||||
aTagInfo.tag = tag;
|
||||
aTagInfo.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
// set text and color of the listitem
|
||||
function UpdateTagEntry(aTagInfo, aEntry)
|
||||
{
|
||||
aEntry.firstChild.firstChild.value = aTagInfo.tag;
|
||||
aEntry.lastChild.lastChild.color = aTagInfo.color;
|
||||
aEntry.lastChild.lastChild.color = aTagInfo.color || 'inherit';
|
||||
}
|
||||
|
||||
function AppendTagEntry(aTagInfo, aRefChild)
|
||||
|
@ -149,10 +117,9 @@ function AppendTagEntry(aTagInfo, aRefChild)
|
|||
// You MUST first set its type attribute (to select the correct binding), then
|
||||
// add the element to the DOM (to bind the binding) and finally set the color
|
||||
// property(!) afterwards. Try in any other order and fail... :-(
|
||||
var key = aTagInfo.key;
|
||||
|
||||
var tagCell = document.createElement('listcell');
|
||||
var textbox = document.createElement('textbox');
|
||||
textbox.setAttribute('flex', 1);
|
||||
tagCell.appendChild(textbox);
|
||||
|
||||
var colorCell = document.createElement('listcell');
|
||||
|
@ -162,8 +129,9 @@ function AppendTagEntry(aTagInfo, aRefChild)
|
|||
|
||||
var entry = document.createElement('listitem');
|
||||
entry.addEventListener('focus', OnFocus, true);
|
||||
entry.addEventListener('change', OnChange, false);
|
||||
entry.setAttribute('allowevents', 'true'); // activate textbox and colorpicker
|
||||
entry.taginfo = aTagInfo;
|
||||
entry.tagInfo = aTagInfo;
|
||||
entry.appendChild(tagCell);
|
||||
entry.appendChild(colorCell);
|
||||
|
||||
|
@ -185,6 +153,194 @@ function FocusTagEntry(aEntry)
|
|||
aEntry.firstChild.firstChild.focus();
|
||||
}
|
||||
|
||||
function GetTagOrdinal(aTagInfo)
|
||||
{
|
||||
if (aTagInfo.ordinal)
|
||||
return aTagInfo.ordinal;
|
||||
return aTagInfo.key;
|
||||
}
|
||||
|
||||
function SetTagOrdinal(aTagInfo, aOrdinal)
|
||||
{
|
||||
var ordinal = aTagInfo.ordinal;
|
||||
aTagInfo.ordinal = (aTagInfo.key != aOrdinal) ? aOrdinal : '';
|
||||
if (aTagInfo.ordinal != ordinal)
|
||||
aTagInfo.changed = true;
|
||||
}
|
||||
|
||||
function BisectString(aPrev, aNext)
|
||||
{
|
||||
// find a string which is lexically greater than aPrev and lesser than aNext:
|
||||
// - copy leading parts common to aPrev and aNext into the result
|
||||
// - find the first position where aPrev and aNext differ:
|
||||
// - if we can squeeze a character in between there: fine, done!
|
||||
// - if not:
|
||||
// - if the rest of aNext is longer than one character, we can squeeze
|
||||
// in just the first aNext rest-character and be done!
|
||||
// - else we try to "increment" aPrev a bit to fit in
|
||||
if ((aPrev >= aNext) || (aPrev + kOrdinalCharLow >= aNext))
|
||||
return ''; // no such string exists
|
||||
|
||||
// pad the shorter string
|
||||
var lenPrev = aPrev.length;
|
||||
var lenNext = aNext.length;
|
||||
var lenMax = Math.max(lenPrev, lenNext);
|
||||
|
||||
// loop over both strings at once, padding if necessary
|
||||
var constructing = false;
|
||||
var result = '';
|
||||
for (var i = 0; i < lenMax; ++i)
|
||||
{
|
||||
var prevChar = (i < lenPrev) ? aPrev[i] : kOrdinalPadding;
|
||||
var nextChar = constructing ? kOrdinalCharHigh
|
||||
: (i < lenNext) ? aNext[i]
|
||||
: kOrdinalPadding;
|
||||
var prevCode = prevChar.charCodeAt(0);
|
||||
var nextCode = nextChar.charCodeAt(0);
|
||||
if (prevCode == nextCode)
|
||||
{
|
||||
// copy common characters
|
||||
result += prevChar;
|
||||
}
|
||||
else if (prevCode + 1 < nextCode)
|
||||
{
|
||||
// found a real bisecting string
|
||||
result += String.fromCharCode((prevCode + nextCode) / 2);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// nextCode is greater than prevCode, but there's no place in between.
|
||||
// But if aNext[i+1] exists, then nextChar will suffice and we're done!
|
||||
// ("x" < "xsomething")
|
||||
if (i + 1 < lenNext)
|
||||
{
|
||||
// found a real bisecting string
|
||||
return result + nextChar;
|
||||
}
|
||||
// just copy over prevChar and enter construction mode
|
||||
result += prevChar;
|
||||
constructing = true;
|
||||
}
|
||||
}
|
||||
return ''; // nothing found
|
||||
}
|
||||
|
||||
function RecalculateOrdinal(aEntry)
|
||||
{
|
||||
// Calculate a new ordinal for the given entry, assuming that both its
|
||||
// predecessor's and successor's are correct, i.e. ord(p) < ord(s)!
|
||||
var tagInfo = aEntry.tagInfo;
|
||||
var ordinal = tagInfo.key;
|
||||
// get neighbouring ordinals
|
||||
var prevOrdinal = '', nextOrdinal = '';
|
||||
var prev = aEntry.previousSibling;
|
||||
if (prev && prev.nodeName == 'listitem') // first.prev == listhead
|
||||
prevOrdinal = GetTagOrdinal(prev.tagInfo);
|
||||
var next = aEntry.nextSibling;
|
||||
if (next)
|
||||
{
|
||||
nextOrdinal = GetTagOrdinal(next.tagInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ensure key < nextOrdinal if entry is the last/only entry
|
||||
nextOrdinal = prevOrdinal || ordinal;
|
||||
nextOrdinal = String.fromCharCode(nextOrdinal.charCodeAt(0) + 2);
|
||||
}
|
||||
|
||||
if (prevOrdinal < ordinal && ordinal < nextOrdinal)
|
||||
{
|
||||
// no ordinal needed, just clear it
|
||||
SetTagOrdinal(tagInfo, '')
|
||||
return;
|
||||
}
|
||||
|
||||
// so we need a new ordinal, because key <= prevOrdinal or key >= nextOrdinal
|
||||
ordinal = BisectString(prevOrdinal, nextOrdinal);
|
||||
if (ordinal)
|
||||
{
|
||||
// found a new ordinal
|
||||
SetTagOrdinal(tagInfo, ordinal)
|
||||
return;
|
||||
}
|
||||
|
||||
// couldn't find an ordinal before the nextOrdinal, so take that instead
|
||||
// and recalculate a new one for the next entry
|
||||
SetTagOrdinal(tagInfo, nextOrdinal);
|
||||
if (next)
|
||||
ApplyChange(next);
|
||||
}
|
||||
|
||||
function OnChange(aEvent)
|
||||
{
|
||||
ApplyChange(aEvent.currentTarget);
|
||||
}
|
||||
|
||||
function ApplyChange(aEntry)
|
||||
{
|
||||
if (!aEntry)
|
||||
{
|
||||
dump('ApplyChange: aEntry is null! (called by ' + ApplyChange.caller.name + ')\n');
|
||||
return;
|
||||
}
|
||||
|
||||
// the tag data got changed, so write it back to the system
|
||||
var tagInfo = aEntry.tagInfo;
|
||||
UpdateTagInfo(tagInfo, aEntry);
|
||||
// ensure unique tag name
|
||||
var dupeList = ReadTagListFromUI(aEntry);
|
||||
var uniqueTag = DisambiguateTag(tagInfo.tag, dupeList);
|
||||
if (tagInfo.tag != uniqueTag)
|
||||
{
|
||||
tagInfo.tag = uniqueTag;
|
||||
tagInfo.changed = true;
|
||||
UpdateTagEntry(tagInfo, aEntry);
|
||||
}
|
||||
|
||||
if (gInstantApply)
|
||||
{
|
||||
// If the item was newly added, we still can rename the key,
|
||||
// so that it's in sync with the actual tag.
|
||||
if (tagInfo.new && tagInfo.key)
|
||||
{
|
||||
// Do not clear the "new" flag!
|
||||
// The key will only stick after closing the dialog.
|
||||
gTagService.deleteKey(tagInfo.key);
|
||||
tagInfo.key = '';
|
||||
}
|
||||
if (!tagInfo.key)
|
||||
{
|
||||
// create a new key, based upon the new tag
|
||||
gTagService.addTag(tagInfo.tag, '', '');
|
||||
tagInfo.key = gTagService.getKeyForTag(tagInfo.tag);
|
||||
}
|
||||
|
||||
// Recalculate the sort ordinal, if necessary.
|
||||
// We assume that the neighbour's ordinals are correct,
|
||||
// i.e. that ordinal(pos - 1) < ordinal(pos + 1)!
|
||||
RecalculateOrdinal(aEntry);
|
||||
WriteTag(tagInfo);
|
||||
}
|
||||
}
|
||||
|
||||
function WriteTag(aTagInfo)
|
||||
{
|
||||
//dump('********** WriteTag: ' + aTagInfo.toSource() + '\n');
|
||||
try
|
||||
{
|
||||
gTagService.addTagForKey(aTagInfo.key,
|
||||
aTagInfo.tag,
|
||||
aTagInfo.color,
|
||||
aTagInfo.ordinal);
|
||||
aTagInfo.changed = false;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
dump('WriteTag: update exception:\n' + e);
|
||||
}
|
||||
}
|
||||
|
||||
function UpdateButtonStates()
|
||||
{
|
||||
var entry = gTagList.selectedItem;
|
||||
|
@ -196,6 +352,16 @@ function UpdateButtonStates()
|
|||
gLowerButton.disabled = !entry || !gTagList.getNextItem(entry, 1);
|
||||
}
|
||||
|
||||
function ReadTagListFromUI(aIgnoreEntry)
|
||||
{
|
||||
// reads distinct tag names from the UI
|
||||
var dupeList = {}; // indexed by tag
|
||||
for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling)
|
||||
if ((entry != aIgnoreEntry) && (entry.localName == 'listitem'))
|
||||
dupeList[entry.firstChild.firstChild.value] = true;
|
||||
return dupeList;
|
||||
}
|
||||
|
||||
function DisambiguateTag(aTag, aTagList)
|
||||
{
|
||||
if (aTag in aTagList)
|
||||
|
@ -210,20 +376,25 @@ function DisambiguateTag(aTag, aTagList)
|
|||
|
||||
function AddTag()
|
||||
{
|
||||
// Add a new tag to the UI here. It will be only be written to the
|
||||
// preference system only if the OKHandler is executed!
|
||||
// Add a new tag to the UI here.
|
||||
// It will be be written to the preference system
|
||||
// (a) directly on each change for instant apply, or
|
||||
// (b) only if the dialogaccept handler is executed.
|
||||
|
||||
// create unique tag name
|
||||
var dupeList = {}; // indexed by tag
|
||||
for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling)
|
||||
if (entry.localName == 'listitem')
|
||||
dupeList[entry.firstChild.firstChild.value] = true;
|
||||
// create new unique tag name
|
||||
var dupeList = ReadTagListFromUI();
|
||||
var tag = DisambiguateTag(gAddButton.getAttribute('defaulttagname'), dupeList);
|
||||
|
||||
// create new tag list entry
|
||||
var tagInfo = {tag: tag, key: '', color: '', ordinal: ''};
|
||||
var tagInfo = {tag: tag,
|
||||
key: '',
|
||||
color: 'inherit',
|
||||
ordinal: '',
|
||||
new: true,
|
||||
changed: true};
|
||||
var refChild = gTagList.getNextItem(gTagList.selectedItem, 1);
|
||||
var newEntry = AppendTagEntry(tagInfo, refChild);
|
||||
ApplyChange(newEntry);
|
||||
FocusTagEntry(newEntry);
|
||||
}
|
||||
|
||||
|
@ -231,11 +402,16 @@ function DeleteTag()
|
|||
{
|
||||
// Delete the selected tag from the UI here. If it was added during this
|
||||
// preference dialog session, we can drop it at once; if it was read from
|
||||
// the preferences system, we need to remember killing it in the OKHandler.
|
||||
// the preferences system, we may need to remember killing it in OnOK.
|
||||
var entry = gTagList.selectedItem;
|
||||
var key = entry.taginfo.key;
|
||||
var key = entry.tagInfo.key;
|
||||
if (key)
|
||||
gDeletedTags[key] = true; // dummy value
|
||||
{
|
||||
if (gInstantApply)
|
||||
gTagService.deleteKey(key);
|
||||
else
|
||||
gDeletedTags[key] = true; // dummy value
|
||||
}
|
||||
// after removing, move focus to next entry, if it exist, else try previous
|
||||
var newFocusItem = gTagList.getNextItem(entry, 1) ||
|
||||
gTagList.getPreviousItem(entry, 1);
|
||||
|
@ -249,15 +425,17 @@ function DeleteTag()
|
|||
function MoveTag(aMoveUp)
|
||||
{
|
||||
// Move the selected tag one position up or down in the tagList's child order.
|
||||
// This reordering may require changing ordinal strings, which will happen
|
||||
// when we write tag data to the preferences system in the OKHandler.
|
||||
// This reordering may require changing ordinal strings.
|
||||
var entry = gTagList.selectedItem;
|
||||
UpdateTagInfo(entry.taginfo, entry); // remember changed values
|
||||
var tagInfo = entry.tagInfo;
|
||||
UpdateTagInfo(tagInfo, entry); // remember changed values
|
||||
var successor = aMoveUp ? gTagList.getPreviousItem(entry, 1)
|
||||
: gTagList.getNextItem(entry, 2);
|
||||
entry.parentNode.insertBefore(entry, successor);
|
||||
FocusTagEntry(entry);
|
||||
UpdateTagEntry(entry.taginfo, entry); // needs to be visible
|
||||
tagInfo.changed = true;
|
||||
UpdateTagEntry(tagInfo, entry); // needs to be visible
|
||||
ApplyChange(entry);
|
||||
}
|
||||
|
||||
function Restore()
|
||||
|
@ -266,9 +444,14 @@ function Restore()
|
|||
// Remember any known keys for deletion in the OKHandler.
|
||||
while (gTagList.getRowCount())
|
||||
{
|
||||
var key = gTagList.removeItemAt(0).taginfo.key;
|
||||
var key = gTagList.removeItemAt(0).tagInfo.key;
|
||||
if (key)
|
||||
gDeletedTags[key] = true; // dummy value
|
||||
{
|
||||
if (gInstantApply)
|
||||
gTagService.deleteKey(key);
|
||||
else
|
||||
gDeletedTags[key] = true; // dummy value
|
||||
}
|
||||
}
|
||||
// add default items (no ordinal strings for those)
|
||||
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
|
@ -282,116 +465,49 @@ function Restore()
|
|||
var key = "$label" + i;
|
||||
var tag = prefDescription.getComplexValue(i, kLocalizedString).data;
|
||||
var color = prefColor.getCharPref(i);
|
||||
var tagInfo = {tag: tag, key: key, color: color};
|
||||
AppendTagEntry(tagInfo, null);
|
||||
var tagInfo = {tag: tag,
|
||||
key: key,
|
||||
color: color,
|
||||
ordinal: '',
|
||||
new: false,
|
||||
changed: true};
|
||||
var newEntry = AppendTagEntry(tagInfo, null);
|
||||
ApplyChange(newEntry);
|
||||
}
|
||||
FocusTagEntry(gTagList.getItemAtIndex(0));
|
||||
}
|
||||
|
||||
function OnOK()
|
||||
{
|
||||
var i;
|
||||
var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
|
||||
.getService(Components.interfaces.nsIMsgTagService);
|
||||
// we may be called in another page's context, so get the stored data from the
|
||||
// wsm the hard way
|
||||
var pageData = parent.hPrefWindow.wsm.dataManager.pageData[TAGPANEL_URI];
|
||||
var activeTags = pageData[ACTIVE_TAGS_ID];
|
||||
var deletedTags = pageData[DELETED_TAGS_ID];
|
||||
|
||||
// remove all deleted tags from the preferences system
|
||||
for (var key in deletedTags)
|
||||
tagService.deleteKey(key);
|
||||
for (var key in gDeletedTags)
|
||||
gTagService.deleteKey(key);
|
||||
|
||||
// count dupes so that we can eliminate them later
|
||||
var dupeCounts = {}; // indexed by tag
|
||||
for (i = 0; i < activeTags.length; ++i)
|
||||
// Write tags to the preferences system, creating keys and ordinal strings.
|
||||
for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling)
|
||||
{
|
||||
var tag = activeTags[i].tag;
|
||||
if (tag in dupeCounts)
|
||||
++dupeCounts[tag];
|
||||
else
|
||||
dupeCounts[tag] = 0; // no dupes found yet
|
||||
}
|
||||
|
||||
// Now write tags to the preferences system, create keys and ordinal strings.
|
||||
// Manually set ordinal strings are NOT retained!
|
||||
var lastTagInfo = null;
|
||||
for (i = 0; i < activeTags.length; ++i)
|
||||
{
|
||||
var tagInfo = activeTags[i];
|
||||
if (tagInfo)
|
||||
if (entry.localName == 'listitem')
|
||||
{
|
||||
var dupeCount = dupeCounts[tagInfo.tag];
|
||||
if (dupeCount > 0)
|
||||
// only write listitems which have changed (this includes new ones)
|
||||
var tagInfo = entry.tagInfo;
|
||||
if (tagInfo.changed)
|
||||
{
|
||||
// ignore the first dupe, but set mark for further processing
|
||||
dupeCounts[tagInfo.tag] = -1;
|
||||
if (!tagInfo.key)
|
||||
{
|
||||
// newly added tag, need to create a key and read it
|
||||
gTagService.addTag(tagInfo.tag, '', '');
|
||||
tagInfo.key = gTagService.getKeyForTag(tagInfo.tag);
|
||||
}
|
||||
if (tagInfo.key)
|
||||
{
|
||||
// Recalculate the sort ordinal, if necessary.
|
||||
// We assume that the neighbour's ordinals are correct,
|
||||
// i.e. that ordinal(pos - 1) < ordinal(pos + 1)!
|
||||
RecalculateOrdinal(entry);
|
||||
// update the tag definition
|
||||
WriteTag(tagInfo);
|
||||
}
|
||||
}
|
||||
else if (dupeCount < 0)
|
||||
{
|
||||
tagInfo.tag = DisambiguateTag(tagInfo.tag, dupeCounts);
|
||||
dupeCounts[tagInfo.tag] = 0; // new tag name is unique
|
||||
}
|
||||
|
||||
if (!tagInfo.key)
|
||||
{
|
||||
// newly added tag, need to create a key and read it
|
||||
tagService.addTag(tagInfo.tag, '', '');
|
||||
tagInfo.key = tagService.getKeyForTag(tagInfo.tag);
|
||||
}
|
||||
|
||||
if (tagInfo.key)
|
||||
{
|
||||
if (!lastTagInfo)
|
||||
{
|
||||
// the first tag list entry needs no ordinal string
|
||||
lastTagInfo = tagInfo;
|
||||
tagInfo.ordinal = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
// if tagInfo's key is lower than that of its predecessor,
|
||||
// it needs an ordinal string
|
||||
var lastOrdinal = lastTagInfo.ordinal || lastTagInfo.key;
|
||||
if (lastOrdinal >= tagInfo.key)
|
||||
{
|
||||
// create new ordinal
|
||||
var tail = lastOrdinal.length - 1;
|
||||
if (('a' <= lastOrdinal[tail]) && (lastOrdinal[tail] < 'z'))
|
||||
{
|
||||
// increment last character
|
||||
lastOrdinal = lastOrdinal.substr(0, tail) +
|
||||
String.fromCharCode(lastOrdinal.charCodeAt(tail) + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// just begin a new increment position
|
||||
lastOrdinal += 'a';
|
||||
}
|
||||
tagInfo.ordinal = lastOrdinal;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no ordinal necessary
|
||||
tagInfo.ordinal = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Update the tag definition
|
||||
try
|
||||
{
|
||||
tagService.addTagForKey(tagInfo.key,
|
||||
tagInfo.tag,
|
||||
tagInfo.color,
|
||||
tagInfo.ordinal);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
dump('Could not update tag:\n' + e);
|
||||
}
|
||||
lastTagInfo = tagInfo;
|
||||
} // have key
|
||||
} // have tagInfo
|
||||
} // for all active tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,73 +39,80 @@
|
|||
-
|
||||
- ***** END LICENSE BLOCK ***** -->
|
||||
|
||||
|
||||
<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
|
||||
|
||||
<?xul-overlay href="chrome://global/content/globalOverlay.xul"?>
|
||||
<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
|
||||
<!DOCTYPE page SYSTEM "chrome://messenger/locale/pref-tags.dtd">
|
||||
|
||||
<!DOCTYPE page SYSTEM "chrome://messenger/locale/pref-labels.dtd">
|
||||
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<prefpane id="tags_pane"
|
||||
label="&pref.tags.title;"
|
||||
script="chrome://messenger/content/pref-tags.js">
|
||||
|
||||
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="InitTagPanel()"
|
||||
headertitle="&pref.tags.title;">
|
||||
<script type="application/x-javascript" src="chrome://messenger/content/pref-labels.js"/>
|
||||
<preferences id="tags_preferences">
|
||||
<preference id="pref.tags.disable_button.add"
|
||||
name="pref.tags.disable_button.add"
|
||||
type="bool"/>
|
||||
<preference id="pref.tags.disable_button.delete"
|
||||
name="pref.tags.disable_button.delete"
|
||||
type="bool"/>
|
||||
<preference id="pref.tags.disable_button.lower"
|
||||
name="pref.tags.disable_button.lower"
|
||||
type="bool"/>
|
||||
<preference id="pref.tags.disable_button.raise"
|
||||
name="pref.tags.disable_button.raise"
|
||||
type="bool"/>
|
||||
<preference id="pref.tags.disable_button.restore"
|
||||
name="pref.tags.disable_button.restore"
|
||||
type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<script type="application/x-javascript">
|
||||
<![CDATA[
|
||||
var _elementIDs = ["addTagButton", "deleteTagButton",
|
||||
"raiseTagButton", "lowerTagButton",
|
||||
"restoreButton"];
|
||||
]]>
|
||||
</script>
|
||||
<groupbox flex="1">
|
||||
<caption label="&pref.tags.caption;"/>
|
||||
<description>&pref.tags.description;</description>
|
||||
<hbox flex="1">
|
||||
<listbox id="tagList" flex="1" onselect="UpdateButtonStates();">
|
||||
<listcols>
|
||||
<listcol flex="1"/>
|
||||
<listcol/>
|
||||
</listcols>
|
||||
<listhead>
|
||||
<listheader label="&tagColumn.label;"/>
|
||||
<listheader label="&colorColumn.label;"/>
|
||||
</listhead>
|
||||
</listbox>
|
||||
|
||||
<vbox>
|
||||
<button id="addTagButton"
|
||||
label="&addTagButton.label;"
|
||||
accesskey="&addTagButton.accesskey;"
|
||||
defaulttagname="&defaultTagName.label;"
|
||||
preference="pref.tags.disable_button.add"
|
||||
oncommand="AddTag();"/>
|
||||
<button id="deleteTagButton"
|
||||
label="&deleteTagButton.label;"
|
||||
accesskey="&deleteTagButton.accesskey;"
|
||||
preference="pref.tags.disable_button.delete"
|
||||
oncommand="DeleteTag();"/>
|
||||
<spacer flex="1"/>
|
||||
<button id="raiseTagButton"
|
||||
label="&raiseTagButton.label;"
|
||||
accesskey="&raiseTagButton.accesskey;"
|
||||
preference="pref.tags.disable_button.raise"
|
||||
oncommand="MoveTag(true);"/>
|
||||
<button id="lowerTagButton"
|
||||
label="&lowerTagButton.label;"
|
||||
accesskey="&lowerTagButton.accesskey;"
|
||||
preference="pref.tags.disable_button.lower"
|
||||
oncommand="MoveTag(false);"/>
|
||||
<spacer flex="1"/>
|
||||
<button id="restoreButton"
|
||||
label="&restoreButton.label;"
|
||||
accesskey="&restoreButton.accesskey;"
|
||||
preference="pref.tags.disable_button.restore"
|
||||
oncommand="Restore();"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
<groupbox flex="1">
|
||||
<caption label="&pref.tags.caption;"/>
|
||||
<description>&pref.tags.description;</description>
|
||||
<hbox flex="1">
|
||||
<listbox id="tagList" flex="1" onselect="UpdateButtonStates();">
|
||||
<listcols>
|
||||
<listcol flex="1"/>
|
||||
<listcol/>
|
||||
</listcols>
|
||||
<listhead>
|
||||
<listheader label="&tagColumn.label;"/>
|
||||
<listheader label="&colorColumn.label;"/>
|
||||
</listhead>
|
||||
</listbox>
|
||||
|
||||
<vbox>
|
||||
<button id="addTagButton"
|
||||
label="&addTagButton.label;"
|
||||
accesskey="&addTagButton.accesskey;"
|
||||
defaulttagname="&defaultTagName.label;"
|
||||
prefstring="pref.tags.disable_button.add"
|
||||
oncommand="AddTag();"/>
|
||||
<button id="deleteTagButton"
|
||||
label="&deleteTagButton.label;"
|
||||
accesskey="&deleteTagButton.accesskey;"
|
||||
prefstring="pref.tags.disable_button.delete"
|
||||
oncommand="DeleteTag();"/>
|
||||
<spacer flex="1"/>
|
||||
<button id="raiseTagButton"
|
||||
label="&raiseTagButton.label;"
|
||||
accesskey="&raiseTagButton.accesskey;"
|
||||
prefstring="pref.tags.disable_button.raise"
|
||||
oncommand="MoveTag(true);"/>
|
||||
<button id="lowerTagButton"
|
||||
label="&lowerTagButton.label;"
|
||||
accesskey="&lowerTagButton.accesskey;"
|
||||
prefstring="pref.tags.disable_button.lower"
|
||||
oncommand="MoveTag(false);"/>
|
||||
<spacer flex="1"/>
|
||||
<button id="restoreButton"
|
||||
label="&restoreButton.label;"
|
||||
accesskey="&restoreButton.accesskey;"
|
||||
prefstring="pref.tags.disable_button.restore"
|
||||
oncommand="Restore();"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
</page>
|
||||
</prefpane>
|
||||
</overlay>
|
||||
|
|
|
@ -641,7 +641,7 @@
|
|||
<menuitem id="threadPaneContext-tagCustomize"
|
||||
label="&tagCustomize.label;"
|
||||
accesskey="&tagCustomize.accesskey;"
|
||||
oncommand="goPreferences('mailnews', 'chrome://messenger/content/pref-labels.xul', 'mailtagspref');"/>
|
||||
oncommand="goPreferences('tags_pane');"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="threadPaneContext-mark" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
|
||||
|
@ -1019,7 +1019,7 @@
|
|||
<menuitem id="messagePaneContext-tagCustomize"
|
||||
label="&tagCustomize.label;"
|
||||
accesskey="&tagCustomize.accesskey;"
|
||||
oncommand="goPreferences('mailnews', 'chrome://messenger/content/pref-labels.xul', 'mailtagspref');"/>
|
||||
oncommand="goPreferences('tags_pane');"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="messagePaneContext-mark" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
|
||||
|
@ -1310,7 +1310,7 @@
|
|||
label="&accountManagerCmd.label;"
|
||||
accesskey="&accountManagerCmd.accesskey;"
|
||||
oncommand="MsgAccountManager(null);"/>
|
||||
<menuitem id="menu_preferences" oncommand="goPreferences('mail_prefs_general')"/>
|
||||
<menuitem id="menu_preferences" oncommand="goPreferences('mailnews_pane')"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
|
@ -1855,7 +1855,7 @@
|
|||
<menuitem id="tagMenu-tagCustomize"
|
||||
label="&tagCustomize.label;"
|
||||
accesskey="&tagCustomize.accesskey;"
|
||||
oncommand="goPreferences('mailnews', 'chrome://messenger/content/pref-labels.xul', 'mailtagspref');"/>
|
||||
oncommand="goPreferences('tags_pane');"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="markMenu" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
|
||||
|
|
|
@ -80,8 +80,8 @@ messenger.jar:
|
|||
content/messenger/pref-notifications.js (base/prefs/resources/content/pref-notifications.js)
|
||||
content/messenger/pref-junk.xul (base/prefs/resources/content/pref-junk.xul)
|
||||
content/messenger/pref-junk.js (base/prefs/resources/content/pref-junk.js)
|
||||
content/messenger/pref-labels.xul (base/prefs/resources/content/pref-labels.xul)
|
||||
content/messenger/pref-labels.js (base/prefs/resources/content/pref-labels.js)
|
||||
content/messenger/pref-tags.xul (base/prefs/resources/content/pref-tags.xul)
|
||||
content/messenger/pref-tags.js (base/prefs/resources/content/pref-tags.js)
|
||||
content/messenger/pref-viewing_messages.xul (base/prefs/resources/content/pref-viewing_messages.xul)
|
||||
content/messenger/pref-receipts.xul (base/prefs/resources/content/pref-receipts.xul)
|
||||
content/messenger/pref-receipts.js (base/prefs/resources/content/pref-receipts.js)
|
||||
|
|
|
@ -24,7 +24,7 @@ var fm = {
|
|||
"chrome://messenger/content/addressbook/pref-addressing.xul": "mail_prefs_addressing",
|
||||
"chrome://messenger/content/pref-offline.xul": "mail_prefs_offline",
|
||||
"chrome://messenger/content/pref-junk.xul": "mail-prefs-junk",
|
||||
"chrome://messenger/content/pref-labels.xul": "mail-prefs-tags",
|
||||
"chrome://messenger/content/pref-tags.xul": "mail-prefs-tags",
|
||||
"chrome://messenger/content/pref-receipts.xul": "mail-prefs-receipts",
|
||||
"chrome://editor/content/pref-composer.xul": "composer_prefs_general",
|
||||
"chrome://editor/content/pref-editing.xul": "composer_prefs_newpage",
|
||||
|
|
|
@ -279,7 +279,7 @@
|
|||
locale/@AB_CD@/messenger/prefs.properties (%chrome/mailnews/pref/prefs.properties)
|
||||
locale/@AB_CD@/messenger/pref-character_encoding.dtd (%chrome/mailnews/pref/pref-character_encoding.dtd)
|
||||
locale/@AB_CD@/messenger/pref-junk.dtd (%chrome/mailnews/pref/pref-junk.dtd)
|
||||
locale/@AB_CD@/messenger/pref-labels.dtd (%chrome/mailnews/pref/pref-labels.dtd)
|
||||
locale/@AB_CD@/messenger/pref-tags.dtd (%chrome/mailnews/pref/pref-tags.dtd)
|
||||
locale/@AB_CD@/messenger/pref-mailnews.dtd (%chrome/mailnews/pref/pref-mailnews.dtd)
|
||||
locale/@AB_CD@/messenger/pref-notifications.dtd (%chrome/mailnews/pref/pref-notifications.dtd)
|
||||
locale/@AB_CD@/messenger/pref-offline.dtd (%chrome/mailnews/pref/pref-offline.dtd)
|
||||
|
|
|
@ -46,21 +46,13 @@
|
|||
|
||||
/* ::::: Tags ::::: */
|
||||
|
||||
@-moz-document url("chrome://messenger/content/pref-labels.xul")
|
||||
#tagList > listhead
|
||||
{
|
||||
.listheader-label
|
||||
{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
listcell
|
||||
{
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
textbox
|
||||
{
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#tagList > listitem > listcell
|
||||
{
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
|
|
@ -46,21 +46,13 @@
|
|||
|
||||
/* ::::: Tags ::::: */
|
||||
|
||||
@-moz-document url("chrome://messenger/content/pref-labels.xul")
|
||||
#tagList > listhead
|
||||
{
|
||||
.listheader-label
|
||||
{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
listcell
|
||||
{
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
textbox
|
||||
{
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#tagList > listitem > listcell
|
||||
{
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче