Bug 385266 - New starring, bookmarking and tagging UI. This is work-in-progress and not yet enabled for anything but the star button itself. Smart folders are not yet hooked up either. r=dietrich.

This commit is contained in:
mozilla.mano@sent.com 2007-08-15 18:15:50 -07:00
parent 239bab7b12
commit 1bfb840f8e
23 changed files with 1351 additions and 147 deletions

View File

@ -592,3 +592,141 @@ var PlacesMenuDNDController = {
_dragSupported: true
#endif
};
var PlacesStarButton = {
init: function PSB_init() {
PlacesUtils.bookmarks.addObserver(this, false);
},
uninit: function PSB_uninit() {
PlacesUtils.bookmarks.removeObserver(this);
},
QueryInterface: function PSB_QueryInterface(aIID) {
if (aIID.equals(Ci.nsIDOMEventListener) ||
aIID.equals(Ci.nsINavBookmarkObserver) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_NOINTERFACE;
},
get panel() {
return document.getElementById("editBookmarkPanel");
},
_starred: false,
_batching: false,
updateState: function PSB_updateState() {
var uri = getBrowser().currentURI;
this._starred = uri && PlacesUtils.bookmarks.isBookmarked(uri);
if (this._starred)
document.getElementById("star-icon").setAttribute("starred", "true");
else
document.getElementById("star-icon").removeAttribute("starred");
},
_star: function PSB_star(aBrowser) {
var uri = aBrowser.currentURI;
if (!uri)
throw "No URL";
var title = PlacesUtils.history.getPageTitle(uri);
var descAnno = {
name: DESCRIPTION_ANNO,
value: PlacesUtils.getDescriptionFromDocument(aBrowser.contentDocument)
};
var txn = PlacesUtils.ptm.createItem(uri, PlacesUtils.placesRootId, -1,
title, null, [descAnno]);
PlacesUtils.ptm.commitTransaction(txn);
},
// nsIDOMEventListener
handleEvent: function PSB_handleEvent(aEvent) {
if (aEvent.originalTarget != this.panel)
return;
// This only happens for auto-hide. When the panel is closed from within
// itself, doneCallback removes the listener and only then hides the popup
gAddBookmarksPanel.saveItem();
gAddBookmarksPanel.uninitPanel();
},
showBookmarkPagePopup: function PSB_showBookmarkPagePopup(aBrowser) {
const bms = PlacesUtils.bookmarks;
var dockTo = document.getElementById("star-icon");
if (!dockTo)
dockTo = getBrowser();
var panel = this.panel;
panel.showPopup(dockTo, -1, -1, "popup", "bottomright", "topright");
var uri = aBrowser.currentURI;
var itemId = -1;
var bmkIds = bms.getBookmarkIdsForURI(uri, {});
for each (var bk in bmkIds) {
// Find the first folder which isn't a tag container
var folder = bms.getFolderIdForItem(bk);
if (folder == PlacesUtils.placesRootId ||
bms.getFolderIdForItem(folder) != PlacesUtils.tagRootId) {
itemId = bk;
break;
}
}
if (itemId == -1) {
// if we're called before the URI is bookmarked, or if the remaining
// items for this url are under tag containers, star the page first
itemId = this._star(aBrowser);
}
gAddBookmarksPanel.initPanel(itemId, PlacesUtils.tm, this.doneCallback,
{ hiddenRows: "description" });
panel.addEventListener("popuphiding", this, false);
},
onClick: function PSB_onClick(aEvent) {
if (this._starred)
this.showBookmarkPagePopup(getBrowser());
else
this._star(getBrowser());
},
doneCallback: function PSB_doneCallback(aSavedChanges) {
var panel = PlacesStarButton.panel;
panel.removeEventListener("popuphiding", PlacesStarButton, false);
gAddBookmarksPanel.uninitPanel();
panel.hidePopup();
},
// nsINavBookmarkObserver
onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {
this._batching = true;
},
onEndUpdateBatch: function PSB_onEndUpdateBatch() {
this.updateState();
this._batching = false;
},
onItemAdded: function PSB_onItemAdded(aItemId, aFolder, aIndex) {
if (!this._batching && !this._starred)
this.updateState();
},
onItemRemoved: function PSB_onItemRemoved(aItemId, aFolder, aIndex) {
if (!this._batching)
this.updateState();
},
onItemChanged: function PSB_onItemChanged(aItemId, aProperty,
aIsAnnotationProperty, aValue) {
if (!this._batching && aProperty == "uri")
this.updateState();
},
onItemVisited: function() { },
onItemMoved: function() { }
};

View File

@ -1017,6 +1017,7 @@ function delayedStartup()
initBookmarksToolbar();
PlacesUtils.bookmarks.addObserver(gBookmarksObserver, false);
PlacesStarButton.init();
// called when we go into full screen, even if it is
// initiated by a web page script
@ -1157,6 +1158,7 @@ function BrowserShutdown()
}
PlacesUtils.bookmarks.removeObserver(gBookmarksObserver);
PlacesStarButton.uninit();
try {
gPrefService.removeObserver(gAutoHideTabbarPrefListener.domain,
@ -3519,6 +3521,9 @@ nsBrowserStatusHandler.prototype =
gURLBar.value = userTypedValue;
SetPageProxyState("invalid");
}
// Update starring UI
PlacesStarButton.updateState(aLocationURI);
}
}
UpdateBackForwardCommands(gBrowser.webNavigation);

View File

@ -53,6 +53,7 @@
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
# All DTD information is stored in a separate file so that it can be shared by
# hiddenWindow.xul.
@ -100,6 +101,11 @@
<panel type="autocomplete" chromedir="&locale.dir;" id="PopupAutoComplete" noautofocus="true"/>
<panel id="editBookmarkPanel"
position="after_end">
<vbox id="editBookmarkPanelContent" flex="1"/>
</panel>
<popup id="toolbar-context-menu"
onpopupshowing="onViewToolbarsPopupShowing(event);">
<menuseparator/>
@ -249,6 +255,7 @@
level="safe"
onclick="goDoCommand('safebrowsing-show-warning')" />
#endif
<image id="star-icon" onclick="if (event.button == 0) PlacesStarButton.onClick(event);"/>
</hbox>
</textbox>
<stack id="go-button-stack">

View File

