diff --git a/js/components/addEditBookmark.js b/app/renderer/components/bookmarks/addEditBookmark.js similarity index 75% rename from js/components/addEditBookmark.js rename to app/renderer/components/bookmarks/addEditBookmark.js index 0070e294e9..b455a26816 100644 --- a/js/components/addEditBookmark.js +++ b/app/renderer/components/bookmarks/addEditBookmark.js @@ -3,10 +3,14 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ const React = require('react') -const ImmutableComponent = require('./immutableComponent') -const Dialog = require('./dialog') -const windowActions = require('../actions/windowActions') -const AddEditBookmarkHanger = require('../../app/renderer/components/addEditBookmarkHanger') + +// Components +const ImmutableComponent = require('../../../../js/components/immutableComponent') +const Dialog = require('../../../../js/components/dialog') +const AddEditBookmarkHanger = require('./addEditBookmarkHanger') + +// Actions +const windowActions = require('../../../../js/actions/windowActions') class AddEditBookmark extends ImmutableComponent { constructor () { diff --git a/app/renderer/components/addEditBookmarkHanger.js b/app/renderer/components/bookmarks/addEditBookmarkHanger.js similarity index 91% rename from app/renderer/components/addEditBookmarkHanger.js rename to app/renderer/components/bookmarks/addEditBookmarkHanger.js index fe4336098a..09367a9a14 100644 --- a/app/renderer/components/addEditBookmarkHanger.js +++ b/app/renderer/components/bookmarks/addEditBookmarkHanger.js @@ -3,17 +3,25 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ const React = require('react') -const ImmutableComponent = require('../../../js/components/immutableComponent') -const Button = require('../../../js/components/button') -const cx = require('../../../js/lib/classSet') -const windowActions = require('../../../js/actions/windowActions') -const appActions = require('../../../js/actions/appActions') -const KeyCodes = require('../../common/constants/keyCodes') -const siteTags = require('../../../js/constants/siteTags') -const settings = require('../../../js/constants/settings') -const siteUtil = require('../../../js/state/siteUtil') -const UrlUtil = require('../../../js/lib/urlutil') -const getSetting = require('../../../js/settings').getSetting + +// Components +const ImmutableComponent = require('../../../../js/components/immutableComponent') +const Button = require('../../../../js/components/button') + +// Actions +const appActions = require('../../../../js/actions/appActions') +const windowActions = require('../../../../js/actions/windowActions') + +// Constants +const KeyCodes = require('../../../common/constants/keyCodes') +const siteTags = require('../../../../js/constants/siteTags') +const settings = require('../../../../js/constants/settings') + +// Utils +const cx = require('../../../../js/lib/classSet') +const siteUtil = require('../../../../js/state/siteUtil') +const UrlUtil = require('../../../../js/lib/urlutil') +const getSetting = require('../../../../js/settings').getSetting class AddEditBookmarkHanger extends ImmutableComponent { constructor () { diff --git a/app/renderer/components/bookmarks/bookmarkToolbarButton.js b/app/renderer/components/bookmarks/bookmarkToolbarButton.js new file mode 100644 index 0000000000..35f298fe43 --- /dev/null +++ b/app/renderer/components/bookmarks/bookmarkToolbarButton.js @@ -0,0 +1,348 @@ +/* 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/. */ + +const React = require('react') +const ReactDOM = require('react-dom') +const Immutable = require('immutable') +const {StyleSheet, css} = require('aphrodite/no-important') + +// Components +const ImmutableComponent = require('../../../../js/components/immutableComponent') + +// Actions +const windowActions = require('../../../../js/actions/windowActions') +const appActions = require('../../../../js/actions/appActions') + +// Store +const windowStore = require('../../../../js/stores/windowStore') + +// Constants +const siteTags = require('../../../../js/constants/siteTags') +const dragTypes = require('../../../../js/constants/dragTypes') + +// Utils +const siteUtil = require('../../../../js/state/siteUtil') +const {getCurrentWindowId} = require('../../currentWindow') +const dnd = require('../../../../js/dnd') +const iconSize = require('../../../common/lib/faviconUtil').iconSize +const cx = require('../../../../js/lib/classSet') + +// Styles +const globalStyles = require('../styles/global') + +class BookmarkToolbarButton extends ImmutableComponent { + constructor () { + super() + this.onClick = this.onClick.bind(this) + this.onMouseOver = this.onMouseOver.bind(this) + this.onDragStart = this.onDragStart.bind(this) + this.onDragEnd = this.onDragEnd.bind(this) + this.onDragEnter = this.onDragEnter.bind(this) + this.onDragLeave = this.onDragLeave.bind(this) + this.onDragOver = this.onDragOver.bind(this) + this.onContextMenu = this.onContextMenu.bind(this) + } + componentDidMount () { + this.bookmarkNode.addEventListener('auxclick', this.onAuxClick.bind(this)) + } + get activeFrame () { + return windowStore.getFrame(this.props.activeFrameKey) + } + onAuxClick (e) { + if (e.button === 1) { + this.onClick(e) + } + } + onClick (e) { + if (!this.props.clickBookmarkItem(this.props.bookmark, e) && + this.props.bookmark.get('tags').includes(siteTags.BOOKMARK_FOLDER)) { + if (this.props.contextMenuDetail) { + windowActions.setContextMenuDetail() + return + } + e.target = ReactDOM.findDOMNode(this) + this.props.showBookmarkFolderMenu(this.props.bookmark, e) + } + } + + onMouseOver (e) { + // Behavior when a bookmarks toolbar folder has its list expanded + if (this.props.selectedFolderId) { + if (this.isFolder && this.props.selectedFolderId !== this.props.bookmark.get('folderId')) { + // Auto-expand the menu if user mouses over another folder + e.target = ReactDOM.findDOMNode(this) + this.props.showBookmarkFolderMenu(this.props.bookmark, e) + } else if (!this.isFolder && this.props.selectedFolderId !== -1) { + // Hide the currently expanded menu if user mouses over a non-folder + windowActions.setBookmarksToolbarSelectedFolderId(-1) + windowActions.setContextMenuDetail() + } + } + } + + onDragStart (e) { + dnd.onDragStart(dragTypes.BOOKMARK, this.props.bookmark, e) + } + + onDragEnd (e) { + dnd.onDragEnd(dragTypes.BOOKMARK, this.props.bookmark, e) + } + + onDragEnter (e) { + // Bookmark specific DND code to expand hover when on a folder item + if (this.isFolder) { + e.target = ReactDOM.findDOMNode(this) + if (dnd.isMiddle(e.target, e.clientX)) { + this.props.showBookmarkFolderMenu(this.props.bookmark, e) + appActions.draggedOver({ + draggingOverKey: this.props.bookmark, + draggingOverType: dragTypes.BOOKMARK, + draggingOverWindowId: getCurrentWindowId(), + expanded: true + }) + } + } + } + + onDragLeave (e) { + // Bookmark specific DND code to expand hover when on a folder item + if (this.isFolder) { + appActions.draggedOver({ + draggingOverKey: this.props.bookmark, + draggingOverType: dragTypes.BOOKMARK, + draggingOverWindowId: getCurrentWindowId(), + expanded: false + }) + } + } + + onDragOver (e) { + dnd.onDragOver(dragTypes.BOOKMARK, this.bookmarkNode.getBoundingClientRect(), this.props.bookmark, this.draggingOverData, e) + } + + get draggingOverData () { + if (!this.props.draggingOverData || + !Immutable.is(this.props.draggingOverData.get('draggingOverKey'), this.props.bookmark)) { + return + } + + return this.props.draggingOverData + } + + get isDragging () { + return Immutable.is(this.props.bookmark, dnd.getInterBraveDragData()) + } + + get isDraggingOverLeft () { + if (!this.draggingOverData) { + return false + } + return this.draggingOverData.get('draggingOverLeftHalf') + } + + get isExpanded () { + if (!this.props.draggingOverData) { + return false + } + return this.props.draggingOverData.get('expanded') + } + + get isDraggingOverRight () { + if (!this.draggingOverData) { + return false + } + return this.draggingOverData.get('draggingOverRightHalf') + } + + get isFolder () { + return siteUtil.isFolder(this.props.bookmark) + } + + onContextMenu (e) { + this.props.openContextMenu(this.props.bookmark, e) + } + + render () { + let showingFavicon = this.props.showFavicon + let iconStyle = { + minWidth: iconSize, + width: iconSize + } + + if (showingFavicon) { + let icon = this.props.bookmark.get('favicon') + + if (icon) { + iconStyle = Object.assign(iconStyle, { + backgroundImage: `url(${icon})`, + backgroundSize: iconSize, + height: iconSize + }) + } else if (!this.isFolder) { + showingFavicon = false + } + } + + const siteDetailTitle = this.props.bookmark.get('customTitle') || this.props.bookmark.get('title') + const siteDetailLocation = this.props.bookmark.get('location') + let hoverTitle + if (this.isFolder) { + hoverTitle = siteDetailTitle + } else { + hoverTitle = siteDetailTitle + ? siteDetailTitle + '\n' + siteDetailLocation + : siteDetailLocation + } + + return { this.bookmarkNode = node }} + title={hoverTitle} + onClick={this.onClick} + onMouseOver={this.onMouseOver} + onDragStart={this.onDragStart} + onDragEnd={this.onDragEnd} + onDragEnter={this.onDragEnter} + onDragLeave={this.onDragLeave} + onDragOver={this.onDragOver} + onContextMenu={this.onContextMenu}> + { + this.isFolder && this.props.showFavicon + ? + : null + } + { + // Fill in a favicon if we want one but there isn't one + !this.isFolder && this.props.showFavicon && !showingFavicon + ? + : null + } + { + !this.isFolder && showingFavicon + ? + : null + } + + { + (this.isFolder ? false : (this.props.showFavicon && this.props.showOnlyFavicon)) + ? '' + : siteDetailTitle || siteDetailLocation + } + + { + this.isFolder + ? + : null + } + + } +} + +module.exports = BookmarkToolbarButton + +const bookmarkItemMaxWidth = '100px' +const bookmarkItemPadding = '4px' +const bookmarkItemMargin = '3px' +const bookmarkItemChevronMargin = '4px' +const bookmarkToolbarButtonDraggingMargin = '25px' + +const styles = StyleSheet.create({ + bookmarkToolbarButton: { + display: 'flex', + alignItems: 'center', + boxSizing: 'border-box', + borderRadius: '3px', + color: globalStyles.color.mediumGray, + cursor: 'default', + fontSize: '11px', + lineHeight: '1.3', + margin: `auto ${bookmarkItemMargin}`, + maxWidth: bookmarkItemMaxWidth, + padding: `2px ${bookmarkItemPadding}`, + textOverflow: 'ellipsis', + userSelect: 'none', + whiteSpace: 'nowrap', + WebkitAppRegion: 'no-drag', + + ':hover': { + background: '#fff', + boxShadow: '0 1px 5px 0 rgba(0, 0, 0, 0.1)' + } + }, + bookmarkToolbarButton__draggingOverLeft: { + marginLeft: bookmarkToolbarButtonDraggingMargin + }, + bookmarkToolbarButton__draggingOverRight: { + marginRight: bookmarkToolbarButtonDraggingMargin + }, + bookmarkToolbarButton__isDragging: { + opacity: '0.2' + }, + bookmarkToolbarButton__showOnlyFavicon: { + padding: '2px 4px', + margin: 'auto 0' + }, + bookmarkToolbarButton__marginRightZero: { + marginRight: 0 + }, + bookmarkToolbarButton__bookmarkFavicon: { + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + display: 'inline-block', + marginRight: '4px' + }, + bookmarkToolbarButton__bookmarkFolder: { + fontSize: globalStyles.spacing.bookmarksFolderIconSize, + textAlign: 'center', + color: globalStyles.color.darkGray + }, + bookmarkToolbarButton__bookmarkFile: { + fontSize: globalStyles.spacing.bookmarksFileIconSize, + textAlign: 'center', + color: globalStyles.color.darkGray + }, + bookmarkToolbarButton__bookmarkText: { + textOverflow: 'ellipsis', + overflow: 'hidden' + }, + bookmarkToolbarButton__bookmarkFolderChevron: { + color: '#676767', + fontSize: '8px', + marginLeft: bookmarkItemChevronMargin + } +}) diff --git a/app/renderer/components/bookmarks/bookmarksToolbar.js b/app/renderer/components/bookmarks/bookmarksToolbar.js new file mode 100644 index 0000000000..8b731963b0 --- /dev/null +++ b/app/renderer/components/bookmarks/bookmarksToolbar.js @@ -0,0 +1,278 @@ +/* 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/. */ + +const React = require('react') +const ReactDOM = require('react-dom') +const {StyleSheet, css} = require('aphrodite/no-important') + +// Components +const ImmutableComponent = require('../../../../js/components/immutableComponent') +const Button = require('../../../../js/components/button') +const BookmarkToolbarButton = require('./bookmarkToolbarButton') + +// Actions +const windowActions = require('../../../../js/actions/windowActions') +const bookmarkActions = require('../../../../js/actions/bookmarkActions') +const appActions = require('../../../../js/actions/appActions') + +// Store +const windowStore = require('../../../../js/stores/windowStore') + +// Constants +const siteTags = require('../../../../js/constants/siteTags') +const dragTypes = require('../../../../js/constants/dragTypes') + +// Utils +const siteUtil = require('../../../../js/state/siteUtil') +const contextMenus = require('../../../../js/contextMenus') +const cx = require('../../../../js/lib/classSet') +const dnd = require('../../../../js/dnd') +const dndData = require('../../../../js/dndData') +const calculateTextWidth = require('../../../../js/lib/textCalculator').calculateTextWidth +const iconSize = require('../../../common/lib/faviconUtil').iconSize + +// Styles +const globalStyles = require('../styles/global') + +class BookmarksToolbar extends ImmutableComponent { + constructor () { + super() + this.onDrop = this.onDrop.bind(this) + this.onDragEnter = this.onDragEnter.bind(this) + this.onDragOver = this.onDragOver.bind(this) + this.onContextMenu = this.onContextMenu.bind(this) + this.onMoreBookmarksMenu = this.onMoreBookmarksMenu.bind(this) + this.openContextMenu = this.openContextMenu.bind(this) + this.clickBookmarkItem = this.clickBookmarkItem.bind(this) + this.showBookmarkFolderMenu = this.showBookmarkFolderMenu.bind(this) + } + get activeFrame () { + return windowStore.getFrame(this.props.activeFrameKey) + } + onDrop (e) { + e.preventDefault() + const bookmark = dnd.prepareBookmarkDataFromCompatible(e.dataTransfer) + if (bookmark) { + // Figure out the droppedOn element filtering out the source drag item + let droppedOn = dnd.closestFromXOffset(this.bookmarkRefs.filter((bookmarkRef) => { + if (!bookmarkRef) { + return false + } + return !siteUtil.isEquivalent(bookmarkRef.props.bookmark, bookmark) + }), e.clientX) + if (droppedOn.selectedRef) { + const isLeftSide = dnd.isLeftSide(ReactDOM.findDOMNode(droppedOn.selectedRef), e.clientX) + const droppedOnSiteDetail = droppedOn.selectedRef.props.bookmark || droppedOn.selectedRef.props.bookmarkFolder + appActions.moveSite(bookmark, droppedOnSiteDetail, isLeftSide, droppedOnSiteDetail.get('tags').includes(siteTags.BOOKMARK_FOLDER) && droppedOn && droppedOn.isDroppedOn) + } + return + } + const droppedHTML = e.dataTransfer.getData('text/html') + if (droppedHTML) { + var parser = new window.DOMParser() + var doc = parser.parseFromString(droppedHTML, 'text/html') + var a = doc.querySelector('a') + if (a && a.href) { + appActions.addSite({ + title: a.innerText, + location: e.dataTransfer.getData('text/plain') + }, siteTags.BOOKMARK) + return + } + } + if (e.dataTransfer.files.length > 0) { + Array.from(e.dataTransfer.files).forEach((file) => + appActions.addSite({ location: file.path, title: file.name }, siteTags.BOOKMARK)) + return + } + + e.dataTransfer.getData('text/uri-list') + .split('\n') + .map((x) => x.trim()) + .filter((x) => !x.startsWith('#') && x.length > 0) + .forEach((url) => + appActions.addSite({ location: url }, siteTags.BOOKMARK)) + } + openContextMenu (bookmark, e) { + contextMenus.onSiteDetailContextMenu(bookmark, this.activeFrame, e) + } + clickBookmarkItem (bookmark, e) { + return bookmarkActions.clickBookmarkItem(this.bookmarks, bookmark, this.activeFrame, e) + } + showBookmarkFolderMenu (bookmark, e) { + windowActions.setBookmarksToolbarSelectedFolderId(bookmark.get('folderId')) + contextMenus.onShowBookmarkFolderMenu(this.bookmarks, bookmark, this.activeFrame, e) + } + updateBookmarkData (props) { + this.bookmarks = siteUtil.getBookmarks(props.sites).toList().sort(siteUtil.siteSort) + + const noParentItems = this.bookmarks + .filter((bookmark) => !bookmark.get('parentFolderId')) + let widthAccountedFor = 0 + const overflowButtonWidth = 25 + + // Dynamically calculate how many bookmark items should appear on the toolbar + // before it is actually rendered. + if (!this.root) { + this.root = window.getComputedStyle(document.querySelector(':root')) + this.maxWidth = Number.parseInt(this.root.getPropertyValue('--bookmark-item-max-width'), 10) + this.padding = Number.parseInt(this.root.getPropertyValue('--bookmark-item-padding'), 10) * 2 + // Toolbar padding is only on the left + this.toolbarPadding = Number.parseInt(this.root.getPropertyValue('--bookmarks-toolbar-padding'), 10) + this.bookmarkItemMargin = Number.parseInt(this.root.getPropertyValue('--bookmark-item-margin'), 10) * 2 + // No margin for show only favicons + this.chevronMargin = Number.parseInt(this.root.getPropertyValue('--bookmark-item-chevron-margin'), 10) + this.fontSize = this.root.getPropertyValue('--bookmark-item-font-size') + this.fontFamily = this.root.getPropertyValue('--default-font-family') + this.chevronWidth = this.chevronMargin + Number.parseInt(this.fontSize) + } + const margin = props.showFavicon && props.showOnlyFavicon ? 0 : this.bookmarkItemMargin + widthAccountedFor += this.toolbarPadding + + // Loop through until we fill up the entire bookmark toolbar width + let i + for (i = 0; i < noParentItems.size; i++) { + let iconWidth = props.showFavicon ? iconSize : 0 + // font-awesome file icons are 3px smaller + if (props.showFavicon && !noParentItems.getIn([i, 'folderId']) && !noParentItems.getIn([i, 'favicon'])) { + iconWidth -= 3 + } + const chevronWidth = props.showFavicon && noParentItems.getIn([i, 'folderId']) ? this.chevronWidth : 0 + if (props.showFavicon && props.showOnlyFavicon) { + widthAccountedFor += this.padding + iconWidth + chevronWidth + } else { + const text = noParentItems.getIn([i, 'customTitle']) || noParentItems.getIn([i, 'title']) || noParentItems.getIn([i, 'location']) + widthAccountedFor += Math.min(calculateTextWidth(text, `${this.fontSize} ${this.fontFamily}`) + this.padding + iconWidth + chevronWidth, this.maxWidth) + } + widthAccountedFor += margin + if (widthAccountedFor >= props.windowWidth - overflowButtonWidth) { + break + } + } + this.bookmarksForToolbar = noParentItems.take(i).sort(siteUtil.siteSort) + // Show at most 100 items in the overflow menu + this.overflowBookmarkItems = noParentItems.skip(i).take(100).sort(siteUtil.siteSort) + } + componentWillMount () { + this.updateBookmarkData(this.props) + } + componentWillUpdate (nextProps) { + if (nextProps.sites !== this.props.sites || + nextProps.windowWidth !== this.props.windowWidth || + nextProps.showFavicon !== this.props.showFavicon || + nextProps.showOnlyFavicon !== this.props.showOnlyFavicon) { + this.updateBookmarkData(nextProps) + } + } + onDragEnter (e) { + if (dndData.hasDragData(e.dataTransfer, dragTypes.BOOKMARK)) { + if (Array.from(e.target.classList).includes('overflowIndicator')) { + this.onMoreBookmarksMenu(e) + } + } + } + onDragOver (e) { + const sourceDragData = dndData.getDragData(e.dataTransfer, dragTypes.BOOKMARK) + if (sourceDragData) { + e.dataTransfer.dropEffect = 'move' + e.preventDefault() + return + } + // console.log(e.dataTransfer.types, e.dataTransfer.getData('text/plain'), e.dataTransfer.getData('text/uri-list'), e.dataTransfer.getData('text/html')) + let intersection = e.dataTransfer.types.filter((x) => + ['text/plain', 'text/uri-list', 'text/html', 'Files'].includes(x)) + if (intersection.length > 0) { + e.dataTransfer.dropEffect = 'copy' + e.preventDefault() + } + } + onMoreBookmarksMenu (e) { + contextMenus.onMoreBookmarksMenu(this.activeFrame, this.bookmarks, this.overflowBookmarkItems, e) + } + onContextMenu (e) { + const closest = dnd.closestFromXOffset(this.bookmarkRefs.filter((x) => !!x), e.clientX).selectedRef + contextMenus.onTabsToolbarContextMenu(this.activeFrame, (closest && closest.props.bookmark) || undefined, closest && closest.isDroppedOn, e) + } + render () { + let showFavicon = this.props.showFavicon + let showOnlyFavicon = this.props.showOnlyFavicon + + this.bookmarkRefs = [] + return
+ { + this.bookmarksForToolbar.map((bookmark, i) => + this.bookmarkRefs.push(node)} + key={i} + contextMenuDetail={this.props.contextMenuDetail} + activeFrameKey={this.props.activeFrameKey} + draggingOverData={this.props.draggingOverData} + openContextMenu={this.openContextMenu} + clickBookmarkItem={this.clickBookmarkItem} + showBookmarkFolderMenu={this.showBookmarkFolderMenu} + bookmark={bookmark} + showFavicon={this.props.showFavicon} + showOnlyFavicon={this.props.showOnlyFavicon} + selectedFolderId={this.props.selectedFolderId} />) + } + { + this.overflowBookmarkItems.size !== 0 + ?
+ } +} + +const styles = StyleSheet.create({ + bookmarksToolbar: { + boxSizing: 'border-box', + display: 'flex', + flex: 1, + padding: `${globalStyles.spacing.navbarMenubarMargin} 10px` + }, + bookmarksToolbar__allowDragging: { + WebkitAppRegion: 'drag' + }, + bookmarksToolbar__showOnlyFavicon: { + padding: `${globalStyles.spacing.navbarMenubarMargin} 0 ${globalStyles.spacing.tabPagesHeight} 10px` + }, + bookmarksToolbar__bookmarkButton: { + boxSizing: 'border-box', + fontSize: '14px', + height: 'auto', + lineHeight: '12px', + marginLeft: 'auto', + marginRight: '5px', + width: 'auto', + userSelect: 'none' + }, + bookmarksToolbar__overflowIndicator: { + paddingLeft: '6px', + paddingRight: '11px', + margin: 'auto 0 auto auto', + WebkitAppRegion: 'no-drag' + } +}) + +module.exports = BookmarksToolbar diff --git a/app/renderer/components/bookmarksToolbar.js b/app/renderer/components/bookmarksToolbar.js deleted file mode 100644 index 9329c76564..0000000000 --- a/app/renderer/components/bookmarksToolbar.js +++ /dev/null @@ -1,581 +0,0 @@ -/* 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/. */ - -const React = require('react') -const ReactDOM = require('react-dom') -const Immutable = require('immutable') -const ImmutableComponent = require('../../../js/components/immutableComponent') -const contextMenus = require('../../../js/contextMenus') -const windowActions = require('../../../js/actions/windowActions') -const bookmarkActions = require('../../../js/actions/bookmarkActions') -const appActions = require('../../../js/actions/appActions') -const siteTags = require('../../../js/constants/siteTags') -const siteUtil = require('../../../js/state/siteUtil') -const dragTypes = require('../../../js/constants/dragTypes') -const Button = require('../../../js/components/button') -const cx = require('../../../js/lib/classSet') -const dnd = require('../../../js/dnd') -const dndData = require('../../../js/dndData') -const calculateTextWidth = require('../../../js/lib/textCalculator').calculateTextWidth -const windowStore = require('../../../js/stores/windowStore') -const iconSize = require('../../common/lib/faviconUtil').iconSize -const {getCurrentWindowId} = require('../currentWindow') - -const {StyleSheet, css} = require('aphrodite/no-important') -const globalStyles = require('./styles/global') - -class BookmarkToolbarButton extends ImmutableComponent { - constructor () { - super() - this.onClick = this.onClick.bind(this) - this.onMouseOver = this.onMouseOver.bind(this) - this.onDragStart = this.onDragStart.bind(this) - this.onDragEnd = this.onDragEnd.bind(this) - this.onDragEnter = this.onDragEnter.bind(this) - this.onDragLeave = this.onDragLeave.bind(this) - this.onDragOver = this.onDragOver.bind(this) - this.onContextMenu = this.onContextMenu.bind(this) - } - componentDidMount () { - this.bookmarkNode.addEventListener('auxclick', this.onAuxClick.bind(this)) - } - get activeFrame () { - return windowStore.getFrame(this.props.activeFrameKey) - } - onAuxClick (e) { - if (e.button === 1) { - this.onClick(e) - } - } - onClick (e) { - if (!this.props.clickBookmarkItem(this.props.bookmark, e) && - this.props.bookmark.get('tags').includes(siteTags.BOOKMARK_FOLDER)) { - if (this.props.contextMenuDetail) { - windowActions.setContextMenuDetail() - return - } - e.target = ReactDOM.findDOMNode(this) - this.props.showBookmarkFolderMenu(this.props.bookmark, e) - } - } - - onMouseOver (e) { - // Behavior when a bookmarks toolbar folder has its list expanded - if (this.props.selectedFolderId) { - if (this.isFolder && this.props.selectedFolderId !== this.props.bookmark.get('folderId')) { - // Auto-expand the menu if user mouses over another folder - e.target = ReactDOM.findDOMNode(this) - this.props.showBookmarkFolderMenu(this.props.bookmark, e) - } else if (!this.isFolder && this.props.selectedFolderId !== -1) { - // Hide the currently expanded menu if user mouses over a non-folder - windowActions.setBookmarksToolbarSelectedFolderId(-1) - windowActions.setContextMenuDetail() - } - } - } - - onDragStart (e) { - dnd.onDragStart(dragTypes.BOOKMARK, this.props.bookmark, e) - } - - onDragEnd (e) { - dnd.onDragEnd(dragTypes.BOOKMARK, this.props.bookmark, e) - } - - onDragEnter (e) { - // Bookmark specific DND code to expand hover when on a folder item - if (this.isFolder) { - e.target = ReactDOM.findDOMNode(this) - if (dnd.isMiddle(e.target, e.clientX)) { - this.props.showBookmarkFolderMenu(this.props.bookmark, e) - appActions.draggedOver({ - draggingOverKey: this.props.bookmark, - draggingOverType: dragTypes.BOOKMARK, - draggingOverWindowId: getCurrentWindowId(), - expanded: true - }) - } - } - } - - onDragLeave (e) { - // Bookmark specific DND code to expand hover when on a folder item - if (this.isFolder) { - appActions.draggedOver({ - draggingOverKey: this.props.bookmark, - draggingOverType: dragTypes.BOOKMARK, - draggingOverWindowId: getCurrentWindowId(), - expanded: false - }) - } - } - - onDragOver (e) { - dnd.onDragOver(dragTypes.BOOKMARK, this.bookmarkNode.getBoundingClientRect(), this.props.bookmark, this.draggingOverData, e) - } - - get draggingOverData () { - if (!this.props.draggingOverData || - !Immutable.is(this.props.draggingOverData.get('draggingOverKey'), this.props.bookmark)) { - return - } - - return this.props.draggingOverData - } - - get isDragging () { - return Immutable.is(this.props.bookmark, dnd.getInterBraveDragData()) - } - - get isDraggingOverLeft () { - if (!this.draggingOverData) { - return false - } - return this.draggingOverData.get('draggingOverLeftHalf') - } - - get isExpanded () { - if (!this.props.draggingOverData) { - return false - } - return this.props.draggingOverData.get('expanded') - } - - get isDraggingOverRight () { - if (!this.draggingOverData) { - return false - } - return this.draggingOverData.get('draggingOverRightHalf') - } - - get isFolder () { - return siteUtil.isFolder(this.props.bookmark) - } - - onContextMenu (e) { - this.props.openContextMenu(this.props.bookmark, e) - } - - render () { - let showingFavicon = this.props.showFavicon - let iconStyle = { - minWidth: iconSize, - width: iconSize - } - - if (showingFavicon) { - let icon = this.props.bookmark.get('favicon') - - if (icon) { - iconStyle = Object.assign(iconStyle, { - backgroundImage: `url(${icon})`, - backgroundSize: iconSize, - height: iconSize - }) - } else if (!this.isFolder) { - showingFavicon = false - } - } - - const siteDetailTitle = this.props.bookmark.get('customTitle') || this.props.bookmark.get('title') - const siteDetailLocation = this.props.bookmark.get('location') - let hoverTitle - if (this.isFolder) { - hoverTitle = siteDetailTitle - } else { - hoverTitle = siteDetailTitle - ? siteDetailTitle + '\n' + siteDetailLocation - : siteDetailLocation - } - - return { this.bookmarkNode = node }} - title={hoverTitle} - onClick={this.onClick} - onMouseOver={this.onMouseOver} - onDragStart={this.onDragStart} - onDragEnd={this.onDragEnd} - onDragEnter={this.onDragEnter} - onDragLeave={this.onDragLeave} - onDragOver={this.onDragOver} - onContextMenu={this.onContextMenu}> - { - this.isFolder && this.props.showFavicon - ? - : null - } - { - // Fill in a favicon if we want one but there isn't one - !this.isFolder && this.props.showFavicon && !showingFavicon - ? - : null - } - { - !this.isFolder && showingFavicon - ? : null - } - - { - (this.isFolder ? false : (this.props.showFavicon && this.props.showOnlyFavicon)) - ? '' - : siteDetailTitle || siteDetailLocation - } - - { - this.isFolder - ? - : null - } - - } -} - -class BookmarksToolbar extends ImmutableComponent { - constructor () { - super() - this.onDrop = this.onDrop.bind(this) - this.onDragEnter = this.onDragEnter.bind(this) - this.onDragOver = this.onDragOver.bind(this) - this.onContextMenu = this.onContextMenu.bind(this) - this.onMoreBookmarksMenu = this.onMoreBookmarksMenu.bind(this) - this.openContextMenu = this.openContextMenu.bind(this) - this.clickBookmarkItem = this.clickBookmarkItem.bind(this) - this.showBookmarkFolderMenu = this.showBookmarkFolderMenu.bind(this) - } - get activeFrame () { - return windowStore.getFrame(this.props.activeFrameKey) - } - onDrop (e) { - e.preventDefault() - const bookmark = dnd.prepareBookmarkDataFromCompatible(e.dataTransfer) - if (bookmark) { - // Figure out the droppedOn element filtering out the source drag item - let droppedOn = dnd.closestFromXOffset(this.bookmarkRefs.filter((bookmarkRef) => { - if (!bookmarkRef) { - return false - } - return !siteUtil.isEquivalent(bookmarkRef.props.bookmark, bookmark) - }), e.clientX) - if (droppedOn.selectedRef) { - const isLeftSide = dnd.isLeftSide(ReactDOM.findDOMNode(droppedOn.selectedRef), e.clientX) - const droppedOnSiteDetail = droppedOn.selectedRef.props.bookmark || droppedOn.selectedRef.props.bookmarkFolder - appActions.moveSite(bookmark, droppedOnSiteDetail, isLeftSide, droppedOnSiteDetail.get('tags').includes(siteTags.BOOKMARK_FOLDER) && droppedOn && droppedOn.isDroppedOn) - } - return - } - const droppedHTML = e.dataTransfer.getData('text/html') - if (droppedHTML) { - var parser = new window.DOMParser() - var doc = parser.parseFromString(droppedHTML, 'text/html') - var a = doc.querySelector('a') - if (a && a.href) { - appActions.addSite({ - title: a.innerText, - location: e.dataTransfer.getData('text/plain') - }, siteTags.BOOKMARK) - return - } - } - if (e.dataTransfer.files.length > 0) { - Array.from(e.dataTransfer.files).forEach((file) => - appActions.addSite({ location: file.path, title: file.name }, siteTags.BOOKMARK)) - return - } - - e.dataTransfer.getData('text/uri-list') - .split('\n') - .map((x) => x.trim()) - .filter((x) => !x.startsWith('#') && x.length > 0) - .forEach((url) => - appActions.addSite({ location: url }, siteTags.BOOKMARK)) - } - openContextMenu (bookmark, e) { - contextMenus.onSiteDetailContextMenu(bookmark, this.activeFrame, e) - } - clickBookmarkItem (bookmark, e) { - return bookmarkActions.clickBookmarkItem(this.bookmarks, bookmark, this.activeFrame, e) - } - showBookmarkFolderMenu (bookmark, e) { - windowActions.setBookmarksToolbarSelectedFolderId(bookmark.get('folderId')) - contextMenus.onShowBookmarkFolderMenu(this.bookmarks, bookmark, this.activeFrame, e) - } - updateBookmarkData (props) { - this.bookmarks = siteUtil.getBookmarks(props.sites).toList().sort(siteUtil.siteSort) - - const noParentItems = this.bookmarks - .filter((bookmark) => !bookmark.get('parentFolderId')) - let widthAccountedFor = 0 - const overflowButtonWidth = 25 - - // Dynamically calculate how many bookmark items should appear on the toolbar - // before it is actually rendered. - if (!this.root) { - this.root = window.getComputedStyle(document.querySelector(':root')) - this.maxWidth = Number.parseInt(this.root.getPropertyValue('--bookmark-item-max-width'), 10) - this.padding = Number.parseInt(this.root.getPropertyValue('--bookmark-item-padding'), 10) * 2 - // Toolbar padding is only on the left - this.toolbarPadding = Number.parseInt(this.root.getPropertyValue('--bookmarks-toolbar-padding'), 10) - this.bookmarkItemMargin = Number.parseInt(this.root.getPropertyValue('--bookmark-item-margin'), 10) * 2 - // No margin for show only favicons - this.chevronMargin = Number.parseInt(this.root.getPropertyValue('--bookmark-item-chevron-margin'), 10) - this.fontSize = this.root.getPropertyValue('--bookmark-item-font-size') - this.fontFamily = this.root.getPropertyValue('--default-font-family') - this.chevronWidth = this.chevronMargin + Number.parseInt(this.fontSize) - } - const margin = props.showFavicon && props.showOnlyFavicon ? 0 : this.bookmarkItemMargin - widthAccountedFor += this.toolbarPadding - - // Loop through until we fill up the entire bookmark toolbar width - let i - for (i = 0; i < noParentItems.size; i++) { - let iconWidth = props.showFavicon ? iconSize : 0 - // font-awesome file icons are 3px smaller - if (props.showFavicon && !noParentItems.getIn([i, 'folderId']) && !noParentItems.getIn([i, 'favicon'])) { - iconWidth -= 3 - } - const chevronWidth = props.showFavicon && noParentItems.getIn([i, 'folderId']) ? this.chevronWidth : 0 - if (props.showFavicon && props.showOnlyFavicon) { - widthAccountedFor += this.padding + iconWidth + chevronWidth - } else { - const text = noParentItems.getIn([i, 'customTitle']) || noParentItems.getIn([i, 'title']) || noParentItems.getIn([i, 'location']) - widthAccountedFor += Math.min(calculateTextWidth(text, `${this.fontSize} ${this.fontFamily}`) + this.padding + iconWidth + chevronWidth, this.maxWidth) - } - widthAccountedFor += margin - if (widthAccountedFor >= props.windowWidth - overflowButtonWidth) { - break - } - } - this.bookmarksForToolbar = noParentItems.take(i).sort(siteUtil.siteSort) - // Show at most 100 items in the overflow menu - this.overflowBookmarkItems = noParentItems.skip(i).take(100).sort(siteUtil.siteSort) - } - componentWillMount () { - this.updateBookmarkData(this.props) - } - componentWillUpdate (nextProps) { - if (nextProps.sites !== this.props.sites || - nextProps.windowWidth !== this.props.windowWidth || - nextProps.showFavicon !== this.props.showFavicon || - nextProps.showOnlyFavicon !== this.props.showOnlyFavicon) { - this.updateBookmarkData(nextProps) - } - } - onDragEnter (e) { - if (dndData.hasDragData(e.dataTransfer, dragTypes.BOOKMARK)) { - if (Array.from(e.target.classList).includes('overflowIndicator')) { - this.onMoreBookmarksMenu(e) - } - } - } - onDragOver (e) { - const sourceDragData = dndData.getDragData(e.dataTransfer, dragTypes.BOOKMARK) - if (sourceDragData) { - e.dataTransfer.dropEffect = 'move' - e.preventDefault() - return - } - // console.log(e.dataTransfer.types, e.dataTransfer.getData('text/plain'), e.dataTransfer.getData('text/uri-list'), e.dataTransfer.getData('text/html')) - let intersection = e.dataTransfer.types.filter((x) => - ['text/plain', 'text/uri-list', 'text/html', 'Files'].includes(x)) - if (intersection.length > 0) { - e.dataTransfer.dropEffect = 'copy' - e.preventDefault() - } - } - onMoreBookmarksMenu (e) { - contextMenus.onMoreBookmarksMenu(this.activeFrame, this.bookmarks, this.overflowBookmarkItems, e) - } - onContextMenu (e) { - const closest = dnd.closestFromXOffset(this.bookmarkRefs.filter((x) => !!x), e.clientX).selectedRef - contextMenus.onTabsToolbarContextMenu(this.activeFrame, (closest && closest.props.bookmark) || undefined, closest && closest.isDroppedOn, e) - } - render () { - let showFavicon = this.props.showFavicon - let showOnlyFavicon = this.props.showOnlyFavicon - - this.bookmarkRefs = [] - return
- { - this.bookmarksForToolbar.map((bookmark, i) => - this.bookmarkRefs.push(node)} - key={i} - contextMenuDetail={this.props.contextMenuDetail} - activeFrameKey={this.props.activeFrameKey} - draggingOverData={this.props.draggingOverData} - openContextMenu={this.openContextMenu} - clickBookmarkItem={this.clickBookmarkItem} - showBookmarkFolderMenu={this.showBookmarkFolderMenu} - bookmark={bookmark} - showFavicon={this.props.showFavicon} - showOnlyFavicon={this.props.showOnlyFavicon} - selectedFolderId={this.props.selectedFolderId} />) - } - { - this.overflowBookmarkItems.size !== 0 - ?
- } -} - -const bookmarkItemMaxWidth = '100px' -const bookmarkItemPadding = '4px' -const bookmarkItemMargin = '3px' -const bookmarkItemChevronMargin = '4px' -const bookmarksToolbarPadding = '10px' - -const bookmarkToolbarButtonDraggingMargin = '25px' - -const styles = StyleSheet.create({ - bookmarksToolbar: { - boxSizing: 'border-box', - display: 'flex', - flex: 1, - padding: `${globalStyles.spacing.navbarMenubarMargin} ${bookmarksToolbarPadding}` - }, - bookmarksToolbar__allowDragging: { - WebkitAppRegion: 'drag' - }, - bookmarksToolbar__showOnlyFavicon: { - padding: `${globalStyles.spacing.navbarMenubarMargin} 0 ${globalStyles.spacing.tabPagesHeight} 10px` - }, - bookmarksToolbar__bookmarkButton: { - boxSizing: 'border-box', - fontSize: '14px', - height: 'auto', - lineHeight: '12px', - marginLeft: 'auto', - marginRight: '5px', - width: 'auto', - userSelect: 'none' - }, - bookmarksToolbar__overflowIndicator: { - paddingLeft: '6px', - paddingRight: '11px', - margin: 'auto 0 auto auto', - WebkitAppRegion: 'no-drag' - }, - - bookmarkToolbarButton: { - display: 'flex', - alignItems: 'center', - boxSizing: 'border-box', - borderRadius: '3px', - color: globalStyles.color.mediumGray, - cursor: 'default', - fontSize: '11px', - lineHeight: '1.3', - margin: `auto ${bookmarkItemMargin}`, - maxWidth: bookmarkItemMaxWidth, - padding: `2px ${bookmarkItemPadding}`, - textOverflow: 'ellipsis', - userSelect: 'none', - whiteSpace: 'nowrap', - WebkitAppRegion: 'no-drag', - - ':hover': { - background: '#fff', - boxShadow: '0 1px 5px 0 rgba(0, 0, 0, 0.1)' - } - }, - bookmarkToolbarButton__draggingOverLeft: { - marginLeft: bookmarkToolbarButtonDraggingMargin - }, - bookmarkToolbarButton__draggingOverRight: { - marginRight: bookmarkToolbarButtonDraggingMargin - }, - bookmarkToolbarButton__isDragging: { - opacity: '0.2' - }, - bookmarkToolbarButton__showOnlyFavicon: { - padding: '2px 4px', - margin: 'auto 0' - }, - bookmarkToolbarButton__marginRightZero: { - marginRight: 0 - }, - bookmarkToolbarButton__bookmarkFavicon: { - backgroundPosition: 'center', - backgroundRepeat: 'no-repeat', - display: 'inline-block', - marginRight: '4px' - }, - bookmarkToolbarButton__bookmarkFolder: { - fontSize: globalStyles.spacing.bookmarksFolderIconSize, - textAlign: 'center', - color: globalStyles.color.darkGray - }, - bookmarkToolbarButton__bookmarkFile: { - fontSize: globalStyles.spacing.bookmarksFileIconSize, - textAlign: 'center', - color: globalStyles.color.darkGray - }, - bookmarkToolbarButton__bookmarkText: { - textOverflow: 'ellipsis', - overflow: 'hidden' - }, - bookmarkToolbarButton__bookmarkFolderChevron: { - color: '#676767', - fontSize: '8px', - marginLeft: bookmarkItemChevronMargin - } -}) - -module.exports = BookmarksToolbar diff --git a/app/renderer/components/navigation/navigationBar.js b/app/renderer/components/navigation/navigationBar.js index 847b994a0d..0f6a537db8 100644 --- a/app/renderer/components/navigation/navigationBar.js +++ b/app/renderer/components/navigation/navigationBar.js @@ -15,7 +15,7 @@ const messages = require('../../../../js/constants/messages') const settings = require('../../../../js/constants/settings') const ipc = require('electron').ipcRenderer const {isSourceAboutUrl} = require('../../../../js/lib/appUrlUtil') -const AddEditBookmarkHanger = require('../addEditBookmarkHanger') +const AddEditBookmarkHanger = require('../bookmarks/addEditBookmarkHanger') const siteUtil = require('../../../../js/state/siteUtil') const eventUtil = require('../../../../js/lib/eventUtil') const UrlUtil = require('../../../../js/lib/urlutil') diff --git a/js/components/main.js b/js/components/main.js index af2c452d10..f07c985a93 100644 --- a/js/components/main.js +++ b/js/components/main.js @@ -32,10 +32,10 @@ const ImportBrowserDataPanel = require('../../app/renderer/components/importBrow const WidevinePanel = require('../../app/renderer/components/widevinePanel') const AutofillAddressPanel = require('./autofillAddressPanel') const AutofillCreditCardPanel = require('./autofillCreditCardPanel') -const AddEditBookmark = require('./addEditBookmark') +const AddEditBookmark = require('../../app/renderer/components/bookmarks/addEditBookmark') const LoginRequired = require('./loginRequired') const ReleaseNotes = require('../../app/renderer/components/releaseNotes') -const BookmarksToolbar = require('../../app/renderer/components/bookmarksToolbar') +const BookmarksToolbar = require('../../app/renderer/components/bookmarks/bookmarksToolbar') const ContextMenu = require('./contextMenu') const PopupWindow = require('./popupWindow') const NoScriptInfo = require('./noScriptInfo')