/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** * Selects a left pane built-in item. * * @param {string} item The built-in item to select, may be one of (case sensitive): * AllBookmarks, BookmarksMenu, BookmarksToolbar, * History, Downloads, Tags, UnfiledBookmarks.
*/
selectLeftPaneBuiltIn(item) { switch (item) { case"AllBookmarks": this._places.selectItems([PlacesUtils.virtualAllBookmarksGuid]);
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true; break; case"History": this._places.selectItems([PlacesUtils.virtualHistoryGuid]);
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true; break; case"Downloads": this._places.selectItems([PlacesUtils.virtualDownloadsGuid]); break; case"Tags": this._places.selectItems([PlacesUtils.virtualTagsGuid]); break; case"BookmarksMenu": this.selectLeftPaneContainerByHierarchy([
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.bookmarks.virtualMenuGuid,
]); break; case"BookmarksToolbar": this.selectLeftPaneContainerByHierarchy([
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.bookmarks.virtualToolbarGuid,
]); break; case"UnfiledBookmarks": this.selectLeftPaneContainerByHierarchy([
PlacesUtils.virtualAllBookmarksGuid,
PlacesUtils.bookmarks.virtualUnfiledGuid,
]); break; default: thrownew Error(
`Unrecognized item ${item} passed to selectLeftPaneRootItem`
);
}
},
/** * Opens a given hierarchy in the left pane, stopping at the last reachable * container. Note: item ids should be considered deprecated. * * @param {Array | string | number} aHierarchy * A single container or an array of containers, sorted from * the outmost to the innermost in the hierarchy. Each * container may be either an item id, a Places URI string, * or a named query, like: * "BookmarksMenu", "BookmarksToolbar", "UnfiledBookmarks", "AllBookmarks".
*/
selectLeftPaneContainerByHierarchy(aHierarchy) { if (!aHierarchy) { thrownew Error("Containers hierarchy not specified");
}
let hierarchy = [].concat(aHierarchy);
let selectWasSuppressed = this._places.view.selection.selectEventsSuppressed; if (!selectWasSuppressed) { this._places.view.selection.selectEventsSuppressed = true;
} try { for (let container of hierarchy) { if (typeof container != "string") { thrownew Error("Invalid container type found: " + container);
}
try { this.selectLeftPaneBuiltIn(container);
} catch (ex) { if (container.substr(0, 6) == "place:") { this._places.selectPlaceURI(container);
} else { // Must be a guid. this._places.selectItems([container], false);
}
}
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
}
} finally { if (!selectWasSuppressed) { this._places.view.selection.selectEventsSuppressed = false;
}
}
},
init: function PO_init() { // Register the downloads view. const DOWNLOADS_QUERY = "place:transition=" +
Ci.nsINavHistoryService.TRANSITION_DOWNLOAD + "&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
if (AppConstants.platform === "macosx") { // 1. Map Edit->Find command to OrganizerCommand_find:all. Need to map // both the menuitem and the Find key.
let findMenuItem = document.getElementById("menu_find");
findMenuItem.setAttribute("command", "OrganizerCommand_find:all");
let findKey = document.getElementById("key_find");
findKey.setAttribute("command", "OrganizerCommand_find:all");
// 2. Disable some keybindings from browser.xhtml
let elements = ["cmd_handleBackspace", "cmd_handleShiftBackspace"]; for (let i = 0; i < elements.length; i++) {
document.getElementById(elements[i]).setAttribute("disabled", "true");
}
// 3. MacOS uses a <toolbarbutton> instead of a <menu>
document
.getElementById("organizeButton")
.addEventListener("popupshowing", () => {
document.getElementById("placeContent").focus();
});
}
// remove the "Edit" and "Edit Bookmark" context-menu item, we're in our own details pane
let contextMenu = document.getElementById("placesContext");
contextMenu.removeChild(document.getElementById("placesContext_show:info"));
contextMenu.removeChild(
document.getElementById("placesContext_show_bookmark:info")
);
contextMenu.removeChild(
document.getElementById("placesContext_show_folder:info")
);
let columnsContextPopup = document.getElementById("placesColumnsContext");
columnsContextPopup.addEventListener("command", event => {
ViewMenu.showHideColumn(event.target);
event.stopPropagation();
});
columnsContextPopup.addEventListener("popupshowing", event =>
ViewMenu.fillWithColumns(event, null, null, "checkbox", false)
);
if (!this._places.hasSelection) { // If no node was found for the given place: uri, just load it directly
ContentArea.currentPlace = aLocation;
} this.updateDetailsPane();
back: function PO_back() { this._forwardHistory.unshift(this.location); var historyEntry = this._backHistory.shift(); this._location = null; this.location = historyEntry;
},
forward: function PO_forward() { this._backHistory.unshift(this.location); var historyEntry = this._forwardHistory.shift(); this._location = null; this.location = historyEntry;
},
/** * Called when a place folder is selected in the left pane. * * @param resetSearchBox * true if the search box should also be reset, false otherwise. * The search box should be reset when a new folder in the left * pane is selected; the search scope and text need to be cleared in * preparation for the new folder. Note that if the user manually * resets the search box, either by clicking its reset button or by * deleting its text, this will be false.
*/
_cachedLeftPaneSelectedURI: null,
onPlaceSelected: function PO_onPlaceSelected(resetSearchBox) { // Don't change the right-hand pane contents when there's no selection. if (!this._places.hasSelection) { return;
}
let node = this._places.selectedNode;
let placeURI = node.uri;
// If either the place of the content tree in the right pane has changed or // the user cleared the search box, update the place, hide the search UI, // and update the back/forward buttons by setting location. if (ContentArea.currentPlace != placeURI || !resetSearchBox) {
ContentArea.currentPlace = placeURI; this.location = placeURI;
}
// When we invalidate a container we use suppressSelectionEvent, when it is // unset a select event is fired, in many cases the selection did not really // change, so we should check for it, and return early in such a case. Note // that we cannot return any earlier than this point, because when // !resetSearchBox, we need to update location and hide the UI as above, // even though the selection has not changed. if (placeURI == this._cachedLeftPaneSelectedURI) { return;
} this._cachedLeftPaneSelectedURI = placeURI;
// At this point, resetSearchBox is true, because the left pane selection // has changed; otherwise we would have returned earlier.
/** * Sets the search scope based on aNode's properties. * * @param {object} aNode * the node to set up scope from
*/
_setSearchScopeForNode: function PO__setScopeForNode(aNode) {
let itemGuid = aNode.bookmarkGuid;
if (
PlacesUtils.nodeIsHistoryContainer(aNode) ||
itemGuid == PlacesUtils.virtualHistoryGuid
) {
PlacesQueryBuilder.setScope("history");
} elseif (itemGuid == PlacesUtils.virtualDownloadsGuid) {
PlacesQueryBuilder.setScope("downloads");
} else { // Default to All Bookmarks for all other nodes, per bug 469437.
PlacesQueryBuilder.setScope("bookmarks");
}
},
/** * Handle clicks on the places list. * Single Left click, right click or modified click do not result in any * special action, since they're related to selection. * * @param {object} aEvent * The mouse event.
*/
onPlacesListClick: function PO_onPlacesListClick(aEvent) { // Only handle clicks on tree children. if (aEvent.target.localName != "treechildren") { return;
}
let node = this._places.selectedNode; if (node) {
let middleClick = aEvent.button == 1 && aEvent.detail == 1; if (middleClick && PlacesUtils.nodeIsContainer(node)) { // The command execution function will take care of seeing if the // selection is a folder or a different container type, and will // load its contents in tabs.
PlacesUIUtils.openMultipleLinksInTabs(node, aEvent, this._places);
}
}
},
/** * Handle focus changes on the places list and the current content view.
*/
updateDetailsPane: function PO_updateDetailsPane() { if (!ContentArea.currentViewOptions.showDetailsPane) { return;
} // _fillDetailsPane is only invoked when the activeElement is a tree, // there's no other case where we need to update the details pane. This // means it's not possible that while some input field in the panel is // focused we try to update the panel contents causing potential dataloss // of the user's input.
let view = PlacesUIUtils.getViewForNode(document.activeElement); if (view) {
let selectedNodes = view.selectedNode
? [view.selectedNode]
: view.selectedNodes; this._fillDetailsPane(selectedNodes);
}
},
/** * Handle openFlatContainer events. * * @param {object} aContainer * The node the event was dispatched on.
*/
openFlatContainer(aContainer) { if (aContainer.bookmarkGuid) {
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true; this._places.selectItems([aContainer.bookmarkGuid], false);
} elseif (PlacesUtils.nodeIsQuery(aContainer)) { this._places.selectPlaceURI(aContainer.uri);
}
},
/** * @returns {object} * Returns the options associated with the query currently loaded in the * main places pane.
*/
getCurrentOptions: function PO_getCurrentOptions() { return PlacesUtils.asQuery(ContentArea.currentView.result.root)
.queryOptions;
},
/** * Show the migration wizard for importing passwords, * cookies, history, preferences, and bookmarks.
*/
importFromBrowser: function PO_importFromBrowser() { // We pass in the type of source we're using for use in telemetry:
MigrationUtils.showMigrationWizard(window, {
entrypoint: MigrationUtils.MIGRATION_ENTRYPOINTS.PLACES,
});
},
/** * Open a file-picker and import the selected file into the bookmarks store
*/
importFromFile: function PO_importFromFile() {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) { if (aResult != Ci.nsIFilePicker.returnCancel && fp.fileURL) { var { BookmarkHTMLUtils } = ChromeUtils.importESModule( "resource://gre/modules/BookmarkHTMLUtils.sys.mjs"
);
BookmarkHTMLUtils.importFromURL(fp.fileURL.spec).catch(console.error);
}
};
/** * Populates the restore menu with the dates of the backups available.
*/
populateRestoreMenu: function PO_populateRestoreMenu() {
let restorePopup = document.getElementById("fileRestorePopup");
const dtOptions = {
dateStyle: "long",
};
let dateFormatter = new Services.intl.DateTimeFormat(undefined, dtOptions);
// Remove existing menu items. Last item is the restoreFromFile item. while (restorePopup.childNodes.length > 1) {
restorePopup.firstChild.remove();
}
(async () => {
let backupFiles = await PlacesBackups.getBackupFiles(); if (!backupFiles.length) { return;
}
// Populate menu with backups. for (let file of backupFiles) {
let fileSize = (await IOUtils.stat(file)).size;
let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
let sizeString = PlacesUtils.getFormattedString("backupFileSizeText", [
size,
unit,
]);
/** * Called when a menuitem is selected from the restore menu. * * @param {object} aMenuItem The menuitem that was selected.
*/
async onRestoreMenuItemClick(aMenuItem) {
let backupName = aMenuItem.getAttribute("value");
let backupFilePaths = await PlacesBackups.getBackupFiles(); for (let backupFilePath of backupFilePaths) { if (PathUtils.filename(backupFilePath) == backupName) {
PlacesOrganizer.restoreBookmarksFromFile(backupFilePath); break;
}
}
},
/** * Called when 'Choose File...' is selected from the restore menu. * Prompts for a file and restores bookmarks to those in the file.
*/
onRestoreBookmarksFromFile: function PO_onRestoreBookmarksFromFile() {
let backupsDir = Services.dirsvc.get("Desk", Ci.nsIFile);
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let fpCallback = aResult => { if (aResult != Ci.nsIFilePicker.returnCancel) { this.restoreBookmarksFromFile(fp.file.path);
}
};
/** * Backup bookmarks to desktop, auto-generate a filename with a date. * The file is a JSON serialization of bookmarks, tags and any annotations * of those items.
*/
backupBookmarks: function PO_backupBookmarks() {
let backupsDir = Services.dirsvc.get("Desk", Ci.nsIFile);
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let fpCallback = function fpCallback_done(aResult) { if (aResult != Ci.nsIFilePicker.returnCancel) { // There is no OS.File version of the filepicker yet (Bug 937812).
PlacesBackups.saveBookmarksToJSONFile(fp.file.path).catch(
console.error
);
}
};
_fillDetailsPane: function PO__fillDetailsPane(aNodeList) { var infoBox = document.getElementById("infoBox"); var itemsCountBox = document.getElementById("itemsCountBox");
// Make sure the infoBox UI is visible if we need to use it, we hide it // below when we don't.
infoBox.hidden = false;
itemsCountBox.hidden = true;
let selectedNode = aNodeList.length == 1 ? aNodeList[0] : null;
// Don't update the panel if it's already editing this node, unless we're // in multi-edit mode. if (
selectedNode &&
!gEditItemOverlay.multiEdit &&
((gEditItemOverlay.concreteGuid &&
gEditItemOverlay.concreteGuid ==
PlacesUtils.getConcreteItemGuid(selectedNode)) ||
(!selectedNode.bookmarkGuid &&
gEditItemOverlay.uri &&
gEditItemOverlay.uri == selectedNode.uri))
) { return;
}
// Clean up the panel before initing it again.
gEditItemOverlay.uninitPanel(false);
/** * Folders to include when searching.
*/
_folders: [],
get folders() { if (!this._folders.length) { this._folders = PlacesUtils.bookmarks.userContentRoots;
} returnthis._folders;
},
set folders(aFolders) { this._folders = aFolders;
},
/** * Run a search for the specified text, over the collection specified by * the dropdown arrow. The default is all bookmarks, but can be * localized to the active collection. * * @param {string} filterString * The text to search for.
*/
search(filterString) { var PO = PlacesOrganizer; // If the user empties the search box manually, reset it and load all // contents of the current scope. // XXX this might be to jumpy, maybe should search for "", so results // are ungrouped, and search box not reset if (filterString == "") {
PO.onPlaceSelected(false); return;
}
let currentView = ContentArea.currentView;
// Search according to the current scope, which was set by // PQB_setScope() switch (PlacesSearchBox.filterCollection) { case"bookmarks":
currentView.applyFilter(filterString, this.folders);
Glean.library.search.bookmarks.add(1); this.cumulativeBookmarkSearches++; break; case"history": {
let currentOptions = PO.getCurrentOptions(); if (
currentOptions.queryType !=
Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY
) {
let query = PlacesUtils.history.getNewQuery();
query.searchTerms = filterString;
let options = currentOptions.clone(); // Make sure we're getting uri results.
options.resultType = currentOptions.RESULTS_AS_URI;
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
options.includeHidden = true;
currentView.load([query], options);
} else {
TelemetryStopwatch.start(HISTORY_LIBRARY_SEARCH_TELEMETRY);
currentView.applyFilter(filterString, null, true);
TelemetryStopwatch.finish(HISTORY_LIBRARY_SEARCH_TELEMETRY);
Glean.library.search.history.add(1); this.cumulativeHistorySearches++;
} break;
} case"downloads": { // The new downloads view doesn't use places for searching downloads.
currentView.searchTerm = filterString; break;
} default: thrownew Error("Invalid filterCollection on search");
}
// Update the details panel
PlacesOrganizer.updateDetailsPane();
},
/** * Finds across all history, downloads or all bookmarks.
*/
findAll() { switch (this.filterCollection) { case"history":
PlacesQueryBuilder.setScope("history"); break; case"downloads":
PlacesQueryBuilder.setScope("downloads"); break; default:
PlacesQueryBuilder.setScope("bookmarks"); break;
} this.focus();
},
/** * Updates the search input placeholder to match the current collection.
*/
updatePlaceholder() {
let l10nId = ""; switch (this.filterCollection) { case"history":
l10nId = "places-search-history"; break; case"downloads":
l10nId = "places-search-downloads"; break; default:
l10nId = "places-search-bookmarks";
}
document.l10n.setAttributes(this.searchFilter, l10nId);
},
/** * Gets/sets the active collection from the dropdown menu. * * @returns {string}
*/
get filterCollection() { returnthis.searchFilter.getAttribute("collection");
},
set filterCollection(collectionName) { if (collectionName == this.filterCollection) { return;
}
/** * Set up the gray text in the search bar as the Places View loads.
*/
init() { this.updatePlaceholder();
},
/** * Gets or sets the text shown in the Places Search Box * * @returns {string}
*/
get value() { returnthis.searchFilter.value;
},
set value(value) { this.searchFilter.value = value;
},
};
function updateTelemetry(urlsOpened) {
let historyLinks = urlsOpened.filter(
link => !link.isBookmark && !PlacesUtils.nodeIsBookmark(link)
); if (!historyLinks.length) {
Services.telemetry
.getHistogramById("PLACES_LIBRARY_CUMULATIVE_BOOKMARK_SEARCHES")
.add(PlacesSearchBox.cumulativeBookmarkSearches);
// Record cumulative search count before selecting History link from Library
Services.telemetry
.getHistogramById("PLACES_LIBRARY_CUMULATIVE_HISTORY_SEARCHES")
.add(PlacesSearchBox.cumulativeHistorySearches);
/** * Functions and data for advanced query builder
*/ var PlacesQueryBuilder = {
queries: [],
queryOptions: null,
/** * Sets the search scope. This can be called when no search is active, and * in that case, when `search()` is called, `aScope` will be used. * If there is an active search, it's performed again to * update the content tree. * * @param {"bookmarks" | "downloads" | "history"} aScope * The search scope: "bookmarks", "downloads" or "history".
*/
setScope(aScope) { // Determine filterCollection, folders, and scopeButtonId based on aScope. var filterCollection; var folders = []; switch (aScope) { case"history":
filterCollection = "history"; break; case"bookmarks":
filterCollection = "bookmarks";
folders = PlacesUtils.bookmarks.userContentRoots; break; case"downloads":
filterCollection = "downloads"; break; default: thrownew Error("Invalid search scope");
}
// Update the search box. Re-search if there's an active search.
PlacesSearchBox.filterCollection = filterCollection;
PlacesSearchBox.folders = folders; var searchStr = PlacesSearchBox.searchFilter.value; if (searchStr) {
PlacesSearchBox.search(searchStr);
}
},
};
/** * Population and commands for the View Menu.
*/ var ViewMenu = {
init() {
let columnsPopup = document.querySelector("#viewColumns > menupopup");
columnsPopup.addEventListener("command", event => {
event.stopPropagation(); this.showHideColumn(event.target);
});
columnsPopup.addEventListener("popupshowing", event => this.fillWithColumns(event, null, null, "checkbox", false)
);
/** * Removes content generated previously from a menupopup. * * @param {object} popup * The popup that contains the previously generated content. * @param {string} startID * The id attribute of an element that is the start of the * dynamically generated region - remove elements after this * item only. * Must be contained by popup. Can be null (in which case the * contents of popup are removed). * @param {string} endID * The id attribute of an element that is the end of the * dynamically generated region - remove elements up to this * item only. * Must be contained by popup. Can be null (in which case all * items until the end of the popup will be removed). Ignored * if startID is null. * @returns {object|null} The element for the caller to insert new items before, * null if the caller should just append to the popup.
*/
_clean: function VM__clean(popup, startID, endID) { if (endID && !startID) { thrownew Error("meaningless to have valid endID and null startID");
} if (startID) { var startElement = document.getElementById(startID); if (startElement.parentNode != popup) { thrownew Error("startElement is not in popup");
} if (!startElement) { thrownew Error("startID does not correspond to an existing element");
} var endElement = null; if (endID) {
endElement = document.getElementById(endID); if (endElement.parentNode != popup) { thrownew Error("endElement is not in popup");
} if (!endElement) { thrownew Error("endID does not correspond to an existing element");
}
} while (startElement.nextSibling != endElement) {
popup.removeChild(startElement.nextSibling);
} return endElement;
} while (popup.hasChildNodes()) {
popup.firstChild.remove();
} returnnull;
},
/** * Fills a menupopup with a list of columns * * @param {object} event * The popupshowing event that invoked this function. * @param {string} startID * see _clean * @param {string} endID * see _clean * @param {string} type * the type of the menuitem, e.g. "radio" or "checkbox". * Can be null (no-type). * Checkboxes are checked if the column is visible. * @param {boolean} localize * If localize is true, the column label and accesskey are set * via DOM Localization. * If localize is false, the column label is used as label and * no accesskey is assigned.
*/
fillWithColumns: function VM_fillWithColumns(
event,
startID,
endID,
type,
localize
) { var popup = event.target; var pivot = this._clean(popup, startID, endID);
var content = document.getElementById("placeContent"); var columns = content.columns; for (var i = 0; i < columns.count; ++i) { var column = columns.getColumnAt(i).element; var menuitem = document.createXULElement("menuitem");
menuitem.id = "menucol_" + column.id;
menuitem.column = column; if (localize) { const l10nId = SORTBY_L10N_IDS.get(column.getAttribute("anonid"));
document.l10n.setAttributes(menuitem, l10nId);
} else { const label = column.getAttribute("label");
menuitem.setAttribute("label", label);
} if (type == "radio") {
menuitem.setAttribute("type", "radio");
menuitem.setAttribute("name", "columns"); // This column is the sort key. Its item is checked. if (column.hasAttribute("sortDirection")) {
menuitem.setAttribute("checked", "true");
}
} elseif (type == "checkbox") {
menuitem.setAttribute("type", "checkbox"); // Cannot uncheck the primary column. if (column.getAttribute("primary") == "true") {
menuitem.setAttribute("disabled", "true");
} // Items for visible columns are checked. if (!column.hidden) {
menuitem.setAttribute("checked", "true");
}
} if (pivot) {
popup.insertBefore(menuitem, pivot);
} else {
popup.appendChild(menuitem);
}
}
event.stopPropagation();
},
/** * Set up the content of the view menu. * * @param {object} event * The event that invoked this function
*/
populateSortMenu: function VM_populateSortMenu(event) { this.fillWithColumns(
event, "viewUnsorted", "directionSeparator", "radio", true
);
var sortColumn = this._getSortColumn(); var viewSortAscending = document.getElementById("viewSortAscending"); var viewSortDescending = document.getElementById("viewSortDescending"); // We need to remove an existing checked attribute because the unsorted // menu item is not rebuilt every time we open the menu like the others. var viewUnsorted = document.getElementById("viewUnsorted"); if (!sortColumn) {
viewSortAscending.removeAttribute("checked");
viewSortDescending.removeAttribute("checked");
viewUnsorted.setAttribute("checked", "true");
} elseif (sortColumn.getAttribute("sortDirection") == "ascending") {
viewSortAscending.setAttribute("checked", "true");
viewSortDescending.removeAttribute("checked");
viewUnsorted.removeAttribute("checked");
} elseif (sortColumn.getAttribute("sortDirection") == "descending") {
viewSortDescending.setAttribute("checked", "true");
viewSortAscending.removeAttribute("checked");
viewUnsorted.removeAttribute("checked");
}
},
/** * Shows/Hides a tree column. * * @param {object} element * The menuitem element for the column
*/
showHideColumn: function VM_showHideColumn(element) { var column = element.column;
var splitter = column.nextSibling; if (splitter && splitter.localName != "splitter") {
splitter = null;
}
/** * Gets the last column that was sorted. * * @returns {object|null} the currently sorted column, null if there is no sorted column.
*/
_getSortColumn: function VM__getSortColumn() { var content = document.getElementById("placeContent"); var cols = content.columns; for (var i = 0; i < cols.count; ++i) { var column = cols.getColumnAt(i).element; var sortDirection = column.getAttribute("sortDirection"); if (sortDirection == "ascending" || sortDirection == "descending") { return column;
}
} returnnull;
},
/** * Sorts the view by the specified column. * * @param {object} aColumn * The colum that is the sort key. Can be null - the * current sort column or the title column will be used. * @param {string} aDirection * The direction to sort - "ascending" or "descending". * Can be null - the last direction or descending will be used. * * If both aColumnID and aDirection are null, the view will be unsorted.
*/
setSortColumn: function VM_setSortColumn(aColumn, aDirection) { var result = document.getElementById("placeContent").result; if (!aColumn && !aDirection) {
result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; return;
}
var columnId; if (aColumn) {
columnId = aColumn.getAttribute("anonid"); if (!aDirection) {
let sortColumn = this._getSortColumn(); if (sortColumn) {
aDirection = sortColumn.getAttribute("sortDirection");
}
}
} else {
let sortColumn = this._getSortColumn();
columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title";
}
// This maps the possible values of columnId (i.e., anonid's of treecols in // placeContent) to the default sortingMode for each column. // key: Sort key in the name of one of the // nsINavHistoryQueryOptions.SORT_BY_* constants // dir: Default sort direction to use if none has been specified const colLookupTable = {
title: { key: "TITLE", dir: "ascending" },
tags: { key: "TAGS", dir: "ascending" },
url: { key: "URI", dir: "ascending" },
date: { key: "DATE", dir: "descending" },
visitCount: { key: "VISITCOUNT", dir: "descending" },
dateAdded: { key: "DATEADDED", dir: "descending" },
lastModified: { key: "LASTMODIFIED", dir: "descending" },
};
// Make sure we have a valid column. if (!colLookupTable.hasOwnProperty(columnId)) { thrownew Error("Invalid column");
}
// Use a default sort direction if none has been specified. If aDirection // is invalid, result.sortingMode will be undefined, which has the effect // of unsorting the tree.
aDirection = (aDirection || colLookupTable[columnId].dir).toUpperCase();
/** * Gets the content view to be used for loading the given query. * If a custom view was set by setContentViewForQueryString, that * view would be returned, else the default tree view is returned * * @param {string} aQueryString * a query string * @returns {object} the view to be used for loading aQueryString.
*/
getContentViewForQueryString: function CA_getContentViewForQueryString(
aQueryString
) { try { if (this._specialViews.has(aQueryString)) {
let { view, options } = this._specialViews.get(aQueryString); if (typeof view == "function") {
view = view(); this._specialViews.set(aQueryString, { view, options });
} return view;
}
} catch (ex) {
console.error(ex);
} return ContentTree.view;
},
/** * Sets a custom view to be used rather than the default places tree * whenever the given query is selected in the left pane. * * @param {string} aQueryString * a query string * @param {object} aView * Either the custom view or a function that will return the view * the first (and only) time it's called. * @param {object} [aOptions] * Object defining special options for the view. * @see ContentTree.viewOptions for supported options and default values.
*/
setContentViewForQueryString: function CA_setContentViewForQueryString(
aQueryString,
aView,
aOptions
) { if (
!aQueryString ||
(typeof aView != "object" && typeof aView != "function")
) { thrownew Error("Invalid arguments");
}
get currentView() {
let selectedPane = [...this._box.children].filter(
child => !child.hidden
)[0]; return PlacesUIUtils.getViewForNode(selectedPane);
},
set currentView(aNewView) {
let oldView = this.currentView; if (oldView != aNewView) {
oldView.associatedElement.hidden = true;
aNewView.associatedElement.hidden = false;
// If the content area inactivated view was focused, move focus // to the new view. if (document.activeElement == oldView.associatedElement) {
aNewView.associatedElement.focus();
}
}
},
get currentPlace() { returnthis.currentView.place;
},
set currentPlace(aQueryString) {
let oldView = this.currentView;
let newView = this.getContentViewForQueryString(aQueryString);
newView.place = aQueryString; if (oldView != newView) {
oldView.active = false; this.currentView = newView; this._setupView();
newView.active = true;
}
},
/** * Applies view options.
*/
_setupView: function CA__setupView() {
let options = this.currentViewOptions;
// showDetailsPane.
let detailsPane = document.getElementById("detailsPane");
detailsPane.hidden = !options.showDetailsPane;
// toolbarSet. for (let elt of this._toolbar.childNodes) { // On Windows and Linux the menu buttons are menus wrapped in a menubar. if (elt.id == "placesMenu") { for (let menuElt of elt.childNodes) {
menuElt.hidden = !options.toolbarSet.includes(menuElt.id);
}
} else {
elt.hidden = !options.toolbarSet.includes(elt.id);
}
}
},
/** * Options for the current view. * * @see {@link ContentTree.viewOptions} for supported options and default values. * @returns {{showDetailsPane: boolean;toolbarSet: string;}}
*/
get currentViewOptions() { // Use ContentTree options as default.
let viewOptions = ContentTree.viewOptions; if (this._specialViews.has(this.currentPlace)) {
let { options } = this._specialViews.get(this.currentPlace); for (let option in options) {
viewOptions[option] = options[option];
}
} return viewOptions;
},
onClick: function CT_onClick(aEvent) {
let node = this.view.selectedNode; if (node) {
let doubleClick = aEvent.button == 0 && aEvent.detail == 2;
let middleClick = aEvent.button == 1 && aEvent.detail == 1; if (PlacesUtils.nodeIsURI(node) && (doubleClick || middleClick)) { // Open associated uri in the browser. this.openSelectedNode(aEvent);
} elseif (middleClick && PlacesUtils.nodeIsContainer(node)) { // The command execution function will take care of seeing if the // selection is a folder or a different container type, and will // load its contents in tabs.
PlacesUIUtils.openMultipleLinksInTabs(node, aEvent, this.view);
}
}
},
onKeyPress: function CT_onKeyPress(aEvent) { if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) { this.openSelectedNode(aEvent);
}
},
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.