@ -0,0 +1,581 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 the Places Bookmark Properties dialog.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* 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
* 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 ***** */
const LAST_USED_ANNO = "bookmarkPropertiesDialog/lastUsed";
const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
var gAddBookmarksPanel = {
/**
* The Microsummary Service for displaying microsummaries.
*/
__mss: null,
get _mss() {
if (!this.__mss)
this.__mss = Cc["@mozilla.org/microsummary/service;1"].
getService(Ci.nsIMicrosummaryService);
return this.__mss;
},
_uri: null,
_itemId: -1,
_itemType: -1,
_microsummaries: null,
_doneCallback: null,
_currentTags: [],
_hiddenRows: [],
/**
* Determines the initial data for the item edited or added by this dialog
*/
_determineInfo: function ABP__determineInfo(aInfo) {
const bms = PlacesUtils.bookmarks;
this._itemType = bms.getItemType(this._itemId);
if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK)
this._currentTags = PlacesUtils.tagging.getTagsForURI(this._uri);
else
this._currentTags.splice(0);
// hidden rows
if (aInfo && aInfo.hiddenRows)
this._hiddenRows = aInfo.hiddenRows;
else
this._hiddenRows.splice(0);
},
_showHideRows: function EBP__showHideRows() {
this._element("nameRow").hidden = this._hiddenRows.indexOf("name") != -1;
this._element("folderRow").hidden =
this._hiddenRows.indexOf("folderPicker") != -1;
this._element("tagsRow").hidden = this._hiddenRows.indexOf("tags") != -1 ||
this._itemType != Ci.nsINavBookmarksService.TYPE_BOOKMARK;
this._element("descriptionRow").hidden =
this._hiddenRows.indexOf("description") != -1;
},
/**
* Initialize the panel
*/
initPanel: function ABP_initPanel(aItemId, aTm, aDoneCallback, aInfo) {
this._folderMenuList = this._element("folderMenuList");
this._folderTree = this._element("folderTree");
this._tm = aTm;
this._itemId = aItemId;
this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
this._doneCallback = aDoneCallback;
this._determineInfo(aInfo);
// folder picker
this._initFolderMenuList();
// name picker
this._initNamePicker();
// tags field
this._element("tagsField").value = this._currentTags.join(", ");
// description field
this._element("descriptionField").value =
PlacesUtils.getItemDescription(this._itemId);
this._showHideRows();
},
/**
* Appends a menu-item representing a bookmarks folder to a menu-popup.
* @param aMenupopup
* The popup to which the menu-item should be added.
* @param aFolderId
* The identifier of the bookmarks folder.
* @return the new menu item.
*/
_appendFolderItemToMenupopup:
function BPP__appendFolderItemToMenuList(aMenupopup, aFolderId) {
// First make sure the folders-separator is visible
this._element("foldersSeparator").hidden = false;
var folderMenuItem = document.createElement("menuitem");
var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
folderMenuItem.folderId = aFolderId;
folderMenuItem.setAttribute("label", folderTitle);
folderMenuItem.className = "menuitem-iconic folder-icon";
aMenupopup.appendChild(folderMenuItem);
return folderMenuItem;
},
_initFolderMenuList: function BPP__initFolderMenuList() {
// clean up first
var menupopup = this._folderMenuList.menupopup;
while (menupopup.childNodes.length > 4)
menupopup.removeChild(menupopup.lastChild);
var container = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
// only show "All Bookmarks" if the url isn't bookmarked somewhere else
this._element("placesRootItem").hidden = container != PlacesUtils.placesRootId;
// List of recently used folders:
var annos = PlacesUtils.annotations;
var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO, { });
/**
* The value of the LAST_USED_ANNO annotation is the time (in the form of
* Date.getTime) at which the folder has been last used.
*
* First we build the annotated folders array, each item has both the
* folder identifier and the time at which it was last-used by this dialog
* set. Then we sort it descendingly based on the time field.
*/
var folders = [];
for (var i=0; i < folderIds.length; i++) {
var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
folders.push({ folderId: folderIds[i], lastUsed: lastUsed });
}
folders.sort(function(a, b) {
if (b.lastUsed < a.lastUsed)
return -1;
if (b.lastUsed > a.lastUsed)
return 1;
return 0;
});
var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST, folders.length);
for (i=0; i < numberOfItems; i++) {
this._appendFolderItemToMenupopup(menupopup, folders[i].folderId);
}
var defaultItem = this._getFolderMenuItem(container, true);
this._folderMenuList.selectedItem = defaultItem;
// Hide the folders-separator if no folder is annotated as recently-used
this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 4);
},
QueryInterface: function BPP_QueryInterface(aIID) {
if (aIID.equals(Ci.nsIMicrosummaryObserver) ||
aIID.equals(Ci.nsIDOMEventListener) ||
aIID.eqauls(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
_element: function BPP__element(aID) {
return document.getElementById("editBMPanel_" + aID);
},
_createMicrosummaryMenuItem:
function BPP__createMicrosummaryMenuItem(aMicrosummary) {
var menuItem = document.createElement("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 or load its content.
menuItem.microsummary = aMicrosummary;
// 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();
}
return menuItem;
},
_initNamePicker: function ABP_initNamePicker() {
var userEnteredNameField = this._element("userEnteredName");
var namePicker = this._element("namePicker");
var droppable = false;
userEnteredNameField.label =
PlacesUtils.bookmarks.getItemTitle(this._itemId);
// clean up old entries
var menupopup = namePicker.menupopup;
while (menupopup.childNodes.length > 2)
menupopup.removeChild(menupopup.lastChild);
var itemToSelect = userEnteredNameField;
try {
this._microsummaries = this._mss.getMicrosummaries(this._uri, -1);
}
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
droppable = true;
while (enumerator.hasMoreElements()) {
var microsummary = enumerator.getNext()
.QueryInterface(Ci.nsIMicrosummary);
var menuItem = this._createMicrosummaryMenuItem(microsummary);
menupopup.appendChild(menuItem);
}
}
this._microsummaries.addObserver(this);
}
if (namePicker.selectedItem == itemToSelect)
namePicker.value = itemToSelect.label;
else
namePicker.selectedItem = itemToSelect;
namePicker.setAttribute("droppable", droppable);
},
// nsIMicrosummaryObserver
onContentLoaded: function ABP_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-menulists to set the DOMAttrModified handler
// as well.
//
// Also note the order importance: if the label of the menu-item is
// set to 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;
}
}
},
onElementAppended: function BPP_onElementAppended(aMicrosummary) {
var namePicker = this._element("namePicker");
namePicker.menupopup
.appendChild(this._createMicrosummaryMenuItem(aMicrosummary));
// Make sure the drop-marker is shown
namePicker.setAttribute("droppable", "true");
},
uninitPanel: function ABP_uninitPanel() {
if (this._microsummaries)
this._microsummaries.removeObserver(this);
// hide the folder tree if it was previously visible
if (!this._folderTree.collapsed)
this.toggleFolderTreeVisibility();
// hide the tag selector if it was previously visible
var tagsSelector = this._element("tagsSelector");
if (!tagsSelector.collapsed)
tagsSelector.collapsed = true;
},
saveItem: function ABP_saveItem() {
var container = this._getFolderIdFromMenuList();
const bms = PlacesUtils.bookmarks;
const ptm = PlacesUtils.ptm;
var txns = [];
// container
if (bms.getFolderIdForItem(this._itemId) != container)
txns.push(ptm.moveItem(this._itemId, container, -1));
// title
var newTitle = this._element("userEnteredName").label;
if (bms.getItemTitle(this._itemId) != newTitle)
txns.push(ptm.editItemTitle(this._itemId, newTitle));
// description
var newDescription = this._element("descriptionField").value;
if (newDescription != PlacesUtils.getItemDescription(this._itemId))
txns.push(ptm.editItemDescription(this._itemId, newDescription));
// Tags, NOT YET UNDOABLE
var tags = this._getTagsArrayFromTagField();
if (tags.length > 0 || this._currentTags.length > 0) {
var tagsToRemove = [];
var tagsToAdd = [];
var t;
for each (t in this._currentTags) {
if (tags.indexOf(t) == -1)
tagsToRemove.push(t);
}
for each (t in tags) {
if (this._currentTags.indexOf(t) == -1)
tagsToAdd.push(t);
}
if (tagsToAdd.length > 0)
PlacesUtils.tagging.tagURI(this._uri, tagsToAdd);
if (tagsToRemove.length > 0)
PlacesUtils.tagging.untagURI(this._uri, tagsToRemove);
}
if (txns.length > 0) {
// Mark the containing folder as recently-used if it isn't the
// "All Bookmarks" root
if (container != PlacesUtils.placesRootId)
this._markFolderAsRecentlyUsed(container);
}
if (txns.length > 0)
ptm.commitTransaction(ptm.aggregateTransactions("Edit Item", txns));
},
onNamePickerInput: function ABP_onNamePickerInput() {
this._element("userEnteredName").label = this._element("namePicker").value;
},
toggleFolderTreeVisibility: function ABP_toggleFolderTreeVisibility() {
var expander = this._element("foldersExpander");
if (!this._folderTree.collapsed) {
expander.className = "expander-down";
expander.setAttribute("tooltiptext",
expander.getAttribute("tooltiptextdown"));
this._folderTree.collapsed = true;
}
else {
expander.className = "expander-up"
expander.setAttribute("tooltiptext",
expander.getAttribute("tooltiptextup"));
if (!this._folderTree.treeBoxObject.view.isContainerOpen(0))
this._folderTree.treeBoxObject.view.toggleOpenState(0);
this._folderTree.selectFolders([this._getFolderIdFromMenuList()]);
this._folderTree.collapsed = false;
this._folderTree.focus();
}
},
_getFolderIdFromMenuList:
function BPP__getFolderIdFromMenuList() {
var selectedItem = this._folderMenuList.selectedItem
switch (selectedItem.id) {
case "editBMPanel_placesRootItem":
return PlacesUtils.placesRootId;
case "editBMPanel_bmRootItem":
return PlacesUtils.bookmarksRootId;
case "editBMPanel_toolbarFolderItem":
return PlacesUtils.toolbarFolderId;
}
NS_ASSERT("folderId" in selectedItem,
"Invalid menuitem in the folders-menulist");
return selectedItem.folderId;
},
/**
* Get the corresponding menu-item in the folder-menu-list for a bookmarks
* folder if such an item exists. Otherwise, this creates a menu-item for the
* folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
* the new item replaces the last menu-item.
* @param aFolderId
* The identifier of the bookmarks folder
* @param aCheckStaticFolderItems
* whether or not to also treat the static items at the top of
* menu-list. Note dynamic items get precedence even if this is set to
* true.
*/
_getFolderMenuItem:
function BPP__getFolderMenuItem(aFolderId, aCheckStaticFolderItems) {
var menupopup = this._folderMenuList.menupopup;
// 0: All Bookmarks, 1: Bookmarks root, 2: toolbar folder, 3: separator
for (var i=4; i < menupopup.childNodes.length; i++) {
if (menupopup.childNodes[i].folderId == aFolderId)
return menupopup.childNodes[i];
}
if (aCheckStaticFolderItems) {
if (aFolderId == PlacesUtils.placesRootId)
return this._element("placesRootItem");
if (aFolderId == PlacesUtils.bookmarksRootId)
return this._element("bmRootItem")
if (aFolderId == PlacesUtils.toolbarFolderId)
return this._element("toolbarFolderItem")
}
// 3 special folders + separator + folder-items-count limit
if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
menupopup.removeChild(menupopup.lastChild);
return this._appendFolderItemToMenupopup(menupopup, aFolderId);
},
onMenuListFolderSelect: function BPP_onMenuListFolderSelect(aEvent) {
if (this._folderTree.hidden)
return;
this._folderTree.selectFolders([this._getFolderIdFromMenuList()]);
},
onFolderTreeSelect: function BPP_onFolderTreeSelect() {
var selectedNode = this._folderTree.selectedNode;
if (!selectedNode)
return;
var folderId = selectedNode.itemId;
// Don't set the selected item if the static item for the folder is
// already selected
var oldSelectedItem = this._folderMenuList.selectedItem;
if ((oldSelectedItem.id == "editBMPanel_toolbarFolderItem" &&
folderId == PlacesUtils.bookmarks.toolbarFolder) ||
(oldSelectedItem.id == "editBMPanel_bmRootItem" &&
folderId == PlacesUtils.bookmarks.bookmarksRoot))
return;
var folderItem = this._getFolderMenuItem(folderId, false);
this._folderMenuList.selectedItem = folderItem;
},
_markFolderAsRecentlyUsed:
function ABP__markFolderAsRecentlyUsed(aFolderId) {
// We'll figure out when/if to expire the annotation if it turns out
// we keep this recently-used-folders implementation
PlacesUtils.annotations
.setItemAnnotation(aFolderId, LAST_USED_ANNO,
new Date().getTime(), 0,
Ci.nsIAnnotationService.EXPIRE_NEVER);
},
accept: function ABP_accept() {
this.saveItem();
if (typeof(this._doneCallback) == "function")
this._doneCallback();
},
deleteAndClose: function ABP_deleteAndClose() {
// remove the item
if (this._itemId != -1)
PlacesUtils.bookmarks.removeItem(this._itemId);
// remove all tags for the associated url
PlacesUtils.tagging.untagURI(this._uri, null);
if (typeof(this._doneCallback) == "function")
this._doneCallback();
},
toggleTagsSelector: function ABP_toggleTagsSelector() {
var tagsSelector = this._element("tagsSelector");
var expander = this._element("tagsSelectorExpander");
if (tagsSelector.collapsed) {
expander.className = "expander-down";
expander.setAttribute("tooltiptext",
expander.getAttribute("tooltiptextdown"));
// rebuild the tag list
while (tagsSelector.hasChildNodes())
tagsSelector.removeChild(tagsSelector.lastChild);
var tagsInField = this._getTagsArrayFromTagField();
var allTags = PlacesUtils.tagging.allTags;
for each (var tag in allTags) {
var elt = document.createElement("listitem");
elt.setAttribute("type", "checkbox");
elt.setAttribute("label", tag);
if (tagsInField.indexOf(tag) != -1)
elt.setAttribute("checked", "true");
tagsSelector.appendChild(elt);
}
// This is a no-op if we've added the listener.
tagsSelector.addEventListener("CheckboxStateChange", this, false);
}
else {
expander.className = "expander-down";
expander.setAttribute("tooltiptext",
expander.getAttribute("tooltiptextdown"));
}
tagsSelector.collapsed = !tagsSelector.collapsed;
},
_getTagsArrayFromTagField: function() {
// we don't require the leading space (after each comma)
var tags = this._element("tagsField").value.split(",");
for (var i=0; i < tags.length; i++) {
// remove trailing and leading spaces
tags[i] = tags[i].replace(/^\s+/, "").replace(/\s+$/, "");
// remove empty entries from the array.
if (tags[i] == "") {
tags.splice(i, 1);
i--;
}
}
return tags;
},
// nsIDOMEventListener
handleEvent: function ABP_nsIDOMEventListener(aEvent) {
if (aEvent.type == "CheckboxStateChange") {
// Update the tags field when items are checked/unchecked in the listbox
var tags = this._getTagsArrayFromTagField();
if (aEvent.target.checked)
tags.push(aEvent.target.label);
else {
var indexOfItem = tags.indexOf(aEvent.target.label);
if (indexOfItem != -1)
tags.splice(indexOfItem, 1);
}
this._element("tagsField").value = tags.join(", ");
}
}
};

