12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064 |
- /* 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/. */
- const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
- const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
- var gEditItemOverlay = {
- _uri: null,
- _itemId: -1,
- _itemIds: [],
- _uris: [],
- _tags: [],
- _allTags: [],
- _keyword: null,
- _multiEdit: false,
- _itemType: -1,
- _readOnly: false,
- _hiddenRows: [],
- _onPanelReady: false,
- _observersAdded: false,
- _staticFoldersListBuilt: false,
- _initialized: false,
- _titleOverride: "",
- // the first field which was edited after this panel was initialized for
- // a certain item
- _firstEditedField: "",
- get itemId() {
- return this._itemId;
- },
- get uri() {
- return this._uri;
- },
- get multiEdit() {
- return this._multiEdit;
- },
- /**
- * Determines the initial data for the item edited or added by this dialog
- */
- _determineInfo: function(aInfo) {
- // hidden rows
- if (aInfo && aInfo.hiddenRows)
- this._hiddenRows = aInfo.hiddenRows;
- else
- this._hiddenRows.splice(0, this._hiddenRows.length);
- // force-read-only
- this._readOnly = aInfo && aInfo.forceReadOnly;
- this._titleOverride = aInfo && aInfo.titleOverride ? aInfo.titleOverride
- : "";
- this._onPanelReady = aInfo && aInfo.onPanelReady;
- },
- _showHideRows: function() {
- var isBookmark = this._itemId != -1 &&
- this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK;
- var isQuery = false;
- if (this._uri)
- isQuery = this._uri.schemeIs("place");
- this._element("nameRow").collapsed = this._hiddenRows.indexOf("name") != -1;
- this._element("folderRow").collapsed =
- this._hiddenRows.indexOf("folderPicker") != -1 || this._readOnly;
- this._element("tagsRow").collapsed = !this._uri ||
- this._hiddenRows.indexOf("tags") != -1 || isQuery;
- // Collapse the tag selector if the item does not accept tags.
- if (!this._element("tagsSelectorRow").collapsed &&
- this._element("tagsRow").collapsed)
- this.toggleTagsSelector();
- this._element("descriptionRow").collapsed =
- this._hiddenRows.indexOf("description") != -1 || this._readOnly;
- this._element("keywordRow").collapsed = !isBookmark || this._readOnly ||
- this._hiddenRows.indexOf("keyword") != -1 || isQuery;
- this._element("locationRow").collapsed = !(this._uri && !isQuery) ||
- this._hiddenRows.indexOf("location") != -1;
- this._element("loadInSidebarCheckbox").collapsed = !isBookmark || isQuery ||
- this._readOnly || this._hiddenRows.indexOf("loadInSidebar") != -1;
- this._element("feedLocationRow").collapsed = !this._isLivemark ||
- this._hiddenRows.indexOf("feedLocation") != -1;
- this._element("siteLocationRow").collapsed = !this._isLivemark ||
- this._hiddenRows.indexOf("siteLocation") != -1;
- this._element("selectionCount").hidden = !this._multiEdit;
- },
- /**
- * Initialize the panel
- * @param aFor
- * Either a places-itemId (of a bookmark, folder or a live bookmark),
- * an array of itemIds (used for bulk tagging), or a URI object (in
- * which case, the panel would be initialized in read-only mode).
- * @param [optional] aInfo
- * JS object which stores additional info for the panel
- * initialization. The following properties may bet set:
- * * hiddenRows (Strings array): list of rows to be hidden regardless
- * of the item edited. Possible values: "title", "location",
- * "description", "keyword", "loadInSidebar", "feedLocation",
- * "siteLocation", folderPicker"
- * * forceReadOnly - set this flag to initialize the panel to its
- * read-only (view) mode even if the given item is editable.
- */
- initPanel: function(aFor, aInfo) {
- // 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);
- var aItemIdList;
- if (Array.isArray(aFor)) {
- aItemIdList = aFor;
- aFor = aItemIdList[0];
- }
- else if (this._multiEdit) {
- this._multiEdit = false;
- this._tags = [];
- this._uris = [];
- this._allTags = [];
- this._itemIds = [];
- this._element("selectionCount").hidden = true;
- }
- this._folderMenuList = this._element("folderMenuList");
- this._folderTree = this._element("folderTree");
- this._determineInfo(aInfo);
- if (aFor instanceof Ci.nsIURI) {
- this._itemId = -1;
- this._uri = aFor;
- this._readOnly = true;
- }
- else {
- this._itemId = aFor;
- // We can't store information on invalid itemIds.
- this._readOnly = this._readOnly || this._itemId == -1;
- var containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
- this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId);
- if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
- this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
- this._keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId);
- this._initTextField("keywordField", this._keyword);
- this._element("loadInSidebarCheckbox").checked =
- PlacesUtils.annotations.itemHasAnnotation(this._itemId,
- PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
- }
- else {
- this._uri = null;
- this._isLivemark = false;
- PlacesUtils.livemarks.getLivemark({id: this._itemId })
- .then(aLivemark => {
- this._isLivemark = true;
- this._initTextField("feedLocationField", aLivemark.feedURI.spec, true);
- this._initTextField("siteLocationField", aLivemark.siteURI ? aLivemark.siteURI.spec : "", true);
- this._showHideRows();
- }, () => undefined);
- }
- // folder picker
- this._initFolderMenuList(containerId);
- // description field
- this._initTextField("descriptionField",
- PlacesUIUtils.getItemDescription(this._itemId));
- }
- if (this._itemId == -1 ||
- this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
- this._isLivemark = false;
- this._initTextField("locationField", this._uri.spec);
- if (!aItemIdList) {
- var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
- this._initTextField("tagsField", tags, false);
- }
- else {
- this._multiEdit = true;
- this._allTags = [];
- this._itemIds = aItemIdList;
- for (var i = 0; i < aItemIdList.length; i++) {
- if (aItemIdList[i] instanceof Ci.nsIURI) {
- this._uris[i] = aItemIdList[i];
- this._itemIds[i] = -1;
- }
- else
- this._uris[i] = PlacesUtils.bookmarks.getBookmarkURI(this._itemIds[i]);
- this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
- }
- this._allTags = this._getCommonTags();
- this._initTextField("tagsField", this._allTags.join(", "), false);
- this._element("itemsCountText").value =
- PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
- this._itemIds.length,
- [this._itemIds.length]);
- }
- // tags selector
- this._rebuildTagsSelectorList();
- }
- // name picker
- this._initNamePicker();
-
- this._showHideRows();
- // observe changes
- if (!this._observersAdded) {
- // Single bookmarks observe any change. History entries and multiEdit
- // observe only tags changes, through bookmarks.
- if (this._itemId != -1 || this._uri || this._multiEdit)
- PlacesUtils.bookmarks.addObserver(this, false);
- this._element("namePicker").addEventListener("blur", this);
- this._element("locationField").addEventListener("blur", this);
- this._element("tagsField").addEventListener("blur", this);
- this._element("keywordField").addEventListener("blur", this);
- this._element("descriptionField").addEventListener("blur", this);
- window.addEventListener("unload", this, false);
- this._observersAdded = true;
- }
- let focusElement = () => {
- this._initialized = true;
- };
- if (this._onPanelReady) {
- this._onPanelReady(focusElement);
- } else {
- focusElement();
- }
- },
- /**
- * Finds tags that are in common among this._tags entries that track tags
- * for each selected uri.
- * The tags arrays should be kept up-to-date for this to work properly.
- *
- * @return array of common tags for the selected uris.
- */
- _getCommonTags: function() {
- return this._tags[0].filter(
- function(aTag) this._tags.every(
- function(aTags) aTags.indexOf(aTag) != -1
- ), this
- );
- },
- _initTextField: function(aTextFieldId, aValue, aReadOnly) {
- var field = this._element(aTextFieldId);
- field.readOnly = aReadOnly !== undefined ? aReadOnly : this._readOnly;
- if (field.value != aValue) {
- field.value = aValue;
- this._editorTransactionManagerClear(field);
- }
- },
- /**
- * 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(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(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 (var 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 (var 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: function(aIID) {
- if (aIID.equals(Ci.nsIDOMEventListener) ||
- aIID.equals(Ci.nsINavBookmarkObserver) ||
- aIID.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
- _element: function(aID) {
- return document.getElementById("editBMPanel_" + aID);
- },
- _editorTransactionManagerClear: function(aItem) {
- // Clear the editor's undo stack
- let transactionManager;
- try {
- transactionManager = aItem.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();
- }
- },
- _getItemStaticTitle: function() {
- if (this._titleOverride)
- return this._titleOverride;
- let title = "";
- if (this._itemId == -1) {
- title = PlacesUtils.history.getPageTitle(this._uri);
- }
- else {
- title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
- }
- return title;
- },
- _initNamePicker: function() {
- var namePicker = this._element("namePicker");
- namePicker.value = this._getItemStaticTitle();
- namePicker.readOnly = this._readOnly;
- this._editorTransactionManagerClear(namePicker);
- },
- uninitPanel: function(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) {
- if (this._itemId != -1 || this._uri || this._multiEdit)
- PlacesUtils.bookmarks.removeObserver(this);
- this._element("namePicker").removeEventListener("blur", this);
- this._element("locationField").removeEventListener("blur", this);
- this._element("tagsField").removeEventListener("blur", this);
- this._element("keywordField").removeEventListener("blur", this);
- this._element("descriptionField").removeEventListener("blur", this);
- this._observersAdded = false;
- }
- this._itemId = -1;
- this._uri = null;
- this._uris = [];
- this._tags = [];
- this._allTags = [];
- this._itemIds = [];
- this._multiEdit = false;
- this._firstEditedField = "";
- this._initialized = false;
- this._titleOverride = "";
- this._readOnly = false;
- },
- onTagsFieldBlur: function() {
- if (this._updateTags()) // if anything has changed
- this._mayUpdateFirstEditField("tagsField");
- },
- _updateTags: function() {
- if (this._multiEdit)
- return this._updateMultipleTagsForItems();
- return this._updateSingleTagForItem();
- },
- _updateSingleTagForItem: function() {
- var currentTags = PlacesUtils.tagging.getTagsForURI(this._uri);
- var tags = this._getTagsArrayFromTagField();
- if (tags.length > 0 || currentTags.length > 0) {
- var tagsToRemove = [];
- var tagsToAdd = [];
- var txns = [];
- for (var i = 0; i < currentTags.length; i++) {
- if (tags.indexOf(currentTags[i]) == -1)
- tagsToRemove.push(currentTags[i]);
- }
- for (var i = 0; i < tags.length; i++) {
- if (currentTags.indexOf(tags[i]) == -1)
- tagsToAdd.push(tags[i]);
- }
- if (tagsToRemove.length > 0) {
- let untagTxn = new PlacesUntagURITransaction(this._uri, tagsToRemove);
- txns.push(untagTxn);
- }
- if (tagsToAdd.length > 0) {
- let tagTxn = new PlacesTagURITransaction(this._uri, tagsToAdd);
- txns.push(tagTxn);
- }
- if (txns.length > 0) {
- let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
- PlacesUtils.transactionManager.doTransaction(aggregate);
- // Ensure the tagsField is in sync, clean it up from empty tags
- var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
- this._initTextField("tagsField", tags, false);
- return true;
- }
- }
- return false;
- },
- /**
- * 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: function(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._multiEdit || 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);
- },
- _updateMultipleTagsForItems: function() {
- var tags = this._getTagsArrayFromTagField();
- if (tags.length > 0 || this._allTags.length > 0) {
- var tagsToRemove = [];
- var tagsToAdd = [];
- var txns = [];
- for (var i = 0; i < this._allTags.length; i++) {
- if (tags.indexOf(this._allTags[i]) == -1)
- tagsToRemove.push(this._allTags[i]);
- }
- for (var i = 0; i < this._tags.length; i++) {
- tagsToAdd[i] = [];
- for (var j = 0; j < tags.length; j++) {
- if (this._tags[i].indexOf(tags[j]) == -1)
- tagsToAdd[i].push(tags[j]);
- }
- }
- if (tagsToAdd.length > 0) {
- for (let i = 0; i < this._uris.length; i++) {
- if (tagsToAdd[i].length > 0) {
- let tagTxn = new PlacesTagURITransaction(this._uris[i],
- tagsToAdd[i]);
- txns.push(tagTxn);
- }
- }
- }
- if (tagsToRemove.length > 0) {
- for (let i = 0; i < this._uris.length; i++) {
- let untagTxn = new PlacesUntagURITransaction(this._uris[i],
- tagsToRemove);
- txns.push(untagTxn);
- }
- }
- if (txns.length > 0) {
- let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
- PlacesUtils.transactionManager.doTransaction(aggregate);
- this._allTags = tags;
- this._tags = [];
- for (let i = 0; i < this._uris.length; i++) {
- this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
- }
- // Ensure the tagsField is in sync, clean it up from empty tags
- this._initTextField("tagsField", tags, false);
- return true;
- }
- }
- return false;
- },
- onNamePickerBlur: function() {
- if (this._itemId == -1)
- return;
- var namePicker = this._element("namePicker")
- // Here we update either the item title or its cached static title
- var newTitle = namePicker.value;
- if (!newTitle &&
- PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) == PlacesUtils.tagsFolderId) {
- // We don't allow setting an empty title for a tag, restore the old one.
- this._initNamePicker();
- }
- else if (this._getItemStaticTitle() != newTitle) {
- this._mayUpdateFirstEditField("namePicker");
- let txn = new PlacesEditItemTitleTransaction(this._itemId, newTitle);
- PlacesUtils.transactionManager.doTransaction(txn);
- }
- },
- onDescriptionFieldBlur: function() {
- var description = this._element("descriptionField").value;
- if (description != PlacesUIUtils.getItemDescription(this._itemId)) {
- var annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
- type : Ci.nsIAnnotationService.TYPE_STRING,
- flags : 0,
- value : description,
- expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
- var txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
- PlacesUtils.transactionManager.doTransaction(txn);
- }
- },
- onLocationFieldBlur: function() {
- var uri;
- try {
- uri = PlacesUIUtils.createFixedURI(this._element("locationField").value);
- }
- catch(ex) { return; }
- if (!this._uri.equals(uri)) {
- var txn = new PlacesEditBookmarkURITransaction(this._itemId, uri);
- PlacesUtils.transactionManager.doTransaction(txn);
- this._uri = uri;
- }
- },
- onKeywordFieldBlur: function() {
- let oldKeyword = this._keyword;
- let keyword = this._keyword = this._element("keywordField").value;
- if (keyword != oldKeyword) {
- let txn = new PlacesEditBookmarkKeywordTransaction(this._itemId,
- keyword,
- null,
- oldKeyword);
- PlacesUtils.transactionManager.doTransaction(txn);
- }
- },
- onLoadInSidebarCheckboxCommand:
- function() {
- let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO };
- if (this._element("loadInSidebarCheckbox").checked)
- annoObj.value = true;
- let txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
- PlacesUtils.transactionManager.doTransaction(txn);
- },
- toggleFolderTreeVisibility: function() {
- 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:
- function() {
- 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:
- function(aFolderId) {
- var menupopup = this._folderMenuList.menupopup;
- for (let i = 0; i < menupopup.childNodes.length; i++) {
- if ("folderId" in menupopup.childNodes[i] &&
- menupopup.childNodes[i].folderId == aFolderId)
- return menupopup.childNodes[i];
- }
- // 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: function(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
- var container = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
- var item = this._getFolderMenuItem(container);
- this._folderMenuList.selectedItem = item;
- // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
- // menulist right away
- setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
- return;
- }
- // Move the item
- var container = this._getFolderIdFromMenuList();
- if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) {
- var txn = new PlacesMoveItemTransaction(this._itemId,
- container,
- PlacesUtils.bookmarks.DEFAULT_INDEX);
- PlacesUtils.transactionManager.doTransaction(txn);
- // Mark the containing folder as recently-used if it isn't in the
- // static list
- if (container != PlacesUtils.unfiledBookmarksFolderId &&
- container != PlacesUtils.toolbarFolderId &&
- container != PlacesUtils.bookmarksMenuFolderId)
- this._markFolderAsRecentlyUsed(container);
- }
- // Update folder-tree selection
- var folderTreeRow = this._element("folderTreeRow");
- if (!folderTreeRow.collapsed) {
- var selectedNode = this._folderTree.selectedNode;
- if (!selectedNode ||
- PlacesUtils.getConcreteItemId(selectedNode) != container)
- this._folderTree.selectItems([container]);
- }
- },
- onFolderTreeSelect: function() {
- 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:
- function(aFolderId) {
- var txns = [];
- // Expire old unused recent folders
- var anno = this._getLastUsedAnnotationObject(false);
- while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
- var folderId = this._recentFolders.pop().folderId;
- let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno);
- txns.push(annoTxn);
- }
- // Mark folder as recently used
- anno = this._getLastUsedAnnotationObject(true);
- let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno);
- txns.push(annoTxn);
- let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns);
- PlacesUtils.transactionManager.doTransaction(aggregate);
- },
- /**
- * 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:
- function(aLastUsed) {
- var anno = { name: LAST_USED_ANNO,
- type: Ci.nsIAnnotationService.TYPE_INT32,
- flags: 0,
- value: aLastUsed ? new Date().getTime() : null,
- expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
- return anno;
- },
- _rebuildTagsSelectorList: function() {
- var tagsSelector = this._element("tagsSelector");
- var 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);
- var tagsInField = this._getTagsArrayFromTagField();
- var allTags = PlacesUtils.tagging.allTags;
- for (var i = 0; i < allTags.length; i++) {
- var tag = allTags[i];
- 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);
- 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: 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;
- 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.
- */
- _getTagsArrayFromTagField: function() {
- let tags = this._element("tagsField").value;
- return tags.trim()
- .split(/\s*,\s*/) // Split on commas and remove spaces.
- .filter(function(tag) tag.length > 0); // Kill empty tags.
- },
- newFolder: function() {
- var 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...
- var defaultLabel = this._element("newFolderButton").label;
- var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index);
- PlacesUtils.transactionManager.doTransaction(txn);
- this._folderTree.focus();
- this._folderTree.selectItems([this._lastNewItem]);
- this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
- this._folderTree.columns.getFirstColumn());
- },
- // nsIDOMEventListener
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "CheckboxStateChange":
- // Update the tags field when items are checked/unchecked in the listbox
- var tags = this._getTagsArrayFromTagField();
- if (aEvent.target.checked) {
- if (tags.indexOf(aEvent.target.label) == -1)
- 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(", ");
- this._updateTags();
- break;
- case "blur":
- let replaceFn = (str, firstLetter) => firstLetter.toUpperCase();
- let nodeName = aEvent.target.id.replace(/editBMPanel_(\w)/, replaceFn);
- this["on" + nodeName + "Blur"]();
- break;
- case "unload":
- this.uninitPanel(false);
- break;
- }
- },
- // nsINavBookmarkObserver
- onItemChanged: function(aItemId, aProperty,
- aIsAnnotationProperty, aValue,
- aLastModified, aItemType) {
- if (aProperty == "tags") {
- // Tags case is special, since they should be updated if either:
- // - the notification is for the edited bookmark
- // - the notification is for the edited history entry
- // - the notification is for one of edited uris
- let shouldUpdateTagsField = this._itemId == aItemId;
- if (this._itemId == -1 || this._multiEdit) {
- // Check if the changed uri is part of the modified ones.
- let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
- let uris = this._multiEdit ? this._uris : [this._uri];
- uris.forEach(function(aURI, aIndex) {
- if (aURI.equals(changedURI)) {
- shouldUpdateTagsField = true;
- if (this._multiEdit) {
- this._tags[aIndex] = PlacesUtils.tagging.getTagsForURI(this._uris[aIndex]);
- }
- }
- }, this);
- }
- if (shouldUpdateTagsField) {
- if (this._multiEdit) {
- this._allTags = this._getCommonTags();
- this._initTextField("tagsField", this._allTags.join(", "), false);
- }
- else {
- let tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
- this._initTextField("tagsField", tags, false);
- }
- }
- // Any tags change should be reflected in the tags selector.
- this._rebuildTagsSelectorList();
- return;
- }
- if (this._itemId != aItemId) {
- if (aProperty == "title") {
- // 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.
- var menupopup = this._folderMenuList.menupopup;
- for (let i = 0; i < menupopup.childNodes.length; i++) {
- if ("folderId" in menupopup.childNodes[i] &&
- menupopup.childNodes[i].folderId == aItemId) {
- menupopup.childNodes[i].label = aValue;
- break;
- }
- }
- }
- return;
- }
- switch (aProperty) {
- case "title":
- var namePicker = this._element("namePicker");
- if (namePicker.value != aValue) {
- namePicker.value = aValue;
- this._editorTransactionManagerClear(namePicker);
- }
- break;
- case "uri":
- var locationField = this._element("locationField");
- if (locationField.value != aValue) {
- this._uri = Cc["@mozilla.org/network/io-service;1"].
- getService(Ci.nsIIOService).
- newURI(aValue, null, null);
- this._initTextField("locationField", this._uri.spec);
- this._initNamePicker();
- this._initTextField("tagsField",
- PlacesUtils.tagging
- .getTagsForURI(this._uri).join(", "),
- false);
- this._rebuildTagsSelectorList();
- }
- break;
- case "keyword":
- this._keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId);
- this._initTextField("keywordField", this._keyword);
- break;
- case PlacesUIUtils.DESCRIPTION_ANNO:
- this._initTextField("descriptionField",
- PlacesUIUtils.getItemDescription(this._itemId));
- break;
- case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
- this._element("loadInSidebarCheckbox").checked =
- PlacesUtils.annotations.itemHasAnnotation(this._itemId,
- PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
- break;
- case PlacesUtils.LMANNO_FEEDURI:
- let feedURISpec =
- PlacesUtils.annotations.getItemAnnotation(this._itemId,
- PlacesUtils.LMANNO_FEEDURI);
- this._initTextField("feedLocationField", feedURISpec, true);
- break;
- case PlacesUtils.LMANNO_SITEURI:
- let siteURISpec = "";
- try {
- siteURISpec =
- PlacesUtils.annotations.getItemAnnotation(this._itemId,
- PlacesUtils.LMANNO_SITEURI);
- } catch (ex) {}
- this._initTextField("siteLocationField", siteURISpec, true);
- break;
- }
- },
- onItemMoved: function(aItemId, aOldParent, aOldIndex,
- aNewParent, aNewIndex, aItemType) {
- if (aItemId != this._itemId ||
- aNewParent == this._getFolderIdFromMenuList())
- return;
- var folderItem = this._getFolderMenuItem(aNewParent);
- // just setting selectItem _does not_ trigger oncommand, so we don't
- // recurse
- this._folderMenuList.selectedItem = folderItem;
- },
- onItemAdded: function(aItemId, aParentId, aIndex, aItemType,
- aURI) {
- this._lastNewItem = aItemId;
- },
- onItemRemoved: function() { },
- onBeginUpdateBatch: function() { },
- onEndUpdateBatch: function() { },
- onItemVisited: function() { },
- };
|