| // Copyright 2016 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. |
| |
| import 'chrome://resources/cr_components/managed_footnote/managed_footnote.js'; |
| import 'chrome://resources/cr_elements/shared_style_css.m.js'; |
| import 'chrome://resources/cr_elements/shared_vars_css.m.js'; |
| import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; |
| import 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.js'; |
| import 'chrome://resources/cr_elements/cr_splitter/cr_splitter.js'; |
| import './folder_node.js'; |
| import './list.js'; |
| import './router.js'; |
| import './shared_vars.js'; |
| import './strings.m.js'; |
| import './command_manager.js'; |
| import './toolbar.js'; |
| |
| import {CrSplitterElement} from 'chrome://resources/cr_elements/cr_splitter/cr_splitter.js'; |
| import {FindShortcutMixin, FindShortcutMixinInterface} from 'chrome://resources/cr_elements/find_shortcut_mixin.js'; |
| import {StoreObserver} from 'chrome://resources/js/cr/ui/store.m.js'; |
| import {EventTracker} from 'chrome://resources/js/event_tracker.m.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; |
| import {IronScrollTargetBehavior} from 'chrome://resources/polymer/v3_0/iron-scroll-target-behavior/iron-scroll-target-behavior.js'; |
| import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {setSearchResults} from './actions.js'; |
| import {destroy as destroyApiListener, init as initApiListener} from './api_listener.js'; |
| import {LOCAL_STORAGE_FOLDER_STATE_KEY, LOCAL_STORAGE_TREE_WIDTH_KEY, ROOT_NODE_ID} from './constants.js'; |
| import {DNDManager} from './dnd_manager.js'; |
| import {MouseFocusMixin} from './mouse_focus_behavior.js'; |
| import {Store} from './store.js'; |
| import {BookmarksStoreClientInterface, StoreClient} from './store_client.js'; |
| import {BookmarksToolbarElement} from './toolbar.js'; |
| import {BookmarksPageState, FolderOpenState} from './types.js'; |
| import {createEmptyState, normalizeNodes} from './util.js'; |
| |
| const BookmarksAppElementBase = |
| mixinBehaviors( |
| [StoreClient, IronScrollTargetBehavior], |
| MouseFocusMixin(FindShortcutMixin(PolymerElement))) as { |
| new (): PolymerElement & BookmarksStoreClientInterface & |
| StoreObserver<BookmarksPageState>& FindShortcutMixinInterface & |
| IronScrollTargetBehavior |
| }; |
| |
| export interface BookmarksAppElement { |
| $: { |
| splitter: CrSplitterElement, |
| sidebar: HTMLDivElement, |
| } |
| } |
| |
| export class BookmarksAppElement extends BookmarksAppElementBase { |
| static get is() { |
| return 'bookmarks-app'; |
| } |
| |
| static get properties() { |
| return { |
| searchTerm_: { |
| type: String, |
| observer: 'searchTermChanged_', |
| }, |
| |
| folderOpenState_: { |
| type: Object, |
| observer: 'folderOpenStateChanged_', |
| }, |
| |
| sidebarWidth_: String, |
| |
| toolbarShadow_: { |
| type: Boolean, |
| reflectToAttribute: true, |
| }, |
| }; |
| } |
| |
| private eventTracker_: EventTracker = new EventTracker(); |
| private dndManager_: DNDManager|null = null; |
| private folderOpenState_: FolderOpenState; |
| private searchTerm_: string; |
| private sidebarWidth_: string; |
| private toolbarShadow_: boolean; |
| |
| constructor() { |
| super(); |
| |
| // Regular expression that captures the leading slash, the content and the |
| // trailing slash in three different groups. |
| const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/; |
| const path = location.pathname.replace(CANONICAL_PATH_REGEX, '$1$2'); |
| if (path !== '/') { // Only queries are supported, not subpages. |
| window.history.replaceState(undefined /* stateObject */, '', '/'); |
| } |
| } |
| |
| connectedCallback() { |
| super.connectedCallback(); |
| |
| document.documentElement.classList.remove('loading'); |
| |
| this.watch('searchTerm_', function(state: BookmarksPageState) { |
| return state.search.term; |
| }); |
| |
| this.watch('folderOpenState_', function(state: BookmarksPageState) { |
| return state.folderOpenState; |
| }); |
| |
| chrome.bookmarks.getTree((results) => { |
| const nodeMap = normalizeNodes(results[0]!); |
| const initialState = createEmptyState(); |
| initialState.nodes = nodeMap; |
| initialState.selectedFolder = nodeMap[ROOT_NODE_ID]!.children![0]!; |
| const folderStateString = |
| window.localStorage[LOCAL_STORAGE_FOLDER_STATE_KEY]; |
| initialState.folderOpenState = folderStateString ? |
| new Map(JSON.parse(folderStateString)) : |
| new Map(); |
| |
| Store.getInstance().init(initialState); |
| initApiListener(); |
| |
| setTimeout(function() { |
| chrome.metricsPrivate.recordTime( |
| 'BookmarkManager.ResultsRenderedTime', |
| Math.floor(window.performance.now())); |
| }); |
| }); |
| |
| this.initializeSplitter_(); |
| |
| this.dndManager_ = new DNDManager(); |
| this.dndManager_.init(); |
| |
| this.scrollTarget = this.shadowRoot!.querySelector('bookmarks-list'); |
| } |
| |
| disconnectedCallback() { |
| super.disconnectedCallback(); |
| |
| this.eventTracker_.remove(window, 'resize'); |
| this.dndManager_!.destroy(); |
| destroyApiListener(); |
| } |
| |
| private initializeSplitter_(): void { |
| const splitter = this.$.splitter; |
| const splitterTarget = this.$.sidebar; |
| |
| // The splitter persists the size of the left component in the local store. |
| if (LOCAL_STORAGE_TREE_WIDTH_KEY in window.localStorage) { |
| splitterTarget.style.width = |
| window.localStorage[LOCAL_STORAGE_TREE_WIDTH_KEY]; |
| } |
| this.sidebarWidth_ = getComputedStyle(splitterTarget).width; |
| |
| splitter.addEventListener('resize', (e) => { |
| window.localStorage[LOCAL_STORAGE_TREE_WIDTH_KEY] = |
| splitterTarget.style.width; |
| this.updateSidebarWidth_(); |
| }); |
| |
| this.eventTracker_.add(splitter, 'dragmove', |
| () => this.updateSidebarWidth_()); |
| this.eventTracker_.add(window, 'resize', () => this.updateSidebarWidth_()); |
| } |
| |
| private updateSidebarWidth_(): void { |
| this.sidebarWidth_ = getComputedStyle(this.$.sidebar).width; |
| } |
| |
| private searchTermChanged_(newValue: string, oldValue?: string) { |
| if (oldValue !== undefined && !newValue) { |
| this.dispatchEvent(new CustomEvent('iron-announce', { |
| bubbles: true, |
| composed: true, |
| detail: {text: loadTimeData.getString('searchCleared')} |
| })); |
| } |
| |
| if (!this.searchTerm_) { |
| return; |
| } |
| |
| chrome.bookmarks.search(this.searchTerm_, (results) => { |
| const ids = results.map(function(node) { |
| return node.id; |
| }); |
| this.dispatch(setSearchResults(ids)); |
| this.dispatchEvent(new CustomEvent('iron-announce', { |
| bubbles: true, |
| composed: true, |
| detail: { |
| text: ids.length > 0 ? |
| loadTimeData.getStringF('searchResults', this.searchTerm_) : |
| loadTimeData.getString('noSearchResults') |
| } |
| })); |
| }); |
| } |
| |
| private folderOpenStateChanged_(): void { |
| window.localStorage[LOCAL_STORAGE_FOLDER_STATE_KEY] = |
| JSON.stringify(Array.from(this.folderOpenState_)); |
| } |
| |
| // Override FindShortcutMixin methods. |
| handleFindShortcut(modalContextOpen: boolean): boolean { |
| if (modalContextOpen) { |
| return false; |
| } |
| this.shadowRoot!.querySelector<BookmarksToolbarElement>( |
| 'bookmarks-toolbar')!.searchField.showAndFocus(); |
| return true; |
| } |
| |
| // Override FindShortcutMixin methods. |
| searchInputHasFocus(): boolean { |
| return this.shadowRoot!.querySelector<BookmarksToolbarElement>( |
| 'bookmarks-toolbar')!.searchField.isSearchFocused(); |
| } |
| |
| private onUndoClick_(): void { |
| this.dispatchEvent( |
| new CustomEvent('command-undo', {bubbles: true, composed: true})); |
| } |
| |
| /** Overridden from IronScrollTargetBehavior */ |
| _scrollHandler() { |
| this.toolbarShadow_ = this.scrollTarget!.scrollTop !== 0; |
| } |
| |
| |
| static get template() { |
| return html`{__html_template__}`; |
| } |
| } |
| |
| customElements.define(BookmarksAppElement.is, BookmarksAppElement); |