View File

@ -0,0 +1,150 @@
# ***** 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 the Places Edit Bookmarks Panel code.
#
# The Initial Developer of the Original Code is
# Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Asaf Romano <mano@mozilla.com> (Original Author)
#
# 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 *****
<!DOCTYPE overlay [
<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
%placesDTD;
]>
<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
<overlay id="editBookmarkOverlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://browser/content/places/editBookmarkOverlay.js"/>
<vbox id="editBookmarkPanelContent">
<grid id="editBookmarkPanelGrid" flex="1">
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row align="center" id="editBMPanel_nameRow">
<label value="&editBookmarkOverlay.name.label;"
contorl="editBMPanel_namePicker"/>
<menulist id="editBMPanel_namePicker"
flex="1"
editable="true"
droppable="false"
oninput="gAddBookmarksPanel.onNamePickerInput();">
<menupopup>
<menuitem id="editBMPanel_userEnteredName"/>
<menuitem disabled="true">
<menuseparator flex="1"/>
<label value="&editBookmarkOverlay.liveTitlesSeparator.label;"/>
</menuitem>
</menupopup>
</menulist>
</row>
<row align="center" id="editBMPanel_folderRow">
<label value="&editBookmarkOverlay.folder.label;"
control="editBMPanel_folderMenuList"/>
<menulist id="editBMPanel_folderMenuList"
class="folder-icon"
oncommand="gAddBookmarksPanel.onMenuListFolderSelect();">
<menupopup>
<!-- Static item for special folders -->
<menuitem id="editBMPanel_placesRootItem"
label="&editBookmarkOverlay.allBookmarksFolderItem.label;"
class="menuitem-iconic folder-icon"/>
<menuitem id="editBMPanel_bmRootItem"
label="&editBookmarkOverlay.bookmarksMenuFolderItem.label;"
class="menuitem-iconic folder-icon"/>
<menuitem id="editBMPanel_toolbarFolderItem"
label="&editBookmarkOverlay.toolbarFolderItem.label;"
class="menuitem-iconic folder-icon"/>
<menuseparator id="editBMPanel_foldersSeparator" hidden="true"/>
</menupopup>
</menulist>
<button id="editBMPanel_foldersExpander"
class="expander-down"
tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
oncommand="gAddBookmarksPanel.toggleFolderTreeVisibility();"/>
</row>
<tree id="editBMPanel_folderTree"
class="placesTree"
type="places"
height="150"
collapsed="true"
onselect="gAddBookmarksPanel.onFolderTreeSelect();"
showRoot="true"
place="place:folder=2&amp;group=3&amp;excludeItems=1&amp;excludeQueries=1&amp;excludeReadOnlyFolders=1"
hidecolumnpicker="true">
<treecols>
<treecol anonid="title" flex="1" primary="true" hideheader="true"/>
</treecols>
<treechildren flex="1"/>
</tree>
<row align="center" id="editBMPanel_tagsRow">
<label value="&editBookmarkOverlay.tags.label;"
control="tagsField"/>
<textbox id="editBMPanel_tagsField"/>
<button id="editBMPanel_tagsSelectorExpander"
class="expander-down"
tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
oncommand="gAddBookmarksPanel.toggleTagsSelector();"/>
</row>
<!-- XXX: Temporary workaround -->
</rows></grid>
<listbox id="editBMPanel_tagsSelector" height="150" collapsed="true"/>
<grid flex="1"><columns><column/><column flex="1"/></columns><rows>
<row id="editBMPanel_descriptionRow" align="center">
<label value="&editBookmarkOverlay.description.label;"
control="editBMPanel_descriptionField"/>
<textbox id="editBMPanel_descriptionField"/>
</row>
</rows>
</grid>
<hbox>
<spacer flex="1"/>
<button label="&editBookmarkOverlay.delete.label;"
oncommand="gAddBookmarksPanel.deleteAndClose();"/>
<button label="&editBookmarkOverlay.ok.label;"
default="true"
oncommand="gAddBookmarksPanel.accept();"/>
</hbox>
</vbox>
</overlay>

