michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0:
michael@0: this.EXPORTED_SYMBOLS = ["PlacesUIUtils"];
michael@0:
michael@0: var Ci = Components.interfaces;
michael@0: var Cc = Components.classes;
michael@0: var Cr = Components.results;
michael@0: var Cu = Components.utils;
michael@0:
michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0: Cu.import("resource://gre/modules/Services.jsm");
michael@0:
michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
michael@0: "resource://gre/modules/PluralForm.jsm");
michael@0:
michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
michael@0: "resource://gre/modules/PrivateBrowsingUtils.jsm");
michael@0:
michael@0: #ifdef MOZ_SERVICES_SYNC
michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Weave",
michael@0: "resource://services-sync/main.js");
michael@0: #endif
michael@0:
michael@0: XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
michael@0: Cu.import("resource://gre/modules/PlacesUtils.jsm");
michael@0: return PlacesUtils;
michael@0: });
michael@0:
michael@0: this.PlacesUIUtils = {
michael@0: ORGANIZER_LEFTPANE_VERSION: 7,
michael@0: ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
michael@0: ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
michael@0:
michael@0: LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
michael@0: DESCRIPTION_ANNO: "bookmarkProperties/description",
michael@0:
michael@0: TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab",
michael@0:
michael@0: /**
michael@0: * Makes a URI from a spec, and do fixup
michael@0: * @param aSpec
michael@0: * The string spec of the URI
michael@0: * @returns A URI object for the spec.
michael@0: */
michael@0: createFixedURI: function PUIU_createFixedURI(aSpec) {
michael@0: return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
michael@0: },
michael@0:
michael@0: getFormattedString: function PUIU_getFormattedString(key, params) {
michael@0: return bundle.formatStringFromName(key, params, params.length);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Get a localized plural string for the specified key name and numeric value
michael@0: * substituting parameters.
michael@0: *
michael@0: * @param aKey
michael@0: * String, key for looking up the localized string in the bundle
michael@0: * @param aNumber
michael@0: * Number based on which the final localized form is looked up
michael@0: * @param aParams
michael@0: * Array whose items will substitute #1, #2,... #n parameters
michael@0: * in the string.
michael@0: *
michael@0: * @see https://developer.mozilla.org/en/Localization_and_Plurals
michael@0: * @return The localized plural string.
michael@0: */
michael@0: getPluralString: function PUIU_getPluralString(aKey, aNumber, aParams) {
michael@0: let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey));
michael@0:
michael@0: // Replace #1 with aParams[0], #2 with aParams[1], and so on.
michael@0: return str.replace(/\#(\d+)/g, function (matchedId, matchedNumber) {
michael@0: let param = aParams[parseInt(matchedNumber, 10) - 1];
michael@0: return param !== undefined ? param : matchedId;
michael@0: });
michael@0: },
michael@0:
michael@0: getString: function PUIU_getString(key) {
michael@0: return bundle.GetStringFromName(key);
michael@0: },
michael@0:
michael@0: get _copyableAnnotations() [
michael@0: this.DESCRIPTION_ANNO,
michael@0: this.LOAD_IN_SIDEBAR_ANNO,
michael@0: PlacesUtils.POST_DATA_ANNO,
michael@0: PlacesUtils.READ_ONLY_ANNO,
michael@0: ],
michael@0:
michael@0: /**
michael@0: * Get a transaction for copying a uri item (either a bookmark or a history
michael@0: * entry) from one container to another.
michael@0: *
michael@0: * @param aData
michael@0: * JSON object of dropped or pasted item properties
michael@0: * @param aContainer
michael@0: * The container being copied into
michael@0: * @param aIndex
michael@0: * The index within the container the item is copied to
michael@0: * @return A nsITransaction object that performs the copy.
michael@0: *
michael@0: * @note Since a copy creates a completely new item, only some internal
michael@0: * annotations are synced from the old one.
michael@0: * @see this._copyableAnnotations for the list of copyable annotations.
michael@0: */
michael@0: _getURIItemCopyTransaction:
michael@0: function PUIU__getURIItemCopyTransaction(aData, aContainer, aIndex)
michael@0: {
michael@0: let transactions = [];
michael@0: if (aData.dateAdded) {
michael@0: transactions.push(
michael@0: new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
michael@0: );
michael@0: }
michael@0: if (aData.lastModified) {
michael@0: transactions.push(
michael@0: new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
michael@0: );
michael@0: }
michael@0:
michael@0: let keyword = aData.keyword || null;
michael@0: let annos = [];
michael@0: if (aData.annos) {
michael@0: annos = aData.annos.filter(function (aAnno) {
michael@0: return this._copyableAnnotations.indexOf(aAnno.name) != -1;
michael@0: }, this);
michael@0: }
michael@0:
michael@0: return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri),
michael@0: aContainer, aIndex, aData.title,
michael@0: keyword, annos, transactions);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Gets a transaction for copying (recursively nesting to include children)
michael@0: * a folder (or container) and its contents from one folder to another.
michael@0: *
michael@0: * @param aData
michael@0: * Unwrapped dropped folder data - Obj containing folder and children
michael@0: * @param aContainer
michael@0: * The container we are copying into
michael@0: * @param aIndex
michael@0: * The index in the destination container to insert the new items
michael@0: * @return A nsITransaction object that will perform the copy.
michael@0: *
michael@0: * @note Since a copy creates a completely new item, only some internal
michael@0: * annotations are synced from the old one.
michael@0: * @see this._copyableAnnotations for the list of copyable annotations.
michael@0: */
michael@0: _getFolderCopyTransaction:
michael@0: function PUIU__getFolderCopyTransaction(aData, aContainer, aIndex)
michael@0: {
michael@0: function getChildItemsTransactions(aChildren)
michael@0: {
michael@0: let transactions = [];
michael@0: let index = aIndex;
michael@0: aChildren.forEach(function (node, i) {
michael@0: // Make sure that items are given the correct index, this will be
michael@0: // passed by the transaction manager to the backend for the insertion.
michael@0: // Insertion behaves differently for DEFAULT_INDEX (append).
michael@0: if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) {
michael@0: index = i;
michael@0: }
michael@0:
michael@0: if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
michael@0: if (node.livemark && node.annos) {
michael@0: transactions.push(
michael@0: PlacesUIUtils._getLivemarkCopyTransaction(node, aContainer, index)
michael@0: );
michael@0: }
michael@0: else {
michael@0: transactions.push(
michael@0: PlacesUIUtils._getFolderCopyTransaction(node, aContainer, index)
michael@0: );
michael@0: }
michael@0: }
michael@0: else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
michael@0: transactions.push(new PlacesCreateSeparatorTransaction(-1, index));
michael@0: }
michael@0: else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) {
michael@0: transactions.push(
michael@0: PlacesUIUtils._getURIItemCopyTransaction(node, -1, index)
michael@0: );
michael@0: }
michael@0: else {
michael@0: throw new Error("Unexpected item under a bookmarks folder");
michael@0: }
michael@0: });
michael@0: return transactions;
michael@0: }
michael@0:
michael@0: if (aContainer == PlacesUtils.tagsFolderId) { // Copying a tag folder.
michael@0: let transactions = [];
michael@0: if (aData.children) {
michael@0: aData.children.forEach(function(aChild) {
michael@0: transactions.push(
michael@0: new PlacesTagURITransaction(PlacesUtils._uri(aChild.uri),
michael@0: [aData.title])
michael@0: );
michael@0: });
michael@0: }
michael@0: return new PlacesAggregatedTransaction("addTags", transactions);
michael@0: }
michael@0:
michael@0: if (aData.livemark && aData.annos) { // Copying a livemark.
michael@0: return this._getLivemarkCopyTransaction(aData, aContainer, aIndex);
michael@0: }
michael@0:
michael@0: let transactions = getChildItemsTransactions(aData.children);
michael@0: if (aData.dateAdded) {
michael@0: transactions.push(
michael@0: new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
michael@0: );
michael@0: }
michael@0: if (aData.lastModified) {
michael@0: transactions.push(
michael@0: new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
michael@0: );
michael@0: }
michael@0:
michael@0: let annos = [];
michael@0: if (aData.annos) {
michael@0: annos = aData.annos.filter(function (aAnno) {
michael@0: return this._copyableAnnotations.indexOf(aAnno.name) != -1;
michael@0: }, this);
michael@0: }
michael@0:
michael@0: return new PlacesCreateFolderTransaction(aData.title, aContainer, aIndex,
michael@0: annos, transactions);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Gets a transaction for copying a live bookmark item from one container to
michael@0: * another.
michael@0: *
michael@0: * @param aData
michael@0: * Unwrapped live bookmarkmark data
michael@0: * @param aContainer
michael@0: * The container we are copying into
michael@0: * @param aIndex
michael@0: * The index in the destination container to insert the new items
michael@0: * @return A nsITransaction object that will perform the copy.
michael@0: *
michael@0: * @note Since a copy creates a completely new item, only some internal
michael@0: * annotations are synced from the old one.
michael@0: * @see this._copyableAnnotations for the list of copyable annotations.
michael@0: */
michael@0: _getLivemarkCopyTransaction:
michael@0: function PUIU__getLivemarkCopyTransaction(aData, aContainer, aIndex)
michael@0: {
michael@0: if (!aData.livemark || !aData.annos) {
michael@0: throw new Error("node is not a livemark");
michael@0: }
michael@0:
michael@0: let feedURI, siteURI;
michael@0: let annos = [];
michael@0: if (aData.annos) {
michael@0: annos = aData.annos.filter(function (aAnno) {
michael@0: if (aAnno.name == PlacesUtils.LMANNO_FEEDURI) {
michael@0: feedURI = PlacesUtils._uri(aAnno.value);
michael@0: }
michael@0: else if (aAnno.name == PlacesUtils.LMANNO_SITEURI) {
michael@0: siteURI = PlacesUtils._uri(aAnno.value);
michael@0: }
michael@0: return this._copyableAnnotations.indexOf(aAnno.name) != -1
michael@0: }, this);
michael@0: }
michael@0:
michael@0: return new PlacesCreateLivemarkTransaction(feedURI, siteURI, aData.title,
michael@0: aContainer, aIndex, annos);
michael@0: },
michael@0:
michael@0: /**
michael@0: * Constructs a Transaction for the drop or paste of a blob of data into
michael@0: * a container.
michael@0: * @param data
michael@0: * The unwrapped data blob of dropped or pasted data.
michael@0: * @param type
michael@0: * The content type of the data
michael@0: * @param container
michael@0: * The container the data was dropped or pasted into
michael@0: * @param index
michael@0: * The index within the container the item was dropped or pasted at
michael@0: * @param copy
michael@0: * The drag action was copy, so don't move folders or links.
michael@0: * @returns An object implementing nsITransaction that can perform
michael@0: * the move/insert.
michael@0: */
michael@0: makeTransaction:
michael@0: function PUIU_makeTransaction(data, type, container, index, copy)
michael@0: {
michael@0: switch (data.type) {
michael@0: case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
michael@0: if (copy) {
michael@0: return this._getFolderCopyTransaction(data, container, index);
michael@0: }
michael@0:
michael@0: // Otherwise move the item.
michael@0: return new PlacesMoveItemTransaction(data.id, container, index);
michael@0: break;
michael@0: case PlacesUtils.TYPE_X_MOZ_PLACE:
michael@0: if (copy || data.id == -1) { // Id is -1 if the place is not bookmarked.
michael@0: return this._getURIItemCopyTransaction(data, container, index);
michael@0: }
michael@0:
michael@0: // Otherwise move the item.
michael@0: return new PlacesMoveItemTransaction(data.id, container, index);
michael@0: break;
michael@0: case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
michael@0: if (copy) {
michael@0: // There is no data in a separator, so copying it just amounts to
michael@0: // inserting a new separator.
michael@0: return new PlacesCreateSeparatorTransaction(container, index);
michael@0: }
michael@0:
michael@0: // Otherwise move the item.
michael@0: return new PlacesMoveItemTransaction(data.id, container, index);
michael@0: break;
michael@0: default:
michael@0: if (type == PlacesUtils.TYPE_X_MOZ_URL ||
michael@0: type == PlacesUtils.TYPE_UNICODE ||
michael@0: type == this.TYPE_TAB_DROP) {
michael@0: let title = type != PlacesUtils.TYPE_UNICODE ? data.title
michael@0: : data.uri;
michael@0: return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
michael@0: container, index, title);
michael@0: }
michael@0: }
michael@0: return null;
michael@0: },
michael@0:
michael@0: /**
michael@0: * Shows the bookmark dialog corresponding to the specified info.
michael@0: *
michael@0: * @param aInfo
michael@0: * Describes the item to be edited/added in the dialog.
michael@0: * See documentation at the top of bookmarkProperties.js
michael@0: * @param aWindow
michael@0: * Owner window for the new dialog.
michael@0: *
michael@0: * @see documentation at the top of bookmarkProperties.js
michael@0: * @return true if any transaction has been performed, false otherwise.
michael@0: */
michael@0: showBookmarkDialog:
michael@0: function PUIU_showBookmarkDialog(aInfo, aParentWindow) {
michael@0: // Preserve size attributes differently based on the fact the dialog has
michael@0: // a folder picker or not. If the picker is visible, the dialog should
michael@0: // be resizable since it may not show enough content for the folders
michael@0: // hierarchy.
michael@0: let hasFolderPicker = !("hiddenRows" in aInfo) ||
michael@0: aInfo.hiddenRows.indexOf("folderPicker") == -1;
michael@0: // Use a different chrome url, since this allows to persist different sizes,
michael@0: // based on resizability of the dialog.
michael@0: let dialogURL = hasFolderPicker ?
michael@0: "chrome://browser/content/places/bookmarkProperties2.xul" :
michael@0: "chrome://browser/content/places/bookmarkProperties.xul";
michael@0:
michael@0: let features =
michael@0: "centerscreen,chrome,modal,resizable=" + (hasFolderPicker ? "yes" : "no");
michael@0:
michael@0: aParentWindow.openDialog(dialogURL, "", features, aInfo);
michael@0: return ("performed" in aInfo && aInfo.performed);
michael@0: },
michael@0:
michael@0: _getTopBrowserWin: function PUIU__getTopBrowserWin() {
michael@0: return Services.wm.getMostRecentWindow("navigator:browser");
michael@0: },
michael@0:
michael@0: /**
michael@0: * Returns the closet ancestor places view for the given DOM node
michael@0: * @param aNode
michael@0: * a DOM node
michael@0: * @return the closet ancestor places view if exists, null otherwsie.
michael@0: */
michael@0: getViewForNode: function PUIU_getViewForNode(aNode) {
michael@0: let node = aNode;
michael@0:
michael@0: // The view for a