From 0802623b6eaf0664d9aa4e51a24be0e4f322f3c8 Mon Sep 17 00:00:00 2001 From: NejcZdovc Date: Wed, 12 Jul 2017 19:23:04 +0200 Subject: [PATCH] Splits sites object in the app state Resolves #1856 Resolves #2771 Resolves #3646 Resolves #3694 Resolves #4224 Resolves #4260 Resolves #4833 Resolves #4868 Resolves #4929 Resolves #5699 Resolves #6104 Resolves #6108 Resolves #6585 Resolves #8022 Resolves #9301 Resolves #9326 Resolves #9978 Resolves #10026 Auditors: Test Plan: --- app/browser/api/topSites.js | 89 +-- app/browser/bookmarksExporter.js | 47 +- app/browser/menu.js | 43 +- .../reducers/bookmarkFoldersReducer.js | 88 +++ app/browser/reducers/bookmarksReducer.js | 97 +++ app/browser/reducers/historyReducer.js | 70 ++ app/browser/reducers/pinnedSitesReducer.js | 72 ++ app/browser/reducers/sitesReducer.js | 192 ------ .../reducers/urlBarSuggestionsReducer.js | 10 +- app/browser/tabs.js | 30 +- app/browser/windows.js | 7 +- app/common/cache/bookmarkOrderCache.js | 131 ++++ app/common/lib/bookmarkFoldersUtil.js | 62 ++ app/common/lib/bookmarkUtil.js | 139 +++- app/common/lib/historyUtil.js | 66 +- app/common/lib/menuUtil.js | 45 +- app/common/lib/pinnedSitesUtil.js | 115 ++++ app/common/lib/siteSuggestions.js | 5 +- app/common/lib/windowsUtil.js | 17 - app/common/state/aboutHistoryState.js | 13 +- app/common/state/aboutNewTabState.js | 7 + app/common/state/bookmarkFoldersState.js | 176 +++++ app/common/state/bookmarksState.js | 237 +++++++ app/common/state/historyState.js | 67 ++ app/common/state/pinnedSitesState.js | 125 ++++ app/common/state/siteCache.js | 2 + app/common/state/siteState.js | 18 - app/common/state/tabState.js | 11 +- app/importer.js | 132 ++-- app/index.js | 5 +- .../about/bookmarks/bookmarkFolderItem.js | 48 +- .../about/bookmarks/bookmarkFolderList.js | 3 + .../about/bookmarks/bookmarkTitleCell.js | 6 +- .../about/bookmarks/bookmarkTitleHeader.js | 6 +- app/renderer/about/bookmarks/bookmarks.js | 33 +- app/renderer/about/bookmarks/bookmarksList.js | 33 +- .../bookmarks/addEditBookmarkFolder.js | 103 +++ .../bookmarks/addEditBookmarkFolderForm.js | 235 +++++++ .../bookmarks/addEditBookmarkForm.js | 35 +- .../bookmarks/addEditBookmarkHanger.js | 51 +- .../bookmarks/bookmarkToolbarButton.js | 24 +- .../components/bookmarks/bookmarksToolbar.js | 25 +- app/renderer/components/frame/frame.js | 6 +- app/renderer/components/main/main.js | 23 +- .../components/navigation/navigationBar.js | 2 +- app/renderer/components/navigation/urlBar.js | 3 +- .../components/tabs/content/closeTabIcon.js | 5 +- .../components/tabs/content/favIcon.js | 4 +- app/renderer/components/tabs/pinnedTabs.js | 12 +- app/renderer/components/tabs/tab.js | 5 +- app/renderer/reducers/contextMenuReducer.js | 155 +++-- app/renderer/reducers/frameReducer.js | 7 +- app/sessionStore.js | 159 ++++- app/sync.js | 52 +- docs/state.md | 59 +- js/about/aboutActions.js | 10 - js/about/newtab.js | 200 +++--- js/actions/appActions.js | 119 ++-- js/actions/bookmarkActions.js | 34 +- js/actions/windowActions.js | 48 +- js/constants/appConstants.js | 16 +- js/constants/siteTags.js | 4 +- js/constants/windowConstants.js | 5 +- js/contextMenus.js | 169 ++--- js/data/newTabData.js | 117 ++-- js/lib/importer.js | 27 +- js/lib/textCalculator.js | 2 +- js/state/frameStateUtil.js | 1 + js/state/siteUtil.js | 647 +----------------- js/state/syncUtil.js | 60 +- js/stores/appStore.js | 51 +- js/stores/windowStore.js | 58 +- test/about/bookmarksManagerTest.js | 43 +- test/about/historyTest.js | 10 +- test/about/ledgerPanelTest.js | 30 +- test/about/ledgerTableTest.js | 2 +- test/about/newTabTest.js | 34 +- test/app/sessionStoreTest.js | 12 +- test/bookmark-components/bookmarksTest.js | 47 +- .../bookmarksToolbarTest.js | 32 +- test/lib/brave.js | 118 +++- test/misc-components/syncTest.js | 2 +- test/navbar-components/navigationBarTest.js | 6 +- .../urlBarSuggestionsTest.js | 4 +- test/navbar-components/urlBarTest.js | 34 +- .../app/common/lib/pinnedSitesUtilTest.js | 39 ++ test/unit/app/common/lib/windowsUtilTest.js | 40 -- test/unit/state/siteUtilTest.js | 77 --- 88 files changed, 3201 insertions(+), 2109 deletions(-) create mode 100644 app/browser/reducers/bookmarkFoldersReducer.js create mode 100644 app/browser/reducers/bookmarksReducer.js create mode 100644 app/browser/reducers/historyReducer.js create mode 100644 app/browser/reducers/pinnedSitesReducer.js delete mode 100644 app/browser/reducers/sitesReducer.js create mode 100644 app/common/cache/bookmarkOrderCache.js create mode 100644 app/common/lib/bookmarkFoldersUtil.js create mode 100644 app/common/lib/pinnedSitesUtil.js delete mode 100644 app/common/lib/windowsUtil.js create mode 100644 app/common/state/bookmarkFoldersState.js create mode 100644 app/common/state/bookmarksState.js create mode 100644 app/common/state/historyState.js create mode 100644 app/common/state/pinnedSitesState.js delete mode 100644 app/common/state/siteState.js create mode 100644 app/renderer/components/bookmarks/addEditBookmarkFolder.js create mode 100644 app/renderer/components/bookmarks/addEditBookmarkFolderForm.js create mode 100644 test/unit/app/common/lib/pinnedSitesUtilTest.js delete mode 100644 test/unit/app/common/lib/windowsUtilTest.js diff --git a/app/browser/api/topSites.js b/app/browser/api/topSites.js index 48f3c9ccfce..a4fc6329a65 100644 --- a/app/browser/api/topSites.js +++ b/app/browser/api/topSites.js @@ -7,39 +7,33 @@ const Immutable = require('immutable') const appActions = require('../../../js/actions/appActions') const debounce = require('../../../js/lib/debounce') -const siteUtil = require('../../../js/state/siteUtil') +const historyState = require('../../common/state/historyState') const {isSourceAboutUrl} = require('../../../js/lib/appUrlUtil') -const aboutNewTabMaxEntries = 100 +const aboutNewTabMaxEntries = 18 let appStore let minCountOfTopSites let minAccessOfTopSites -const compareSites = (site1, site2) => { - if (!site1 || !site2) return false - return site1.get('location') === site2.get('location') && - site1.get('partitionNumber') === site2.get('partitionNumber') -} - const pinnedTopSites = (state) => { - return (state.getIn(['about', 'newtab', 'pinnedTopSites']) || Immutable.List()).setSize(18) + return state.getIn(['about', 'newtab', 'pinnedTopSites'], Immutable.List()) } const ignoredTopSites = (state) => { - return state.getIn(['about', 'newtab', 'ignoredTopSites']) || Immutable.List() + return state.getIn(['about', 'newtab', 'ignoredTopSites'], Immutable.List()) } -const isPinned = (state, siteProps) => { - return pinnedTopSites(state).filter((site) => compareSites(site, siteProps)).size > 0 +const isPinned = (state, siteKey) => { + return pinnedTopSites(state).find(site => site.get('key') === siteKey) } -const isIgnored = (state, siteProps) => { - return ignoredTopSites(state).filter((site) => compareSites(site, siteProps)).size > 0 +const isIgnored = (state, siteKey) => { + return ignoredTopSites(state).includes(siteKey) } const sortCountDescending = (left, right) => { - const leftCount = left.get('count') || 0 - const rightCount = right.get('count') || 0 + const leftCount = left.get('count', 0) + const rightCount = right.get('count', 0) if (leftCount < rightCount) { return 1 } @@ -55,25 +49,6 @@ const sortCountDescending = (left, right) => { return 0 } -const removeDuplicateDomains = (list) => { - const siteDomains = new Set() - return list.filter((site) => { - if (!site.get('location')) { - return false - } - try { - const hostname = require('../../common/urlParse')(site.get('location')).hostname - if (!siteDomains.has(hostname)) { - siteDomains.add(hostname) - return true - } - } catch (e) { - console.log('Error parsing hostname: ', e) - } - return false - }) -} - const calculateTopSites = (clearCache) => { if (clearCache) { clearTopSiteCacheData() @@ -91,18 +66,21 @@ const startCalculatingTopSiteData = debounce(() => { } const state = appStore.getState() // remove folders; sort by visit count; enforce a max limit - const sites = (state.get('sites') ? state.get('sites').toList() : new Immutable.List()) - .filter((site) => !siteUtil.isFolder(site) && - !siteUtil.isImportedBookmark(site) && - !isSourceAboutUrl(site.get('location')) && + const sites = historyState.getSites(state) + .filter((site, key) => !isSourceAboutUrl(site.get('location')) && + !isPinned(state, key) && + !isIgnored(state, key) && (minCountOfTopSites === undefined || (site.get('count') || 0) >= minCountOfTopSites) && - (minAccessOfTopSites === undefined || (site.get('lastAccessedTime') || 0) >= minAccessOfTopSites)) + (minAccessOfTopSites === undefined || (site.get('lastAccessedTime') || 0) >= minAccessOfTopSites) + ) .sort(sortCountDescending) .slice(0, aboutNewTabMaxEntries) + .map((site, key) => site.set('key', key)) + .toList() for (let i = 0; i < sites.size; i++) { - const count = sites.getIn([i, 'count']) || 0 - const access = sites.getIn([i, 'lastAccessedTime']) || 0 + const count = sites.getIn([i, 'count'], 0) + const access = sites.getIn([i, 'lastAccessedTime'], 0) if (minCountOfTopSites === undefined || count < minCountOfTopSites) { minCountOfTopSites = count } @@ -111,32 +89,7 @@ const startCalculatingTopSiteData = debounce(() => { } } - // Filter out pinned and ignored sites - let unpinnedSites = sites.filter((site) => !(isPinned(state, site) || isIgnored(state, site))) - unpinnedSites = removeDuplicateDomains(unpinnedSites) - - // Merge the pinned and unpinned lists together - // Pinned items have priority because the position is important - let gridSites = pinnedTopSites(state).map((pinnedSite) => { - // Fetch latest siteDetail objects from appState.sites using location/partition - if (pinnedSite) { - const matches = sites.filter((site) => compareSites(site, pinnedSite)) - if (matches.size > 0) return matches.first() - } - // Default to unpinned items - const firstSite = unpinnedSites.first() - unpinnedSites = unpinnedSites.shift() - return firstSite - }) - - // Include up to [aboutNewTabMaxEntries] entries so that folks - // can ignore sites and have new items fill those empty spaces - if (unpinnedSites.size > 0) { - gridSites = gridSites.concat(unpinnedSites) - } - - const finalData = gridSites.filter((site) => site != null) - appActions.topSiteDataAvailable(finalData) + appActions.topSiteDataAvailable(sites) }, 5 * 1000) const clearTopSiteCacheData = () => { diff --git a/app/browser/bookmarksExporter.js b/app/browser/bookmarksExporter.js index 598501ed3da..572f85328c7 100644 --- a/app/browser/bookmarksExporter.js +++ b/app/browser/bookmarksExporter.js @@ -11,15 +11,23 @@ const electron = require('electron') const dialog = electron.dialog const app = electron.app const BrowserWindow = electron.BrowserWindow -const getSetting = require('../../js/settings').getSetting + +// State +const bookmarksState = require('../common/state/bookmarksState') + +// Constants const settings = require('../../js/constants/settings') -const siteTags = require('../../js/constants/siteTags') -const siteUtil = require('../../js/state/siteUtil') -const isWindows = process.platform === 'win32' + +// Utils +const {getSetting} = require('../../js/settings') +const platformUtil = require('../common/lib/platformUtil') +const bookmarkFoldersUtil = require('../common/lib/bookmarkFoldersUtil') +const bookmarkUtil = require('../common/lib/bookmarkUtil') + const indentLength = 2 const indentType = ' ' -function showDialog (sites) { +const showDialog = (state) => { const focusedWindow = BrowserWindow.getFocusedWindow() const fileName = moment().format('DD_MM_YYYY') + '.html' const defaultPath = path.join(getSetting(settings.DEFAULT_DOWNLOAD_SAVE_PATH) || app.getPath('downloads'), fileName) @@ -34,17 +42,15 @@ function showDialog (sites) { }] }, (fileName) => { if (fileName) { - personal = createBookmarkArray(sites) - other = createBookmarkArray(sites, -1, false) + personal = createBookmarkArray(state) + other = createBookmarkArray(state, -1, false) fs.writeFileSync(fileName, createBookmarkHTML(personal, other)) } }) } -function createBookmarkArray (sites, parentFolderId, first = true, depth = 1) { - const filteredBookmarks = parentFolderId - ? sites.filter((site) => site.get('parentFolderId') === parentFolderId) - : sites.filter((site) => !site.get('parentFolderId')) +const createBookmarkArray = (state, parentFolderId = 0, first = true, depth = 1) => { + const bookmarks = bookmarksState.getBookmarksWithFolders(state, parentFolderId) let payload = [] let title let indentFirst = indentType.repeat(depth * indentLength) @@ -52,16 +58,13 @@ function createBookmarkArray (sites, parentFolderId, first = true, depth = 1) { if (first) payload.push(`${indentFirst}

`) - filteredBookmarks.forEach((site) => { - if (site.get('tags').includes(siteTags.BOOKMARK) && site.get('location')) { - title = site.get('customTitle') || site.get('title') || site.get('location') + bookmarks.forEach((site) => { + if (bookmarkUtil.isBookmark(site) && site.get('location')) { + title = site.get('title', site.get('location')) payload.push(`${indentNext}

${title}`) - } else if (siteUtil.isFolder(site)) { - const folderId = site.get('folderId') - - title = site.get('customTitle') || site.get('title') - payload.push(`${indentNext}

${title}

`) - payload = payload.concat(createBookmarkArray(sites, folderId, true, (depth + 1))) + } else if (bookmarkFoldersUtil.isFolder(site)) { + payload.push(`${indentNext}

${site.get('title')}