View File

@ -427,7 +427,8 @@ PlacesTreeView.prototype = {
COLUMN_TYPE_LASTMODIFIED: 8,
_getColumnType: function PTV__getColumnType(aColumn) {
switch (aColumn.id) {
var columnType = aColumn.id || aColumn.element.getAttribute("anonid");
switch (columnType) {
case "title":
return this.COLUMN_TYPE_TITLE;
case "url":
@ -876,7 +877,8 @@ PlacesTreeView.prototype = {
},
getCellProperties: function PTV_getCellProperties(aRow, aColumn, aProperties) {
if (aColumn.id != "title")
var columnType = aColumn.id || aColumn.element.getAttribute("anonid") ;
if (columnType != "title")
return;
this._ensureValidRow(aRow);

View File

@ -159,6 +159,16 @@ var PlacesUtils = {
return this._microsummaries;
},
/**
* The Places Tagging Service
*/
get tagging() {
if (!this._tagging)
this._tagging = Cc["@mozilla.org/browser/tagging-service;1"].
getService(Ci.nsITaggingService);
return this._tagging;
},
_RDF: null,
get RDF() {
if (!this._RDF)
@ -1349,15 +1359,16 @@ var PlacesUtils = {
setAnnotationsForURI: function PU_setAnnotationsForURI(aURI, aAnnos) {
var annosvc = this.annotations;
aAnnos.forEach(function(anno) {
var flags = ("flags" in anno) ? anno.flags : 0;
var expires = ("expires" in anno) ?
anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
if (anno.type == annosvc.TYPE_BINARY) {
annosvc.setPageAnnotationBinary(aURI, anno.name, anno.value,
anno.value.length, anno.mimeType,
anno.flags, anno.expires);
}
else {
annosvc.setPageAnnotation(aURI, anno.name, anno.value,
anno.flags, anno.expires);
flags, expires);
}
else
annosvc.setPageAnnotation(aURI, anno.name, anno.value, flags, expires);
});
},
@ -1373,14 +1384,17 @@ var PlacesUtils = {
setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos) {
var annosvc = this.annotations;
aAnnos.forEach(function(anno) {
var flags = ("flags" in anno) ? anno.flags : 0;
var expires = ("expires" in anno) ?
anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
if (anno.type == annosvc.TYPE_BINARY) {
annosvc.setItemAnnotationBinary(aItemId, anno.name, anno.value,
anno.value.length, anno.mimeType,
anno.flags, anno.expires);
flags, expires);
}
else {
annosvc.setItemAnnotation(aItemId, anno.name, anno.value,
anno.flags, anno.expires);
annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags,
expires);
}
});
},
@ -1439,6 +1453,13 @@ var PlacesUtils = {
return this._toolbarFolderId;
},
get tagRootId() {
if (!("_tagRootId" in this))
this._tagRootId = this.bookmarks.tagRoot;
return this._tagRootId;
},
/**
* Set the POST data associated with a URI, if any.
* Used by POST keywords.
@ -1466,6 +1487,19 @@ var PlacesUtils = {
return null;
},
/**
* Retrieve the description of an item
* @param aItemId
* item identifier
* @returns the description of the given item, or an empty string if it is
* not set.
*/
getItemDescription: function PU_getItemDescription(aItemId) {
if (this.annotations.itemHasAnnotation(aItemId, DESCRIPTION_ANNO))
return this.annotations.getItemAnnotation(aItemId, DESCRIPTION_ANNO);
return "";
},
/**
* Converts a JavaScript object into a JSON string
* (see http://www.json.org/ for the full grammar).

View File

@ -31,3 +31,5 @@ browser.jar:
* content/browser/bookmarks/sidebarUtils.js (content/sidebarUtils.js)
* content/browser/places/moveBookmarks.xul (content/moveBookmarks.xul)
* content/browser/places/moveBookmarks.js (content/moveBookmarks.js)
* content/browser/places/editBookmarkOverlay.xul (content/editBookmarkOverlay.xul)
* content/browser/places/editBookmarkOverlay.js (content/editBookmarkOverlay.js)

View File

@ -0,0 +1,13 @@
<!ENTITY editBookmarkOverlay.name.label "Name:">
<!ENTITY editBookmarkOverlay.liveTitlesSeparator.label "Live Titles">
<!ENTITY editBookmarkOverlay.folder.label "Folder:">
<!ENTITY editBookmarkOverlay.allBookmarksFolderItem.label "All Bookmarks">
<!ENTITY editBookmarkOverlay.bookmarksMenuFolderItem.label "Bookmarks Menu">
<!ENTITY editBookmarkOverlay.toolbarFolderItem.label "Bookmarks Toolbar">
<!ENTITY editBookmarkOverlay.foldersExpanderDown.tooltip "Show all the bookmarks folders">
<!ENTITY editBookmarkOverlay.expanderUp.tooltip "Hide">
<!ENTITY editBookmarkOverlay.tags.label "Tags">
<!ENTITY editBookmarkOverlay.description.label "Description:">
<!ENTITY editBookmarkOverlay.tagsExpanderDown.tooltip "Show all tags">
<!ENTITY editBookmarkOverlay.ok.label "OK">
<!ENTITY editBookmarkOverlay.delete.label "Delete">

View File

@ -27,6 +27,7 @@
locale/browser/sessionstore.properties (%chrome/browser/sessionstore.properties)
locale/browser/places/places.dtd (%chrome/browser/places/places.dtd)
locale/browser/places/places.properties (%chrome/browser/places/places.properties)
locale/browser/places/editBookmarkOverlay.dtd (%chrome/browser/places/editBookmarkOverlay.dtd)
locale/browser/places/bookmarkProperties.dtd (%chrome/browser/places/bookmarkProperties.dtd)
locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
locale/browser/preferences/selectBookmark.dtd (%chrome/browser/preferences/selectBookmark.dtd)

View File

@ -1269,3 +1269,12 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
-moz-border-bottom-colors: -moz-mac-menushadow -moz-mac-menushadow ThreeDShadow !important;
-moz-border-left-colors: ThreeDLightShadow ThreeDHighlight !important;
}
/* star icon */
#star-icon {
list-style-image: url("chrome://browser/skin/places/starPage.png");
}
#star-icon[starred="true"] {
list-style-image: url("chrome://browser/skin/places/pageStarred.png");
}

View File

@ -44,6 +44,9 @@ classic.jar:
skin/classic/browser/places/livemarkFolder.png (places/livemarkFolder.png)
skin/classic/browser/places/livemarkFolderHover.png (places/livemarkFolderHover.png)
skin/classic/browser/places/bookmarkProperties.css (places/bookmarkProperties.css)
skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
skin/classic/browser/places/starPage.png (places/starPage.png)
skin/classic/browser/places/pageStarred.png (places/pageStarred.png)
skin/classic/browser/places/organizer-toolbar.png (bookmarks/Bookmarks-toolbar.png)
skin/classic/browser/places/expander-closed-active.png (bookmarks/expander-closed-active.png)
skin/classic/browser/places/expander-closed.png (bookmarks/expander-closed.png)

View File

@ -0,0 +1,143 @@
/* ***** 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>
* 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
* 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 menulist ****/
.folder-icon > .menulist-label-box > .menulist-icon,
.folder-icon > .menu-iconic-left > .menu-iconic-icon {
width: 16px;
height: 16px;
}
.folder-icon > .menu-iconic-left {
display: -moz-box;
}
.folder-icon {
list-style-image: url("chrome://global/skin/tree/folder.png") !important;
}
.menulist-icon {
margin: 0 !important;
}
/**** folder tree ****/
#editBMPanel_folderTree {
margin: 6px 0;
}
/**** expanders ****/
.expander-up,
.expander-down {
-moz-appearance: none;
margin-left: 8px;
padding: 0;
min-width: 0;
}
.expander-up {
list-style-image: url("chrome://browser/skin/places/expander-open.png") !important;
}
.expander-down {
list-style-image: url("chrome://browser/skin/places/expander-closed.png") !important;
}
.expander-down:hover:active {
list-style-image: url("chrome://browser/skin/places/expander-closed-active.png") !important;
}
.expander-up:hover:active {
list-style-image: url("chrome://browser/skin/places/expander-open-active.png") !important;
}
/**** name picker ****/
/* Make the microsummary picker look like a regular textbox instead of
* an editable menulist when no microsummaries are available.
*/
#editBMPanel_namePicker[droppable="false"] {
-moz-appearance: none;
margin: 0px;
border: none;
padding: 0px;
height: auto !important;
}
#editBMPanel_namePicker[droppable="false"] > .menulist-dropmarker {
display: none;
}
#editBMPanel_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;
}
#editBMPanel_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. */
#editBMPanel_namePicker[droppable="false"] > .menulist-dropmarker {
display: none;
}
#editBMPanel_namePicker[droppable="false"] > menupopup {
display: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1439,3 +1439,12 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
.bookmark-item[dragover-bottom="true"] {
-moz-border-bottom-colors: #000000;
}
/* star icon */
#star-icon {
list-style-image: url("chrome://browser/skin/places/starPage.png");
}
#star-icon[starred="true"] {
list-style-image: url("chrome://browser/skin/places/pageStarred.png");
}

