| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview Utility functions for the Bookmarks page. |
| */ |
| |
| cr.define('bookmarks.util', function() { |
| /** |
| * Returns the list of bookmark IDs to be displayed in the UI, taking into |
| * account search and the currently selected folder. |
| * @param {!BookmarksPageState} state |
| * @return {!Array<string>} |
| */ |
| function getDisplayedList(state) { |
| if (isShowingSearch(state)) { |
| return assert(state.search.results); |
| } |
| |
| return assert(state.nodes[state.selectedFolder].children); |
| } |
| |
| /** |
| * @param {BookmarkTreeNode} treeNode |
| * @return {!BookmarkNode} |
| */ |
| function normalizeNode(treeNode) { |
| const node = Object.assign({}, treeNode); |
| // Node index is not necessary and not kept up-to-date. Remove it from the |
| // data structure so we don't accidentally depend on the incorrect |
| // information. |
| delete node.index; |
| |
| if (!('url' in node)) { |
| // The onCreated API listener returns folders without |children| defined. |
| node.children = (node.children || []).map(function(child) { |
| return child.id; |
| }); |
| } |
| return /** @type {BookmarkNode} */ (node); |
| } |
| |
| /** |
| * @param {BookmarkTreeNode} rootNode |
| * @return {NodeMap} |
| */ |
| function normalizeNodes(rootNode) { |
| /** @type {NodeMap} */ |
| const nodeMap = {}; |
| const stack = []; |
| stack.push(rootNode); |
| |
| while (stack.length > 0) { |
| const node = stack.pop(); |
| nodeMap[node.id] = normalizeNode(node); |
| if (!node.children) { |
| continue; |
| } |
| |
| node.children.forEach(function(child) { |
| stack.push(child); |
| }); |
| } |
| |
| return nodeMap; |
| } |
| |
| /** @return {!BookmarksPageState} */ |
| function createEmptyState() { |
| return { |
| nodes: {}, |
| selectedFolder: BOOKMARKS_BAR_ID, |
| folderOpenState: new Map(), |
| prefs: { |
| canEdit: true, |
| incognitoAvailability: IncognitoAvailability.ENABLED, |
| }, |
| search: { |
| term: '', |
| inProgress: false, |
| results: null, |
| }, |
| selection: { |
| items: new Set(), |
| anchor: null, |
| }, |
| }; |
| } |
| |
| /** |
| * @param {BookmarksPageState} state |
| * @return {boolean} |
| */ |
| function isShowingSearch(state) { |
| return state.search.results != null; |
| } |
| |
| /** |
| * Returns true if the node with ID |itemId| is modifiable, allowing |
| * the node to be renamed, moved or deleted. Note that if a node is |
| * uneditable, it may still have editable children (for example, the top-level |
| * folders). |
| * @param {BookmarksPageState} state |
| * @param {string} itemId |
| * @return {boolean} |
| */ |
| function canEditNode(state, itemId) { |
| return itemId != ROOT_NODE_ID && |
| state.nodes[itemId].parentId != ROOT_NODE_ID && |
| !state.nodes[itemId].unmodifiable && state.prefs.canEdit; |
| } |
| |
| /** |
| * Returns true if it is possible to modify the children list of the node with |
| * ID |itemId|. This includes rearranging the children or adding new ones. |
| * @param {BookmarksPageState} state |
| * @param {string} itemId |
| * @return {boolean} |
| */ |
| function canReorderChildren(state, itemId) { |
| return itemId != ROOT_NODE_ID && !state.nodes[itemId].unmodifiable && |
| state.prefs.canEdit; |
| } |
| |
| /** |
| * @param {string} id |
| * @param {NodeMap} nodes |
| * @return {boolean} |
| */ |
| function hasChildFolders(id, nodes) { |
| const children = nodes[id].children; |
| for (let i = 0; i < children.length; i++) { |
| if (nodes[children[i]].children) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Get all descendants of a node, including the node itself. |
| * @param {NodeMap} nodes |
| * @param {string} baseId |
| * @return {!Set<string>} |
| */ |
| function getDescendants(nodes, baseId) { |
| const descendants = new Set(); |
| const stack = []; |
| stack.push(baseId); |
| |
| while (stack.length > 0) { |
| const id = stack.pop(); |
| const node = nodes[id]; |
| |
| if (!node) { |
| continue; |
| } |
| |
| descendants.add(id); |
| |
| if (!node.children) { |
| continue; |
| } |
| |
| node.children.forEach(function(childId) { |
| stack.push(childId); |
| }); |
| } |
| |
| return descendants; |
| } |
| |
| /** |
| * @param {string} name |
| * @param {number} value |
| * @param {number} maxValue |
| */ |
| function recordEnumHistogram(name, value, maxValue) { |
| chrome.send('metricsHandler:recordInHistogram', [name, value, maxValue]); |
| } |
| |
| /** |
| * @param {!Object<string, T>} map |
| * @param {!Set<string>} ids |
| * @return {!Object<string, T>} |
| * @template T |
| */ |
| function removeIdsFromObject(map, ids) { |
| const newObject = Object.assign({}, map); |
| ids.forEach(function(id) { |
| delete newObject[id]; |
| }); |
| return newObject; |
| } |
| |
| |
| /** |
| * @param {!Map<string, T>} map |
| * @param {!Set<string>} ids |
| * @return {!Map<string, T>} |
| * @template T |
| */ |
| function removeIdsFromMap(map, ids) { |
| const newMap = new Map(map); |
| ids.forEach(function(id) { |
| newMap.delete(id); |
| }); |
| return newMap; |
| } |
| |
| /** |
| * @param {!Set<string>} set |
| * @param {!Set<string>} ids |
| * @return {!Set<string>} |
| */ |
| function removeIdsFromSet(set, ids) { |
| const difference = new Set(set); |
| ids.forEach(function(id) { |
| difference.delete(id); |
| }); |
| return difference; |
| } |
| |
| return { |
| canEditNode: canEditNode, |
| canReorderChildren: canReorderChildren, |
| createEmptyState: createEmptyState, |
| getDescendants: getDescendants, |
| getDisplayedList: getDisplayedList, |
| hasChildFolders: hasChildFolders, |
| isShowingSearch: isShowingSearch, |
| normalizeNode: normalizeNode, |
| normalizeNodes: normalizeNodes, |
| recordEnumHistogram: recordEnumHistogram, |
| removeIdsFromMap: removeIdsFromMap, |
| removeIdsFromObject: removeIdsFromObject, |
| removeIdsFromSet: removeIdsFromSet, |
| }; |
| }); |