diff --git a/src/common/constants.js b/src/common/constants.js index 3279074..4024b37 100644 --- a/src/common/constants.js +++ b/src/common/constants.js @@ -1,3 +1,5 @@ +export const ILLEGAL_URLS = ['about:', 'chrome:', 'file:', 'wss:'] + export const PICKED_TAB_PROPS = ['url', 'title', 'favIconUrl', 'pinned'] export const PICKED_LIST_RPOPS = ['_id', 'tabs', 'title', 'time', 'pinned', 'expand', 'color'] export const SYNCED_LIST_PROPS = ['_id', 'tabs', 'title', 'time', 'pinned', 'color'] diff --git a/src/common/list.js b/src/common/list.js index 35cee40..947ac3f 100644 --- a/src/common/list.js +++ b/src/common/list.js @@ -1,13 +1,11 @@ import _ from 'lodash' -import { - PICKED_TAB_PROPS, - PICKED_LIST_RPOPS, -} from './constants' import {genObjectId} from './utils' +import {normalizeTab} from './tab' +import {PICKED_LIST_RPOPS} from './constants' export const createNewTabList = ({tabs, title, time}) => ({ _id: genObjectId(), - tabs: tabs || [], + tabs: Array.isArray(tabs) ? tabs.map(normalizeTab) : [], title: title || '', time: time || Date.now(), titleEditing: false, @@ -17,10 +15,10 @@ export const createNewTabList = ({tabs, title, time}) => ({ }) // Preserving the needed properties before store lists. -export const normalize = list => { +export const normalizeList = list => { const normalizedList = _.pick(list, PICKED_LIST_RPOPS) - normalizedList.tabs = normalizedList.tabs.map(tab => _.pick(tab, PICKED_TAB_PROPS)) + normalizedList.tabs = normalizedList.tabs.map(normalizeTab) return normalizedList } -export default {createNewTabList, normalize} +export default {createNewTabList, normalizeList} diff --git a/src/common/listManager.js b/src/common/listManager.js index 9147c60..8331f03 100644 --- a/src/common/listManager.js +++ b/src/common/listManager.js @@ -5,6 +5,7 @@ import { END_FRONT, END_BACKGROUND, } from './constants' +import {isBackground} from './utils' const cache = { lists: null, ops: null } const getStorage = async () => { @@ -87,32 +88,33 @@ manager.modifiers = { return true }, } +const applyChangesToStorage = async (method, args) => { + const {lists, ops} = await getStorage() + if (manager.modifiers[method](lists, args)) ops.push({method: name, args, time: Date.now()}) + saveStorage() +} const addEventListener = (receiveFrom, callback) => browser.runtime.onMessage.addListener(({listModifed, from}) => { - if (receiveFrom !== from) return - if (!listModifed) return + if (receiveFrom !== from || !listModifed) return const {method, args} = listModifed return callback(method, args) }) const genMethods = isBackground => { - for (const [name, func] of Object.entries(manager.modifiers)) { - manager[name] = isBackground ? async (...args) => { // for background - console.debug('[list manager] modify list:', name, ...args) - const {lists, ops} = await getStorage() - if (func(lists, args)) ops.push({method: name, args, time: Date.now()}) - saveStorage() - await browser.runtime.sendMessage({listModifed: {method: name, args}, from: END_BACKGROUND}) + Object.keys(manager.modifiers).forEach(method => { + manager[method] = isBackground ? async (...args) => { // for background + console.debug('[list manager] modify list:', method, ...args) + await applyChangesToStorage(method, args) + await browser.runtime.sendMessage({listModifed: {method, args}, from: END_BACKGROUND}) } : async (...args) => { // for front end console.debug('[list manager] call to modify list:', name, ...args) - await browser.runtime.sendMessage({listModifed: {method: name, args}, from: END_FRONT}) + await browser.runtime.sendMessage({listModifed: {method, args}, from: END_FRONT}) } - } + }) } manager.init = async () => { if (manager.inited) return - const background = await browser.runtime.getBackgroundPage() - const isBackground = window === background - if (isBackground) await addEventListener(END_FRONT, (method, args) => manager[method](...args)) - genMethods(isBackground) + const _isBackground = await isBackground() + if (_isBackground) await addEventListener(END_FRONT, applyChangesToStorage) + genMethods(_isBackground) manager.inited = true } const receiver = [] diff --git a/src/common/migrate.js b/src/common/migrate.js index 62381e3..995a22e 100644 --- a/src/common/migrate.js +++ b/src/common/migrate.js @@ -1,5 +1,5 @@ import _ from 'lodash' -import {normalize} from './list' +import {normalizeList} from './list' import options from './options' import {genObjectId} from './utils' import listManager from './listManager' @@ -12,7 +12,7 @@ const migrate = async () => { if (version >= '1.4.0') return // every list need an ID const {lists} = await browser.storage.local.get('lists') - const {0: listsWithoutId, 1: listsWithId} = _.groupBy(lists.map(normalize), list => +!!list._id) + const {0: listsWithoutId, 1: listsWithId} = _.groupBy(lists.map(normalizeList), list => +!!list._id) await browser.storage.local.set({lists: listsWithId}) for (const list of listsWithoutId.reverse()) { diff --git a/src/common/service/boss.js b/src/common/service/boss.js index 76fdc34..334712c 100644 --- a/src/common/service/boss.js +++ b/src/common/service/boss.js @@ -1,8 +1,8 @@ -import _ from 'lodash' import { TOKEN_KEY, AUTH_HEADER, } from '../constants' +import {isBackground} from '../utils' import browser from 'webextension-polyfill' const apiUrl = 'http://127.0.0.1:3000' @@ -172,8 +172,18 @@ const login = async token => { await refresh() } +const initTimer = async () => { + if (window._timerInited || !await isBackground()) return + window._timerInited = true + window.setInterval(() => { + if (!navigator.onLine) return + refresh() + }, 60000) +} + export default { hasToken, refresh, login, + initTimer, } diff --git a/src/common/storage.js b/src/common/storage.js index 5e2c1d4..ef8498e 100644 --- a/src/common/storage.js +++ b/src/common/storage.js @@ -1,19 +1,17 @@ import _ from 'lodash' -import list from '@/common/list' +import {normalizeList} from '@/common/list' import browser from 'webextension-polyfill' const get = key => browser.storage.local.get(key) -const set = obj => { - return browser.storage.local.set(obj) -} +const set = obj => browser.storage.local.set(obj) const getLists = () => get('lists') .then(({lists}) => lists || []) const setLists = async lists => { if (!Array.isArray(lists)) throw new TypeError(lists) - const handledLists = lists.filter(i => Array.isArray(i.tabs)).map(list.normalize) + const handledLists = lists.filter(i => Array.isArray(i.tabs)).map(normalizeList) const {opts} = await get('opts') if (opts && opts.removeDuplicate) { handledLists.forEach(list => { diff --git a/src/common/tab.js b/src/common/tab.js new file mode 100644 index 0000000..8fc9163 --- /dev/null +++ b/src/common/tab.js @@ -0,0 +1,9 @@ +import _ from 'lodash' +import {PICKED_TAB_PROPS} from './constants' + +// it also be applied for the browser Tab object +export const normalizeTab = tab => { + const normalizedTab = _.pick(tab, PICKED_TAB_PROPS) + normalizedTab.muted = normalizedTab.muted || tab.mutedInfo && tab.mutedInfo.muted + return normalizedTab +} diff --git a/src/common/tabs.js b/src/common/tabs.js index 09174f8..e78786c 100644 --- a/src/common/tabs.js +++ b/src/common/tabs.js @@ -3,13 +3,8 @@ import {createNewTabList} from './list' import _ from 'lodash' import browser from 'webextension-polyfill' import listManager from './listManager' -import {PICKED_TAB_PROPS} from './constants' +import {ILLEGAL_URLS} from './constants' listManager.init() -const pickTabs = tabs => tabs.map(tab => { - const pickedTab = _.pick(tab, PICKED_TAB_PROPS) - pickedTab.muted = tab.mutedInfo && tab.mutedInfo.muted - return pickedTab -}) const getAllInWindow = windowId => browser.tabs.query({windowId}) @@ -60,9 +55,7 @@ const groupTabsInCurrentWindow = async () => { return result } -const isLegalURL = url => [ - 'about:', 'chrome:', 'file:', 'wss:' -].every(prefix => !url.startsWith(prefix)) +const isLegalURL = url => ILLEGAL_URLS.every(prefix => !url.startsWith(prefix)) const storeTabs = async (tabs, listIndex) => { const appUrl = browser.runtime.getURL('') @@ -74,7 +67,7 @@ const storeTabs = async (tabs, listIndex) => { browser.tabs.remove(tabs.map(i => i.id)) const lists = await storage.getLists() if (listIndex == null) { - const newList = createNewTabList({tabs: pickTabs(tabs)}) + const newList = createNewTabList({tabs}) if (opts.pinNewList) newList.pinned = true lists.unshift(newList) await listManager.addList(newList) diff --git a/src/common/utils.js b/src/common/utils.js index 0cc0ca9..6585eff 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -39,3 +39,7 @@ export const genObjectId = () => { const timestamp = (new Date().getTime() / 1000 | 0).toString(16) return timestamp + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, () => (Math.random() * 16 | 0).toString(16)).toLowerCase() } +export const isBackground = async () => { + if (window._isBackground != null) window._isBackground = window === await browser.runtime.getBackgroundPage() + return window._isBackground +} diff --git a/src/component/main/Toolbar.vue b/src/component/main/Toolbar.vue index 356d25c..ef9c980 100644 --- a/src/component/main/Toolbar.vue +++ b/src/component/main/Toolbar.vue @@ -72,8 +72,8 @@ export default { ...mapMutations(['setConflict']), init() { this.onScroll() - document.addEventListener('online', () => this.online = true) - document.addEventListener('offline', () => this.online = false) + window.addEventListener('online', () => this.online = true) + window.addEventListener('offline', () => this.online = false) chrome.runtime.onMessage.addListener(msg => { console.log(msg) if (msg.refresh) { diff --git a/src/page/main/DetailList.vue b/src/page/main/DetailList.vue index 0503981..5cfda95 100644 --- a/src/page/main/DetailList.vue +++ b/src/page/main/DetailList.vue @@ -251,7 +251,8 @@ import draggable from 'vuedraggable' import __ from '@/common/i18n' import tabs from '@/common/tabs' -import list from '@/common/list' +import {normalizeTab} from '@/common/tab' +import {createNewTabList} from '@/common/list' import storage from '@/common/storage' import listManager from '@/common/listManager' import {formatTime} from '@/common/utils' @@ -332,13 +333,13 @@ export default { this.expandList(newStatus[index], index) }, tabMoved(changedLists) { + // judge last list firstly in order to avoid list index changed _.uniq(changedLists).sort((a, b) => b - a).forEach(listIndex => { const list = this.lists[listIndex] + console.log('tab moved', list) if (list.tabs.length === 0) this.removeList(listIndex) else listManager.updateListById(list._id, _.pick(list, 'tabs')) }) - // this.storeLists() - }, async getLists() { const lists = await storage.getLists() @@ -364,11 +365,6 @@ export default { updateExpandStatus() { this.$refs.panel.updateFromValue(this.expandStatus) }, - storeLists: _.debounce(async function storeLists() { - console.time('store') - await storage.setLists(this.lists) // eslint-disable-line - console.timeEnd('store') - }, 2000), removeList(listIndex) { const list = this.lists[listIndex] if ((list.tabs.length !== 0) && this.opts.alertRemoveList && !confirm(`${__('ui_remove_list')}: @@ -376,14 +372,12 @@ export default { ${__('ui_created')} ${formatTime(list.time)}`)) return this.lists.splice(listIndex, 1) listManager.removeListById(list._id) - // this.storeLists() }, removeTab(listIndex, tabIndex) { const list = this.lists[listIndex] list.tabs.splice(tabIndex, 1) if (this.lists[listIndex].tabs.length === 0) this.removeList(listIndex) listManager.updateListById(this.lists[listIndex]._id, _.pick(list, 'tabs')) - // this.storeLists() }, openTab(listIndex, tabIndex) { tabs.openTab(this.lists[listIndex].tabs[tabIndex]) @@ -400,7 +394,6 @@ export default { saveTitle(listIndex) { const list = this.lists[listIndex] this.$set(list, 'titleEditing', false) - // this.storeLists() listManager.updateListById(list._id, _.pick(list, 'title')) }, getDomain(url) { @@ -413,12 +406,10 @@ export default { pinList(listIndex, pin = true) { const list = this.lists[listIndex] list.pinned = pin - // this.storeLists() listManager.updateListById(list._id, _.pick(list, 'pinned')) }, swapListItem(a, b) { [this.lists[a], this.lists[b]] = [this.lists[b], this.lists[a]] - // this.storeLists() this.$forceUpdate() const list = this.lists[a] listManager.changeListOrderRelatively(list._id, b - a) @@ -435,14 +426,12 @@ export default { const list = this.lists[listIndex] if (list.expand === expand) return list.expand = expand - // this.storeLists() listManager.updateListById(list._id, _.pick(list, 'expand')) }, changeColor(listIndex, color) { const list = this.lists[listIndex] this.$set(list, 'color', color) list.color = color - // this.storeLists() listManager.updateListById(list._id, _.pick(list, 'color')) }, rightClicked(listIndex, tabIndex, $event) { @@ -479,23 +468,24 @@ export default { tabs.push(tab) }) if (targetListIndex === -1) { - this.lists.unshift(list.createNewTabList({tabs})) + const newList = createNewTabList({tabs}) + this.lists.unshift(newList) + listManager.addList(newList) + this.tabMoved(changedLists.map(i => i + 1)) // it will create a new list } else { - tabs.forEach(tab => this.lists[targetListIndex].tabs.push(tab)) + tabs.forEach(tab => this.lists[targetListIndex].tabs.push(normalizeTab(tab))) + this.tabMoved(changedLists) } - this.tabMoved(changedLists) }, duplicateSelectedItems() { const items = this.getSelectedItems() - console.log('item', items) if (!items) return const changedLists = [] items.forEach(({listIndex, tabIndex}) => { changedLists.push(listIndex) const tab = Object.assign({}, this.lists[listIndex].tabs[tabIndex]) - this.lists[listIndex].tabs.push(tab) + this.lists[listIndex].tabs.push(normalizeTab(tab)) }) - console.log('list', this.lists[changedLists[0]]) this.tabMoved(changedLists) }, async copyLinksOfSelectedItems() { @@ -519,10 +509,12 @@ export default { removeSelectedItems() { const items = this.getSelectedItems() if (!items) return + const changedLists = [] items.forEach(({listIndex, tabIndex}) => { + changedLists.push(listIndex) this.lists[listIndex].tabs.splice(tabIndex, 1) }) - this.tabMoved() + this.tabMoved(changedLists) }, showAll(listIndex) { this.lists[listIndex].showAll = true