Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed"; |
michael@0 | 6 | const MAX_FOLDER_ITEM_IN_MENU_LIST = 5; |
michael@0 | 7 | |
michael@0 | 8 | var gEditItemOverlay = { |
michael@0 | 9 | _uri: null, |
michael@0 | 10 | _itemId: -1, |
michael@0 | 11 | _itemIds: [], |
michael@0 | 12 | _uris: [], |
michael@0 | 13 | _tags: [], |
michael@0 | 14 | _allTags: [], |
michael@0 | 15 | _multiEdit: false, |
michael@0 | 16 | _itemType: -1, |
michael@0 | 17 | _readOnly: false, |
michael@0 | 18 | _hiddenRows: [], |
michael@0 | 19 | _observersAdded: false, |
michael@0 | 20 | _staticFoldersListBuilt: false, |
michael@0 | 21 | _initialized: false, |
michael@0 | 22 | _titleOverride: "", |
michael@0 | 23 | |
michael@0 | 24 | // the first field which was edited after this panel was initialized for |
michael@0 | 25 | // a certain item |
michael@0 | 26 | _firstEditedField: "", |
michael@0 | 27 | |
michael@0 | 28 | get itemId() { |
michael@0 | 29 | return this._itemId; |
michael@0 | 30 | }, |
michael@0 | 31 | |
michael@0 | 32 | get uri() { |
michael@0 | 33 | return this._uri; |
michael@0 | 34 | }, |
michael@0 | 35 | |
michael@0 | 36 | get multiEdit() { |
michael@0 | 37 | return this._multiEdit; |
michael@0 | 38 | }, |
michael@0 | 39 | |
michael@0 | 40 | /** |
michael@0 | 41 | * Determines the initial data for the item edited or added by this dialog |
michael@0 | 42 | */ |
michael@0 | 43 | _determineInfo: function EIO__determineInfo(aInfo) { |
michael@0 | 44 | // hidden rows |
michael@0 | 45 | if (aInfo && aInfo.hiddenRows) |
michael@0 | 46 | this._hiddenRows = aInfo.hiddenRows; |
michael@0 | 47 | else |
michael@0 | 48 | this._hiddenRows.splice(0, this._hiddenRows.length); |
michael@0 | 49 | // force-read-only |
michael@0 | 50 | this._readOnly = aInfo && aInfo.forceReadOnly; |
michael@0 | 51 | this._titleOverride = aInfo && aInfo.titleOverride ? aInfo.titleOverride |
michael@0 | 52 | : ""; |
michael@0 | 53 | }, |
michael@0 | 54 | |
michael@0 | 55 | _showHideRows: function EIO__showHideRows() { |
michael@0 | 56 | var isBookmark = this._itemId != -1 && |
michael@0 | 57 | this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK; |
michael@0 | 58 | var isQuery = false; |
michael@0 | 59 | if (this._uri) |
michael@0 | 60 | isQuery = this._uri.schemeIs("place"); |
michael@0 | 61 | |
michael@0 | 62 | this._element("nameRow").collapsed = this._hiddenRows.indexOf("name") != -1; |
michael@0 | 63 | this._element("folderRow").collapsed = |
michael@0 | 64 | this._hiddenRows.indexOf("folderPicker") != -1 || this._readOnly; |
michael@0 | 65 | this._element("tagsRow").collapsed = !this._uri || |
michael@0 | 66 | this._hiddenRows.indexOf("tags") != -1 || isQuery; |
michael@0 | 67 | // Collapse the tag selector if the item does not accept tags. |
michael@0 | 68 | if (!this._element("tagsSelectorRow").collapsed && |
michael@0 | 69 | this._element("tagsRow").collapsed) |
michael@0 | 70 | this.toggleTagsSelector(); |
michael@0 | 71 | this._element("descriptionRow").collapsed = |
michael@0 | 72 | this._hiddenRows.indexOf("description") != -1 || this._readOnly; |
michael@0 | 73 | this._element("keywordRow").collapsed = !isBookmark || this._readOnly || |
michael@0 | 74 | this._hiddenRows.indexOf("keyword") != -1 || isQuery; |
michael@0 | 75 | this._element("locationRow").collapsed = !(this._uri && !isQuery) || |
michael@0 | 76 | this._hiddenRows.indexOf("location") != -1; |
michael@0 | 77 | this._element("loadInSidebarCheckbox").collapsed = !isBookmark || isQuery || |
michael@0 | 78 | this._readOnly || this._hiddenRows.indexOf("loadInSidebar") != -1; |
michael@0 | 79 | this._element("feedLocationRow").collapsed = !this._isLivemark || |
michael@0 | 80 | this._hiddenRows.indexOf("feedLocation") != -1; |
michael@0 | 81 | this._element("siteLocationRow").collapsed = !this._isLivemark || |
michael@0 | 82 | this._hiddenRows.indexOf("siteLocation") != -1; |
michael@0 | 83 | this._element("selectionCount").hidden = !this._multiEdit; |
michael@0 | 84 | }, |
michael@0 | 85 | |
michael@0 | 86 | /** |
michael@0 | 87 | * Initialize the panel |
michael@0 | 88 | * @param aFor |
michael@0 | 89 | * Either a places-itemId (of a bookmark, folder or a live bookmark), |
michael@0 | 90 | * an array of itemIds (used for bulk tagging), or a URI object (in |
michael@0 | 91 | * which case, the panel would be initialized in read-only mode). |
michael@0 | 92 | * @param [optional] aInfo |
michael@0 | 93 | * JS object which stores additional info for the panel |
michael@0 | 94 | * initialization. The following properties may bet set: |
michael@0 | 95 | * * hiddenRows (Strings array): list of rows to be hidden regardless |
michael@0 | 96 | * of the item edited. Possible values: "title", "location", |
michael@0 | 97 | * "description", "keyword", "loadInSidebar", "feedLocation", |
michael@0 | 98 | * "siteLocation", folderPicker" |
michael@0 | 99 | * * forceReadOnly - set this flag to initialize the panel to its |
michael@0 | 100 | * read-only (view) mode even if the given item is editable. |
michael@0 | 101 | */ |
michael@0 | 102 | initPanel: function EIO_initPanel(aFor, aInfo) { |
michael@0 | 103 | // For sanity ensure that the implementer has uninited the panel before |
michael@0 | 104 | // trying to init it again, or we could end up leaking due to observers. |
michael@0 | 105 | if (this._initialized) |
michael@0 | 106 | this.uninitPanel(false); |
michael@0 | 107 | |
michael@0 | 108 | var aItemIdList; |
michael@0 | 109 | if (Array.isArray(aFor)) { |
michael@0 | 110 | aItemIdList = aFor; |
michael@0 | 111 | aFor = aItemIdList[0]; |
michael@0 | 112 | } |
michael@0 | 113 | else if (this._multiEdit) { |
michael@0 | 114 | this._multiEdit = false; |
michael@0 | 115 | this._tags = []; |
michael@0 | 116 | this._uris = []; |
michael@0 | 117 | this._allTags = []; |
michael@0 | 118 | this._itemIds = []; |
michael@0 | 119 | this._element("selectionCount").hidden = true; |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | this._folderMenuList = this._element("folderMenuList"); |
michael@0 | 123 | this._folderTree = this._element("folderTree"); |
michael@0 | 124 | |
michael@0 | 125 | this._determineInfo(aInfo); |
michael@0 | 126 | if (aFor instanceof Ci.nsIURI) { |
michael@0 | 127 | this._itemId = -1; |
michael@0 | 128 | this._uri = aFor; |
michael@0 | 129 | this._readOnly = true; |
michael@0 | 130 | } |
michael@0 | 131 | else { |
michael@0 | 132 | this._itemId = aFor; |
michael@0 | 133 | // We can't store information on invalid itemIds. |
michael@0 | 134 | this._readOnly = this._readOnly || this._itemId == -1; |
michael@0 | 135 | |
michael@0 | 136 | var containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId); |
michael@0 | 137 | this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId); |
michael@0 | 138 | if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) { |
michael@0 | 139 | this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId); |
michael@0 | 140 | this._initTextField("keywordField", |
michael@0 | 141 | PlacesUtils.bookmarks |
michael@0 | 142 | .getKeywordForBookmark(this._itemId)); |
michael@0 | 143 | this._element("loadInSidebarCheckbox").checked = |
michael@0 | 144 | PlacesUtils.annotations.itemHasAnnotation(this._itemId, |
michael@0 | 145 | PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO); |
michael@0 | 146 | } |
michael@0 | 147 | else { |
michael@0 | 148 | this._uri = null; |
michael@0 | 149 | this._isLivemark = false; |
michael@0 | 150 | PlacesUtils.livemarks.getLivemark({id: this._itemId }) |
michael@0 | 151 | .then(aLivemark => { |
michael@0 | 152 | this._isLivemark = true; |
michael@0 | 153 | this._initTextField("feedLocationField", aLivemark.feedURI.spec, true); |
michael@0 | 154 | this._initTextField("siteLocationField", aLivemark.siteURI ? aLivemark.siteURI.spec : "", true); |
michael@0 | 155 | this._showHideRows(); |
michael@0 | 156 | }, () => undefined); |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | // folder picker |
michael@0 | 160 | this._initFolderMenuList(containerId); |
michael@0 | 161 | |
michael@0 | 162 | // description field |
michael@0 | 163 | this._initTextField("descriptionField", |
michael@0 | 164 | PlacesUIUtils.getItemDescription(this._itemId)); |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | if (this._itemId == -1 || |
michael@0 | 168 | this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) { |
michael@0 | 169 | this._isLivemark = false; |
michael@0 | 170 | |
michael@0 | 171 | this._initTextField("locationField", this._uri.spec); |
michael@0 | 172 | if (!aItemIdList) { |
michael@0 | 173 | var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", "); |
michael@0 | 174 | this._initTextField("tagsField", tags, false); |
michael@0 | 175 | } |
michael@0 | 176 | else { |
michael@0 | 177 | this._multiEdit = true; |
michael@0 | 178 | this._allTags = []; |
michael@0 | 179 | this._itemIds = aItemIdList; |
michael@0 | 180 | for (var i = 0; i < aItemIdList.length; i++) { |
michael@0 | 181 | if (aItemIdList[i] instanceof Ci.nsIURI) { |
michael@0 | 182 | this._uris[i] = aItemIdList[i]; |
michael@0 | 183 | this._itemIds[i] = -1; |
michael@0 | 184 | } |
michael@0 | 185 | else |
michael@0 | 186 | this._uris[i] = PlacesUtils.bookmarks.getBookmarkURI(this._itemIds[i]); |
michael@0 | 187 | this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]); |
michael@0 | 188 | } |
michael@0 | 189 | this._allTags = this._getCommonTags(); |
michael@0 | 190 | this._initTextField("tagsField", this._allTags.join(", "), false); |
michael@0 | 191 | this._element("itemsCountText").value = |
michael@0 | 192 | PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", |
michael@0 | 193 | this._itemIds.length, |
michael@0 | 194 | [this._itemIds.length]); |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | // tags selector |
michael@0 | 198 | this._rebuildTagsSelectorList(); |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | // name picker |
michael@0 | 202 | this._initNamePicker(); |
michael@0 | 203 | |
michael@0 | 204 | this._showHideRows(); |
michael@0 | 205 | |
michael@0 | 206 | // observe changes |
michael@0 | 207 | if (!this._observersAdded) { |
michael@0 | 208 | // Single bookmarks observe any change. History entries and multiEdit |
michael@0 | 209 | // observe only tags changes, through bookmarks. |
michael@0 | 210 | if (this._itemId != -1 || this._uri || this._multiEdit) |
michael@0 | 211 | PlacesUtils.bookmarks.addObserver(this, false); |
michael@0 | 212 | window.addEventListener("unload", this, false); |
michael@0 | 213 | this._observersAdded = true; |
michael@0 | 214 | } |
michael@0 | 215 | |
michael@0 | 216 | this._initialized = true; |
michael@0 | 217 | }, |
michael@0 | 218 | |
michael@0 | 219 | /** |
michael@0 | 220 | * Finds tags that are in common among this._tags entries that track tags |
michael@0 | 221 | * for each selected uri. |
michael@0 | 222 | * The tags arrays should be kept up-to-date for this to work properly. |
michael@0 | 223 | * |
michael@0 | 224 | * @return array of common tags for the selected uris. |
michael@0 | 225 | */ |
michael@0 | 226 | _getCommonTags: function() { |
michael@0 | 227 | return this._tags[0].filter( |
michael@0 | 228 | function (aTag) this._tags.every( |
michael@0 | 229 | function (aTags) aTags.indexOf(aTag) != -1 |
michael@0 | 230 | ), this |
michael@0 | 231 | ); |
michael@0 | 232 | }, |
michael@0 | 233 | |
michael@0 | 234 | _initTextField: function(aTextFieldId, aValue, aReadOnly) { |
michael@0 | 235 | var field = this._element(aTextFieldId); |
michael@0 | 236 | field.readOnly = aReadOnly !== undefined ? aReadOnly : this._readOnly; |
michael@0 | 237 | |
michael@0 | 238 | if (field.value != aValue) { |
michael@0 | 239 | field.value = aValue; |
michael@0 | 240 | |
michael@0 | 241 | // clear the undo stack |
michael@0 | 242 | var editor = field.editor; |
michael@0 | 243 | if (editor) |
michael@0 | 244 | editor.transactionManager.clear(); |
michael@0 | 245 | } |
michael@0 | 246 | }, |
michael@0 | 247 | |
michael@0 | 248 | /** |
michael@0 | 249 | * Appends a menu-item representing a bookmarks folder to a menu-popup. |
michael@0 | 250 | * @param aMenupopup |
michael@0 | 251 | * The popup to which the menu-item should be added. |
michael@0 | 252 | * @param aFolderId |
michael@0 | 253 | * The identifier of the bookmarks folder. |
michael@0 | 254 | * @return the new menu item. |
michael@0 | 255 | */ |
michael@0 | 256 | _appendFolderItemToMenupopup: |
michael@0 | 257 | function EIO__appendFolderItemToMenuList(aMenupopup, aFolderId) { |
michael@0 | 258 | // First make sure the folders-separator is visible |
michael@0 | 259 | this._element("foldersSeparator").hidden = false; |
michael@0 | 260 | |
michael@0 | 261 | var folderMenuItem = document.createElement("menuitem"); |
michael@0 | 262 | var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId) |
michael@0 | 263 | folderMenuItem.folderId = aFolderId; |
michael@0 | 264 | folderMenuItem.setAttribute("label", folderTitle); |
michael@0 | 265 | folderMenuItem.className = "menuitem-iconic folder-icon"; |
michael@0 | 266 | aMenupopup.appendChild(folderMenuItem); |
michael@0 | 267 | return folderMenuItem; |
michael@0 | 268 | }, |
michael@0 | 269 | |
michael@0 | 270 | _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) { |
michael@0 | 271 | // clean up first |
michael@0 | 272 | var menupopup = this._folderMenuList.menupopup; |
michael@0 | 273 | while (menupopup.childNodes.length > 6) |
michael@0 | 274 | menupopup.removeChild(menupopup.lastChild); |
michael@0 | 275 | |
michael@0 | 276 | const bms = PlacesUtils.bookmarks; |
michael@0 | 277 | const annos = PlacesUtils.annotations; |
michael@0 | 278 | |
michael@0 | 279 | // Build the static list |
michael@0 | 280 | var unfiledItem = this._element("unfiledRootItem"); |
michael@0 | 281 | if (!this._staticFoldersListBuilt) { |
michael@0 | 282 | unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId); |
michael@0 | 283 | unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId; |
michael@0 | 284 | var bmMenuItem = this._element("bmRootItem"); |
michael@0 | 285 | bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId); |
michael@0 | 286 | bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId; |
michael@0 | 287 | var toolbarItem = this._element("toolbarFolderItem"); |
michael@0 | 288 | toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId); |
michael@0 | 289 | toolbarItem.folderId = PlacesUtils.toolbarFolderId; |
michael@0 | 290 | this._staticFoldersListBuilt = true; |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | // List of recently used folders: |
michael@0 | 294 | var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO); |
michael@0 | 295 | |
michael@0 | 296 | /** |
michael@0 | 297 | * The value of the LAST_USED_ANNO annotation is the time (in the form of |
michael@0 | 298 | * Date.getTime) at which the folder has been last used. |
michael@0 | 299 | * |
michael@0 | 300 | * First we build the annotated folders array, each item has both the |
michael@0 | 301 | * folder identifier and the time at which it was last-used by this dialog |
michael@0 | 302 | * set. Then we sort it descendingly based on the time field. |
michael@0 | 303 | */ |
michael@0 | 304 | this._recentFolders = []; |
michael@0 | 305 | for (var i = 0; i < folderIds.length; i++) { |
michael@0 | 306 | var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO); |
michael@0 | 307 | this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed }); |
michael@0 | 308 | } |
michael@0 | 309 | this._recentFolders.sort(function(a, b) { |
michael@0 | 310 | if (b.lastUsed < a.lastUsed) |
michael@0 | 311 | return -1; |
michael@0 | 312 | if (b.lastUsed > a.lastUsed) |
michael@0 | 313 | return 1; |
michael@0 | 314 | return 0; |
michael@0 | 315 | }); |
michael@0 | 316 | |
michael@0 | 317 | var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST, |
michael@0 | 318 | this._recentFolders.length); |
michael@0 | 319 | for (var i = 0; i < numberOfItems; i++) { |
michael@0 | 320 | this._appendFolderItemToMenupopup(menupopup, |
michael@0 | 321 | this._recentFolders[i].folderId); |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | var defaultItem = this._getFolderMenuItem(aSelectedFolder); |
michael@0 | 325 | this._folderMenuList.selectedItem = defaultItem; |
michael@0 | 326 | |
michael@0 | 327 | // Set a selectedIndex attribute to show special icons |
michael@0 | 328 | this._folderMenuList.setAttribute("selectedIndex", |
michael@0 | 329 | this._folderMenuList.selectedIndex); |
michael@0 | 330 | |
michael@0 | 331 | // Hide the folders-separator if no folder is annotated as recently-used |
michael@0 | 332 | this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6); |
michael@0 | 333 | this._folderMenuList.disabled = this._readOnly; |
michael@0 | 334 | }, |
michael@0 | 335 | |
michael@0 | 336 | QueryInterface: function EIO_QueryInterface(aIID) { |
michael@0 | 337 | if (aIID.equals(Ci.nsIDOMEventListener) || |
michael@0 | 338 | aIID.equals(Ci.nsINavBookmarkObserver) || |
michael@0 | 339 | aIID.equals(Ci.nsISupports)) |
michael@0 | 340 | return this; |
michael@0 | 341 | |
michael@0 | 342 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 343 | }, |
michael@0 | 344 | |
michael@0 | 345 | _element: function EIO__element(aID) { |
michael@0 | 346 | return document.getElementById("editBMPanel_" + aID); |
michael@0 | 347 | }, |
michael@0 | 348 | |
michael@0 | 349 | _getItemStaticTitle: function EIO__getItemStaticTitle() { |
michael@0 | 350 | if (this._titleOverride) |
michael@0 | 351 | return this._titleOverride; |
michael@0 | 352 | |
michael@0 | 353 | let title = ""; |
michael@0 | 354 | if (this._itemId == -1) { |
michael@0 | 355 | title = PlacesUtils.history.getPageTitle(this._uri); |
michael@0 | 356 | } |
michael@0 | 357 | else { |
michael@0 | 358 | title = PlacesUtils.bookmarks.getItemTitle(this._itemId); |
michael@0 | 359 | } |
michael@0 | 360 | return title; |
michael@0 | 361 | }, |
michael@0 | 362 | |
michael@0 | 363 | _initNamePicker: function EIO_initNamePicker() { |
michael@0 | 364 | var namePicker = this._element("namePicker"); |
michael@0 | 365 | namePicker.value = this._getItemStaticTitle(); |
michael@0 | 366 | namePicker.readOnly = this._readOnly; |
michael@0 | 367 | |
michael@0 | 368 | // clear the undo stack |
michael@0 | 369 | var editor = namePicker.editor; |
michael@0 | 370 | if (editor) |
michael@0 | 371 | editor.transactionManager.clear(); |
michael@0 | 372 | }, |
michael@0 | 373 | |
michael@0 | 374 | uninitPanel: function EIO_uninitPanel(aHideCollapsibleElements) { |
michael@0 | 375 | if (aHideCollapsibleElements) { |
michael@0 | 376 | // hide the folder tree if it was previously visible |
michael@0 | 377 | var folderTreeRow = this._element("folderTreeRow"); |
michael@0 | 378 | if (!folderTreeRow.collapsed) |
michael@0 | 379 | this.toggleFolderTreeVisibility(); |
michael@0 | 380 | |
michael@0 | 381 | // hide the tag selector if it was previously visible |
michael@0 | 382 | var tagsSelectorRow = this._element("tagsSelectorRow"); |
michael@0 | 383 | if (!tagsSelectorRow.collapsed) |
michael@0 | 384 | this.toggleTagsSelector(); |
michael@0 | 385 | } |
michael@0 | 386 | |
michael@0 | 387 | if (this._observersAdded) { |
michael@0 | 388 | if (this._itemId != -1 || this._uri || this._multiEdit) |
michael@0 | 389 | PlacesUtils.bookmarks.removeObserver(this); |
michael@0 | 390 | |
michael@0 | 391 | this._observersAdded = false; |
michael@0 | 392 | } |
michael@0 | 393 | |
michael@0 | 394 | this._itemId = -1; |
michael@0 | 395 | this._uri = null; |
michael@0 | 396 | this._uris = []; |
michael@0 | 397 | this._tags = []; |
michael@0 | 398 | this._allTags = []; |
michael@0 | 399 | this._itemIds = []; |
michael@0 | 400 | this._multiEdit = false; |
michael@0 | 401 | this._firstEditedField = ""; |
michael@0 | 402 | this._initialized = false; |
michael@0 | 403 | this._titleOverride = ""; |
michael@0 | 404 | this._readOnly = false; |
michael@0 | 405 | }, |
michael@0 | 406 | |
michael@0 | 407 | onTagsFieldBlur: function EIO_onTagsFieldBlur() { |
michael@0 | 408 | if (this._updateTags()) // if anything has changed |
michael@0 | 409 | this._mayUpdateFirstEditField("tagsField"); |
michael@0 | 410 | }, |
michael@0 | 411 | |
michael@0 | 412 | _updateTags: function EIO__updateTags() { |
michael@0 | 413 | if (this._multiEdit) |
michael@0 | 414 | return this._updateMultipleTagsForItems(); |
michael@0 | 415 | return this._updateSingleTagForItem(); |
michael@0 | 416 | }, |
michael@0 | 417 | |
michael@0 | 418 | _updateSingleTagForItem: function EIO__updateSingleTagForItem() { |
michael@0 | 419 | var currentTags = PlacesUtils.tagging.getTagsForURI(this._uri); |
michael@0 | 420 | var tags = this._getTagsArrayFromTagField(); |
michael@0 | 421 | if (tags.length > 0 || currentTags.length > 0) { |
michael@0 | 422 | var tagsToRemove = []; |
michael@0 | 423 | var tagsToAdd = []; |
michael@0 | 424 | var txns = []; |
michael@0 | 425 | for (var i = 0; i < currentTags.length; i++) { |
michael@0 | 426 | if (tags.indexOf(currentTags[i]) == -1) |
michael@0 | 427 | tagsToRemove.push(currentTags[i]); |
michael@0 | 428 | } |
michael@0 | 429 | for (var i = 0; i < tags.length; i++) { |
michael@0 | 430 | if (currentTags.indexOf(tags[i]) == -1) |
michael@0 | 431 | tagsToAdd.push(tags[i]); |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | if (tagsToRemove.length > 0) { |
michael@0 | 435 | let untagTxn = new PlacesUntagURITransaction(this._uri, tagsToRemove); |
michael@0 | 436 | txns.push(untagTxn); |
michael@0 | 437 | } |
michael@0 | 438 | if (tagsToAdd.length > 0) { |
michael@0 | 439 | let tagTxn = new PlacesTagURITransaction(this._uri, tagsToAdd); |
michael@0 | 440 | txns.push(tagTxn); |
michael@0 | 441 | } |
michael@0 | 442 | |
michael@0 | 443 | if (txns.length > 0) { |
michael@0 | 444 | let aggregate = new PlacesAggregatedTransaction("Update tags", txns); |
michael@0 | 445 | PlacesUtils.transactionManager.doTransaction(aggregate); |
michael@0 | 446 | |
michael@0 | 447 | // Ensure the tagsField is in sync, clean it up from empty tags |
michael@0 | 448 | var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", "); |
michael@0 | 449 | this._initTextField("tagsField", tags, false); |
michael@0 | 450 | return true; |
michael@0 | 451 | } |
michael@0 | 452 | } |
michael@0 | 453 | return false; |
michael@0 | 454 | }, |
michael@0 | 455 | |
michael@0 | 456 | /** |
michael@0 | 457 | * Stores the first-edit field for this dialog, if the passed-in field |
michael@0 | 458 | * is indeed the first edited field |
michael@0 | 459 | * @param aNewField |
michael@0 | 460 | * the id of the field that may be set (without the "editBMPanel_" |
michael@0 | 461 | * prefix) |
michael@0 | 462 | */ |
michael@0 | 463 | _mayUpdateFirstEditField: function EIO__mayUpdateFirstEditField(aNewField) { |
michael@0 | 464 | // * The first-edit-field behavior is not applied in the multi-edit case |
michael@0 | 465 | // * if this._firstEditedField is already set, this is not the first field, |
michael@0 | 466 | // so there's nothing to do |
michael@0 | 467 | if (this._multiEdit || this._firstEditedField) |
michael@0 | 468 | return; |
michael@0 | 469 | |
michael@0 | 470 | this._firstEditedField = aNewField; |
michael@0 | 471 | |
michael@0 | 472 | // set the pref |
michael@0 | 473 | var prefs = Cc["@mozilla.org/preferences-service;1"]. |
michael@0 | 474 | getService(Ci.nsIPrefBranch); |
michael@0 | 475 | prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField); |
michael@0 | 476 | }, |
michael@0 | 477 | |
michael@0 | 478 | _updateMultipleTagsForItems: function EIO__updateMultipleTagsForItems() { |
michael@0 | 479 | var tags = this._getTagsArrayFromTagField(); |
michael@0 | 480 | if (tags.length > 0 || this._allTags.length > 0) { |
michael@0 | 481 | var tagsToRemove = []; |
michael@0 | 482 | var tagsToAdd = []; |
michael@0 | 483 | var txns = []; |
michael@0 | 484 | for (var i = 0; i < this._allTags.length; i++) { |
michael@0 | 485 | if (tags.indexOf(this._allTags[i]) == -1) |
michael@0 | 486 | tagsToRemove.push(this._allTags[i]); |
michael@0 | 487 | } |
michael@0 | 488 | for (var i = 0; i < this._tags.length; i++) { |
michael@0 | 489 | tagsToAdd[i] = []; |
michael@0 | 490 | for (var j = 0; j < tags.length; j++) { |
michael@0 | 491 | if (this._tags[i].indexOf(tags[j]) == -1) |
michael@0 | 492 | tagsToAdd[i].push(tags[j]); |
michael@0 | 493 | } |
michael@0 | 494 | } |
michael@0 | 495 | |
michael@0 | 496 | if (tagsToAdd.length > 0) { |
michael@0 | 497 | for (let i = 0; i < this._uris.length; i++) { |
michael@0 | 498 | if (tagsToAdd[i].length > 0) { |
michael@0 | 499 | let tagTxn = new PlacesTagURITransaction(this._uris[i], |
michael@0 | 500 | tagsToAdd[i]); |
michael@0 | 501 | txns.push(tagTxn); |
michael@0 | 502 | } |
michael@0 | 503 | } |
michael@0 | 504 | } |
michael@0 | 505 | if (tagsToRemove.length > 0) { |
michael@0 | 506 | for (let i = 0; i < this._uris.length; i++) { |
michael@0 | 507 | let untagTxn = new PlacesUntagURITransaction(this._uris[i], |
michael@0 | 508 | tagsToRemove); |
michael@0 | 509 | txns.push(untagTxn); |
michael@0 | 510 | } |
michael@0 | 511 | } |
michael@0 | 512 | |
michael@0 | 513 | if (txns.length > 0) { |
michael@0 | 514 | let aggregate = new PlacesAggregatedTransaction("Update tags", txns); |
michael@0 | 515 | PlacesUtils.transactionManager.doTransaction(aggregate); |
michael@0 | 516 | |
michael@0 | 517 | this._allTags = tags; |
michael@0 | 518 | this._tags = []; |
michael@0 | 519 | for (let i = 0; i < this._uris.length; i++) { |
michael@0 | 520 | this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]); |
michael@0 | 521 | } |
michael@0 | 522 | |
michael@0 | 523 | // Ensure the tagsField is in sync, clean it up from empty tags |
michael@0 | 524 | this._initTextField("tagsField", tags, false); |
michael@0 | 525 | return true; |
michael@0 | 526 | } |
michael@0 | 527 | } |
michael@0 | 528 | return false; |
michael@0 | 529 | }, |
michael@0 | 530 | |
michael@0 | 531 | onNamePickerChange: function EIO_onNamePickerChange() { |
michael@0 | 532 | if (this._itemId == -1) |
michael@0 | 533 | return; |
michael@0 | 534 | |
michael@0 | 535 | var namePicker = this._element("namePicker") |
michael@0 | 536 | |
michael@0 | 537 | // Here we update either the item title or its cached static title |
michael@0 | 538 | var newTitle = namePicker.value; |
michael@0 | 539 | if (!newTitle && |
michael@0 | 540 | PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) == PlacesUtils.tagsFolderId) { |
michael@0 | 541 | // We don't allow setting an empty title for a tag, restore the old one. |
michael@0 | 542 | this._initNamePicker(); |
michael@0 | 543 | } |
michael@0 | 544 | else if (this._getItemStaticTitle() != newTitle) { |
michael@0 | 545 | this._mayUpdateFirstEditField("namePicker"); |
michael@0 | 546 | let txn = new PlacesEditItemTitleTransaction(this._itemId, newTitle); |
michael@0 | 547 | PlacesUtils.transactionManager.doTransaction(txn); |
michael@0 | 548 | } |
michael@0 | 549 | }, |
michael@0 | 550 | |
michael@0 | 551 | onDescriptionFieldBlur: function EIO_onDescriptionFieldBlur() { |
michael@0 | 552 | var description = this._element("descriptionField").value; |
michael@0 | 553 | if (description != PlacesUIUtils.getItemDescription(this._itemId)) { |
michael@0 | 554 | var annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO, |
michael@0 | 555 | type : Ci.nsIAnnotationService.TYPE_STRING, |
michael@0 | 556 | flags : 0, |
michael@0 | 557 | value : description, |
michael@0 | 558 | expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; |
michael@0 | 559 | var txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj); |
michael@0 | 560 | PlacesUtils.transactionManager.doTransaction(txn); |
michael@0 | 561 | } |
michael@0 | 562 | }, |
michael@0 | 563 | |
michael@0 | 564 | onLocationFieldBlur: function EIO_onLocationFieldBlur() { |
michael@0 | 565 | var uri; |
michael@0 | 566 | try { |
michael@0 | 567 | uri = PlacesUIUtils.createFixedURI(this._element("locationField").value); |
michael@0 | 568 | } |
michael@0 | 569 | catch(ex) { return; } |
michael@0 | 570 | |
michael@0 | 571 | if (!this._uri.equals(uri)) { |
michael@0 | 572 | var txn = new PlacesEditBookmarkURITransaction(this._itemId, uri); |
michael@0 | 573 | PlacesUtils.transactionManager.doTransaction(txn); |
michael@0 | 574 | this._uri = uri; |
michael@0 | 575 | } |
michael@0 | 576 | }, |
michael@0 | 577 | |
michael@0 | 578 | onKeywordFieldBlur: function EIO_onKeywordFieldBlur() { |
michael@0 | 579 | var keyword = this._element("keywordField").value; |
michael@0 | 580 | if (keyword != PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId)) { |
michael@0 | 581 | var txn = new PlacesEditBookmarkKeywordTransaction(this._itemId, keyword); |
michael@0 | 582 | PlacesUtils.transactionManager.doTransaction(txn); |
michael@0 | 583 | } |
michael@0 | 584 | }, |
michael@0 | 585 | |
michael@0 | 586 | onLoadInSidebarCheckboxCommand: |
michael@0 | 587 | function EIO_onLoadInSidebarCheckboxCommand() { |
michael@0 | 588 | let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO }; |
michael@0 | 589 | if (this._element("loadInSidebarCheckbox").checked) |
michael@0 | 590 | annoObj.value = true; |
michael@0 | 591 | let txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj); |
michael@0 | 592 | PlacesUtils.transactionManager.doTransaction(txn); |
michael@0 | 593 | }, |
michael@0 | 594 | |
michael@0 | 595 | toggleFolderTreeVisibility: function EIO_toggleFolderTreeVisibility() { |
michael@0 | 596 | var expander = this._element("foldersExpander"); |
michael@0 | 597 | var folderTreeRow = this._element("folderTreeRow"); |
michael@0 | 598 | if (!folderTreeRow.collapsed) { |
michael@0 | 599 | expander.className = "expander-down"; |
michael@0 | 600 | expander.setAttribute("tooltiptext", |
michael@0 | 601 | expander.getAttribute("tooltiptextdown")); |
michael@0 | 602 | folderTreeRow.collapsed = true; |
michael@0 | 603 | this._element("chooseFolderSeparator").hidden = |
michael@0 | 604 | this._element("chooseFolderMenuItem").hidden = false; |
michael@0 | 605 | } |
michael@0 | 606 | else { |
michael@0 | 607 | expander.className = "expander-up" |
michael@0 | 608 | expander.setAttribute("tooltiptext", |
michael@0 | 609 | expander.getAttribute("tooltiptextup")); |
michael@0 | 610 | folderTreeRow.collapsed = false; |
michael@0 | 611 | |
michael@0 | 612 | // XXXmano: Ideally we would only do this once, but for some odd reason, |
michael@0 | 613 | // the editable mode set on this tree, together with its collapsed state |
michael@0 | 614 | // breaks the view. |
michael@0 | 615 | const FOLDER_TREE_PLACE_URI = |
michael@0 | 616 | "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + |
michael@0 | 617 | PlacesUIUtils.allBookmarksFolderId; |
michael@0 | 618 | this._folderTree.place = FOLDER_TREE_PLACE_URI; |
michael@0 | 619 | |
michael@0 | 620 | this._element("chooseFolderSeparator").hidden = |
michael@0 | 621 | this._element("chooseFolderMenuItem").hidden = true; |
michael@0 | 622 | var currentFolder = this._getFolderIdFromMenuList(); |
michael@0 | 623 | this._folderTree.selectItems([currentFolder]); |
michael@0 | 624 | this._folderTree.focus(); |
michael@0 | 625 | } |
michael@0 | 626 | }, |
michael@0 | 627 | |
michael@0 | 628 | _getFolderIdFromMenuList: |
michael@0 | 629 | function EIO__getFolderIdFromMenuList() { |
michael@0 | 630 | var selectedItem = this._folderMenuList.selectedItem; |
michael@0 | 631 | NS_ASSERT("folderId" in selectedItem, |
michael@0 | 632 | "Invalid menuitem in the folders-menulist"); |
michael@0 | 633 | return selectedItem.folderId; |
michael@0 | 634 | }, |
michael@0 | 635 | |
michael@0 | 636 | /** |
michael@0 | 637 | * Get the corresponding menu-item in the folder-menu-list for a bookmarks |
michael@0 | 638 | * folder if such an item exists. Otherwise, this creates a menu-item for the |
michael@0 | 639 | * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached, |
michael@0 | 640 | * the new item replaces the last menu-item. |
michael@0 | 641 | * @param aFolderId |
michael@0 | 642 | * The identifier of the bookmarks folder. |
michael@0 | 643 | */ |
michael@0 | 644 | _getFolderMenuItem: |
michael@0 | 645 | function EIO__getFolderMenuItem(aFolderId) { |
michael@0 | 646 | var menupopup = this._folderMenuList.menupopup; |
michael@0 | 647 | |
michael@0 | 648 | for (let i = 0; i < menupopup.childNodes.length; i++) { |
michael@0 | 649 | if ("folderId" in menupopup.childNodes[i] && |
michael@0 | 650 | menupopup.childNodes[i].folderId == aFolderId) |
michael@0 | 651 | return menupopup.childNodes[i]; |
michael@0 | 652 | } |
michael@0 | 653 | |
michael@0 | 654 | // 3 special folders + separator + folder-items-count limit |
michael@0 | 655 | if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST) |
michael@0 | 656 | menupopup.removeChild(menupopup.lastChild); |
michael@0 | 657 | |
michael@0 | 658 | return this._appendFolderItemToMenupopup(menupopup, aFolderId); |
michael@0 | 659 | }, |
michael@0 | 660 | |
michael@0 | 661 | onFolderMenuListCommand: function EIO_onFolderMenuListCommand(aEvent) { |
michael@0 | 662 | // Set a selectedIndex attribute to show special icons |
michael@0 | 663 | this._folderMenuList.setAttribute("selectedIndex", |
michael@0 | 664 | this._folderMenuList.selectedIndex); |
michael@0 | 665 | |
michael@0 | 666 | if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") { |
michael@0 | 667 | // reset the selection back to where it was and expand the tree |
michael@0 | 668 | // (this menu-item is hidden when the tree is already visible |
michael@0 | 669 | var container = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId); |
michael@0 | 670 | var item = this._getFolderMenuItem(container); |
michael@0 | 671 | this._folderMenuList.selectedItem = item; |
michael@0 | 672 | // XXXmano HACK: setTimeout 100, otherwise focus goes back to the |
michael@0 | 673 | // menulist right away |
michael@0 | 674 | setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this); |
michael@0 | 675 | return; |
michael@0 | 676 | } |
michael@0 | 677 | |
michael@0 | 678 | // Move the item |
michael@0 | 679 | var container = this._getFolderIdFromMenuList(); |
michael@0 | 680 | if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) { |
michael@0 | 681 | var txn = new PlacesMoveItemTransaction(this._itemId, |
michael@0 | 682 | container, |
michael@0 | 683 | PlacesUtils.bookmarks.DEFAULT_INDEX); |
michael@0 | 684 | PlacesUtils.transactionManager.doTransaction(txn); |
michael@0 | 685 | |
michael@0 | 686 | // Mark the containing folder as recently-used if it isn't in the |
michael@0 | 687 | // static list |
michael@0 | 688 | if (container != PlacesUtils.unfiledBookmarksFolderId && |
michael@0 | 689 | container != PlacesUtils.toolbarFolderId && |
michael@0 | 690 | container != PlacesUtils.bookmarksMenuFolderId) |
michael@0 | 691 | this._markFolderAsRecentlyUsed(container); |
michael@0 | 692 | } |
michael@0 | 693 | |
michael@0 | 694 | // Update folder-tree selection |
michael@0 | 695 | var folderTreeRow = this._element("folderTreeRow"); |
michael@0 | 696 | if (!folderTreeRow.collapsed) { |
michael@0 | 697 | var selectedNode = this._folderTree.selectedNode; |
michael@0 | 698 | if (!selectedNode || |
michael@0 | 699 | PlacesUtils.getConcreteItemId(selectedNode) != container) |
michael@0 | 700 | this._folderTree.selectItems([container]); |
michael@0 | 701 | } |
michael@0 | 702 | }, |
michael@0 | 703 | |
michael@0 | 704 | onFolderTreeSelect: function EIO_onFolderTreeSelect() { |
michael@0 | 705 | var selectedNode = this._folderTree.selectedNode; |
michael@0 | 706 | |
michael@0 | 707 | // Disable the "New Folder" button if we cannot create a new folder |
michael@0 | 708 | this._element("newFolderButton") |
michael@0 | 709 | .disabled = !this._folderTree.insertionPoint || !selectedNode; |
michael@0 | 710 | |
michael@0 | 711 | if (!selectedNode) |
michael@0 | 712 | return; |
michael@0 | 713 | |
michael@0 | 714 | var folderId = PlacesUtils.getConcreteItemId(selectedNode); |
michael@0 | 715 | if (this._getFolderIdFromMenuList() == folderId) |
michael@0 | 716 | return; |
michael@0 | 717 | |
michael@0 | 718 | var folderItem = this._getFolderMenuItem(folderId); |
michael@0 | 719 | this._folderMenuList.selectedItem = folderItem; |
michael@0 | 720 | folderItem.doCommand(); |
michael@0 | 721 | }, |
michael@0 | 722 | |
michael@0 | 723 | _markFolderAsRecentlyUsed: |
michael@0 | 724 | function EIO__markFolderAsRecentlyUsed(aFolderId) { |
michael@0 | 725 | var txns = []; |
michael@0 | 726 | |
michael@0 | 727 | // Expire old unused recent folders |
michael@0 | 728 | var anno = this._getLastUsedAnnotationObject(false); |
michael@0 | 729 | while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) { |
michael@0 | 730 | var folderId = this._recentFolders.pop().folderId; |
michael@0 | 731 | let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno); |
michael@0 | 732 | txns.push(annoTxn); |
michael@0 | 733 | } |
michael@0 | 734 | |
michael@0 | 735 | // Mark folder as recently used |
michael@0 | 736 | anno = this._getLastUsedAnnotationObject(true); |
michael@0 | 737 | let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno); |
michael@0 | 738 | txns.push(annoTxn); |
michael@0 | 739 | |
michael@0 | 740 | let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns); |
michael@0 | 741 | PlacesUtils.transactionManager.doTransaction(aggregate); |
michael@0 | 742 | }, |
michael@0 | 743 | |
michael@0 | 744 | /** |
michael@0 | 745 | * Returns an object which could then be used to set/unset the |
michael@0 | 746 | * LAST_USED_ANNO annotation for a folder. |
michael@0 | 747 | * |
michael@0 | 748 | * @param aLastUsed |
michael@0 | 749 | * Whether to set or unset the LAST_USED_ANNO annotation. |
michael@0 | 750 | * @returns an object representing the annotation which could then be used |
michael@0 | 751 | * with the transaction manager. |
michael@0 | 752 | */ |
michael@0 | 753 | _getLastUsedAnnotationObject: |
michael@0 | 754 | function EIO__getLastUsedAnnotationObject(aLastUsed) { |
michael@0 | 755 | var anno = { name: LAST_USED_ANNO, |
michael@0 | 756 | type: Ci.nsIAnnotationService.TYPE_INT32, |
michael@0 | 757 | flags: 0, |
michael@0 | 758 | value: aLastUsed ? new Date().getTime() : null, |
michael@0 | 759 | expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; |
michael@0 | 760 | |
michael@0 | 761 | return anno; |
michael@0 | 762 | }, |
michael@0 | 763 | |
michael@0 | 764 | _rebuildTagsSelectorList: function EIO__rebuildTagsSelectorList() { |
michael@0 | 765 | var tagsSelector = this._element("tagsSelector"); |
michael@0 | 766 | var tagsSelectorRow = this._element("tagsSelectorRow"); |
michael@0 | 767 | if (tagsSelectorRow.collapsed) |
michael@0 | 768 | return; |
michael@0 | 769 | |
michael@0 | 770 | // Save the current scroll position and restore it after the rebuild. |
michael@0 | 771 | let firstIndex = tagsSelector.getIndexOfFirstVisibleRow(); |
michael@0 | 772 | let selectedIndex = tagsSelector.selectedIndex; |
michael@0 | 773 | let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label |
michael@0 | 774 | : null; |
michael@0 | 775 | |
michael@0 | 776 | while (tagsSelector.hasChildNodes()) |
michael@0 | 777 | tagsSelector.removeChild(tagsSelector.lastChild); |
michael@0 | 778 | |
michael@0 | 779 | var tagsInField = this._getTagsArrayFromTagField(); |
michael@0 | 780 | var allTags = PlacesUtils.tagging.allTags; |
michael@0 | 781 | for (var i = 0; i < allTags.length; i++) { |
michael@0 | 782 | var tag = allTags[i]; |
michael@0 | 783 | var elt = document.createElement("listitem"); |
michael@0 | 784 | elt.setAttribute("type", "checkbox"); |
michael@0 | 785 | elt.setAttribute("label", tag); |
michael@0 | 786 | if (tagsInField.indexOf(tag) != -1) |
michael@0 | 787 | elt.setAttribute("checked", "true"); |
michael@0 | 788 | tagsSelector.appendChild(elt); |
michael@0 | 789 | if (selectedTag === tag) |
michael@0 | 790 | selectedIndex = tagsSelector.getIndexOfItem(elt); |
michael@0 | 791 | } |
michael@0 | 792 | |
michael@0 | 793 | // Restore position. |
michael@0 | 794 | // The listbox allows to scroll only if the required offset doesn't |
michael@0 | 795 | // overflow its capacity, thus need to adjust the index for removals. |
michael@0 | 796 | firstIndex = |
michael@0 | 797 | Math.min(firstIndex, |
michael@0 | 798 | tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows()); |
michael@0 | 799 | tagsSelector.scrollToIndex(firstIndex); |
michael@0 | 800 | if (selectedIndex >= 0 && tagsSelector.itemCount > 0) { |
michael@0 | 801 | selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1); |
michael@0 | 802 | tagsSelector.selectedIndex = selectedIndex; |
michael@0 | 803 | tagsSelector.ensureIndexIsVisible(selectedIndex); |
michael@0 | 804 | } |
michael@0 | 805 | }, |
michael@0 | 806 | |
michael@0 | 807 | toggleTagsSelector: function EIO_toggleTagsSelector() { |
michael@0 | 808 | var tagsSelector = this._element("tagsSelector"); |
michael@0 | 809 | var tagsSelectorRow = this._element("tagsSelectorRow"); |
michael@0 | 810 | var expander = this._element("tagsSelectorExpander"); |
michael@0 | 811 | if (tagsSelectorRow.collapsed) { |
michael@0 | 812 | expander.className = "expander-up"; |
michael@0 | 813 | expander.setAttribute("tooltiptext", |
michael@0 | 814 | expander.getAttribute("tooltiptextup")); |
michael@0 | 815 | tagsSelectorRow.collapsed = false; |
michael@0 | 816 | this._rebuildTagsSelectorList(); |
michael@0 | 817 | |
michael@0 | 818 | // This is a no-op if we've added the listener. |
michael@0 | 819 | tagsSelector.addEventListener("CheckboxStateChange", this, false); |
michael@0 | 820 | } |
michael@0 | 821 | else { |
michael@0 | 822 | expander.className = "expander-down"; |
michael@0 | 823 | expander.setAttribute("tooltiptext", |
michael@0 | 824 | expander.getAttribute("tooltiptextdown")); |
michael@0 | 825 | tagsSelectorRow.collapsed = true; |
michael@0 | 826 | } |
michael@0 | 827 | }, |
michael@0 | 828 | |
michael@0 | 829 | /** |
michael@0 | 830 | * Splits "tagsField" element value, returning an array of valid tag strings. |
michael@0 | 831 | * |
michael@0 | 832 | * @return Array of tag strings found in the field value. |
michael@0 | 833 | */ |
michael@0 | 834 | _getTagsArrayFromTagField: function EIO__getTagsArrayFromTagField() { |
michael@0 | 835 | let tags = this._element("tagsField").value; |
michael@0 | 836 | return tags.trim() |
michael@0 | 837 | .split(/\s*,\s*/) // Split on commas and remove spaces. |
michael@0 | 838 | .filter(function (tag) tag.length > 0); // Kill empty tags. |
michael@0 | 839 | }, |
michael@0 | 840 | |
michael@0 | 841 | newFolder: function EIO_newFolder() { |
michael@0 | 842 | var ip = this._folderTree.insertionPoint; |
michael@0 | 843 | |
michael@0 | 844 | // default to the bookmarks menu folder |
michael@0 | 845 | if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) { |
michael@0 | 846 | ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, |
michael@0 | 847 | PlacesUtils.bookmarks.DEFAULT_INDEX, |
michael@0 | 848 | Ci.nsITreeView.DROP_ON); |
michael@0 | 849 | } |
michael@0 | 850 | |
michael@0 | 851 | // XXXmano: add a separate "New Folder" string at some point... |
michael@0 | 852 | var defaultLabel = this._element("newFolderButton").label; |
michael@0 | 853 | var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index); |
michael@0 | 854 | PlacesUtils.transactionManager.doTransaction(txn); |
michael@0 | 855 | this._folderTree.focus(); |
michael@0 | 856 | this._folderTree.selectItems([ip.itemId]); |
michael@0 | 857 | PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true; |
michael@0 | 858 | this._folderTree.selectItems([this._lastNewItem]); |
michael@0 | 859 | this._folderTree.startEditing(this._folderTree.view.selection.currentIndex, |
michael@0 | 860 | this._folderTree.columns.getFirstColumn()); |
michael@0 | 861 | }, |
michael@0 | 862 | |
michael@0 | 863 | // nsIDOMEventListener |
michael@0 | 864 | handleEvent: function EIO_nsIDOMEventListener(aEvent) { |
michael@0 | 865 | switch (aEvent.type) { |
michael@0 | 866 | case "CheckboxStateChange": |
michael@0 | 867 | // Update the tags field when items are checked/unchecked in the listbox |
michael@0 | 868 | var tags = this._getTagsArrayFromTagField(); |
michael@0 | 869 | |
michael@0 | 870 | if (aEvent.target.checked) { |
michael@0 | 871 | if (tags.indexOf(aEvent.target.label) == -1) |
michael@0 | 872 | tags.push(aEvent.target.label); |
michael@0 | 873 | } |
michael@0 | 874 | else { |
michael@0 | 875 | var indexOfItem = tags.indexOf(aEvent.target.label); |
michael@0 | 876 | if (indexOfItem != -1) |
michael@0 | 877 | tags.splice(indexOfItem, 1); |
michael@0 | 878 | } |
michael@0 | 879 | this._element("tagsField").value = tags.join(", "); |
michael@0 | 880 | this._updateTags(); |
michael@0 | 881 | break; |
michael@0 | 882 | case "unload": |
michael@0 | 883 | this.uninitPanel(false); |
michael@0 | 884 | break; |
michael@0 | 885 | } |
michael@0 | 886 | }, |
michael@0 | 887 | |
michael@0 | 888 | // nsINavBookmarkObserver |
michael@0 | 889 | onItemChanged: function EIO_onItemChanged(aItemId, aProperty, |
michael@0 | 890 | aIsAnnotationProperty, aValue, |
michael@0 | 891 | aLastModified, aItemType) { |
michael@0 | 892 | if (aProperty == "tags") { |
michael@0 | 893 | // Tags case is special, since they should be updated if either: |
michael@0 | 894 | // - the notification is for the edited bookmark |
michael@0 | 895 | // - the notification is for the edited history entry |
michael@0 | 896 | // - the notification is for one of edited uris |
michael@0 | 897 | let shouldUpdateTagsField = this._itemId == aItemId; |
michael@0 | 898 | if (this._itemId == -1 || this._multiEdit) { |
michael@0 | 899 | // Check if the changed uri is part of the modified ones. |
michael@0 | 900 | let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId); |
michael@0 | 901 | let uris = this._multiEdit ? this._uris : [this._uri]; |
michael@0 | 902 | uris.forEach(function (aURI, aIndex) { |
michael@0 | 903 | if (aURI.equals(changedURI)) { |
michael@0 | 904 | shouldUpdateTagsField = true; |
michael@0 | 905 | if (this._multiEdit) { |
michael@0 | 906 | this._tags[aIndex] = PlacesUtils.tagging.getTagsForURI(this._uris[aIndex]); |
michael@0 | 907 | } |
michael@0 | 908 | } |
michael@0 | 909 | }, this); |
michael@0 | 910 | } |
michael@0 | 911 | |
michael@0 | 912 | if (shouldUpdateTagsField) { |
michael@0 | 913 | if (this._multiEdit) { |
michael@0 | 914 | this._allTags = this._getCommonTags(); |
michael@0 | 915 | this._initTextField("tagsField", this._allTags.join(", "), false); |
michael@0 | 916 | } |
michael@0 | 917 | else { |
michael@0 | 918 | let tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", "); |
michael@0 | 919 | this._initTextField("tagsField", tags, false); |
michael@0 | 920 | } |
michael@0 | 921 | } |
michael@0 | 922 | |
michael@0 | 923 | // Any tags change should be reflected in the tags selector. |
michael@0 | 924 | this._rebuildTagsSelectorList(); |
michael@0 | 925 | return; |
michael@0 | 926 | } |
michael@0 | 927 | |
michael@0 | 928 | if (this._itemId != aItemId) { |
michael@0 | 929 | if (aProperty == "title") { |
michael@0 | 930 | // If the title of a folder which is listed within the folders |
michael@0 | 931 | // menulist has been changed, we need to update the label of its |
michael@0 | 932 | // representing element. |
michael@0 | 933 | var menupopup = this._folderMenuList.menupopup; |
michael@0 | 934 | for (let i = 0; i < menupopup.childNodes.length; i++) { |
michael@0 | 935 | if ("folderId" in menupopup.childNodes[i] && |
michael@0 | 936 | menupopup.childNodes[i].folderId == aItemId) { |
michael@0 | 937 | menupopup.childNodes[i].label = aValue; |
michael@0 | 938 | break; |
michael@0 | 939 | } |
michael@0 | 940 | } |
michael@0 | 941 | } |
michael@0 | 942 | |
michael@0 | 943 | return; |
michael@0 | 944 | } |
michael@0 | 945 | |
michael@0 | 946 | switch (aProperty) { |
michael@0 | 947 | case "title": |
michael@0 | 948 | var namePicker = this._element("namePicker"); |
michael@0 | 949 | if (namePicker.value != aValue) { |
michael@0 | 950 | namePicker.value = aValue; |
michael@0 | 951 | // clear undo stack |
michael@0 | 952 | namePicker.editor.transactionManager.clear(); |
michael@0 | 953 | } |
michael@0 | 954 | break; |
michael@0 | 955 | case "uri": |
michael@0 | 956 | var locationField = this._element("locationField"); |
michael@0 | 957 | if (locationField.value != aValue) { |
michael@0 | 958 | this._uri = Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 959 | getService(Ci.nsIIOService). |
michael@0 | 960 | newURI(aValue, null, null); |
michael@0 | 961 | this._initTextField("locationField", this._uri.spec); |
michael@0 | 962 | this._initNamePicker(); |
michael@0 | 963 | this._initTextField("tagsField", |
michael@0 | 964 | PlacesUtils.tagging |
michael@0 | 965 | .getTagsForURI(this._uri).join(", "), |
michael@0 | 966 | false); |
michael@0 | 967 | this._rebuildTagsSelectorList(); |
michael@0 | 968 | } |
michael@0 | 969 | break; |
michael@0 | 970 | case "keyword": |
michael@0 | 971 | this._initTextField("keywordField", |
michael@0 | 972 | PlacesUtils.bookmarks |
michael@0 | 973 | .getKeywordForBookmark(this._itemId)); |
michael@0 | 974 | break; |
michael@0 | 975 | case PlacesUIUtils.DESCRIPTION_ANNO: |
michael@0 | 976 | this._initTextField("descriptionField", |
michael@0 | 977 | PlacesUIUtils.getItemDescription(this._itemId)); |
michael@0 | 978 | break; |
michael@0 | 979 | case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO: |
michael@0 | 980 | this._element("loadInSidebarCheckbox").checked = |
michael@0 | 981 | PlacesUtils.annotations.itemHasAnnotation(this._itemId, |
michael@0 | 982 | PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO); |
michael@0 | 983 | break; |
michael@0 | 984 | case PlacesUtils.LMANNO_FEEDURI: |
michael@0 | 985 | let feedURISpec = |
michael@0 | 986 | PlacesUtils.annotations.getItemAnnotation(this._itemId, |
michael@0 | 987 | PlacesUtils.LMANNO_FEEDURI); |
michael@0 | 988 | this._initTextField("feedLocationField", feedURISpec, true); |
michael@0 | 989 | break; |
michael@0 | 990 | case PlacesUtils.LMANNO_SITEURI: |
michael@0 | 991 | let siteURISpec = ""; |
michael@0 | 992 | try { |
michael@0 | 993 | siteURISpec = |
michael@0 | 994 | PlacesUtils.annotations.getItemAnnotation(this._itemId, |
michael@0 | 995 | PlacesUtils.LMANNO_SITEURI); |
michael@0 | 996 | } catch (ex) {} |
michael@0 | 997 | this._initTextField("siteLocationField", siteURISpec, true); |
michael@0 | 998 | break; |
michael@0 | 999 | } |
michael@0 | 1000 | }, |
michael@0 | 1001 | |
michael@0 | 1002 | onItemMoved: function EIO_onItemMoved(aItemId, aOldParent, aOldIndex, |
michael@0 | 1003 | aNewParent, aNewIndex, aItemType) { |
michael@0 | 1004 | if (aItemId != this._itemId || |
michael@0 | 1005 | aNewParent == this._getFolderIdFromMenuList()) |
michael@0 | 1006 | return; |
michael@0 | 1007 | |
michael@0 | 1008 | var folderItem = this._getFolderMenuItem(aNewParent); |
michael@0 | 1009 | |
michael@0 | 1010 | // just setting selectItem _does not_ trigger oncommand, so we don't |
michael@0 | 1011 | // recurse |
michael@0 | 1012 | this._folderMenuList.selectedItem = folderItem; |
michael@0 | 1013 | }, |
michael@0 | 1014 | |
michael@0 | 1015 | onItemAdded: function EIO_onItemAdded(aItemId, aParentId, aIndex, aItemType, |
michael@0 | 1016 | aURI) { |
michael@0 | 1017 | this._lastNewItem = aItemId; |
michael@0 | 1018 | }, |
michael@0 | 1019 | |
michael@0 | 1020 | onItemRemoved: function() { }, |
michael@0 | 1021 | onBeginUpdateBatch: function() { }, |
michael@0 | 1022 | onEndUpdateBatch: function() { }, |
michael@0 | 1023 | onItemVisited: function() { }, |
michael@0 | 1024 | }; |