diff options
Diffstat (limited to 'src/background/shared')
-rw-r--r-- | src/background/shared/bookmarks.js | 9 | ||||
-rw-r--r-- | src/background/shared/completions/bookmarks.js | 15 | ||||
-rw-r--r-- | src/background/shared/completions/histories.js | 83 | ||||
-rw-r--r-- | src/background/shared/completions/index.js | 129 | ||||
-rw-r--r-- | src/background/shared/completions/tabs.js | 8 | ||||
-rw-r--r-- | src/background/shared/indicators.js | 13 | ||||
-rw-r--r-- | src/background/shared/tabs.js | 173 | ||||
-rw-r--r-- | src/background/shared/zooms.js | 38 |
8 files changed, 468 insertions, 0 deletions
diff --git a/src/background/shared/bookmarks.js b/src/background/shared/bookmarks.js new file mode 100644 index 0000000..5e7927b --- /dev/null +++ b/src/background/shared/bookmarks.js @@ -0,0 +1,9 @@ +const create = (title, url) => { + return browser.bookmarks.create({ + type: 'bookmark', + title, + url, + }); +}; + +export { create }; diff --git a/src/background/shared/completions/bookmarks.js b/src/background/shared/completions/bookmarks.js new file mode 100644 index 0000000..1adb350 --- /dev/null +++ b/src/background/shared/completions/bookmarks.js @@ -0,0 +1,15 @@ +const getCompletions = (keywords) => { + return browser.bookmarks.search({ query: keywords }).then((items) => { + return items.filter((item) => { + let url = undefined; + try { + url = new URL(item.url); + } catch (e) { + return false; + } + return item.type === 'bookmark' && url.protocol !== 'place:'; + }).slice(0, 10); + }); +}; + +export { getCompletions }; diff --git a/src/background/shared/completions/histories.js b/src/background/shared/completions/histories.js new file mode 100644 index 0000000..a7d3d47 --- /dev/null +++ b/src/background/shared/completions/histories.js @@ -0,0 +1,83 @@ +const filterHttp = (items) => { + const httpsHosts = items + .filter(item => item[1].protocol === 'https:') + .map(item => item[1].host); + const httpsHostSet = new Set(httpsHosts); + return items.filter( + item => !(item[1].protocol === 'http:' && httpsHostSet.has(item[1].host)) + ); +}; + +const filterEmptyTitle = (items) => { + return items.filter(item => item[0].title && item[0].title !== ''); +}; + +const filterClosedPath = (items) => { + const allSimplePaths = items + .filter(item => item[1].hash === '' && item[1].search === '') + .map(item => item[1].origin + item[1].pathname); + const allSimplePathSet = new Set(allSimplePaths); + return items.filter( + item => !(item[1].hash === '' && item[1].search === '' && + (/\/$/).test(item[1].pathname) && + allSimplePathSet.has( + (item[1].origin + item[1].pathname).replace(/\/$/, '') + ) + ) + ); +}; + +const reduceByPathname = (items, min) => { + let hash = {}; + for (let item of items) { + let pathname = item[1].origin + item[1].pathname; + if (!hash[pathname]) { + hash[pathname] = item; + } else if (hash[pathname][1].href.length > item[1].href.length) { + hash[pathname] = item; + } + } + let filtered = Object.values(hash); + if (filtered.length < min) { + return items; + } + return filtered; +}; + +const reduceByOrigin = (items, min) => { + let hash = {}; + for (let item of items) { + let origin = item[1].origin; + if (!hash[origin]) { + hash[origin] = item; + } else if (hash[origin][1].href.length > item[1].href.length) { + hash[origin] = item; + } + } + let filtered = Object.values(hash); + if (filtered.length < min) { + return items; + } + return filtered; +}; + +const getCompletions = (keyword) => { + return browser.history.search({ + text: keyword, + startTime: 0, + }).then((historyItems) => { + return [historyItems.map(item => [item, new URL(item.url)])] + .map(filterEmptyTitle) + .map(filterHttp) + .map(filterClosedPath) + .map(items => reduceByPathname(items, 10)) + .map(items => reduceByOrigin(items, 10)) + .map(items => items + .sort((x, y) => x[0].visitCount < y[0].visitCount) + .slice(0, 10) + .map(item => item[0]) + )[0]; + }); +}; + +export { getCompletions }; diff --git a/src/background/shared/completions/index.js b/src/background/shared/completions/index.js new file mode 100644 index 0000000..728cee7 --- /dev/null +++ b/src/background/shared/completions/index.js @@ -0,0 +1,129 @@ +import * as tabs from './tabs'; +import * as histories from './histories'; +import * as bookmarks from './bookmarks'; + +const getSearchCompletions = (command, keywords, searchConfig) => { + let engineNames = Object.keys(searchConfig.engines); + let engineItems = engineNames.filter(name => name.startsWith(keywords)) + .map(name => ({ + caption: name, + content: command + ' ' + name + })); + return Promise.resolve(engineItems); +}; + +const getHistoryCompletions = (command, keywords) => { + return histories.getCompletions(keywords).then((pages) => { + return pages.map((page) => { + return { + caption: page.title, + content: command + ' ' + page.url, + url: page.url + }; + }); + }); +}; + +const getBookmarksCompletions = (command, keywords) => { + return bookmarks.getCompletions(keywords).then((items) => { + return items.map((item) => { + return { + caption: item.title, + content: command + ' ' + item.url, + url: item.url, + }; + }); + }); +}; + +const getOpenCompletions = (command, keywords, searchConfig) => { + return Promise.all([ + getSearchCompletions(command, keywords, searchConfig), + getHistoryCompletions(command, keywords), + getBookmarksCompletions(command, keywords), + ]).then(([engineItems, historyItems, bookmarkItems]) => { + let completions = []; + if (engineItems.length > 0) { + completions.push({ + name: 'Search Engines', + items: engineItems + }); + } + if (historyItems.length > 0) { + completions.push({ + name: 'History', + items: historyItems + }); + } + if (bookmarkItems.length > 0) { + completions.push({ + name: 'Bookmarks', + items: bookmarkItems + }); + } + return completions; + }); +}; + +const getBufferCompletions = (command, keywords, excludePinned) => { + return tabs.getCompletions(keywords, excludePinned).then((got) => { + let items = got.map((tab) => { + return { + caption: tab.title, + content: command + ' ' + tab.title, + url: tab.url, + icon: tab.favIconUrl + }; + }); + return [ + { + name: 'Buffers', + items: items + } + ]; + }); +}; + +const getCompletions = (line, settings) => { + let typedWords = line.trim().split(/ +/); + let typing = ''; + if (!line.endsWith(' ')) { + typing = typedWords.pop(); + } + + if (typedWords.length === 0) { + return Promise.resolve([]); + } + let name = typedWords.shift(); + let keywords = typedWords.concat(typing).join(' '); + + switch (name) { + case 'o': + case 'open': + case 't': + case 'tabopen': + case 'w': + case 'winopen': + return getOpenCompletions(name, keywords, settings.search); + case 'b': + case 'buffer': + return getBufferCompletions(name, keywords, false); + case 'bd!': + case 'bdel!': + case 'bdelete!': + case 'bdeletes!': + return getBufferCompletions(name, keywords, false); + case 'bd': + case 'bdel': + case 'bdelete': + case 'bdeletes': + return getBufferCompletions(name, keywords, true); + } + return Promise.resolve([]); +}; + +const complete = (line, settings) => { + return getCompletions(line, settings); +}; + +export { complete }; diff --git a/src/background/shared/completions/tabs.js b/src/background/shared/completions/tabs.js new file mode 100644 index 0000000..bdb2741 --- /dev/null +++ b/src/background/shared/completions/tabs.js @@ -0,0 +1,8 @@ +import * as tabs from '../tabs'; + +const getCompletions = (keyword, excludePinned) => { + return tabs.queryByKeyword(keyword, excludePinned); +}; + + +export { getCompletions }; diff --git a/src/background/shared/indicators.js b/src/background/shared/indicators.js new file mode 100644 index 0000000..74002c4 --- /dev/null +++ b/src/background/shared/indicators.js @@ -0,0 +1,13 @@ +const enable = () => { + return browser.browserAction.setIcon({ + path: 'resources/enabled_32x32.png', + }); +}; + +const disable = () => { + return browser.browserAction.setIcon({ + path: 'resources/disabled_32x32.png', + }); +}; + +export { enable, disable }; diff --git a/src/background/shared/tabs.js b/src/background/shared/tabs.js new file mode 100644 index 0000000..62e26ac --- /dev/null +++ b/src/background/shared/tabs.js @@ -0,0 +1,173 @@ +import * as tabCompletions from './completions/tabs'; + +const closeTab = (id) => { + return browser.tabs.get(id).then((tab) => { + if (!tab.pinned) { + return browser.tabs.remove(id); + } + }); +}; + +const closeTabForce = (id) => { + return browser.tabs.remove(id); +}; + +const queryByKeyword = (keyword, excludePinned = false) => { + return browser.tabs.query({ currentWindow: true }).then((tabs) => { + return tabs.filter((t) => { + return t.url.toLowerCase().includes(keyword.toLowerCase()) || + t.title && t.title.toLowerCase().includes(keyword.toLowerCase()); + }).filter((t) => { + return !(excludePinned && t.pinned); + }); + }); +}; + +const closeTabByKeywords = (keyword) => { + return queryByKeyword(keyword, false).then((tabs) => { + if (tabs.length === 0) { + throw new Error('No matching buffer for ' + keyword); + } else if (tabs.length > 1) { + throw new Error('More than one match for ' + keyword); + } + browser.tabs.remove(tabs[0].id); + }); +}; + +const closeTabByKeywordsForce = (keyword) => { + return queryByKeyword(keyword, true).then((tabs) => { + if (tabs.length === 0) { + throw new Error('No matching buffer for ' + keyword); + } else if (tabs.length > 1) { + throw new Error('More than one match for ' + keyword); + } + browser.tabs.remove(tabs[0].id); + }); +}; + +const closeTabsByKeywords = (keyword) => { + tabCompletions.getCompletions(keyword).then((tabs) => { + let tabs2 = tabs.filter(tab => !tab.pinned); + browser.tabs.remove(tabs2.map(tab => tab.id)); + }); +}; + +const closeTabsByKeywordsForce = (keyword) => { + tabCompletions.getCompletions(keyword).then((tabs) => { + browser.tabs.remove(tabs.map(tab => tab.id)); + }); +}; + +const reopenTab = () => { + return browser.sessions.getRecentlyClosed({ + maxResults: 1 + }).then((sessions) => { + if (sessions.length === 0) { + return; + } + let session = sessions[0]; + if (session.tab) { + return browser.sessions.restore(session.tab.sessionId); + } + return browser.sessions.restore(session.window.sessionId); + }); +}; + +const selectAt = (index) => { + return browser.tabs.query({ currentWindow: true }).then((tabs) => { + if (tabs.length < 2) { + return; + } + if (index < 0 || tabs.length <= index) { + throw new RangeError(`tab ${index + 1} does not exist`); + } + let id = tabs[index].id; + return browser.tabs.update(id, { active: true }); + }); +}; + +const selectByKeyword = (current, keyword) => { + return queryByKeyword(keyword).then((tabs) => { + if (tabs.length === 0) { + throw new RangeError('No matching buffer for ' + keyword); + } + for (let tab of tabs) { + if (tab.index > current.index) { + return browser.tabs.update(tab.id, { active: true }); + } + } + return browser.tabs.update(tabs[0].id, { active: true }); + }); +}; + +const selectPrevTab = (current, count) => { + return browser.tabs.query({ currentWindow: true }).then((tabs) => { + if (tabs.length < 2) { + return; + } + let select = (current - count + tabs.length) % tabs.length; + let id = tabs[select].id; + return browser.tabs.update(id, { active: true }); + }); +}; + +const selectNextTab = (current, count) => { + return browser.tabs.query({ currentWindow: true }).then((tabs) => { + if (tabs.length < 2) { + return; + } + let select = (current + count) % tabs.length; + let id = tabs[select].id; + return browser.tabs.update(id, { active: true }); + }); +}; + +const selectFirstTab = () => { + return browser.tabs.query({ currentWindow: true }).then((tabs) => { + let id = tabs[0].id; + return browser.tabs.update(id, { active: true }); + }); +}; + +const selectLastTab = () => { + return browser.tabs.query({ currentWindow: true }).then((tabs) => { + let id = tabs[tabs.length - 1].id; + return browser.tabs.update(id, { active: true }); + }); +}; + +const selectTab = (id) => { + return browser.tabs.update(id, { active: true }); +}; + +const reload = (current, cache) => { + return browser.tabs.reload( + current.id, + { bypassCache: cache } + ); +}; + +const updateTabPinned = (current, pinned) => { + return browser.tabs.query({ currentWindow: true, active: true }) + .then(() => { + return browser.tabs.update(current.id, { pinned: pinned }); + }); +}; + +const toggleTabPinned = (current) => { + updateTabPinned(current, !current.pinned); +}; + +const duplicate = (id) => { + return browser.tabs.duplicate(id); +}; + +export { + closeTab, closeTabForce, + queryByKeyword, closeTabByKeywords, closeTabByKeywordsForce, + closeTabsByKeywords, closeTabsByKeywordsForce, + reopenTab, selectAt, selectByKeyword, + selectPrevTab, selectNextTab, selectFirstTab, + selectLastTab, selectTab, reload, updateTabPinned, + toggleTabPinned, duplicate +}; diff --git a/src/background/shared/zooms.js b/src/background/shared/zooms.js new file mode 100644 index 0000000..e3e2aa6 --- /dev/null +++ b/src/background/shared/zooms.js @@ -0,0 +1,38 @@ +// For chromium +// const ZOOM_SETTINGS = [ +// 0.25, 0.33, 0.50, 0.66, 0.75, 0.80, 0.90, 1.00, +// 1.10, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00, 4.00, 5.00 +// ]; + +const ZOOM_SETTINGS = [ + 0.33, 0.50, 0.66, 0.75, 0.80, 0.90, 1.00, + 1.10, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00 +]; + +const zoomIn = (tabId = undefined) => { + return browser.tabs.getZoom(tabId).then((factor) => { + for (let f of ZOOM_SETTINGS) { + if (f > factor) { + browser.tabs.setZoom(tabId, f); + break; + } + } + }); +}; + +const zoomOut = (tabId = undefined) => { + return browser.tabs.getZoom(tabId).then((factor) => { + for (let f of [].concat(ZOOM_SETTINGS).reverse()) { + if (f < factor) { + browser.tabs.setZoom(tabId, f); + break; + } + } + }); +}; + +const neutral = (tabId = undefined) => { + return browser.tabs.setZoom(tabId, 1); +}; + +export { zoomIn, zoomOut, neutral }; |