Bug 373502 - Implement Fx2 style microsummary picker in the properties dialog. r=dietrich.

git-svn-id: svn://10.0.0.236/trunk@222212 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
mozilla.mano%sent.com 2007-03-22 23:13:57 +00:00
parent d69b531ce7
commit 4e9ba1ddf2
7 changed files with 438 additions and 229 deletions

View File

@ -35,7 +35,7 @@ interface nsIMicrosummaryObserver : nsISupports
};
[scriptable, uuid(71c469b4-6e31-427e-922d-48aafbd17344)]
[scriptable, uuid(05b48344-d0a7-427e-934e-9a6e0d5ecced)]
interface nsIMicrosummaryGenerator : nsISupports
{
// Has the generator itself, which may be a remote resource, been loaded.
@ -62,6 +62,12 @@ interface nsIMicrosummaryGenerator : nsISupports
// of the generators they create.
readonly attribute nsIURI uri;
/**
* microsummary-generator equivalence test
* generators equal if their canonical locations equal, see uri attribute.
*/
boolean equals(in nsIMicrosummaryGenerator aOther);
// For generators installed by the user or bundled with the browser, the
// local URI points to the location of the local file containing the
// generator's XML.
@ -103,7 +109,7 @@ interface nsIMicrosummaryGenerator : nsISupports
};
[scriptable, uuid(1b1f232d-e65f-446a-9984-786578526072)]
[scriptable, uuid(a999eabf-68f9-45c2-919f-2ad33777e3e3)]
interface nsIMicrosummary : nsISupports
{
// the URI of the page being summarized
@ -139,6 +145,15 @@ interface nsIMicrosummary : nsISupports
*/
void removeObserver(in nsIMicrosummaryObserver observer);
/**
* Microsummary equivalence test
* Microsummaries equal if they summarize the same page with the same
* microsummary-generator (see also nsIMicrosummaryGenerator::equals).
*
* Note: this method returns false if either objects don't have a generator
*/
boolean equals(in nsIMicrosummary aOther);
/**
* Update the microsummary, first loading its generator and page content
* as necessary. If you want know when a microsummary finishes updating,

View File

@ -20,6 +20,7 @@
* Contributor(s):
* Myk Melez <myk@mozilla.org> (Original Author)
* Simon Bünzli <zeniko@gmail.com>
* Asaf Romano <mano@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -350,8 +351,7 @@ MicrosummaryService.prototype = {
// upgraded from earlier versions to make bug 346822 no longer significant.
this._fixGeneratorID(resource.content, resource.uri);
var generator = new MicrosummaryGenerator();
generator.localURI = resource.uri;
var generator = new MicrosummaryGenerator(null, resource.uri);
generator.initFromXML(resource.content);
// Add the generator to the local generators cache.
@ -481,8 +481,7 @@ MicrosummaryService.prototype = {
file = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
file.append(fileName);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
generator = new MicrosummaryGenerator();
generator.localURI = this._ios.newFileURI(file);
generator = new MicrosummaryGenerator(null, this._ios.newFileURI(file));
this._localGenerators[generatorID] = generator;
topic = "microsummary-generator-installed";
}
@ -563,12 +562,20 @@ MicrosummaryService.prototype = {
// If this is the current microsummary for this bookmark, load the content
// from the datastore so it shows up immediately in microsummary picking UI.
if (bookmarkID && this.isMicrosummary(bookmarkID, microsummary))
microsummary.content = this._getField(bookmarkID, FIELD_GENERATED_TITLE);
microsummary._content = this._getField(bookmarkID, FIELD_GENERATED_TITLE);
microsummaries.AppendElement(microsummary);
}
}
// If a bookmark identifier has been provided, list its microsummary
// synchronously, if any.
if (bookmarkID && this.hasMicrosummary(bookmarkID)) {
var currentMicrosummary = this.getMicrosummary(bookmarkID);
if (!microsummaries.hasItemForMicrosummary(currentMicrosummary))
microsummaries.AppendElement(currentMicrosummary);
}
// Get microsummaries defined by the page. If we don't have the page,
// download it asynchronously, and then finish populating the set.
var resource = getLoadedMicrosummaryResource(pageURI);
@ -868,13 +875,10 @@ MicrosummaryService.prototype = {
var pageURI = this._getPageForBookmark(bookmarkID);
var generatorURI = this._uri(this._getField(bookmarkID, FIELD_MICSUM_GEN_URI));
var localGenerator = this._localGenerators[generatorURI.spec];
var generator = this._localGenerators[generatorURI.spec] ||
new MicrosummaryGenerator(generatorURI);
var microsummary = new Microsummary(pageURI, localGenerator);
if (!localGenerator)
microsummary.generator.uri = generatorURI;
return microsummary;
return new Microsummary(pageURI, generator);
},
/**
@ -994,15 +998,15 @@ MicrosummaryService.prototype = {
* for the bookmark
*
*/
isMicrosummary: function MSS_isMicrosummary(bookmarkID, microsummary) {
if (!this.hasMicrosummary(bookmarkID))
return false;
var currentGen = this._getField(bookmarkID, FIELD_MICSUM_GEN_URI);
if (microsummary.generator.uri.equals(this._uri(currentGen)))
return true;
isMicrosummary: function MSS_isMicrosummary(aBookmarkID, aMicrosummary) {
if (!aMicrosummary || !aBookmarkID)
throw Cr.NS_ERROR_INVALID_ARG;
if (this.hasMicrosummary(aBookmarkID)) {
currentMicrosummarry = this.getMicrosummary(aBookmarkID);
if (aMicrosummary.equals(currentMicrosummarry))
return true;
}
return false
},
@ -1033,11 +1037,10 @@ MicrosummaryService.prototype = {
throw("can't get URL for bookmark with ID " + bookmarkID);
var generatorURI = this._uri(this._getField(bookmarkID, FIELD_MICSUM_GEN_URI));
var localGenerator = this._localGenerators[generatorURI.spec];
var generator = this._localGenerators[generatorURI.spec] ||
new MicrosummaryGenerator(generatorURI);
var microsummary = new Microsummary(pageURI, localGenerator);
if (!localGenerator)
microsummary.generator.uri = generatorURI;
var microsummary = new Microsummary(pageURI, generator);
// A microsummary observer that calls the microsummary service
// to update the datastore when the microsummary finishes loading.
@ -1069,10 +1072,13 @@ MicrosummaryService.prototype = {
function Microsummary(pageURI, generator) {
function Microsummary(aPageURI, aGenerator) {
this._observers = [];
this.pageURI = pageURI;
this.generator = generator ? generator : new MicrosummaryGenerator();
this._pageURI = aPageURI || null;
this._generator = aGenerator || null;
this._content = null;
this._pageContent = null;
this._updateInterval = null;
}
Microsummary.prototype = {
@ -1117,15 +1123,13 @@ Microsummary.prototype = {
},
// nsIMicrosummary
_content: null,
get content() {
// If we have everything we need to generate the content, generate it.
if (this._content == null &&
if (!this._content &&
this.generator.loaded &&
(this.pageContent || !this.generator.needsPageContent)) {
this._content = this.generator.generateMicrosummary(this.pageContent);
this.updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
}
// Note: we return "null" if the content wasn't already generated and we
@ -1135,17 +1139,21 @@ Microsummary.prototype = {
// setting an observer to tell them when content generation is done.
return this._content;
},
set content(newValue) { this._content = newValue },
_generator: null,
get generator() { return this._generator },
set generator(newValue) { this._generator = newValue },
set generator(newValue) { return this._generator = newValue },
_pageURI: null,
get pageURI() { return this._pageURI },
set pageURI(newValue) { this._pageURI = newValue },
get pageURI() { return this._pageURI },
equals: function(aOther) {
if (this._generator &&
this._pageURI.equals(aOther.pageURI) &&
this._generator.equals(aOther.generator))
return true;
return false;
},
_pageContent: null,
get pageContent() {
if (!this._pageContent) {
// If the page is currently loaded into a browser window, use that.
@ -1158,16 +1166,13 @@ Microsummary.prototype = {
return this._pageContent;
},
set pageContent(newValue) { this._pageContent = newValue },
set pageContent(newValue) { return this._pageContent = newValue },
_updateInterval: null,
get updateInterval() { return this._updateInterval; },
set updateInterval(newValue) { this._updateInterval = newValue; },
set updateInterval(newValue) { return this._updateInterval = newValue; },
// nsIMicrosummary
_observers: null,
addObserver: function MS_addObserver(observer) {
// Register the observer, but only if it isn't already registered,
// so that we don't call the same observer twice for any given change.
@ -1242,8 +1247,8 @@ Microsummary.prototype = {
// Now that we have both the generator and (if needed) the page content,
// generate the microsummary, then let the observers know about it.
this.content = this.generator.generateMicrosummary(this.pageContent);
this.updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
this._content = this.generator.generateMicrosummary(this.pageContent);
this._updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
this.pageContent = null;
for ( var i = 0; i < this._observers.length; i++ )
this._observers[i].onContentLoaded(this);
@ -1381,7 +1386,15 @@ Microsummary.prototype = {
function MicrosummaryGenerator() {}
function MicrosummaryGenerator(aURI, aLocalURI, aName) {
this._uri = aURI || null;
this._localURI = aLocalURI || null;
this._name = aName || null;
this._loaded = false;
this._rules = [];
this._template = null;
this._content = null;
}
MicrosummaryGenerator.prototype = {
@ -1412,33 +1425,18 @@ MicrosummaryGenerator.prototype = {
// but for generators stored in the app or profile generators directory
// it's the value of the generator tag's "uri" attribute (or its local URI
// should the "uri" attribute be missing).
_uri: null,
get uri() { return this._uri || this.localURI },
set uri(newValue) { this._uri = newValue },
// For generators bundled with the browser or installed by the user,
// the local URI is the URI of the local file containing the generator XML.
_localURI: null,
get localURI() { return this._localURI },
set localURI(newValue) { this._localURI = newValue },
_name: null,
get name() { return this._name },
set name(newValue) { this._name = newValue },
_template: null,
get template() { return this._template },
set template(newValue) { this._template = newValue },
_content: null,
get content() { return this._content },
set content(newValue) { this._content = newValue },
_loaded: false,
get loaded() { return this._loaded },
set loaded(newValue) { this._loaded = newValue },
_rules: null,
equals: function(aOther) {
// XXX: could the uri attribute for an exposed generator ever be null?
return aOther.uri.equals(this.uri);
},
/**
* Determines whether or not the generator applies to a given URI.
@ -1475,12 +1473,12 @@ MicrosummaryGenerator.prototype = {
},
get needsPageContent() {
if (this.template)
if (this._template)
return true;
else if (this.content)
if (this._content)
return false;
else
throw("needsPageContent called on uninitialized microsummary generator");
throw("needsPageContent called on uninitialized microsummary generator");
},
/**
@ -1490,11 +1488,10 @@ MicrosummaryGenerator.prototype = {
*
* @param text
* the text content
*
*/
initFromText: function(text) {
this.content = text;
this.loaded = true;
this._content = text;
this._loaded = true;
},
/**
@ -1518,12 +1515,12 @@ MicrosummaryGenerator.prototype = {
if (!generatorNode)
throw Cr.NS_ERROR_FAILURE;
this.name = generatorNode.getAttribute("name");
this._name = generatorNode.getAttribute("name");
// If this is a local generator (i.e. it has a local URI), then we have
// to retrieve its URI from the "uri" attribute of its generator tag.
if (this.localURI && generatorNode.hasAttribute("uri")) {
this.uri = this._ios.newURI(generatorNode.getAttribute("uri"), null, null);
this._uri = this._ios.newURI(generatorNode.getAttribute("uri"), null, null);
}
function getFirstChildByTagName(tagName, parentNode, namespace) {
@ -1539,7 +1536,7 @@ MicrosummaryGenerator.prototype = {
// Slurp the include/exclude rules that determine the pages to which
// this generator applies. Order is important, so we add the rules
// in the order in which they appear in the XML.
this._rules = [];
this._rules.splice(0);
var pages = getFirstChildByTagName("pages", generatorNode, MICSUM_NS);
if (pages) {
// XXX Make sure the pages tag exists.
@ -1588,23 +1585,23 @@ MicrosummaryGenerator.prototype = {
var templateNode = getFirstChildByTagName("template", generatorNode, MICSUM_NS);
if (templateNode) {
this.template = getFirstChildByTagName("transform", templateNode, XSLT_NS) ||
getFirstChildByTagName("stylesheet", templateNode, XSLT_NS);
this._template = getFirstChildByTagName("transform", templateNode, XSLT_NS) ||
getFirstChildByTagName("stylesheet", templateNode, XSLT_NS);
}
// XXX Make sure the template is a valid XSL transform sheet.
this.loaded = true;
this._loaded = true;
},
generateMicrosummary: function MSD_generateMicrosummary(pageContent) {
var content;
if (this.content) {
content = this.content;
} else if (this.template) {
if (this._content)
content = this._content;
else if (this._template)
content = this._processTemplate(pageContent);
} else
else
throw("generateMicrosummary called on uninitialized microsummary generator");
// Clean up the output
@ -1616,7 +1613,7 @@ MicrosummaryGenerator.prototype = {
},
calculateUpdateInterval: function MSD_calculateUpdateInterval(doc) {
if (this.content || !this._updateIntervals || !doc)
if (this._content || !this._updateIntervals || !doc)
return null;
for (var i = 0; i < this._updateIntervals.length; i++) {
@ -1636,7 +1633,7 @@ MicrosummaryGenerator.prototype = {
},
_processTemplate: function MSD__processTemplate(doc) {
LOG("processing template " + this.template + " against document " + doc);
LOG("processing template " + this._template + " against document " + doc);
// XXX Should we just have one global instance of the processor?
var processor = Cc["@mozilla.org/document-transformer;1?type=xslt"].
@ -1646,7 +1643,7 @@ MicrosummaryGenerator.prototype = {
// for security (otherwise local generators would be able to load local files).
processor.flags |= Ci.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
processor.importStylesheet(this.template);
processor.importStylesheet(this._template);
var fragment = processor.transformToFragment(doc, doc);
LOG("template processing result: " + fragment.textContent);
@ -1672,7 +1669,6 @@ function MicrosummarySet() {
}
MicrosummarySet.prototype = {
// IO Service
__ios: null,
get _ios() {
@ -1695,9 +1691,6 @@ MicrosummarySet.prototype = {
return this;
},
_observers: null,
_elements: null,
// nsIMicrosummaryObserver
onContentLoaded: function MSSet_onContentLoaded(microsummary) {
@ -1780,13 +1773,25 @@ MicrosummarySet.prototype = {
continue;
}
var microsummary = new Microsummary(resource.uri, null);
microsummary.generator.name = linkTitle;
microsummary.generator.uri = generatorURI;
this.AppendElement(microsummary);
var generator = new MicrosummaryGenerator(generatorURI, null, linkTitle);
var microsummary = new Microsummary(resource.uri, generator);
if (!this.hasItemForMicrosummary(microsummary))
this.AppendElement(microsummary);
}
},
/**
* Determines whether the given microsumary is already represented in the
* set.
*/
hasItemForMicrosummary: function MSSet_hasItemForMicrosummary(aMicrosummary) {
for (var i = 0; i < this._elements.length; i++) {
if (this._elements[i].equals(aMicrosummary))
return true;
}
return false;
},
// XXX Turn this into a complete implementation of nsICollection?
AppendElement: function MSSet_AppendElement(element) {
// Query the element to a microsummary.
@ -1818,6 +1823,7 @@ MicrosummarySet.prototype = {
*/
function ArrayEnumerator(aItems) {
this._index = 0;
this._contents = [];
if (aItems) {
for (var i = 0; i < aItems.length; ++i) {
if (!aItems[i])
@ -1837,9 +1843,6 @@ ArrayEnumerator.prototype = {
throw Cr.NS_ERROR_NO_INTERFACE;
return this;
},
_index: 0,
_contents: [],
hasMoreElements: function() {
return this._index < this._contents.length;
@ -1892,7 +1895,18 @@ function MicrosummaryResource(uri) {
if (uri.scheme != "http" && uri.scheme != "https" && uri.scheme != "file")
throw NS_ERROR_DOM_BAD_URI;
this._uri = uri;
this._uri = uri || null;
this._content = null;
this._contentType = null;
this._isXML = false;
this.__authFailed = false;
// A function to call when we finish loading/parsing the resource.
this._loadCallback = null;
// A function to call if we get an error while loading/parsing the resource.
this._errorCallback = null;
// A hidden iframe to parse HTML content.
this._iframe = null;
}
MicrosummaryResource.prototype = {
@ -1905,35 +1919,22 @@ MicrosummaryResource.prototype = {
return this.__ios;
},
_uri: null,
get uri() {
return this._uri;
},
_content: null,
get content() {
return this._content;
},
_contentType: null,
get contentType() {
return this._contentType;
},
_isXML: false,
get isXML() {
return this._isXML;
},
// A function to call when we finish loading/parsing the resource.
_loadCallback: null,
// A function to call if we get an error while loading/parsing the resource.
_errorCallback: null,
// A hidden iframe to parse HTML content.
_iframe: null,
// Implement notification callback interfaces so we can suppress UI
// and abort loads for bad SSL certs and HTTP authorization requests.
@ -1995,9 +1996,8 @@ MicrosummaryResource.prototype = {
// Auth requests appear to succeed when we cancel them (since the server
// redirects us to a "you're not authorized" page), so we have to set a flag
// to let the load handler know to treat the load as a failure.
__authFailed: false,
get _authFailed() { return this.__authFailed },
set _authFailed(newValue) { this.__authFailed = newValue },
get _authFailed() { return this.__authFailed; },
set _authFailed(newValue) { return this.__authFailed = newValue },
// nsIAuthPromptProvider

View File

@ -78,7 +78,6 @@
* - "location"
* - "description"
* - "keyword"
* - "microsummary"
* - "load in sidebar"
* - "feedURI"
* - "siteURI"
@ -358,30 +357,12 @@ var BookmarkPropertiesPanel = {
}
},
_initMicrosummaryPicker: function BPP__initMicrosummaryPicker() {
var placeURI = null;
if (this._action == ACTION_EDIT) {
NS_ASSERT(this._bookmarkId, "No bookmark identifier");
placeURI = PlacesUtils.bookmarks.getItemURI(this._bookmarkId);
}
try {
this._microsummaries = this._mss.getMicrosummaries(this._bookmarkURI,
placeURI);
}
catch(ex) {
// There was a problem retrieving microsummaries; disable the picker.
// The microsummary service will throw an exception in at least
// two cases:
// 1. the bookmarked URI contains a scheme that the service won't
// download for security reasons (currently it only handles http,
// https, and file);
// 2. the page to which the URI refers isn't HTML or XML (the only two
// content types the service knows how to summarize).
this._element("microsummaryRow").hidden = true;
return;
}
this._microsummaries.addObserver(this._microsummaryObserver);
this._rebuildMicrosummaryPicker();
QueryInterface: function BPP_QueryInterface(aIID) {
if (aIID.equals(Ci.nsIMicrosummaryObserver) ||
aIID.eqauls(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
_element: function BPP__element(aID) {
@ -398,7 +379,7 @@ var BookmarkPropertiesPanel = {
return;
if (hiddenRows.indexOf("title") != -1)
this._element("titleTextfield").hidden = true;
this._element("namePicker").hidden = true;
if (hiddenRows.indexOf("location") != -1)
this._element("locationRow").hidden = true;
if (hiddenRows.indexOf("keyword") != -1)
@ -411,8 +392,6 @@ var BookmarkPropertiesPanel = {
this._element("livemarkFeedLocationRow").hidden = true;
if (hiddenRows.indexOf("siteURI") != -1)
this._element("livemarkSiteLocationRow").hidden = true;
if (hiddenRows.indexOf("microsummary") != -1)
this._element("microsummaryRow").hidden = true;
if (hiddenRows.indexOf("load in sidebar") != -1)
this._element("loadInSidebarCheckbox").hidden = true;
},
@ -423,8 +402,8 @@ var BookmarkPropertiesPanel = {
_populateProperties: function BPP__populateProperties() {
document.title = this._getDialogTitle();
document.documentElement.getButton("accept").label = this._getAcceptLabel();
this._element("titleTextfield").value = this._itemTitle;
this._initNamePicker();
this._element("descriptionTextfield").value = this._itemDescription;
if (this._itemType == BOOKMARK_ITEM) {
@ -454,83 +433,127 @@ var BookmarkPropertiesPanel = {
this._element("livemarkSiteLocationRow").hidden = true;
}
if (this._itemType == BOOKMARK_ITEM && this._bookmarkURI) {
// _initMicrosummaryPicker may also hide the row
this._initMicrosummaryPicker();
}
else
this._element("microsummaryRow").hidden = true;
if (this._action == ACTION_EDIT)
this._element("folderRow").hidden = true;
},
//XXXDietrich - bug 370215 - update to use bookmark id once 360133 is fixed.
_rebuildMicrosummaryPicker: function BPP__rebuildMicrosummaryPicker() {
var microsummaryMenuList = this._element("microsummaryMenuList");
var microsummaryMenuPopup = this._element("microsummaryMenuPopup");
_createMicrosummaryMenuItem:
function BPP__createMicrosummaryMenuItem(aMicrosummary) {
var menuItem = document.createElement("menuitem");
// Remove old items from the menu, except the first item, which represents
// "don't show a microsummary; show the page title instead".
while (microsummaryMenuPopup.childNodes.length > 1)
microsummaryMenuPopup.removeChild(microsummaryMenuPopup.lastChild);
// Store a reference to the microsummary in the menu item, so we know
// which microsummary this menu item represents when it's time to
// save changes or load its content.
menuItem.microsummary = aMicrosummary;
var enumerator = this._microsummaries.Enumerate();
while (enumerator.hasMoreElements()) {
var microsummary = enumerator.getNext().QueryInterface(Ci.nsIMicrosummary);
// Content may have to be generated asynchronously; we don't necessarily
// have it now. If we do, great; otherwise, fall back to the generator
// name, then the URI, and we trigger a microsummary content update. Once
// the update completes, the microsummary will notify our observer to
// update the corresponding menu-item.
// XXX Instead of just showing the generator name or (heaven forbid)
// its URI when we don't have content, we should tell the user that
// we're loading the microsummary, perhaps with some throbbing to let
// her know it is in progress.
if (aMicrosummary.content)
menuItem.setAttribute("label", aMicrosummary.content);
else {
menuItem.setAttribute("label", aMicrosummary.generator.name ||
aMicrosummary.generator.uri.spec);
aMicrosummary.update();
}
var menuItem = document.createElement("menuitem");
return menuItem;
},
// Store a reference to the microsummary in the menu item, so we know
// which microsummary this menu item represents when it's time to save
// changes to the datastore.
menuItem.microsummary = microsummary;
_initNamePicker: function BPP_initNamePicker() {
var userEnteredNameField = this._element("userEnteredName");
var namePicker = this._element("namePicker");
userEnteredNameField.label = this._itemTitle;
// Content may have to be generated asynchronously; we don't necessarily
// have it now. If we do, great; otherwise, fall back to the generator
// name, then the URI, and we trigger a microsummary content update.
// Once the update completes, the microsummary will notify our observer
// to rebuild the menu.
// XXX Instead of just showing the generator name or (heaven forbid)
// its URI when we don't have content, we should tell the user that we're
// loading the microsummary, perhaps with some throbbing to let her know
// it's in progress.
if (microsummary.content)
menuItem.setAttribute("label", microsummary.content);
else {
menuItem.setAttribute("label", microsummary.generator ?
microsummary.generator.name :
microsummary.generatorURI.spec);
microsummary.update();
// Non-bookmark items always use the item-title itself
if (this._itemType != BOOKMARK_ITEM || !this._bookmarkURI) {
namePicker.selectedItem = userEnteredNameField;
return;
}
var itemToSelect = userEnteredNameField;
var placeURI = null;
if (this._action == ACTION_EDIT) {
NS_ASSERT(this._bookmarkId, "No bookmark identifier");
placeURI = PlacesUtils.bookmarks.getItemURI(this._bookmarkId);
}
try {
this._microsummaries = this._mss.getMicrosummaries(this._bookmarkURI,
placeURI);
}
catch(ex) {
// getMicrosummaries will throw an exception in at least two cases:
// 1. the bookmarked URI contains a scheme that the service won't
// download for security reasons (currently it only handles http,
// https, and file);
// 2. the page to which the URI refers isn't HTML or XML (the only two
// content types the service knows how to summarize).
this._microsummaries = null;
}
if (this._microsummaries) {
var enumerator = this._microsummaries.Enumerate();
if (enumerator.hasMoreElements()) {
// Show the drop marker if there are microsummaries
namePicker.setAttribute("droppable", "true");
var menupopup = namePicker.menupopup;
while (enumerator.hasMoreElements()) {
var microsummary = enumerator.getNext()
.QueryInterface(Ci.nsIMicrosummary);
var menuItem = this._createMicrosummaryMenuItem(microsummary);
if (this._action == ACTION_EDIT &&
this._mss.isMicrosummary(placeURI, microsummary))
itemToSelect = menuItem;
menupopup.appendChild(menuItem);
}
}
microsummaryMenuPopup.appendChild(menuItem);
this._microsummaries.addObserver(this);
}
if (this._action == ACTION_EDIT) {
NS_ASSERT(this._bookmarkId, "No bookmark identifier");
var placeURI = PlacesUtils.bookmarks.getItemURI(this._bookmarkId);
// Select the item if this is the current microsummary for the bookmark.
if (this._mss.isMicrosummary(placeURI, microsummary))
microsummaryMenuList.selectedItem = menuItem;
namePicker.selectedItem = itemToSelect;
},
// nsIMicrosummaryObserver
onContentLoaded: function BPP_onContentLoaded(aMicrosummary) {
var namePicker = this._element("namePicker");
var childNodes = namePicker.menupopup.childNodes;
// 0: user-entered item; 1: separator
for (var i = 2; i < childNodes.length; i++) {
if (childNodes[i].microsummary == aMicrosummary) {
var newLabel = aMicrosummary.content;
// XXXmano: non-editable menulist would do this for us, see bug 360220
// We should fix editable-menulsits to set the DOMAttrModified as well
//
// Also note the order importance: if the label of the menu-item is
// set the something different than the menulist's current value,
// the menulist no longer has selectedItem set
if (namePicker.selectedItem == childNodes[i])
namePicker.value = newLabel;
childNodes[i].label = newLabel;
return;
}
}
},
_microsummaryObserver: {
QueryInterface: function (aIID) {
if (!aIID.equals(Ci.nsIMicrosummaryObserver) &&
!aIID.equals(Ci.nsISupports))
throw Cr.NS_ERROR_NO_INTERFACE;
return this;
},
onElementAppended: function BPP_onElementAppended(aMicrosummary) {
var namePicker = this._element("namePicker");
namePicker.menupopup
.appendChild(this._createMicrosummaryMenuItem(aMicrosummary));
onContentLoaded: function(aMicrosummary) {
BookmarkPropertiesPanel._rebuildMicrosummaryPicker();
},
onElementAppended: function(aMicrosummary) {
BookmarkPropertiesPanel._rebuildMicrosummaryPicker();
}
// Make sure the drop-marker is shown
namePicker.setAttribute("droppable", "true");
},
/**
@ -544,7 +567,7 @@ var BookmarkPropertiesPanel = {
onDialogUnload: function BPP_onDialogUnload() {
if (this._microsummaries)
this._microsummaries.removeObserver(this._microsummaryObserver);
this._microsummaries.removeObserver(this);
},
onDialogAccept: function BPP_onDialogAccept() {
@ -648,14 +671,13 @@ var BookmarkPropertiesPanel = {
if (siteURIString)
siteURI = PlacesUtils._uri(siteURIString);
var name = this._element("titleTextfield").value;
var name = this._element("namePicker").value;
return new PlacesCreateLivemarkTransaction(feedURI, siteURI,
name, containerId,
indexInContainer);
}
else if (this._itemType == BOOKMARK_FOLDER) { // folder
var name = this._element("titleTextfield").value;
var name = this._element("namePicker").value;
return new PlacesCreateFolderTransaction(name, containerId,
indexInContainer);
}
@ -688,7 +710,7 @@ var BookmarkPropertiesPanel = {
// title transaction
// XXXmano: this isn't necessary for new folders. We should probably
// make insertItem take a title too (like insertFolder)
var newTitle = this._element("titleTextfield").value;
var newTitle = this._element("userEnteredName").label;
if (this._action != ACTION_EDIT || newTitle != this._itemTitle)
transactions.push(this._getEditTitleTransaction(itemId, newTitle));
@ -736,17 +758,18 @@ var BookmarkPropertiesPanel = {
}
// microsummaries
var menuList = this._element("microsummaryMenuList");
if (isElementVisible(menuList)) {
if (this._itemType == BOOKMARK_ITEM) {
var namePicker = this._element("namePicker");
// Something should always be selected in the microsummary menu,
// but if nothing is selected, then conservatively assume we should
// just display the bookmark title.
if (menuList.selectedIndex == -1)
menuList.selectedIndex = 0;
if (namePicker.selectedIndex == -1)
namePicker.selectedIndex = 0;
// This will set microsummary == undefined if the user selected
// the "don't display a microsummary" item.
var newMicrosummary = menuList.selectedItem.microsummary;
var newMicrosummary = namePicker.selectedItem.microsummary;
if (this._action == ACTION_ADD && newMicrosummary) {
transactions.push(
@ -818,5 +841,9 @@ var BookmarkPropertiesPanel = {
this._tm.doTransaction(aggregate);
}
}
},
onNamePickerInput: function BPP_onNamePickerInput() {
this._element("userEnteredName").label = this._element("namePicker").value;
}
};

View File

@ -37,8 +37,19 @@
</columns>
<rows id="placesInfoRows">
<row id="titleRow" align="center">
<label value="&bookmark.property.title;" control="titleTextfield"/>
<textbox id="titleTextfield"/>
<label value="&bookmark.property.title;" control="namePicker"/>
<menulist id="namePicker"
editable="true"
droppable="false"
oninput="BookmarkPropertiesPanel.onNamePickerInput();">
<menupopup>
<menuitem id="userEnteredName"/>
<menuitem disabled="true">
<menuseparator flex="1"/>
<label value="&liveTitlesSeparator.label;"/>
</menuitem>
</menupopup>
</menulist>
</row>
<row id="locationRow" align="center">
<label value="&bookmark.property.location;" control="editURLBar"/>
@ -66,15 +77,6 @@
<label value="&bookmark.property.description;" control="descriptionTextfield"/>
<textbox id="descriptionTextfield" multiline="true"/>
</row>
<row id="microsummaryRow" align="center">
<label value="&bookmark.property.microsummary;" control="microsummaryMenuList"/>
<menulist id="microsummaryMenuList">
<menupopup id="microsummaryMenuPopup">
<menuitem label="&bookmark.property.microsummary.none;"
selected="true"/>
</menupopup>
</menulist>
</row>
<row id="folderRow" flex="1" style="min-height: 100px;">
<vbox align="end">
<hbox align="start" flex="1">

View File

@ -8,10 +8,8 @@
"Shortcut">
<!ENTITY bookmark.property.description
"Description">
<!ENTITY bookmark.property.microsummary
"Summary">
<!ENTITY bookmark.property.microsummary.none
"Don't Display a Summary">
<!ENTITY liveTitlesSeparator.label
"Live Titles">
<!ENTITY bookmark.property.folders
"Folders">
<!ENTITY bookmark.property.folder_list

View File

@ -1,3 +1,98 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Firefox.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Myk Melez <myk@mozilla.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
/**** folder tree ****/
#folderTree {
margin: 4px;
}
/**** name picker ****/
/* Make the microsummary picker look like a regular textbox instead of
* an editable menulist when no microsummaries are available.
*/
menulist#namePicker[droppable="false"] {
-moz-appearance: none;
margin: 0px;
border: none;
padding: 0px;
height: auto !important;
}
menulist#namePicker[droppable="false"] > .menulist-dropmarker {
display: none;
}
menulist#namePicker[droppable="false"] > .menulist-editable-box {
/* These rules are duplicates of the rules for the textbox element
* in textbox.css and should track changes in that file.
*/
-moz-appearance: textfield;
cursor: text;
margin: 2px 4px;
border: 2px solid;
-moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
-moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
-moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
-moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
padding: 2px 2px 3px 4px;
background-color: -moz-Field;
color: -moz-FieldText;
}
menulist#namePicker[droppable="false"] > .menulist-editable-box > html|*.textbox-input {
margin: 0px !important;
border: none !important;
padding: 0px !important;
background-color: inherit;
color: inherit;
font: inherit;
}
/* Hide the drop marker and the popup. */
menulist#namePicker[droppable="false"] > .menulist-dropmarker {
display: none;
}
menulist#namePicker[droppable="false"] > menupopup {
display: none;
}

View File

@ -1,3 +1,75 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Firefox.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Myk Melez <myk@mozilla.org>
* Simon BŸnzli <zeniko@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**** folder tree ****/
#folderTree {
margin: 4px;
}
/**** name picker ****/
/* Make the microsummary picker look like a regular textbox instead of
* an editable menulist when no microsummaries are available.
*/
menulist#namePicker[droppable="false"] {
/* These rules come from the textbox element in textbox.css. */
/* Normal editable menulists set this to "none". */
-moz-appearance: textfield;
cursor: text;
border: 2px solid;
-moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
-moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
-moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
-moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
background-color: -moz-Field;
color: -moz-FieldText;
}
menulist#namePicker[droppable="false"] > .menulist-editable-box {
/* Normal editable menulists set this to "menulist-textfield". */
-moz-appearance: none;
padding: 2px 2px 3px 4px;
}
/* Hide the drop marker and the popup when no microsummaries are available. */
menulist#namePicker[droppable="false"] > .menulist-dropmarker,
menulist#namePicker[droppable="false"] > menupopup {
display: none;
}