browser/components/places/content/editBookmarkOverlay.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/places/content/editBookmarkOverlay.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1024 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
     1.9 +const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
    1.10 +
    1.11 +var gEditItemOverlay = {
    1.12 +  _uri: null,
    1.13 +  _itemId: -1,
    1.14 +  _itemIds: [],
    1.15 +  _uris: [],
    1.16 +  _tags: [],
    1.17 +  _allTags: [],
    1.18 +  _multiEdit: false,
    1.19 +  _itemType: -1,
    1.20 +  _readOnly: false,
    1.21 +  _hiddenRows: [],
    1.22 +  _observersAdded: false,
    1.23 +  _staticFoldersListBuilt: false,
    1.24 +  _initialized: false,
    1.25 +  _titleOverride: "",
    1.26 +
    1.27 +  // the first field which was edited after this panel was initialized for
    1.28 +  // a certain item
    1.29 +  _firstEditedField: "",
    1.30 +
    1.31 +  get itemId() {
    1.32 +    return this._itemId;
    1.33 +  },
    1.34 +
    1.35 +  get uri() {
    1.36 +    return this._uri;
    1.37 +  },
    1.38 +
    1.39 +  get multiEdit() {
    1.40 +    return this._multiEdit;
    1.41 +  },
    1.42 +
    1.43 +  /**
    1.44 +   * Determines the initial data for the item edited or added by this dialog
    1.45 +   */
    1.46 +  _determineInfo: function EIO__determineInfo(aInfo) {
    1.47 +    // hidden rows
    1.48 +    if (aInfo && aInfo.hiddenRows)
    1.49 +      this._hiddenRows = aInfo.hiddenRows;
    1.50 +    else
    1.51 +      this._hiddenRows.splice(0, this._hiddenRows.length);
    1.52 +    // force-read-only
    1.53 +    this._readOnly = aInfo && aInfo.forceReadOnly;
    1.54 +    this._titleOverride = aInfo && aInfo.titleOverride ? aInfo.titleOverride
    1.55 +                                                       : "";
    1.56 +  },
    1.57 +
    1.58 +  _showHideRows: function EIO__showHideRows() {
    1.59 +    var isBookmark = this._itemId != -1 &&
    1.60 +                     this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK;
    1.61 +    var isQuery = false;
    1.62 +    if (this._uri)
    1.63 +      isQuery = this._uri.schemeIs("place");
    1.64 +
    1.65 +    this._element("nameRow").collapsed = this._hiddenRows.indexOf("name") != -1;
    1.66 +    this._element("folderRow").collapsed =
    1.67 +      this._hiddenRows.indexOf("folderPicker") != -1 || this._readOnly;
    1.68 +    this._element("tagsRow").collapsed = !this._uri ||
    1.69 +      this._hiddenRows.indexOf("tags") != -1 || isQuery;
    1.70 +    // Collapse the tag selector if the item does not accept tags.
    1.71 +    if (!this._element("tagsSelectorRow").collapsed &&
    1.72 +        this._element("tagsRow").collapsed)
    1.73 +      this.toggleTagsSelector();
    1.74 +    this._element("descriptionRow").collapsed =
    1.75 +      this._hiddenRows.indexOf("description") != -1 || this._readOnly;
    1.76 +    this._element("keywordRow").collapsed = !isBookmark || this._readOnly ||
    1.77 +      this._hiddenRows.indexOf("keyword") != -1 || isQuery;
    1.78 +    this._element("locationRow").collapsed = !(this._uri && !isQuery) ||
    1.79 +      this._hiddenRows.indexOf("location") != -1;
    1.80 +    this._element("loadInSidebarCheckbox").collapsed = !isBookmark || isQuery ||
    1.81 +      this._readOnly || this._hiddenRows.indexOf("loadInSidebar") != -1;
    1.82 +    this._element("feedLocationRow").collapsed = !this._isLivemark ||
    1.83 +      this._hiddenRows.indexOf("feedLocation") != -1;
    1.84 +    this._element("siteLocationRow").collapsed = !this._isLivemark ||
    1.85 +      this._hiddenRows.indexOf("siteLocation") != -1;
    1.86 +    this._element("selectionCount").hidden = !this._multiEdit;
    1.87 +  },
    1.88 +
    1.89 +  /**
    1.90 +   * Initialize the panel
    1.91 +   * @param aFor
    1.92 +   *        Either a places-itemId (of a bookmark, folder or a live bookmark),
    1.93 +   *        an array of itemIds (used for bulk tagging), or a URI object (in 
    1.94 +   *        which case, the panel would be initialized in read-only mode).
    1.95 +   * @param [optional] aInfo
    1.96 +   *        JS object which stores additional info for the panel
    1.97 +   *        initialization. The following properties may bet set:
    1.98 +   *        * hiddenRows (Strings array): list of rows to be hidden regardless
    1.99 +   *          of the item edited. Possible values: "title", "location",
   1.100 +   *          "description", "keyword", "loadInSidebar", "feedLocation",
   1.101 +   *          "siteLocation", folderPicker"
   1.102 +   *        * forceReadOnly - set this flag to initialize the panel to its
   1.103 +   *          read-only (view) mode even if the given item is editable.
   1.104 +   */
   1.105 +  initPanel: function EIO_initPanel(aFor, aInfo) {
   1.106 +    // For sanity ensure that the implementer has uninited the panel before
   1.107 +    // trying to init it again, or we could end up leaking due to observers.
   1.108 +    if (this._initialized)
   1.109 +      this.uninitPanel(false);
   1.110 +
   1.111 +    var aItemIdList;
   1.112 +    if (Array.isArray(aFor)) {
   1.113 +      aItemIdList = aFor;
   1.114 +      aFor = aItemIdList[0];
   1.115 +    }
   1.116 +    else if (this._multiEdit) {
   1.117 +      this._multiEdit = false;
   1.118 +      this._tags = [];
   1.119 +      this._uris = [];
   1.120 +      this._allTags = [];
   1.121 +      this._itemIds = [];
   1.122 +      this._element("selectionCount").hidden = true;
   1.123 +    }
   1.124 +
   1.125 +    this._folderMenuList = this._element("folderMenuList");
   1.126 +    this._folderTree = this._element("folderTree");
   1.127 +
   1.128 +    this._determineInfo(aInfo);
   1.129 +    if (aFor instanceof Ci.nsIURI) {
   1.130 +      this._itemId = -1;
   1.131 +      this._uri = aFor;
   1.132 +      this._readOnly = true;
   1.133 +    }
   1.134 +    else {
   1.135 +      this._itemId = aFor;
   1.136 +      // We can't store information on invalid itemIds.
   1.137 +      this._readOnly = this._readOnly || this._itemId == -1;
   1.138 +
   1.139 +      var containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
   1.140 +      this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId);
   1.141 +      if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
   1.142 +        this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
   1.143 +        this._initTextField("keywordField",
   1.144 +                            PlacesUtils.bookmarks
   1.145 +                                       .getKeywordForBookmark(this._itemId));
   1.146 +        this._element("loadInSidebarCheckbox").checked =
   1.147 +          PlacesUtils.annotations.itemHasAnnotation(this._itemId,
   1.148 +                                                    PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
   1.149 +      }
   1.150 +      else {
   1.151 +        this._uri = null;
   1.152 +        this._isLivemark = false;
   1.153 +        PlacesUtils.livemarks.getLivemark({id: this._itemId })
   1.154 +          .then(aLivemark => {
   1.155 +            this._isLivemark = true;
   1.156 +            this._initTextField("feedLocationField", aLivemark.feedURI.spec, true);
   1.157 +            this._initTextField("siteLocationField", aLivemark.siteURI ? aLivemark.siteURI.spec : "", true);
   1.158 +            this._showHideRows();
   1.159 +          }, () => undefined);
   1.160 +      }
   1.161 +
   1.162 +      // folder picker
   1.163 +      this._initFolderMenuList(containerId);
   1.164 +
   1.165 +      // description field
   1.166 +      this._initTextField("descriptionField", 
   1.167 +                          PlacesUIUtils.getItemDescription(this._itemId));
   1.168 +    }
   1.169 +
   1.170 +    if (this._itemId == -1 ||
   1.171 +        this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
   1.172 +      this._isLivemark = false;
   1.173 +
   1.174 +      this._initTextField("locationField", this._uri.spec);
   1.175 +      if (!aItemIdList) {
   1.176 +        var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
   1.177 +        this._initTextField("tagsField", tags, false);
   1.178 +      }
   1.179 +      else {
   1.180 +        this._multiEdit = true;
   1.181 +        this._allTags = [];
   1.182 +        this._itemIds = aItemIdList;
   1.183 +        for (var i = 0; i < aItemIdList.length; i++) {
   1.184 +          if (aItemIdList[i] instanceof Ci.nsIURI) {
   1.185 +            this._uris[i] = aItemIdList[i];
   1.186 +            this._itemIds[i] = -1;
   1.187 +          }
   1.188 +          else
   1.189 +            this._uris[i] = PlacesUtils.bookmarks.getBookmarkURI(this._itemIds[i]);
   1.190 +          this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
   1.191 +        }
   1.192 +        this._allTags = this._getCommonTags();
   1.193 +        this._initTextField("tagsField", this._allTags.join(", "), false);
   1.194 +        this._element("itemsCountText").value =
   1.195 +          PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
   1.196 +                                        this._itemIds.length,
   1.197 +                                        [this._itemIds.length]);
   1.198 +      }
   1.199 +
   1.200 +      // tags selector
   1.201 +      this._rebuildTagsSelectorList();
   1.202 +    }
   1.203 +
   1.204 +    // name picker
   1.205 +    this._initNamePicker();
   1.206 +    
   1.207 +    this._showHideRows();
   1.208 +
   1.209 +    // observe changes
   1.210 +    if (!this._observersAdded) {
   1.211 +      // Single bookmarks observe any change.  History entries and multiEdit
   1.212 +      // observe only tags changes, through bookmarks.
   1.213 +      if (this._itemId != -1 || this._uri || this._multiEdit)
   1.214 +        PlacesUtils.bookmarks.addObserver(this, false);
   1.215 +      window.addEventListener("unload", this, false);
   1.216 +      this._observersAdded = true;
   1.217 +    }
   1.218 +
   1.219 +    this._initialized = true;
   1.220 +  },
   1.221 +
   1.222 +  /**
   1.223 +   * Finds tags that are in common among this._tags entries that track tags
   1.224 +   * for each selected uri.
   1.225 +   * The tags arrays should be kept up-to-date for this to work properly.
   1.226 +   *
   1.227 +   * @return array of common tags for the selected uris.
   1.228 +   */
   1.229 +  _getCommonTags: function() {
   1.230 +    return this._tags[0].filter(
   1.231 +      function (aTag) this._tags.every(
   1.232 +        function (aTags) aTags.indexOf(aTag) != -1
   1.233 +      ), this
   1.234 +    );
   1.235 +  },
   1.236 +
   1.237 +  _initTextField: function(aTextFieldId, aValue, aReadOnly) {
   1.238 +    var field = this._element(aTextFieldId);
   1.239 +    field.readOnly = aReadOnly !== undefined ? aReadOnly : this._readOnly;
   1.240 +
   1.241 +    if (field.value != aValue) {
   1.242 +      field.value = aValue;
   1.243 +
   1.244 +      // clear the undo stack
   1.245 +      var editor = field.editor;
   1.246 +      if (editor)
   1.247 +        editor.transactionManager.clear();
   1.248 +    }
   1.249 +  },
   1.250 +
   1.251 +  /**
   1.252 +   * Appends a menu-item representing a bookmarks folder to a menu-popup.
   1.253 +   * @param aMenupopup
   1.254 +   *        The popup to which the menu-item should be added.
   1.255 +   * @param aFolderId
   1.256 +   *        The identifier of the bookmarks folder.
   1.257 +   * @return the new menu item.
   1.258 +   */
   1.259 +  _appendFolderItemToMenupopup:
   1.260 +  function EIO__appendFolderItemToMenuList(aMenupopup, aFolderId) {
   1.261 +    // First make sure the folders-separator is visible
   1.262 +    this._element("foldersSeparator").hidden = false;
   1.263 +
   1.264 +    var folderMenuItem = document.createElement("menuitem");
   1.265 +    var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
   1.266 +    folderMenuItem.folderId = aFolderId;
   1.267 +    folderMenuItem.setAttribute("label", folderTitle);
   1.268 +    folderMenuItem.className = "menuitem-iconic folder-icon";
   1.269 +    aMenupopup.appendChild(folderMenuItem);
   1.270 +    return folderMenuItem;
   1.271 +  },
   1.272 +
   1.273 +  _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) {
   1.274 +    // clean up first
   1.275 +    var menupopup = this._folderMenuList.menupopup;
   1.276 +    while (menupopup.childNodes.length > 6)
   1.277 +      menupopup.removeChild(menupopup.lastChild);
   1.278 +
   1.279 +    const bms = PlacesUtils.bookmarks;
   1.280 +    const annos = PlacesUtils.annotations;
   1.281 +
   1.282 +    // Build the static list
   1.283 +    var unfiledItem = this._element("unfiledRootItem");
   1.284 +    if (!this._staticFoldersListBuilt) {
   1.285 +      unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
   1.286 +      unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
   1.287 +      var bmMenuItem = this._element("bmRootItem");
   1.288 +      bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
   1.289 +      bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
   1.290 +      var toolbarItem = this._element("toolbarFolderItem");
   1.291 +      toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
   1.292 +      toolbarItem.folderId = PlacesUtils.toolbarFolderId;
   1.293 +      this._staticFoldersListBuilt = true;
   1.294 +    }
   1.295 +
   1.296 +    // List of recently used folders:
   1.297 +    var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO);
   1.298 +
   1.299 +    /**
   1.300 +     * The value of the LAST_USED_ANNO annotation is the time (in the form of
   1.301 +     * Date.getTime) at which the folder has been last used.
   1.302 +     *
   1.303 +     * First we build the annotated folders array, each item has both the
   1.304 +     * folder identifier and the time at which it was last-used by this dialog
   1.305 +     * set. Then we sort it descendingly based on the time field.
   1.306 +     */
   1.307 +    this._recentFolders = [];
   1.308 +    for (var i = 0; i < folderIds.length; i++) {
   1.309 +      var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
   1.310 +      this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed });
   1.311 +    }
   1.312 +    this._recentFolders.sort(function(a, b) {
   1.313 +      if (b.lastUsed < a.lastUsed)
   1.314 +        return -1;
   1.315 +      if (b.lastUsed > a.lastUsed)
   1.316 +        return 1;
   1.317 +      return 0;
   1.318 +    });
   1.319 +
   1.320 +    var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
   1.321 +                                 this._recentFolders.length);
   1.322 +    for (var i = 0; i < numberOfItems; i++) {
   1.323 +      this._appendFolderItemToMenupopup(menupopup,
   1.324 +                                        this._recentFolders[i].folderId);
   1.325 +    }
   1.326 +
   1.327 +    var defaultItem = this._getFolderMenuItem(aSelectedFolder);
   1.328 +    this._folderMenuList.selectedItem = defaultItem;
   1.329 +
   1.330 +    // Set a selectedIndex attribute to show special icons
   1.331 +    this._folderMenuList.setAttribute("selectedIndex",
   1.332 +                                      this._folderMenuList.selectedIndex);
   1.333 +
   1.334 +    // Hide the folders-separator if no folder is annotated as recently-used
   1.335 +    this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
   1.336 +    this._folderMenuList.disabled = this._readOnly;
   1.337 +  },
   1.338 +
   1.339 +  QueryInterface: function EIO_QueryInterface(aIID) {
   1.340 +    if (aIID.equals(Ci.nsIDOMEventListener) ||
   1.341 +        aIID.equals(Ci.nsINavBookmarkObserver) ||
   1.342 +        aIID.equals(Ci.nsISupports))
   1.343 +      return this;
   1.344 +
   1.345 +    throw Cr.NS_ERROR_NO_INTERFACE;
   1.346 +  },
   1.347 +
   1.348 +  _element: function EIO__element(aID) {
   1.349 +    return document.getElementById("editBMPanel_" + aID);
   1.350 +  },
   1.351 +
   1.352 +  _getItemStaticTitle: function EIO__getItemStaticTitle() {
   1.353 +    if (this._titleOverride)
   1.354 +      return this._titleOverride;
   1.355 +
   1.356 +    let title = "";
   1.357 +    if (this._itemId == -1) {
   1.358 +      title = PlacesUtils.history.getPageTitle(this._uri);
   1.359 +    }
   1.360 +    else {
   1.361 +      title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
   1.362 +    }
   1.363 +    return title;
   1.364 +  },
   1.365 +
   1.366 +  _initNamePicker: function EIO_initNamePicker() {
   1.367 +    var namePicker = this._element("namePicker");
   1.368 +    namePicker.value = this._getItemStaticTitle();
   1.369 +    namePicker.readOnly = this._readOnly;
   1.370 +
   1.371 +    // clear the undo stack
   1.372 +    var editor = namePicker.editor;
   1.373 +    if (editor)
   1.374 +      editor.transactionManager.clear();
   1.375 +  },
   1.376 +
   1.377 +  uninitPanel: function EIO_uninitPanel(aHideCollapsibleElements) {
   1.378 +    if (aHideCollapsibleElements) {
   1.379 +      // hide the folder tree if it was previously visible
   1.380 +      var folderTreeRow = this._element("folderTreeRow");
   1.381 +      if (!folderTreeRow.collapsed)
   1.382 +        this.toggleFolderTreeVisibility();
   1.383 +
   1.384 +      // hide the tag selector if it was previously visible
   1.385 +      var tagsSelectorRow = this._element("tagsSelectorRow");
   1.386 +      if (!tagsSelectorRow.collapsed)
   1.387 +        this.toggleTagsSelector();
   1.388 +    }
   1.389 +
   1.390 +    if (this._observersAdded) {
   1.391 +      if (this._itemId != -1 || this._uri || this._multiEdit)
   1.392 +        PlacesUtils.bookmarks.removeObserver(this);
   1.393 +
   1.394 +      this._observersAdded = false;
   1.395 +    }
   1.396 +
   1.397 +    this._itemId = -1;
   1.398 +    this._uri = null;
   1.399 +    this._uris = [];
   1.400 +    this._tags = [];
   1.401 +    this._allTags = [];
   1.402 +    this._itemIds = [];
   1.403 +    this._multiEdit = false;
   1.404 +    this._firstEditedField = "";
   1.405 +    this._initialized = false;
   1.406 +    this._titleOverride = "";
   1.407 +    this._readOnly = false;
   1.408 +  },
   1.409 +
   1.410 +  onTagsFieldBlur: function EIO_onTagsFieldBlur() {
   1.411 +    if (this._updateTags()) // if anything has changed
   1.412 +      this._mayUpdateFirstEditField("tagsField");
   1.413 +  },
   1.414 +
   1.415 +  _updateTags: function EIO__updateTags() {
   1.416 +    if (this._multiEdit)
   1.417 +      return this._updateMultipleTagsForItems();
   1.418 +    return this._updateSingleTagForItem();
   1.419 +  },
   1.420 +
   1.421 +  _updateSingleTagForItem: function EIO__updateSingleTagForItem() {
   1.422 +    var currentTags = PlacesUtils.tagging.getTagsForURI(this._uri);
   1.423 +    var tags = this._getTagsArrayFromTagField();
   1.424 +    if (tags.length > 0 || currentTags.length > 0) {
   1.425 +      var tagsToRemove = [];
   1.426 +      var tagsToAdd = [];
   1.427 +      var txns = []; 
   1.428 +      for (var i = 0; i < currentTags.length; i++) {
   1.429 +        if (tags.indexOf(currentTags[i]) == -1)
   1.430 +          tagsToRemove.push(currentTags[i]);
   1.431 +      }
   1.432 +      for (var i = 0; i < tags.length; i++) {
   1.433 +        if (currentTags.indexOf(tags[i]) == -1)
   1.434 +          tagsToAdd.push(tags[i]);
   1.435 +      }
   1.436 +
   1.437 +      if (tagsToRemove.length > 0) {
   1.438 +        let untagTxn = new PlacesUntagURITransaction(this._uri, tagsToRemove);
   1.439 +        txns.push(untagTxn);
   1.440 +      }
   1.441 +      if (tagsToAdd.length > 0) {
   1.442 +        let tagTxn = new PlacesTagURITransaction(this._uri, tagsToAdd);
   1.443 +        txns.push(tagTxn);
   1.444 +      }
   1.445 +
   1.446 +      if (txns.length > 0) {
   1.447 +        let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
   1.448 +        PlacesUtils.transactionManager.doTransaction(aggregate);
   1.449 +
   1.450 +        // Ensure the tagsField is in sync, clean it up from empty tags
   1.451 +        var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
   1.452 +        this._initTextField("tagsField", tags, false);
   1.453 +        return true;
   1.454 +      }
   1.455 +    }
   1.456 +    return false;
   1.457 +  },
   1.458 +
   1.459 +   /**
   1.460 +    * Stores the first-edit field for this dialog, if the passed-in field
   1.461 +    * is indeed the first edited field
   1.462 +    * @param aNewField
   1.463 +    *        the id of the field that may be set (without the "editBMPanel_"
   1.464 +    *        prefix)
   1.465 +    */
   1.466 +  _mayUpdateFirstEditField: function EIO__mayUpdateFirstEditField(aNewField) {
   1.467 +    // * The first-edit-field behavior is not applied in the multi-edit case
   1.468 +    // * if this._firstEditedField is already set, this is not the first field,
   1.469 +    //   so there's nothing to do
   1.470 +    if (this._multiEdit || this._firstEditedField)
   1.471 +      return;
   1.472 +
   1.473 +    this._firstEditedField = aNewField;
   1.474 +
   1.475 +    // set the pref
   1.476 +    var prefs = Cc["@mozilla.org/preferences-service;1"].
   1.477 +                getService(Ci.nsIPrefBranch);
   1.478 +    prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField);
   1.479 +  },
   1.480 +
   1.481 +  _updateMultipleTagsForItems: function EIO__updateMultipleTagsForItems() {
   1.482 +    var tags = this._getTagsArrayFromTagField();
   1.483 +    if (tags.length > 0 || this._allTags.length > 0) {
   1.484 +      var tagsToRemove = [];
   1.485 +      var tagsToAdd = [];
   1.486 +      var txns = []; 
   1.487 +      for (var i = 0; i < this._allTags.length; i++) {
   1.488 +        if (tags.indexOf(this._allTags[i]) == -1)
   1.489 +          tagsToRemove.push(this._allTags[i]);
   1.490 +      }
   1.491 +      for (var i = 0; i < this._tags.length; i++) {
   1.492 +        tagsToAdd[i] = [];
   1.493 +        for (var j = 0; j < tags.length; j++) {
   1.494 +          if (this._tags[i].indexOf(tags[j]) == -1)
   1.495 +            tagsToAdd[i].push(tags[j]);
   1.496 +        }
   1.497 +      }
   1.498 +
   1.499 +      if (tagsToAdd.length > 0) {
   1.500 +        for (let i = 0; i < this._uris.length; i++) {
   1.501 +          if (tagsToAdd[i].length > 0) {
   1.502 +            let tagTxn = new PlacesTagURITransaction(this._uris[i],
   1.503 +                                                     tagsToAdd[i]);
   1.504 +            txns.push(tagTxn);
   1.505 +          }
   1.506 +        }
   1.507 +      }
   1.508 +      if (tagsToRemove.length > 0) {
   1.509 +        for (let i = 0; i < this._uris.length; i++) {
   1.510 +          let untagTxn = new PlacesUntagURITransaction(this._uris[i],
   1.511 +                                                       tagsToRemove);
   1.512 +          txns.push(untagTxn);
   1.513 +        }
   1.514 +      }
   1.515 +
   1.516 +      if (txns.length > 0) {
   1.517 +        let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
   1.518 +        PlacesUtils.transactionManager.doTransaction(aggregate);
   1.519 +
   1.520 +        this._allTags = tags;
   1.521 +        this._tags = [];
   1.522 +        for (let i = 0; i < this._uris.length; i++) {
   1.523 +          this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
   1.524 +        }
   1.525 +
   1.526 +        // Ensure the tagsField is in sync, clean it up from empty tags
   1.527 +        this._initTextField("tagsField", tags, false);
   1.528 +        return true;
   1.529 +      }
   1.530 +    }
   1.531 +    return false;
   1.532 +  },
   1.533 +
   1.534 +  onNamePickerChange: function EIO_onNamePickerChange() {
   1.535 +    if (this._itemId == -1)
   1.536 +      return;
   1.537 +
   1.538 +    var namePicker = this._element("namePicker")
   1.539 +
   1.540 +    // Here we update either the item title or its cached static title
   1.541 +    var newTitle = namePicker.value;
   1.542 +    if (!newTitle &&
   1.543 +        PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) == PlacesUtils.tagsFolderId) {
   1.544 +      // We don't allow setting an empty title for a tag, restore the old one.
   1.545 +      this._initNamePicker();
   1.546 +    }
   1.547 +    else if (this._getItemStaticTitle() != newTitle) {
   1.548 +      this._mayUpdateFirstEditField("namePicker");
   1.549 +      let txn = new PlacesEditItemTitleTransaction(this._itemId, newTitle);
   1.550 +      PlacesUtils.transactionManager.doTransaction(txn);
   1.551 +    }
   1.552 +  },
   1.553 +
   1.554 +  onDescriptionFieldBlur: function EIO_onDescriptionFieldBlur() {
   1.555 +    var description = this._element("descriptionField").value;
   1.556 +    if (description != PlacesUIUtils.getItemDescription(this._itemId)) {
   1.557 +      var annoObj = { name   : PlacesUIUtils.DESCRIPTION_ANNO,
   1.558 +                      type   : Ci.nsIAnnotationService.TYPE_STRING,
   1.559 +                      flags  : 0,
   1.560 +                      value  : description,
   1.561 +                      expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
   1.562 +      var txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
   1.563 +      PlacesUtils.transactionManager.doTransaction(txn);
   1.564 +    }
   1.565 +  },
   1.566 +
   1.567 +  onLocationFieldBlur: function EIO_onLocationFieldBlur() {
   1.568 +    var uri;
   1.569 +    try {
   1.570 +      uri = PlacesUIUtils.createFixedURI(this._element("locationField").value);
   1.571 +    }
   1.572 +    catch(ex) { return; }
   1.573 +
   1.574 +    if (!this._uri.equals(uri)) {
   1.575 +      var txn = new PlacesEditBookmarkURITransaction(this._itemId, uri);
   1.576 +      PlacesUtils.transactionManager.doTransaction(txn);
   1.577 +      this._uri = uri;
   1.578 +    }
   1.579 +  },
   1.580 +
   1.581 +  onKeywordFieldBlur: function EIO_onKeywordFieldBlur() {
   1.582 +    var keyword = this._element("keywordField").value;
   1.583 +    if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) {
   1.584 +      var txn = new PlacesEditBookmarkKeywordTransaction(this._itemId, keyword);
   1.585 +      PlacesUtils.transactionManager.doTransaction(txn);
   1.586 +    }
   1.587 +  },
   1.588 +
   1.589 +  onLoadInSidebarCheckboxCommand:
   1.590 +  function EIO_onLoadInSidebarCheckboxCommand() {
   1.591 +    let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO };
   1.592 +    if (this._element("loadInSidebarCheckbox").checked)
   1.593 +      annoObj.value = true;
   1.594 +    let txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
   1.595 +    PlacesUtils.transactionManager.doTransaction(txn);
   1.596 +  },
   1.597 +
   1.598 +  toggleFolderTreeVisibility: function EIO_toggleFolderTreeVisibility() {
   1.599 +    var expander = this._element("foldersExpander");
   1.600 +    var folderTreeRow = this._element("folderTreeRow");
   1.601 +    if (!folderTreeRow.collapsed) {
   1.602 +      expander.className = "expander-down";
   1.603 +      expander.setAttribute("tooltiptext",
   1.604 +                            expander.getAttribute("tooltiptextdown"));
   1.605 +      folderTreeRow.collapsed = true;
   1.606 +      this._element("chooseFolderSeparator").hidden =
   1.607 +        this._element("chooseFolderMenuItem").hidden = false;
   1.608 +    }
   1.609 +    else {
   1.610 +      expander.className = "expander-up"
   1.611 +      expander.setAttribute("tooltiptext",
   1.612 +                            expander.getAttribute("tooltiptextup"));
   1.613 +      folderTreeRow.collapsed = false;
   1.614 +
   1.615 +      // XXXmano: Ideally we would only do this once, but for some odd reason,
   1.616 +      // the editable mode set on this tree, together with its collapsed state
   1.617 +      // breaks the view.
   1.618 +      const FOLDER_TREE_PLACE_URI =
   1.619 +        "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
   1.620 +        PlacesUIUtils.allBookmarksFolderId;
   1.621 +      this._folderTree.place = FOLDER_TREE_PLACE_URI;
   1.622 +
   1.623 +      this._element("chooseFolderSeparator").hidden =
   1.624 +        this._element("chooseFolderMenuItem").hidden = true;
   1.625 +      var currentFolder = this._getFolderIdFromMenuList();
   1.626 +      this._folderTree.selectItems([currentFolder]);
   1.627 +      this._folderTree.focus();
   1.628 +    }
   1.629 +  },
   1.630 +
   1.631 +  _getFolderIdFromMenuList:
   1.632 +  function EIO__getFolderIdFromMenuList() {
   1.633 +    var selectedItem = this._folderMenuList.selectedItem;
   1.634 +    NS_ASSERT("folderId" in selectedItem,
   1.635 +              "Invalid menuitem in the folders-menulist");
   1.636 +    return selectedItem.folderId;
   1.637 +  },
   1.638 +
   1.639 +  /**
   1.640 +   * Get the corresponding menu-item in the folder-menu-list for a bookmarks
   1.641 +   * folder if such an item exists. Otherwise, this creates a menu-item for the
   1.642 +   * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
   1.643 +   * the new item replaces the last menu-item.
   1.644 +   * @param aFolderId
   1.645 +   *        The identifier of the bookmarks folder.
   1.646 +   */
   1.647 +  _getFolderMenuItem:
   1.648 +  function EIO__getFolderMenuItem(aFolderId) {
   1.649 +    var menupopup = this._folderMenuList.menupopup;
   1.650 +
   1.651 +    for (let i = 0; i < menupopup.childNodes.length; i++) {
   1.652 +      if ("folderId" in menupopup.childNodes[i] &&
   1.653 +          menupopup.childNodes[i].folderId == aFolderId)
   1.654 +        return menupopup.childNodes[i];
   1.655 +    }
   1.656 +
   1.657 +    // 3 special folders + separator + folder-items-count limit
   1.658 +    if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
   1.659 +      menupopup.removeChild(menupopup.lastChild);
   1.660 +
   1.661 +    return this._appendFolderItemToMenupopup(menupopup, aFolderId);
   1.662 +  },
   1.663 +
   1.664 +  onFolderMenuListCommand: function EIO_onFolderMenuListCommand(aEvent) {
   1.665 +    // Set a selectedIndex attribute to show special icons
   1.666 +    this._folderMenuList.setAttribute("selectedIndex",
   1.667 +                                      this._folderMenuList.selectedIndex);
   1.668 +
   1.669 +    if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
   1.670 +      // reset the selection back to where it was and expand the tree
   1.671 +      // (this menu-item is hidden when the tree is already visible
   1.672 +      var container = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
   1.673 +      var item = this._getFolderMenuItem(container);
   1.674 +      this._folderMenuList.selectedItem = item;
   1.675 +      // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
   1.676 +      // menulist right away
   1.677 +      setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
   1.678 +      return;
   1.679 +    }
   1.680 +
   1.681 +    // Move the item
   1.682 +    var container = this._getFolderIdFromMenuList();
   1.683 +    if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) {
   1.684 +      var txn = new PlacesMoveItemTransaction(this._itemId, 
   1.685 +                                              container, 
   1.686 +                                              PlacesUtils.bookmarks.DEFAULT_INDEX);
   1.687 +      PlacesUtils.transactionManager.doTransaction(txn);
   1.688 +
   1.689 +      // Mark the containing folder as recently-used if it isn't in the
   1.690 +      // static list
   1.691 +      if (container != PlacesUtils.unfiledBookmarksFolderId &&
   1.692 +          container != PlacesUtils.toolbarFolderId &&
   1.693 +          container != PlacesUtils.bookmarksMenuFolderId)
   1.694 +        this._markFolderAsRecentlyUsed(container);
   1.695 +    }
   1.696 +
   1.697 +    // Update folder-tree selection
   1.698 +    var folderTreeRow = this._element("folderTreeRow");
   1.699 +    if (!folderTreeRow.collapsed) {
   1.700 +      var selectedNode = this._folderTree.selectedNode;
   1.701 +      if (!selectedNode ||
   1.702 +          PlacesUtils.getConcreteItemId(selectedNode) != container)
   1.703 +        this._folderTree.selectItems([container]);
   1.704 +    }
   1.705 +  },
   1.706 +
   1.707 +  onFolderTreeSelect: function EIO_onFolderTreeSelect() {
   1.708 +    var selectedNode = this._folderTree.selectedNode;
   1.709 +
   1.710 +    // Disable the "New Folder" button if we cannot create a new folder
   1.711 +    this._element("newFolderButton")
   1.712 +        .disabled = !this._folderTree.insertionPoint || !selectedNode;
   1.713 +
   1.714 +    if (!selectedNode)
   1.715 +      return;
   1.716 +
   1.717 +    var folderId = PlacesUtils.getConcreteItemId(selectedNode);
   1.718 +    if (this._getFolderIdFromMenuList() == folderId)
   1.719 +      return;
   1.720 +
   1.721 +    var folderItem = this._getFolderMenuItem(folderId);
   1.722 +    this._folderMenuList.selectedItem = folderItem;
   1.723 +    folderItem.doCommand();
   1.724 +  },
   1.725 +
   1.726 +  _markFolderAsRecentlyUsed:
   1.727 +  function EIO__markFolderAsRecentlyUsed(aFolderId) {
   1.728 +    var txns = [];
   1.729 +
   1.730 +    // Expire old unused recent folders
   1.731 +    var anno = this._getLastUsedAnnotationObject(false);
   1.732 +    while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
   1.733 +      var folderId = this._recentFolders.pop().folderId;
   1.734 +      let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno);
   1.735 +      txns.push(annoTxn);
   1.736 +    }
   1.737 +
   1.738 +    // Mark folder as recently used
   1.739 +    anno = this._getLastUsedAnnotationObject(true);
   1.740 +    let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno);
   1.741 +    txns.push(annoTxn);
   1.742 +
   1.743 +    let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns);
   1.744 +    PlacesUtils.transactionManager.doTransaction(aggregate);
   1.745 +  },
   1.746 +
   1.747 +  /**
   1.748 +   * Returns an object which could then be used to set/unset the
   1.749 +   * LAST_USED_ANNO annotation for a folder.
   1.750 +   *
   1.751 +   * @param aLastUsed
   1.752 +   *        Whether to set or unset the LAST_USED_ANNO annotation.
   1.753 +   * @returns an object representing the annotation which could then be used
   1.754 +   *          with the transaction manager.
   1.755 +   */
   1.756 +  _getLastUsedAnnotationObject:
   1.757 +  function EIO__getLastUsedAnnotationObject(aLastUsed) {
   1.758 +    var anno = { name: LAST_USED_ANNO,
   1.759 +                 type: Ci.nsIAnnotationService.TYPE_INT32,
   1.760 +                 flags: 0,
   1.761 +                 value: aLastUsed ? new Date().getTime() : null,
   1.762 +                 expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
   1.763 +
   1.764 +    return anno;
   1.765 +  },
   1.766 +
   1.767 +  _rebuildTagsSelectorList: function EIO__rebuildTagsSelectorList() {
   1.768 +    var tagsSelector = this._element("tagsSelector");
   1.769 +    var tagsSelectorRow = this._element("tagsSelectorRow");
   1.770 +    if (tagsSelectorRow.collapsed)
   1.771 +      return;
   1.772 +
   1.773 +    // Save the current scroll position and restore it after the rebuild.
   1.774 +    let firstIndex = tagsSelector.getIndexOfFirstVisibleRow();
   1.775 +    let selectedIndex = tagsSelector.selectedIndex;
   1.776 +    let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label
   1.777 +                                         : null;
   1.778 +
   1.779 +    while (tagsSelector.hasChildNodes())
   1.780 +      tagsSelector.removeChild(tagsSelector.lastChild);
   1.781 +
   1.782 +    var tagsInField = this._getTagsArrayFromTagField();
   1.783 +    var allTags = PlacesUtils.tagging.allTags;
   1.784 +    for (var i = 0; i < allTags.length; i++) {
   1.785 +      var tag = allTags[i];
   1.786 +      var elt = document.createElement("listitem");
   1.787 +      elt.setAttribute("type", "checkbox");
   1.788 +      elt.setAttribute("label", tag);
   1.789 +      if (tagsInField.indexOf(tag) != -1)
   1.790 +        elt.setAttribute("checked", "true");
   1.791 +      tagsSelector.appendChild(elt);
   1.792 +      if (selectedTag === tag)
   1.793 +        selectedIndex = tagsSelector.getIndexOfItem(elt);
   1.794 +    }
   1.795 +
   1.796 +    // Restore position.
   1.797 +    // The listbox allows to scroll only if the required offset doesn't
   1.798 +    // overflow its capacity, thus need to adjust the index for removals.
   1.799 +    firstIndex =
   1.800 +      Math.min(firstIndex,
   1.801 +               tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows());
   1.802 +    tagsSelector.scrollToIndex(firstIndex);
   1.803 +    if (selectedIndex >= 0 && tagsSelector.itemCount > 0) {
   1.804 +      selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1);
   1.805 +      tagsSelector.selectedIndex = selectedIndex;
   1.806 +      tagsSelector.ensureIndexIsVisible(selectedIndex);
   1.807 +    }
   1.808 +  },
   1.809 +
   1.810 +  toggleTagsSelector: function EIO_toggleTagsSelector() {
   1.811 +    var tagsSelector = this._element("tagsSelector");
   1.812 +    var tagsSelectorRow = this._element("tagsSelectorRow");
   1.813 +    var expander = this._element("tagsSelectorExpander");
   1.814 +    if (tagsSelectorRow.collapsed) {
   1.815 +      expander.className = "expander-up";
   1.816 +      expander.setAttribute("tooltiptext",
   1.817 +                            expander.getAttribute("tooltiptextup"));
   1.818 +      tagsSelectorRow.collapsed = false;
   1.819 +      this._rebuildTagsSelectorList();
   1.820 +
   1.821 +      // This is a no-op if we've added the listener.
   1.822 +      tagsSelector.addEventListener("CheckboxStateChange", this, false);
   1.823 +    }
   1.824 +    else {
   1.825 +      expander.className = "expander-down";
   1.826 +      expander.setAttribute("tooltiptext",
   1.827 +                            expander.getAttribute("tooltiptextdown"));
   1.828 +      tagsSelectorRow.collapsed = true;
   1.829 +    }
   1.830 +  },
   1.831 +
   1.832 +  /**
   1.833 +   * Splits "tagsField" element value, returning an array of valid tag strings.
   1.834 +   *
   1.835 +   * @return Array of tag strings found in the field value.
   1.836 +   */
   1.837 +  _getTagsArrayFromTagField: function EIO__getTagsArrayFromTagField() {
   1.838 +    let tags = this._element("tagsField").value;
   1.839 +    return tags.trim()
   1.840 +               .split(/\s*,\s*/) // Split on commas and remove spaces.
   1.841 +               .filter(function (tag) tag.length > 0); // Kill empty tags.
   1.842 +  },
   1.843 +
   1.844 +  newFolder: function EIO_newFolder() {
   1.845 +    var ip = this._folderTree.insertionPoint;
   1.846 +
   1.847 +    // default to the bookmarks menu folder
   1.848 +    if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
   1.849 +        ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
   1.850 +                                PlacesUtils.bookmarks.DEFAULT_INDEX,
   1.851 +                                Ci.nsITreeView.DROP_ON);
   1.852 +    }
   1.853 +
   1.854 +    // XXXmano: add a separate "New Folder" string at some point...
   1.855 +    var defaultLabel = this._element("newFolderButton").label;
   1.856 +    var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index);
   1.857 +    PlacesUtils.transactionManager.doTransaction(txn);
   1.858 +    this._folderTree.focus();
   1.859 +    this._folderTree.selectItems([ip.itemId]);
   1.860 +    PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true;
   1.861 +    this._folderTree.selectItems([this._lastNewItem]);
   1.862 +    this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
   1.863 +                                  this._folderTree.columns.getFirstColumn());
   1.864 +  },
   1.865 +
   1.866 +  // nsIDOMEventListener
   1.867 +  handleEvent: function EIO_nsIDOMEventListener(aEvent) {
   1.868 +    switch (aEvent.type) {
   1.869 +    case "CheckboxStateChange":
   1.870 +      // Update the tags field when items are checked/unchecked in the listbox
   1.871 +      var tags = this._getTagsArrayFromTagField();
   1.872 +
   1.873 +      if (aEvent.target.checked) {
   1.874 +        if (tags.indexOf(aEvent.target.label) == -1)
   1.875 +          tags.push(aEvent.target.label);
   1.876 +      }
   1.877 +      else {
   1.878 +        var indexOfItem = tags.indexOf(aEvent.target.label);
   1.879 +        if (indexOfItem != -1)
   1.880 +          tags.splice(indexOfItem, 1);
   1.881 +      }
   1.882 +      this._element("tagsField").value = tags.join(", ");
   1.883 +      this._updateTags();
   1.884 +      break;
   1.885 +    case "unload":
   1.886 +      this.uninitPanel(false);
   1.887 +      break;
   1.888 +    }
   1.889 +  },
   1.890 +
   1.891 +  // nsINavBookmarkObserver
   1.892 +  onItemChanged: function EIO_onItemChanged(aItemId, aProperty,
   1.893 +                                            aIsAnnotationProperty, aValue,
   1.894 +                                            aLastModified, aItemType) {
   1.895 +    if (aProperty == "tags") {
   1.896 +      // Tags case is special, since they should be updated if either:
   1.897 +      // - the notification is for the edited bookmark
   1.898 +      // - the notification is for the edited history entry
   1.899 +      // - the notification is for one of edited uris
   1.900 +      let shouldUpdateTagsField = this._itemId == aItemId;
   1.901 +      if (this._itemId == -1 || this._multiEdit) {
   1.902 +        // Check if the changed uri is part of the modified ones.
   1.903 +        let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
   1.904 +        let uris = this._multiEdit ? this._uris : [this._uri];
   1.905 +        uris.forEach(function (aURI, aIndex) {
   1.906 +          if (aURI.equals(changedURI)) {
   1.907 +            shouldUpdateTagsField = true;
   1.908 +            if (this._multiEdit) {
   1.909 +              this._tags[aIndex] = PlacesUtils.tagging.getTagsForURI(this._uris[aIndex]);
   1.910 +            }
   1.911 +          }
   1.912 +        }, this);
   1.913 +      }
   1.914 +
   1.915 +      if (shouldUpdateTagsField) {
   1.916 +        if (this._multiEdit) {
   1.917 +          this._allTags = this._getCommonTags();
   1.918 +          this._initTextField("tagsField", this._allTags.join(", "), false);
   1.919 +        }
   1.920 +        else {
   1.921 +          let tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
   1.922 +          this._initTextField("tagsField", tags, false);
   1.923 +        }
   1.924 +      }
   1.925 +
   1.926 +      // Any tags change should be reflected in the tags selector.
   1.927 +      this._rebuildTagsSelectorList();
   1.928 +      return;
   1.929 +    }
   1.930 +
   1.931 +    if (this._itemId != aItemId) {
   1.932 +      if (aProperty == "title") {
   1.933 +        // If the title of a folder which is listed within the folders
   1.934 +        // menulist has been changed, we need to update the label of its
   1.935 +        // representing element.
   1.936 +        var menupopup = this._folderMenuList.menupopup;
   1.937 +        for (let i = 0; i < menupopup.childNodes.length; i++) {
   1.938 +          if ("folderId" in menupopup.childNodes[i] &&
   1.939 +              menupopup.childNodes[i].folderId == aItemId) {
   1.940 +            menupopup.childNodes[i].label = aValue;
   1.941 +            break;
   1.942 +          }
   1.943 +        }
   1.944 +      }
   1.945 +
   1.946 +      return;
   1.947 +    }
   1.948 +
   1.949 +    switch (aProperty) {
   1.950 +    case "title":
   1.951 +      var namePicker = this._element("namePicker");
   1.952 +      if (namePicker.value != aValue) {
   1.953 +        namePicker.value = aValue;
   1.954 +        // clear undo stack
   1.955 +        namePicker.editor.transactionManager.clear();
   1.956 +      }
   1.957 +      break;
   1.958 +    case "uri":
   1.959 +      var locationField = this._element("locationField");
   1.960 +      if (locationField.value != aValue) {
   1.961 +        this._uri = Cc["@mozilla.org/network/io-service;1"].
   1.962 +                    getService(Ci.nsIIOService).
   1.963 +                    newURI(aValue, null, null);
   1.964 +        this._initTextField("locationField", this._uri.spec);
   1.965 +        this._initNamePicker();
   1.966 +        this._initTextField("tagsField",
   1.967 +                             PlacesUtils.tagging
   1.968 +                                        .getTagsForURI(this._uri).join(", "),
   1.969 +                            false);
   1.970 +        this._rebuildTagsSelectorList();
   1.971 +      }
   1.972 +      break;
   1.973 +    case "keyword":
   1.974 +      this._initTextField("keywordField",
   1.975 +                          PlacesUtils.bookmarks
   1.976 +                                     .getKeywordForBookmark(this._itemId));
   1.977 +      break;
   1.978 +    case PlacesUIUtils.DESCRIPTION_ANNO:
   1.979 +      this._initTextField("descriptionField",
   1.980 +                          PlacesUIUtils.getItemDescription(this._itemId));
   1.981 +      break;
   1.982 +    case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
   1.983 +      this._element("loadInSidebarCheckbox").checked =
   1.984 +        PlacesUtils.annotations.itemHasAnnotation(this._itemId,
   1.985 +                                                  PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
   1.986 +      break;
   1.987 +    case PlacesUtils.LMANNO_FEEDURI:
   1.988 +      let feedURISpec =
   1.989 +        PlacesUtils.annotations.getItemAnnotation(this._itemId,
   1.990 +                                                  PlacesUtils.LMANNO_FEEDURI);
   1.991 +      this._initTextField("feedLocationField", feedURISpec, true);
   1.992 +      break;
   1.993 +    case PlacesUtils.LMANNO_SITEURI:
   1.994 +      let siteURISpec = "";
   1.995 +      try {
   1.996 +        siteURISpec =
   1.997 +          PlacesUtils.annotations.getItemAnnotation(this._itemId,
   1.998 +                                                    PlacesUtils.LMANNO_SITEURI);
   1.999 +      } catch (ex) {}
  1.1000 +      this._initTextField("siteLocationField", siteURISpec, true);
  1.1001 +      break;
  1.1002 +    }
  1.1003 +  },
  1.1004 +
  1.1005 +  onItemMoved: function EIO_onItemMoved(aItemId, aOldParent, aOldIndex,
  1.1006 +                                        aNewParent, aNewIndex, aItemType) {
  1.1007 +    if (aItemId != this._itemId ||
  1.1008 +        aNewParent == this._getFolderIdFromMenuList())
  1.1009 +      return;
  1.1010 +
  1.1011 +    var folderItem = this._getFolderMenuItem(aNewParent);
  1.1012 +
  1.1013 +    // just setting selectItem _does not_ trigger oncommand, so we don't
  1.1014 +    // recurse
  1.1015 +    this._folderMenuList.selectedItem = folderItem;
  1.1016 +  },
  1.1017 +
  1.1018 +  onItemAdded: function EIO_onItemAdded(aItemId, aParentId, aIndex, aItemType,
  1.1019 +                                        aURI) {
  1.1020 +    this._lastNewItem = aItemId;
  1.1021 +  },
  1.1022 +
  1.1023 +  onItemRemoved: function() { },
  1.1024 +  onBeginUpdateBatch: function() { },
  1.1025 +  onEndUpdateBatch: function() { },
  1.1026 +  onItemVisited: function() { },
  1.1027 +};

mercurial