View File

@ -50,6 +50,9 @@ classic.jar:
skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
skin/classic/browser/places/folderDragOver.png (places/folderDragOver.png)
skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
skin/classic/browser/places/starPage.png (places/starPage.png)
skin/classic/browser/places/pageStarred.png (places/pageStarred.png)
skin/classic/browser/places/bookmarkProperties.css (places/bookmarkProperties.css)
skin/classic/browser/places/organizer-toolbar.png (bookmarks/Bookmarks-toolbar.png)
#ifdef MOZ_SAFE_BROWSING

View File

@ -0,0 +1,110 @@
/* ***** 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>
* 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
* 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 menulist ****/
.folder-icon > .menulist-label-box > .menulist-icon {
width: 16px;
height: 16px;
}
.folder-icon > .menu-iconic-left {
display: -moz-box;
}
.folder-icon {
list-style-image: url("chrome://global/skin/icons/folder-item.png") !important;
-moz-image-region: rect(0px, 32px, 16px, 16px) !important;
}
/**** expanders ****/
.expander-up,
.expander-down {
min-width: 0;
}
.expander-up {
list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
}
.expander-down {
list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
}
.expander-down:hover:active {
list-style-image: url("chrome://global/skin/arrow/arrow-dn-hov.gif");
}
.expander-up:hover:active {
list-style-image: url("chrome://global/skin/arrow/arrow-up-hov.gif");
}
/**** name picker ****/
/* Make the microsummary picker look like a regular textbox instead of
* an editable menulist when no microsummaries are available.
*/
#editBMPanel_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;
}
#editBMPanel_namePicker[droppable="false"] > .menulist-dropmarker {
/* Normal editable menulists set this to "menulist-textfield". */
-moz-appearance: none;
padding: 2px 2px 3px 4px;
}
/* Hide the drop marker and the popup. */
#editBMPanel_namePicker[droppable="false"] > .menulist-dropmarker,
#editBMPanel_namePicker[droppable="false"] > menupopup {
display: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -15,12 +15,12 @@
* The Original Code is Places Tagging Service code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Asaf Romano <mano@mozilla.com>
* Asaf Romano <mano@mozilla.com> (Original Author)
*
* 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
@ -41,7 +41,7 @@
interface nsIURI;
interface nsIVariant;
[scriptable, uuid(962bf94f-e719-4cf6-8967-74ff5d2020df)]
[scriptable, uuid(03dd79c2-c834-4747-a204-79d5a29c6bd9)]
interface nsITaggingService : nsISupports
{
/**
@ -54,9 +54,7 @@ interface nsITaggingService : nsISupports
* @param aTags
* Array of tags to set for the given URL.
*/
void tagURI(in nsIURI aURI,
[const, array, size_is(aCount)] in wstring aTags,
in unsigned long aCount);
void tagURI(in nsIURI aURI, in nsIVariant aTags);
/**
* Removes tags from a URL. Tags from aTags which are not set for the
@ -65,11 +63,10 @@ interface nsITaggingService : nsISupports
* @param aURI
* the URL to un-tag.
* @param aTags
* Array of tags to unset.
* Array of tags to unset. pass null to remove all tags from the given
* url.
*/
void untagURI(in nsIURI aURI,
[const, array, size_is(aCount)] in wstring aTags,
in unsigned long aCount);
void untagURI(in nsIURI aURI, in nsIVariant aTags);
/**
* Retrieves all URLs tagged with the given tag.
@ -88,4 +85,9 @@ interface nsITaggingService : nsISupports
* @returns array of tags (sorted by name).
*/
nsIVariant getTagsForURI(in nsIURI aURI);
/**
* Retrieves all tags used to tag URIs in the data-base (sorted by name).
*/
readonly attribute nsIVariant allTags;
};

