// 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_elements/cr_icon_button/cr_icon_button.m.js'; import 'chrome://resources/cr_elements/cr_icons_css.m.js'; import 'chrome://resources/cr_elements/shared_vars_css.m.js'; import './shared_style.js'; import './strings.m.js'; import {assert} from 'chrome://resources/js/assert.m.js'; import {isMac} from 'chrome://resources/js/cr.m.js'; import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js'; import {getFaviconForPageURL} from 'chrome://resources/js/icon.m.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {selectItem} from './actions.js'; import {CommandManager} from './command_manager.js'; import {Command, MenuSource} from './constants.js'; import {StoreClient} from './store_client.js'; import {BookmarkNode} from './types.js'; Polymer({ is: 'bookmarks-item', _template: html`{__html_template__}`, behaviors: [ StoreClient, ], properties: { itemId: { type: String, observer: 'onItemIdChanged_', }, ironListTabIndex: Number, /** @private {BookmarkNode} */ item_: { type: Object, observer: 'onItemChanged_', }, /** @private */ isSelectedItem_: { type: Boolean, reflectToAttribute: true, }, /** @private */ isMultiSelect_: Boolean, /** @private */ isFolder_: Boolean, /** @private */ lastTouchPoints_: Number, }, observers: [ 'updateFavicon_(item_.url)', ], listeners: { 'click': 'onClick_', 'dblclick': 'onDblClick_', 'contextmenu': 'onContextMenu_', 'keydown': 'onKeydown_', 'auxclick': 'onMiddleClick_', 'mousedown': 'cancelMiddleMouseBehavior_', 'mouseup': 'cancelMiddleMouseBehavior_', 'touchstart': 'onTouchStart_', }, /** @override */ attached() { this.watch('item_', store => store.nodes[this.itemId]); this.watch( 'isSelectedItem_', store => store.selection.items.has(this.itemId)); this.watch('isMultiSelect_', store => store.selection.items.size > 1); this.updateFromStore(); }, focusMenuButton() { focusWithoutInk(this.$.menuButton); }, /** @return {BookmarksItemElement} */ getDropTarget() { return this; }, /** * @param {Event} e * @private */ onContextMenu_(e) { e.preventDefault(); e.stopPropagation(); // Prevent context menu from appearing after a drag, but allow opening the // context menu through 2 taps if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents && this.lastTouchPoints_ !== 2) { return; } this.focus(); if (!this.isSelectedItem_) { this.selectThisItem_(); } this.fire('open-command-menu', { x: e.clientX, y: e.clientY, source: MenuSource.ITEM, }); }, /** * @param {Event} e * @private */ onMenuButtonClick_(e) { e.stopPropagation(); e.preventDefault(); // Skip selecting the item if this item is part of a multi-selected group. if (!this.isMultiSelectMenu_()) { this.selectThisItem_(); } this.fire('open-command-menu', { targetElement: e.target, source: MenuSource.ITEM, }); }, /** @private */ selectThisItem_() { this.dispatch(selectItem(this.itemId, this.getState(), { clear: true, range: false, toggle: false, })); }, /** @private */ onItemIdChanged_() { // TODO(tsergeant): Add a histogram to measure whether this assertion fails // for real users. assert(this.getState().nodes[this.itemId]); this.updateFromStore(); }, /** @private */ onItemChanged_() { this.isFolder_ = !this.item_.url; this.setAttribute( 'aria-label', this.item_.title || this.item_.url || loadTimeData.getString('folderLabel')); }, /** * @param {MouseEvent} e * @private */ onClick_(e) { // Ignore double clicks so that Ctrl double-clicking an item won't deselect // the item before opening. if (e.detail !== 2) { const addKey = isMac ? e.metaKey : e.ctrlKey; this.dispatch(selectItem(this.itemId, this.getState(), { clear: !addKey, range: e.shiftKey, toggle: addKey && !e.shiftKey, })); } e.stopPropagation(); e.preventDefault(); }, /** * @private * @param {KeyboardEvent} e */ onKeydown_(e) { if (e.key === 'ArrowLeft') { this.focus(); } else if (e.key === 'ArrowRight') { this.$.menuButton.focus(); } }, /** * @param {MouseEvent} e * @private */ onDblClick_(e) { if (!this.isSelectedItem_) { this.selectThisItem_(); } const commandManager = CommandManager.getInstance(); const itemSet = this.getState().selection.items; if (commandManager.canExecute(Command.OPEN, itemSet)) { commandManager.handle(Command.OPEN, itemSet); } }, /** * @param {MouseEvent} e * @private */ onMiddleClick_(e) { if (e.button !== 1) { return; } this.selectThisItem_(); if (this.isFolder_) { return; } const commandManager = CommandManager.getInstance(); const itemSet = this.getState().selection.items; const command = e.shiftKey ? Command.OPEN : Command.OPEN_NEW_TAB; if (commandManager.canExecute(command, itemSet)) { commandManager.handle(command, itemSet); } }, /** * @param {TouchEvent} e * @private */ onTouchStart_(e) { this.lastTouchPoints_ = e.touches.length; }, /** * Prevent default middle-mouse behavior. On Windows, this prevents autoscroll * (during mousedown), and on Linux this prevents paste (during mouseup). * @param {MouseEvent} e * @private */ cancelMiddleMouseBehavior_(e) { if (e.button === 1) { e.preventDefault(); } }, /** * @param {string} url * @private */ updateFavicon_(url) { this.$.icon.className = url ? 'website-icon' : 'folder-icon'; this.$.icon.style.backgroundImage = url ? getFaviconForPageURL(url, false) : ''; }, /** * @return {string} * @private */ getButtonAriaLabel_() { if (!this.item_) { return ''; // Item hasn't loaded, skip for now. } if (this.isMultiSelectMenu_()) { return loadTimeData.getStringF('moreActionsMultiButtonAxLabel'); } return loadTimeData.getStringF( 'moreActionsButtonAxLabel', this.item_.title); }, /** * This item is part of a group selection. * @return {boolean} * @private */ isMultiSelectMenu_() { return this.isSelectedItem_ && this.isMultiSelect_; }, });