summaryrefslogtreecommitdiffstats
path: root/browser/components/places/content/editBookmarkOverlay.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/places/content/editBookmarkOverlay.js')
-rw-r--r--browser/components/places/content/editBookmarkOverlay.js1196
1 files changed, 0 insertions, 1196 deletions
diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js
deleted file mode 100644
index d59f5c764..000000000
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ /dev/null
@@ -1,1196 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-
-const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
-const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
-
-var gEditItemOverlay = {
- _observersAdded: false,
- _staticFoldersListBuilt: false,
-
- _paneInfo: null,
- _setPaneInfo(aInitInfo) {
- if (!aInitInfo)
- return this._paneInfo = null;
-
- if ("uris" in aInitInfo && "node" in aInitInfo)
- throw new Error("ambiguous pane info");
- if (!("uris" in aInitInfo) && !("node" in aInitInfo))
- throw new Error("Neither node nor uris set for pane info");
-
- let node = "node" in aInitInfo ? aInitInfo.node : null;
-
- // Since there's no true UI for folder shortcuts (they show up just as their target
- // folders), when the pane shows for them it's opened in read-only mode, showing the
- // properties of the target folder.
- let itemId = node ? node.itemId : -1;
- let itemGuid = PlacesUIUtils.useAsyncTransactions && node ?
- PlacesUtils.getConcreteItemGuid(node) : null;
- let isItem = itemId != -1;
- let isFolderShortcut = isItem &&
- node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
- let isTag = node && PlacesUtils.nodeIsTagQuery(node);
- if (isTag) {
- itemId = PlacesUtils.getConcreteItemId(node);
- // For now we don't have access to the item guid synchronously for tags,
- // so we'll need to fetch it later.
- }
- let isURI = node && PlacesUtils.nodeIsURI(node);
- let uri = isURI ? NetUtil.newURI(node.uri) : null;
- let title = node ? node.title : null;
- let isBookmark = isItem && isURI;
- let bulkTagging = !node;
- let uris = bulkTagging ? aInitInfo.uris : null;
- let visibleRows = new Set();
- let isParentReadOnly = false;
- let postData = aInitInfo.postData;
- if (node && "parent" in node) {
- let parent = node.parent;
- if (parent) {
- isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) ||
- PlacesUIUtils.isContentsReadOnly(parent);
- }
- }
- let focusedElement = aInitInfo.focusedElement;
- let onPanelReady = aInitInfo.onPanelReady;
-
- return this._paneInfo = { itemId, itemGuid, isItem,
- isURI, uri, title,
- isBookmark, isFolderShortcut, isParentReadOnly,
- bulkTagging, uris,
- visibleRows, postData, isTag, focusedElement,
- onPanelReady };
- },
-
- get initialized() {
- return this._paneInfo != null;
- },
-
- // Backwards-compatibility getters
- get itemId() {
- if (!this.initialized || this._paneInfo.bulkTagging)
- return -1;
- return this._paneInfo.itemId;
- },
-
- get uri() {
- if (!this.initialized)
- return null;
- if (this._paneInfo.bulkTagging)
- return this._paneInfo.uris[0];
- return this._paneInfo.uri;
- },
-
- get multiEdit() {
- return this.initialized && this._paneInfo.bulkTagging;
- },
-
- // Check if the pane is initialized to show only read-only fields.
- get readOnly() {
- // TODO (Bug 1120314): Folder shortcuts are currently read-only due to some
- // quirky implementation details (the most important being the "smart"
- // semantics of node.title that makes hard to edit the right entry).
- // This pane is read-only if:
- // * the panel is not initialized
- // * the node is a folder shortcut
- // * the node is not bookmarked and not a tag container
- // * the node is child of a read-only container and is not a bookmarked
- // URI nor a tag container
- return !this.initialized ||
- this._paneInfo.isFolderShortcut ||
- (!this._paneInfo.isItem && !this._paneInfo.isTag) ||
- (this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark && !this._paneInfo.isTag);
- },
-
- // the first field which was edited after this panel was initialized for
- // a certain item
- _firstEditedField: "",
-
- _initNamePicker() {
- if (this._paneInfo.bulkTagging)
- throw new Error("_initNamePicker called unexpectedly");
-
- // title may by null, which, for us, is the same as an empty string.
- this._initTextField(this._namePicker, this._paneInfo.title || "");
- },
-
- _initLocationField() {
- if (!this._paneInfo.isURI)
- throw new Error("_initLocationField called unexpectedly");
- this._initTextField(this._locationField, this._paneInfo.uri.spec);
- },
-
- _initDescriptionField() {
- if (!this._paneInfo.isItem)
- throw new Error("_initDescriptionField called unexpectedly");
-
- this._initTextField(this._descriptionField,
- PlacesUIUtils.getItemDescription(this._paneInfo.itemId));
- },
-
- _initKeywordField: Task.async(function* (newKeyword = "") {
- if (!this._paneInfo.isBookmark) {
- throw new Error("_initKeywordField called unexpectedly");
- }
-
- // Reset the field status synchronously now, eventually we'll reinit it
- // later if we find an existing keyword. This way we can ensure to be in a
- // consistent status when reusing the panel across different bookmarks.
- this._keyword = newKeyword;
- this._initTextField(this._keywordField, newKeyword);
-
- if (!newKeyword) {
- let entries = [];
- yield PlacesUtils.keywords.fetch({ url: this._paneInfo.uri.spec },
- e => entries.push(e));
- if (entries.length > 0) {
- // We show an existing keyword if either POST data was not provided, or
- // if the POST data is the same.
- let existingKeyword = entries[0].keyword;
- let postData = this._paneInfo.postData;
- if (postData) {
- let sameEntry = entries.find(e => e.postData === postData);
- existingKeyword = sameEntry ? sameEntry.keyword : "";
- }
- if (existingKeyword) {
- this._keyword = existingKeyword;
- // Update the text field to the existing keyword.
- this._initTextField(this._keywordField, this._keyword);
- }
- }
- }
- }),
-
- _initLoadInSidebar: Task.async(function* () {
- if (!this._paneInfo.isBookmark)
- throw new Error("_initLoadInSidebar called unexpectedly");
-
- this._loadInSidebarCheckbox.checked =
- PlacesUtils.annotations.itemHasAnnotation(
- this._paneInfo.itemId, PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
- }),
-
- /**
- * Initialize the panel.
- *
- * @param aInfo
- * An object having:
- * 1. one of the following properties:
- * - node: either a result node or a node-like object representing the
- * item to be edited. A node-like object must have the following
- * properties (with values that match exactly those a result node
- * would have): itemId, bookmarkGuid, uri, title, type.
- * - uris: an array of uris for bulk tagging.
- *
- * 2. any of the following optional properties:
- * - hiddenRows (Strings array): list of rows to be hidden regardless
- * of the item edited. Possible values: "title", "location",
- * "description", "keyword", "loadInSidebar", "feedLocation",
- * "siteLocation", folderPicker"
- */
- initPanel(aInfo) {
- if (typeof(aInfo) != "object" || aInfo === null)
- throw new Error("aInfo must be an object.");
- if ("node" in aInfo) {
- try {
- aInfo.node.type;
- } catch (e) {
- // If the lazy loader for |type| generates an exception, it means that
- // this bookmark could not be loaded. This sometimes happens when tests
- // create a bookmark by clicking the bookmark star, then try to cleanup
- // before the bookmark panel has finished opening. Either way, if we
- // cannot retrieve the bookmark information, we cannot open the panel.
- return;
- }
- }
-
- // For sanity ensure that the implementer has uninited the panel before
- // trying to init it again, or we could end up leaking due to observers.
- if (this.initialized)
- this.uninitPanel(false);
-
- let { itemId, isItem, isURI,
- isBookmark, bulkTagging, uris,
- visibleRows, focusedElement,
- onPanelReady } = this._setPaneInfo(aInfo);
-
- let showOrCollapse =
- (rowId, isAppropriateForInput, nameInHiddenRows = null) => {
- let visible = isAppropriateForInput;
- if (visible && "hiddenRows" in aInfo && nameInHiddenRows)
- visible &= aInfo.hiddenRows.indexOf(nameInHiddenRows) == -1;
- if (visible)
- visibleRows.add(rowId);
- return !(this._element(rowId).collapsed = !visible);
- };
-
- if (showOrCollapse("nameRow", !bulkTagging, "name")) {
- this._initNamePicker();
- this._namePicker.readOnly = this.readOnly;
- }
-
- // In some cases we want to hide the location field, since it's not
- // human-readable, but we still want to initialize it.
- showOrCollapse("locationRow", isURI, "location");
- if (isURI) {
- this._initLocationField();
- this._locationField.readOnly = this.readOnly;
- }
-
- // hide the description field for
- if (showOrCollapse("descriptionRow", isItem && !this.readOnly,
- "description")) {
- this._initDescriptionField();
- this._descriptionField.readOnly = this.readOnly;
- }
-
- if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
- this._initKeywordField().catch(Components.utils.reportError);
- this._keywordField.readOnly = this.readOnly;
- }
-
- // Collapse the tag selector if the item does not accept tags.
- if (showOrCollapse("tagsRow", isURI || bulkTagging, "tags"))
- this._initTagsField().catch(Components.utils.reportError);
- else if (!this._element("tagsSelectorRow").collapsed)
- this.toggleTagsSelector().catch(Components.utils.reportError);
-
- // Load in sidebar.
- if (showOrCollapse("loadInSidebarCheckbox", isBookmark, "loadInSidebar")) {
- this._initLoadInSidebar();
- }
-
- // Folder picker.
- // Technically we should check that the item is not moveable, but that's
- // not cheap (we don't always have the parent), and there's no use case for
- // this (it's only the Star UI that shows the folderPicker)
- if (showOrCollapse("folderRow", isItem, "folderPicker")) {
- let containerId = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
- this._initFolderMenuList(containerId);
- }
-
- // Selection count.
- if (showOrCollapse("selectionCount", bulkTagging)) {
- this._element("itemsCountText").value =
- PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
- uris.length,
- [uris.length]);
- }
-
- // Observe changes.
- if (!this._observersAdded) {
- PlacesUtils.bookmarks.addObserver(this, false);
- window.addEventListener("unload", this, false);
- this._observersAdded = true;
- }
-
- let focusElement = () => {
- // The focusedElement possible values are:
- // * preferred: focus the field that the user touched first the last
- // time the pane was shown (either namePicker or tagsField)
- // * first: focus the first non collapsed textbox
- // Note: since all controls are collapsed by default, we don't get the
- // default XUL dialog behavior, that selects the first control, so we set
- // the focus explicitly.
- // Note: If focusedElement === "preferred", this file expects gPrefService
- // to be defined in the global scope.
- let elt;
- if (focusedElement === "preferred") {
- /* eslint-disable no-undef */
- elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
- /* eslint-enable no-undef */
- } else if (focusedElement === "first") {
- elt = document.querySelector("textbox:not([collapsed=true])");
- }
- if (elt) {
- elt.focus();
- elt.select();
- }
- };
-
- if (onPanelReady) {
- onPanelReady(focusElement);
- } else {
- focusElement();
- }
- },
-
- /**
- * Finds tags that are in common among this._currentInfo.uris;
- */
- _getCommonTags() {
- if ("_cachedCommonTags" in this._paneInfo)
- return this._paneInfo._cachedCommonTags;
-
- let uris = [...this._paneInfo.uris];
- let firstURI = uris.shift();
- let commonTags = new Set(PlacesUtils.tagging.getTagsForURI(firstURI));
- if (commonTags.size == 0)
- return this._cachedCommonTags = [];
-
- for (let uri of uris) {
- let curentURITags = PlacesUtils.tagging.getTagsForURI(uri);
- for (let tag of commonTags) {
- if (!curentURITags.includes(tag)) {
- commonTags.delete(tag)
- if (commonTags.size == 0)
- return this._paneInfo.cachedCommonTags = [];
- }
- }
- }
- return this._paneInfo._cachedCommonTags = [...commonTags];
- },
-
- _initTextField(aElement, aValue) {
- if (aElement.value != aValue) {
- aElement.value = aValue;
-
- // Clear the editor's undo stack
- let transactionManager;
- try {
- transactionManager = aElement.editor.transactionManager;
- } catch (e) {
- // When retrieving the transaction manager, editor may be null resulting
- // in a TypeError. Additionally, the transaction manager may not
- // exist yet, which causes access to it to throw NS_ERROR_FAILURE.
- // In either event, the transaction manager doesn't exist it, so we
- // don't need to worry about clearing it.
- if (!(e instanceof TypeError) && e.result != Cr.NS_ERROR_FAILURE) {
- throw e;
- }
- }
- if (transactionManager) {
- transactionManager.clear();
- }
- }
- },
-
- /**
- * 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(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 EIO__initFolderMenuList(aSelectedFolder) {
- // clean up first
- var menupopup = this._folderMenuList.menupopup;
- while (menupopup.childNodes.length > 6)
- menupopup.removeChild(menupopup.lastChild);
-
- const bms = PlacesUtils.bookmarks;
- const annos = PlacesUtils.annotations;
-
- // Build the static list
- var unfiledItem = this._element("unfiledRootItem");
- if (!this._staticFoldersListBuilt) {
- unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
- unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
- var bmMenuItem = this._element("bmRootItem");
- bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
- bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
- var toolbarItem = this._element("toolbarFolderItem");
- toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
- toolbarItem.folderId = PlacesUtils.toolbarFolderId;
- this._staticFoldersListBuilt = true;
- }
-
- // List of recently used folders:
- 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.
- */
- this._recentFolders = [];
- for (let i = 0; i < folderIds.length; i++) {
- var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
- this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed });
- }
- this._recentFolders.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,
- this._recentFolders.length);
- for (let i = 0; i < numberOfItems; i++) {
- this._appendFolderItemToMenupopup(menupopup,
- this._recentFolders[i].folderId);
- }
-
- var defaultItem = this._getFolderMenuItem(aSelectedFolder);
- this._folderMenuList.selectedItem = defaultItem;
-
- // Set a selectedIndex attribute to show special icons
- this._folderMenuList.setAttribute("selectedIndex",
- this._folderMenuList.selectedIndex);
-
- // Hide the folders-separator if no folder is annotated as recently-used
- this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
- this._folderMenuList.disabled = this.readOnly;
- },
-
- QueryInterface:
- XPCOMUtils.generateQI([Components.interfaces.nsIDOMEventListener,
- Components.interfaces.nsINavBookmarkObserver]),
-
- _element(aID) {
- return document.getElementById("editBMPanel_" + aID);
- },
-
- uninitPanel(aHideCollapsibleElements) {
- if (aHideCollapsibleElements) {
- // Hide the folder tree if it was previously visible.
- var folderTreeRow = this._element("folderTreeRow");
- if (!folderTreeRow.collapsed)
- this.toggleFolderTreeVisibility();
-
- // Hide the tag selector if it was previously visible.
- var tagsSelectorRow = this._element("tagsSelectorRow");
- if (!tagsSelectorRow.collapsed)
- this.toggleTagsSelector();
- }
-
- if (this._observersAdded) {
- PlacesUtils.bookmarks.removeObserver(this);
- this._observersAdded = false;
- }
-
- this._setPaneInfo(null);
- this._firstEditedField = "";
- },
-
- onTagsFieldChange() {
- if (this._paneInfo.isURI || this._paneInfo.bulkTagging) {
- this._updateTags().then(
- anyChanges => {
- if (anyChanges)
- this._mayUpdateFirstEditField("tagsField");
- }, Components.utils.reportError);
- }
- },
-
- /**
- * For a given array of currently-set tags and the tags-input-field
- * value, returns which tags should be removed and which should be added in
- * the form of { removedTags: [...], newTags: [...] }.
- */
- _getTagsChanges(aCurrentTags) {
- let inputTags = this._getTagsArrayFromTagsInputField();
-
- // Optimize the trivial cases (which are actually the most common).
- if (inputTags.length == 0 && aCurrentTags.length == 0)
- return { newTags: [], removedTags: [] };
- if (inputTags.length == 0)
- return { newTags: [], removedTags: aCurrentTags };
- if (aCurrentTags.length == 0)
- return { newTags: inputTags, removedTags: [] };
-
- let removedTags = aCurrentTags.filter(t => !inputTags.includes(t));
- let newTags = inputTags.filter(t => !aCurrentTags.includes(t));
- return { removedTags, newTags };
- },
-
- // Adds and removes tags for one or more uris.
- _setTagsFromTagsInputField: Task.async(function* (aCurrentTags, aURIs) {
- let { removedTags, newTags } = this._getTagsChanges(aCurrentTags);
- if (removedTags.length + newTags.length == 0)
- return false;
-
- if (!PlacesUIUtils.useAsyncTransactions) {
- let txns = [];
- for (let uri of aURIs) {
- if (removedTags.length > 0)
- txns.push(new PlacesUntagURITransaction(uri, removedTags));
- if (newTags.length > 0)
- txns.push(new PlacesTagURITransaction(uri, newTags));
- }
-
- PlacesUtils.transactionManager.doTransaction(
- new PlacesAggregatedTransaction("Update tags", txns));
- return true;
- }
-
- let setTags = function* () {
- if (newTags.length > 0) {
- yield PlacesTransactions.Tag({ urls: aURIs, tags: newTags })
- .transact();
- }
- if (removedTags.length > 0) {
- yield PlacesTransactions.Untag({ urls: aURIs, tags: removedTags })
- .transact();
- }
- };
-
- // Only in the library info-pane it's safe (and necessary) to batch these.
- // TODO bug 1093030: cleanup this mess when the bookmarksProperties dialog
- // and star UI code don't "run a batch in the background".
- if (window.document.documentElement.id == "places")
- PlacesTransactions.batch(setTags).catch(Components.utils.reportError);
- else
- Task.spawn(setTags).catch(Components.utils.reportError);
- return true;
- }),
-
- _updateTags: Task.async(function*() {
- let uris = this._paneInfo.bulkTagging ?
- this._paneInfo.uris : [this._paneInfo.uri];
- let currentTags = this._paneInfo.bulkTagging ?
- yield this._getCommonTags() :
- PlacesUtils.tagging.getTagsForURI(uris[0]);
- let anyChanges = yield this._setTagsFromTagsInputField(currentTags, uris);
- if (!anyChanges)
- return false;
-
- // The panel could have been closed in the meanwhile.
- if (!this._paneInfo)
- return false;
-
- // Ensure the tagsField is in sync, clean it up from empty tags
- currentTags = this._paneInfo.bulkTagging ?
- this._getCommonTags() :
- PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri);
- this._initTextField(this._tagsField, currentTags.join(", "), false);
- return true;
- }),
-
- /**
- * Stores the first-edit field for this dialog, if the passed-in field
- * is indeed the first edited field
- * @param aNewField
- * the id of the field that may be set (without the "editBMPanel_"
- * prefix)
- */
- _mayUpdateFirstEditField(aNewField) {
- // * The first-edit-field behavior is not applied in the multi-edit case
- // * if this._firstEditedField is already set, this is not the first field,
- // so there's nothing to do
- if (this._paneInfo.bulkTagging || this._firstEditedField)
- return;
-
- this._firstEditedField = aNewField;
-
- // set the pref
- var prefs = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField);
- },
-
- onNamePickerChange() {
- if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag))
- return;
-
- // Here we update either the item title or its cached static title
- let newTitle = this._namePicker.value;
- if (!newTitle &&
- PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) == PlacesUtils.tagsFolderId) {
- // We don't allow setting an empty title for a tag, restore the old one.
- this._initNamePicker();
- }
- else {
- this._mayUpdateFirstEditField("namePicker");
- if (!PlacesUIUtils.useAsyncTransactions) {
- let txn = new PlacesEditItemTitleTransaction(this._paneInfo.itemId,
- newTitle);
- PlacesUtils.transactionManager.doTransaction(txn);
- return;
- }
- Task.spawn(function* () {
- let guid = this._paneInfo.isTag
- ? (yield PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
- : this._paneInfo.itemGuid;
- PlacesTransactions.EditTitle({ guid, title: newTitle })
- .transact().catch(Components.utils.reportError);
- }).catch(Components.utils.reportError);
- }
- },
-
- onDescriptionFieldChange() {
- if (this.readOnly || !this._paneInfo.isItem)
- return;
-
- let itemId = this._paneInfo.itemId;
- let description = this._element("descriptionField").value;
- if (description != PlacesUIUtils.getItemDescription(this._paneInfo.itemId)) {
- let annotation =
- { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
- if (!PlacesUIUtils.useAsyncTransactions) {
- let txn = new PlacesSetItemAnnotationTransaction(itemId,
- annotation);
- PlacesUtils.transactionManager.doTransaction(txn);
- return;
- }
- let guid = this._paneInfo.itemGuid;
- PlacesTransactions.Annotate({ guid, annotation })
- .transact().catch(Components.utils.reportError);
- }
- },
-
- onLocationFieldChange() {
- if (this.readOnly || !this._paneInfo.isBookmark)
- return;
-
- let newURI;
- try {
- newURI = PlacesUIUtils.createFixedURI(this._locationField.value);
- }
- catch (ex) {
- // TODO: Bug 1089141 - Provide some feedback about the invalid url.
- return;
- }
-
- if (this._paneInfo.uri.equals(newURI))
- return;
-
- if (!PlacesUIUtils.useAsyncTransactions) {
- let txn = new PlacesEditBookmarkURITransaction(this._paneInfo.itemId, newURI);
- PlacesUtils.transactionManager.doTransaction(txn);
- return;
- }
- let guid = this._paneInfo.itemGuid;
- PlacesTransactions.EditUrl({ guid, url: newURI })
- .transact().catch(Components.utils.reportError);
- },
-
- onKeywordFieldChange() {
- if (this.readOnly || !this._paneInfo.isBookmark)
- return;
-
- let itemId = this._paneInfo.itemId;
- let oldKeyword = this._keyword;
- let keyword = this._keyword = this._keywordField.value;
- let postData = this._paneInfo.postData;
- if (!PlacesUIUtils.useAsyncTransactions) {
- let txn = new PlacesEditBookmarkKeywordTransaction(itemId,
- keyword,
- postData,
- oldKeyword);
- PlacesUtils.transactionManager.doTransaction(txn);
- return;
- }
- let guid = this._paneInfo.itemGuid;
- PlacesTransactions.EditKeyword({ guid, keyword, postData, oldKeyword })
- .transact().catch(Components.utils.reportError);
- },
-
- onLoadInSidebarCheckboxCommand() {
- if (!this.initialized || !this._paneInfo.isBookmark)
- return;
-
- let annotation = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO };
- if (this._loadInSidebarCheckbox.checked)
- annotation.value = true;
-
- if (!PlacesUIUtils.useAsyncTransactions) {
- let itemId = this._paneInfo.itemId;
- let txn = new PlacesSetItemAnnotationTransaction(itemId,
- annotation);
- PlacesUtils.transactionManager.doTransaction(txn);
- return;
- }
- let guid = this._paneInfo.itemGuid;
- PlacesTransactions.Annotate({ guid, annotation })
- .transact().catch(Components.utils.reportError);
- },
-
- toggleFolderTreeVisibility() {
- var expander = this._element("foldersExpander");
- var folderTreeRow = this._element("folderTreeRow");
- if (!folderTreeRow.collapsed) {
- expander.className = "expander-down";
- expander.setAttribute("tooltiptext",
- expander.getAttribute("tooltiptextdown"));
- folderTreeRow.collapsed = true;
- this._element("chooseFolderSeparator").hidden =
- this._element("chooseFolderMenuItem").hidden = false;
- }
- else {
- expander.className = "expander-up"
- expander.setAttribute("tooltiptext",
- expander.getAttribute("tooltiptextup"));
- folderTreeRow.collapsed = false;
-
- // XXXmano: Ideally we would only do this once, but for some odd reason,
- // the editable mode set on this tree, together with its collapsed state
- // breaks the view.
- const FOLDER_TREE_PLACE_URI =
- "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
- PlacesUIUtils.allBookmarksFolderId;
- this._folderTree.place = FOLDER_TREE_PLACE_URI;
-
- this._element("chooseFolderSeparator").hidden =
- this._element("chooseFolderMenuItem").hidden = true;
- var currentFolder = this._getFolderIdFromMenuList();
- this._folderTree.selectItems([currentFolder]);
- this._folderTree.focus();
- }
- },
-
- _getFolderIdFromMenuList() {
- var selectedItem = this._folderMenuList.selectedItem;
- 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.
- */
- _getFolderMenuItem(aFolderId) {
- let menupopup = this._folderMenuList.menupopup;
- let menuItem = Array.prototype.find.call(
- menupopup.childNodes, menuItem => menuItem.folderId === aFolderId);
- if (menuItem !== undefined)
- return menuItem;
-
- // 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);
- },
-
- onFolderMenuListCommand(aEvent) {
- // Set a selectedIndex attribute to show special icons
- this._folderMenuList.setAttribute("selectedIndex",
- this._folderMenuList.selectedIndex);
-
- if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
- // reset the selection back to where it was and expand the tree
- // (this menu-item is hidden when the tree is already visible
- let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId);
- let item = this._getFolderMenuItem(containerId);
- this._folderMenuList.selectedItem = item;
- // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
- // menulist right away
- setTimeout(() => this.toggleFolderTreeVisibility(), 100);
- return;
- }
-
- // Move the item
- let containerId = this._getFolderIdFromMenuList();
- if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId &&
- this._paneInfo.itemId != containerId) {
- if (PlacesUIUtils.useAsyncTransactions) {
- Task.spawn(function* () {
- let newParentGuid = yield PlacesUtils.promiseItemGuid(containerId);
- let guid = this._paneInfo.itemGuid;
- yield PlacesTransactions.Move({ guid, newParentGuid }).transact();
- }.bind(this));
- }
- else {
- let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId,
- containerId,
- PlacesUtils.bookmarks.DEFAULT_INDEX);
- PlacesUtils.transactionManager.doTransaction(txn);
- }
-
- // Mark the containing folder as recently-used if it isn't in the
- // static list
- if (containerId != PlacesUtils.unfiledBookmarksFolderId &&
- containerId != PlacesUtils.toolbarFolderId &&
- containerId != PlacesUtils.bookmarksMenuFolderId) {
- this._markFolderAsRecentlyUsed(containerId)
- .catch(Components.utils.reportError);
- }
-
- // Auto-show the bookmarks toolbar when adding / moving an item there.
- if (containerId == PlacesUtils.toolbarFolderId) {
- Services.obs.notifyObservers(null, "autoshow-bookmarks-toolbar", null);
- }
- }
-
- // Update folder-tree selection
- var folderTreeRow = this._element("folderTreeRow");
- if (!folderTreeRow.collapsed) {
- var selectedNode = this._folderTree.selectedNode;
- if (!selectedNode ||
- PlacesUtils.getConcreteItemId(selectedNode) != containerId)
- this._folderTree.selectItems([containerId]);
- }
- },
-
- onFolderTreeSelect() {
- var selectedNode = this._folderTree.selectedNode;
-
- // Disable the "New Folder" button if we cannot create a new folder
- this._element("newFolderButton")
- .disabled = !this._folderTree.insertionPoint || !selectedNode;
-
- if (!selectedNode)
- return;
-
- var folderId = PlacesUtils.getConcreteItemId(selectedNode);
- if (this._getFolderIdFromMenuList() == folderId)
- return;
-
- var folderItem = this._getFolderMenuItem(folderId);
- this._folderMenuList.selectedItem = folderItem;
- folderItem.doCommand();
- },
-
- _markFolderAsRecentlyUsed: Task.async(function* (aFolderId) {
- if (!PlacesUIUtils.useAsyncTransactions) {
- let txns = [];
-
- // Expire old unused recent folders.
- let annotation = this._getLastUsedAnnotationObject(false);
- while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
- let folderId = this._recentFolders.pop().folderId;
- let annoTxn = new PlacesSetItemAnnotationTransaction(folderId,
- annotation);
- txns.push(annoTxn);
- }
-
- // Mark folder as recently used
- annotation = this._getLastUsedAnnotationObject(true);
- let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId,
- annotation);
- txns.push(annoTxn);
-
- let aggregate =
- new PlacesAggregatedTransaction("Update last used folders", txns);
- PlacesUtils.transactionManager.doTransaction(aggregate);
- return;
- }
-
- // Expire old unused recent folders.
- let guids = [];
- while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
- let folderId = this._recentFolders.pop().folderId;
- let guid = yield PlacesUtils.promiseItemGuid(folderId);
- guids.push(guid);
- }
- if (guids.length > 0) {
- let annotation = this._getLastUsedAnnotationObject(false);
- PlacesTransactions.Annotate({ guids, annotation })
- .transact().catch(Components.utils.reportError);
- }
-
- // Mark folder as recently used
- let annotation = this._getLastUsedAnnotationObject(true);
- let guid = yield PlacesUtils.promiseItemGuid(aFolderId);
- PlacesTransactions.Annotate({ guid, annotation })
- .transact().catch(Components.utils.reportError);
- }),
-
- /**
- * Returns an object which could then be used to set/unset the
- * LAST_USED_ANNO annotation for a folder.
- *
- * @param aLastUsed
- * Whether to set or unset the LAST_USED_ANNO annotation.
- * @returns an object representing the annotation which could then be used
- * with the transaction manager.
- */
- _getLastUsedAnnotationObject(aLastUsed) {
- return { name: LAST_USED_ANNO,
- value: aLastUsed ? new Date().getTime() : null };
- },
-
- _rebuildTagsSelectorList: Task.async(function* () {
- let tagsSelector = this._element("tagsSelector");
- let tagsSelectorRow = this._element("tagsSelectorRow");
- if (tagsSelectorRow.collapsed)
- return;
-
- // Save the current scroll position and restore it after the rebuild.
- let firstIndex = tagsSelector.getIndexOfFirstVisibleRow();
- let selectedIndex = tagsSelector.selectedIndex;
- let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label
- : null;
-
- while (tagsSelector.hasChildNodes()) {
- tagsSelector.removeChild(tagsSelector.lastChild);
- }
-
- let tagsInField = this._getTagsArrayFromTagsInputField();
- let allTags = PlacesUtils.tagging.allTags;
- for (tag of allTags) {
- let elt = document.createElement("listitem");
- elt.setAttribute("type", "checkbox");
- elt.setAttribute("label", tag);
- if (tagsInField.includes(tag))
- elt.setAttribute("checked", "true");
- tagsSelector.appendChild(elt);
- if (selectedTag === tag)
- selectedIndex = tagsSelector.getIndexOfItem(elt);
- }
-
- // Restore position.
- // The listbox allows to scroll only if the required offset doesn't
- // overflow its capacity, thus need to adjust the index for removals.
- firstIndex =
- Math.min(firstIndex,
- tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows());
- tagsSelector.scrollToIndex(firstIndex);
- if (selectedIndex >= 0 && tagsSelector.itemCount > 0) {
- selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1);
- tagsSelector.selectedIndex = selectedIndex;
- tagsSelector.ensureIndexIsVisible(selectedIndex);
- }
- }),
-
- toggleTagsSelector: Task.async(function* () {
- var tagsSelector = this._element("tagsSelector");
- var tagsSelectorRow = this._element("tagsSelectorRow");
- var expander = this._element("tagsSelectorExpander");
- if (tagsSelectorRow.collapsed) {
- expander.className = "expander-up";
- expander.setAttribute("tooltiptext",
- expander.getAttribute("tooltiptextup"));
- tagsSelectorRow.collapsed = false;
- yield this._rebuildTagsSelectorList();
-
- // 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"));
- tagsSelectorRow.collapsed = true;
- }
- }),
-
- /**
- * Splits "tagsField" element value, returning an array of valid tag strings.
- *
- * @return Array of tag strings found in the field value.
- */
- _getTagsArrayFromTagsInputField() {
- let tags = this._element("tagsField").value;
- return tags.trim()
- .split(/\s*,\s*/) // Split on commas and remove spaces.
- .filter(tag => tag.length > 0); // Kill empty tags.
- },
-
- newFolder: Task.async(function* () {
- let ip = this._folderTree.insertionPoint;
-
- // default to the bookmarks menu folder
- if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
- ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
- PlacesUtils.bookmarks.DEFAULT_INDEX,
- Ci.nsITreeView.DROP_ON);
- }
-
- // XXXmano: add a separate "New Folder" string at some point...
- let title = this._element("newFolderButton").label;
- if (PlacesUIUtils.useAsyncTransactions) {
- let parentGuid = yield ip.promiseGuid();
- yield PlacesTransactions.NewFolder({ parentGuid, title, index: ip.index })
- .transact().catch(Components.utils.reportError);
- }
- else {
- let txn = new PlacesCreateFolderTransaction(title, ip.itemId, ip.index);
- PlacesUtils.transactionManager.doTransaction(txn);
- }
-
- this._folderTree.focus();
- this._folderTree.selectItems([ip.itemId]);
- PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true;
- this._folderTree.selectItems([this._lastNewItem]);
- this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
- this._folderTree.columns.getFirstColumn());
- }),
-
- // nsIDOMEventListener
- handleEvent(aEvent) {
- switch (aEvent.type) {
- case "CheckboxStateChange":
- // Update the tags field when items are checked/unchecked in the listbox
- let tags = this._getTagsArrayFromTagsInputField();
- let tagCheckbox = aEvent.target;
-
- let curTagIndex = tags.indexOf(tagCheckbox.label);
- let tagsSelector = this._element("tagsSelector");
- tagsSelector.selectedItem = tagCheckbox;
-
- if (tagCheckbox.checked) {
- if (curTagIndex == -1)
- tags.push(tagCheckbox.label);
- }
- else if (curTagIndex != -1) {
- tags.splice(curTagIndex, 1);
- }
- this._element("tagsField").value = tags.join(", ");
- this._updateTags();
- break;
- case "unload":
- this.uninitPanel(false);
- break;
- }
- },
-
- _initTagsField: Task.async(function* () {
- let tags;
- if (this._paneInfo.isURI)
- tags = PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri);
- else if (this._paneInfo.bulkTagging)
- tags = this._getCommonTags();
- else
- throw new Error("_promiseTagsStr called unexpectedly");
-
- this._initTextField(this._tagsField, tags.join(", "));
- }),
-
- _onTagsChange(aItemId) {
- let paneInfo = this._paneInfo;
- let updateTagsField = false;
- if (paneInfo.isURI) {
- if (paneInfo.isBookmark && aItemId == paneInfo.itemId) {
- updateTagsField = true;
- }
- else if (!paneInfo.isBookmark) {
- let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
- updateTagsField = changedURI.equals(paneInfo.uri);
- }
- }
- else if (paneInfo.bulkTagging) {
- let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
- if (paneInfo.uris.some(uri => uri.equals(changedURI))) {
- updateTagsField = true;
- delete this._paneInfo._cachedCommonTags;
- }
- }
- else {
- throw new Error("_onTagsChange called unexpectedly");
- }
-
- if (updateTagsField)
- this._initTagsField().catch(Components.utils.reportError);
-
- // Any tags change should be reflected in the tags selector.
- if (this._element("tagsSelector"))
- this._rebuildTagsSelectorList().catch(Components.utils.reportError);
- },
-
- _onItemTitleChange(aItemId, aNewTitle) {
- if (!this._paneInfo.isBookmark)
- return;
- if (aItemId == this._paneInfo.itemId) {
- this._paneInfo.title = aNewTitle;
- this._initTextField(this._namePicker, aNewTitle);
- }
- else if (this._paneInfo.visibleRows.has("folderRow")) {
- // If the title of a folder which is listed within the folders
- // menulist has been changed, we need to update the label of its
- // representing element.
- let menupopup = this._folderMenuList.menupopup;
- for (menuitem of menupopup.childNodes) {
- if ("folderId" in menuitem && menuitem.folderId == aItemId) {
- menuitem.label = aNewTitle;
- break;
- }
- }
- }
- },
-
- // nsINavBookmarkObserver
- onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue,
- aLastModified, aItemType) {
- if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) {
- this._onTagsChange(aItemId);
- }
- else if (aProperty == "title" && this._paneInfo.isItem) {
- // This also updates titles of folders in the folder menu list.
- this._onItemTitleChange(aItemId, aValue);
- }
- else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
- return;
- }
-
- switch (aProperty) {
- case "uri":
- let newURI = NetUtil.newURI(aValue);
- if (!newURI.equals(this._paneInfo.uri)) {
- this._paneInfo.uri = newURI;
- if (this._paneInfo.visibleRows.has("locationRow"))
- this._initLocationField();
-
- if (this._paneInfo.visibleRows.has("tagsRow")) {
- delete this._paneInfo._cachedCommonTags;
- this._onTagsChange(aItemId);
- }
- }
- break;
- case "keyword":
- if (this._paneInfo.visibleRows.has("keywordRow"))
- this._initKeywordField(aValue).catch(Components.utils.reportError);
- break;
- case PlacesUIUtils.DESCRIPTION_ANNO:
- if (this._paneInfo.visibleRows.has("descriptionRow"))
- this._initDescriptionField();
- break;
- case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
- if (this._paneInfo.visibleRows.has("loadInSidebarCheckbox"))
- this._initLoadInSidebar();
- break;
- }
- },
-
- onItemMoved(aItemId, aOldParent, aOldIndex,
- aNewParent, aNewIndex, aItemType) {
- if (!this._paneInfo.isItem ||
- !this._paneInfo.visibleRows.has("folderPicker") ||
- this._paneInfo.itemId != aItemOd ||
- aNewParent == this._getFolderIdFromMenuList()) {
- return;
- }
-
- // Just setting selectItem _does not_ trigger oncommand, so we don't
- // recurse.
- this._folderMenuList.selectedItem = this._getFolderMenuItem(aNewParent);
- },
-
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) {
- this._lastNewItem = aItemId;
- },
-
- onItemRemoved() { },
- onBeginUpdateBatch() { },
- onEndUpdateBatch() { },
- onItemVisited() { },
-};
-
-
-for (let elt of ["folderMenuList", "folderTree", "namePicker",
- "locationField", "descriptionField", "keywordField",
- "tagsField", "loadInSidebarCheckbox"]) {
- let eltScoped = elt;
- XPCOMUtils.defineLazyGetter(gEditItemOverlay, `_${eltScoped}`,
- () => gEditItemOverlay._element(eltScoped));
-}