View File

@ -12,7 +12,7 @@
* the License for the specific language governing rights and
* limitations under the License.
*
* The Original Code is the Places Tagging Service
* The Original Code is the Places Tagging Service.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation.
@ -20,7 +20,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Asaf Romano <mano@mozilla.com>
* Asaf Romano <mano@mozilla.com> (Original Author)
*
* 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
@ -40,16 +40,16 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const TAGS_CLASSID = Components.ID("{6a059068-1630-11dc-8314-0800200c9a66}");
const TAGS_CLASSNAME = "Places Tagging Service";
const TAGS_CONTRACTID = "@mozilla.org/browser/tagging-service;1";
const TAG_CONTAINER_ICON_URI = "chrome://mozapps/skin/places/tagContainerIcon.png"
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const NH_CONTRACTID = "@mozilla.org/browser/nav-history-service;1";
const BMS_CONTRACTID = "@mozilla.org/browser/nav-bookmarks-service;1";
const IO_CONTRACTID = "@mozilla.org/network/io-service;1";
const ANNO_CONTRACTID = "@mozilla.org/browser/annotation-service;1";
const FAV_CONTRACTID = "@mozilla.org/browser/favicon-service;1";
const OBSS_CONTRACTID = "@mozilla.org/observer-service;1";
const TAG_CONTAINER_ICON_URI = "chrome://mozapps/skin/places/tagContainerIcon.png"
var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
@ -57,20 +57,6 @@ var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
* The Places Tagging Service
*/
function TaggingService() {
this._tags = [];
var options = this._history.getNewQueryOptions();
var query = this._history.getNewQuery();
query.setFolders([this._bms.tagRoot], 1);
var result = this._history.executeQuery(query, options);
var rootNode = result.root;
rootNode.containerOpen = true;
var cc = rootNode.childCount;
for (var i=0; i < cc; i++) {
var child = rootNode.getChild(i);
this._tags.push({ itemId: child.itemId, name: child.title });
}
}
TaggingService.prototype = {
@ -86,24 +72,49 @@ TaggingService.prototype = {
return this.__history;
},
// nsISupports
QueryInterface: function TS_QueryInterface(iid) {
if (iid.equals(Ci.nsITaggingService) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NO_INTERFACE;
get _annos() {
if (!this.__annos)
this.__annos = Cc[ANNO_CONTRACTID].getService(Ci.nsIAnnotationService);
return this.__annos;
},
get _tagsResult() {
if (!this.__tagsResult) {
var options = this._history.getNewQueryOptions();
var query = this._history.getNewQuery();
query.setFolders([this._bms.tagRoot], 1);
this.__tagsResult = this._history.executeQuery(query, options);
this.__tagsResult.root.containerOpen = true;
// we need to null out the result on shutdown
var observerSvc = Cc[OBSS_CONTRACTID].getService(Ci.nsIObserverService);
observerSvc.addObserver(this, "xpcom-shutdown", false);
}
return this.__tagsResult;
},
// Feed XPCOMUtils
classDescription: "Places Tagging Service",
contractID: "@mozilla.org/browser/tagging-service;1",
classID: Components.ID("{6a059068-1630-11dc-8314-0800200c9a66}"),
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsITaggingService,
Ci.nsIObserver]),
/**
* If there's no tag with the given name, -1 is returned.
* If there's no tag with the given name, null is returned;
*/
_getTagIndex: function TS__getTagIndex(aName) {
for (var i=0; i < this._tags.length; i++) {
if (this._tags[i].name == aName)
return i;
_getTagNode: function TS__getTagIndex(aName) {
var root = this._tagsResult.root;
var cc = root.childCount;
for (var i=0; i < cc; i++) {
var child = root.getChild(i);
if (child.title == aName)
return child;
}
return -1;
return null;
},
get _tagContainerIcon() {
@ -116,7 +127,7 @@ TaggingService.prototype = {
},
/**
* Creates a tag container under the tags-root with the given name
* Creates a tag container under the tags-root with the given name.
*
* @param aName
* the name for the new container.
@ -125,7 +136,6 @@ TaggingService.prototype = {
_createTag: function TS__createTag(aName) {
var id = this._bms.createFolder(this._bms.tagRoot, aName,
this._bms.DEFAULT_INDEX);
this._tags.push({ itemId: id, name: aName});
// Set the favicon
var faviconService = Cc[FAV_CONTRACTID].getService(Ci.nsIFaviconService);
@ -164,22 +174,22 @@ TaggingService.prototype = {
},
// nsITaggingService
tagURI: function TS_tagURI(aURI, aTags, aCount) {
if (!aURI)
tagURI: function TS_tagURI(aURI, aTags) {
if (!aURI || !aTags)
throw Cr.NS_ERROR_INVALID_ARG;
for (var i=0; i < aTags.length; i++) {
if (aTags[i].length == 0)
throw Cr.NS_ERROR_INVALID_ARG;
var tagIndex = this._getTagIndex(aTags[i]);
if (tagIndex == -1) {
var tagNode = this._getTagNode(aTags[i]);
if (!tagNode) {
var tagId = this._createTag(aTags[i]);
this._bms.insertBookmark(tagId, aURI, this._bms.DEFAULT_INDEX, "");
}
else {
var tagId = this._tags[tagIndex].itemId;
if (!this._isURITaggedInternal(aURI, tagId, {}))
var tagId = tagNode.itemId;
if (!this._isURITaggedInternal(aURI, tagNode.itemId, {}))
this._bms.insertBookmark(tagId, aURI, this._bms.DEFAULT_INDEX, "");
}
}
@ -188,38 +198,41 @@ TaggingService.prototype = {
/**
* Removes the tag container from the tags-root if the given tag is empty.
*
* @param aTagIndex
* the index of a tag element under the _tags array
* @param aTagId
* the item-id of the tag element under the tags root
*/
_removeTagAtIndexIfEmpty: function TS__removeTagAtIndexIfEmpty(aTagIndex) {
_removeTagIfEmpty: function TS__removeTagIfEmpty(aTagId) {
var options = this._history.getNewQueryOptions();
var query = this._history.getNewQuery();
query.setFolders([this._tags[aTagIndex].itemId], 1);
query.setFolders([aTagId], 1);
var result = this._history.executeQuery(query, options);
var rootNode = result.root;
rootNode.containerOpen = true;
if (rootNode.childCount == 0) {
this._bms.removeFolder(this._tags[aTagIndex].itemId);
this._tags.splice(aTagIndex, 1);
}
if (rootNode.childCount == 0)
this._bms.removeFolder(aTagId);
},
// nsITaggingService
untagURI: function TS_untagURI(aURI, aTags, aCount) {
untagURI: function TS_untagURI(aURI, aTags) {
if (!aURI)
throw Cr.NS_ERROR_INVALID_ARG;
if (!aTags) {
// see IDL.
// XXXmano: write a perf-sensitive version of this code path...
aTags = this.getTagsForURI(aURI);
}
for (var i=0; i < aTags.length; i++) {
if (aTags[i].length == 0)
throw Cr.NS_ERROR_INVALID_ARG;
var tagIndex = this._getTagIndex(aTags[i]);
if (tagIndex != -1) {
var tagNode = this._getTagNode(aTags[i]);
if (tagNode) {
var itemId = { };
if (this._isURITaggedInternal(aURI, this._tags[tagIndex].itemId,
itemId)) {
if (this._isURITaggedInternal(aURI, tagNode.itemId, itemId)) {
this._bms.removeItem(itemId.value);
this._removeTagAtIndexIfEmpty(tagIndex);
this._removeTagIfEmpty(tagNode.itemId);
}
}
}
@ -231,18 +244,14 @@ TaggingService.prototype = {
throw Cr.NS_ERROR_INVALID_ARG;
var uris = [];
var tagIndex = this._getTagIndex(aTag);
if (tagIndex != -1) {
var tagId = this._tags[tagIndex].itemId;
var options = this._history.getNewQueryOptions();
var query = this._history.getNewQuery();
query.setFolders([tagId], 1);
var result = this._history.executeQuery(query, options);
var rootNode = result.root;
rootNode.containerOpen = true;
var cc = rootNode.childCount;
var tagNode = this._getTagNode(aTag);
if (tagNode) {
tagNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
tagNode.containerOpen = true;
var cc = tagNode.childCount;
for (var i=0; i < cc; i++)
uris.push(gIoService.newURI(rootNode.getChild(i).uri, null, null));
uris.push(gIoService.newURI(tagNode.getChild(i).uri, null, null));
tagNode.containerOpen = false;
}
return uris;
},
@ -253,72 +262,49 @@ TaggingService.prototype = {
throw Cr.NS_ERROR_INVALID_ARG;
var tags = [];
var bookmarkIds = this._bms.getBookmarkIdsForURI(aURI, {});
var root = this._tagsResult.root;
var cc = root.childCount;
for (var i=0; i < bookmarkIds.length; i++) {
var parent = this._bms.getFolderIdForItem(bookmarkIds[i]);
for (var j=0; j < this._tags.length; j++) {
if (this._tags[j].itemId == parent)
tags.push(this._tags[j].name);
for (var j=0; j < cc; j++) {
var child = root.getChild(j);
if (child.itemId == parent)
tags.push(child.title);
}
}
// sort the tag list
tags.sort();
return tags;
}
};
var gModule = {
registerSelf: function(componentManager, fileSpec, location, type) {
componentManager = componentManager.QueryInterface(Ci.nsIComponentRegistrar);
for (var key in this._objects) {
var obj = this._objects[key];
componentManager.registerFactoryLocation(obj.CID,
obj.className,
obj.contractID,
fileSpec,
location,
type);
}
},
unregisterSelf: function(componentManager, fileSpec, location) {},
getClassObject: function(componentManager, cid, iid) {
if (!iid.equals(Components.interfaces.nsIFactory))
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
for (var key in this._objects) {
if (cid.equals(this._objects[key].CID))
return this._objects[key].factory;
}
throw Cr.NS_ERROR_NO_INTERFACE;
},
_objects: {
service: {
CID : TAGS_CLASSID,
contractID : TAGS_CONTRACTID,
className : TAGS_CLASSNAME,
factory : TaggingServiceFactory = {
createInstance: function(aOuter, aIID) {
if (aOuter != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
var svc = new TaggingService();
return svc.QueryInterface(aIID);
}
}
}
},
canUnload: function(componentManager) {
return true;
// nsITaggingService
get allTags() {
var tags = [];
var root = this._tagsResult.root;
var cc = root.childCount;
for (var j=0; j < cc; j++) {
var child = root.getChild(j);
tags.push(child.title);
}
// sort the tag list
tags.sort();
return tags;
},
// nsIObserver
observe: function TS_observe(subject, topic, data) {
if (topic == "xpcom-shutdown") {
this.__tagsResult.root.containerOpen = false;
this.__tagsResult = null;
var observerSvc = Cc[OBSS_CONTRACTID].getService(Ci.nsIObserverService);
observerSvc.removeObserver(this, "xpcom-shutdown");
}
}
};
function NSGetModule(compMgr, fileSpec) {
return gModule;
return XPCOMUtils.generateModule([TaggingService]);
}

View File

@ -16,12 +16,12 @@
* The Original Code is Places Tagging Service unit test code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Asaf Romano <mano@mozilla.com>
* Asaf Romano <mano@mozilla.com> (Original Author)
*
* 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
@ -78,8 +78,8 @@ function run_test() {
var uri2 = uri("https://bar.tld/");
// this also tests that the multiple folders are not created for the same tag
tagssvc.tagURI(uri1, ["tag 1"], 1);
tagssvc.tagURI(uri2, ["tag 1"], 1);
tagssvc.tagURI(uri1, ["tag 1"]);
tagssvc.tagURI(uri2, ["tag 1"]);
do_check_eq(tagRoot.childCount, 1);
var tag1node = tagRoot.getChild(0)
@ -89,12 +89,12 @@ function run_test() {
do_check_eq(tag1node.childCount, 2);
// Tagging the same url twice with the same tag should be a no-op
tagssvc.tagURI(uri1, ["tag 1"], 1);
tagssvc.tagURI(uri1, ["tag 1"]);
do_check_eq(tag1node.childCount, 2);
// the former should be ignored.
do_check_eq(tagRoot.childCount, 1);
tagssvc.tagURI(uri1, ["tag 1", "tag 2"], 2);
tagssvc.tagURI(uri1, ["tag 1", "tag 2"]);
do_check_eq(tagRoot.childCount, 2);
// test getTagsForURI
@ -112,10 +112,16 @@ function run_test() {
do_check_true(tag1uris[0].equals(uri1));
do_check_true(tag1uris[1].equals(uri2));
tagssvc.untagURI(uri1, ["tag 1"], 1);
// test allTags attribute
var allTags = tagssvc.allTags;
do_check_eq(allTags.length, 2);
do_check_eq(allTags[0], "tag 1");
do_check_eq(allTags[1], "tag 2");
tagssvc.untagURI(uri1, ["tag 1"]);
do_check_eq(tag1node.childCount, 1);
// removing the last uri from a tag should remove the tag-container
tagssvc.untagURI(uri2, ["tag 1"], 1);
tagssvc.untagURI(uri2, ["tag 1"]);
do_check_eq(tagRoot.childCount, 1);
}