`) + payload = payload.concat(createBookmarkArray(state, site.get('folderId'), true, (depth + 1))) } }) @@ -70,8 +73,8 @@ function createBookmarkArray (sites, parentFolderId, first = true, depth = 1) { return payload } -function createBookmarkHTML (personal, other) { - const breakTag = (isWindows) ? '\r\n' : '\n' +const createBookmarkHTML = (personal, other) => { + const breakTag = (platformUtil.isWindows()) ? '\r\n' : '\n' const title = 'Bookmarks' return ` diff --git a/app/browser/menu.js b/app/browser/menu.js index a5b550a8619..b83a907d51d 100644 --- a/app/browser/menu.js +++ b/app/browser/menu.js @@ -17,7 +17,6 @@ const appConstants = require('../../js/constants/appConstants') const windowConstants = require('../../js/constants/windowConstants') const messages = require('../../js/constants/messages') const settings = require('../../js/constants/settings') -const siteTags = require('../../js/constants/siteTags') // State const {getByTabId} = require('../common/state/tabState') @@ -35,8 +34,8 @@ const frameStateUtil = require('../../js/state/frameStateUtil') const menuUtil = require('../common/lib/menuUtil') const {getSetting} = require('../../js/settings') const locale = require('../locale') -const {isLocationBookmarked} = require('../../js/state/siteUtil') const platformUtil = require('../common/lib/platformUtil') +const bookmarkUtil = require('../common/lib/bookmarkUtil') const isDarwin = platformUtil.isDarwin() const isLinux = platformUtil.isLinux() const isWindows = platformUtil.isWindows() @@ -376,7 +375,7 @@ const updateRecentlyClosedMenuItems = (state) => { } const isCurrentLocationBookmarked = (state) => { - return isLocationBookmarked(state, currentLocation) + return bookmarkUtil.isLocationBookmarked(state, currentLocation) } const createBookmarksSubmenu = (state) => { @@ -406,7 +405,7 @@ const createBookmarksSubmenu = (state) => { CommonMenu.exportBookmarksMenuItem() ] - const bookmarks = menuUtil.createBookmarkTemplateItems(state.get('sites')) + const bookmarks = menuUtil.createBookmarkTemplateItems(state) if (bookmarks.length > 0) { submenu.push(CommonMenu.separatorMenuItem) submenu = submenu.concat(bookmarks) @@ -693,38 +692,14 @@ const doAction = (state, action) => { } break } - case appConstants.APP_APPLY_SITE_RECORDS: - if (action.records && action.records.find((record) => record.objectData === 'bookmark')) { - createMenu(state) - } - break - case appConstants.APP_ADD_SITE: case appConstants.APP_ADD_BOOKMARK: case appConstants.APP_EDIT_BOOKMARK: - { - if (action.tag === siteTags.BOOKMARK || action.tag === siteTags.BOOKMARK_FOLDER) { - createMenu(state) - } else if (action.siteDetail && action.siteDetail.constructor === Immutable.List && action.tag === undefined) { - let shouldRebuild = false - action.siteDetail.forEach((site) => { - const tag = site.getIn(['tags', 0]) - if (tag === siteTags.BOOKMARK || tag === siteTags.BOOKMARK_FOLDER) { - shouldRebuild = true - } - }) - if (shouldRebuild) { - createMenu(state) - } - } - break - } - case appConstants.APP_REMOVE_SITE: - { - if (action.tag === siteTags.BOOKMARK || action.tag === siteTags.BOOKMARK_FOLDER) { - createMenu(state) - } - break - } + case appConstants.APP_REMOVE_BOOKMARK: + case appConstants.APP_ADD_BOOKMARK_FOLDER: + case appConstants.APP_EDIT_BOOKMARK_FOLDER: + case appConstants.APP_REMOVE_BOOKMARK_FOLDER: + createMenu(state) + break case appConstants.APP_ON_CLEAR_BROWSING_DATA: { const defaults = state.get('clearBrowsingDataDefaults') diff --git a/app/browser/reducers/bookmarkFoldersReducer.js b/app/browser/reducers/bookmarkFoldersReducer.js new file mode 100644 index 00000000000..365bae4ca32 --- /dev/null +++ b/app/browser/reducers/bookmarkFoldersReducer.js @@ -0,0 +1,88 @@ +/* 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 Immutable = require('immutable') + +// State +const bookmarkFoldersState = require('../../common/state/bookmarkFoldersState') + +// Constants +const appConstants = require('../../../js/constants/appConstants') + +// Utils +const {makeImmutable} = require('../../common/state/immutableUtil') +const syncUtil = require('../../../js/state/syncUtil') + +const bookmarkFoldersReducer = (state, action, immutableAction) => { + action = immutableAction || makeImmutable(action) + switch (action.get('actionType')) { + case appConstants.APP_ADD_BOOKMARK_FOLDER: + { + const closestKey = action.get('closestKey') + let folder = action.get('folderDetails') + + if (folder == null) { + break + } + + if (Immutable.List.isList(folder)) { + action.get('folderDetails', Immutable.List()).forEach((folder) => { + state = bookmarkFoldersState.addFolder(state, folder, closestKey) + + if (syncUtil.syncEnabled()) { + state = syncUtil.updateSiteCache(state, folder) + } + }) + } else { + state = bookmarkFoldersState.addFolder(state, folder, closestKey) + + if (syncUtil.syncEnabled()) { + state = syncUtil.updateSiteCache(state, folder) + } + } + break + } + case appConstants.APP_EDIT_BOOKMARK_FOLDER: + { + let folder = action.get('folderDetails') + + if (folder == null) { + break + } + + state = bookmarkFoldersState.editFolder(state, folder, action.get('editKey')) + + if (syncUtil.syncEnabled()) { + state = syncUtil.updateSiteCache(state, folder) + } + + break + } + case appConstants.APP_MOVE_BOOKMARK_FOLDER: + { + state = bookmarkFoldersState.moveFolder( + state, + action.get('folderKey'), + action.get('destinationKey'), + action.get('append'), + action.get('moveIntoParent') + ) + + if (syncUtil.syncEnabled()) { + const destinationDetail = state.getIn(['sites', action.get('destinationKey')]) + state = syncUtil.updateSiteCache(state, destinationDetail) + } + break + } + case appConstants.APP_REMOVE_BOOKMARK_FOLDER: + { + state = bookmarkFoldersState.removeFolder(state, action.get('folderKey')) + break + } + } + + return state +} + +module.exports = bookmarkFoldersReducer diff --git a/app/browser/reducers/bookmarksReducer.js b/app/browser/reducers/bookmarksReducer.js new file mode 100644 index 00000000000..d6271bcfc4b --- /dev/null +++ b/app/browser/reducers/bookmarksReducer.js @@ -0,0 +1,97 @@ +/* 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 Immutable = require('immutable') + +// State +const bookmarksState = require('../../common/state/bookmarksState') + +// Constants +const appConstants = require('../../../js/constants/appConstants') + +// Utils +const {makeImmutable} = require('../../common/state/immutableUtil') +const syncUtil = require('../../../js/state/syncUtil') +const bookmarkUtil = require('../../common/lib/bookmarkUtil') +const siteCache = require('../../common/state/siteCache') + +const bookmarksReducer = (state, action, immutableAction) => { + action = immutableAction || makeImmutable(action) + switch (action.get('actionType')) { + case appConstants.APP_SET_STATE: + state = siteCache.loadLocationSiteKeysCache(state) + break + case appConstants.APP_ADD_BOOKMARK: + { + const closestKey = action.get('closestKey') + let bookmark = action.get('siteDetail') + + if (bookmark == null) { + break + } + + if (Immutable.List.isList(bookmark)) { + action.get('siteDetail', Immutable.List()).forEach((bookmark) => { + state = bookmarksState.addBookmark(state, bookmark, closestKey) + + if (syncUtil.syncEnabled()) { + state = syncUtil.updateSiteCache(state, bookmark) + } + }) + } else { + state = bookmarksState.addBookmark(state, bookmark, closestKey) + + if (syncUtil.syncEnabled()) { + state = syncUtil.updateSiteCache(state, bookmark) + } + } + + state = bookmarkUtil.updateActiveTabBookmarked(state) + break + } + case appConstants.APP_EDIT_BOOKMARK: + { + let bookmark = action.get('siteDetail') + + if (bookmark == null) { + break + } + + state = bookmarksState.editBookmark(state, bookmark, action.get('editKey')) + + if (syncUtil.syncEnabled()) { + state = syncUtil.updateSiteCache(state, bookmark) + } + + state = bookmarkUtil.updateActiveTabBookmarked(state) + break + } + case appConstants.APP_MOVE_BOOKMARK: + { + state = bookmarksState.moveBookmark( + state, + action.get('bookmarkKey'), + action.get('destinationKey'), + action.get('append'), + action.get('moveIntoParent') + ) + + if (syncUtil.syncEnabled()) { + const destinationDetail = state.getIn(['sites', action.get('destinationKey')]) + state = syncUtil.updateSiteCache(state, destinationDetail) + } + break + } + case appConstants.APP_REMOVE_BOOKMARK: + { + state = bookmarksState.removeBookmark(state, action.get('bookmarkKey')) + state = bookmarkUtil.updateActiveTabBookmarked(state) + break + } + } + + return state +} + +module.exports = bookmarksReducer diff --git a/app/browser/reducers/historyReducer.js b/app/browser/reducers/historyReducer.js new file mode 100644 index 00000000000..20e7ccb51ba --- /dev/null +++ b/app/browser/reducers/historyReducer.js @@ -0,0 +1,70 @@ +/* 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 Immutable = require('immutable') + +// State +const historyState = require('../../common/state/historyState') + +// Actions +const appActions = require('../../../js/actions/appActions') +const syncActions = require('../../../js/actions/syncActions') + +// Constants +const appConstants = require('../../../js/constants/appConstants') + +// Utils +const {makeImmutable} = require('../../common/state/immutableUtil') +const syncUtil = require('../../../js/state/syncUtil') +const siteUtil = require('../../../js/state/siteUtil') +const filtering = require('../../filtering') + +const historyReducer = (state, action, immutableAction) => { + action = immutableAction || makeImmutable(action) + switch (action.get('actionType')) { + case appConstants.APP_ON_CLEAR_BROWSING_DATA: + { + const defaults = state.get('clearBrowsingDataDefaults') + const temp = state.get('tempClearBrowsingData', Immutable.Map()) + const clearData = defaults ? defaults.merge(temp) : temp + if (clearData.get('browserHistory')) { + state = historyState.clearSites() + filtering.clearHistory() + } + break + } + case appConstants.APP_ADD_HISTORY_SITE: + { + const isSyncEnabled = syncUtil.syncEnabled() + state = historyState.addSite(state, action.get('siteDetail')) + if (isSyncEnabled) { + state = syncUtil.updateSiteCache(state, action.get('siteDetail')) + } + break + } + + case appConstants.APP_ADD_HISTORY_SITES: + { + // TODO we need to do it like this until we implement action inside actions + action.get('historyList', Immutable.List()).forEach(site => appActions.addHistorySite(site)) + break + } + + case appConstants.APP_REMOVE_HISTORY_SITE: + { + const siteKey = siteUtil.getSiteKey(action.siteDetail) + state = historyState.removeSite(state, siteKey) + + if (syncUtil.syncEnabled()) { + syncActions.removeSite(historyState.getSite(state, siteKey)) + state = syncUtil.updateSiteCache(state, action.siteDetail) + } + break + } + } + + return state +} + +module.exports = historyReducer diff --git a/app/browser/reducers/pinnedSitesReducer.js b/app/browser/reducers/pinnedSitesReducer.js new file mode 100644 index 00000000000..a371c42c672 --- /dev/null +++ b/app/browser/reducers/pinnedSitesReducer.js @@ -0,0 +1,72 @@ +/* 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/. */ + +// State +const pinnedSitesState = require('../../common/state/pinnedSitesState') +const tabState = require('../../common/state/tabState') + +// Constants +const appConstants = require('../../../js/constants/appConstants') + +// Utils +const {makeImmutable} = require('../../common/state/immutableUtil') +const syncUtil = require('../../../js/state/syncUtil') +const pinnedSitesUtil = require('../../common/lib/pinnedSitesUtil') + +const historyReducer = (state, action, immutableAction) => { + action = immutableAction || makeImmutable(action) + switch (action.get('actionType')) { + case appConstants.APP_TAB_UPDATED: + { + if (action.getIn(['changeInfo', 'pinned']) != null) { + const pinned = action.getIn(['changeInfo', 'pinned']) + const tabId = action.getIn(['tabValue', 'tabId']) + const tab = tabState.getByTabId(state, tabId) + if (!tab) { + console.warn('Trying to pin a tabId which does not exist:', tabId, 'tabs: ', state.get('tabs').toJS()) + break + } + const sites = pinnedSitesState.getSites(state) + const siteDetail = pinnedSitesUtil.getDetailsFromTab(sites, tab) + if (pinned) { + state = pinnedSitesState.addPinnedSite(state, siteDetail) + } else { + state = pinnedSitesState.removePinnedSite(state, siteDetail) + } + if (syncUtil.syncEnabled()) { + state = syncUtil.updateSiteCache(state, siteDetail) + } + } + break + } + case appConstants.APP_CREATE_TAB_REQUESTED: + { + const createProperties = action.get('createProperties') + if (createProperties.get('pinned')) { + state = pinnedSitesUtil.addPinnedSite(state, pinnedSitesUtil.getDetailFromProperties(createProperties)) + } + break + } + case appConstants.APP_ON_PINNED_TAB_REORDER: + { + state = pinnedSitesState.reOrderSite( + state, + action.siteKey, + action.destinationKey, + action.prepend + ) + + // TODO do we need this for pinned sites? + if (syncUtil.syncEnabled()) { + const newSite = state.getIn(['pinnedSites', action.siteKey]) + state = syncUtil.updateSiteCache(state, newSite) + } + break + } + } + + return state +} + +module.exports = historyReducer diff --git a/app/browser/reducers/sitesReducer.js b/app/browser/reducers/sitesReducer.js deleted file mode 100644 index 6f5035404bf..00000000000 --- a/app/browser/reducers/sitesReducer.js +++ /dev/null @@ -1,192 +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/. */ - -'use strict' - -const appConstants = require('../../../js/constants/appConstants') -const filtering = require('../../filtering') -const siteCache = require('../../common/state/siteCache') -const siteTags = require('../../../js/constants/siteTags') -const siteUtil = require('../../../js/state/siteUtil') -const syncActions = require('../../../js/actions/syncActions') -const syncUtil = require('../../../js/state/syncUtil') -const Immutable = require('immutable') -const settings = require('../../../js/constants/settings') -const {getSetting} = require('../../../js/settings') -const writeActions = require('../../../js/constants/sync/proto').actions -const tabState = require('../../common/state/tabState') - -const syncEnabled = () => { - return getSetting(settings.SYNC_ENABLED) === true -} - -const updateTabBookmarked = (state, tabValue) => { - if (!tabValue || !tabValue.get('tabId')) { - return state - } - const bookmarked = siteUtil.isLocationBookmarked(state, tabValue.get('url')) - return tabState.updateTabValue(state, tabValue.set('bookmarked', bookmarked)) -} - -const updateActiveTabBookmarked = (state) => { - const tab = tabState.getActiveTab(state) - if (!tab) { - return state - } - return updateTabBookmarked(state, tab) -} - -const sitesReducer = (state, action, immutableAction) => { - switch (action.actionType) { - case appConstants.APP_SET_STATE: - state = siteCache.loadLocationSiteKeysCache(state) - break - case appConstants.APP_ON_CLEAR_BROWSING_DATA: - { - const defaults = state.get('clearBrowsingDataDefaults') - const temp = state.get('tempClearBrowsingData', Immutable.Map()) - const clearData = defaults ? defaults.merge(temp) : temp - if (clearData.get('browserHistory')) { - state = state.set('sites', siteUtil.clearHistory(state.get('sites'))) - filtering.clearHistory() - } - break - } - case appConstants.APP_ADD_SITE: - { - const isSyncEnabled = syncEnabled() - if (Immutable.List.isList(action.siteDetail)) { - action.siteDetail.forEach((s) => { - state = siteUtil.addSite(state, s, action.tag, action.skipSync) - if (isSyncEnabled) { - state = syncUtil.updateSiteCache(state, s) - } - }) - } else { - let sites = state.get('sites') - if (!action.siteDetail.get('folderId') && siteUtil.isFolder(action.siteDetail)) { - action.siteDetail = action.siteDetail.set('folderId', siteUtil.getNextFolderId(sites)) - } - state = siteUtil.addSite(state, action.siteDetail, action.tag, action.skipSync) - if (isSyncEnabled) { - state = syncUtil.updateSiteCache(state, action.siteDetail) - } - } - break - } - case appConstants.APP_ADD_BOOKMARK: - case appConstants.APP_EDIT_BOOKMARK: - { - const isSyncEnabled = syncEnabled() - const sites = state.get('sites') - const closestKey = action.closestKey - let site = action.siteDetail - - if (site == null || action.tag == null) { - break - } - - if (!site.get('folderId') && action.tag === siteTags.BOOKMARK_FOLDER) { - site = site.set('folderId', siteUtil.getNextFolderId(sites)) - } - - state = siteUtil.addSite(state, site, action.tag, action.editKey) - - if (closestKey != null) { - const sourceKey = siteUtil.getSiteKey(site) - state = siteUtil.moveSite(state, sourceKey, closestKey, false, false, true) - } - - if (isSyncEnabled) { - state = syncUtil.updateSiteCache(state, site) - } - - state = updateActiveTabBookmarked(state) - break - } - case appConstants.APP_REMOVE_SITE: - const removeSiteSyncCallback = action.skipSync ? undefined : syncActions.removeSite - state = siteUtil.removeSite(state, action.siteDetail, action.tag, true, removeSiteSyncCallback) - if (syncEnabled()) { - state = syncUtil.updateSiteCache(state, action.siteDetail) - } - state = updateActiveTabBookmarked(state) - break - case appConstants.APP_MOVE_SITE: - state = siteUtil.moveSite(state, - action.sourceKey, action.destinationKey, action.prepend, - action.destinationIsParent, false) - state = state.set('sites', state.get('sites').sort(siteUtil.siteSort)) - if (syncEnabled()) { - const destinationDetail = state.getIn(['sites', action.destinationKey]) - state = syncUtil.updateSiteCache(state, destinationDetail) - } - break - case appConstants.APP_APPLY_SITE_RECORDS: - let nextFolderId = siteUtil.getNextFolderId(state.get('sites')) - // Ensure that all folders are assigned folderIds - action.records.forEach((record, i) => { - if (record.action !== writeActions.DELETE && - record.bookmark && record.bookmark.isFolder && - record.bookmark.site && - typeof record.bookmark.site.folderId !== 'number') { - record.bookmark.site.folderId = nextFolderId - action.records.set(i, record) - nextFolderId = nextFolderId + 1 - } - }) - action.records.forEach((record) => { - if (record.action === writeActions.DELETE) { - state = siteUtil.removeSiteByObjectId(state, record.objectId, record.objectData) - } else { - const siteData = syncUtil.getSiteDataFromRecord(record, state, action.records) - const tag = siteData.tag - let siteDetail = siteData.siteDetail - switch (record.action) { - case writeActions.CREATE: - state = siteUtil.addSite(state, siteDetail, tag, undefined, true) - break - case writeActions.UPDATE: - state = siteUtil.addSite(state, siteDetail, tag, siteData.existingObjectData, true) - break - } - state = syncUtil.updateSiteCache(state, siteDetail) - } - }) - break - case appConstants.APP_TAB_UPDATED: - if (immutableAction.getIn(['changeInfo', 'pinned']) != null) { - const pinned = immutableAction.getIn(['changeInfo', 'pinned']) - const tabId = immutableAction.getIn(['tabValue', 'tabId']) - const tab = state.get('tabs').find((tab) => tab.get('tabId') === tabId) - if (!tab) { - console.warn('Trying to pin a tabId which does not exist:', tabId, 'tabs: ', state.get('tabs').toJS()) - break - } - const sites = state.get('sites') - const siteDetail = siteUtil.getDetailFromTab(tab, siteTags.PINNED, sites) - if (pinned) { - state = siteUtil.addSite(state, siteDetail, siteTags.PINNED) - } else { - state = siteUtil.removeSite(state, siteDetail, siteTags.PINNED) - } - if (syncEnabled()) { - state = syncUtil.updateSiteCache(state, siteDetail) - } - } - state = updateTabBookmarked(state, action.tabValue) - break - case appConstants.APP_CREATE_TAB_REQUESTED: { - const createProperties = immutableAction.get('createProperties') - if (createProperties.get('pinned')) { - state = siteUtil.addSite(state, - siteUtil.getDetailFromCreateProperties(createProperties), siteTags.PINNED) - } - break - } - } - return state -} - -module.exports = sitesReducer diff --git a/app/browser/reducers/urlBarSuggestionsReducer.js b/app/browser/reducers/urlBarSuggestionsReducer.js index 8873091a9ad..b69c594a0ef 100644 --- a/app/browser/reducers/urlBarSuggestionsReducer.js +++ b/app/browser/reducers/urlBarSuggestionsReducer.js @@ -4,16 +4,18 @@ 'use strict' +const Immutable = require('immutable') const appConstants = require('../../../js/constants/appConstants') const {generateNewSuggestionsList, generateNewSearchXHRResults} = require('../../common/lib/suggestion') const {init, add} = require('../../common/lib/siteSuggestions') -const Immutable = require('immutable') const {makeImmutable} = require('../../common/state/immutableUtil') const tabState = require('../../common/state/tabState') +const historyState = require('../../common/state/historyState') +const bookmarksState = require('../../common/state/bookmarksState') const urlBarSuggestionsReducer = (state, action) => { switch (action.actionType) { - case appConstants.APP_ADD_SITE: + case appConstants.APP_ADD_HISTORY_SITE: case appConstants.APP_ADD_BOOKMARK: case appConstants.APP_EDIT_BOOKMARK: if (Immutable.List.isList(action.siteDetail)) { @@ -25,7 +27,9 @@ const urlBarSuggestionsReducer = (state, action) => { } break case appConstants.APP_SET_STATE: - init(Object.values(action.appState.get('sites').toJS())) + const bookmarks = bookmarksState.getBookmarks(action.appState) + const history = historyState.getSites(action.appState) + init(Object.values(bookmarks.concat(history).toJS())) break case appConstants.APP_URL_BAR_TEXT_CHANGED: generateNewSuggestionsList(state, action.windowId, action.tabId, action.input) diff --git a/app/browser/tabs.js b/app/browser/tabs.js index d2e87933b1e..4e03c422565 100644 --- a/app/browser/tabs.js +++ b/app/browser/tabs.js @@ -22,12 +22,15 @@ const messages = require('../../js/constants/messages') const aboutHistoryState = require('../common/state/aboutHistoryState') const appStore = require('../../js/stores/appStore') const appConfig = require('../../js/constants/appConfig') -const siteTags = require('../../js/constants/siteTags') const {newTabMode} = require('../common/constants/settingsEnums') const {cleanupWebContents, currentWebContents, getWebContents, updateWebContents} = require('./webContentsCache') const {FilterOptions} = require('ad-block') const {isResourceEnabled} = require('../filtering') const autofill = require('../autofill') +const bookmarksState = require('../common/state/bookmarksState') +const bookmarkFoldersState = require('../common/state/bookmarkFoldersState') +const historyState = require('../common/state/historyState') +const bookmarkOrderCache = require('../common/cache/bookmarkOrderCache') let currentPartitionNumber = 0 const incrementPartitionNumber = () => ++currentPartitionNumber @@ -134,21 +137,12 @@ ipcMain.on(messages.ABOUT_COMPONENT_INITIALIZED, (e) => { }) }) -const getBookmarksData = function (state) { - let bookmarkSites = new Immutable.OrderedMap() - let bookmarkFolderSites = new Immutable.OrderedMap() - state.get('sites').forEach((site, siteKey) => { - const tags = site.get('tags') - if (tags.includes(siteTags.BOOKMARK)) { - bookmarkSites = bookmarkSites.set(siteKey, site) - } - if (tags.includes(siteTags.BOOKMARK_FOLDER)) { - bookmarkFolderSites = bookmarkFolderSites.set(siteKey, site) - } - }) - const bookmarks = bookmarkSites.toList().toJS() - const bookmarkFolders = bookmarkFolderSites.toList().toJS() - return {bookmarks, bookmarkFolders} +const getBookmarksData = (state) => { + return { + bookmarks: bookmarksState.getBookmarks(state).toJS(), + bookmarkFolders: bookmarkFoldersState.getFolders(state).toJS(), + bookmarkOrder: bookmarkOrderCache.getOrderCache(state).toJS() + } } const updateAboutDetails = (tab, tabValue) => { @@ -812,7 +806,7 @@ const api = { getHistoryEntries: (state, action) => { const tab = getWebContents(action.get('tabId')) - const sites = state ? state.get('sites') : null + const sites = state ? historyState.getSites(state) : null if (tab && !tab.isDestroyed()) { let history = { @@ -836,7 +830,7 @@ const api = { // TODO: return brave lion (or better: get icon from extension if possible as data URI) } else { if (sites) { - const site = sites.find(function (element) { return element.get('location') === url }) + const site = sites.find((element) => element.get('location') === url) if (site) { entry.icon = site.get('favicon') } diff --git a/app/browser/windows.js b/app/browser/windows.js index 028aee44eed..fec40cd6d14 100644 --- a/app/browser/windows.js +++ b/app/browser/windows.js @@ -9,14 +9,14 @@ const debounce = require('../../js/lib/debounce') const {getSetting} = require('../../js/settings') const locale = require('../locale') const LocalShortcuts = require('../localShortcuts') -const {getPinnedSiteProps} = require('../common/lib/windowsUtil') const {makeImmutable} = require('../common/state/immutableUtil') const {getPinnedTabsByWindowId} = require('../common/state/tabState') const messages = require('../../js/constants/messages') const settings = require('../../js/constants/settings') -const siteTags = require('../../js/constants/siteTags') const windowState = require('../common/state/windowState') const Immutable = require('immutable') +const pinnedSitesState = require('../common/state/pinnedSitesState') +const pinnedSitesUtil = require('../common/lib/pinnedSitesUtil') // TODO(bridiver) - set window uuid let currentWindows = {} @@ -69,8 +69,7 @@ const updatePinnedTabs = (win) => { const appStore = require('../../js/stores/appStore') const state = appStore.getState() const windowId = win.id - const pinnedSites = state.get('sites').toList().filter((site) => - site.get('tags').includes(siteTags.PINNED)).map(site => getPinnedSiteProps(site)) + const pinnedSites = pinnedSitesState.getSites(state).map(site => pinnedSitesUtil.getPinnedSiteProps(site)) const pinnedTabs = getPinnedTabsByWindowId(state, windowId) pinnedSites.filter((site) => diff --git a/app/common/cache/bookmarkOrderCache.js b/app/common/cache/bookmarkOrderCache.js new file mode 100644 index 00000000000..3b47e30fa27 --- /dev/null +++ b/app/common/cache/bookmarkOrderCache.js @@ -0,0 +1,131 @@ +const Immutable = require('immutable') +const siteTags = require('../../../js/constants/siteTags') +const bookmarkFoldersUtil = require('../lib/bookmarkFoldersUtil') + +const setOrder = (cache, key, tag, destinationKey, append = true) => { + let newCache = Immutable.List() + let i = 0 + + for (let item of cache) { + if (item.get('key') === destinationKey) { + if (append) { + newCache = newCache.push(item.set('order', i)) + i++ + newCache = newCache.push(Immutable.fromJS({ + key: key, + order: i, + type: tag + })) + } else { + newCache = newCache.push(Immutable.fromJS({ + key: key, + order: i, + type: tag + })) + i++ + newCache = newCache.push(item.set('order', i)) + } + } else if (item.get('key') === key) { + continue + } else { + newCache = newCache.push(item.set('order', i)) + } + i++ + } + + return newCache +} + +const addCacheItem = (state, parentId = 0, key, destinationKey, tag, append) => { + parentId = parentId.toString() + key = key.toString() + // cache with this parentId doesn't exist yet + if (!state.hasIn(['bookmarkOrderCache', parentId])) { + return state.setIn(['bookmarkOrderCache', parentId], Immutable.fromJS([ + { + key: key, + order: 0, + type: tag + } + ])) + } + + const cache = state.getIn(['bookmarkOrderCache', parentId]) + // destination key is not provided + if (destinationKey == null) { + return state.setIn(['bookmarkOrderCache', parentId], cache.push(Immutable.fromJS( + { + key: key, + order: cache.size, + type: tag + } + ))) + } + + // destination key is given + const newCache = setOrder(cache, key, tag, destinationKey, append) + return state.setIn(['bookmarkOrderCache', parentId], newCache) +} + +const addBookmarkToCache = (state, parentId, key, destinationKey, append) => { + return addCacheItem(state, parentId, key, destinationKey, siteTags.BOOKMARK, append) +} + +const addFolderToCache = (state, parentId, key, destinationKey, append) => { + return addCacheItem(state, parentId, key, destinationKey, siteTags.BOOKMARK_FOLDER, append) +} + +const getFoldersByParentId = (state, parentId) => { + return state.getIn(['bookmarkOrderCache', parentId.toString()], Immutable.List()) + .filter(item => bookmarkFoldersUtil.isFolder(item)) +} + +const getBookmarksByParentId = (state, parentId = 0) => { + return state.getIn(['bookmarkOrderCache', parentId.toString()], Immutable.List()) + .filter(item => bookmarkFoldersUtil.isFolder(item)) +} + +const getBookmarksWithFolders = (state, parentId) => { + return state.getIn(['bookmarkOrderCache', parentId.toString()], Immutable.List()) +} + +const removeCacheKey = (state, parentId, key) => { + parentId = parentId.toString() + key = key.toString() + const cache = state.getIn(['bookmarkOrderCache', parentId]) + + if (cache == null) { + return state + } + + let newCache = Immutable.List() + let i = 0 + + for (let item of cache) { + if (item.get('key') !== key) { + newCache = newCache.push(item.set('order', i)) + i++ + } + } + + return state.setIn(['bookmarkOrderCache', parentId], newCache) +} + +const removeCacheParent = (state, parentId) => { + return state.deleteIn(['bookmarkOrderCache', parentId.toString()]) +} + +const getOrderCache = (state) => { + return state.get('bookmarkOrderCache', Immutable.Map()) +} + +module.exports = { + addBookmarkToCache, + addFolderToCache, + removeCacheKey, + getFoldersByParentId, + getBookmarksByParentId, + getBookmarksWithFolders, + removeCacheParent, + getOrderCache +} diff --git a/app/common/lib/bookmarkFoldersUtil.js b/app/common/lib/bookmarkFoldersUtil.js new file mode 100644 index 00000000000..9afde39b0cb --- /dev/null +++ b/app/common/lib/bookmarkFoldersUtil.js @@ -0,0 +1,62 @@ +/* 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 siteTags = require('../../../js/constants/siteTags') + +const isFolderNameValid = (title) => { + return title != null && title.trim().length > 0 +} + +const getNextFolderIdItem = (folders) => + folders.max((folderA, folderB) => { + const folderIdA = folderA.get('folderId') + const folderIdB = folderB.get('folderId') + if (folderIdA === folderIdB) { + return 0 + } + if (folderIdA === undefined) { + return false + } + if (folderIdB === undefined) { + return true + } + return folderIdA > folderIdB + }) + +const getNextFolderId = (folders) => { + const defaultFolderId = 0 + if (!folders) { + return defaultFolderId + } + const maxIdItem = getNextFolderIdItem(folders) + return (maxIdItem ? (maxIdItem.get('folderId') || 0) : 0) + 1 +} + +const getNextFolderName = (folders, name) => { + if (!folders) { + return name + } + const site = folders.find((site) => site.get('title') === name) + if (!site) { + return name + } + const filenameFormat = /(.*) \((\d+)\)/ + let result = filenameFormat.exec(name) + if (!result) { + return getNextFolderName(folders, name + ' (1)') + } + + const nextNum = parseInt(result[2]) + 1 + return getNextFolderName(folders, result[1] + ' (' + nextNum + ')') +} + +const isFolder = (folder) => { + return folder.get('type') === siteTags.BOOKMARK_FOLDER +} + +module.exports = { + isFolderNameValid, + getNextFolderId, + getNextFolderName, + isFolder +} diff --git a/app/common/lib/bookmarkUtil.js b/app/common/lib/bookmarkUtil.js index a8c399db21f..ef68614c403 100644 --- a/app/common/lib/bookmarkUtil.js +++ b/app/common/lib/bookmarkUtil.js @@ -4,25 +4,24 @@ const Immutable = require('immutable') +// State +const bookmarksState = require('../state/bookmarksState') +const tabState = require('../state/tabState') + // Constants const dragTypes = require('../../../js/constants/dragTypes') const {bookmarksToolbarMode} = require('../constants/settingsEnums') const settings = require('../../../js/constants/settings') +const siteTags = require('../../../js/constants/siteTags') // Utils const domUtil = require('../../renderer/lib/domUtil') -const siteUtil = require('../../../js/state/siteUtil') +const siteCache = require('../state/siteCache') const {calculateTextWidth} = require('../../../js/lib/textCalculator') const {iconSize} = require('../../../js/constants/config') const {getSetting} = require('../../../js/settings') -function bookmarkHangerHeading (editMode, isFolder, isAdded) { - if (isFolder) { - return editMode - ? 'bookmarkFolderEditing' - : 'bookmarkFolderAdding' - } - +const bookmarkHangerHeading = (editMode, isAdded) => { if (isAdded) { return 'bookmarkAdded' } @@ -32,19 +31,8 @@ function bookmarkHangerHeading (editMode, isFolder, isAdded) { : 'bookmarkCreateNew' } -const displayBookmarkName = (detail) => { - const customTitle = detail.get('customTitle') - if (customTitle !== undefined && customTitle !== '') { - return customTitle || '' - } - return detail.get('title') || '' -} - -const isBookmarkNameValid = (title, location, isFolder, customTitle) => { - const newTitle = title || customTitle - return isFolder - ? (newTitle != null && newTitle !== 0) && newTitle.trim().length > 0 - : location != null && location.trim().length > 0 +const isBookmarkNameValid = (location) => { + return location != null && location.trim().length > 0 } const showOnlyFavicon = () => { @@ -65,23 +53,21 @@ const getDNDBookmarkData = (state, bookmarkKey) => { return data.get('draggingOverKey') === bookmarkKey ? data : Immutable.Map() } -let oldSites +let oldBookmarks let lastValue let lastWidth const getToolbarBookmarks = (state) => { - const sites = state.get('sites', Immutable.List()) + const bookmarks = bookmarksState.getBookmarksWithFolders(state) const windowWidth = window.innerWidth - if (sites === oldSites && + if (bookmarks === oldBookmarks && lastWidth === windowWidth && - lastValue) { + lastValue + ) { return lastValue } - oldSites = sites + oldBookmarks = bookmarks lastWidth = windowWidth - const noParentItems = siteUtil.getBookmarks(sites) - .filter((bookmark) => !bookmark.get('parentFolderId')) - .sort(siteUtil.siteSort) let widthAccountedFor = 0 const overflowButtonWidth = 25 const onlyFavicon = showOnlyFavicon() @@ -104,7 +90,7 @@ const getToolbarBookmarks = (state) => { // Loop through until we fill up the entire bookmark toolbar width let i = 0 - for (let bookmark of noParentItems) { + for (let bookmark of bookmarks) { const current = bookmark[1] let iconWidth = favicon ? iconSize : 0 // font-awesome file icons are 3px smaller @@ -115,7 +101,7 @@ const getToolbarBookmarks = (state) => { if (favicon && onlyFavicon) { widthAccountedFor += padding + iconWidth + currentChevronWidth } else { - const text = current.get('customTitle') || current.get('title') || current.get('location') + const text = current.get('title') || current.get('location') widthAccountedFor += Math.min(calculateTextWidth(text, `${fontSize} ${fontFamily}`) + padding + iconWidth + currentChevronWidth, maxWidth) } widthAccountedFor += margin @@ -125,20 +111,99 @@ const getToolbarBookmarks = (state) => { i++ } - lastValue = { - visibleBookmarks: noParentItems.take(i).map((item, index) => index).toList(), + return { + visibleBookmarks: bookmarks.take(i).map((item, index) => index).toList(), // Show at most 100 items in the overflow menu - hiddenBookmarks: noParentItems.skip(i).take(100).map((item, index) => index).toList() + hiddenBookmarks: bookmarks.skip(i).take(100).map((item, index) => index).toList() + } +} + +const getDetailFromFrame = (frame) => { + return Immutable.fromJS({ + location: frame.get('location'), + title: frame.get('title'), + partitionNumber: frame.get('partitionNumber'), + favicon: frame.get('icon'), + themeColor: frame.get('themeColor') || frame.get('computedThemeColor') + }) +} + +/** + * Checks if a location is bookmarked. + * + * @param state The application state Immutable map + * @param {string} location + * @return {boolean} + */ +const isLocationBookmarked = (state, location) => { + const bookmarks = bookmarksState.getBookmarks(state) + const siteKeys = siteCache.getLocationSiteKeys(state, location) + + if (!siteKeys || siteKeys.length === 0) { + return false + } + + return siteKeys.some(key => bookmarks.has(key)) +} + +/** + * Converts a siteDetail to createProperties format + * @param {Object} bookmark - A bookmark detail as per app state + * @return {Object} A createProperties plain JS object, not ImmutableJS + */ +const toCreateProperties = (bookmark) => { + return { + url: bookmark.get('location'), + partitionNumber: bookmark.get('partitionNumber') + } +} + +/** + * Filters bookmarks relative to a parent folder + * @param state - The application state + * @param folderKey The folder key to filter to + */ +const getBookmarksByParentId = (state, folderKey) => { + const bookmarks = bookmarksState.getBookmarks(state) + if (!folderKey) { + return bookmarks + } + + return bookmarks.filter((bookmark) => bookmark.get('parentFolderId') === folderKey) +} + +const isBookmark = (bookmark) => { + return bookmark.get('type') === siteTags.BOOKMARK +} + +const updateTabBookmarked = (state, tabValue) => { + if (!tabValue || !tabValue.get('tabId')) { + return state + } + const bookmarked = isLocationBookmarked(state, tabValue.get('url')) + return tabState.updateTabValue(state, tabValue.set('bookmarked', bookmarked)) +} + +const updateActiveTabBookmarked = (state) => { + const tab = tabState.getActiveTab(state) + if (!tab) { + return state } - return lastValue + return updateTabBookmarked(state, tab) } module.exports = { bookmarkHangerHeading, - displayBookmarkName, isBookmarkNameValid, showOnlyFavicon, showFavicon, getDNDBookmarkData, - getToolbarBookmarks + getToolbarBookmarks, + getDetailFromFrame, + isLocationBookmarked, + toCreateProperties, + getBookmarksByParentId, + isBookmark, + updateTabBookmarked, + updateActiveTabBookmarked } diff --git a/app/common/lib/historyUtil.js b/app/common/lib/historyUtil.js index c3cade0a637..e769b492e04 100644 --- a/app/common/lib/historyUtil.js +++ b/app/common/lib/historyUtil.js @@ -8,15 +8,13 @@ const {makeImmutable} = require('../state/immutableUtil') const siteUtil = require('../../../js/state/siteUtil') const aboutHistoryMaxEntries = 500 -module.exports.maxEntries = aboutHistoryMaxEntries - const sortTimeDescending = (left, right) => { if (left.get('lastAccessedTime') < right.get('lastAccessedTime')) return 1 if (left.get('lastAccessedTime') > right.get('lastAccessedTime')) return -1 return 0 } -module.exports.getHistory = (sites) => { +const getHistory = (sites) => { sites = makeImmutable(sites) ? makeImmutable(sites).toList() : new Immutable.List() return sites.filter((site) => siteUtil.isHistoryEntry(site)) .sort(sortTimeDescending) @@ -30,7 +28,7 @@ const getDayString = (entry, locale) => { : '' } -module.exports.groupEntriesByDay = (history, locale) => { +const groupEntriesByDay = (history, locale) => { const reduced = history.reduce((previousValue, currentValue, currentIndex, array) => { const result = currentIndex === 1 ? [] : previousValue if (currentIndex === 1) { @@ -60,7 +58,7 @@ module.exports.groupEntriesByDay = (history, locale) => { * Return an array with ALL entries. * Format is expected to be array containing one array per day. */ -module.exports.totalEntries = (entriesByDay) => { +const totalEntries = (entriesByDay) => { entriesByDay = makeImmutable(entriesByDay) || new Immutable.List() let result = new Immutable.List() @@ -69,3 +67,61 @@ module.exports.totalEntries = (entriesByDay) => { }) return result } + +const prepareHistoryEntry = (siteDetail) => { + return makeImmutable({ + lastAccessedTime: new Date().getTime(), + objectId: undefined, + title: siteDetail.get('title'), + location: siteDetail.get('location'), + themeColor: siteDetail.get('themeColor'), + favicon: siteDetail.get('favicon', siteDetail.get('icon')), + count: 1 + }) +} + +const mergeSiteDetails = (oldDetail, newDetail) => { + const objectId = newDetail.has('objectId') ? newDetail.get('objectId') : oldDetail.get('objectId', undefined) + + let site = makeImmutable({ + title: newDetail.get('title'), + location: newDetail.get('location'), + count: ~~oldDetail.get('count', 0) + 1, + lastAccessedTime: new Date().getTime(), + objectId + }) + + const themeColor = newDetail.has('themeColor') ? newDetail.get('themeColor') : oldDetail.get('themeColor') + if (themeColor) { + site = site.set('themeColor', themeColor) + } + + // we need to have a fallback to icon, because frame has icon for it + const favicon = (newDetail.has('favicon') || newDetail.has('icon')) + ? newDetail.get('favicon', newDetail.get('icon')) + : oldDetail.get('favicon') + if (favicon) { + site = site.set('favicon', favicon) + } + + return site +} + +const getDetailFromFrame = (frame) => { + return makeImmutable({ + location: frame.get('location'), + title: frame.get('title'), + partitionNumber: frame.get('partitionNumber'), + favicon: frame.get('icon'), + themeColor: frame.get('themeColor') || frame.get('computedThemeColor') + }) +} + +module.exports = { + getHistory, + groupEntriesByDay, + totalEntries, + prepareHistoryEntry, + mergeSiteDetails, + getDetailFromFrame +} diff --git a/app/common/lib/menuUtil.js b/app/common/lib/menuUtil.js index 65f24d89987..b68bb4e53ea 100644 --- a/app/common/lib/menuUtil.js +++ b/app/common/lib/menuUtil.js @@ -4,16 +4,25 @@ 'use strict' const MenuItem = require('electron').MenuItem + +// Constants +const config = require('../../../js/constants/config') + +// State +const bookmarksState = require('../state/bookmarksState') + +// Actions +const appActions = require('../../../js/actions/appActions') +const windowActions = require('../../../js/actions/windowActions') + +// Utils const {makeImmutable} = require('../../common/state/immutableUtil') const CommonMenu = require('../../common/commonMenu') -const siteTags = require('../../../js/constants/siteTags') const eventUtil = require('../../../js/lib/eventUtil') -const siteUtil = require('../../../js/state/siteUtil') const locale = require('../../locale') -const appActions = require('../../../js/actions/appActions') -const config = require('../../../js/constants/config') const {separatorMenuItem} = require('../../common/commonMenu') -const windowActions = require('../../../js/actions/windowActions') +const bookmarkUtil = require('./bookmarkUtil') +const bookmarkFoldersUtil = require('./bookmarkFoldersUtil') /** * Get the an electron MenuItem object from a Menu based on its label @@ -73,14 +82,12 @@ module.exports.setTemplateItemChecked = (template, label, checked) => { return null } -const createBookmarkTemplateItems = (bookmarks, parentFolderId) => { - const filteredBookmarks = parentFolderId - ? bookmarks.filter((bookmark) => bookmark.get('parentFolderId') === parentFolderId) - : bookmarks.filter((bookmark) => !bookmark.get('parentFolderId')) +const createBookmarkTemplateItems = (state, parentFolderId) => { + const bookmarks = bookmarksState.getBookmarksWithFolders(state, parentFolderId) const payload = [] - filteredBookmarks.forEach((site) => { - if (site.get('tags').includes(siteTags.BOOKMARK) && site.get('location')) { + bookmarks.forEach((site) => { + if (bookmarkUtil.isBookmark(site)) { payload.push({ // TODO include label made from favicon. It needs to be of type NativeImage // which can be made using a Buffer / DataURL / local image @@ -89,7 +96,7 @@ const createBookmarkTemplateItems = (bookmarks, parentFolderId) => { // and as such there may need to be another mechanism or cache // // see: https://github.com/brave/browser-laptop/issues/3050 - label: site.get('customTitle') || site.get('title') || site.get('location'), + label: site.get('title', site.get('location')), click: (item, focusedWindow, e) => { if (eventUtil.isForSecondaryAction(e)) { appActions.createTabRequested({ @@ -102,12 +109,10 @@ const createBookmarkTemplateItems = (bookmarks, parentFolderId) => { } } }) - } else if (siteUtil.isFolder(site)) { - const folderId = site.get('folderId') - const submenuItems = bookmarks.filter((bookmark) => bookmark.get('parentFolderId') === folderId) + } else if (bookmarkFoldersUtil.isFolder(site)) { payload.push({ - label: site.get('customTitle') || site.get('title'), - submenu: submenuItems.count() > 0 ? createBookmarkTemplateItems(bookmarks, folderId) : null + label: site.get('title'), + submenu: createBookmarkTemplateItems(state, site.get('folderId')) }) } }) @@ -117,10 +122,10 @@ const createBookmarkTemplateItems = (bookmarks, parentFolderId) => { /** * Used to create bookmarks and bookmark folder entries for the "Bookmarks" menu * - * @param sites The application state's Immutable sites list + * @param state The application state */ -module.exports.createBookmarkTemplateItems = (sites) => { - return createBookmarkTemplateItems(siteUtil.getBookmarks(sites)) +module.exports.createBookmarkTemplateItems = (state) => { + return createBookmarkTemplateItems(state) } /** diff --git a/app/common/lib/pinnedSitesUtil.js b/app/common/lib/pinnedSitesUtil.js new file mode 100644 index 00000000000..d9662763128 --- /dev/null +++ b/app/common/lib/pinnedSitesUtil.js @@ -0,0 +1,115 @@ +/* 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 Immutable = require('immutable') +const siteUtil = require('../../../js/state/siteUtil') +const {makeImmutable} = require('../state/immutableUtil') + +const getSitesBySubkey = (sites, siteKey) => { + if (!sites || !siteKey) { + return makeImmutable([]) + } + const splitKey = siteKey.split('|', 2) + const partialKey = splitKey.join('|') + const matches = sites.filter((site, key) => { + return key.indexOf(partialKey) > -1 + }) + return matches.toList() +} + +const getDetailsFromTab = (sites, tab) => { + let location = tab.get('url') + const partitionNumber = tab.get('partitionNumber') + let parentFolderId + + // TODO check if needed https://github.com/brave/browser-laptop/pull/8588 + // we need to find which sites should be send in, I am guessing bookmarks + + // if site map is available, look up extra information: + // - original url (if redirected) + // - parent folder id + if (sites) { + // get all sites matching URL and partition (disregarding parentFolderId) + let siteKey = siteUtil.getSiteKey(makeImmutable({location, partitionNumber})) + let results = getSitesBySubkey(sites, siteKey) + + // only check for provisional location if entry is not found + if (results.size === 0) { + // if provisional location is different, grab any results which have that URL + // this may be different if the site was redirected + const provisionalLocation = tab.getIn(['frame', 'provisionalLocation']) + if (provisionalLocation && provisionalLocation !== location) { + siteKey = siteUtil.getSiteKey(makeImmutable({ + location: provisionalLocation, + partitionNumber + })) + results = results.merge(getSitesBySubkey(sites, siteKey)) + } + } + + // update details which get returned below + if (results.size > 0) { + location = results.getIn([0, 'location']) + parentFolderId = results.getIn([0, 'parentFolderId']) + } + } + + const siteDetail = { + location: location, + title: tab.get('title') + } + + // TODO I think that we don't need this one + if (partitionNumber) { + siteDetail.partitionNumber = partitionNumber + } + + if (parentFolderId) { + siteDetail.parentFolderId = parentFolderId + } + + return makeImmutable(siteDetail) +} + +const getDetailFromProperties = (createProperties) => { + const siteDetail = { + location: createProperties.get('url') + } + + if (createProperties.get('partitionNumber') !== undefined) { + siteDetail.partitionNumber = createProperties.get('partitionNumber') + } + return makeImmutable(siteDetail) +} + +const getDetailFromFrame = (frame) => { + const pinnedLocation = frame.get('pinnedLocation') + let location = frame.get('location') + if (pinnedLocation !== 'about:blank') { + location = pinnedLocation + } + + return makeImmutable({ + location, + title: frame.get('title'), + partitionNumber: frame.get('partitionNumber'), + favicon: frame.get('icon'), + themeColor: frame.get('themeColor') || frame.get('computedThemeColor') + }) +} + +const getPinnedSiteProps = site => { + return Immutable.fromJS({ + location: site.get('location'), + order: site.get('order'), + partitionNumber: site.get('partitionNumber', 0) + }) +} + +module.exports = { + getDetailsFromTab, + getDetailFromProperties, + getDetailFromFrame, + getPinnedSiteProps +} diff --git a/app/common/lib/siteSuggestions.js b/app/common/lib/siteSuggestions.js index 229d1bb4841..e2c40e21255 100644 --- a/app/common/lib/siteSuggestions.js +++ b/app/common/lib/siteSuggestions.js @@ -61,11 +61,8 @@ const tokenizeInput = (data) => { return [] } url = data.location - if (data.customTitle) { - parts = getPartsFromNonUrlInput(data.customTitle) - } if (data.title) { - parts = parts.concat(getPartsFromNonUrlInput(data.title)) + parts = getPartsFromNonUrlInput(data.title) } if (data.tags) { parts = parts.concat(data.tags.map(getTagToken)) diff --git a/app/common/lib/windowsUtil.js b/app/common/lib/windowsUtil.js deleted file mode 100644 index 4141a5a060d..00000000000 --- a/app/common/lib/windowsUtil.js +++ /dev/null @@ -1,17 +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 Immutable = require('immutable') - -const getPinnedSiteProps = site => { - return Immutable.fromJS({ - location: site.get('location'), - order: site.get('order'), - partitionNumber: site.get('partitionNumber') || 0 - }) -} - -module.exports = { - getPinnedSiteProps -} diff --git a/app/common/state/aboutHistoryState.js b/app/common/state/aboutHistoryState.js index 6f9048993e6..f52a4fede69 100644 --- a/app/common/state/aboutHistoryState.js +++ b/app/common/state/aboutHistoryState.js @@ -2,7 +2,9 @@ * 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 Immutable = require('immutable') const {makeImmutable} = require('./immutableUtil') +const historyState = require('./historyState') const historyUtil = require('../lib/historyUtil') const aboutHistoryState = { @@ -10,10 +12,17 @@ const aboutHistoryState = { state = makeImmutable(state) return state.getIn(['about', 'history']) }, + setHistory: (state) => { state = makeImmutable(state) - state = state.setIn(['about', 'history', 'entries'], - historyUtil.getHistory(state.get('sites'))) + const sites = historyState.getSites(state) + state = state.setIn(['about', 'history', 'entries'], historyUtil.getHistory(sites)) + return state.setIn(['about', 'history', 'updatedStamp'], new Date().getTime()) + }, + + clearHistory: (state) => { + state = makeImmutable(state) + state = state.setIn(['about', 'history', 'entries'], Immutable.Map()) return state.setIn(['about', 'history', 'updatedStamp'], new Date().getTime()) } } diff --git a/app/common/state/aboutNewTabState.js b/app/common/state/aboutNewTabState.js index ea855edaf04..ac9a551bdd2 100644 --- a/app/common/state/aboutNewTabState.js +++ b/app/common/state/aboutNewTabState.js @@ -31,6 +31,13 @@ const aboutNewTabState = { topSites = makeImmutable(topSites) state = state.setIn(['about', 'newtab', 'sites'], topSites) return state.setIn(['about', 'newtab', 'updatedStamp'], new Date().getTime()) + }, + + clearTopSites: (state) => { + state = makeImmutable(state) + + state = state.setIn(['about', 'newtab', 'sites'], makeImmutable([])) + return state.setIn(['about', 'newtab', 'updatedStamp'], new Date().getTime()) } } diff --git a/app/common/state/bookmarkFoldersState.js b/app/common/state/bookmarkFoldersState.js new file mode 100644 index 00000000000..2a551fb90ee --- /dev/null +++ b/app/common/state/bookmarkFoldersState.js @@ -0,0 +1,176 @@ +/* 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 assert = require('assert') +const Immutable = require('immutable') + +// Actions +const syncActions = require('../../../js/actions/syncActions') + +// Constants +const settings = require('../../../js/constants/settings') +const siteTags = require('../../../js/constants/siteTags') + +// State +const bookmarkOrderCache = require('../cache/bookmarkOrderCache') + +// Utils +const bookmarkFoldersUtil = require('../lib/bookmarkFoldersUtil') +const siteUtil = require('../../../js/state/siteUtil') +const {makeImmutable, isMap} = require('./immutableUtil') +const {getSetting} = require('../../../js/settings') + +const validateState = function (state) { + state = makeImmutable(state) + assert.ok(isMap(state), 'state must be an Immutable.Map') + assert.ok(isMap(state.get('bookmarkFolders')), 'state must contain an Immutable.Map of bookmarkFolders') + return state +} + +const bookmarkFoldersState = { + getFolders: (state) => { + state = validateState(state) + return state.get('bookmarkFolders', Immutable.Map()) + }, + + getFolder: (state, folderKey) => { + state = validateState(state) + folderKey = folderKey.toString() + return state.getIn(['bookmarkFolders', folderKey], Immutable.Map()) + }, + + getFoldersByParentId: (state, parentFolderId) => { + state = validateState(state) + + const folders = bookmarkOrderCache.getFoldersByParentId(state, parentFolderId) + return folders.map(folder => bookmarkFoldersState.getFolder(state, folder.get('key'))) + }, + + addFolder: (state, folderDetails, destinationKey) => { + state = validateState(state) + folderDetails = makeImmutable(folderDetails) + let folders = bookmarkFoldersState.getFolders(state) + let key = folderDetails.get('folderId') + + if (!folderDetails.has('folderId')) { + key = bookmarkFoldersUtil.getNextFolderId(folders) + } + + const newFolder = makeImmutable({ + title: folderDetails.get('title'), + folderId: ~~key, + parentFolderId: ~~folderDetails.get('parentFolderId', 0), + partitionNumber: ~~folderDetails.get('partitionNumber', 0), + objectId: null, + type: siteTags.BOOKMARK_FOLDER + }) + + state = state.setIn(['bookmarkFolders', key.toString()], newFolder) + state = bookmarkOrderCache.addFolderToCache(state, newFolder.get('parentFolderId'), key, destinationKey) + return state + }, + + editFolder: (state, folderDetails, editKey) => { + state = validateState(state) + const oldFolder = bookmarkFoldersState.getFolder(state, editKey) + + const newFolder = oldFolder.merge(makeImmutable({ + title: folderDetails.get('title'), + parentFolderId: ~~folderDetails.get('parentFolderId', 0) + })) + + if (oldFolder.get('parentFolderId') !== newFolder.get('parentFolderId')) { + state = bookmarkOrderCache.removeCacheKey(state, oldFolder.get('parentFolderId'), editKey) + state = bookmarkOrderCache.addFolderToCache(state, newFolder.get('parentFolderId'), editKey) + } + + state = state.setIn(['bookmarkFolders', editKey.toString()], newFolder) + return state + }, + + removeFolder: (state, folderKey) => { + const bookmarksState = require('./bookmarksState') + const folders = bookmarkFoldersState.getFolders(state) + const folder = bookmarkFoldersState.getFolder(state, folderKey) + + if (folder.isEmpty()) { + return state + } + + if (getSetting(settings.SYNC_ENABLED) === true) { + syncActions.removeSite(folder) + } + + folders.filter(folder => folder.get('parentFolderId') === ~~folderKey) + .map(folder => { + state = bookmarksState.removeBookmarksByParentId(state, folder.get('folderId')) + state = bookmarkFoldersState.removeFolder(state, folder.get('folderId')) + state = bookmarkOrderCache.removeCacheParent(state, folder.get('folderId')) + state = bookmarkOrderCache.removeCacheKey(state, folder.get('parentFolderId'), folderKey) + }) + + state = bookmarksState.removeBookmarksByParentId(state, folderKey) + state = bookmarkOrderCache.removeCacheParent(state, folderKey) + state = bookmarkOrderCache.removeCacheKey(state, folder.get('parentFolderId'), folderKey) + return state.deleteIn(['bookmarkFolders', folderKey.toString()]) + }, + + getFoldersWithoutKey: (state, folderKey, parentFolderId = 0, labelPrefix = '') => { + let folders = [] + const results = bookmarkFoldersState.getFoldersByParentId(state, parentFolderId) + + const resultSize = results.size + for (let i = 0; i < resultSize; i++) { + const folder = results.get(i) + if (folder.get('folderId') === folderKey) { + continue + } + + const label = labelPrefix + folder.get('title') + folders.push({ + folderId: folder.get('folderId'), + label + }) + const subSites = bookmarkFoldersState.getFoldersWithoutKey(state, folderKey, folder.get('folderId'), (label || '') + ' / ') + folders = folders.concat(subSites) + } + + return folders + }, + + moveFolder: (state, folderKey, destinationKey, append, moveIntoParent) => { + const bookmarksState = require('./bookmarksState') + let folder = bookmarkFoldersState.getFolder(state, folderKey) + let destinationItem = bookmarksState.findBookmark(state, destinationKey) + + if (folder.isEmpty()) { + return state + } + + if (moveIntoParent || destinationItem.get('parentFolderId') !== folder.get('parentFolderId')) { + const parentFolderId = destinationItem.get('type') === siteTags.BOOKMARK + ? destinationItem.get('parentFolderId') + : destinationItem.get('folderId') + + state = bookmarkOrderCache.removeCacheKey(state, folder.get('parentFolderId'), folderKey) + folder = folder.set('parentFolderId', ~~parentFolderId) + const newKey = siteUtil.getSiteKey(folder) + state = state.deleteIn(['bookmarkFolders', folderKey]) + state = bookmarkOrderCache.addFolderToCache(state, folder.get('parentFolderId'), newKey) + return state.setIn(['bookmarkFolders', newKey.toString()], folder) + } + + state = bookmarkOrderCache.removeCacheKey(state, folder.get('parentFolderId'), folderKey) + state = bookmarkOrderCache.addFolderToCache( + state, + folder.get('parentFolderId'), + folderKey, + destinationKey, + append + ) + return state + } +} + +module.exports = bookmarkFoldersState diff --git a/app/common/state/bookmarksState.js b/app/common/state/bookmarksState.js new file mode 100644 index 00000000000..0ef921d1a3c --- /dev/null +++ b/app/common/state/bookmarksState.js @@ -0,0 +1,237 @@ +/* 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 assert = require('assert') +const Immutable = require('immutable') + +// Constants +const settings = require('../../../js/constants/settings') +const siteTags = require('../../../js/constants/siteTags') + +// State +const historyState = require('./historyState') +const bookmarkOrderCache = require('../cache/bookmarkOrderCache') +const bookmarkFoldersState = require('./bookmarkFoldersState') + +// Actions +const syncActions = require('../../../js/actions/syncActions') + +// Utils +const siteUtil = require('../../../js/state/siteUtil') +const UrlUtil = require('../../../js/lib/urlutil') +const siteCache = require('./siteCache') +const {getSetting} = require('../../../js/settings') +const {makeImmutable, isMap} = require('./immutableUtil') + +const validateState = function (state) { + state = makeImmutable(state) + assert.ok(isMap(state), 'state must be an Immutable.Map') + assert.ok(isMap(state.get('bookmarks')), 'state must contain an Immutable.Map of bookmarks') + return state +} + +const bookmarksState = { + getBookmarks: (state) => { + state = validateState(state) + return state.get('bookmarks') + }, + + getBookmark: (state, key) => { + state = validateState(state) + return state.getIn(['bookmarks', key], Immutable.Map()) + }, + + /** + * Use this function if you only have a key and don't know if key is for folder or regular bookmark + * @param state + * @param key + */ + findBookmark: (state, key) => { + state = validateState(state) + let bookmark = bookmarksState.getBookmark(state, key) + if (bookmark.isEmpty()) { + bookmark = bookmarkFoldersState.getFolder(state, key) + } + + return bookmark + }, + + getBookmarksWithFolders: (state, parentFolderId = 0) => { + state = validateState(state) + + const cache = bookmarkOrderCache.getBookmarksWithFolders(state, parentFolderId) + let bookmarks = Immutable.OrderedMap() + + for (let item of cache) { + if (item.get('type') === siteTags.BOOKMARK) { + bookmarks = bookmarks.set(item.get('key'), bookmarksState.getBookmark(state, item.get('key'))) + } else { + bookmarks = bookmarks.set(item.get('key'), bookmarkFoldersState.getFolder(state, item.get('key'))) + } + } + + return bookmarks + }, + + addBookmark: (state, bookmarkDetail, destinationKey) => { + state = validateState(state) + + bookmarkDetail = makeImmutable(bookmarkDetail) + let location + if (bookmarkDetail.has('location')) { + location = UrlUtil.getLocationIfPDF(bookmarkDetail.get('location')) + bookmarkDetail = bookmarkDetail.set('location', location) + } + + const key = siteUtil.getSiteKey(bookmarkDetail) + const historyItem = historyState.getSite(state, key) + + let bookmark = makeImmutable({ + title: bookmarkDetail.get('title', ' '), + location: bookmarkDetail.get('location'), + parentFolderId: ~~bookmarkDetail.get('parentFolderId', 0), + partitionNumber: ~~historyItem.get('partitionNumber', 0), + objectId: null, + favicon: historyItem.get('favicon'), + themeColor: historyItem.get('themeColor'), + type: siteTags.BOOKMARK + }) + + if (key === null) { + return state + } + + state = state.setIn(['bookmarks', key], bookmark) + state = siteCache.addLocationSiteKey(state, location, key) + state = bookmarkOrderCache.addBookmarkToCache(state, bookmark.get('parentFolderId'), key, destinationKey) + return state + }, + + editBookmark: (state, bookmarkDetail, editKey) => { + state = validateState(state) + + const oldBookmark = bookmarksState.getBookmark(state, editKey) + let newBookmark = oldBookmark.merge(bookmarkDetail) + + let location + if (newBookmark.has('location')) { + location = UrlUtil.getLocationIfPDF(newBookmark.get('location')) + newBookmark = newBookmark.set('location', location) + } + + state = siteCache.removeLocationSiteKey(state, oldBookmark.get('location'), editKey) + + const newKey = siteUtil.getSiteKey(newBookmark) + if (newKey === null) { + return state + } + + if (editKey !== newKey) { + state = state.deleteIn(['bookmarks', editKey]) + state = bookmarkOrderCache.removeCacheKey(state, oldBookmark.get('parentFolderId'), editKey) + state = bookmarkOrderCache.addBookmarkToCache(state, newBookmark.get('parentFolderId'), newKey) + } + + state = state.setIn(['bookmarks', newKey], newBookmark) + state = siteCache.addLocationSiteKey(state, location, newKey) + return state + }, + + removeBookmark: (state, bookmarkKey) => { + state = validateState(state) + + const bookmark = bookmarksState.getBookmark(state, bookmarkKey) + + if (bookmark.isEmpty()) { + return state + } + + if (getSetting(settings.SYNC_ENABLED) === true) { + syncActions.removeSite(bookmark) + } + + state = siteCache.removeLocationSiteKey(state, bookmark.get('location'), bookmarkKey) + state = bookmarkOrderCache.removeCacheKey(state, bookmark.get('parentFolderId'), bookmarkKey) + + return state.deleteIn(['bookmarks', bookmarkKey]) + }, + + /** + * Removes bookmarks based on the parent ID + * Cache is cleared in the function that is calling this one + * @param state - App state + * @param parentFolderId - parent id of the folder that we are deleting + */ + removeBookmarksByParentId: (state, parentFolderId) => { + state = validateState(state) + + const bookmarks = bookmarksState.getBookmarks(state) + .filter(bookmark => bookmark.get('parentFolderId') !== ~~parentFolderId) + + return state.set('bookmarks', bookmarks) + }, + + /** + * Update the favicon URL for all entries in the state sites + * which match a given location. Currently, there should only be + * one match, but this will handle multiple. + * + * @param state The application state + * @param location URL for the entry needing an update + * @param favicon favicon URL + */ + updateSiteFavicon: (state, location, favicon) => { + state = validateState(state) + + if (UrlUtil.isNotURL(location)) { + return state + } + + const siteKeys = siteCache.getLocationSiteKeys(state, location) + if (!siteKeys || siteKeys.length === 0) { + return state + } + + siteKeys.forEach((siteKey) => { + state = state.setIn(['bookmarks', siteKey, 'favicon'], favicon) + }) + return state + }, + + moveBookmark: (state, bookmarkKey, destinationKey, append, moveIntoParent) => { + let bookmark = bookmarksState.getBookmark(state, bookmarkKey) + let destinationItem = bookmarksState.findBookmark(state, destinationKey) + + if (bookmark.isEmpty()) { + return state + } + + // move bookmark into a new folder + if (moveIntoParent || destinationItem.get('parentFolderId') !== bookmark.get('parentFolderId')) { + const parentFolderId = destinationItem.get('type') === siteTags.BOOKMARK + ? destinationItem.get('parentFolderId') + : destinationItem.get('folderId') + + state = bookmarkOrderCache.removeCacheKey(state, bookmark.get('parentFolderId'), bookmarkKey) + bookmark = bookmark.set('parentFolderId', ~~parentFolderId) + const newKey = siteUtil.getSiteKey(bookmark) + state = state.deleteIn(['bookmarks', bookmarkKey]) + state = bookmarkOrderCache.addBookmarkToCache(state, bookmark.get('parentFolderId'), newKey) + return state.setIn(['bookmarks', newKey.toString()], bookmark) + } + + // move bookmark to another place + state = bookmarkOrderCache.removeCacheKey(state, bookmark.get('parentFolderId'), bookmarkKey) + state = bookmarkOrderCache.addBookmarkToCache( + state, + bookmark.get('parentFolderId'), + bookmarkKey, + destinationKey, + append + ) + return state + } +} + +module.exports = bookmarksState diff --git a/app/common/state/historyState.js b/app/common/state/historyState.js new file mode 100644 index 00000000000..29a65d6c172 --- /dev/null +++ b/app/common/state/historyState.js @@ -0,0 +1,67 @@ +/* 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 assert = require('assert') +const Immutable = require('immutable') +const siteUtil = require('../../../js/state/siteUtil') +const historyUtil = require('../lib/historyUtil') +const urlUtil = require('../../../js/lib/urlutil') +const siteCache = require('./siteCache') +const {makeImmutable, isMap} = require('./immutableUtil') + +const validateState = function (state) { + state = makeImmutable(state) + assert.ok(isMap(state), 'state must be an Immutable.Map') + assert.ok(isMap(state.get('historySites')), 'state must contain an Immutable.Map of historySites') + return state +} + +const historyState = { + getSites: (state) => { + state = validateState(state) + return state.get('historySites', Immutable.Map()) + }, + + getSite: (state, key) => { + state = validateState(state) + return state.getIn(['historySites', key], Immutable.Map()) + }, + + addSite: (state, siteDetail) => { + let sites = historyState.getSites(state) + let siteKey = siteUtil.getSiteKey(siteDetail) + siteDetail = makeImmutable(siteDetail) + + const oldSite = sites.get(siteKey) + let site + if (oldSite) { + site = historyUtil.mergeSiteDetails(oldSite, siteDetail) + } else { + let location + if (siteDetail.has('location')) { + location = urlUtil.getLocationIfPDF(siteDetail.get('location')) + siteDetail = siteDetail.set('location', location) + } + + siteKey = siteUtil.getSiteKey(siteDetail) + site = historyUtil.prepareHistoryEntry(siteDetail) + state = siteCache.addLocationSiteKey(state, location, siteKey) + } + + state = state.setIn(['historySites', siteKey], site) + return state + }, + + removeSite: (state, siteKey) => { + // TODO should we remove this only when a tab with this siteKey is not opened + // if not, we are deleting data that is use for bookmarking + state = state.deleteIn(['historySites', siteKey]) + }, + + clearSites: (state) => { + return state.set('historySites', Immutable.Map()) + } +} + +module.exports = historyState diff --git a/app/common/state/pinnedSitesState.js b/app/common/state/pinnedSitesState.js new file mode 100644 index 00000000000..b231d780a53 --- /dev/null +++ b/app/common/state/pinnedSitesState.js @@ -0,0 +1,125 @@ +/* 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 assert = require('assert') +const Immutable = require('immutable') +const siteCache = require('./siteCache') +const siteUtil = require('../../../js/state/siteUtil') +const urlUtil = require('../../../js/lib/urlutil') +const {makeImmutable, isMap} = require('./immutableUtil') + +const validateState = function (state) { + state = makeImmutable(state) + assert.ok(isMap(state), 'state must be an Immutable.Map') + assert.ok(isMap(state.get('pinnedSites')), 'state must contain an Immutable.Map of pinnedSites') + return state +} + +const pinnedSiteState = { + getSites: (state) => { + state = validateState(state) + return state.get('pinnedSites') + }, + + /** + * Adds the specified siteDetail in appState.pinnedSites. + * @param {Immutable.Map} state The application state Immutable map + * @param {Immutable.Map} site The siteDetail that we want to add + */ + addPinnedSite: (state, site) => { + state = validateState(state) + const sites = pinnedSiteState.getSites(state) || Immutable.Map() + let location + if (site.has('location')) { + location = urlUtil.getLocationIfPDF(site.get('location')) + site = site.set('location', location) + } + + site = site.set('order', sites.size) + + const key = siteUtil.getSiteKey(site) + if (key === null) { + return state + } + + state = state.setIn(['pinnedSites', key], site) + state = siteCache.addLocationSiteKey(state, location, key) + return state + }, + + /** + * Removes the given pinned site from the pinnedSites + * + * @param {Immutable.Map} state The application state Immutable map + * @param {Immutable.Map} siteDetail The siteDetail to be removed + * @return {Immutable.Map} The new state Immutable object + */ + removePinnedSite: (state, siteDetail) => { + state = validateState(state) + const key = siteUtil.getSiteKey(siteDetail) + if (!key) { + return state + } + + const location = siteDetail.get('location') + state = siteCache.removeLocationSiteKey(state, location, key) + + const stateKey = ['pinnedSites', key] + let site = state.getIn(stateKey) + if (!site) { + return state + } + + // TODO update order, so that is up to date + + return state.deleteIn(stateKey, site) + }, + + /** + * Moves the specified pinned site from one location to another + * + * @param state The application state Immutable map + * @param sourceKey The site key to move + * @param destinationKey The site key to move to + * @param prepend Whether the destination detail should be prepended or not + * @return The new state Immutable object + */ + reOrderSite: (state, sourceKey, destinationKey, prepend) => { + state = validateState(state) + let sites = state.get('pinnedSites') + let sourceSite = sites.get(sourceKey, Immutable.Map()) + const destinationSite = sites.get(destinationKey, Immutable.Map()) + + if (sourceSite.isEmpty()) { + return state + } + + const sourceSiteIndex = sourceSite.get('order') + const destinationSiteIndex = destinationSite.get('order') + let newIndex = destinationSiteIndex + (prepend ? 0 : 1) + if (destinationSiteIndex > sourceSiteIndex) { + --newIndex + } + + state = state.set('pinnedSites', state.get('pinnedSites').map((site, index) => { + const siteOrder = site.get('order') + if (index === sourceKey) { + return site + } + + if (siteOrder >= newIndex && siteOrder < sourceSiteIndex) { + return site.set('order', siteOrder + 1) + } else if (siteOrder <= newIndex && siteOrder > sourceSiteIndex) { + return site.set('order', siteOrder - 1) + } + + return site + })) + + sourceSite = sourceSite.set('order', newIndex) + return state.setIn(['pinnedSites', sourceKey], sourceSite) + } +} + +module.exports = pinnedSiteState diff --git a/app/common/state/siteCache.js b/app/common/state/siteCache.js index beb1a207928..a436daae1f9 100644 --- a/app/common/state/siteCache.js +++ b/app/common/state/siteCache.js @@ -7,6 +7,8 @@ const siteUtil = require('../../../js/state/siteUtil') const appUrlUtil = require('../../../js/lib/appUrlUtil') const UrlUtil = require('../../../js/lib/urlutil') +// TODO what to do with this file? + const createLocationSiteKeysCache = (state) => { state = state.set('locationSiteKeysCache', new Immutable.Map()) state.get('sites').forEach((site, siteKey) => { diff --git a/app/common/state/siteState.js b/app/common/state/siteState.js deleted file mode 100644 index 0f74f8390a6..00000000000 --- a/app/common/state/siteState.js +++ /dev/null @@ -1,18 +0,0 @@ -const assert = require('assert') -const {makeImmutable, isMap, isList} = require('./immutableUtil') - -const validateState = function (state) { - state = makeImmutable(state) - assert.ok(isMap(state), 'state must be an Immutable.Map') - assert.ok(isList(state.get('sites')), 'state must contain an Immutable.List of sites') - return state -} - -const siteState = { - getSites: (state) => { - state = validateState(state) - return state.get('sites') - } -} - -module.exports = siteState diff --git a/app/common/state/tabState.js b/app/common/state/tabState.js index 4cc5ece7b24..965125eaac2 100644 --- a/app/common/state/tabState.js +++ b/app/common/state/tabState.js @@ -13,7 +13,6 @@ const windowState = require('./windowState') const { makeImmutable, isMap, isList } = require('./immutableUtil') // this file should eventually replace frameStateUtil const frameStateUtil = require('../../../js/state/frameStateUtil') -const {isLocationBookmarked} = require('../../../js/state/siteUtil') const validateId = function (propName, id) { assert.ok(id, `${propName} cannot be null`) @@ -239,6 +238,13 @@ const tabState = { return state.get('tabs').filter((tab) => !!tab.get('pinned')) }, + isTabPinned: (state, tabId) => { + state = validateState(state) + tabId = validateId('tabId', tabId) + const tab = tabState.getByTabId(state, tabId) + return tab != null ? !!tab.get('pinned') : false + }, + getNonPinnedTabs: (state) => { state = validateState(state) return state.get('tabs').filter((tab) => !tab.get('pinned')) @@ -447,8 +453,9 @@ const tabState = { return state } + const bookmarkUtil = require('../lib/bookmarkUtil') const frameLocation = action.getIn(['frame', 'location']) - const frameBookmarked = isLocationBookmarked(state, frameLocation) + const frameBookmarked = bookmarkUtil.isLocationBookmarked(state, frameLocation) const frameValue = action.get('frame').set('bookmarked', frameBookmarked) tabValue = tabValue.set('frame', makeImmutable(frameValue)) return tabState.updateTabValue(state, tabValue) diff --git a/app/importer.js b/app/importer.js index b88c47d7fac..aecf9d29004 100644 --- a/app/importer.js +++ b/app/importer.js @@ -10,21 +10,32 @@ const importer = electron.importer const dialog = electron.dialog const BrowserWindow = electron.BrowserWindow const session = electron.session -const siteUtil = require('../js/state/siteUtil') -const AppStore = require('../js/stores/appStore') -const siteTags = require('../js/constants/siteTags') -const appActions = require('../js/actions/appActions') + +// Store +const appStore = require('../js/stores/appStore') + +// State +const tabState = require('./common/state/tabState') +const bookmarksState = require('./common/state/bookmarksState') +const bookmarkFoldersState = require('./common/state/bookmarkFoldersState') + +// Constants const messages = require('../js/constants/messages') const settings = require('../js/constants/settings') -const getSetting = require('../js/settings').getSetting + +// Actions +const appActions = require('../js/actions/appActions') + +// Utils +const {getSetting} = require('../js/settings') const locale = require('./locale') const tabMessageBox = require('./browser/tabMessageBox') const {makeImmutable} = require('./common/state/immutableUtil') -const tabState = require('./common/state/tabState') +const bookmarkFoldersUtil = require('./common/lib/bookmarkFoldersUtil') -var isImportingBookmarks = false -var hasBookmarks -var importedSites +let isImportingBookmarks = false +let hasBookmarks +// let importedSites exports.init = () => { importer.initialize() @@ -33,10 +44,8 @@ exports.init = () => { exports.importData = (selected) => { if (selected.get('favorites')) { isImportingBookmarks = true - const sites = AppStore.getState().get('sites') - hasBookmarks = sites.find( - (site) => siteUtil.isBookmark(site) || siteUtil.isFolder(site) - ) + const state = appStore.getState() + hasBookmarks = bookmarksState.getBookmarks(state).size > 0 || bookmarkFoldersState.getFolders(state).size > 0 } if (selected !== undefined) { importer.importData(selected.toJS()) @@ -45,10 +54,8 @@ exports.importData = (selected) => { exports.importHTML = (selected) => { isImportingBookmarks = true - const sites = AppStore.getState().get('sites') - hasBookmarks = sites.find( - (site) => siteUtil.isBookmark(site) || siteUtil.isFolder(site) - ) + const state = appStore.getState() + hasBookmarks = bookmarksState.getBookmarks(state).size > 0 || bookmarkFoldersState.getFolders(state).size > 0 const files = dialog.showOpenDialog({ properties: ['openFile'], filters: [{ @@ -69,7 +76,7 @@ importer.on('update-supported-browsers', (e, detail) => { } }) -importer.on('add-history-page', (e, history, visitSource) => { +importer.on('add-history-page', (e, history) => { let sites = [] for (let i = 0; i < history.length; ++i) { const site = { @@ -79,80 +86,70 @@ importer.on('add-history-page', (e, history, visitSource) => { } sites.push(site) } - appActions.addSite(makeImmutable(sites)) + appActions.addHistorySite(makeImmutable(sites)) }) importer.on('add-homepage', (e, detail) => { }) -const getParentFolderId = (path, pathMap, sites, topLevelFolderId, nextFolderIdObject) => { +const getParentFolderId = (path, pathMap, folders, topLevelFolderId, nextFolderIdObject) => { const pathLen = path.length if (!pathLen) { return topLevelFolderId } + const parentFolder = path.pop() let parentFolderId = pathMap[parentFolder] if (parentFolderId === undefined) { parentFolderId = nextFolderIdObject.id++ pathMap[parentFolder] = parentFolderId - const folder = { - customTitle: parentFolder, + folders.push({ + title: parentFolder, folderId: parentFolderId, - parentFolderId: getParentFolderId(path, pathMap, sites, topLevelFolderId, nextFolderIdObject), - lastAccessedTime: 0, - creationTime: (new Date()).getTime(), - tags: [siteTags.BOOKMARK_FOLDER] - } - sites.push(folder) + parentFolderId: getParentFolderId(path, pathMap, folders, topLevelFolderId, nextFolderIdObject) + }) } return parentFolderId } -importer.on('add-bookmarks', (e, bookmarks, topLevelFolder) => { - let nextFolderId = siteUtil.getNextFolderId(AppStore.getState().get('sites')) +importer.on('add-bookmarks', (e, importedBookmarks, topLevelFolder) => { + const state = appStore.getState() + const bookmarkFolders = bookmarkFoldersState.getFolders(state) + let nextFolderId = bookmarkFoldersUtil.getNextFolderId(bookmarkFolders) let nextFolderIdObject = { id: nextFolderId } let pathMap = {} - let sites = [] - let topLevelFolderId = 0 - topLevelFolderId = nextFolderIdObject.id++ - sites.push({ - customTitle: siteUtil.getNextFolderName(AppStore.getState().get('sites'), topLevelFolder), + let folders = [] + let bookmarks = [] + let topLevelFolderId = nextFolderIdObject.id++ + + folders.push({ + title: bookmarkFoldersUtil.getNextFolderName(bookmarkFolders, topLevelFolder), folderId: topLevelFolderId, - parentFolderId: 0, - lastAccessedTime: 0, - creationTime: (new Date()).getTime(), - tags: [siteTags.BOOKMARK_FOLDER] + parentFolderId: 0 }) - for (let i = 0; i < bookmarks.length; ++i) { - let path = bookmarks[i].path - let parentFolderId = getParentFolderId(path, pathMap, sites, topLevelFolderId, nextFolderIdObject) - if (bookmarks[i].is_folder) { + + for (let i = 0; i < importedBookmarks.length; ++i) { + let path = importedBookmarks[i].path + let parentFolderId = getParentFolderId(path, pathMap, folders, topLevelFolderId, nextFolderIdObject) + if (importedBookmarks[i].is_folder) { const folderId = nextFolderIdObject.id++ - pathMap[bookmarks[i].title] = folderId - const folder = { - customTitle: bookmarks[i].title, + pathMap[importedBookmarks[i].title] = folderId + folders.push({ + title: importedBookmarks[i].title, folderId: folderId, - parentFolderId: parentFolderId, - lastAccessedTime: 0, - creationTime: bookmarks[i].creation_time * 1000, - tags: [siteTags.BOOKMARK_FOLDER] - } - sites.push(folder) + parentFolderId: parentFolderId + }) } else { - const site = { - title: bookmarks[i].title, - customTitle: bookmarks[i].title, - location: bookmarks[i].url, - parentFolderId: parentFolderId, - lastAccessedTime: 0, - creationTime: bookmarks[i].creation_time * 1000, - tags: [siteTags.BOOKMARK] - } - sites.push(site) + bookmarks.push({ + title: importedBookmarks[i].title, + location: importedBookmarks[i].url, + parentFolderId: parentFolderId + }) } } - importedSites = makeImmutable(sites) - appActions.addSite(makeImmutable(sites)) + + appActions.addBookmarkFolder(makeImmutable(folders)) + appActions.addBookmark(makeImmutable(bookmarks)) }) importer.on('add-favicons', (e, detail) => { @@ -168,7 +165,9 @@ importer.on('add-favicons', (e, detail) => { } } }) - let sites = importedSites +// TODO what should be done here? update all bookmarks with new fav icons? + /* + let sites = importedSites sites = sites.map((site) => { if ((site.get('favicon') === undefined && site.get('location') !== undefined && faviconMap[site.get('location')] !== undefined) || @@ -179,6 +178,7 @@ importer.on('add-favicons', (e, detail) => { } }) appActions.addSite(sites) + */ }) importer.on('add-keywords', (e, templateUrls, uniqueOnHostAndPath) => { @@ -208,7 +208,7 @@ importer.on('add-cookies', (e, cookies) => { }) const getActiveTabId = () => { - return tabState.getActiveTabId(AppStore.getState()) + return tabState.getActiveTabId(appStore.getState()) } const showImportWarning = function () { diff --git a/app/index.js b/app/index.js index 2ee37e47f22..262fd96253a 100644 --- a/app/index.js +++ b/app/index.js @@ -159,6 +159,7 @@ app.on('ready', () => { appActions.networkDisconnected() }) + // TODO what should we do here loadAppStatePromise.then((initialState) => { // Do this after loading the state // For tests we always want to load default app state @@ -301,7 +302,7 @@ app.on('ready', () => { }) ipcMain.on(messages.EXPORT_BOOKMARKS, () => { - BookmarksExporter.showDialog(appStore.getState().get('sites')) + BookmarksExporter.showDialog(appStore.getState()) }) // DO NOT TO THIS LIST - see above @@ -330,7 +331,7 @@ app.on('ready', () => { }) process.on(messages.EXPORT_BOOKMARKS, () => { - BookmarksExporter.showDialog(appStore.getState().get('sites')) + BookmarksExporter.showDialog(appStore.getState()) }) ready = true diff --git a/app/renderer/about/bookmarks/bookmarkFolderItem.js b/app/renderer/about/bookmarks/bookmarkFolderItem.js index f2a31c5d9a4..55bf62d4bd5 100644 --- a/app/renderer/about/bookmarks/bookmarkFolderItem.js +++ b/app/renderer/about/bookmarks/bookmarkFolderItem.js @@ -10,9 +10,11 @@ const ImmutableComponent = require('../../components/immutableComponent') // Actions const aboutActions = require('../../../../js/about/aboutActions') +const appActions = require('../../../../js/actions/appActions') // Constants const dragTypes = require('../../../../js/constants/dragTypes') +const siteTags = require('../../../../js/constants/siteTags') // Utils const dndData = require('../../../../js/dndData') @@ -31,7 +33,7 @@ class BookmarkFolderItem extends ImmutableComponent { e.dataTransfer.effectAllowed = 'all' dndData.setupDataTransferURL(e.dataTransfer, this.props.bookmarkFolder.get('location'), - this.props.bookmarkFolder.get('customTitle') || this.props.bookmarkFolder.get('title')) + this.props.bookmarkFolder.get('title')) dndData.setupDataTransferBraveData(e.dataTransfer, dragTypes.BOOKMARK, this.props.bookmarkFolder) } } @@ -54,11 +56,22 @@ class BookmarkFolderItem extends ImmutableComponent { moveBookmark (e, bookmark) { if (siteUtil.isMoveAllowed(this.props.allBookmarkFolders, bookmark, this.props.bookmarkFolder)) { const bookmarkSiteKey = siteUtil.getSiteKey(bookmark) - const bookmarkFolderSiteKey = siteUtil.getSiteKey(this.props.bookmarkFolder) - aboutActions.moveSite(bookmarkSiteKey, - bookmarkFolderSiteKey, - dndData.shouldPrependVerticalItem(e.target, e.clientY), - true) + + if (bookmark.get('type') === siteTags.BOOKMARK_FOLDER) { + appActions.moveBookmarkFolder( + bookmarkSiteKey, + this.props.bookmarkFolder.get('folderId'), + dndData.shouldPrependVerticalItem(e.target, e.clientY), + true + ) + } else { + appActions.moveBookmark( + bookmarkSiteKey, + this.props.bookmarkFolder.get('folderId'), + dndData.shouldPrependVerticalItem(e.target, e.clientY), + true + ) + } } } clearSelection () { @@ -86,17 +99,27 @@ class BookmarkFolderItem extends ImmutableComponent { this.clearSelection() } } + + get childBookmarkFolders () { + const cached = this.props.bookmarkOrder.get(this.props.bookmarkFolder.get('folderId').toString()) + + if (cached == null) { + return Immutable.Map() + } + return cached + .filter(item => item.get('type') === siteTags.BOOKMARK_FOLDER) + .map(folder => this.props.allBookmarkFolders.get(folder.get('key').toString())) + } + render () { const BookmarkFolderList = require('./bookmarkFolderList') - const childBookmarkFolders = this.props.allBookmarkFolders - .filter((bookmarkFolder) => (bookmarkFolder.get('parentFolderId') || 0) === this.props.bookmarkFolder.get('folderId')) return
- {this.props.bookmarkFolder.get('customTitle') || this.props.bookmarkFolder.get('title')} + {this.props.bookmarkFolder.get('title')}
{ - childBookmarkFolders.size > 0 + !this.childBookmarkFolders.isEmpty() ? : null } diff --git a/app/renderer/about/bookmarks/bookmarkFolderList.js b/app/renderer/about/bookmarks/bookmarkFolderList.js index ee918f34731..4f13f12ae06 100644 --- a/app/renderer/about/bookmarks/bookmarkFolderList.js +++ b/app/renderer/about/bookmarks/bookmarkFolderList.js @@ -35,6 +35,7 @@ class BookmarkFolderList extends ImmutableComponent { allBookmarkFolders={this.props.allBookmarkFolders} selectedFolderId={this.props.selectedFolderId} bookmarkFolder={Immutable.fromJS({folderId: 0, tags: [siteTags.BOOKMARK_FOLDER]})} + bookmarkOrder={this.props.bookmarkOrder} /> : null } @@ -50,6 +51,7 @@ class BookmarkFolderList extends ImmutableComponent { selected={!this.props.search && this.props.selectedFolderId === bookmarkFolder.get('folderId')} selectedFolderId={this.props.selectedFolderId} onChangeSelectedFolder={this.props.onChangeSelectedFolder} + bookmarkOrder={this.props.bookmarkOrder} /> ) } @@ -65,6 +67,7 @@ class BookmarkFolderList extends ImmutableComponent { allBookmarkFolders={this.props.allBookmarkFolders} selectedFolderId={this.props.selectedFolderId} bookmarkFolder={Immutable.fromJS({folderId: -1, tags: [siteTags.BOOKMARK_FOLDER]})} + bookmarkOrder={this.props.bookmarkOrder} /> : null } diff --git a/app/renderer/about/bookmarks/bookmarkTitleCell.js b/app/renderer/about/bookmarks/bookmarkTitleCell.js index f2c99d121e6..ef09e57f44c 100644 --- a/app/renderer/about/bookmarks/bookmarkTitleCell.js +++ b/app/renderer/about/bookmarks/bookmarkTitleCell.js @@ -11,14 +11,14 @@ const ImmutableComponent = require('../../components/immutableComponent') const {iconSize} = require('../../../../js/constants/config') // Utils -const siteUtil = require('../../../../js/state/siteUtil') const cx = require('../../../../js/lib/classSet') +const bookmarkFoldersUtil = require('../../../common/lib/bookmarkFoldersUtil') class BookmarkTitleCell extends ImmutableComponent { render () { let iconStyle const icon = this.props.siteDetail.get('favicon') - if (!siteUtil.isFolder(this.props.siteDetail)) { + if (!bookmarkFoldersUtil.isFolder(this.props.siteDetail)) { if (icon) { iconStyle = { minWidth: iconSize, @@ -30,7 +30,7 @@ class BookmarkTitleCell extends ImmutableComponent { } } - const bookmarkTitle = this.props.siteDetail.get('customTitle') || this.props.siteDetail.get('title') + const bookmarkTitle = this.props.siteDetail.get('title') const bookmarkLocation = this.props.siteDetail.get('location') const defaultIcon = 'fa fa-file-o' diff --git a/app/renderer/about/bookmarks/bookmarkTitleHeader.js b/app/renderer/about/bookmarks/bookmarkTitleHeader.js index 953ca91de7f..9cdad907cba 100644 --- a/app/renderer/about/bookmarks/bookmarkTitleHeader.js +++ b/app/renderer/about/bookmarks/bookmarkTitleHeader.js @@ -11,9 +11,6 @@ const ImmutableComponent = require('../../components/immutableComponent') // Actions const windowActions = require('../../../../js/actions/windowActions') -// Constants -const siteTags = require('../../../../js/constants/siteTags') - class BookmarkTitleHeader extends ImmutableComponent { constructor () { super() @@ -21,8 +18,7 @@ class BookmarkTitleHeader extends ImmutableComponent { } addBookmark () { const newBookmark = Immutable.fromJS({ - parentFolderId: this.props.selectedFolderId, - tags: [siteTags.BOOKMARK] + parentFolderId: this.props.selectedFolderId }) windowActions.addBookmark(newBookmark) } diff --git a/app/renderer/about/bookmarks/bookmarks.js b/app/renderer/about/bookmarks/bookmarks.js index 936af4a748a..3dd745d30d3 100644 --- a/app/renderer/about/bookmarks/bookmarks.js +++ b/app/renderer/about/bookmarks/bookmarks.js @@ -38,6 +38,7 @@ class Bookmarks extends React.Component { this.state = { bookmarks: Immutable.Map(), bookmarkFolders: Immutable.Map(), + bookmarkOrder: Immutable.Map(), selectedFolderId: 0, search: '' } @@ -46,7 +47,8 @@ class Bookmarks extends React.Component { const detail = handle.memory() this.setState({ bookmarks: Immutable.fromJS((detail && detail.bookmarks) || {}), - bookmarkFolders: Immutable.fromJS((detail && detail.bookmarkFolders) || {}) + bookmarkFolders: Immutable.fromJS((detail && detail.bookmarkFolders) || {}), + bookmarkOrder: Immutable.fromJS((detail && detail.bookmarkOrder) || {}) }) }) } @@ -86,13 +88,25 @@ class Bookmarks extends React.Component { searchedBookmarks (searchTerm, bookmarks) { return bookmarks.filter((bookmark) => { - const title = bookmark.get('customTitle') + bookmark.get('title') + bookmark.get('location') + const title = bookmark.get('title') + bookmark.get('location') return title.match(new RegExp(searchTerm, 'gi')) }) } get bookmarksInFolder () { - return this.state.bookmarks.filter((bookmark) => (bookmark.get('parentFolderId') || 0) === this.state.selectedFolderId) + const cached = this.state.bookmarkOrder.get(this.state.selectedFolderId.toString()) + + if (cached == null) { + return Immutable.Map() + } + + return cached + .filter(item => item.get('type') === siteTags.BOOKMARK) + .map(bookmark => this.state.bookmarks.get(bookmark.get('key'))) + } + + get bookmarkFolders () { + return this.state.bookmarkFolders.filter((bookmark) => bookmark.get('parentFolderId') === -1) } importBrowserData () { @@ -104,11 +118,9 @@ class Bookmarks extends React.Component { } addBookmarkFolder () { - const newFolder = Immutable.fromJS({ - parentFolderId: this.state.selectedFolderId, - tags: [siteTags.BOOKMARK_FOLDER] - }) - windowActions.addBookmark(newFolder) + windowActions.addBookmarkFolder(Immutable.fromJS({ + parentFolderId: this.state.selectedFolderId + })) } clearSelection () { @@ -146,11 +158,12 @@ class Bookmarks extends React.Component { bookmark.get('parentFolderId') === -1)} + bookmarkFolders={this.bookmarkFolders} allBookmarkFolders={this.state.bookmarkFolders} isRoot selectedFolderId={this.state.selectedFolderId} - search={this.state.search} /> + search={this.state.search} + bookmarkOrder={this.state.bookmarkOrder} />
folder.get('folderId') === siteDetail.get('parentFolderId')) - : Immutable.fromJS({folderId: 0, tags: [siteTags.BOOKMARK_FOLDER]}) - destinationIsParent = true - } - - if (siteUtil.isMoveAllowed(this.props.allBookmarkFolders, bookmark, siteDetail)) { - const bookmarkSiteKey = siteUtil.getSiteKey(bookmark.toJS()) - const siteKey = siteUtil.getSiteKey(siteDetail.toJS()) + const bookmarkSiteKey = siteUtil.getSiteKey(bookmark) + const siteKey = siteUtil.getSiteKey(siteDetail) - aboutActions.moveSite(bookmarkSiteKey, - siteKey, - dndData.shouldPrependVerticalItem(e.target, e.clientY), - destinationIsParent) - } + appActions.moveBookmark( + bookmarkSiteKey, + siteKey, + dndData.shouldPrependVerticalItem(e.target, e.clientY), + destinationIsParent + ) } /** * Bookmark (one or multiple) or BookmarkFolderItem object was dropped @@ -124,7 +117,7 @@ class BookmarksList extends ImmutableComponent { rows={this.props.bookmarks.map((entry) => [ { cell: , - value: entry.get('customTitle') || entry.get('title') || entry.get('location') + value: entry.get('title', entry.get('location')) }, { html: formatUtil.toLocaleString(entry.get('lastAccessedTime'), ''), diff --git a/app/renderer/components/bookmarks/addEditBookmarkFolder.js b/app/renderer/components/bookmarks/addEditBookmarkFolder.js new file mode 100644 index 00000000000..2bed10a30f0 --- /dev/null +++ b/app/renderer/components/bookmarks/addEditBookmarkFolder.js @@ -0,0 +1,103 @@ +/* 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 Immutable = require('immutable') +const {StyleSheet, css} = require('aphrodite/no-important') + +// Components +const ReduxComponent = require('../reduxComponent') +const Dialog = require('../common/dialog') +const AddEditBookmarkFolderForm = require('./addEditBookmarkFolderForm') +const {CommonFormBookmarkHanger} = require('../common/commonForm') + +// State +const bookmarkFoldersState = require('../../../common/state/bookmarkFoldersState') +const bookmarksState = require('../../../common/state/bookmarksState') + +// Actions +const windowActions = require('../../../../js/actions/windowActions') + +// Utils +const cx = require('../../../../js/lib/classSet') +const bookmarkFoldersUtil = require('../../../common/lib/bookmarkFoldersUtil') + +// Styles +const globalStyles = require('../styles/global') + +class AddEditBookmarkFolder extends React.Component { + constructor (props) { + super(props) + this.onClose = this.onClose.bind(this) + this.onClick = this.onClick.bind(this) + } + + onClose () { + windowActions.onBookmarkFolderClose() + } + + onClick (e) { + e.stopPropagation() + } + + mergeProps (state, ownProps) { + const currentWindow = state.get('currentWindow') + const bookmarkDetail = currentWindow.get('bookmarkFolderDetail', Immutable.Map()) + const folderDetails = bookmarkDetail.get('folderDetails') || Immutable.Map() + const editMode = bookmarkDetail.has('editKey') + + const props = {} + // used in renderer + props.heading = editMode + ? 'bookmarkFolderEditing' + : 'bookmarkFolderAdding' + props.parentFolderId = folderDetails.get('parentFolderId') + props.partitionNumber = folderDetails.get('partitionNumber') + props.folderName = folderDetails.get('title') + props.isFolderNameValid = bookmarkFoldersUtil.isFolderNameValid(folderDetails.get('title')) + props.folders = bookmarkFoldersState.getFoldersWithoutKey(state, folderDetails.get('folderId')) // TODO (nejc) improve, primitives only + props.editKey = bookmarkDetail.get('editKey', null) + props.closestKey = bookmarkDetail.get('closestKey', null) + props.hasBookmarks = bookmarksState.getBookmarks(state).size > 0 || bookmarkFoldersState.getFolders(state).size > 0 + + return props + } + + render () { + return + +
+ + +
+ } +} + +const styles = StyleSheet.create({ + // Copied from commonForm.js + commonFormSection: { + // PR #7985 + margin: `${globalStyles.spacing.dialogInsideMargin} 30px` + }, + commonFormTitle: { + color: globalStyles.color.braveOrange, + fontSize: '1.2em' + } +}) + +module.exports = ReduxComponent.connect(AddEditBookmarkFolder) diff --git a/app/renderer/components/bookmarks/addEditBookmarkFolderForm.js b/app/renderer/components/bookmarks/addEditBookmarkFolderForm.js new file mode 100644 index 00000000000..614daa610f3 --- /dev/null +++ b/app/renderer/components/bookmarks/addEditBookmarkFolderForm.js @@ -0,0 +1,235 @@ +/* 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 Immutable = require('immutable') +const {StyleSheet, css} = require('aphrodite/no-important') + +// Components +const BrowserButton = require('../common/browserButton') +const { + CommonFormSection, + CommonFormDropdown, + CommonFormButtonWrapper, + commonFormStyles +} = require('../common/commonForm') + +// Actions +const appActions = require('../../../../js/actions/appActions') +const windowActions = require('../../../../js/actions/windowActions') + +// Constants +const KeyCodes = require('../../../common/constants/keyCodes') +const settings = require('../../../../js/constants/settings') + +// Utils +const UrlUtil = require('../../../../js/lib/urlutil') +const {getSetting} = require('../../../../js/settings') +const bookmarkFoldersUtil = require('../../../common/lib/bookmarkFoldersUtil') + +// Styles +const globalStyles = require('../styles/global') +const commonStyles = require('../styles/commonStyles') + +class AddEditBookmarkFolderForm extends React.Component { + constructor (props) { + super(props) + this.onNameChange = this.onNameChange.bind(this) + this.onParentFolderChange = this.onParentFolderChange.bind(this) + this.onKeyDown = this.onKeyDown.bind(this) + this.onClose = this.onClose.bind(this) + this.onSave = this.onSave.bind(this) + this.onFolderRemove = this.onFolderRemove.bind(this) + this.state = { + title: props.folderName, + parentFolderId: props.parentFolderId, + isDisabled: props.isDisabled + } + } + + componentDidMount () { + setImmediate(() => { + this.folderName.select() + }) + } + + onKeyDown (e) { + switch (e.keyCode) { + case KeyCodes.ENTER: + this.onSave() + break + case KeyCodes.ESC: + this.onClose() + break + } + } + + onClose () { + windowActions.onBookmarkFolderClose() + } + + updateButtonStatus (newValue) { + if (newValue !== this.state.isDisabled) { + this.setState({ + isDisabled: newValue + }) + } + } + + onNameChange (e) { + let title = e.target.value + + this.setState({ + title: title + }) + + this.updateButtonStatus(!bookmarkFoldersUtil.isFolderNameValid(title)) + } + + onParentFolderChange (e) { + this.setState({ + parentFolderId: ~~e.target.value + }) + } + + onSave () { + // First check if the title of the bookmarkDetail is set + if (this.state.isDisabled) { + return false + } + + // show bookmark if hidden + if (!this.props.hasBookmarks && !getSetting(settings.SHOW_BOOKMARKS_TOOLBAR)) { + appActions.changeSetting(settings.SHOW_BOOKMARKS_TOOLBAR, true) + } + + let data = Immutable.fromJS({ + parentFolderId: this.state.parentFolderId + }) + + if (this.props.editKey != null) { + data = data.set('folderId', this.props.editKey) + } + + // handle title input + let title = this.state.title + if (typeof title === 'string' && UrlUtil.isURL(title)) { + const punycodeUrl = UrlUtil.getPunycodeUrl(title) + if (punycodeUrl.replace(/\/$/, '') !== title) { + title = punycodeUrl + } + } + data = data.set('title', title) + + if (this.props.editKey != null) { + appActions.editBookmarkFolder(data, this.props.editKey) + } else { + appActions.addBookmarkFolder(data, this.props.closestKey) + } + + this.onClose() + } + + onFolderRemove () { + appActions.removeBookmarkFolder(Immutable.fromJS({ + parentFolderId: this.props.parentFolderId, + partitionNumber: this.props.partitionNumber, + folderId: this.props.editKey + })) + this.onClose() + } + + render () { + return
+ +
+
+
+
+
+
+
+ + { + this.props.editKey != null + ? + : + } + + +
+ } +} + +const styles = StyleSheet.create({ + bookmarkHanger__label: { + display: 'block', + marginBottom: `calc(${globalStyles.spacing.dialogInsideMargin} / 3)` + }, + bookmarkHanger__marginRow: { + marginTop: `calc(${globalStyles.spacing.dialogInsideMargin} / 2)` + }, + + bookmark__sectionWrapper: { + display: 'flex', + flexFlow: 'column nowrap' + } +}) + +module.exports = AddEditBookmarkFolderForm diff --git a/app/renderer/components/bookmarks/addEditBookmarkForm.js b/app/renderer/components/bookmarks/addEditBookmarkForm.js index a744a8d50f4..2dd64b19ea4 100644 --- a/app/renderer/components/bookmarks/addEditBookmarkForm.js +++ b/app/renderer/components/bookmarks/addEditBookmarkForm.js @@ -22,7 +22,6 @@ 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 @@ -45,7 +44,7 @@ class AddEditBookmarkForm extends React.Component { this.onSave = this.onSave.bind(this) this.onRemoveBookmark = this.onRemoveBookmark.bind(this) this.state = { - title: props.bookmarkName, + title: props.title, location: props.location, parentFolderId: props.parentFolderId, isDisabled: props.isDisabled @@ -87,8 +86,6 @@ class AddEditBookmarkForm extends React.Component { this.setState({ title: title }) - - this.updateButtonStatus(!isBookmarkNameValid(title, this.state.location, this.props.isFolder)) } onLocationChange (e) { @@ -98,7 +95,7 @@ class AddEditBookmarkForm extends React.Component { location: location }) - this.updateButtonStatus(!isBookmarkNameValid(this.state.title, location, this.props.isFolder)) + this.updateButtonStatus(!isBookmarkNameValid(location)) } onParentFolderChange (e) { @@ -118,16 +115,10 @@ class AddEditBookmarkForm extends React.Component { appActions.changeSetting(settings.SHOW_BOOKMARKS_TOOLBAR, true) } - const tag = this.props.isFolder ? siteTags.BOOKMARK_FOLDER : siteTags.BOOKMARK let data = Immutable.fromJS({ - parentFolderId: this.state.parentFolderId, - title: this.props.currentTitle + parentFolderId: this.state.parentFolderId }) - if (this.props.isFolder && this.props.editKey != null) { - data = data.set('folderId', this.props.editKey) - } - // handle title input let title = this.state.title if (typeof title === 'string' && UrlUtil.isURL(title)) { @@ -136,10 +127,7 @@ class AddEditBookmarkForm extends React.Component { title = punycodeUrl } } - - if (this.props.currentTitle !== title || !title) { - data = data.set('customTitle', title || 0) - } + data = data.set('title', title) // handle location input let location = this.state.location @@ -152,23 +140,16 @@ class AddEditBookmarkForm extends React.Component { data = data.set('location', location) if (this.props.editKey != null) { - appActions.editBookmark(data, this.props.editKey, tag) + appActions.editBookmark(data, this.props.editKey) } else { - appActions.addBookmark(data, tag, this.props.closestKey) + appActions.addBookmark(data, this.props.closestKey) } this.onClose() } onRemoveBookmark () { - const tag = this.props.isFolder ? siteTags.BOOKMARK_FOLDER : siteTags.BOOKMARK - // TODO check if you need to add folderId as prop or you can use editKey - appActions.removeSite(Immutable.fromJS({ - parentFolderId: this.props.parentFolderId, - location: this.props.location, - partitionNumber: this.props.partitionNumber, - folderId: this.props.isFolder ? this.props.editKey : null - }), tag) + appActions.removeBookmark(this.props.editKey) this.onClose() } @@ -203,7 +184,7 @@ class AddEditBookmarkForm extends React.Component {
{ - !this.props.isFolder && !this.props.isAdded + !this.props.isAdded ?
siteUtil.isBookmark(site) || siteUtil.isFolder(site) - ) + props.hasBookmarks = bookmarksState.getBookmarks(state).size > 0 || bookmarkFoldersState.getFolders(state).size > 0 return props } @@ -132,14 +123,12 @@ class AddEditBookmarkHanger extends React.Component { [css(styles.commonFormTitle)]: true })} data-l10n-id={this.props.heading} /> 0) { Array.from(e.dataTransfer.items).forEach((item) => { - item.getAsString((name) => appActions.addSite({ location: item.type, title: name }, siteTags.BOOKMARK)) + item.getAsString((name) => appActions.addBookmark(Immutable.fromJS({ + location: item.type, + title: name + }))) }) return } @@ -92,7 +101,7 @@ class BookmarksToolbar extends React.Component { .map((x) => x.trim()) .filter((x) => !x.startsWith('#') && x.length > 0) .forEach((url) => - appActions.addSite({ location: url }, siteTags.BOOKMARK)) + appActions.addBookmark(Immutable.fromJS({ location: url }))) } onDragEnter (e) { diff --git a/app/renderer/components/frame/frame.js b/app/renderer/components/frame/frame.js index 5ad3ef64eff..00b1af41a15 100644 --- a/app/renderer/components/frame/frame.js +++ b/app/renderer/components/frame/frame.js @@ -30,7 +30,6 @@ const tabMessageBoxState = require('../../../common/state/tabMessageBoxState') // Utils const frameStateUtil = require('../../../../js/state/frameStateUtil') -const siteUtil = require('../../../../js/state/siteUtil') const UrlUtil = require('../../../../js/lib/urlutil') const cx = require('../../../../js/lib/classSet') const urlParse = require('../../../common/urlParse') @@ -49,6 +48,7 @@ const {isFocused} = require('../../currentWindow') const debounce = require('../../../../js/lib/debounce') const locale = require('../../../../js/l10n') const imageUtil = require('../../../../js/lib/imageUtil') +const historyUtil = require('../../../common/lib/historyUtil') // Constants const settings = require('../../../../js/constants/settings') @@ -624,7 +624,7 @@ class Frame extends React.Component { // with setTitle. We either need to delay this call until the title is // or add a way to update it setTimeout(() => { - appActions.addSite(siteUtil.getDetailFromFrame(this.frame)) + appActions.addHistorySite(historyUtil.getDetailFromFrame(this.frame)) }, 250) } @@ -668,7 +668,7 @@ class Frame extends React.Component { url: e.validatedURL }) appActions.loadURLRequested(this.props.tabId, 'about:error') - appActions.removeSite(siteUtil.getDetailFromFrame(this.frame)) + appActions.removeHistorySite(historyUtil.getDetailFromFrame(this.frame)) } else if (isAborted(e.errorCode)) { // just stay put } else if (provisionLoadFailure) { diff --git a/app/renderer/components/main/main.js b/app/renderer/components/main/main.js index 5504295141d..c93ce69aed8 100644 --- a/app/renderer/components/main/main.js +++ b/app/renderer/components/main/main.js @@ -33,6 +33,7 @@ const WidevinePanel = require('./widevinePanel') const AutofillAddressPanel = require('../autofill/autofillAddressPanel') const AutofillCreditCardPanel = require('../autofill/autofillCreditCardPanel') const AddEditBookmarkHanger = require('../bookmarks/addEditBookmarkHanger') +const AddEditBookmarkFolder = require('../bookmarks/addEditBookmarkFolder') const LoginRequired = require('./loginRequired') const ReleaseNotes = require('./releaseNotes') const BookmarksToolbar = require('../bookmarks/bookmarksToolbar') @@ -534,21 +535,22 @@ class Main extends React.Component { props.isFullScreen = activeFrame.get('isFullScreen', false) props.isMaximized = isMaximized() || isFullScreen() props.captionButtonsVisible = isWindows - props.showContextMenu = !!currentWindow.get('contextMenuDetail') - props.showPopupWindow = !!currentWindow.get('popupWindowDetail') + props.showContextMenu = currentWindow.has('contextMenuDetail') + props.showPopupWindow = currentWindow.has('popupWindowDetail') props.showSiteInfo = currentWindow.getIn(['ui', 'siteInfo', 'isVisible']) && !isSourceAboutUrl(activeFrame.get('location')) props.showBravery = shieldState.braveShieldsEnabled(activeFrame) && !!currentWindow.get('braveryPanelDetail') - props.showClearData = !!currentWindow.getIn(['ui', 'isClearBrowsingDataPanelVisible']) - props.showImportData = !!currentWindow.get('importBrowserDataDetail') + props.showClearData = currentWindow.hasIn(['ui', 'isClearBrowsingDataPanelVisible']) + props.showImportData = currentWindow.has('importBrowserDataDetail') props.showWidevine = currentWindow.getIn(['widevinePanelDetail', 'shown']) && !isLinux - props.showAutoFillAddress = !!currentWindow.get('autofillAddressDetail') - props.showAutoFillCC = !!currentWindow.get('autofillCreditCardDetail') + props.showAutoFillAddress = currentWindow.has('autofillAddressDetail') + props.showAutoFillCC = currentWindow.has('autofillCreditCardDetail') props.showLogin = !!loginRequiredDetails - props.showBookmarkHanger = currentWindow.get('bookmarkDetail') && + props.showBookmarkHanger = currentWindow.has('bookmarkDetail') && !currentWindow.getIn(['bookmarkDetail', 'isBookmarkHanger']) - props.showNoScript = currentWindow.getIn(['ui', 'noScriptInfo', 'isVisible']) && + props.showBookmarkFolderDialog = currentWindow.has('bookmarkFolderDetail') + props.showNoScript = currentWindow.hasIn(['ui', 'noScriptInfo', 'isVisible']) && siteUtil.getOrigin(activeFrame.get('location')) props.showReleaseNotes = currentWindow.getIn(['ui', 'releaseNotes', 'isVisible']) props.showCheckDefault = isFocused() && defaultBrowserState.shouldDisplayDialog(state) @@ -657,6 +659,11 @@ class Main extends React.Component { ? : null } + { + this.props.showBookmarkFolderDialog + ? + : null + } { this.props.showNoScript ? diff --git a/app/renderer/components/navigation/navigationBar.js b/app/renderer/components/navigation/navigationBar.js index 59cb062677b..c7ae1abe9f9 100644 --- a/app/renderer/components/navigation/navigationBar.js +++ b/app/renderer/components/navigation/navigationBar.js @@ -52,7 +52,7 @@ class NavigationBar extends React.Component { if (editing) { windowActions.editBookmark(true, this.props.bookmarkKey) } else { - windowActions.onBookmarkAdded(true, this.props.bookmarkKey) + windowActions.onBookmarkAdded(true) } } diff --git a/app/renderer/components/navigation/urlBar.js b/app/renderer/components/navigation/urlBar.js index 8c8c2bc5b68..93d674942c0 100644 --- a/app/renderer/components/navigation/urlBar.js +++ b/app/renderer/components/navigation/urlBar.js @@ -167,7 +167,8 @@ class UrlBar extends React.Component { if (e.shiftKey) { const selectedIndex = this.props.urlbarLocationSuffix.length > 0 ? 1 : this.props.selectedIndex if (selectedIndex !== undefined) { - appActions.removeSite({ location: this.props.suggestionLocation }) + // TODO double check if this is correct + appActions.removeHistorySite({ location: this.props.suggestionLocation }) } } else { this.hideAutoComplete() diff --git a/app/renderer/components/tabs/content/closeTabIcon.js b/app/renderer/components/tabs/content/closeTabIcon.js index 577350c99cc..34bc48fa5bb 100644 --- a/app/renderer/components/tabs/content/closeTabIcon.js +++ b/app/renderer/components/tabs/content/closeTabIcon.js @@ -50,8 +50,9 @@ class CloseTabIcon extends React.Component { mergeProps (state, ownProps) { const currentWindow = state.get('currentWindow') const frameKey = ownProps.frameKey - const isPinnedTab = frameStateUtil.isPinned(currentWindow, frameKey) const frame = frameStateUtil.getFrameByKey(currentWindow, frameKey) || Immutable.Map() + const tabId = frame.get('tabId', tabState.TAB_ID_NONE) + const isPinnedTab = tabState.isTabPinned(state, tabId) const props = {} // used in renderer @@ -64,7 +65,7 @@ class CloseTabIcon extends React.Component { // used in functions props.frameKey = frameKey props.fixTabWidth = ownProps.fixTabWidth - props.tabId = frame.get('tabId', tabState.TAB_ID_NONE) + props.tabId = tabId props.hasFrame = !frame.isEmpty() return props diff --git a/app/renderer/components/tabs/content/favIcon.js b/app/renderer/components/tabs/content/favIcon.js index 354088de059..181300b4fb7 100644 --- a/app/renderer/components/tabs/content/favIcon.js +++ b/app/renderer/components/tabs/content/favIcon.js @@ -12,6 +12,7 @@ const TabIcon = require('./tabIcon') // State const tabContentState = require('../../../../common/state/tabContentState') +const tabState = require('../../../../common/state/tabState') // Utils const frameStateUtil = require('../../../../../js/state/frameStateUtil') @@ -34,12 +35,13 @@ class Favicon extends React.Component { const frameKey = ownProps.frameKey const frame = frameStateUtil.getFrameByKey(currentWindow, frameKey) || Immutable.Map() const isTabLoading = tabContentState.isTabLoading(currentWindow, frameKey) + const tabId = frame.get('tabId', tabState.TAB_ID_NONE) const props = {} // used in renderer props.isTabLoading = isTabLoading props.favicon = !isTabLoading && frame.get('icon') - props.isPinnedTab = frameStateUtil.isPinned(currentWindow, frameKey) + props.isPinnedTab = tabState.isTabPinned(state, tabId) props.tabIconColor = tabContentState.getTabIconColor(currentWindow, frameKey) props.isNarrowestView = tabContentState.isNarrowestView(currentWindow, frameKey) diff --git a/app/renderer/components/tabs/pinnedTabs.js b/app/renderer/components/tabs/pinnedTabs.js index 5615a69b1b5..4a9cc6b81b4 100644 --- a/app/renderer/components/tabs/pinnedTabs.js +++ b/app/renderer/components/tabs/pinnedTabs.js @@ -18,7 +18,6 @@ const windowActions = require('../../../../js/actions/windowActions') const windowStore = require('../../../../js/stores/windowStore') // Constants -const siteTags = require('../../../../js/constants/siteTags') const dragTypes = require('../../../../js/constants/dragTypes') // Utils @@ -26,6 +25,7 @@ const siteUtil = require('../../../../js/state/siteUtil') const dnd = require('../../../../js/dnd') const dndData = require('../../../../js/dndData') const frameStateUtil = require('../../../../js/state/frameStateUtil') +const pinnedSitesUtil = require('../../../common/lib/pinnedSitesUtil') const {isIntermediateAboutPage} = require('../../../../js/lib/appUrlUtil') class PinnedTabs extends React.Component { @@ -58,12 +58,14 @@ class PinnedTabs extends React.Component { if (!sourceDragData.get('pinnedLocation')) { appActions.tabPinned(sourceDragData.get('tabId'), true) } else { - const sourceDetails = siteUtil.getDetailFromFrame(sourceDragData, siteTags.PINNED) + const sourceDetails = pinnedSitesUtil.getDetailFromFrame(sourceDragData) const droppedOnFrame = this.dropFrame(droppedOnTab.props.frameKey) - const destinationDetails = siteUtil.getDetailFromFrame(droppedOnFrame, siteTags.PINNED) - appActions.moveSite(siteUtil.getSiteKey(sourceDetails), + const destinationDetails = pinnedSitesUtil.getDetailFromFrame(droppedOnFrame) + appActions.onPinnedTabReorder( + siteUtil.getSiteKey(sourceDetails), siteUtil.getSiteKey(destinationDetails), - isLeftSide) + isLeftSide + ) } } }, 0) diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index ebac3a874b5..d7a85e975c6 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -225,6 +225,7 @@ class Tab extends React.Component { const partition = typeof frame.get('partitionNumber') === 'string' ? frame.get('partitionNumber').replace(/^partition-/i, '') : frame.get('partitionNumber') + const tabId = frame.get('tabId', tabState.TAB_ID_NONE) const props = {} // used in renderer @@ -234,7 +235,7 @@ class Tab extends React.Component { props.notificationBarActive = notificationBarActive props.isActive = frameStateUtil.isFrameKeyActive(currentWindow, props.frameKey) props.tabWidth = currentWindow.getIn(['ui', 'tabs', 'fixTabWidth']) - props.isPinnedTab = frameStateUtil.isPinned(currentWindow, props.frameKey) + props.isPinnedTab = tabState.isTabPinned(state, tabId) props.canPlayAudio = tabContentState.canPlayAudio(currentWindow, props.frameKey) props.themeColor = tabContentState.getThemeColor(currentWindow, props.frameKey) props.isNarrowView = tabContentState.isNarrowView(currentWindow, props.frameKey) @@ -256,7 +257,7 @@ class Tab extends React.Component { props.totalTabs = state.get('tabs').size props.dragData = state.getIn(['dragData', 'type']) === dragTypes.TAB && state.get('dragData') props.hasTabInFullScreen = tabContentState.hasTabInFullScreen(currentWindow) - props.tabId = frame.get('tabId', tabState.TAB_ID_NONE) + props.tabId = tabId return props } diff --git a/app/renderer/reducers/contextMenuReducer.js b/app/renderer/reducers/contextMenuReducer.js index 36f3795b6a6..8a81000e92a 100644 --- a/app/renderer/reducers/contextMenuReducer.js +++ b/app/renderer/reducers/contextMenuReducer.js @@ -20,6 +20,7 @@ const appStoreRenderer = require('../../../js/stores/appStoreRenderer') // State const contextMenuState = require('../../common/state/contextMenuState') +const bookmarksState = require('../../common/state/bookmarksState') // Actions const appActions = require('../../../js/actions/appActions') @@ -37,6 +38,8 @@ const menuUtil = require('../../common/lib/menuUtil') const urlUtil = require('../../../js/lib/urlutil') const frameStateUtil = require('../../../js/state/frameStateUtil') const dndData = require('../../../js/dndData') +const bookmarkFoldersUtil = require('../../common/lib/bookmarkFoldersUtil') +const historyState = require('../../common/state/historyState') const {makeImmutable, isMap} = require('../../common/state/immutableUtil') const {getCurrentWindow} = require('../../renderer/currentWindow') const {getSetting} = require('../../../js/settings') @@ -237,18 +240,18 @@ const addFolderMenuItem = (closestDestinationDetail, isParent) => { return { label: locale.translation('addFolder'), click: () => { - let siteDetail = Immutable.fromJS({ tags: [siteTags.BOOKMARK_FOLDER] }) let closestKey = null + let folderDetails = Immutable.Map() if (closestDestinationDetail) { closestKey = siteUtil.getSiteKey(closestDestinationDetail) if (isParent) { - siteDetail = siteDetail.set('parentFolderId', (closestDestinationDetail.get('folderId') || closestDestinationDetail.get('parentFolderId'))) + folderDetails = folderDetails.set('parentFolderId', (closestDestinationDetail.get('folderId') || closestDestinationDetail.get('parentFolderId'))) } } - windowActions.addBookmark(siteDetail, closestKey) + windowActions.addBookmarkFolder(folderDetails, closestKey) } } } @@ -273,52 +276,48 @@ const openAllInNewTabsMenuItem = (folderDetail) => { } } -const siteDetailTemplateInit = (state, siteKey) => { - let isHistoryEntry = false - let multipleHistoryEntries = false - let multipleBookmarks = false - let isFolder = false - let isSystemFolder = false - let deleteLabel - let deleteTag - let siteDetail = appStoreRenderer.state.getIn(['sites', siteKey], Immutable.Map()) - const activeFrame = frameStateUtil.getActiveFrame(state) || Immutable.Map() +const getLabel = (siteDetail, activeFrame, type) => { + let label = '' - // TODO(bsclifton): pull this out to a method - if (siteUtil.isBookmark(siteDetail) && activeFrame) { - deleteLabel = 'deleteBookmark' - deleteTag = siteTags.BOOKMARK - } else if (siteUtil.isFolder(siteDetail)) { - isFolder = true - isSystemFolder = siteDetail.get('folderId') === 0 || - siteDetail.get('folderId') === -1 - deleteLabel = 'deleteFolder' - deleteTag = siteTags.BOOKMARK_FOLDER - } else if (siteUtil.isHistoryEntry(siteDetail)) { - isHistoryEntry = true - deleteLabel = 'deleteHistoryEntry' + if (type === siteTags.BOOKMARK && activeFrame) { + label = 'deleteBookmark' + } else if (type === siteTags.BOOKMARK_FOLDER) { + label = 'deleteFolder' + } else if (type === siteTags.HISTORY) { + label = 'deleteHistoryEntry' } else if (Immutable.List.isList(siteDetail)) { // Multiple bookmarks OR history entries selected - multipleHistoryEntries = true + /* multipleHistoryEntries = true multipleBookmarks = true siteDetail.forEach((site) => { if (!siteUtil.isBookmark(site)) multipleBookmarks = false if (!siteUtil.isHistoryEntry(site)) multipleHistoryEntries = false }) if (multipleBookmarks) { - deleteLabel = 'deleteBookmarks' + label = 'deleteBookmarks' deleteTag = siteTags.BOOKMARK } else if (multipleHistoryEntries) { - deleteLabel = 'deleteHistoryEntries' - } - } else { - deleteLabel = '' + label = 'deleteHistoryEntries' + } */ } + return label +} + +// TODO check when we send in multiple siteKeys +const siteDetailTemplateInit = (state, siteKey, type) => { + let isFolder = type === siteTags.BOOKMARK_FOLDER + let siteDetail = bookmarksState.findBookmark(appStoreRenderer.state, siteKey) + if (siteDetail.isEmpty()) { + siteDetail = historyState.getSite(state, siteKey) + } + let multiple = Immutable.List.isList(siteDetail) + const activeFrame = frameStateUtil.getActiveFrame(state) || Immutable.Map() const template = [] + const label = getLabel(siteDetail, activeFrame, type) - if (!isFolder) { - if (!Immutable.List.isList(siteDetail)) { + if (type !== siteTags.BOOKMARK_FOLDER) { + if (!multiple) { const location = siteDetail.get('location') template.push( @@ -348,52 +347,62 @@ const siteDetailTemplateInit = (state, siteKey) => { template.push(openAllInNewTabsMenuItem(siteDetail), CommonMenu.separatorMenuItem) } - if (!isSystemFolder) { + if (siteDetail.get('folderId') !== 0 && siteDetail.get('folderId') !== -1) { // Picking this menu item pops up the AddEditBookmark modal // - History can be deleted but not edited // - Multiple bookmarks cannot be edited at once // - "Bookmarks Toolbar" and "Other Bookmarks" folders cannot be deleted - if (!isHistoryEntry && !multipleHistoryEntries && !multipleBookmarks) { + if (type !== siteTags.HISTORY && !multiple) { template.push( { label: locale.translation(isFolder ? 'editFolder' : 'editBookmark'), click: () => { const editKey = siteUtil.getSiteKey(siteDetail) - windowActions.editBookmark(false, editKey) + if (isFolder) { + windowActions.editBookmarkFolder(editKey) + } else { + windowActions.editBookmark(false, editKey) + } } }, - CommonMenu.separatorMenuItem) + CommonMenu.separatorMenuItem + ) } template.push( { - label: locale.translation(deleteLabel), + label: locale.translation(label), click: () => { - if (Immutable.List.isList(siteDetail)) { - siteDetail.forEach((site) => appActions.removeSite(site, deleteTag)) - } else { - appActions.removeSite(siteDetail, deleteTag) + // TODO implement multiple + const key = siteUtil.getSiteKey(siteDetail) + if (type === siteTags.HISTORY) { + appActions.removeHistorySite(siteDetail) + } else if (type === siteTags.BOOKMARK) { + appActions.removeBookmark(key) + } else if (type === siteTags.BOOKMARK_FOLDER) { + appActions.removeBookmarkFolder(key) } } }) } - if (!isHistoryEntry && !multipleHistoryEntries) { + if (type !== siteTags.HISTORY) { if (template[template.length - 1] !== CommonMenu.separatorMenuItem) { template.push(CommonMenu.separatorMenuItem) } template.push( - addBookmarkMenuItem('addBookmark', siteUtil.getDetailFromFrame(activeFrame, siteTags.BOOKMARK), siteDetail, true), - addFolderMenuItem(siteDetail, true)) + addBookmarkMenuItem('addBookmark', bookmarkUtil.getDetailFromFrame(activeFrame), siteDetail, true), + addFolderMenuItem(siteDetail, true) + ) } return menuUtil.sanitizeTemplateItems(template) } -const onSiteDetailMenu = (state, siteKey) => { +const onSiteDetailMenu = (state, siteKey, type) => { state = validateState(state) - const template = siteDetailTemplateInit(state, siteKey) + const template = siteDetailTemplateInit(state, siteKey, type) const menu = Menu.buildFromTemplate(template) menu.popup(getCurrentWindow()) @@ -402,9 +411,7 @@ const onSiteDetailMenu = (state, siteKey) => { const showBookmarkFolderInit = (state, parentBookmarkFolderKey) => { const appState = appStoreRenderer.state - const allBookmarkItems = siteUtil.getBookmarks(appState.get('sites', Immutable.List())) - const parentBookmarkFolder = appState.getIn(['sites', parentBookmarkFolderKey]) - const items = siteUtil.filterSitesRelativeTo(allBookmarkItems, parentBookmarkFolder) + const items = bookmarksState.getBookmarksWithFolders(appState, parentBookmarkFolderKey) if (items.size === 0) { return [{ l10nLabelId: 'emptyFolderItem', @@ -417,8 +424,11 @@ const showBookmarkFolderInit = (state, parentBookmarkFolderKey) => { e.preventDefault() const bookmark = dnd.prepareBookmarkDataFromCompatible(e.dataTransfer) if (bookmark) { - const bookmarkSiteKey = siteUtil.getSiteKey(bookmark) - appActions.moveSite(bookmarkSiteKey, parentBookmarkFolderKey, false, true) + if (bookmark.get('type') === siteTags.BOOKMARK_FOLDER) { + appActions.moveBookmarkFolder(bookmark.get('key'), parentBookmarkFolderKey, false, true) + } else { + appActions.moveBookmark(bookmark.get('key'), parentBookmarkFolderKey, false, true) + } } } }] @@ -433,7 +443,7 @@ const bookmarkItemsInit = (state, items) => { for (let item of items) { const site = item[1] const siteKey = item[0] - const isFolder = siteUtil.isFolder(site) + const isFolder = bookmarkFoldersUtil.isFolder(site) let faIcon if (showFavicon && !site.get('favicon')) { faIcon = isFolder ? 'fa-folder-o' : 'fa-file-o' @@ -441,11 +451,11 @@ const bookmarkItemsInit = (state, items) => { const templateItem = { bookmark: site, draggable: true, - label: site.get('customTitle', site.get('title', site.get('location'))), + label: site.get('title', site.get('location')), icon: showFavicon ? site.get('favicon') : undefined, faIcon, contextMenu: function () { - windowActions.onSiteDetailMenu(siteKey) + windowActions.onSiteDetailMenu(siteKey, isFolder ? siteTags.BOOKMARK_FOLDER : siteTags.BOOKMARK) }, dragEnd: function () { dnd.onDragEnd() @@ -453,8 +463,9 @@ const bookmarkItemsInit = (state, items) => { dragStart: function (e) { dnd.onDragStart(dragTypes.BOOKMARK, Immutable.fromJS({ location: site.get('location'), - title: site.get('customTitle', site.get('title')), - bookmarkKey: siteKey + title: site.get('title'), + key: siteKey, + type: isFolder ? siteTags.BOOKMARK_FOLDER : siteTags.BOOKMARK }), e) }, dragOver: function (e) { @@ -465,11 +476,23 @@ const bookmarkItemsInit = (state, items) => { e.preventDefault() const bookmarkItem = dnd.prepareBookmarkDataFromCompatible(e.dataTransfer) if (bookmarkItem) { - appActions.moveSite(bookmarkItem.get('bookmarkKey'), siteKey, dndData.shouldPrependVerticalItem(e.target, e.clientY)) + if (bookmarkItem.get('type') === siteTags.BOOKMARK_FOLDER) { + appActions.moveBookmarkFolder( + bookmarkItem.get('key'), + siteKey, + dndData.shouldPrependVerticalItem(e.target, e.clientY) + ) + } else { + appActions.moveBookmark( + bookmarkItem.get('key'), + siteKey, + dndData.shouldPrependVerticalItem(e.target, e.clientY) + ) + } } }, click: function (e) { - bookmarkActions.clickBookmarkItem(siteKey, activeFrame.get('tabId'), e) + bookmarkActions.clickBookmarkItem(siteKey, activeFrame.get('tabId'), isFolder, e) } } if (isFolder) { @@ -485,14 +508,14 @@ const onMoreBookmarksMenu = (state, action) => { action = validateAction(action) state = validateState(state) - const sites = appStoreRenderer.state.get('sites') - let newSites = {} + const appState = appStoreRenderer.state + let newSites = Immutable.OrderedMap() - for (let siteKey of action.get('bookmarks')) { - newSites[siteKey] = sites.get(siteKey) + for (let key of action.get('bookmarks')) { + newSites.set(key, bookmarksState.findBookmark(appState, key)) } - const menuTemplate = bookmarkItemsInit(state, Immutable.fromJS(newSites)) + const menuTemplate = bookmarkItemsInit(state, newSites) menuTemplate.push({ l10nLabelId: 'moreBookmarks', @@ -651,7 +674,7 @@ const contextMenuReducer = (windowState, action) => { windowState = onShowBookmarkFolderMenu(windowState, action) break case windowConstants.WINDOW_ON_SITE_DETAIL_MENU: - windowState = onSiteDetailMenu(windowState, action.bookmarkKey) + windowState = onSiteDetailMenu(windowState, action.bookmarkKey, action.type) break } return windowState diff --git a/app/renderer/reducers/frameReducer.js b/app/renderer/reducers/frameReducer.js index cfe38fcc3d6..a10fac339eb 100644 --- a/app/renderer/reducers/frameReducer.js +++ b/app/renderer/reducers/frameReducer.js @@ -10,7 +10,6 @@ const Immutable = require('immutable') const appConstants = require('../../../js/constants/appConstants') const windowConstants = require('../../../js/constants/windowConstants') const config = require('../../../js/constants/config') -const siteTags = require('../../../js/constants/siteTags') // Actions const appActions = require('../../../js/actions/appActions') @@ -20,7 +19,7 @@ const frameStateUtil = require('../../../js/state/frameStateUtil') const {getCurrentWindowId} = require('../currentWindow') const {getSourceAboutUrl, getSourceMagnetUrl} = require('../../../js/lib/appUrlUtil') const {isURL, isPotentialPhishingUrl, getUrlFromInput} = require('../../../js/lib/urlutil') -const siteUtil = require('../../../js/state/siteUtil') +const bookmarkUtil = require('../../common/lib/bookmarkUtil') const setFullScreen = (state, action) => { const index = frameStateUtil.getIndexByTabId(state, action.tabId) @@ -229,8 +228,8 @@ const frameReducer = (state, action, immutableAction) => { // TODO make this an appAction that gets the bookmark data from tabState const frameProps = frameStateUtil.getFrameByTabId(state, action.tabId) if (frameProps) { - const bookmark = siteUtil.getDetailFromFrame(frameProps, siteTags.BOOKMARK) - appActions.addSite(bookmark, siteTags.BOOKMARK) + const bookmark = bookmarkUtil.getDetailFromFrame(frameProps) + appActions.addBookmark(bookmark) } break } diff --git a/app/sessionStore.js b/app/sessionStore.js index 852fbc3cb33..f45e7b19b22 100644 --- a/app/sessionStore.js +++ b/app/sessionStore.js @@ -17,39 +17,32 @@ const path = require('path') const electron = require('electron') const os = require('os') const app = electron.app -const locale = require('./locale') + +// Constants const UpdateStatus = require('../js/constants/updateStatus') const settings = require('../js/constants/settings') +const siteTags = require('../js/constants/siteTags') const downloadStates = require('../js/constants/downloadStates') + +// State +const tabState = require('./common/state/tabState') +const windowState = require('./common/state/windowState') + +// Utils +const locale = require('./locale') const siteUtil = require('../js/state/siteUtil') -const { topSites, pinnedTopSites } = require('../js/data/newTabData') -const { defaultSiteSettingsList } = require('../js/data/siteSettingsList') -const sessionStorageVersion = 1 +const {pinnedTopSites} = require('../js/data/newTabData') +const {defaultSiteSettingsList} = require('../js/data/siteSettingsList') const filtering = require('./filtering') const autofill = require('./autofill') const {navigatableTypes} = require('../js/lib/appUrlUtil') const Channel = require('./channel') -const { makeImmutable } = require('./common/state/immutableUtil') -const tabState = require('./common/state/tabState') -const windowState = require('./common/state/windowState') - +const {makeImmutable} = require('./common/state/immutableUtil') +const {getSetting} = require('../js/settings') const platformUtil = require('./common/lib/platformUtil') -const getSetting = require('../js/settings').getSetting -const sessionStorageName = `session-store-${sessionStorageVersion}` -const getTopSiteMap = () => { - if (Array.isArray(topSites) && topSites.length) { - let siteMap = {} - let order = 0 - topSites.forEach((site) => { - let key = siteUtil.getSiteKey(Immutable.fromJS(site)) - site.order = order++ - siteMap[key] = site - }) - return siteMap - } - return {} -} +const sessionStorageVersion = 1 +const sessionStorageName = `session-store-${sessionStorageVersion}` const getTempStoragePath = (filename) => { const epochTimestamp = (new Date()).getTime().toString() @@ -329,7 +322,7 @@ module.exports.cleanAppData = (data, isShutdown) => { if (data.sites) { const clearHistory = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_HISTORY) === true if (clearHistory) { - data.sites = siteUtil.clearHistory(Immutable.fromJS(data.sites)).toJS() + data.historySites = {} if (data.about) { delete data.about.history delete data.about.newtab @@ -498,6 +491,115 @@ module.exports.runPreMigrations = (data) => { } } + // TODO remove this second level additional checks + if (data.sites) { + // pinned sites + if (!data.pinnedSites) { + data.pinnedSites = {} + for (let key of Object.keys(data.sites)) { + const site = data.sites[key] + if (site.tags && site.tags.includes('pinned')) { + delete site.tags + data.pinnedSites[key] = site + } + } + } + + // default sites + let newTab = data.about.newtab + + // TODO remove this pinnedSites check + if (newTab && !data.pinnedSites) { + const ignoredSites = [] + const pinnedSites = [] + + if (newTab.ignoredTopSites) { + for (let site of newTab.ignoredTopSites) { + if (site) { + ignoredSites.push(`${site.location}|0|0`) + } + } + data.about.newtab.ignoredTopSites = ignoredSites + } + + if (newTab.pinnedTopSites) { + for (let site of newTab.pinnedTopSites) { + if (site) { + site.key = `${site.location}|0|0` + pinnedSites.push(site) + } + } + data.about.newtab.pinnedTopSites = pinnedSites + } + + data.about.newtab.sites = [] + } + + // bookmark folders + if (!data.bookmarkFolders) { + data.bookmarkFolders = {} + + for (let key of Object.keys(data.sites)) { + const folder = data.sites[key] + if (folder.tags && folder.tags.includes(siteTags.BOOKMARK_FOLDER)) { + delete folder.tags + + if (folder.customTitle) { + folder.title = folder.customTitle + delete folder.customTitle + } + + if (folder.parentFolderId == null) { + folder.parentFolderId = 0 + } + + folder.type = siteTags.BOOKMARK_FOLDER + data.bookmarkFolders['' + key + ''] = folder + } + } + } + + // bookmarks + if (!data.bookmarks) { + data.bookmarks = {} + + for (let key of Object.keys(data.sites)) { + const bookmark = data.sites[key] + if (bookmark.tags && bookmark.tags.includes(siteTags.BOOKMARK)) { + delete bookmark.tags + + if (bookmark.customTitle) { + bookmark.title = bookmark.customTitle + delete bookmark.customTitle + } + + if (bookmark.parentFolderId == null) { + bookmark.parentFolderId = 0 + } + + bookmark.type = siteTags.BOOKMARK + data.bookmarks[key] = bookmark + } + } + } + // TODO sort newly added bookmarks and folders, add bookmarkOrderCache + + // history + if (!data.historySites) { + data.historySites = {} + + for (let key of Object.keys(data.sites)) { + const site = data.sites[key] + if (site.lastAccessedTime || !site.tags || site.tags.length === 0) { + data.historySites[key] = site + } + } + } + + // TODO enable this delete when everything is done + // delete data.sites + } + return data } @@ -634,7 +736,12 @@ module.exports.defaultAppState = () => { lastConfirmedRecordTimestamp: 0 }, locationSiteKeysCache: undefined, - sites: getTopSiteMap(), + sites: {}, + pinnedSites: {}, + bookmarks: {}, + bookmarkFolders: {}, + bookmarkOrderCache: {}, + historySites: {}, tabs: [], windows: [], extensions: {}, @@ -658,7 +765,7 @@ module.exports.defaultAppState = () => { about: { newtab: { gridLayoutSize: 'small', - sites: topSites, + sites: [], ignoredTopSites: [], pinnedTopSites: pinnedTopSites }, diff --git a/app/sync.js b/app/sync.js index 05122a0fc51..f5ebcc05c78 100644 --- a/app/sync.js +++ b/app/sync.js @@ -19,13 +19,14 @@ const appActions = require('../js/actions/appActions') const syncConstants = require('../js/constants/syncConstants') const appDispatcher = require('../js/dispatcher/appDispatcher') const AppStore = require('../js/stores/appStore') -const siteUtil = require('../js/state/siteUtil') const syncUtil = require('../js/state/syncUtil') const syncPendState = require('./common/state/syncPendState') const getSetting = require('../js/settings').getSetting const settings = require('../js/constants/settings') const extensions = require('./extensions') const {unescapeJSONPointer} = require('./common/lib/jsonUtil') +const bookmarkFoldersState = require('./common/state/bookmarkFoldersState') +const bookmarksState = require('./common/state/bookmarksState') const CATEGORY_MAP = syncUtil.CATEGORY_MAP const CATEGORY_NAMES = Object.keys(categories) @@ -33,7 +34,7 @@ const SYNC_ACTIONS = Object.values(syncConstants) // Fields that should trigger a sync SEND when changed const SYNC_FIELDS = { - sites: ['location', 'customTitle', 'folderId', 'parentFolderId', 'tags'], + sites: ['location', 'title', 'folderId', 'parentFolderId', 'tags'], siteSettings: Object.keys(syncUtil.siteSettingDefaults) } @@ -260,7 +261,8 @@ module.exports.onSyncReady = (isFirstRun, e) => { sendSyncRecords(e.sender, writeActions.CREATE, [deviceRecord]) deviceIdSent = true } - const sites = appState.get('sites') || new Immutable.List() + const bookmarks = bookmarksState.getBookmarks(appState) + const bookmarkFolders = bookmarkFoldersState.getFolders(appState) const seed = appState.get('seed') || new Immutable.List() /** @@ -273,38 +275,34 @@ module.exports.onSyncReady = (isFirstRun, e) => { */ const folderToObjectId = {} const bookmarksToSync = [] - const shouldSyncBookmark = (site) => { - if (!site) { - return false - } - if (siteUtil.isSiteBookmarked(sites, site) !== true && siteUtil.isFolder(site) !== true) { - return false - } + + const shouldSyncBookmark = (bookmark) => { // originalSeed is set on reset to prevent synced bookmarks on a device // from being re-synced. - const originalSeed = site.get('originalSeed') - if (site.get('objectId') && (!originalSeed || seed.equals(originalSeed))) { + const originalSeed = bookmark.get('originalSeed') + if (bookmark.get('objectId') && (!originalSeed || seed.equals(originalSeed))) { return false } - if (folderToObjectId[site.get('folderId')]) { return false } - return syncUtil.isSyncable('bookmark', site) + + return !folderToObjectId[bookmark.get('folderId')] } - const syncBookmark = (site) => { - const siteJS = site.toJS() - const parentFolderId = site.get('parentFolderId') + const syncBookmark = (bookmark) => { + const bookmarkJS = bookmark.toJS() + + const parentFolderId = bookmark.get('parentFolderId') if (typeof parentFolderId === 'number') { if (!folderToObjectId[parentFolderId]) { - const folderResult = siteUtil.getFolder(sites, parentFolderId) + const folderResult = bookmarkFolders.get(parentFolderId) if (folderResult) { syncBookmark(folderResult[1]) } } - siteJS.parentFolderObjectId = folderToObjectId[parentFolderId] + bookmarkJS.parentFolderObjectId = folderToObjectId[parentFolderId] } - const record = syncUtil.createSiteData(siteJS, appState) - const folderId = site.get('folderId') + const record = syncUtil.createSiteData(bookmarkJS, appState) + const folderId = bookmark.get('folderId') if (typeof folderId === 'number') { folderToObjectId[folderId] = record.objectId } @@ -313,12 +311,16 @@ module.exports.onSyncReady = (isFirstRun, e) => { } // Sync bookmarks that have not been synced yet. - sites.forEach((site) => { - if (shouldSyncBookmark(site) !== true) { + bookmarks.forEach((bookmark) => { + if (shouldSyncBookmark(bookmark) !== true) { return } - syncBookmark(site) + + syncBookmark(bookmark) }) + + // TODO add bookamrkFolder sync as well + sendSyncRecords(e.sender, writeActions.CREATE, bookmarksToSync) // Sync site settings that have not been synced yet @@ -490,7 +492,7 @@ module.exports.init = function (appState) { } if (!bookmarksToolbarShown && isFirstRun) { // syncing for the first time - const bookmarks = siteUtil.getBookmarks(AppStore.getState().get('sites')) + const bookmarks = bookmarksState.getBookmarks(AppStore.getState()) if (!bookmarks.size) { for (const record of records) { if (record && record.objectData === 'bookmark') { diff --git a/docs/state.md b/docs/state.md index cf2a9e3bd44..7c77232c4ac 100644 --- a/docs/state.md +++ b/docs/state.md @@ -53,6 +53,28 @@ AppStore timestamp: number } }, + bookmarks: { + [bookmarkKey]: { + favicon: string, // URL of the favicon + location: string, + objectId: Array., + originalSeed: Array., // bookmarks that have been synced before a sync profile reset + parentFolderId: number, + partitionNumber: number, // optionally specifies a specific session + skipSync: boolean, + title: string + } + }, + bookmarkFolders: { + [folderKey]: { + folderId: number, + objectId: Array., + originalSeed: Array., // only set for bookmarks that have been synced before a sync profile reset + parentFolderId: number, // set for bookmarks and bookmark folders only + skipSync: boolean, // Set for objects FETCHed by sync + title: string + } + }, clearBrowsingDataDefaults: { allSiteCookies: boolean, autocompleteData: boolean, @@ -118,6 +140,18 @@ AppStore flash: { enabled: boolean // enable flash }, + historySites: { + [siteKey]: { + favicon: string, // URL of the favicon + lastAccessedTime: number, // datetime.getTime() + location: string, + objectId: Array., + partitionNumber: number, // optionally specifies a specific session + skipSync: boolean, // Set for objects FETCHed by sync + title: string, + themeColor: string + } + }, menu: { template: object // used on Windows and by our tests: template object with Menubar control }, @@ -156,6 +190,13 @@ AppStore origin: string, // origin of the form username: string }], + pinnedSites: { + [siteKey]: { + location: string, + title: string, + order: number + } + }, settings: { // See defaults in js/constants/appConfig.js 'adblock.customRules': string, // custom rules in ABP filter syntax @@ -230,24 +271,6 @@ AppStore 'tabs.switch-to-new-tabs': boolean, // true if newly opened tabs should be focused immediately 'tabs.tabs-per-page': number // number of tabs per tab page }, - sites: { - [siteKey]: { - creationTime: number, //creation time of bookmark - customTitle: string, // User provided title for bookmark; overrides title - favicon: string, // URL of the favicon - folderId: number, // set for bookmark folders only - lastAccessedTime: number, // datetime.getTime() - location: string, - objectId: Array., - originalSeed: Array., // only set for bookmarks that have been synced before a sync profile reset - parentFolderId: number, // set for bookmarks and bookmark folders only - partitionNumber: number, // optionally specifies a specific session - skipSync: boolean, // Set for objects FETCHed by sync - tags: [string], // empty, 'bookmark', 'bookmark-folder', 'default', or 'reader' - themeColor: string, // CSS compatible color string - title: string - } // folder: folderId; bookmark/history: location + partitionNumber + parentFolderId - }, locationSiteKeyCache: { [location]: Array. // location -> site keys }, diff --git a/js/about/aboutActions.js b/js/about/aboutActions.js index 1992015338f..f231492ae89 100644 --- a/js/about/aboutActions.js +++ b/js/about/aboutActions.js @@ -192,16 +192,6 @@ const aboutActions = { ipc.sendToHost(messages.CONTEXT_MENU_OPENED, nodeProps, contextMenuType) }, - moveSite: function (sourceKey, destinationKey, prepend, destinationIsParent) { - aboutActions.dispatchAction({ - actionType: appConstants.APP_MOVE_SITE, - sourceKey, - destinationKey, - prepend, - destinationIsParent - }) - }, - downloadRevealed: function (downloadId) { aboutActions.dispatchAction({ actionType: appConstants.APP_DOWNLOAD_REVEALED, diff --git a/js/about/newtab.js b/js/about/newtab.js index e9a2b83010b..ab8cb493904 100644 --- a/js/about/newtab.js +++ b/js/about/newtab.js @@ -5,26 +5,35 @@ const path = require('path') const React = require('react') const Immutable = require('immutable') -const messages = require('../constants/messages') -const HTML5Backend = require('react-dnd-html5-backend') const {DragDropContext} = require('react-dnd') +const HTML5Backend = require('react-dnd-html5-backend') + +// Components const Stats = require('./newTabComponents/stats') const Clock = require('./newTabComponents/clock') const Block = require('./newTabComponents/block') const SiteRemovalNotification = require('./newTabComponents/siteRemovalNotification') const FooterInfo = require('./newTabComponents/footerInfo') -const aboutActions = require('./aboutActions') -const siteUtil = require('../state/siteUtil') -const urlutils = require('../lib/urlutil') -const siteTags = require('../constants/siteTags') -const config = require('../constants/config') -const backgrounds = require('../data/backgrounds') -const {random} = require('../../app/common/lib/randomUtil') const NewPrivateTab = require('./newprivatetab') + +// Constants +const messages = require('../constants/messages') +const config = require('../constants/config') + +// Actions +const aboutActions = require('./aboutActions') const windowActions = require('../actions/windowActions') +// Data +const backgrounds = require('../data/backgrounds') +const newTabData = require('../data/newTabData') + +// Utils +const urlutils = require('../lib/urlutil') +const {random} = require('../../app/common/lib/randomUtil') const ipc = window.chrome.ipcRenderer +// Styles require('../../less/about/newtab.less') require('../../node_modules/font-awesome/css/font-awesome.css') @@ -32,15 +41,17 @@ class NewTabPage extends React.Component { constructor (props) { super(props) this.state = { - showSiteRemovalNotification: false, + showNotification: false, imageLoadFailed: false, updatedStamp: undefined, showEmptyPage: true, showImages: false, backgroundImage: undefined } - ipc.on(messages.NEWTAB_DATA_UPDATED, (e, newTabData) => { - const data = Immutable.fromJS(newTabData || {}) + this.staticData = Immutable.fromJS(newTabData.topSites) + + ipc.on(messages.NEWTAB_DATA_UPDATED, (e, newData) => { + let data = Immutable.fromJS(newData || {}) const updatedStamp = data.getIn(['newTabDetail', 'updatedStamp']) // Only update if the data has changed. @@ -50,6 +61,18 @@ class NewTabPage extends React.Component { return } + const topSites = data.getIn(['newTabDetail', 'sites'], Immutable.List()) + if (topSites.size < 18) { + const pinned = data.getIn(['newTabDetail', 'pinnedTopSites'], Immutable.List()) + const ignored = data.getIn(['newTabDetail', 'ignoredTopSites'], Immutable.List()) + const preDefined = this.staticData + .filter((site) => { + const key = site.get('key') + return !pinned.find(site => site.get('key') === key) && !ignored.includes(key) + }) + data = data.setIn(['newTabDetail', 'sites'], topSites.concat(preDefined)) + } + const showEmptyPage = !!data.get('showEmptyPage') const showImages = !!data.get('showImages') && !showEmptyPage this.setState({ @@ -63,83 +86,79 @@ class NewTabPage extends React.Component { }) }) } + get showImages () { return this.state.showImages && !!this.state.backgroundImage } + get randomBackgroundImage () { const image = Object.assign({}, backgrounds[Math.floor(random() * backgrounds.length)]) image.style = {backgroundImage: 'url(' + image.source + ')'} return image } + get fallbackImage () { const image = Object.assign({}, config.newtab.fallbackImage) const pathToImage = path.join(__dirname, '..', '..', image.source) image.style = {backgroundImage: 'url(' + `${pathToImage}` + ')'} return image } + get topSites () { - return this.state.newTabData.getIn(['newTabDetail', 'sites']) || Immutable.List() + return this.state.newTabData.getIn(['newTabDetail', 'sites']) } + get pinnedTopSites () { - return (this.state.newTabData.getIn(['newTabDetail', 'pinnedTopSites']) || Immutable.List()).setSize(18) + return this.state.newTabData.getIn(['newTabDetail', 'pinnedTopSites'], Immutable.List()) } + get ignoredTopSites () { - return this.state.newTabData.getIn(['newTabDetail', 'ignoredTopSites']) || Immutable.List() + return this.state.newTabData.getIn(['newTabDetail', 'ignoredTopSites'], Immutable.List()) } + get gridLayoutSize () { - return this.state.newTabData.getIn(['newTabDetail', 'gridLayoutSize']) || 'small' + return this.state.newTabData.getIn(['newTabDetail', 'gridLayoutSize'], 'small') } - isPinned (siteProps) { - return this.pinnedTopSites.filter((site) => { - if (!site || !site.get) return false - return site.get('location') === siteProps.get('location') && - site.get('partitionNumber') === siteProps.get('partitionNumber') - }).size > 0 + + isPinned (siteKey) { + return this.pinnedTopSites.find(site => site.get('key') === siteKey) } - isBookmarked (siteProps) { - return siteUtil.isBookmark(siteProps) + + isBookmarked (siteKey) { + // TODO add bookmark status to site that are send here + return false // siteUtil.isBookmark(null) } + get gridLayout () { const sizeToCount = {large: 18, medium: 12, small: 6} const count = sizeToCount[this.gridLayoutSize] - return this.topSites.take(count) + + let sites = this.pinnedTopSites.take(count) + + if (sites.size < count) { + sites = sites.concat(this.topSites.take(count - sites.size)) + } + + return sites } - showSiteRemovalNotification () { + + showNotification () { this.setState({ - showSiteRemovalNotification: true + showNotification: true }) } + hideSiteRemovalNotification () { this.setState({ - showSiteRemovalNotification: false + showNotification: false }) } - /** - * save number of rows on store. gridsLayout starts with 3 rows (large). - * Rows are reduced at each click and then reset to three again - */ - onChangeGridLayout () { - const gridLayoutSize = this.gridLayoutSize - const changeGridSizeTo = (size) => aboutActions.setNewTabDetail({gridLayoutSize: size}) - - if (gridLayoutSize === 'large') { - changeGridSizeTo('medium') - } else if (gridLayoutSize === 'medium') { - changeGridSizeTo('small') - } else if (gridLayoutSize === 'small') { - changeGridSizeTo('large') - } else { - changeGridSizeTo('large') - } - - return gridLayoutSize - } - - onDraggedSite (currentId, finalId) { + // TODO fix this function + onDraggedSite (siteKey, destinationKey) { let gridSites = this.topSites - const currentPosition = gridSites.filter((site) => site.get('location') === currentId).get(0) - const finalPosition = gridSites.filter((site) => site.get('location') === finalId).get(0) + const currentPosition = gridSites.get(siteKey) + const finalPosition = gridSites.get(destinationKey) const currentPositionIndex = gridSites.indexOf(currentPosition) const finalPositionIndex = gridSites.indexOf(finalPosition) @@ -154,63 +173,59 @@ class NewTabPage extends React.Component { pinnedTopSites = pinnedTopSites.splice(finalPositionIndex, 0, currentPosition) // If site is pinned, update pinnedTopSites list - const newTabState = {} + const newTabState = Immutable.Map() if (this.isPinned(currentPosition)) { - newTabState.pinnedTopSites = pinnedTopSites + newTabState.set('pinnedTopSites', pinnedTopSites) } - newTabState.sites = gridSites + newTabState.set('sites', gridSites) // Only update if there was an actual change - const stateDiff = Immutable.fromJS(newTabState) const existingState = this.state.newTabData || Immutable.fromJS({}) - const proposedState = existingState.mergeIn(['newTabDetail'], stateDiff) + const proposedState = existingState.mergeIn(['newTabDetail'], newTabState) if (!proposedState.isSubset(existingState)) { - aboutActions.setNewTabDetail(stateDiff) + aboutActions.setNewTabDetail(newTabState) } } - onToggleBookmark (siteProps) { - const siteDetail = siteUtil.getDetailFromFrame(siteProps, siteTags.BOOKMARK) - const editing = this.isBookmarked(siteProps) - const key = siteUtil.getSiteKey(siteDetail) + onToggleBookmark (site) { + const editing = this.isBookmarked(site.get('key')) if (editing) { - windowActions.editBookmark(false, key) + windowActions.editBookmark(false, site.get('key')) } else { - windowActions.onBookmarkAdded(false, key) + windowActions.onBookmarkAdded(false, site) } } - onPinnedTopSite (siteProps) { - const currentPosition = this.topSites.filter((site) => siteProps.get('location') === site.get('location')).get(0) - const currentPositionIndex = this.topSites.indexOf(currentPosition) + onPinnedTopSite (siteKey) { + let pinnedTopSites = this.pinnedTopSites + const siteProps = this.topSites.find(site => site.get('key') === siteKey) - // If pinned, leave it null. Otherwise stores site on ignoredTopSites list, retaining the same position - let pinnedTopSites = this.pinnedTopSites.splice(currentPositionIndex, 1, this.isPinned(siteProps) ? null : siteProps) + if (this.isPinned(siteKey)) { + pinnedTopSites = pinnedTopSites.filter(site => site.get('key') !== siteKey) + } else { + pinnedTopSites = pinnedTopSites.push(siteProps) + } aboutActions.setNewTabDetail({pinnedTopSites: pinnedTopSites}) } - onIgnoredTopSite (siteProps) { - this.showSiteRemovalNotification() + onIgnoredTopSite (siteKey) { + this.showNotification(siteKey) // If a pinnedTopSite is ignored, remove it from the pinned list as well const newTabState = {} - if (this.isPinned(siteProps)) { - const gridSites = this.topSites - const currentPosition = gridSites.filter((site) => siteProps.get('location') === site.get('location')).get(0) - const currentPositionIndex = gridSites.indexOf(currentPosition) - const pinnedTopSites = this.pinnedTopSites.splice(currentPositionIndex, 1, null) - newTabState.pinnedTopSites = pinnedTopSites + if (this.isPinned(siteKey)) { + newTabState.pinnedTopSites = this.pinnedTopSites.filter(site => site.get('key') !== siteKey) } - newTabState.ignoredTopSites = this.ignoredTopSites.push(siteProps) + newTabState.ignoredTopSites = this.ignoredTopSites.push(siteKey) aboutActions.setNewTabDetail(newTabState, true) } onUndoIgnoredTopSite () { // Remove last List's entry - const ignoredTopSites = this.ignoredTopSites.splice(-1, 1) + const ignoredTopSites = this.ignoredTopSites.pop() aboutActions.setNewTabDetail({ignoredTopSites: ignoredTopSites}, true) this.hideSiteRemovalNotification() } @@ -236,6 +251,12 @@ class NewTabPage extends React.Component { }) } + getLetterFromUrl (url) { + const hostname = urlutils.getHostname(url.get('location'), true) + const name = url.get('title') || hostname || '?' + return name.charAt(0).toUpperCase() + } + render () { // don't render if user prefers an empty page if (this.state.showEmptyPage && !this.props.isIncognito) { @@ -250,11 +271,6 @@ class NewTabPage extends React.Component { if (!this.state.newTabData) { return null } - const getLetterFromUrl = (url) => { - const hostname = urlutils.getHostname(url.get('location'), true) - const name = url.get('title') || hostname || '?' - return name.charAt(0).toUpperCase() - } const gridLayout = this.gridLayout const backgroundProps = {} let gradientClassName = 'gradient' @@ -278,24 +294,24 @@ class NewTabPage extends React.Component {
{ - this.state.showSiteRemovalNotification + this.state.showNotification ? } records - */ - applySiteRecords: function (records) { - dispatch({ - actionType: appConstants.APP_APPLY_SITE_RECORDS, - records - }) - }, - /** * Dispatch to populate the sync object id -> appState key path mapping cache */ @@ -1472,24 +1444,72 @@ const appActions = { }) }, - addBookmark: function (siteDetail, tag, closestKey) { + addBookmark: function (siteDetail, closestKey) { dispatch({ actionType: appConstants.APP_ADD_BOOKMARK, siteDetail, - tag, closestKey }) }, - editBookmark: function (siteDetail, editKey, tag) { + editBookmark: function (siteDetail, editKey) { dispatch({ actionType: appConstants.APP_EDIT_BOOKMARK, siteDetail, - tag, editKey }) }, + moveBookmark: function (bookmarkKey, destinationKey, append, moveIntoParent) { + dispatch({ + actionType: appConstants.APP_MOVE_BOOKMARK, + bookmarkKey, + destinationKey, + append, + moveIntoParent + }) + }, + + removeBookmark: function (bookmarkKey) { + dispatch({ + actionType: appConstants.APP_REMOVE_BOOKMARK, + bookmarkKey + }) + }, + + addBookmarkFolder: function (folderDetails, closestKey) { + dispatch({ + actionType: appConstants.APP_ADD_BOOKMARK_FOLDER, + folderDetails, + closestKey + }) + }, + + editBookmarkFolder: function (folderDetails, editKey) { + dispatch({ + actionType: appConstants.APP_EDIT_BOOKMARK_FOLDER, + folderDetails, + editKey + }) + }, + + moveBookmarkFolder: function (folderKey, destinationKey, append, moveIntoParent) { + dispatch({ + actionType: appConstants.APP_MOVE_BOOKMARK_FOLDER, + folderKey, + destinationKey, + append, + moveIntoParent + }) + }, + + removeBookmarkFolder: function (folderKey) { + dispatch({ + actionType: appConstants.APP_REMOVE_BOOKMARK_FOLDER, + folderKey + }) + }, + noReportStateModeClicked: function (windowId) { dispatch({ actionType: appConstants.APP_DEBUG_NO_REPORT_STATE_MODE_CLICKED, @@ -1529,6 +1549,15 @@ const appActions = { name, version }) + }, + + onPinnedTabReorder: function (siteKey, destinationKey, prepend) { + dispatch({ + actionType: appConstants.APP_ON_PINNED_TAB_REORDER, + siteKey, + destinationKey, + prepend + }) } } diff --git a/js/actions/bookmarkActions.js b/js/actions/bookmarkActions.js index 51152a2f38b..583fb158180 100644 --- a/js/actions/bookmarkActions.js +++ b/js/actions/bookmarkActions.js @@ -4,33 +4,31 @@ 'use strict' -const Immutable = require('immutable') -const siteUtil = require('../state/siteUtil') const appActions = require('./appActions') const windowActions = require('./windowActions') const eventUtil = require('../lib/eventUtil') const appStoreRenderer = require('../stores/appStoreRenderer') +const bookmarksUtil = require('../../app/common/lib/bookmarkUtil') +const bookmarksState = require('../../app/common/state/bookmarksState') +const bookmarkFoldersSate = require('../../app/common/state/bookmarkFoldersState') const {SWITCH_TO_NEW_TABS} = require('../constants/settings') -const getSetting = require('../settings').getSetting +const {getSetting} = require('../settings') const bookmarkActions = { openBookmarksInFolder: function (folderDetail) { - const allBookmarkItems = siteUtil.getBookmarks(appStoreRenderer.state.get('sites')) - // We have a middle clicked folder - const bookmarks = allBookmarkItems - .filter((bookmark) => (bookmark.get('parentFolderId') || 0) === (folderDetail.get('folderId') || 0) && siteUtil.isBookmark(bookmark)) + const bookmarks = bookmarksUtil.getBookmarksByParentId(appStoreRenderer.state, folderDetail.get('folderId')) // Only load the first 25 tabs as loaded bookmarks .forEach((bookmark, i) => { if (i <= 25) { appActions.createTabRequested( - Object.assign(siteUtil.toCreateProperties(bookmark), { + Object.assign(bookmarksUtil.toCreateProperties(bookmark), { active: false }), getSetting(SWITCH_TO_NEW_TABS)) } else { appActions.createTabRequested({ - location: bookmark.get('location'), + url: bookmark.get('location'), partitionNumber: bookmark.get('partitionNumber'), discarded: true }, false) @@ -42,26 +40,30 @@ const bookmarkActions = { * Performs an action based on the passed in event to the bookmark item * @return true if an action was performed */ - clickBookmarkItem: function (bookmarkKey, tabId, e) { - const bookmarkItem = appStoreRenderer.state.getIn(['sites', bookmarkKey], Immutable.Map()) - const isFolder = siteUtil.isFolder(bookmarkItem) + clickBookmarkItem: function (key, tabId, isFolder, e) { + const isSecondary = eventUtil.isForSecondaryAction(e) + if (!isFolder) { - if (eventUtil.isForSecondaryAction(e)) { + const bookmarkItem = bookmarksState.getBookmark(appStoreRenderer.state, key) + if (isSecondary) { appActions.createTabRequested({ url: bookmarkItem.get('location'), - partitionNumber: (bookmarkItem && bookmarkItem.get && bookmarkItem.get('partitionNumber')) || undefined, + partitionNumber: bookmarkItem.get('partitionNumber') || undefined, active: !!e.shiftKey || getSetting(SWITCH_TO_NEW_TABS) }) } else { appActions.loadURLRequested(tabId, bookmarkItem.get('location')) } + windowActions.setContextMenuDetail() return true - } else if (eventUtil.isForSecondaryAction(e)) { - this.openBookmarksInFolder(bookmarkItem) + } else if (isSecondary) { + const folderItem = bookmarkFoldersSate.getFolder(appStoreRenderer.state, key) + this.openBookmarksInFolder(folderItem) windowActions.setContextMenuDetail() return true } + return false } } diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js index 9f8c5f73149..0fc58cd6f0b 100644 --- a/js/actions/windowActions.js +++ b/js/actions/windowActions.js @@ -512,11 +512,16 @@ const windowActions = { }) }, - onBookmarkAdded: function (isHanger, editKey, siteDetail) { + /** + * Used for adding bookmark site directly and then allowing to + * edit it right afterwords + * @param isHanger + * @param bookmarkDetail - bookmark data, if empty active frame will be used + */ + onBookmarkAdded: function (isHanger, bookmarkDetail) { dispatch({ actionType: windowConstants.WINDOW_ON_BOOKMARK_ADDED, - siteDetail, - editKey, + bookmarkDetail, isHanger }) }, @@ -530,6 +535,38 @@ const windowActions = { }) }, + /** + * Used for displaying bookmark folder dialog + * when adding bookmark site or folder + */ + addBookmarkFolder: function (folderDetails, closestKey) { + dispatch({ + actionType: windowConstants.WINDOW_ON_ADD_BOOKMARK_FOLDER, + folderDetails, + closestKey + }) + }, + + /** + * Used for displaying bookmark folder dialog + * when editing bookmark site or folder + */ + editBookmarkFolder: function (editKey) { + dispatch({ + actionType: windowConstants.WINDOW_ON_EDIT_BOOKMARK_FOLDER, + editKey + }) + }, + + /** + * Used for closing a bookmark dialog + */ + onBookmarkFolderClose: function () { + dispatch({ + actionType: windowConstants.WINDOW_ON_BOOKMARK_FOLDER_CLOSE + }) + }, + /** * Dispatches a message to set context menu detail. * If set, also indicates that the context menu is shown. @@ -1202,10 +1239,11 @@ const windowActions = { }) }, - onSiteDetailMenu: function (bookmarkKey) { + onSiteDetailMenu: function (bookmarkKey, type) { dispatch({ actionType: windowConstants.WINDOW_ON_SITE_DETAIL_MENU, - bookmarkKey + bookmarkKey, + type }) } } diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index f8e12793899..afd249ec9a5 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -13,11 +13,10 @@ const appConstants = { APP_WINDOW_UPDATED: _, APP_TAB_CLOSED: _, APP_TAB_UPDATED: _, - APP_ADD_SITE: _, + APP_ADD_HISTORY_SITE: _, + APP_ADD_HISTORY_SITES: _, APP_SET_STATE: _, - APP_REMOVE_SITE: _, - APP_MOVE_SITE: _, - APP_APPLY_SITE_RECORDS: _, + APP_REMOVE_HISTORY_SITE: _, APP_MERGE_DOWNLOAD_DETAIL: _, // Sets an individual download detail APP_CLEAR_COMPLETED_DOWNLOADS: _, // Removes all completed downloads APP_DEFAULT_WINDOW_PARAMS_CHANGED: _, @@ -144,7 +143,14 @@ const appConstants = { APP_SPELLING_SUGGESTED: _, APP_LEARN_SPELLING: _, APP_FORGET_LEARNED_SPELLING: _, - APP_SET_VERSION_INFO: _ + APP_SET_VERSION_INFO: _, + APP_ON_PINNED_TAB_REORDER: _, + APP_ADD_BOOKMARK_FOLDER: _, + APP_EDIT_BOOKMARK_FOLDER: _, + APP_REMOVE_BOOKMARK_FOLDER: _, + APP_REMOVE_BOOKMARK: _, + APP_MOVE_BOOKMARK_FOLDER: _, + APP_MOVE_BOOKMARK: _ } module.exports = mapValuesByKeys(appConstants) diff --git a/js/constants/siteTags.js b/js/constants/siteTags.js index 4a86ba2fa59..e4b1338131d 100644 --- a/js/constants/siteTags.js +++ b/js/constants/siteTags.js @@ -6,11 +6,9 @@ const mapValuesByKeys = require('../lib/functional').mapValuesByKeys const _ = null const siteTags = { - DEFAULT: _, BOOKMARK: _, BOOKMARK_FOLDER: _, - PINNED: _, - READING_LIST: _ + HISTORY: _ } module.exports = mapValuesByKeys(siteTags) diff --git a/js/constants/windowConstants.js b/js/constants/windowConstants.js index 002f927062f..7186fbf9f51 100644 --- a/js/constants/windowConstants.js +++ b/js/constants/windowConstants.js @@ -111,7 +111,10 @@ const windowConstants = { WINDOW_ON_ADD_BOOKMARK: _, WINDOW_ON_EDIT_BOOKMARK: _, WINDOW_ON_BOOKMARK_CLOSE: _, - WINDOW_ON_BOOKMARK_ADDED: _ + WINDOW_ON_BOOKMARK_ADDED: _, + WINDOW_ON_ADD_BOOKMARK_FOLDER: _, + WINDOW_ON_EDIT_BOOKMARK_FOLDER: _, + WINDOW_ON_BOOKMARK_FOLDER_CLOSE: _ } module.exports = mapValuesByKeys(windowConstants) diff --git a/js/contextMenus.js b/js/contextMenus.js index 50079596af9..9d38cbb7cba 100644 --- a/js/contextMenus.js +++ b/js/contextMenus.js @@ -26,17 +26,21 @@ const locale = require('../js/l10n') const {getSetting} = require('./settings') const settings = require('./constants/settings') const textUtils = require('./lib/text') -const {getPartitionFromNumber, getActiveFrame} = require('./state/frameStateUtil') const {isIntermediateAboutPage, isUrl, aboutUrls} = require('./lib/appUrlUtil') const {getBase64FromImageUrl} = require('./lib/imageUtil') const urlParse = require('../app/common/urlParse') const {getCurrentWindow} = require('../app/renderer/currentWindow') const extensionState = require('../app/common/state/extensionState') const extensionActions = require('../app/common/actions/extensionActions') +const bookmarkUtil = require('../app/common/lib/bookmarkUtil') +const bookmarksState = require('../app/common/state/bookmarksState') +const historyState = require('../app/common/state/historyState') +const frameStateUtil = require('./state/frameStateUtil') +const platformUtil = require('../app/common/lib/platformUtil') const {makeImmutable} = require('../app/common/state/immutableUtil') -const isDarwin = process.platform === 'darwin' -const isLinux = process.platform === 'linux' +const isDarwin = platformUtil.isDarwin() +const isLinux = platformUtil.isLinux() /** * Obtains an add bookmark menu item @@ -70,18 +74,18 @@ const addFolderMenuItem = (closestDestinationDetail, isParent) => { return { label: locale.translation('addFolder'), click: () => { - let siteDetail = Immutable.fromJS({ tags: [siteTags.BOOKMARK_FOLDER] }) let closestKey = null + let folderDetails = Immutable.Map() if (closestDestinationDetail) { closestKey = siteUtil.getSiteKey(closestDestinationDetail) if (isParent) { - siteDetail = siteDetail.set('parentFolderId', (closestDestinationDetail.get('folderId') || closestDestinationDetail.get('parentFolderId'))) + folderDetails = folderDetails.set('parentFolderId', (closestDestinationDetail.get('folderId') || closestDestinationDetail.get('parentFolderId'))) } } - windowActions.addBookmark(siteDetail, closestKey) + windowActions.addBookmarkFolder(folderDetails, closestKey) } } } @@ -133,8 +137,7 @@ function tabsToolbarTemplateInit (bookmarkTitle, bookmarkLink, closestDestinatio template.push(addBookmarkMenuItem('addBookmark', { title: bookmarkTitle, - location: bookmarkLink, - tags: [siteTags.BOOKMARK] + location: bookmarkLink }, closestDestinationDetail, isParent)) template.push(addFolderMenuItem(closestDestinationDetail, isParent)) @@ -225,58 +228,58 @@ function downloadsToolbarTemplateInit (downloadId, downloadItem) { return menuUtil.sanitizeTemplateItems(template) } -function siteDetailTemplateInit (siteDetail, activeFrame) { - let isHistoryEntry = false - let multipleHistoryEntries = false - let multipleBookmarks = false - let isFolder = false - let isSystemFolder = false - let deleteLabel - let deleteTag - - // TODO(bsclifton): pull this out to a method - if (siteUtil.isBookmark(siteDetail) && activeFrame) { - deleteLabel = 'deleteBookmark' - deleteTag = siteTags.BOOKMARK - } else if (siteUtil.isFolder(siteDetail)) { - isFolder = true - isSystemFolder = siteDetail.get('folderId') === 0 || - siteDetail.get('folderId') === -1 - deleteLabel = 'deleteFolder' - deleteTag = siteTags.BOOKMARK_FOLDER - } else if (siteUtil.isHistoryEntry(siteDetail)) { - isHistoryEntry = true - deleteLabel = 'deleteHistoryEntry' +const getLabel = (siteDetail, activeFrame, type) => { + let label = '' + + if (type === siteTags.BOOKMARK && activeFrame) { + label = 'deleteBookmark' + } else if (type === siteTags.BOOKMARK_FOLDER) { + label = 'deleteFolder' + } else if (type === siteTags.HISTORY) { + label = 'deleteHistoryEntry' } else if (Immutable.List.isList(siteDetail)) { // Multiple bookmarks OR history entries selected - multipleHistoryEntries = true - multipleBookmarks = true - siteDetail.forEach((site) => { - if (!siteUtil.isBookmark(site)) multipleBookmarks = false - if (!siteUtil.isHistoryEntry(site)) multipleHistoryEntries = false - }) - if (multipleBookmarks) { - deleteLabel = 'deleteBookmarks' - deleteTag = siteTags.BOOKMARK - } else if (multipleHistoryEntries) { - deleteLabel = 'deleteHistoryEntries' - } - } else { - deleteLabel = '' + /* multipleHistoryEntries = true + multipleBookmarks = true + siteDetail.forEach((site) => { + if (!siteUtil.isBookmark(site)) multipleBookmarks = false + if (!siteUtil.isHistoryEntry(site)) multipleHistoryEntries = false + }) + if (multipleBookmarks) { + label = 'deleteBookmarks' + deleteTag = siteTags.BOOKMARK + } else if (multipleHistoryEntries) { + label = 'deleteHistoryEntries' + } */ } + return label +} + +// TODO check when we send in multiple siteKeys +const siteDetailTemplateInit = (state, siteKey, type) => { + let isFolder = type === siteTags.BOOKMARK_FOLDER + let siteDetail = bookmarksState.findBookmark(appStoreRenderer.state, siteKey) + if (siteDetail.isEmpty()) { + siteDetail = historyState.getSite(state, siteKey) + } + let multiple = Immutable.List.isList(siteDetail) + const activeFrame = frameStateUtil.getActiveFrame(state) || Immutable.Map() const template = [] + const label = getLabel(siteDetail, activeFrame, type) - if (!isFolder) { - if (!Immutable.List.isList(siteDetail)) { + if (type !== siteTags.BOOKMARK_FOLDER) { + if (!multiple) { const location = siteDetail.get('location') - template.push(openInNewTabMenuItem(location, undefined, siteDetail.get('partitionNumber')), + template.push( + openInNewTabMenuItem(location, undefined, siteDetail.get('partitionNumber')), openInNewPrivateTabMenuItem(location), openInNewWindowMenuItem(location, undefined, siteDetail.get('partitionNumber')), openInNewSessionTabMenuItem(location), copyAddressMenuItem('copyLinkAddress', location), - CommonMenu.separatorMenuItem) + CommonMenu.separatorMenuItem + ) } else { let locations = [] let partitionNumbers = [] @@ -285,53 +288,64 @@ function siteDetailTemplateInit (siteDetail, activeFrame) { partitionNumbers.push(site.get('partitionNumber')) }) - template.push(openInNewTabMenuItem(locations, undefined, partitionNumbers), + template.push( + openInNewTabMenuItem(locations, undefined, partitionNumbers), openInNewPrivateTabMenuItem(locations), openInNewSessionTabMenuItem(locations), - CommonMenu.separatorMenuItem) + CommonMenu.separatorMenuItem + ) } } else { - template.push(openAllInNewTabsMenuItem(appStoreRenderer.state.get('sites'), siteDetail), - CommonMenu.separatorMenuItem) + template.push(openAllInNewTabsMenuItem(siteDetail), CommonMenu.separatorMenuItem) } - if (!isSystemFolder) { + if (siteDetail.get('folderId') !== 0 && siteDetail.get('folderId') !== -1) { // Picking this menu item pops up the AddEditBookmark modal // - History can be deleted but not edited // - Multiple bookmarks cannot be edited at once // - "Bookmarks Toolbar" and "Other Bookmarks" folders cannot be deleted - if (!isHistoryEntry && !multipleHistoryEntries && !multipleBookmarks) { + if (type !== siteTags.HISTORY && !multiple) { template.push( { label: locale.translation(isFolder ? 'editFolder' : 'editBookmark'), click: () => { const editKey = siteUtil.getSiteKey(siteDetail) - windowActions.editBookmark(false, editKey) + if (isFolder) { + windowActions.editBookmarkFolder(editKey) + } else { + windowActions.editBookmark(editKey) + } } }, - CommonMenu.separatorMenuItem) + CommonMenu.separatorMenuItem + ) } template.push( { - label: locale.translation(deleteLabel), + label: locale.translation(label), click: () => { - if (Immutable.List.isList(siteDetail)) { - siteDetail.forEach((site) => appActions.removeSite(site, deleteTag)) - } else { - appActions.removeSite(siteDetail, deleteTag) + // TODO implement multiple + const key = siteUtil.getSiteKey(siteDetail) + if (type === siteTags.HISTORY) { + appActions.removeHistorySite(siteDetail) + } else if (type === siteTags.BOOKMARK) { + appActions.removeBookmark(key) + } else if (type === siteTags.BOOKMARK_FOLDER) { + appActions.removeBookmarkFolder(key) } } }) } - if (!isHistoryEntry && !multipleHistoryEntries) { + if (type !== siteTags.HISTORY) { if (template[template.length - 1] !== CommonMenu.separatorMenuItem) { template.push(CommonMenu.separatorMenuItem) } template.push( - addBookmarkMenuItem('addBookmark', siteUtil.getDetailFromFrame(activeFrame, siteTags.BOOKMARK), siteDetail, true), - addFolderMenuItem(siteDetail, true)) + addBookmarkMenuItem('addBookmark', bookmarkUtil.getDetailFromFrame(activeFrame), siteDetail, true), + addFolderMenuItem(siteDetail, true) + ) } return menuUtil.sanitizeTemplateItems(template) @@ -724,11 +738,11 @@ const openInNewTabMenuItem = (url, isPrivate, partitionNumber, openerTabId) => { } } -const openAllInNewTabsMenuItem = (allSites, folderDetail) => { +const openAllInNewTabsMenuItem = (folderDetail) => { return { label: locale.translation('openAllInTabs'), click: () => { - bookmarkActions.openBookmarksInFolder(allSites, folderDetail) + bookmarkActions.openBookmarksInFolder(folderDetail) } } } @@ -920,7 +934,7 @@ function mainTemplateInit (nodeProps, frame, tab) { appActions.createTabRequested({ url: nodeProps.srcURL, openerTabId: frame.get('tabId'), - partition: getPartitionFromNumber(frame.get('partitionNumber'), isPrivate), + partition: frameStateUtil.getPartitionFromNumber(frame.get('partitionNumber'), isPrivate), active: active }) } @@ -1009,8 +1023,7 @@ function mainTemplateInit (nodeProps, frame, tab) { ) if (isLink) { template.push(addBookmarkMenuItem('bookmarkLink', { - location: nodeProps.linkURL, - tags: [siteTags.BOOKMARK] + location: nodeProps.linkURL }, false)) } } @@ -1024,8 +1037,7 @@ function mainTemplateInit (nodeProps, frame, tab) { if (!isImage) { if (isLink) { template.push(addBookmarkMenuItem('bookmarkLink', { - location: nodeProps.linkURL, - tags: [siteTags.BOOKMARK] + location: nodeProps.linkURL }, false)) } else { template.push( @@ -1055,7 +1067,7 @@ function mainTemplateInit (nodeProps, frame, tab) { } }, CommonMenu.separatorMenuItem, - addBookmarkMenuItem('bookmarkPage', siteUtil.getDetailFromFrame(frame, siteTags.BOOKMARK), false)) + addBookmarkMenuItem('bookmarkPage', bookmarkUtil.getDetailFromFrame(frame), false)) if (!isAboutPage) { template.push({ @@ -1204,8 +1216,7 @@ function mainTemplateInit (nodeProps, frame, tab) { template.push( CommonMenu.separatorMenuItem, addBookmarkMenuItem('addBookmark', { - location: nodeProps.linkURL, - tags: [siteTags.BOOKMARK] + location: nodeProps.linkURL }), addFolderMenuItem() ) @@ -1226,11 +1237,11 @@ function onHamburgerMenu (location, e) { } function onMainContextMenu (nodeProps, frame, tab, contextMenuType) { - if (contextMenuType === 'bookmark' || contextMenuType === 'bookmark-folder') { + if (contextMenuType === siteTags.BOOKMARK || contextMenuType === siteTags.BOOKMARK_FOLDER) { const activeFrame = Immutable.fromJS({ location: '', title: '', partitionNumber: frame.get('partitionNumber') }) - onSiteDetailContextMenu(Immutable.fromJS(nodeProps), activeFrame) - } else if (contextMenuType === 'history') { - onSiteDetailContextMenu(Immutable.fromJS(nodeProps)) + onSiteDetailContextMenu(Immutable.fromJS(nodeProps), contextMenuType, activeFrame) + } else if (contextMenuType === siteTags.HISTORY) { + onSiteDetailContextMenu(Immutable.fromJS(nodeProps), contextMenuType) } else if (contextMenuType === 'synopsis') { onLedgerContextMenu(nodeProps.location, nodeProps.hostPattern) } else if (contextMenuType === 'download') { @@ -1281,7 +1292,7 @@ function onUrlBarContextMenu (e) { e.stopPropagation() const searchDetail = appStoreRenderer.state.get('searchDetail') const windowState = windowStore.getState() - const activeFrame = getActiveFrame(windowState) + const activeFrame = frameStateUtil.getActiveFrame(windowState) const inputMenu = Menu.buildFromTemplate(urlBarTemplateInit(searchDetail, activeFrame, e)) inputMenu.popup(getCurrentWindow()) } diff --git a/js/data/newTabData.js b/js/data/newTabData.js index ccc0b4e286c..94431f18e7e 100644 --- a/js/data/newTabData.js +++ b/js/data/newTabData.js @@ -5,75 +5,64 @@ const {getBraveExtUrl} = require('../lib/appUrlUtil') const iconPath = getBraveExtUrl('img/newtab/defaultTopSitesIcon') -const now = 1 - module.exports.pinnedTopSites = [ - { - "count": 1, - "favicon": `${iconPath}/twitter.png`, - "lastAccessedTime": now, - "location": "https://twitter.com/brave", - "partitionNumber": 0, - "tags": ['default'], - "themeColor": "rgb(255, 255, 255)", - "title": "Brave Software (@brave) | Twitter" + { + 'key': 'https://twitter.com/brave/|0|0', + 'count': 0, + 'favicon': `${iconPath}/twitter.png`, + 'location': 'https://twitter.com/brave/', + 'themeColor': 'rgb(255, 255, 255)', + 'title': 'Brave Software (@brave) | Twitter' } ] module.exports.topSites = [ { - "count": 1, - "favicon": `${iconPath}/twitter.png`, - "lastAccessedTime": now, - "location": "https://twitter.com/brave", - "partitionNumber": 0, - "tags": ['default'], - "themeColor": "rgb(255, 255, 255)", - "title": "Brave Software (@brave) | Twitter" - }, { - "count": 1, - "favicon": `${iconPath}/facebook.png`, - "lastAccessedTime": now, - "location": "https://www.facebook.com/BraveSoftware/", - "partitionNumber": 0, - "tags": ['default'], - "themeColor": "rgb(59, 89, 152)", - "title": "Brave Software | Facebook" - }, { - "count": 1, - "favicon": `${iconPath}/youtube.png`, - "lastAccessedTime": now, - "location": "https://www.youtube.com/bravesoftware", - "partitionNumber": 0, - "tags": ['default'], - "themeColor": "#E62117", - "title": "Brave Browser - YouTube" - }, { - "count": 1, - "favicon": `${iconPath}/brave.ico`, - "lastAccessedTime": now, - "location": "https://brave.com/", - "partitionNumber": 0, - "tags": ['default'], - "themeColor": "rgb(255, 255, 255)", - "title": "Brave Software | Building a Better Web" - }, { - "count": 1, - "favicon": `${iconPath}/appstore.png`, - "lastAccessedTime": now, - "location": "https://itunes.apple.com/app/brave-web-browser/id1052879175?mt=8", - "partitionNumber": 0, - "tags": ['default'], - "themeColor": "rgba(255, 255, 255, 1)", - "title": "Brave Web Browser: Fast with built-in adblock on the App Store" - }, { - "count": 1, - "favicon": `${iconPath}/playstore.png`, - "lastAccessedTime": now, - "location": "https://play.google.com/store/apps/details?id=com.brave.browser", - "partitionNumber": 0, - "tags": ['default'], - "themeColor": "rgb(241, 241, 241)", - "title": "Brave Browser: Fast AdBlock – Apps para Android no Google Play" + 'key': 'https://twitter.com/brave/|0|0', + 'count': 0, + 'favicon': `${iconPath}/twitter.png`, + 'location': 'https://twitter.com/brave', + 'themeColor': 'rgb(255, 255, 255)', + 'title': 'Brave Software (@brave) | Twitter' + }, + { + 'key': 'https://www.facebook.com/BraveSoftware/|0|0', + 'count': 0, + 'favicon': `${iconPath}/facebook.png`, + 'location': 'https://www.facebook.com/BraveSoftware/', + 'themeColor': 'rgb(59, 89, 152)', + 'title': 'Brave Software | Facebook' + }, + { + 'key': 'https://www.youtube.com/bravesoftware/|0|0', + 'count': 0, + 'favicon': `${iconPath}/youtube.png`, + 'location': 'https://www.youtube.com/bravesoftware/', + 'themeColor': '#E62117', + 'title': 'Brave Browser - YouTube' + }, + { + 'key': 'https://brave.com/|0|0', + 'count': 0, + 'favicon': `${iconPath}/brave.ico`, + 'location': 'https://brave.com/', + 'themeColor': 'rgb(255, 255, 255)', + 'title': 'Brave Software | Building a Better Web' + }, + { + 'key': 'https://itunes.apple.com/app/brave-web-browser/id1052879175?mt=8|0|0', + 'count': 0, + 'favicon': `${iconPath}/appstore.png`, + 'location': 'https://itunes.apple.com/app/brave-web-browser/id1052879175?mt=8', + 'themeColor': 'rgba(255, 255, 255, 1)', + 'title': 'Brave Web Browser: Fast with built-in adblock on the App Store' + }, + { + 'key': 'https://play.google.com/store/apps/details?id=com.brave.browser|0|0', + 'count': 0, + 'favicon': `${iconPath}/playstore.png`, + 'location': 'https://play.google.com/store/apps/details?id=com.brave.browser', + 'themeColor': 'rgb(241, 241, 241)', + 'title': 'Brave Browser: Fast AdBlock – Apps para Android no Google Play' } ] diff --git a/js/lib/importer.js b/js/lib/importer.js index b0925cff8b2..7d336b86925 100644 --- a/js/lib/importer.js +++ b/js/lib/importer.js @@ -4,15 +4,15 @@ const fs = require('fs') const appActions = require('../actions/appActions') -const siteTags = require('../constants/siteTags') -const siteUtil = require('../state/siteUtil') const Immutable = require('immutable') const appStoreRenderer = require('../stores/appStoreRenderer') +const bookmarFoldersUtil = require('../../app/common/lib/bookmarkFoldersUtil') +const bookmarkFoldersState = require('../../app/common/state/bookmarkFoldersState') /** * Processes a single node from an exported HTML file from Firefox or Chrome * @param {Object} parserState - the current parser state - * @param {Object} node - The current DOM node which is being processed + * @param {Object} domNode - The current DOM node which is being processed */ function processBookmarkNode (parserState, domNode) { switch (domNode.tagName) { @@ -38,12 +38,11 @@ function processBookmarkNode (parserState, domNode) { title: domNode.innerText, folderId: parserState.nextFolderId, parentFolderId: parserState.parentFolderId, - lastAccessedTime: (domNode.getAttribute('LAST_MODIFIED') || domNode.getAttribute('ADD_DATE') || 0) * 1000, - tags: [siteTags.BOOKMARK_FOLDER] + lastAccessedTime: (domNode.getAttribute('LAST_MODIFIED') || domNode.getAttribute('ADD_DATE') || 0) * 1000 } parserState.lastFolderId = parserState.nextFolderId parserState.nextFolderId++ - parserState.sites.push(folder) + parserState.bookmarkFolders.push(folder) } else { parserState.lastFolderId = 0 parserState.foundBookmarksToolbar = true @@ -54,15 +53,13 @@ function processBookmarkNode (parserState, domNode) { if (domNode.href.startsWith('place:')) { break } - const site = { + const bookmarks = { title: domNode.innerText, location: domNode.href, favicon: domNode.getAttribute('ICON'), - parentFolderId: parserState.parentFolderId, - lastAccessedTime: (domNode.getAttribute('LAST_MODIFIED') || domNode.getAttribute('ADD_DATE') || 0) * 1000, - tags: [siteTags.BOOKMARK] + parentFolderId: parserState.parentFolderId } - parserState.sites.push(site) + parserState.bookmarks.push(bookmarks) break } } @@ -91,17 +88,19 @@ module.exports.importFromHTML = (path) => { // Each window's appStoreRenderer holds a copy of the app state, but it's not // mutable, so this is only used for getting the current list of sites. const parserState = { - nextFolderId: siteUtil.getNextFolderId(appStoreRenderer.state.get('sites')), + nextFolderId: bookmarFoldersUtil.getNextFolderId(bookmarkFoldersState.getFolders(appStoreRenderer.state)), lastFolderId: -1, parentFolderId: -1, - sites: [] + bookmarks: [], + bookmarkFolders: [] } // Process each of the nodes starting with the first node which is either DL or DT processBookmarkNode(parserState, doc.querySelector('dl, dt')) // Add the sites to the app store in the main process - appActions.addSite(Immutable.fromJS(parserState.sites)) + appActions.addBookmark(Immutable.fromJS(parserState.bookmarks)) + appActions.addBookmarkFolder(Immutable.fromJS(parserState.bookmarkFolders)) resolve({importCount: parserState.sites.length}) }) }) diff --git a/js/lib/textCalculator.js b/js/lib/textCalculator.js index 8068219c1f4..c543d340b83 100644 --- a/js/lib/textCalculator.js +++ b/js/lib/textCalculator.js @@ -2,8 +2,8 @@ * 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 ctx = document.createElement('canvas').getContext('2d') module.exports.calculateTextWidth = (text, font = '11px Arial') => { + const ctx = document.createElement('canvas').getContext('2d') ctx.font = font return ctx.measureText(text).width } diff --git a/js/state/frameStateUtil.js b/js/state/frameStateUtil.js index b71f871694e..e6a2e422142 100644 --- a/js/state/frameStateUtil.js +++ b/js/state/frameStateUtil.js @@ -58,6 +58,7 @@ function getSortedFrameKeys (state) { .map(frame => frame.get('key')) } +// TODO check if order is preserved, could be a bug function getPinnedFrames (state) { return state.get('frames').filter((frame) => frame.get('pinnedLocation')) } diff --git a/js/state/siteUtil.js b/js/state/siteUtil.js index 5320b15a9c7..60b5b7806e1 100644 --- a/js/state/siteUtil.js +++ b/js/state/siteUtil.js @@ -2,17 +2,11 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict' -const Immutable = require('immutable') -const siteCache = require('../../app/common/state/siteCache') const siteTags = require('../constants/siteTags') -const settings = require('../constants/settings') -const getSetting = require('../settings').getSetting const UrlUtil = require('../lib/urlutil') const urlParse = require('../../app/common/urlParse') -const {makeImmutable} = require('../../app/common/state/immutableUtil') - -const defaultTags = new Immutable.List([siteTags.DEFAULT]) +// TODO remove const isBookmark = (tags) => { if (!tags) { return false @@ -20,6 +14,7 @@ const isBookmark = (tags) => { return tags.includes(siteTags.BOOKMARK) } +// TODO remove const isBookmarkFolder = (tags) => { if (!tags) { return false @@ -28,25 +23,6 @@ const isBookmarkFolder = (tags) => { (tags && typeof tags !== 'string' && tags.includes(siteTags.BOOKMARK_FOLDER)) } -const isPinnedTab = (tags) => { - if (!tags) { - return false - } - return tags.includes(siteTags.PINNED) -} -module.exports.isPinnedTab = isPinnedTab - -const reorderSite = (sites, order) => { - sites = sites.map((site) => { - const siteOrder = site.get('order') - if (siteOrder > order) { - return site.set('order', siteOrder - 1) - } - return site - }) - return sites -} - /** * Sort comparator for sort function */ @@ -109,227 +85,13 @@ module.exports.getLocationFromSiteKey = function (siteKey) { * @param siteDetail The site to check if it's in the specified tag * @return true if the location is already bookmarked */ +// TODO remove this when sync is updated module.exports.isSiteBookmarked = function (sites, siteDetail) { const siteKey = module.exports.getSiteKey(siteDetail) const siteTags = sites.getIn([siteKey, 'tags']) return isBookmark(siteTags) } -/** - * Checks if a location is bookmarked. - * - * @param state The application state Immutable map - * @param {string} location - * @return {boolean} - */ -module.exports.isLocationBookmarked = function (state, location) { - const sites = state.get('sites') - const siteKeys = siteCache.getLocationSiteKeys(state, location) - if (!siteKeys || siteKeys.length === 0) { - return false - } - return siteKeys.some(key => { - const site = sites.get(key) - if (!site) { - return false - } - return isBookmark(site.get('tags')) - }) -} - -const getNextFolderIdItem = (sites) => - sites.max((siteA, siteB) => { - const folderIdA = siteA.get('folderId') - const folderIdB = siteB.get('folderId') - if (folderIdA === folderIdB) { - return 0 - } - if (folderIdA === undefined) { - return false - } - if (folderIdB === undefined) { - return true - } - return folderIdA > folderIdB - }) - -module.exports.getNextFolderId = (sites) => { - const defaultFolderId = 0 - if (!sites) { - return defaultFolderId - } - const maxIdItem = getNextFolderIdItem(sites) - return (maxIdItem ? (maxIdItem.get('folderId') || 0) : 0) + 1 -} - -module.exports.getNextFolderName = (sites, name) => { - if (!sites) { - return name - } - const site = sites.find((site) => - isBookmarkFolder(site.get('tags')) && - site.get('customTitle') === name - ) - if (!site) { - return name - } - const filenameFormat = /(.*) \((\d+)\)/ - let result = filenameFormat.exec(name) - if (!result) { - return module.exports.getNextFolderName(sites, name + ' (1)') - } - const nextNum = parseInt(result[2]) + 1 - return module.exports.getNextFolderName(sites, result[1] + ' (' + nextNum + ')') -} - -const mergeSiteLastAccessedTime = (oldSiteDetail, newSiteDetail, tag) => { - const newTime = newSiteDetail && newSiteDetail.get('lastAccessedTime') - const oldTime = oldSiteDetail && oldSiteDetail.get('lastAccessedTime') - if (!isBookmark(tag) && !isBookmarkFolder(tag)) { - return newTime || new Date().getTime() - } - if (newTime && newTime !== 0) { - return newTime - } else if (oldTime && oldTime !== 0) { - return oldTime - } else { - return 0 - } -} - -// Some details can be copied from the existing siteDetail if null -// ex: parentFolderId, partitionNumber, and favicon -const mergeSiteDetails = (oldSiteDetail, newSiteDetail, tag, folderId, order) => { - let tags = (oldSiteDetail && oldSiteDetail.get('tags')) || new Immutable.List() - if (tag) { - tags = tags.toSet().add(tag).toList() - } - - const customTitle = typeof newSiteDetail.get('customTitle') === 'string' - ? newSiteDetail.get('customTitle') - : (newSiteDetail.get('customTitle') || (oldSiteDetail && oldSiteDetail.get('customTitle'))) - - const lastAccessedTime = mergeSiteLastAccessedTime(oldSiteDetail, newSiteDetail, tag) - - let site = makeImmutable({ - lastAccessedTime, - tags, - objectId: newSiteDetail.get('objectId') || (oldSiteDetail ? oldSiteDetail.get('objectId') : undefined), - title: newSiteDetail.get('title'), - order - }) - - if (oldSiteDetail && oldSiteDetail.get('order') !== undefined) { - site = site.set('order', oldSiteDetail.get('order')) - } - - if (newSiteDetail.get('location')) { - site = site.set('location', newSiteDetail.get('location')) - } - if (folderId) { - site = site.set('folderId', Number(folderId)) - } - if (typeof customTitle === 'string') { - site = site.set('customTitle', customTitle) - } - if (newSiteDetail.get('parentFolderId') !== undefined || (oldSiteDetail && oldSiteDetail.get('parentFolderId'))) { - let parentFolderId = newSiteDetail.get('parentFolderId') !== undefined - ? newSiteDetail.get('parentFolderId') : oldSiteDetail.get('parentFolderId') - site = site.set('parentFolderId', Number(parentFolderId)) - } - if (newSiteDetail.get('partitionNumber') !== undefined || (oldSiteDetail && oldSiteDetail.get('partitionNumber'))) { - let partitionNumber = newSiteDetail.get('partitionNumber') !== undefined - ? newSiteDetail.get('partitionNumber') : oldSiteDetail.get('partitionNumber') - site = site.set('partitionNumber', Number(partitionNumber)) - } - if (newSiteDetail.get('favicon') || (oldSiteDetail && oldSiteDetail.get('favicon'))) { - site = site.set('favicon', newSiteDetail.get('favicon') || oldSiteDetail.get('favicon')) - } - if (newSiteDetail.get('themeColor') || (oldSiteDetail && oldSiteDetail.get('themeColor'))) { - site = site.set('themeColor', newSiteDetail.get('themeColor') || oldSiteDetail.get('themeColor')) - } - if (site.get('tags').size === 0) { - // Increment the visit count for history items - site = site.set('count', ((oldSiteDetail || site).get('count') || 0) + 1) - } - - return site -} - -/** - * Adds the specified siteDetail in appState.sites. - * - * Examples of updating: - * - editing bookmark in add/edit modal - * - when timestamp is added (history entry) - * - moving bookmark to/from a folder - * - * @param state - The application state - * @param siteDetail - The site details object to add or update - * @param tag - The tag to add for this site - * See siteTags.js for supported types. No tag means just a history item - * @param oldKey - key of a changed site - * @param {boolean} skipSync - True if this site was downloaded by sync and - * does not to be re-uploaded - * @return The new state Immutable object - */ -module.exports.addSite = function (state, siteDetail, tag, oldKey, skipSync) { - let sites = state.get('sites') - // Get tag from siteDetail object if not passed via tag param - if (tag === undefined) { - tag = siteDetail.getIn(['tags', 0]) - } - - if (!oldKey) { - oldKey = module.exports.getSiteKey(siteDetail) - } - - const oldSite = oldKey !== null ? sites.get(oldKey) : null - let folderId = siteDetail.get('folderId') - - if (tag === siteTags.BOOKMARK_FOLDER) { - if (!oldSite && folderId) { - // Remove duplicate folder (needed for import) - const dupFolder = sites.find((site) => isBookmarkFolder(site.get('tags')) && - site.get('parentFolderId') === siteDetail.get('parentFolderId') && - site.get('customTitle') === siteDetail.get('customTitle')) - if (dupFolder) { - state = module.exports.removeSite(state, dupFolder, siteTags.BOOKMARK_FOLDER, true) - } - } else if (!folderId) { - // Assign an id if this is a new folder - folderId = module.exports.getNextFolderId(sites) - } - } - - let site = mergeSiteDetails(oldSite, siteDetail, tag, folderId, sites.size) - if (oldSite) { - sites = sites.delete(oldKey) - } - - let location - if (site.has('location')) { - location = UrlUtil.getLocationIfPDF(site.get('location')) - site = site.set('location', location) - } - - const oldLocation = (oldSite && oldSite.get('location')) || site.get('location') - state = siteCache.removeLocationSiteKey(state, oldLocation, oldKey) - - if (skipSync) { - site = site.set('skipSync', true) - } - - state = state.set('sites', sites) - const key = module.exports.getSiteKey(site) - if (key === null) { - return state - } - state = state.setIn(['sites', key], site) - state = siteCache.addLocationSiteKey(state, location, key) - return state -} - /** * Removes the appropriate tag from a site given the site's sync objectId. * @param {Immutable.Map} state - Application state @@ -347,12 +109,16 @@ module.exports.removeSiteByObjectId = function (state, objectId, objectData) { if (!site) { return state } + + // TODO fix so that correct remove is called + /* let tag = '' if (objectData === 'bookmark') { // determine whether this is a folder tag = site.get('folderId') ? siteTags.BOOKMARK_FOLDER : siteTags.BOOKMARK } state = module.exports.removeSite(state, site, tag) + */ // If the object has been removed completely, purge it from the sync site // cache if (!state.getIn(objectKey)) { @@ -361,67 +127,6 @@ module.exports.removeSiteByObjectId = function (state, objectId, objectData) { return state } -/** - * Removes the specified tag from a siteDetail - * - * @param {Immutable.Map} state The application state Immutable map - * @param {Immutable.Map} siteDetail The siteDetail to remove a tag from - * @param {string} tag - * @param {boolean} reorder whether to reorder sites (default with reorder) - * @param {Function=} syncCallback - * @return {Immutable.Map} The new state Immutable object - */ -module.exports.removeSite = function (state, siteDetail, tag, reorder = true, syncCallback) { - let sites = state.get('sites') - const key = module.exports.getSiteKey(siteDetail) - if (!key) { - return state - } - if (getSetting(settings.SYNC_ENABLED) === true && syncCallback) { - syncCallback(sites.getIn([key])) - } - - const tags = sites.getIn([key, 'tags']) - if (isBookmarkFolder(tags)) { - const folderId = sites.getIn([key, 'folderId']) - const childSites = sites.filter((site) => site.get('parentFolderId') === folderId) - childSites.forEach((site) => { - const tags = site.get('tags') - tags.forEach((tag) => { - state = module.exports.removeSite(state, site, tag, false, syncCallback) - }) - }) - } - - const location = siteDetail.get('location') - state = siteCache.removeLocationSiteKey(state, location, key) - - const stateKey = ['sites', key] - let site = state.getIn(stateKey) - if (!site) { - return state - } - if (isBookmark(tag)) { - if (isPinnedTab(tags)) { - const tags = site.get('tags').filterNot((tag) => tag === siteTags.BOOKMARK) - site = site.set('tags', tags) - return state.setIn(stateKey, site) - } - if (state.get('sites').size && reorder) { - const order = state.getIn(stateKey.concat(['order'])) - state = state.set('sites', reorderSite(state.get('sites'), order)) - } - return state.deleteIn(['sites', key]) - } else if (isPinnedTab(tag)) { - const tags = site.get('tags').filterNot((tag) => tag === siteTags.PINNED) - site = site.set('tags', tags) - return state.setIn(stateKey, site) - } else { - site = site.set('lastAccessedTime', undefined) - return state.setIn(stateKey, site) - } -} - /** * Called by isMoveAllowed * Trace a folder's ancestory, collecting all parent folderIds until reaching Bookmarks Toolbar (folderId=0) @@ -459,223 +164,12 @@ module.exports.isMoveAllowed = (sites, sourceDetail, destinationDetail) => { return true } -/** - * Moves the specified site from one location to another - * - * @param state The application state Immutable map - * @param sourceKey The site key to move - * @param destinationKey The site key to move to - * @param prepend Whether the destination detail should be prepended or not, not used if destinationIsParent is true - * @param destinationIsParent Whether the item should be moved inside of the destinationDetail. - * @param disallowReparent If set to true, parent folder will not be set - * @return The new state Immutable object - */ -module.exports.moveSite = function (state, sourceKey, destinationKey, prepend, - destinationIsParent, disallowReparent) { - let sites = state.get('sites') - let sourceSite = sites.get(sourceKey, Immutable.Map()) - const destinationSite = sites.get(destinationKey, Immutable.Map()) - - if (sourceSite.isEmpty() || !module.exports.isMoveAllowed(sites, sourceSite, destinationSite)) { - return state - } - - const sourceSiteIndex = sourceSite.get('order') - let destinationSiteIndex - if (destinationIsParent) { - // When the destination is the parent we want to put it at the end - destinationSiteIndex = sites.size - 1 - prepend = true - } else { - destinationSiteIndex = destinationSite.get('order') - } - - let newIndex = destinationSiteIndex + (prepend ? 0 : 1) - if (destinationSiteIndex > sourceSiteIndex) { - --newIndex - } - - const location = sourceSite.get('location') - state = siteCache.removeLocationSiteKey(state, location, sourceKey) - state = state.deleteIn(['sites', sourceKey]) - state = state.set('sites', state.get('sites').map((site) => { - const siteOrder = site.get('order') - if (siteOrder >= newIndex && siteOrder < sourceSiteIndex) { - return site.set('order', siteOrder + 1) - } else if (siteOrder <= newIndex && siteOrder > sourceSiteIndex) { - return site.set('order', siteOrder - 1) - } - return site - })) - sourceSite = sourceSite.set('order', newIndex) - - if (!disallowReparent) { - if (destinationIsParent && destinationSite.get('folderId') !== sourceSite.get('folderId')) { - sourceSite = sourceSite.set('parentFolderId', destinationSite.get('folderId')) - } else if (destinationSite.get('parentFolderId') == null) { - sourceSite = sourceSite.set('parentFolderId', 0) - } else if (destinationSite.get('parentFolderId') !== sourceSite.get('parentFolderId')) { - sourceSite = sourceSite.set('parentFolderId', destinationSite.get('parentFolderId')) - } - } - const destinationSiteKey = module.exports.getSiteKey(sourceSite) - state = siteCache.addLocationSiteKey(state, location, destinationSiteKey) - return state.setIn(['sites', destinationSiteKey], sourceSite) -} - -module.exports.getDetailFromFrame = function (frame, tag) { - const pinnedLocation = frame.get('pinnedLocation') - let location = frame.get('location') - if (pinnedLocation && pinnedLocation !== 'about:blank' && tag === siteTags.PINNED) { - location = frame.get('pinnedLocation') - } - - return makeImmutable({ - location, - title: frame.get('title'), - partitionNumber: frame.get('partitionNumber'), - tags: tag ? [tag] : [], - favicon: frame.get('icon'), - themeColor: frame.get('themeColor') || frame.get('computedThemeColor') - }) -} - -const getSitesBySubkey = (sites, siteKey, tag) => { - if (!sites || !siteKey) { - return makeImmutable([]) - } - const splitKey = siteKey.split('|', 2) - const partialKey = splitKey.join('|') - const matches = sites.filter((site, key) => { - if (key.indexOf(partialKey) > -1 && (!tag || (tag && site.get('tags').includes(tag)))) { - return true - } - return false - }) - return matches.toList() -} - -module.exports.getDetailFromTab = function (tab, tag, sites) { - let location = tab.get('url') - const partitionNumber = tab.get('partitionNumber') - let parentFolderId - - // if site map is available, look up extra information: - // - original url (if redirected) - // - parent folder id - if (sites) { - let results = makeImmutable([]) - - // get all sites matching URL and partition (disregarding parentFolderId) - let siteKey = module.exports.getSiteKey(makeImmutable({location, partitionNumber})) - results = results.merge(getSitesBySubkey(sites, siteKey, tag)) - - // only check for provisional location if entry is not found - if (results.size === 0) { - // if provisional location is different, grab any results which have that URL - // this may be different if the site was redirected - const provisionalLocation = tab.getIn(['frame', 'provisionalLocation']) - if (provisionalLocation && provisionalLocation !== location) { - siteKey = module.exports.getSiteKey(makeImmutable({ - location: provisionalLocation, - partitionNumber - })) - results = results.merge(getSitesBySubkey(sites, siteKey, tag)) - } - } - - // update details which get returned below - if (results.size > 0) { - location = results.getIn([0, 'location']) - parentFolderId = results.getIn([0, 'parentFolderId']) - } - } - - const siteDetail = { - location: location, - title: tab.get('title'), - tags: tag ? [tag] : [] - } - if (partitionNumber) { - siteDetail.partitionNumber = partitionNumber - } - if (parentFolderId) { - siteDetail.parentFolderId = parentFolderId - } - return Immutable.fromJS(siteDetail) -} - -module.exports.getDetailFromCreateProperties = function (createProperties, tag) { - const siteDetail = { - location: createProperties.get('url'), - tags: tag ? [tag] : [] - } - if (createProperties.get('partitionNumber') !== undefined) { - siteDetail.partitionNumber = createProperties.get('partitionNumber') - } - return Immutable.fromJS(siteDetail) -} - -/** - * Update the favicon URL for all entries in the state sites - * which match a given location. Currently, there should only be - * one match, but this will handle multiple. - * - * @param state The application state - * @param location URL for the entry needing an update - * @param favicon favicon URL - */ -module.exports.updateSiteFavicon = function (state, location, favicon) { - if (UrlUtil.isNotURL(location)) { - return state - } - const siteKeys = siteCache.getLocationSiteKeys(state, location) - if (!siteKeys || siteKeys.length === 0) { - return state - } - siteKeys.forEach((siteKey) => { - state = state.setIn(['sites', siteKey, 'favicon'], favicon) - }) - return state -} - -/** - * Converts a siteDetail to createProperties format - * @param {Object} siteDetail - A site detail as per app state - * @return {Object} A createProperties plain JS object, not ImmutableJS - */ -module.exports.toCreateProperties = function (siteDetail) { - return { - url: siteDetail.get('location'), - partitionNumber: siteDetail.get('partitionNumber') - } -} - -/** - * Compares 2 site details - * @param siteDetail1 The first site detail to compare. - * @param siteDetail2 The second site detail to compare. - * @return true if the site details should be considered the same. - */ -module.exports.isEquivalent = function (siteDetail1, siteDetail2) { - const isFolder1 = module.exports.isFolder(siteDetail1) - const isFolder2 = module.exports.isFolder(siteDetail2) - if (isFolder1 !== isFolder2) { - return false - } - - // If they are both folders - if (isFolder1) { - return siteDetail1.get('folderId') === siteDetail2.get('folderId') - } - return siteDetail1.get('location') === siteDetail2.get('location') && siteDetail1.get('partitionNumber') === siteDetail2.get('partitionNumber') -} - /** * Determines if the site detail is a bookmark. * @param siteDetail The site detail to check. * @return true if the site detail has a bookmark tag. */ +// TODO remove when sync is refactored module.exports.isBookmark = function (siteDetail) { if (siteDetail) { return isBookmark(siteDetail.get('tags')) @@ -688,6 +182,7 @@ module.exports.isBookmark = function (siteDetail) { * @param siteDetail The site detail to check. * @return true if the site detail is a folder. */ +// TODO remove when sync is refactored module.exports.isFolder = function (siteDetail) { if (siteDetail) { return isBookmarkFolder(siteDetail.get('tags')) && siteDetail.get('folderId') !== undefined @@ -695,25 +190,16 @@ module.exports.isFolder = function (siteDetail) { return false } -/** - * Determines if the site detail is an imported bookmark. - * @param siteDetail The site detail to check. - * @return true if the site detail is a folder. - */ -module.exports.isImportedBookmark = function (siteDetail) { - return siteDetail.get('lastAccessedTime') === 0 -} - /** * Determines if the site detail is a history entry. * @param siteDetail The site detail to check. * @return true if the site detail is a history entry. */ +// TODO remove when sync is refactored module.exports.isHistoryEntry = function (siteDetail) { if (siteDetail && typeof siteDetail.get('location') === 'string') { const tags = siteDetail.get('tags') if (siteDetail.get('location').startsWith('about:') || - module.exports.isDefaultEntry(siteDetail) || isBookmarkFolder(tags)) { return false } @@ -722,122 +208,11 @@ module.exports.isHistoryEntry = function (siteDetail) { return false } -/** - * Determines if the site detail is one of default sites in about:newtab. - * @param {Immutable.Map} siteDetail The site detail to check. - * @returns {boolean} if the site detail is a default newtab entry. - */ -module.exports.isDefaultEntry = function (siteDetail) { - return Immutable.is(siteDetail.get('tags'), defaultTags) && - siteDetail.get('lastAccessedTime') === 1 -} - -/** - * Get a folder by folderId - * @returns {Immutable.List.} sites - * @param {number} folderId - * @returns {Array[, ]|undefined} - */ -module.exports.getFolder = function (sites, folderId) { - const entry = sites.findEntry((site, _path) => { - return module.exports.isFolder(site) && site.get('folderId') === folderId - }) - if (!entry) { return undefined } - return entry -} - -/** - * Obtains an array of folders - */ -module.exports.getFolders = function (sites, folderId, parentId, labelPrefix) { - parentId = parentId || 0 - let folders = [] - const results = sites - .filter(site => { - return (site.get('parentFolderId', 0) === parentId && module.exports.isFolder(site)) - }) - .toList() - .sort(module.exports.siteSort) - - const resultSize = results.size - for (let i = 0; i < resultSize; i++) { - const site = results.get(i) - if (site.get('folderId') === folderId) { - continue - } - - const label = (labelPrefix || '') + (site.get('customTitle') || site.get('title')) - folders.push({ - folderId: site.get('folderId'), - parentFolderId: site.get('parentFolderId'), - label - }) - const subsites = module.exports.getFolders(sites, folderId, site.get('folderId'), (label || '') + ' / ') - folders = folders.concat(subsites) - } - - return folders -} - -/** - * Filters out non recent sites based on the app setting for history size. - * @param sites The application state's Immutable sites list. - */ -module.exports.filterOutNonRecents = function (sites) { - const sitesWithTags = sites - .filter((site) => site.get('tags').size) - const topHistorySites = sites - .filter((site) => site.get('tags').size === 0) - .sort((site1, site2) => (site2.get('lastAccessedTime') || 0) - (site1.get('lastAccessedTime') || 0)) - .take(getSetting(settings.AUTOCOMPLETE_HISTORY_SIZE)) - return sitesWithTags.concat(topHistorySites) -} - -/** - * Filters sites relative to a parent site (folder). - * @param sites The application state's Immutable sites list. - * @param relSite The folder to filter to. - */ -module.exports.filterSitesRelativeTo = function (sites, relSite) { - if (!relSite.get('folderId')) { - return sites - } - return sites.filter((site) => site.get('parentFolderId') === relSite.get('folderId')) -} - -/** - * Clears history by - * - filtering out entries which have no tags - * - setting lastAccessedTime to null for remaining entries (bookmarks) - * @param sites The application state's Immutable sites list. - */ -module.exports.clearHistory = function (sites) { - let bookmarks = sites.filter((site) => site.get('tags') && site.get('tags').size > 0) - bookmarks.forEach((site, index) => { - if (site.get('lastAccessedTime')) { - bookmarks = bookmarks.setIn([index, 'lastAccessedTime'], null) - } - }) - return bookmarks -} - -/** - * Returns all sites that have a bookmark tag. - * @param sites The application state's Immutable sites list. - */ - -module.exports.getBookmarks = function (sites) { - if (sites) { - return sites.filter((site) => isBookmarkFolder(site.get('tags')) || isBookmark(site.get('tags'))) - } - return makeImmutable({}) -} - /** * Gets a site origin (scheme + hostname + port) from a URL or null if not * available. * @param {string} location - * @return {string?} + * @return {string|null} */ module.exports.getOrigin = function (location) { // Returns scheme + hostname + port diff --git a/js/state/syncUtil.js b/js/state/syncUtil.js index 2439eef3d19..c56f50910e1 100644 --- a/js/state/syncUtil.js +++ b/js/state/syncUtil.js @@ -9,7 +9,9 @@ const crypto = require('crypto') const writeActions = require('../constants/sync/proto').actions const siteTags = require('../constants/siteTags') const siteUtil = require('./siteUtil') +const {getSetting} = require('../settings') const {isDataUrl} = require('../lib/urlutil') +const settings = require('../constants/settings') const CATEGORY_MAP = { bookmark: { @@ -47,7 +49,19 @@ module.exports.siteSettingDefaults = { // Whitelist of valid browser-laptop site fields. In browser-laptop, site // is used for both bookmarks and history sites. -const SITE_FIELDS = ['objectId', 'location', 'title', 'customTitle', 'tags', 'favicon', 'themeColor', 'lastAccessedTime', 'creationTime', 'partitionNumber', 'folderId', 'parentFolderId'] +const SITE_FIELDS = [ + 'objectId', + 'location', + 'title', + 'tags', + 'favicon', + 'themeColor', + 'lastAccessedTime', + 'creationTime', + 'partitionNumber', + 'folderId', + 'parentFolderId' +] const pickFields = (object, fields) => { return fields.reduce((a, x) => { @@ -101,10 +115,7 @@ module.exports.getSiteDataFromRecord = (record, appState, records) => { record.bookmark && record.bookmark.site, {objectId} ) - if (siteProps.customTitle === '') { - // browser-laptop UI expects the customTitle field to not exist if it is empty - delete siteProps.customTitle - } + delete siteProps.customTitle if (record.objectData === 'bookmark') { const existingFolderId = existingObjectData && existingObjectData.get('folderId') if (existingFolderId) { @@ -151,7 +162,6 @@ const applySiteSettingRecord = (record) => { return value } } - const appActions = require('../actions/appActions') const hostPattern = record.siteSetting.hostPattern if (!hostPattern) { throw new Error('siteSetting.hostPattern is required.') @@ -210,7 +220,7 @@ const applySyncRecord = (record) => { break case 'device': const device = Object.assign({}, record.device, {lastRecordTimestamp: record.syncTimestamp}) - require('../actions/appActions').saveSyncDevices({ + appActions.saveSyncDevices({ [deviceIdString(record.deviceId)]: device }) break @@ -225,19 +235,39 @@ const applySyncRecord = (record) => { */ module.exports.applySyncRecords = (records) => { if (!records || records.length === 0) { return } - const siteRecords = [] + const bookmarkRecords = [] + const bookmarkFoldersRecords = [] + const historyRecords = [] const otherRecords = [] records.forEach((record) => { - if (record && ['bookmark', 'historySite'].includes(record.objectData)) { - siteRecords.push(record) + if (record && ['bookmark'].includes(record.objectData)) { + bookmarkRecords.push(record) + } else if (record && ['historySite'].includes(record.objectData)) { + historyRecords.push(record) + } else if (record && ['bookmark-folder'].includes(record.objectData)) { + bookmarkFoldersRecords.push(record) } else { otherRecords.push(record) } }) applyNonBatchedRecords(otherRecords) - if (siteRecords.length) { + + // TODO we now always add (need to check record.action for what to do) + if (bookmarkRecords.length) { + setImmediate(() => { + appActions.addBookmarks(new Immutable.List(bookmarkRecords)) + }) + } + + if (bookmarkFoldersRecords.length) { setImmediate(() => { - require('../actions/appActions').applySiteRecords(new Immutable.List(siteRecords)) + appActions.addBookmarkFolder(new Immutable.List(bookmarkFoldersRecords)) + }) + } + + if (historyRecords.length) { + setImmediate(() => { + appActions.addHistorySites(new Immutable.List(historyRecords)) }) } } @@ -293,6 +323,7 @@ module.exports.getExistingObject = (categoryName, syncRecord) => { */ module.exports.createSiteCache = (appState) => { const objectsById = new Immutable.Map().withMutations(objectsById => { + // TODO what to do here? appState.get('sites').forEach((site, siteKey) => { const objectId = site.get('objectId') if (!objectId) { return true } @@ -461,7 +492,6 @@ module.exports.createSiteData = (site, appState) => { const siteData = { location: '', title: '', - customTitle: '', favicon: '', lastAccessedTime: 0, creationTime: 0 @@ -582,3 +612,7 @@ module.exports.deepArrayify = deepArrayify module.exports.ipcSafeObject = (object) => { return deepArrayify(object) } + +module.exports.syncEnabled = () => { + return getSetting(settings.SYNC_ENABLED) === true +} diff --git a/js/stores/appStore.js b/js/stores/appStore.js index 04a72501ba5..dda7fc815a7 100644 --- a/js/stores/appStore.js +++ b/js/stores/appStore.js @@ -45,6 +45,8 @@ const extensionState = require('../../app/common/state/extensionState') const aboutNewTabState = require('../../app/common/state/aboutNewTabState') const aboutHistoryState = require('../../app/common/state/aboutHistoryState') const tabState = require('../../app/common/state/tabState') +const bookmarksState = require('../../app/common/state/bookmarksState') +const bookmarkFoldersState = require('../../app/common/state/bookmarkFoldersState') const isDarwin = process.platform === 'darwin' const isWindows = process.platform === 'win32' @@ -343,16 +345,6 @@ function setDefaultWindowSize () { const appStore = new AppStore() const emitChanges = debounce(appStore.emitChanges.bind(appStore), 5) -/** - * Clears out the top X non tagged sites. - * This is debounced to every 1 minute, the cleanup is not particularly intensive - * but there's no point to cleanup frequently. - */ -const filterOutNonRecents = debounce(() => { - appState = appState.set('sites', siteUtil.filterOutNonRecents(appState.get('sites'))) - emitChanges() -}, 60 * 1000) - /** * Useful for updating non-react preferences (electron properties, etc). * Called when any settings are modified (ex: via preferences). @@ -398,6 +390,10 @@ const handleAppAction = (action) => { // until we have a better way to manage dependencies. // tabsReducer must be above dragDropReducer. require('../../app/browser/reducers/tabsReducer'), + require('../../app/browser/reducers/bookmarksReducer'), + require('../../app/browser/reducers/bookmarkFoldersReducer'), + require('../../app/browser/reducers/historyReducer'), + require('../../app/browser/reducers/pinnedSitesReducer'), require('../../app/browser/reducers/sitesReducer'), require('../../app/browser/reducers/windowsReducer'), require('../../app/browser/reducers/syncReducer'), @@ -457,19 +453,11 @@ const handleAppAction = (action) => { case appConstants.APP_DATA_URL_COPIED: nativeImage.copyDataURL(action.dataURL, action.html, action.text) break - case appConstants.APP_ADD_SITE: - case appConstants.APP_ADD_BOOKMARK: - case appConstants.APP_EDIT_BOOKMARK: - const oldSiteSize = appState.get('sites').size - calculateTopSites(false) + case appConstants.APP_ADD_HISTORY_SITE: + calculateTopSites(true) appState = aboutHistoryState.setHistory(appState, action) - // If there was an item added then clear out the old history entries - if (oldSiteSize !== appState.get('sites').size) { - filterOutNonRecents() - } break - case appConstants.APP_APPLY_SITE_RECORDS: - case appConstants.APP_REMOVE_SITE: + case appConstants.APP_REMOVE_HISTORY_SITE: calculateTopSites(true) appState = aboutHistoryState.setHistory(appState, action) break @@ -665,8 +653,8 @@ const handleAppAction = (action) => { const clearData = defaults ? defaults.merge(temp) : temp if (clearData.get('browserHistory')) { - calculateTopSites(true) - appState = aboutHistoryState.setHistory(appState) + appState = aboutNewTabState.clearTopSites(appState) + appState = aboutHistoryState.clearHistory(appState) syncActions.clearHistory() BrowserWindow.getAllWindows().forEach((wnd) => wnd.webContents.send(messages.CLEAR_CLOSED_FRAMES)) } @@ -800,7 +788,7 @@ const handleAppAction = (action) => { appState = appState.set('defaultBrowserCheckComplete', {}) break case windowConstants.WINDOW_SET_FAVICON: - appState = siteUtil.updateSiteFavicon(appState, action.frameProps.get('location'), action.favicon) + appState = bookmarksState.updateSiteFavicon(appState, action.frameProps.get('location'), action.favicon) if (action.frameProps.get('favicon') !== action.favicon) { calculateTopSites(false) } @@ -856,13 +844,22 @@ const handleAppAction = (action) => { const syncDefault = Immutable.fromJS(sessionStore.defaultAppState().sync) const originalSeed = appState.getIn(['sync', 'seed']) appState = appState.set('sync', syncDefault) - appState.get('sites').forEach((site, key) => { - if (site.has('objectId') && syncUtil.isSyncable('bookmark', site)) { + bookmarksState.getBookmarks(appState).forEach((site, key) => { + if (site.has('objectId')) { + // Remember which profile this bookmark was originally synced to. + // Since old bookmarks should be synced when a new profile is created, + // we have to keep track of which profile already has these bookmarks + // or else the old profile may have these bookmarks duplicated. #7405 + appState = appState.setIn(['bookmarks', key, 'originalSeed'], originalSeed) + } + }) + bookmarkFoldersState.getFolders(appState).forEach((site, key) => { + if (site.has('objectId')) { // Remember which profile this bookmark was originally synced to. // Since old bookmarks should be synced when a new profile is created, // we have to keep track of which profile already has these bookmarks // or else the old profile may have these bookmarks duplicated. #7405 - appState = appState.setIn(['sites', key, 'originalSeed'], originalSeed) + appState = appState.setIn(['bookmarks', key, 'originalSeed'], originalSeed) } }) appState.setIn(['sync', 'devices'], {}) diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index 4e09138b53f..2741c6cc2fc 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -23,6 +23,9 @@ const assert = require('assert') const contextMenuState = require('../../app/common/state/contextMenuState') const appStoreRenderer = require('./appStoreRenderer') const windowActions = require('../actions/windowActions') +const siteUtil = require('../state/siteUtil') +const bookmarkFoldersState = require('../../app/common/state/bookmarkFoldersState') +const bookmarksState = require('../../app/common/state/bookmarksState') let windowState = Immutable.fromJS({ activeFrameKey: null, @@ -64,10 +67,6 @@ class WindowStore extends EventEmitter { windowState = newWindowState } - getFrames () { - return frameStateUtil.getFrames(this.state) - } - getFrame (key) { return frameStateUtil.getFrameByKey(windowState, key) } @@ -471,38 +470,55 @@ const doAction = (action) => { windowState = windowState.delete('bookmarkDetail') break case windowConstants.WINDOW_ON_EDIT_BOOKMARK: - const siteDetail = appStoreRenderer.state.getIn(['sites', action.editKey]) + { + const siteDetail = bookmarksState.getBookmark(appStoreRenderer.state, action.editKey) - windowState = windowState.setIn(['bookmarkDetail'], Immutable.fromJS({ - siteDetail: siteDetail, - editKey: action.editKey, - isBookmarkHanger: action.isHanger - })) - break + windowState = windowState.setIn(['bookmarkDetail'], Immutable.fromJS({ + siteDetail: siteDetail, + editKey: action.editKey, + isBookmarkHanger: action.isHanger + })) + break + } case windowConstants.WINDOW_ON_BOOKMARK_ADDED: { - let editKey = action.editKey - const site = appStoreRenderer.state.getIn(['sites', editKey]) - let siteDetail = action.siteDetail + let bookmarkDetail = action.bookmarkDetail - if (site) { - siteDetail = site + if (bookmarkDetail == null) { + bookmarkDetail = frameStateUtil.getActiveFrame(windowState) } - if (siteDetail == null) { - siteDetail = frameStateUtil.getActiveFrame(windowState) - } + bookmarkDetail = bookmarkDetail.set('location', UrlUtil.getLocationIfPDF(bookmarkDetail.get('location'))) - siteDetail = siteDetail.set('location', UrlUtil.getLocationIfPDF(siteDetail.get('location'))) + const editKey = siteUtil.getSiteKey(bookmarkDetail) windowState = windowState.setIn(['bookmarkDetail'], Immutable.fromJS({ - siteDetail: siteDetail, + siteDetail: bookmarkDetail, editKey: editKey, isBookmarkHanger: action.isHanger, isAdded: true })) } break + case windowConstants.WINDOW_ON_ADD_BOOKMARK_FOLDER: + windowState = windowState.setIn(['bookmarkFolderDetail'], Immutable.fromJS({ + folderDetails: action.folderDetails, + closestKey: action.closestKey + })) + break + case windowConstants.WINDOW_ON_EDIT_BOOKMARK_FOLDER: + { + const folderDetails = bookmarkFoldersState.getFolder(appStoreRenderer.state, action.editKey) + + windowState = windowState.setIn(['bookmarkFolderDetail'], Immutable.fromJS({ + folderDetails: folderDetails, + editKey: action.editKey + })) + break + } + case windowConstants.WINDOW_ON_BOOKMARK_FOLDER_CLOSE: + windowState = windowState.delete('bookmarkFolderDetail') + break case windowConstants.WINDOW_AUTOFILL_SELECTION_CLICKED: ipc.send('autofill-selection-clicked', action.tabId, action.value, action.frontEndId, action.index) windowState = windowState.delete('contextMenuDetail') diff --git a/test/about/bookmarksManagerTest.js b/test/about/bookmarksManagerTest.js index 3cecb0dd755..5b4a91ab5d4 100644 --- a/test/about/bookmarksManagerTest.js +++ b/test/about/bookmarksManagerTest.js @@ -10,7 +10,6 @@ const Immutable = require('immutable') describe('about:bookmarks', function () { const folderId = Math.random() const lastVisit = 1476140184441 - const bookmarkTag = [siteTags.BOOKMARK] const browseableSiteUrl = 'page1.html' const browseableSiteTitle = 'Page 1' @@ -30,36 +29,30 @@ describe('about:bookmarks', function () { location: siteWithFavicon, title: 'Page with Favicon', favicon: favicon, - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit }, { location: siteWithoutFavicon, title: 'Page without Favicon', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit } ]) yield client - .addSiteList(sites) + .addBookmarks(sites) .tabByIndex(0) .loadUrl(aboutBookmarksUrl) } function * addDemoSites (client) { const sites = Immutable.fromJS([ - { - customTitle: 'demo1', - folderId: folderId, - parentFolderId: 0, - tags: [siteTags.BOOKMARK_FOLDER] - }, { location: 'https://brave.com', title: 'Brave', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit }, @@ -67,7 +60,7 @@ describe('about:bookmarks', function () { location: 'https://brave.com/test', title: 'Test', customTitle: 'customTest', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit }, @@ -75,41 +68,41 @@ describe('about:bookmarks', function () { location: 'https://brave.com/test', title: 'Test', customTitle: 'customTest', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit }, { location: 'https://www.youtube.com', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit }, { location: 'https://www.facebook.com', title: 'facebook', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit }, { location: 'https://duckduckgo.com', title: 'duckduckgo', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: folderId, lastAccessedTime: lastVisit }, { location: 'https://google.com', title: 'Google', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: folderId, lastAccessedTime: lastVisit }, { location: 'https://bing.com', title: 'Bing', - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: folderId, lastAccessedTime: lastVisit } @@ -117,7 +110,13 @@ describe('about:bookmarks', function () { yield client .waitForBrowserWindow() - .addSiteList(sites) + .addBookmarkFolder({ + customTitle: 'demo1', + folderId: folderId, + parentFolderId: 0, + type: siteTags.BOOKMARK_FOLDER + }) + .addBookmarks(sites) .tabByIndex(0) .loadUrl(aboutBookmarksUrl) } @@ -126,13 +125,13 @@ describe('about:bookmarks', function () { const site = Brave.server.url(browseableSiteUrl) yield client .waitForBrowserWindow() - .addSite({ + .addBookmark({ location: site, title: browseableSiteTitle, - tags: bookmarkTag, + type: siteTags.BOOKMARK, parentFolderId: 0, lastAccessedTime: lastVisit - }, siteTags.BOOKMARK) + }) .tabByIndex(0) .loadUrl(aboutBookmarksUrl) } diff --git a/test/about/historyTest.js b/test/about/historyTest.js index 8045311ec9e..56ca569e254 100644 --- a/test/about/historyTest.js +++ b/test/about/historyTest.js @@ -19,10 +19,10 @@ describe('about:history', function () { function * addDemoSites (client) { yield client - .addSite({ location: 'https://brave.com', title: 'Brave' }) - .addSite({ location: 'https://brave.com/test', customTitle: 'customTest' }) - .addSite({ location: 'https://www.youtube.com' }) - .addSite({ location: 'https://www.facebook.com' }) + .addHistorySite({ location: 'https://brave.com', title: 'Brave' }) + .addHistorySite({ location: 'https://brave.com/test', customTitle: 'customTest' }) + .addHistorySite({ location: 'https://www.youtube.com' }) + .addHistorySite({ location: 'https://www.facebook.com' }) .waitForExist('[data-test-id="tab"][data-frame-key="1"]') .tabByIndex(0) .url(aboutHistoryUrl) @@ -31,7 +31,7 @@ describe('about:history', function () { function * addBrowseableSite (client) { const site = Brave.server.url(browseableSiteUrl) yield client - .addSite({ + .addHistorySite({ location: site, title: 'Page 1' }) diff --git a/test/about/ledgerPanelTest.js b/test/about/ledgerPanelTest.js index 9788d68db38..0a85f86a712 100644 --- a/test/about/ledgerPanelTest.js +++ b/test/about/ledgerPanelTest.js @@ -154,11 +154,11 @@ describe('Regular payment panel tests', function () { .tabByIndex(0) .loadUrl(site1) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site1) + .waitForHistoryEntry(site1) .tabByUrl(site1) .loadUrl(site2) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site2) + .waitForHistoryEntry(site2) .tabByUrl(site2) .loadUrl(prefsUrl) .waitForVisible(paymentsTab) @@ -197,11 +197,11 @@ describe('Regular payment panel tests', function () { .tabByIndex(0) .loadUrl(site1) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site1) + .waitForHistoryEntry(site1) .tabByUrl(site1) .loadUrl(site2) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site2) + .waitForHistoryEntry(site2) .tabByUrl(site2) .loadUrl(prefsUrl) .waitForVisible(paymentsTab) @@ -240,11 +240,11 @@ describe('Regular payment panel tests', function () { .tabByIndex(0) .loadUrl(site1) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site1) + .waitForHistoryEntry(site1) .tabByUrl(site1) .loadUrl(site2) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site2) + .waitForHistoryEntry(site2) .tabByUrl(site2) .loadUrl(prefsUrl) .waitForVisible(paymentsTab) @@ -296,11 +296,11 @@ describe('Regular payment panel tests', function () { yield this.app.client .loadUrl(site1) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site1) + .waitForHistoryEntry(site1) .tabByUrl(site1) .loadUrl(site2) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site2) + .waitForHistoryEntry(site2) .tabByUrl(site2) .loadUrl(prefsUrl) .waitForVisible(paymentsTab) @@ -326,11 +326,11 @@ describe('Regular payment panel tests', function () { .tabByIndex(0) .loadUrl(site1) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site1) + .waitForHistoryEntry(site1) .tabByUrl(site1) .loadUrl(site2) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site2) + .waitForHistoryEntry(site2) .tabByUrl(site2) .loadUrl(prefsUrl) .waitForVisible(paymentsTab) @@ -354,14 +354,14 @@ describe('Regular payment panel tests', function () { .tabByIndex(0) .loadUrl(site1) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site1) + .waitForHistoryEntry(site1) .tabByUrl(site1) .windowByUrl(Brave.browserWindowUrl) .changeSetting(settings.AUTO_SUGGEST_SITES, false) .tabByIndex(0) .loadUrl(site2) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site2) + .waitForHistoryEntry(site2) .tabByUrl(site2) .loadUrl(prefsUrl) .waitForVisible(paymentsTab) @@ -419,15 +419,15 @@ describe('synopsis', function () { yield this.app.client .loadUrl(site3) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site3, false) + .waitForHistoryEntry(site3, false) .tabByUrl(site3) .loadUrl(site1) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site1) + .waitForHistoryEntry(site1) .tabByUrl(site1) .loadUrl(site2) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site2) + .waitForHistoryEntry(site2) .tabByUrl(site2) .loadUrl(prefsUrl) .waitForVisible(paymentsTab) diff --git a/test/about/ledgerTableTest.js b/test/about/ledgerTableTest.js index 8278563f569..5a507f5ba95 100644 --- a/test/about/ledgerTableTest.js +++ b/test/about/ledgerTableTest.js @@ -56,7 +56,7 @@ function * before (client, siteList) { .tabByIndex(0) .loadUrl(site) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(site, false) + .waitForHistoryEntry(site, false) .tabByUrl(site) } diff --git a/test/about/newTabTest.js b/test/about/newTabTest.js index 4ec019c0bba..bc7283ed421 100644 --- a/test/about/newTabTest.js +++ b/test/about/newTabTest.js @@ -42,23 +42,23 @@ describe('about:newtab tests', function () { function * addDemoAboutPages (client) { yield client - .addSite({ location: 'about:about' }) - .addSite({ location: 'about:adblock' }) - .addSite({ location: 'about:autofill' }) - .addSite({ location: 'about:blank' }) - .addSite({ location: 'about:bookmarks' }) - .addSite({ location: 'about:brave' }) - .addSite({ location: 'about:certerror' }) - .addSite({ location: 'about:config' }) - .addSite({ location: 'about:downloads' }) - .addSite({ location: 'about:error' }) - .addSite({ location: 'about:extensions' }) - .addSite({ location: 'about:history' }) - .addSite({ location: 'about:newtab' }) - .addSite({ location: 'about:passwords' }) - .addSite({ location: 'about:preferences' }) - .addSite({ location: 'about:safebrowsing' }) - .addSite({ location: 'about:styles' }) + .addHistorySite({ location: 'about:about' }) + .addHistorySite({ location: 'about:adblock' }) + .addHistorySite({ location: 'about:autofill' }) + .addHistorySite({ location: 'about:blank' }) + .addHistorySite({ location: 'about:bookmarks' }) + .addHistorySite({ location: 'about:brave' }) + .addHistorySite({ location: 'about:certerror' }) + .addHistorySite({ location: 'about:config' }) + .addHistorySite({ location: 'about:downloads' }) + .addHistorySite({ location: 'about:error' }) + .addHistorySite({ location: 'about:extensions' }) + .addHistorySite({ location: 'about:history' }) + .addHistorySite({ location: 'about:newtab' }) + .addHistorySite({ location: 'about:passwords' }) + .addHistorySite({ location: 'about:preferences' }) + .addHistorySite({ location: 'about:safebrowsing' }) + .addHistorySite({ location: 'about:styles' }) .waitForExist('[data-test-id="tab"][data-frame-key="1"]') .tabByIndex(0) .url(aboutNewTabUrl) diff --git a/test/app/sessionStoreTest.js b/test/app/sessionStoreTest.js index e3a384529b4..7f5aa9aeee4 100644 --- a/test/app/sessionStoreTest.js +++ b/test/app/sessionStoreTest.js @@ -6,7 +6,7 @@ const siteTags = require('../../js/constants/siteTags') const settings = require('../../js/constants/settings') describe('sessionStore test', function () { - function * setup (client) { + function * setup () { Brave.addCommands() } @@ -16,7 +16,8 @@ describe('sessionStore test', function () { const page1Url = Brave.server.url('page1.html') const site = { location: page1Url, - title: 'some page' + title: 'some page', + type: siteTags.BOOKMARK } yield Brave.startApp() yield setup(Brave.app.client) @@ -28,7 +29,7 @@ describe('sessionStore test', function () { .windowParentByUrl(page1Url) .activateURLMode() .waitForExist(navigatorNotBookmarked) - .addBookmark(site, siteTags.BOOKMARK) + .addBookmark(site) .waitForExist(navigatorBookmarked) yield Brave.stopApp(false) yield Brave.startApp() @@ -62,7 +63,8 @@ describe('sessionStore test', function () { const page1Url = Brave.server.url('page1.html') const site = { location: page1Url, - title: 'some page' + title: 'some page', + type: siteTags.BOOKMARK } yield Brave.startApp() yield setup(Brave.app.client) @@ -74,7 +76,7 @@ describe('sessionStore test', function () { .windowParentByUrl(page1Url) .activateURLMode() .waitForExist(navigatorNotBookmarked) - .addBookmark(site, siteTags.BOOKMARK) + .addBookmark(site) .waitForExist(navigatorBookmarked) yield Brave.stopApp(false) diff --git a/test/bookmark-components/bookmarksTest.js b/test/bookmark-components/bookmarksTest.js index 60859a6aef8..fe75a7ebe48 100644 --- a/test/bookmark-components/bookmarksTest.js +++ b/test/bookmark-components/bookmarksTest.js @@ -1,7 +1,6 @@ /* global describe, it, before, beforeEach */ const Brave = require('../lib/brave') -const Immutable = require('immutable') const {homepageInput, urlInput, navigator, navigatorBookmarked, navigatorNotBookmarked, doneButton, removeButton, bookmarkNameInput, bookmarkLocationInput} = require('../lib/selectors') const siteTags = require('../../js/constants/siteTags') @@ -87,7 +86,7 @@ describe('bookmark tests', function () { .windowByUrl(Brave.browserWindowUrl) .waitUntil(function () { return this.getAppState().then((val) => { - return val.value.sites['https://www.brave.xn--com-8cd/|0|0'].customTitle === 'https://www.brave.xn--com-8cd/' + return val.value.sites['https://www.brave.xn--com-8cd/|0|0'].title === 'https://www.brave.xn--com-8cd/' }) }) }) @@ -303,12 +302,12 @@ describe('bookmark tests', function () { this.page2Url = Brave.server.url('page2.html') yield setup(this.app.client) yield this.app.client - .addSite({ + .addBookmark({ location: this.page1Url, folderId: 1, parentFolderId: 0, - tags: [siteTags.BOOKMARK] - }, siteTags.BOOKMARK) + type: siteTags.BOOKMARK + }) }) it('on new active tabs', function * () { @@ -332,9 +331,18 @@ describe('bookmark tests', function () { }) describe('menu behavior', function () { + // Skip this test if we are not on Windows, + // we only generate menu on windows + const isWindows = process.platform === 'win32' + Brave.beforeAll(this) before(function * () { + if (!isWindows) { + this.skip() + return + } + yield setup(this.app.client) }) @@ -342,12 +350,12 @@ describe('bookmark tests', function () { const folderName = 'bookmark-folder-rebuild-menu-demo' yield this.app.client - .addSite({ + .addBookmarkFolder({ customTitle: folderName, folderId: 1, parentFolderId: 0, - tags: [siteTags.BOOKMARK_FOLDER] - }, siteTags.BOOKMARK_FOLDER) + type: siteTags.BOOKMARK_FOLDER + }) .waitUntil(function () { return this.getAppState().then((val) => { const bookmarksMenu = val.value.menu.template.find((item) => { @@ -368,12 +376,12 @@ describe('bookmark tests', function () { const bookmarkTitle = 'bookmark-rebuild-menu-demo' yield this.app.client - .addSite({ + .addBookmark({ lastAccessedTime: 456, - tags: [siteTags.BOOKMARK], + type: siteTags.BOOKMARK, location: 'https://brave.com', title: bookmarkTitle - }, siteTags.BOOKMARK) + }) .waitUntil(function () { return this.getAppState().then((val) => { const bookmarksMenu = val.value.menu.template.find((item) => { @@ -393,22 +401,19 @@ describe('bookmark tests', function () { it('rebuilds the menu when add a list of items', function * () { const bookmarkTitle = 'bookmark-rebuild-menu-demo' const folderName = 'bookmark-folder-rebuild-menu-demo' - const sites = Immutable.fromJS([ - { + yield this.app.client + .addBookmarkFolder({ customTitle: folderName, folderId: 1, parentFolderId: 0, - tags: [siteTags.BOOKMARK_FOLDER] - }, - { + type: siteTags.BOOKMARK_FOLDER + }) + .addBookmark({ lastAccessedTime: 123, title: bookmarkTitle, location: 'https://brave.com', - tags: [siteTags.BOOKMARK] - } - ]) - yield this.app.client - .addSiteList(sites) + type: siteTags.BOOKMARK + }) .waitForBrowserWindow() .waitUntil(function () { return this.getAppState().then((val) => { diff --git a/test/bookmark-components/bookmarksToolbarTest.js b/test/bookmark-components/bookmarksToolbarTest.js index f632a03d6bf..759f7a30e6f 100644 --- a/test/bookmark-components/bookmarksToolbarTest.js +++ b/test/bookmark-components/bookmarksToolbarTest.js @@ -57,12 +57,12 @@ describe('bookmarksToolbar', function () { yield this.app.client .changeSetting(settings.SHOW_BOOKMARKS_TOOLBAR, true) .waitForVisible(bookmarksToolbar) - .addSite({ + .addBookmarkFolder({ customTitle: 'demo1', folderId: Math.random(), parentFolderId: 0, - tags: [siteTags.BOOKMARK_FOLDER] - }, siteTags.BOOKMARK_FOLDER) + type: siteTags.BOOKMARK_FOLDER + }) .waitForVisible('[data-test-id="bookmarkToolbarButton"][title=demo1]') .click(bookmarksToolbar) .click('[data-test-id="bookmarkToolbarButton"][title=demo1]') @@ -78,23 +78,23 @@ describe('bookmarksToolbar', function () { yield this.app.client .changeSetting(settings.SHOW_BOOKMARKS_TOOLBAR, true) .waitForVisible(bookmarksToolbar) - .addSite({ + .addBookmarkFolder({ customTitle: 'demo1', folderId: folderId1, parentFolderId: 0, - tags: [siteTags.BOOKMARK_FOLDER] - }, siteTags.BOOKMARK_FOLDER) + type: siteTags.BOOKMARK_FOLDER + }) .waitUntil(function () { return this.getAppState().then((val) => { return findBookmarkFolder('demo1', val) }) }) - .addSite({ + .addBookmarkFolder({ customTitle: 'demo2', folderId: folderId2, parentFolderId: 0, - tags: [siteTags.BOOKMARK_FOLDER] - }, siteTags.BOOKMARK_FOLDER) + type: siteTags.BOOKMARK_FOLDER + }) .waitUntil(function () { return this.getAppState().then((val) => { return findBookmarkFolder('demo2', val) @@ -103,7 +103,7 @@ describe('bookmarksToolbar', function () { .waitForUrl(Brave.newTabUrl) .loadUrl(this.page1Url) .windowParentByUrl(this.page1Url) - .waitForSiteEntry(this.page1Url) + .waitForHistoryEntry(this.page1Url) .waitForVisible(navigator) .activateURLMode() .waitForVisible(navigatorNotBookmarked) @@ -123,12 +123,12 @@ describe('bookmarksToolbar', function () { yield this.app.client .changeSetting(settings.SHOW_BOOKMARKS_TOOLBAR, true) .waitForVisible(bookmarksToolbar) - .addSite({ + .addBookmarkFolder({ customTitle: 'demo1', folderId: Math.random(), parentFolderId: 0, - tags: [siteTags.BOOKMARK_FOLDER] - }, siteTags.BOOKMARK_FOLDER) + type: siteTags.BOOKMARK_FOLDER + }) .waitUntil(function () { return this.getAppState().then((val) => { return findBookmarkFolder('demo1', val) @@ -137,7 +137,7 @@ describe('bookmarksToolbar', function () { .waitForUrl(Brave.newTabUrl) .loadUrl(this.page1Url) .windowParentByUrl(this.page1Url) - .waitForSiteEntry(this.page1Url) + .waitForHistoryEntry(this.page1Url) .waitForVisible(navigator) .activateURLMode() .waitForVisible(navigatorNotBookmarked) @@ -172,7 +172,7 @@ describe('bookmarksToolbar', function () { .waitForUrl(Brave.newTabUrl) .loadUrl(pageWithFavicon) .windowParentByUrl(pageWithFavicon) - .waitForSiteEntry(pageWithFavicon, false) + .waitForHistoryEntry(pageWithFavicon, false) .activateURLMode() .waitForVisible(navigatorNotBookmarked) .click(navigatorNotBookmarked) @@ -197,7 +197,7 @@ describe('bookmarksToolbar', function () { .waitForUrl(Brave.newTabUrl) .loadUrl(pageWithoutFavicon) .windowParentByUrl(pageWithoutFavicon) - .waitForSiteEntry(pageWithoutFavicon, false) + .waitForHistoryEntry(pageWithoutFavicon, false) .activateURLMode() .waitForVisible(navigatorNotBookmarked) .click(navigatorNotBookmarked) diff --git a/test/lib/brave.js b/test/lib/brave.js index f7f7eff2d64..15e981c205d 100644 --- a/test/lib/brave.js +++ b/test/lib/brave.js @@ -452,19 +452,6 @@ var exports = { }, 5000, null, 100) }) - this.app.client.addCommand('waitForSiteEntry', function (location, waitForTitle = true) { - logVerbose('waitForSiteEntry("' + location + '", "' + waitForTitle + '")') - return this.waitUntil(function () { - return this.getAppState().then((val) => { - const ret = val.value && val.value.sites && Array.from(Object.values(val.value.sites)).find( - (site) => site.location === location && - (!waitForTitle || (waitForTitle && site.title))) - logVerbose('waitForSiteEntry("' + location + ', ' + waitForTitle + '") => ' + ret) - return ret - }) - }, 5000, null, 100) - }) - this.app.client.addCommand('waitForAddressEntry', function (location, waitForTitle = true) { logVerbose('waitForAddressEntry("' + location + '", "' + waitForTitle + '")') return this.waitUntil(function () { @@ -473,7 +460,7 @@ var exports = { (site) => site.location === location && (!waitForTitle || (waitForTitle && site.title))) logVerbose('sites:' + JSON.stringify(val.value.sites)) - logVerbose('waitForSiteEntry("' + location + '", ' + waitForTitle + ') => ' + ret) + logVerbose('waitForAddressEntry("' + location + '", ' + waitForTitle + ') => ' + ret) return ret }) }, 5000, null, 100) @@ -672,51 +659,114 @@ var exports = { }) /** - * Adds a site to the sites list, such as a bookmarks. + * Adds a bookmark * * @param {object} siteDetail - Properties for the siteDetail to add - * @param {string} tag - A site tag from js/constants/siteTags.js */ - this.app.client.addCommand('addSite', function (siteDetail, tag) { - logVerbose('addSite("' + siteDetail + '", "' + tag + '")') + this.app.client.addCommand('addBookmark', function (siteDetail) { + logVerbose('addBookmark("' + siteDetail + '")') let waitUrl = siteDetail.location if (isSourceAboutUrl(waitUrl)) { waitUrl = getTargetAboutUrl(waitUrl) } - return this.execute(function (siteDetail, tag) { - return devTools('appActions').addSite(siteDetail, tag) - }, siteDetail, tag).then((response) => response.value) - .waitForSiteEntry(waitUrl, false) + return this.execute(function (siteDetail) { + return devTools('appActions').addBookmark(siteDetail) + }, siteDetail).then((response) => response.value) + .waitForBookmarkEntry(waitUrl, false) + }) + + this.app.client.addCommand('waitForBookmarkEntry', function (location, waitForTitle = true) { + logVerbose('waitForBookmarkEntry("' + location + '", "' + waitForTitle + '")') + return this.waitUntil(function () { + return this.getAppState().then((val) => { + const ret = val.value && val.value.bookmarks && Array.from(Object.values(val.value.bookmarks)).find( + (bookmark) => bookmark.location === location && + (!waitForTitle || (waitForTitle && bookmark.title))) + logVerbose('waitForBookmarkEntry("' + location + ', ' + waitForTitle + '") => ' + ret) + return ret + }) + }, 5000, null, 100) }) /** - * Adds a bookmark to the bookmarks list. + * Adds a history site * * @param {object} siteDetail - Properties for the siteDetail to add - * @param {string} tag - A site tag from js/constants/siteTags.js */ - this.app.client.addCommand('addBookmark', function (siteDetail, tag) { - logVerbose('addBookmark("' + siteDetail + '", "' + tag + '")') + this.app.client.addCommand('addHistorySite', function (siteDetail) { + logVerbose('addHistorySite("' + siteDetail + '")') let waitUrl = siteDetail.location if (isSourceAboutUrl(waitUrl)) { waitUrl = getTargetAboutUrl(waitUrl) } - return this.execute(function (siteDetail, tag) { - return devTools('appActions').addBookmark(siteDetail, tag) - }, siteDetail, tag).then((response) => response.value) - .waitForSiteEntry(waitUrl, false) + return this.execute(function (siteDetail) { + return devTools('appActions').addHistorySite(siteDetail) + }, siteDetail).then((response) => response.value) + .waitForHistoryEntry(waitUrl, false) + }) + + this.app.client.addCommand('waitForHistoryEntry', function (location, waitForTitle = true) { + logVerbose('waitForHistoryEntry("' + location + '", "' + waitForTitle + '")') + return this.waitUntil(function () { + return this.getAppState().then((val) => { + const ret = val.value && val.value.historySites && Array.from(Object.values(val.value.historySites)).find( + (site) => site.location === location && + (!waitForTitle || (waitForTitle && site.title))) + logVerbose('waitForHistoryEntry("' + location + ', ' + waitForTitle + '") => ' + ret) + return ret + }) + }, 5000, null, 100) }) /** - * Adds a list sites to the sites list, including bookmark and foler. + * Adds a bookmark folder * * @param {object} siteDetail - Properties for the siteDetail to add */ - this.app.client.addCommand('addSiteList', function (siteDetail) { - logVerbose('addSiteList("' + siteDetail + '")') + this.app.client.addCommand('addBookmarkFolder', function (siteDetail) { + logVerbose('addBookmarkFolder("' + siteDetail + '")') return this.execute(function (siteDetail) { - return devTools('appActions').addSite(siteDetail) + return devTools('appActions').addBookmarkFolder(siteDetail) }, siteDetail).then((response) => response.value) + .waitForBookmarkFolderEntry(siteDetail.folderId, false) + }) + + this.app.client.addCommand('waitForBookmarkFolderEntry', function (folderId, waitForTitle = true) { + logVerbose('waitForBookmarkFolderEntry("' + folderId + '", "' + waitForTitle + '")') + return this.waitUntil(function () { + return this.getAppState().then((val) => { + const ret = val.value && val.value.bookmarkFolders && Array.from(Object.values(val.value.bookmarkFolders)).find( + (folder) => folder.folderId === folderId && + (!waitForTitle || (waitForTitle && folder.title))) + logVerbose('waitForBookmarkFolderEntry("' + folderId + ', ' + waitForTitle + '") => ' + ret) + return ret + }) + }, 5000, null, 100) + }) + + /** + * Adds a list of bookmarks + * + * @param {object} bookmarkList - List of bookmarks to add + */ + // TODO fix + this.app.client.addCommand('addBookmarks', function (bookmarkList) { + logVerbose('addBookmarks("' + bookmarkList + '")') + return this.execute(function (bookmarkList) { + return devTools('appActions').addBookmark(bookmarkList) + }, bookmarkList).then((response) => response.value) + }) + + /** + * Adds a list of history sites + * + * @param {object} historyList - List of history sites to add + */ + this.app.client.addCommand('addHistorySites', function (historyList) { + logVerbose('addHistorySites("' + historyList + '")') + return this.execute(function (historyList) { + return devTools('appActions').addHistorySites(historyList) + }, historyList).then((response) => response.value) }) /** diff --git a/test/misc-components/syncTest.js b/test/misc-components/syncTest.js index 7f81c8e710b..af375e6e711 100644 --- a/test/misc-components/syncTest.js +++ b/test/misc-components/syncTest.js @@ -673,7 +673,7 @@ describe('Syncing history', function () { .tabByIndex(0) .loadUrl(this.page1Url) .windowParentByUrl(this.page1Url) - .waitForSiteEntry(this.page1Url) + .waitForHistoryEntry(this.page1Url) // For order: Visit page 2 yield Brave.app.client diff --git a/test/navbar-components/navigationBarTest.js b/test/navbar-components/navigationBarTest.js index 48e63e356bc..d54749830f8 100644 --- a/test/navbar-components/navigationBarTest.js +++ b/test/navbar-components/navigationBarTest.js @@ -1181,8 +1181,8 @@ describe('navigationBar tests', function () { .waitForExist(urlInput) .waitForElementFocus(urlInput) .waitForInputText(urlInput, '') - .addSite({ location: 'https://brave.com', title: 'Brave' }) - .waitForSiteEntry('https://brave.com') + .addHistorySite({ location: 'https://brave.com', title: 'Brave' }) + .waitForHistoryEntry('https://brave.com') .keys('br') yield selectsText(this.app.client, 'ave.com') yield blur(this.app.client) @@ -1261,7 +1261,7 @@ describe('navigationBar tests', function () { .url(page1Url) .waitForUrl(page1Url) .windowParentByUrl(page1Url) - .waitForSiteEntry(page1Url) + .waitForHistoryEntry(page1Url) .activateURLMode() .waitForExist(navigatorNotBookmarked) .click(navigatorNotBookmarked) diff --git a/test/navbar-components/urlBarSuggestionsTest.js b/test/navbar-components/urlBarSuggestionsTest.js index 52fa5cee56d..b8cfc3c9e27 100644 --- a/test/navbar-components/urlBarSuggestionsTest.js +++ b/test/navbar-components/urlBarSuggestionsTest.js @@ -24,11 +24,11 @@ describe('urlBarSuggestions', function () { .tabByIndex(0) .loadUrl(this.page1Url) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(this.page1Url) + .waitForHistoryEntry(this.page1Url) .tabByIndex(0) .loadUrl(this.page2Url) .windowByUrl(Brave.browserWindowUrl) - .waitForSiteEntry(this.page2Url) + .waitForHistoryEntry(this.page2Url) .newTab() .waitForUrl(Brave.newTabUrl) .windowByUrl(Brave.browserWindowUrl) diff --git a/test/navbar-components/urlBarTest.js b/test/navbar-components/urlBarTest.js index 6206086dff7..fb840bfe1ec 100644 --- a/test/navbar-components/urlBarTest.js +++ b/test/navbar-components/urlBarTest.js @@ -29,7 +29,7 @@ describe('urlBar tests', function () { yield this.app.client.waitForElementFocus(urlInput) yield this.app.client .onClearBrowsingData('browserHistory', true) - .addSite({ location: 'https://brave.com', title: 'Brave' }) + .addHistorySite({ location: 'https://brave.com', title: 'Brave' }) }) // OMG, Brad would hate this test! @@ -65,10 +65,10 @@ describe('urlBar tests', function () { yield this.app.client .onClearBrowsingData('browserHistory', true) - .addSite({ location: 'https://brave.com', title: 'Brave' }) - .addSite({ location: 'https://brave.com/test' }) - .addSite({ location: 'https://www.youtube.com' }) - .addSite({ location: 'http://uncrate.com' }) + .addHistorySite({ location: 'https://brave.com', title: 'Brave' }) + .addHistorySite({ location: 'https://brave.com/test' }) + .addHistorySite({ location: 'https://www.youtube.com' }) + .addHistorySite({ location: 'http://uncrate.com' }) }) describe('press backspace key', function () { @@ -150,7 +150,7 @@ describe('urlBar tests', function () { describe('typing prefix with characters from "https" prefix', function () { beforeEach(function * () { yield this.app.client - .addSite({ location: 'https://slo-tech.com', title: 'title' }) + .addHistorySite({ location: 'https://slo-tech.com', title: 'title' }) .setInputText(urlInput, '') .keys('s') }) @@ -163,7 +163,7 @@ describe('urlBar tests', function () { describe('typing with characters that do not match prefix should not select first item', function () { beforeEach(function * () { yield this.app.client - .addSite({ location: 'https://slo-tech.com', title: 'title' }) + .addHistorySite({ location: 'https://slo-tech.com', title: 'title' }) .setInputText(urlInput, '') .keys('o') }) @@ -176,7 +176,7 @@ describe('urlBar tests', function () { describe('typing with characters that do not match prefix should not select first item', function () { beforeEach(function * () { yield this.app.client - .addSite({ location: 'https://slo-tech.com', title: 'title' }) + .addHistorySite({ location: 'https://slo-tech.com', title: 'title' }) .setInputText(urlInput, '') .keys('o') }) @@ -378,12 +378,12 @@ describe('urlBar tests', function () { it('typing in the urlbar should override mouse hover for suggestions', function * () { yield this.app.client - .addSite({ location: 'https://brave.com', title: 'Brave' }) - .addSite({ location: 'https://brave.com/test' }) - .addSite({ location: 'https://brave.com/test2' }) - .addSite({ location: 'https://brave.com/test3' }) - .addSite({ location: 'https://brave.com/test4' }) - .addSite({ location: 'https://brianbondy.com/test4' }) + .addHistorySite({ location: 'https://brave.com', title: 'Brave' }) + .addHistorySite({ location: 'https://brave.com/test' }) + .addHistorySite({ location: 'https://brave.com/test2' }) + .addHistorySite({ location: 'https://brave.com/test3' }) + .addHistorySite({ location: 'https://brave.com/test4' }) + .addHistorySite({ location: 'https://brianbondy.com/test4' }) .resizeWindow(500, 300) .setValue(urlInput, 'b') .waitForVisible(urlBarSuggestions) @@ -688,7 +688,7 @@ describe('urlBar tests', function () { yield this.app.client.waitForExist(urlInput) yield this.app.client.waitForElementFocus(urlInput) yield this.app.client - .addSiteList(sites) + .addHistorySites(sites) .waitForInputText(urlInput, '') .windowByUrl(Brave.browserWindowUrl) .newTab() @@ -763,8 +763,8 @@ describe('urlBar tests', function () { yield this.app.client.waitForElementFocus(urlInput) yield this.app.client .onClearBrowsingData('browserHistory', true) - .addSite({ location: 'https://github.com/brave/browser-laptop', title: 'browser-laptop' }) - .addSite({ location: 'https://github.com/brave/ad-block', title: 'Muon' }) + .addHistorySite({ location: 'https://github.com/brave/browser-laptop', title: 'browser-laptop' }) + .addHistorySite({ location: 'https://github.com/brave/ad-block', title: 'Muon' }) }) it('changes only the selection', function * () { diff --git a/test/unit/app/common/lib/pinnedSitesUtilTest.js b/test/unit/app/common/lib/pinnedSitesUtilTest.js new file mode 100644 index 00000000000..e2ef0ffeedf --- /dev/null +++ b/test/unit/app/common/lib/pinnedSitesUtilTest.js @@ -0,0 +1,39 @@ +/* global describe, it */ +const pinnedSitesUtil = require('../../../../../app/common/lib/pinnedSitesUtil') +const assert = require('assert') +const Immutable = require('immutable') + +require('../../../braveUnit') + +describe('pinnedSitesUtil', () => { + const location = 'https://css-tricks.com/' + const order = 9 + const partitionNumber = 5 + const expectedSiteProps = Immutable.fromJS({ + location, + order, + partitionNumber + }) + + let site = Immutable.fromJS({ + favicon: 'https://css-tricks.com/favicon.ico', + lastAccessedTime: 1493560182224, + location: location, + order: order, + partitionNumber: partitionNumber, + title: 'CSS-Tricks' + }) + + describe('getPinnedSiteProps', () => { + it('returns object with necessary fields', () => { + const result = pinnedSitesUtil.getPinnedSiteProps(site) + assert.deepEqual(expectedSiteProps, result) + }) + + it('set partitionNumber field to 0 in case of missing this field', () => { + const newSite = site.delete('partitionNumber') + const result = pinnedSitesUtil.getPinnedSiteProps(newSite) + assert.equal(0, result.get('partitionNumber')) + }) + }) +}) diff --git a/test/unit/app/common/lib/windowsUtilTest.js b/test/unit/app/common/lib/windowsUtilTest.js deleted file mode 100644 index 60ece75b82b..00000000000 --- a/test/unit/app/common/lib/windowsUtilTest.js +++ /dev/null @@ -1,40 +0,0 @@ -/* global describe, beforeEach, it */ -const windowsUtil = require('../../../../../app/common/lib/windowsUtil') -const assert = require('assert') -const Immutable = require('immutable') - -require('../../../braveUnit') - -describe('windowsUtil', () => { - const location = 'https://css-tricks.com/' - const order = 9 - const partitionNumber = 5 - const expectedSiteProps = Immutable.fromJS({ - location, - order, - partitionNumber - }) - let site - - describe('getPinnedSiteProps', () => { - beforeEach(() => { - site = Immutable.fromJS({ - favicon: 'https://css-tricks.com/favicon.ico', - lastAccessedTime: 1493560182224, - location: location, - order: order, - partitionNumber: partitionNumber, - title: 'CSS-Tricks' - }) - }) - it('returns object with necessary fields', () => { - const result = windowsUtil.getPinnedSiteProps(site) - assert.deepEqual(expectedSiteProps, result) - }) - it('set partitionNumber field to 0 in case of missing this field', () => { - site = site.delete('partitionNumber') - const result = windowsUtil.getPinnedSiteProps(site) - assert.equal(0, result.get('partitionNumber')) - }) - }) -}) diff --git a/test/unit/state/siteUtilTest.js b/test/unit/state/siteUtilTest.js index f45653f3ca0..0114b9feaf8 100644 --- a/test/unit/state/siteUtilTest.js +++ b/test/unit/state/siteUtilTest.js @@ -1335,67 +1335,6 @@ describe('siteUtil', function () { }) }) - describe('isEquivalent', function () { - it('returns true if both siteDetail objects are identical', function () { - const siteDetail1 = Immutable.fromJS({ - location: testUrl1, - partitionNumber: 0, - tags: [siteTags.BOOKMARK] - }) - const siteDetail2 = Immutable.fromJS({ - location: testUrl1, - partitionNumber: 0, - tags: [siteTags.BOOKMARK] - }) - assert.equal(siteUtil.isEquivalent(siteDetail1, siteDetail2), true) - }) - it('returns false if one object is a folder and the other is not', function () { - const siteDetail1 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK] - }) - const siteDetail2 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK_FOLDER], - folderId: 1 - }) - assert.equal(siteUtil.isEquivalent(siteDetail1, siteDetail2), false) - }) - it('returns false if both are folders and have a different folderId', function () { - const siteDetail1 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK_FOLDER], - folderId: 0 - }) - const siteDetail2 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK_FOLDER], - folderId: 1 - }) - assert.equal(siteUtil.isEquivalent(siteDetail1, siteDetail2), false) - }) - it('returns false if both are bookmarks and have a different location', function () { - const siteDetail1 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK], - location: testUrl1 - }) - const siteDetail2 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK], - location: 'http://example.com/' - }) - assert.equal(siteUtil.isEquivalent(siteDetail1, siteDetail2), false) - }) - it('returns false if both are bookmarks and have a different partitionNumber', function () { - const siteDetail1 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK], - location: testUrl1, - partitionNumber: 0 - }) - const siteDetail2 = Immutable.fromJS({ - tags: [siteTags.BOOKMARK], - location: testUrl2, - partitionNumber: 1 - }) - assert.equal(siteUtil.isEquivalent(siteDetail1, siteDetail2), false) - }) - }) - describe('isFolder', function () { it('returns true if the input is a siteDetail and has a `BOOKMARK_FOLDER` tag and a folder ID', function () { const siteDetail = Immutable.fromJS({ @@ -1683,20 +1622,4 @@ describe('siteUtil', function () { assert.strictEqual(siteUtil.getOrigin('http://http/test'), 'http://http') }) }) - describe('isPinnedTab', function () { - it('detects pinned tab site', function () { - assert.strictEqual(siteUtil.isPinnedTab(siteTags.PINNED), true) - assert.strictEqual(siteUtil.isPinnedTab([siteTags.PINNED]), true) - }) - it('detects not pinned for no site tags', function () { - assert.strictEqual(siteUtil.isPinnedTab([]), false) - }) - it('detects not pinned for site tags which are not PINNED', function () { - assert.strictEqual(siteUtil.isPinnedTab(siteTags.BOOKMARK), false) - assert.strictEqual(siteUtil.isPinnedTab([siteTags.BOOKMARK]), false) - }) - it('detects pinned when bookmarked and pinned', function () { - assert.strictEqual(siteUtil.isPinnedTab([siteTags.PINNED, siteTags.BOOKMARK]), true) - }) - }) })