Merge pull request #440 from ueokande/background-clean-architecture
Background clean architecturejh-changes
commit
ed2bd7d75e
74 changed files with 1648 additions and 1344 deletions
@ -1,147 +0,0 @@ |
|||||||
import actions from '../actions'; |
|
||||||
import * as consoleActions from './console'; |
|
||||||
import * as tabs from '../shared/tabs'; |
|
||||||
import * as bookmarks from '../shared/bookmarks'; |
|
||||||
import * as parsers from 'shared/commands/parsers'; |
|
||||||
import * as properties from 'shared/settings/properties'; |
|
||||||
|
|
||||||
const openCommand = async(url) => { |
|
||||||
let got = await browser.tabs.query({ |
|
||||||
active: true, currentWindow: true |
|
||||||
}); |
|
||||||
if (got.length > 0) { |
|
||||||
return browser.tabs.update(got[0].id, { url: url }); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const tabopenCommand = (url) => { |
|
||||||
return browser.tabs.create({ url: url }); |
|
||||||
}; |
|
||||||
|
|
||||||
const tabcloseCommand = async() => { |
|
||||||
let got = await browser.tabs.query({ |
|
||||||
active: true, currentWindow: true |
|
||||||
}); |
|
||||||
return browser.tabs.remove(got.map(tab => tab.id)); |
|
||||||
}; |
|
||||||
|
|
||||||
const tabcloseAllCommand = () => { |
|
||||||
return browser.tabs.query({ |
|
||||||
currentWindow: true |
|
||||||
}).then((tabList) => { |
|
||||||
return browser.tabs.remove(tabList.map(tab => tab.id)); |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
const winopenCommand = (url) => { |
|
||||||
return browser.windows.create({ url }); |
|
||||||
}; |
|
||||||
|
|
||||||
const bufferCommand = async(keywords) => { |
|
||||||
if (keywords.length === 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
let keywordsStr = keywords.join(' '); |
|
||||||
let got = await browser.tabs.query({ |
|
||||||
active: true, currentWindow: true |
|
||||||
}); |
|
||||||
if (got.length === 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
if (isNaN(keywordsStr)) { |
|
||||||
return tabs.selectByKeyword(got[0], keywordsStr); |
|
||||||
} |
|
||||||
let index = parseInt(keywordsStr, 10) - 1; |
|
||||||
return tabs.selectAt(index); |
|
||||||
}; |
|
||||||
|
|
||||||
const addbookmarkCommand = async(tab, args) => { |
|
||||||
if (!args[0]) { |
|
||||||
return { type: '' }; |
|
||||||
} |
|
||||||
let item = await bookmarks.create(args.join(' '), tab.url); |
|
||||||
if (!item) { |
|
||||||
return consoleActions.error(tab, 'Could not create a bookmark'); |
|
||||||
} |
|
||||||
return consoleActions.info(tab, 'Saved current page: ' + item.url); |
|
||||||
}; |
|
||||||
|
|
||||||
const setCommand = (args) => { |
|
||||||
if (!args[0]) { |
|
||||||
return { type: '' }; |
|
||||||
} |
|
||||||
|
|
||||||
let [name, value] = parsers.parseSetOption(args[0], properties.types); |
|
||||||
return { |
|
||||||
type: actions.SETTING_SET_PROPERTY, |
|
||||||
name, |
|
||||||
value |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
|
||||||
const doExec = async(tab, line, settings) => { |
|
||||||
let [name, args] = parsers.parseCommandLine(line); |
|
||||||
|
|
||||||
switch (name) { |
|
||||||
case 'o': |
|
||||||
case 'open': |
|
||||||
await openCommand(parsers.normalizeUrl(args, settings.search)); |
|
||||||
break; |
|
||||||
case 't': |
|
||||||
case 'tabopen': |
|
||||||
await tabopenCommand(parsers.normalizeUrl(args, settings.search)); |
|
||||||
break; |
|
||||||
case 'w': |
|
||||||
case 'winopen': |
|
||||||
await winopenCommand(parsers.normalizeUrl(args, settings.search)); |
|
||||||
break; |
|
||||||
case 'b': |
|
||||||
case 'buffer': |
|
||||||
await bufferCommand(args); |
|
||||||
break; |
|
||||||
case 'bd': |
|
||||||
case 'bdel': |
|
||||||
case 'bdelete': |
|
||||||
await tabs.closeTabByKeywords(args.join(' ')); |
|
||||||
break; |
|
||||||
case 'bd!': |
|
||||||
case 'bdel!': |
|
||||||
case 'bdelete!': |
|
||||||
await tabs.closeTabByKeywordsForce(args.join(' ')); |
|
||||||
break; |
|
||||||
case 'bdeletes': |
|
||||||
await tabs.closeTabsByKeywords(args.join(' ')); |
|
||||||
break; |
|
||||||
case 'bdeletes!': |
|
||||||
await tabs.closeTabsByKeywordsForce(args.join(' ')); |
|
||||||
break; |
|
||||||
case 'addbookmark': |
|
||||||
return addbookmarkCommand(tab, args); |
|
||||||
case 'set': |
|
||||||
return setCommand(args); |
|
||||||
case 'q': |
|
||||||
case 'quit': |
|
||||||
await tabcloseCommand(); |
|
||||||
break; |
|
||||||
case 'qa': |
|
||||||
case 'quitall': |
|
||||||
await tabcloseAllCommand(); |
|
||||||
break; |
|
||||||
default: |
|
||||||
return consoleActions.error(tab, name + ' command is not defined'); |
|
||||||
} |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
// eslint-disable-next-line complexity
|
|
||||||
const exec = async(tab, line, settings) => { |
|
||||||
try { |
|
||||||
let action = await doExec(tab, line, settings); |
|
||||||
return action; |
|
||||||
} catch (e) { |
|
||||||
return consoleActions.error(tab, e.toString()); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
export { exec }; |
|
@ -1,41 +0,0 @@ |
|||||||
import messages from 'shared/messages'; |
|
||||||
|
|
||||||
const error = async(tab, text) => { |
|
||||||
await browser.tabs.sendMessage(tab.id, { |
|
||||||
type: messages.CONSOLE_SHOW_ERROR, |
|
||||||
text, |
|
||||||
}); |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
const info = async(tab, text) => { |
|
||||||
await browser.tabs.sendMessage(tab.id, { |
|
||||||
type: messages.CONSOLE_SHOW_INFO, |
|
||||||
text, |
|
||||||
}); |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
const showCommand = async(tab, command) => { |
|
||||||
await browser.tabs.sendMessage(tab.id, { |
|
||||||
type: messages.CONSOLE_SHOW_COMMAND, |
|
||||||
command, |
|
||||||
}); |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
const showFind = async(tab) => { |
|
||||||
await browser.tabs.sendMessage(tab.id, { |
|
||||||
type: messages.CONSOLE_SHOW_FIND |
|
||||||
}); |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
const hide = async(tab) => { |
|
||||||
await browser.tabs.sendMessage(tab.id, { |
|
||||||
type: messages.CONSOLE_HIDE, |
|
||||||
}); |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
export { error, info, showCommand, showFind, hide }; |
|
@ -1,10 +0,0 @@ |
|||||||
import actions from './index'; |
|
||||||
|
|
||||||
const setKeyword = (keyword) => { |
|
||||||
return { |
|
||||||
type: actions.FIND_SET_KEYWORD, |
|
||||||
keyword, |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
export { setKeyword }; |
|
@ -1,11 +0,0 @@ |
|||||||
export default { |
|
||||||
// Settings
|
|
||||||
SETTING_SET_SETTINGS: 'setting.set.settings', |
|
||||||
SETTING_SET_PROPERTY: 'setting.set.property', |
|
||||||
|
|
||||||
// Find
|
|
||||||
FIND_SET_KEYWORD: 'find.set.keyword', |
|
||||||
|
|
||||||
// Tab
|
|
||||||
TAB_SELECTED: 'tab.selected', |
|
||||||
}; |
|
@ -1,20 +0,0 @@ |
|||||||
import actions from '../actions'; |
|
||||||
import * as settingsStorage from 'shared/settings/storage'; |
|
||||||
|
|
||||||
const load = async() => { |
|
||||||
let value = await settingsStorage.loadValue(); |
|
||||||
return { |
|
||||||
type: actions.SETTING_SET_SETTINGS, |
|
||||||
value, |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
const setProperty = (name, value) => { |
|
||||||
return { |
|
||||||
type: actions.SETTING_SET_PROPERTY, |
|
||||||
name, |
|
||||||
value, |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
export { load, setProperty }; |
|
@ -1,34 +0,0 @@ |
|||||||
import actions from './index'; |
|
||||||
|
|
||||||
const openNewTab = async( |
|
||||||
url, openerTabId, background = false, adjacent = false |
|
||||||
) => { |
|
||||||
if (!adjacent) { |
|
||||||
await browser.tabs.create({ url, active: !background }); |
|
||||||
return { type: '' }; |
|
||||||
} |
|
||||||
let tabs = await browser.tabs.query({ |
|
||||||
active: true, currentWindow: true |
|
||||||
}); |
|
||||||
await browser.tabs.create({ |
|
||||||
url, |
|
||||||
openerTabId, |
|
||||||
active: !background, |
|
||||||
index: tabs[0].index + 1 |
|
||||||
}); |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
const openToTab = async(url, tab) => { |
|
||||||
await browser.tabs.update(tab.id, { url: url }); |
|
||||||
return { type: '' }; |
|
||||||
}; |
|
||||||
|
|
||||||
const selected = (tabId) => { |
|
||||||
return { |
|
||||||
type: actions.TAB_SELECTED, |
|
||||||
tabId, |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
export { openNewTab, openToTab, selected }; |
|
@ -1,67 +0,0 @@ |
|||||||
import messages from 'shared/messages'; |
|
||||||
import * as commandActions from 'background/actions/command'; |
|
||||||
import * as settingActions from 'background/actions/setting'; |
|
||||||
import * as findActions from 'background/actions/find'; |
|
||||||
import * as tabActions from 'background/actions/tab'; |
|
||||||
import * as completions from '../shared/completions'; |
|
||||||
|
|
||||||
export default class BackgroundComponent { |
|
||||||
constructor(store) { |
|
||||||
this.store = store; |
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((message, sender) => { |
|
||||||
try { |
|
||||||
return this.onMessage(message, sender); |
|
||||||
} catch (e) { |
|
||||||
return browser.tabs.sendMessage(sender.tab.id, { |
|
||||||
type: messages.CONSOLE_SHOW_ERROR, |
|
||||||
text: e.message, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
onMessage(message, sender) { |
|
||||||
let settings = this.store.getState().setting; |
|
||||||
let find = this.store.getState().find; |
|
||||||
|
|
||||||
switch (message.type) { |
|
||||||
case messages.OPEN_URL: |
|
||||||
if (message.newTab) { |
|
||||||
let action = tabActions.openNewTab( |
|
||||||
message.url, sender.tab.id, message.background, |
|
||||||
settings.value.properties.adjacenttab); |
|
||||||
return this.store.dispatch(action, sender); |
|
||||||
} |
|
||||||
return this.store.dispatch( |
|
||||||
tabActions.openToTab(message.url, sender.tab), sender); |
|
||||||
case messages.CONSOLE_ENTER_COMMAND: |
|
||||||
this.store.dispatch( |
|
||||||
commandActions.exec(sender.tab, message.text, settings.value), |
|
||||||
sender |
|
||||||
); |
|
||||||
return this.broadcastSettingsChanged(); |
|
||||||
case messages.SETTINGS_QUERY: |
|
||||||
return Promise.resolve(this.store.getState().setting.value); |
|
||||||
case messages.CONSOLE_QUERY_COMPLETIONS: |
|
||||||
return completions.complete(message.text, settings.value); |
|
||||||
case messages.SETTINGS_RELOAD: |
|
||||||
this.store.dispatch(settingActions.load()); |
|
||||||
return this.broadcastSettingsChanged(); |
|
||||||
case messages.FIND_GET_KEYWORD: |
|
||||||
return Promise.resolve(find.keyword); |
|
||||||
case messages.FIND_SET_KEYWORD: |
|
||||||
this.store.dispatch(findActions.setKeyword(message.keyword)); |
|
||||||
return Promise.resolve({}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
async broadcastSettingsChanged() { |
|
||||||
let tabs = await browser.tabs.query({}); |
|
||||||
for (let tab of tabs) { |
|
||||||
browser.tabs.sendMessage(tab.id, { |
|
||||||
type: messages.SETTINGS_CHANGED, |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
import * as indicators from '../shared/indicators'; |
|
||||||
import messages from 'shared/messages'; |
|
||||||
|
|
||||||
export default class IndicatorComponent { |
|
||||||
constructor(store) { |
|
||||||
this.store = store; |
|
||||||
|
|
||||||
messages.onMessage(this.onMessage.bind(this)); |
|
||||||
|
|
||||||
browser.browserAction.onClicked.addListener(this.onClicked); |
|
||||||
browser.tabs.onActivated.addListener(async(info) => { |
|
||||||
await browser.tabs.query({ currentWindow: true }); |
|
||||||
return this.onTabActivated(info); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async onTabActivated(info) { |
|
||||||
let { enabled } = await browser.tabs.sendMessage(info.tabId, { |
|
||||||
type: messages.ADDON_ENABLED_QUERY, |
|
||||||
}); |
|
||||||
return this.updateIndicator(enabled); |
|
||||||
} |
|
||||||
|
|
||||||
onClicked(tab) { |
|
||||||
browser.tabs.sendMessage(tab.id, { |
|
||||||
type: messages.ADDON_TOGGLE_ENABLED, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
onMessage(message) { |
|
||||||
switch (message.type) { |
|
||||||
case messages.ADDON_ENABLED_RESPONSE: |
|
||||||
return this.updateIndicator(message.enabled); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
updateIndicator(enabled) { |
|
||||||
if (enabled) { |
|
||||||
return indicators.enable(); |
|
||||||
} |
|
||||||
return indicators.disable(); |
|
||||||
} |
|
||||||
} |
|
@ -1,127 +0,0 @@ |
|||||||
import messages from 'shared/messages'; |
|
||||||
import operations from 'shared/operations'; |
|
||||||
import * as tabs from '../shared//tabs'; |
|
||||||
import * as zooms from '../shared/zooms'; |
|
||||||
import * as consoleActions from '../actions/console'; |
|
||||||
|
|
||||||
export default class BackgroundComponent { |
|
||||||
constructor(store) { |
|
||||||
this.store = store; |
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((message, sender) => { |
|
||||||
try { |
|
||||||
return this.onMessage(message, sender); |
|
||||||
} catch (e) { |
|
||||||
return browser.tabs.sendMessage(sender.tab.id, { |
|
||||||
type: messages.CONSOLE_SHOW_ERROR, |
|
||||||
text: e.message, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
onMessage(message, sender) { |
|
||||||
switch (message.type) { |
|
||||||
case messages.BACKGROUND_OPERATION: |
|
||||||
return this.store.dispatch( |
|
||||||
this.exec(message.operation, sender.tab)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// eslint-disable-next-line complexity, max-lines-per-function
|
|
||||||
async exec(operation, tab) { |
|
||||||
let tabState = this.store.getState().tab; |
|
||||||
|
|
||||||
switch (operation.type) { |
|
||||||
case operations.TAB_CLOSE: |
|
||||||
await tabs.closeTab(tab.id); |
|
||||||
break; |
|
||||||
case operations.TAB_CLOSE_FORCE: |
|
||||||
await tabs.closeTabForce(tab.id); |
|
||||||
break; |
|
||||||
case operations.TAB_REOPEN: |
|
||||||
await tabs.reopenTab(); |
|
||||||
break; |
|
||||||
case operations.TAB_PREV: |
|
||||||
await tabs.selectPrevTab(tab.index, operation.count); |
|
||||||
break; |
|
||||||
case operations.TAB_NEXT: |
|
||||||
await tabs.selectNextTab(tab.index, operation.count); |
|
||||||
break; |
|
||||||
case operations.TAB_FIRST: |
|
||||||
await tabs.selectFirstTab(); |
|
||||||
break; |
|
||||||
case operations.TAB_LAST: |
|
||||||
await tabs.selectLastTab(); |
|
||||||
break; |
|
||||||
case operations.TAB_PREV_SEL: |
|
||||||
if (tabState.previousSelected > 0) { |
|
||||||
await tabs.selectTab(tabState.previousSelected); |
|
||||||
} |
|
||||||
break; |
|
||||||
case operations.TAB_RELOAD: |
|
||||||
await tabs.reload(tab, operation.cache); |
|
||||||
break; |
|
||||||
case operations.TAB_PIN: |
|
||||||
await tabs.updateTabPinned(tab, true); |
|
||||||
break; |
|
||||||
case operations.TAB_UNPIN: |
|
||||||
await tabs.updateTabPinned(tab, false); |
|
||||||
break; |
|
||||||
case operations.TAB_TOGGLE_PINNED: |
|
||||||
await tabs.toggleTabPinned(tab); |
|
||||||
break; |
|
||||||
case operations.TAB_DUPLICATE: |
|
||||||
await tabs.duplicate(tab.id); |
|
||||||
break; |
|
||||||
case operations.ZOOM_IN: |
|
||||||
await zooms.zoomIn(); |
|
||||||
break; |
|
||||||
case operations.ZOOM_OUT: |
|
||||||
await zooms.zoomOut(); |
|
||||||
break; |
|
||||||
case operations.ZOOM_NEUTRAL: |
|
||||||
await zooms.neutral(); |
|
||||||
break; |
|
||||||
case operations.COMMAND_SHOW: |
|
||||||
return consoleActions.showCommand(tab, ''); |
|
||||||
case operations.COMMAND_SHOW_OPEN: |
|
||||||
if (operation.alter) { |
|
||||||
// alter url
|
|
||||||
return consoleActions.showCommand(tab, 'open ' + tab.url); |
|
||||||
} |
|
||||||
return consoleActions.showCommand(tab, 'open '); |
|
||||||
case operations.COMMAND_SHOW_TABOPEN: |
|
||||||
if (operation.alter) { |
|
||||||
// alter url
|
|
||||||
return consoleActions.showCommand(tab, 'tabopen ' + tab.url); |
|
||||||
} |
|
||||||
return consoleActions.showCommand(tab, 'tabopen '); |
|
||||||
case operations.COMMAND_SHOW_WINOPEN: |
|
||||||
if (operation.alter) { |
|
||||||
// alter url
|
|
||||||
return consoleActions.showCommand(tab, 'winopen ' + tab.url); |
|
||||||
} |
|
||||||
return consoleActions.showCommand(tab, 'winopen '); |
|
||||||
case operations.COMMAND_SHOW_BUFFER: |
|
||||||
return consoleActions.showCommand(tab, 'buffer '); |
|
||||||
case operations.COMMAND_SHOW_ADDBOOKMARK: |
|
||||||
if (operation.alter) { |
|
||||||
return consoleActions.showCommand(tab, 'addbookmark ' + tab.title); |
|
||||||
} |
|
||||||
return consoleActions.showCommand(tab, 'addbookmark '); |
|
||||||
case operations.FIND_START: |
|
||||||
return consoleActions.showFind(tab); |
|
||||||
case operations.CANCEL: |
|
||||||
return consoleActions.hide(tab); |
|
||||||
case operations.PAGE_SOURCE: |
|
||||||
await browser.tabs.create({ |
|
||||||
url: 'view-source:' + tab.url, |
|
||||||
index: tab.index + 1, |
|
||||||
openerTabId: tab.id, |
|
||||||
}); |
|
||||||
break; |
|
||||||
} |
|
||||||
return { type: '' }; |
|
||||||
} |
|
||||||
} |
|
@ -1,16 +0,0 @@ |
|||||||
import * as tabActions from '../actions/tab'; |
|
||||||
|
|
||||||
export default class TabComponent { |
|
||||||
constructor(store) { |
|
||||||
this.store = store; |
|
||||||
|
|
||||||
browser.tabs.onActivated.addListener(async(info) => { |
|
||||||
await browser.tabs.query({ currentWindow: true }); |
|
||||||
return this.onTabActivated(info); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
onTabActivated(info) { |
|
||||||
return this.store.dispatch(tabActions.selected(info.tabId)); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,11 @@ |
|||||||
|
import AddonEnabledInteractor from '../usecases/addon-enabled'; |
||||||
|
|
||||||
|
export default class AddonEnabledController { |
||||||
|
constructor() { |
||||||
|
this.addonEnabledInteractor = new AddonEnabledInteractor(); |
||||||
|
} |
||||||
|
|
||||||
|
indicate(enabled) { |
||||||
|
return this.addonEnabledInteractor.indicate(enabled); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
import CompletionsInteractor from '../usecases/completions'; |
||||||
|
import CommandInteractor from '../usecases/command'; |
||||||
|
import Completions from '../domains/completions'; |
||||||
|
|
||||||
|
export default class CommandController { |
||||||
|
constructor() { |
||||||
|
this.completionsInteractor = new CompletionsInteractor(); |
||||||
|
this.commandIndicator = new CommandInteractor(); |
||||||
|
} |
||||||
|
|
||||||
|
getCompletions(line) { |
||||||
|
let trimmed = line.trimStart(); |
||||||
|
let words = trimmed.split(/ +/); |
||||||
|
let name = words[0]; |
||||||
|
if (words.length === 1) { |
||||||
|
return this.completionsInteractor.queryConsoleCommand(name); |
||||||
|
} |
||||||
|
let keywords = trimmed.slice(name.length).trimStart(); |
||||||
|
switch (words[0]) { |
||||||
|
case 'o': |
||||||
|
case 'open': |
||||||
|
case 't': |
||||||
|
case 'tabopen': |
||||||
|
case 'w': |
||||||
|
case 'winopen': |
||||||
|
return this.completionsInteractor.queryOpen(name, keywords); |
||||||
|
case 'b': |
||||||
|
case 'buffer': |
||||||
|
return this.completionsInteractor.queryBuffer(name, keywords); |
||||||
|
case 'bd': |
||||||
|
case 'bdel': |
||||||
|
case 'bdelete': |
||||||
|
case 'bdeletes': |
||||||
|
return this.completionsInteractor.queryBdelete(name, keywords); |
||||||
|
case 'bd!': |
||||||
|
case 'bdel!': |
||||||
|
case 'bdelete!': |
||||||
|
case 'bdeletes!': |
||||||
|
return this.completionsInteractor.queryBdeleteForce(name, keywords); |
||||||
|
case 'set': |
||||||
|
return this.completionsInteractor.querySet(name, keywords); |
||||||
|
} |
||||||
|
return Promise.resolve(Completions.empty()); |
||||||
|
} |
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
|
exec(line) { |
||||||
|
let trimmed = line.trimStart(); |
||||||
|
let words = trimmed.split(/ +/); |
||||||
|
let name = words[0]; |
||||||
|
if (words[0].length === 0) { |
||||||
|
return Promise.resolve(); |
||||||
|
} |
||||||
|
|
||||||
|
let keywords = trimmed.slice(name.length).trimStart(); |
||||||
|
switch (words[0]) { |
||||||
|
case 'o': |
||||||
|
case 'open': |
||||||
|
return this.commandIndicator.open(keywords); |
||||||
|
case 't': |
||||||
|
case 'tabopen': |
||||||
|
return this.commandIndicator.tabopen(keywords); |
||||||
|
case 'w': |
||||||
|
case 'winopen': |
||||||
|
return this.commandIndicator.winopen(keywords); |
||||||
|
case 'b': |
||||||
|
case 'buffer': |
||||||
|
return this.commandIndicator.buffer(keywords); |
||||||
|
case 'bd': |
||||||
|
case 'bdel': |
||||||
|
case 'bdelete': |
||||||
|
return this.commandIndicator.bdelete(false, keywords); |
||||||
|
case 'bd!': |
||||||
|
case 'bdel!': |
||||||
|
case 'bdelete!': |
||||||
|
return this.commandIndicator.bdelete(true, keywords); |
||||||
|
case 'bdeletes': |
||||||
|
return this.commandIndicator.bdeletes(false, keywords); |
||||||
|
case 'bdeletes!': |
||||||
|
return this.commandIndicator.bdeletes(true, keywords); |
||||||
|
case 'addbookmark': |
||||||
|
return this.commandIndicator.addbookmark(keywords); |
||||||
|
case 'q': |
||||||
|
case 'quit': |
||||||
|
return this.commandIndicator.quit(); |
||||||
|
case 'qa': |
||||||
|
case 'quitall': |
||||||
|
return this.commandIndicator.quitAll(); |
||||||
|
case 'set': |
||||||
|
return this.commandIndicator.set(keywords); |
||||||
|
} |
||||||
|
throw new Error(words[0] + ' command is not defined'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
import FindInteractor from '../usecases/find'; |
||||||
|
|
||||||
|
export default class FindController { |
||||||
|
constructor() { |
||||||
|
this.findInteractor = new FindInteractor(); |
||||||
|
} |
||||||
|
|
||||||
|
getKeyword() { |
||||||
|
return this.findInteractor.getKeyword(); |
||||||
|
} |
||||||
|
|
||||||
|
setKeyword(keyword) { |
||||||
|
return this.findInteractor.setKeyword(keyword); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
import LinkInteractor from '../usecases/link'; |
||||||
|
|
||||||
|
export default class LinkController { |
||||||
|
constructor() { |
||||||
|
this.linkInteractor = new LinkInteractor(); |
||||||
|
} |
||||||
|
|
||||||
|
openToTab(url, tabId) { |
||||||
|
this.linkInteractor.openToTab(url, tabId); |
||||||
|
} |
||||||
|
|
||||||
|
openNewTab(url, openerId, background) { |
||||||
|
this.linkInteractor.openNewTab(url, openerId, background); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
import operations from '../../shared/operations'; |
||||||
|
import OperationInteractor from '../usecases/operation'; |
||||||
|
|
||||||
|
export default class OperationController { |
||||||
|
constructor() { |
||||||
|
this.operationInteractor = new OperationInteractor(); |
||||||
|
} |
||||||
|
|
||||||
|
// eslint-disable-next-line complexity, max-lines-per-function
|
||||||
|
exec(operation) { |
||||||
|
switch (operation.type) { |
||||||
|
case operations.TAB_CLOSE: |
||||||
|
return this.operationInteractor.close(false); |
||||||
|
case operations.TAB_CLOSE_FORCE: |
||||||
|
return this.operationInteractor.close(true); |
||||||
|
case operations.TAB_REOPEN: |
||||||
|
return this.operationInteractor.reopen(); |
||||||
|
case operations.TAB_PREV: |
||||||
|
return this.operationInteractor.selectPrev(1); |
||||||
|
case operations.TAB_NEXT: |
||||||
|
return this.operationInteractor.selectNext(1); |
||||||
|
case operations.TAB_FIRST: |
||||||
|
return this.operationInteractor.selectFirst(); |
||||||
|
case operations.TAB_LAST: |
||||||
|
return this.operationInteractor.selectLast(); |
||||||
|
case operations.TAB_PREV_SEL: |
||||||
|
return this.operationInteractor.selectPrevSelected(); |
||||||
|
case operations.TAB_RELOAD: |
||||||
|
return this.operationInteractor.reload(operation.cache); |
||||||
|
case operations.TAB_PIN: |
||||||
|
return this.operationInteractor.setPinned(true); |
||||||
|
case operations.TAB_UNPIN: |
||||||
|
return this.operationInteractor.setPinned(false); |
||||||
|
case operations.TAB_TOGGLE_PINNED: |
||||||
|
return this.operationInteractor.togglePinned(); |
||||||
|
case operations.TAB_DUPLICATE: |
||||||
|
return this.operationInteractor.duplicate(); |
||||||
|
case operations.PAGE_SOURCE: |
||||||
|
return this.operationInteractor.openPageSource(); |
||||||
|
case operations.ZOOM_IN: |
||||||
|
return this.operationInteractor.zoomIn(); |
||||||
|
case operations.ZOOM_OUT: |
||||||
|
return this.operationInteractor.zoomOut(); |
||||||
|
case operations.ZOOM_NEUTRAL: |
||||||
|
return this.operationInteractor.zoomNutoral(); |
||||||
|
case operations.COMMAND_SHOW: |
||||||
|
return this.operationInteractor.showCommand(); |
||||||
|
case operations.COMMAND_SHOW_OPEN: |
||||||
|
return this.operationInteractor.showOpenCommand(operation.alter); |
||||||
|
case operations.COMMAND_SHOW_TABOPEN: |
||||||
|
return this.operationInteractor.showTabopenCommand(operation.alter); |
||||||
|
case operations.COMMAND_SHOW_WINOPEN: |
||||||
|
return this.operationInteractor.showWinopenCommand(operation.alter); |
||||||
|
case operations.COMMAND_SHOW_BUFFER: |
||||||
|
return this.operationInteractor.showBufferCommand(); |
||||||
|
case operations.COMMAND_SHOW_ADDBOOKMARK: |
||||||
|
return this.operationInteractor.showAddbookmarkCommand(operation.alter); |
||||||
|
case operations.FIND_START: |
||||||
|
return this.operationInteractor.findStart(); |
||||||
|
case operations.CANCEL: |
||||||
|
return this.operationInteractor.hideConsole(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,18 @@ |
|||||||
|
import SettingInteractor from '../usecases/setting'; |
||||||
|
import ContentMessageClient from '../infrastructures/content-message-client'; |
||||||
|
|
||||||
|
export default class SettingController { |
||||||
|
constructor() { |
||||||
|
this.settingInteractor = new SettingInteractor(); |
||||||
|
this.contentMessageClient = new ContentMessageClient(); |
||||||
|
} |
||||||
|
|
||||||
|
getSetting() { |
||||||
|
return this.settingInteractor.get(); |
||||||
|
} |
||||||
|
|
||||||
|
async reload() { |
||||||
|
await this.settingInteractor.reload(); |
||||||
|
this.contentMessageClient.broadcastSettingsChanged(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import VersionInteractor from '../usecases/version'; |
||||||
|
|
||||||
|
export default class VersionController { |
||||||
|
constructor() { |
||||||
|
this.versionInteractor = new VersionInteractor(); |
||||||
|
} |
||||||
|
|
||||||
|
notifyIfUpdated() { |
||||||
|
this.versionInteractor.notifyIfUpdated(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
export default class CompletionGroup { |
||||||
|
constructor(name, items) { |
||||||
|
this.name0 = name; |
||||||
|
this.items0 = items; |
||||||
|
} |
||||||
|
|
||||||
|
get name() { |
||||||
|
return this.name0; |
||||||
|
} |
||||||
|
|
||||||
|
get items() { |
||||||
|
return this.items0; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
export default class CompletionItem { |
||||||
|
constructor({ caption, content, url, icon }) { |
||||||
|
this.caption0 = caption; |
||||||
|
this.content0 = content; |
||||||
|
this.url0 = url; |
||||||
|
this.icon0 = icon; |
||||||
|
} |
||||||
|
|
||||||
|
get caption() { |
||||||
|
return this.caption0; |
||||||
|
} |
||||||
|
|
||||||
|
get content() { |
||||||
|
return this.content0; |
||||||
|
} |
||||||
|
|
||||||
|
get url() { |
||||||
|
return this.url0; |
||||||
|
} |
||||||
|
|
||||||
|
get icon() { |
||||||
|
return this.icon0; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
export default class Completions { |
||||||
|
constructor(groups) { |
||||||
|
this.g = groups; |
||||||
|
} |
||||||
|
|
||||||
|
get groups() { |
||||||
|
return this.g; |
||||||
|
} |
||||||
|
|
||||||
|
serialize() { |
||||||
|
return this.groups.map(group => ({ |
||||||
|
name: group.name, |
||||||
|
items: group.items.map(item => ({ |
||||||
|
caption: item.caption, |
||||||
|
content: item.content, |
||||||
|
url: item.url, |
||||||
|
icon: item.icon, |
||||||
|
})), |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
static EMPTY_COMPLETIONS = new Completions([]); |
||||||
|
|
||||||
|
static empty() { |
||||||
|
return Completions.EMPTY_COMPLETIONS; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
import DefaultSettings from '../../shared/settings/default'; |
||||||
|
import * as settingsValues from '../../shared/settings/values'; |
||||||
|
|
||||||
|
export default class Setting { |
||||||
|
constructor({ source, json, form }) { |
||||||
|
this.obj = { |
||||||
|
source, json, form |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
get source() { |
||||||
|
return this.obj.source; |
||||||
|
} |
||||||
|
|
||||||
|
get json() { |
||||||
|
return this.obj.json; |
||||||
|
} |
||||||
|
|
||||||
|
get form() { |
||||||
|
return this.obj.form; |
||||||
|
} |
||||||
|
|
||||||
|
value() { |
||||||
|
let value = JSON.parse(DefaultSettings.json); |
||||||
|
if (this.obj.source === 'json') { |
||||||
|
value = settingsValues.valueFromJson(this.obj.json); |
||||||
|
} else if (this.obj.source === 'form') { |
||||||
|
value = settingsValues.valueFromForm(this.obj.form); |
||||||
|
} |
||||||
|
if (!value.properties) { |
||||||
|
value.properties = {}; |
||||||
|
} |
||||||
|
return { ...settingsValues.valueFromJson(DefaultSettings.json), ...value }; |
||||||
|
} |
||||||
|
|
||||||
|
serialize() { |
||||||
|
return this.obj; |
||||||
|
} |
||||||
|
|
||||||
|
static deserialize(obj) { |
||||||
|
return new Setting({ source: obj.source, json: obj.json, form: obj.form }); |
||||||
|
} |
||||||
|
|
||||||
|
static defaultSettings() { |
||||||
|
return new Setting({ |
||||||
|
source: DefaultSettings.source, |
||||||
|
json: DefaultSettings.json, |
||||||
|
form: {}, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -1,34 +1,8 @@ |
|||||||
import * as settingActions from 'background/actions/setting'; |
import ContentMessageListener from './infrastructures/content-message-listener'; |
||||||
import BackgroundComponent from 'background/components/background'; |
import SettingController from './controllers/setting'; |
||||||
import OperationComponent from 'background/components/operation'; |
import VersionController from './controllers/version'; |
||||||
import TabComponent from 'background/components/tab'; |
|
||||||
import IndicatorComponent from 'background/components/indicator'; |
|
||||||
import reducers from 'background/reducers'; |
|
||||||
import { createStore, applyMiddleware } from 'redux'; |
|
||||||
import promise from 'redux-promise'; |
|
||||||
import * as versions from './shared/versions'; |
|
||||||
|
|
||||||
const store = createStore( |
new SettingController().reload(); |
||||||
reducers, |
new VersionController().notifyIfUpdated(); |
||||||
applyMiddleware(promise), |
|
||||||
); |
|
||||||
|
|
||||||
const checkAndNotifyUpdated = async() => { |
new ContentMessageListener().run(); |
||||||
let updated = await versions.checkUpdated(); |
|
||||||
if (!updated) { |
|
||||||
return; |
|
||||||
} |
|
||||||
await versions.notify(); |
|
||||||
await versions.commit(); |
|
||||||
}; |
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */ |
|
||||||
const backgroundComponent = new BackgroundComponent(store); |
|
||||||
const operationComponent = new OperationComponent(store); |
|
||||||
const tabComponent = new TabComponent(store); |
|
||||||
const indicatorComponent = new IndicatorComponent(store); |
|
||||||
/* eslint-enable no-unused-vars */ |
|
||||||
|
|
||||||
store.dispatch(settingActions.load()); |
|
||||||
|
|
||||||
checkAndNotifyUpdated(); |
|
||||||
|
@ -0,0 +1,25 @@ |
|||||||
|
import messages from '../../shared/messages'; |
||||||
|
|
||||||
|
export default class ContentMessageClient { |
||||||
|
async broadcastSettingsChanged() { |
||||||
|
let tabs = await browser.tabs.query({}); |
||||||
|
for (let tab of tabs) { |
||||||
|
browser.tabs.sendMessage(tab.id, { |
||||||
|
type: messages.SETTINGS_CHANGED, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async getAddonEnabled(tabId) { |
||||||
|
let { enabled } = await browser.tabs.sendMessage(tabId, { |
||||||
|
type: messages.ADDON_ENABLED_QUERY, |
||||||
|
}); |
||||||
|
return enabled; |
||||||
|
} |
||||||
|
|
||||||
|
toggleAddonEnabled(tabId) { |
||||||
|
return browser.tabs.sendMessage(tabId, { |
||||||
|
type: messages.ADDON_TOGGLE_ENABLED, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
import messages from '../../shared/messages'; |
||||||
|
import CommandController from '../controllers/command'; |
||||||
|
import SettingController from '../controllers/setting'; |
||||||
|
import FindController from '../controllers/find'; |
||||||
|
import AddonEnabledController from '../controllers/addon-enabled'; |
||||||
|
import LinkController from '../controllers/link'; |
||||||
|
import OperationController from '../controllers/operation'; |
||||||
|
|
||||||
|
export default class ContentMessageListener { |
||||||
|
constructor() { |
||||||
|
this.settingController = new SettingController(); |
||||||
|
this.commandController = new CommandController(); |
||||||
|
this.findController = new FindController(); |
||||||
|
this.addonEnabledController = new AddonEnabledController(); |
||||||
|
this.linkController = new LinkController(); |
||||||
|
this.backgroundOperationController = new OperationController(); |
||||||
|
} |
||||||
|
|
||||||
|
run() { |
||||||
|
browser.runtime.onMessage.addListener((message, sender) => { |
||||||
|
try { |
||||||
|
return this.onMessage(message, sender).catch((e) => { |
||||||
|
return browser.tabs.sendMessage(sender.tab.id, { |
||||||
|
type: messages.CONSOLE_SHOW_ERROR, |
||||||
|
text: e.message, |
||||||
|
}); |
||||||
|
}); |
||||||
|
} catch (e) { |
||||||
|
return browser.tabs.sendMessage(sender.tab.id, { |
||||||
|
type: messages.CONSOLE_SHOW_ERROR, |
||||||
|
text: e.message, |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
onMessage(message, sender) { |
||||||
|
switch (message.type) { |
||||||
|
case messages.CONSOLE_QUERY_COMPLETIONS: |
||||||
|
return this.onConsoleQueryCompletions(message.text); |
||||||
|
case messages.CONSOLE_ENTER_COMMAND: |
||||||
|
return this.onConsoleEnterCommand(message.text); |
||||||
|
case messages.SETTINGS_QUERY: |
||||||
|
return this.onSettingsQuery(); |
||||||
|
case messages.SETTINGS_RELOAD: |
||||||
|
return this.onSettingsReload(); |
||||||
|
case messages.FIND_GET_KEYWORD: |
||||||
|
return this.onFindGetKeyword(); |
||||||
|
case messages.FIND_SET_KEYWORD: |
||||||
|
return this.onFindSetKeyword(message.keyword); |
||||||
|
case messages.ADDON_ENABLED_RESPONSE: |
||||||
|
return this.onAddonEnabledResponse(message.enabled); |
||||||
|
case messages.OPEN_URL: |
||||||
|
return this.onOpenUrl( |
||||||
|
message.newTab, message.url, sender.tab.id, message.background); |
||||||
|
case messages.BACKGROUND_OPERATION: |
||||||
|
return this.onBackgroundOperation(message.operation); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async onConsoleQueryCompletions(line) { |
||||||
|
let completions = await this.commandController.getCompletions(line); |
||||||
|
return Promise.resolve(completions.serialize()); |
||||||
|
} |
||||||
|
|
||||||
|
onConsoleEnterCommand(text) { |
||||||
|
return this.commandController.exec(text); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
onSettingsQuery() { |
||||||
|
return this.settingController.getSetting(); |
||||||
|
} |
||||||
|
|
||||||
|
onSettingsReload() { |
||||||
|
return this.settingController.reload(); |
||||||
|
} |
||||||
|
|
||||||
|
onFindGetKeyword() { |
||||||
|
return this.findController.getKeyword(); |
||||||
|
} |
||||||
|
|
||||||
|
onFindSetKeyword(keyword) { |
||||||
|
return this.findController.setKeyword(keyword); |
||||||
|
} |
||||||
|
|
||||||
|
onAddonEnabledResponse(enabled) { |
||||||
|
return this.addonEnabledController.indicate(enabled); |
||||||
|
} |
||||||
|
|
||||||
|
onOpenUrl(newTab, url, openerId, background) { |
||||||
|
if (newTab) { |
||||||
|
return this.linkController.openNewTab(url, openerId, background); |
||||||
|
} |
||||||
|
return this.linkController.openToTab(url, openerId); |
||||||
|
} |
||||||
|
|
||||||
|
onBackgroundOperation(operation) { |
||||||
|
return this.backgroundOperationController.exec(operation); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
const db = {}; |
||||||
|
|
||||||
|
export default class MemoryStorage { |
||||||
|
set(name, value) { |
||||||
|
let data = JSON.stringify(value); |
||||||
|
if (typeof data === 'undefined') { |
||||||
|
throw new Error('value is not serializable'); |
||||||
|
} |
||||||
|
db[name] = data; |
||||||
|
} |
||||||
|
|
||||||
|
get(name) { |
||||||
|
let data = db[name]; |
||||||
|
if (!data) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
return JSON.parse(data); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
const NOTIFICATION_ID = 'vimvixen-update'; |
||||||
|
|
||||||
|
export default class Notifier { |
||||||
|
notify(title, message, onclick) { |
||||||
|
const listener = (id) => { |
||||||
|
if (id !== NOTIFICATION_ID) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
onclick(); |
||||||
|
|
||||||
|
browser.notifications.onClicked.removeListener(listener); |
||||||
|
}; |
||||||
|
browser.notifications.onClicked.addListener(listener); |
||||||
|
|
||||||
|
return browser.notifications.create(NOTIFICATION_ID, { |
||||||
|
'type': 'basic', |
||||||
|
'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), |
||||||
|
title, |
||||||
|
message, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
import messages from '../../shared/messages'; |
||||||
|
|
||||||
|
export default class ConsolePresenter { |
||||||
|
showCommand(tabId, command) { |
||||||
|
return browser.tabs.sendMessage(tabId, { |
||||||
|
type: messages.CONSOLE_SHOW_COMMAND, |
||||||
|
command, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
showFind(tabId) { |
||||||
|
return browser.tabs.sendMessage(tabId, { |
||||||
|
type: messages.CONSOLE_SHOW_FIND |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
showInfo(tabId, message) { |
||||||
|
return browser.tabs.sendMessage(tabId, { |
||||||
|
type: messages.CONSOLE_SHOW_INFO, |
||||||
|
text: message, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
showError(tabId, message) { |
||||||
|
return browser.tabs.sendMessage(tabId, { |
||||||
|
type: messages.CONSOLE_SHOW_ERROR, |
||||||
|
text: message, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
hide(tabId) { |
||||||
|
return browser.tabs.sendMessage(tabId, { |
||||||
|
type: messages.CONSOLE_HIDE, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
export default class IndicatorPresenter { |
||||||
|
indicate(enabled) { |
||||||
|
let path = enabled |
||||||
|
? 'resources/enabled_32x32.png' |
||||||
|
: 'resources/disabled_32x32.png'; |
||||||
|
return browser.browserAction.setIcon({ path }); |
||||||
|
} |
||||||
|
|
||||||
|
onClick(listener) { |
||||||
|
browser.browserAction.onClicked.addListener(listener); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
export default class TabPresenter { |
||||||
|
open(url, tabId) { |
||||||
|
return browser.tabs.update(tabId, { url }); |
||||||
|
} |
||||||
|
|
||||||
|
create(url, opts) { |
||||||
|
return browser.tabs.create({ url, ...opts }); |
||||||
|
} |
||||||
|
|
||||||
|
async getCurrent() { |
||||||
|
let tabs = await browser.tabs.query({ |
||||||
|
active: true, currentWindow: true |
||||||
|
}); |
||||||
|
return tabs[0]; |
||||||
|
} |
||||||
|
|
||||||
|
getAll() { |
||||||
|
return browser.tabs.query({ currentWindow: true }); |
||||||
|
} |
||||||
|
|
||||||
|
async getByKeyword(keyword, excludePinned = false) { |
||||||
|
let tabs = await browser.tabs.query({ currentWindow: true }); |
||||||
|
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); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
select(tabId) { |
||||||
|
return browser.tabs.update(tabId, { active: true }); |
||||||
|
} |
||||||
|
|
||||||
|
async selectAt(index) { |
||||||
|
let tabs = await browser.tabs.query({ currentWindow: true }); |
||||||
|
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 }); |
||||||
|
} |
||||||
|
|
||||||
|
remove(ids) { |
||||||
|
return browser.tabs.remove(ids); |
||||||
|
} |
||||||
|
|
||||||
|
async reopen() { |
||||||
|
let window = await browser.windows.getCurrent(); |
||||||
|
let sessions = await browser.sessions.getRecentlyClosed(); |
||||||
|
let session = sessions.find((s) => { |
||||||
|
return s.tab && s.tab.windowId === window.id; |
||||||
|
}); |
||||||
|
if (!session) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (session.tab) { |
||||||
|
return browser.sessions.restore(session.tab.sessionId); |
||||||
|
} |
||||||
|
return browser.sessions.restore(session.window.sessionId); |
||||||
|
} |
||||||
|
|
||||||
|
reload(tabId, cache) { |
||||||
|
return browser.tabs.reload(tabId, { bypassCache: cache }); |
||||||
|
} |
||||||
|
|
||||||
|
setPinned(tabId, pinned) { |
||||||
|
return browser.tabs.update(tabId, { pinned }); |
||||||
|
} |
||||||
|
|
||||||
|
duplicate(id) { |
||||||
|
return browser.tabs.duplicate(id); |
||||||
|
} |
||||||
|
|
||||||
|
getZoom(tabId) { |
||||||
|
return browser.tabs.getZoom(tabId); |
||||||
|
} |
||||||
|
|
||||||
|
setZoom(tabId, factor) { |
||||||
|
return browser.tabs.setZoom(tabId, factor); |
||||||
|
} |
||||||
|
|
||||||
|
async createAdjacent(url, { openerTabId, active }) { |
||||||
|
let tabs = await browser.tabs.query({ |
||||||
|
active: true, currentWindow: true |
||||||
|
}); |
||||||
|
return browser.tabs.create({ |
||||||
|
url, |
||||||
|
openerTabId, |
||||||
|
active, |
||||||
|
index: tabs[0].index + 1 |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
onSelected(listener) { |
||||||
|
browser.tabs.onActivated.addListener(listener); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
export default class WindowPresenter { |
||||||
|
create(url) { |
||||||
|
return browser.windows.create({ url }); |
||||||
|
} |
||||||
|
} |
@ -1,15 +0,0 @@ |
|||||||
import actions from 'content/actions'; |
|
||||||
|
|
||||||
const defaultState = { |
|
||||||
keyword: null, |
|
||||||
}; |
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) { |
|
||||||
switch (action.type) { |
|
||||||
case actions.FIND_SET_KEYWORD: |
|
||||||
return { ...state, |
|
||||||
keyword: action.keyword, }; |
|
||||||
default: |
|
||||||
return state; |
|
||||||
} |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
import { combineReducers } from 'redux'; |
|
||||||
import setting from './setting'; |
|
||||||
import find from './find'; |
|
||||||
import tab from './tab'; |
|
||||||
|
|
||||||
export default combineReducers({ |
|
||||||
setting, find, tab, |
|
||||||
}); |
|
@ -1,22 +0,0 @@ |
|||||||
import actions from 'background/actions'; |
|
||||||
|
|
||||||
const defaultState = { |
|
||||||
value: {}, |
|
||||||
}; |
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) { |
|
||||||
switch (action.type) { |
|
||||||
case actions.SETTING_SET_SETTINGS: |
|
||||||
return { |
|
||||||
value: action.value, |
|
||||||
}; |
|
||||||
case actions.SETTING_SET_PROPERTY: |
|
||||||
return { |
|
||||||
value: { ...state.value, |
|
||||||
properties: { ...state.value.properties, [action.name]: action.value }} |
|
||||||
}; |
|
||||||
default: |
|
||||||
return state; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -1,19 +0,0 @@ |
|||||||
import actions from 'background/actions'; |
|
||||||
|
|
||||||
const defaultState = { |
|
||||||
previousSelected: -1, |
|
||||||
currentSelected: -1, |
|
||||||
}; |
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action = {}) { |
|
||||||
switch (action.type) { |
|
||||||
case actions.TAB_SELECTED: |
|
||||||
return { |
|
||||||
previousSelected: state.currentSelected, |
|
||||||
currentSelected: action.tabId, |
|
||||||
}; |
|
||||||
default: |
|
||||||
return state; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -0,0 +1,13 @@ |
|||||||
|
export default class BookmarkRepository { |
||||||
|
async create(title, url) { |
||||||
|
let item = await browser.bookmarks.create({ |
||||||
|
type: 'bookmark', |
||||||
|
title, |
||||||
|
url, |
||||||
|
}); |
||||||
|
if (!item) { |
||||||
|
throw new Error('Could not create a bookmark'); |
||||||
|
} |
||||||
|
return item; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
export default class CompletionsRepository { |
||||||
|
async queryBookmarks(keywords) { |
||||||
|
let items = await browser.bookmarks.search({ query: keywords }); |
||||||
|
return items.filter((item) => { |
||||||
|
let url = undefined; |
||||||
|
try { |
||||||
|
url = new URL(item.url); |
||||||
|
} catch (e) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return item.type === 'bookmark' && url.protocol !== 'place:'; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
queryHistories(keywords) { |
||||||
|
return browser.history.search({ |
||||||
|
text: keywords, |
||||||
|
startTime: 0, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async queryTabs(keywords, excludePinned) { |
||||||
|
let tabs = await browser.tabs.query({ currentWindow: true }); |
||||||
|
return tabs.filter((t) => { |
||||||
|
return t.url.toLowerCase().includes(keywords.toLowerCase()) || |
||||||
|
t.title && t.title.toLowerCase().includes(keywords.toLowerCase()); |
||||||
|
}).filter((t) => { |
||||||
|
return !(excludePinned && t.pinned); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
import MemoryStorage from '../infrastructures/memory-storage'; |
||||||
|
|
||||||
|
const FIND_KEYWORD_KEY = 'find-keyword'; |
||||||
|
|
||||||
|
export default class FindRepository { |
||||||
|
constructor() { |
||||||
|
this.cache = new MemoryStorage(); |
||||||
|
} |
||||||
|
|
||||||
|
getKeyword() { |
||||||
|
return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY)); |
||||||
|
} |
||||||
|
|
||||||
|
setKeyword(keyword) { |
||||||
|
return this.cache.set(FIND_KEYWORD_KEY, keyword); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,16 @@ |
|||||||
|
import Setting from '../domains/setting'; |
||||||
|
|
||||||
|
export default class SettingRepository { |
||||||
|
save(settings) { |
||||||
|
return browser.storage.local.set({ settings: settings.serialize() }); |
||||||
|
} |
||||||
|
|
||||||
|
async load() { |
||||||
|
let { settings } = await browser.storage.local.get('settings'); |
||||||
|
if (!settings) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return Setting.deserialize(settings); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,23 @@ |
|||||||
|
import MemoryStorage from '../infrastructures/memory-storage'; |
||||||
|
|
||||||
|
const CACHED_SETTING_KEY = 'setting'; |
||||||
|
|
||||||
|
export default class SettingRepository { |
||||||
|
constructor() { |
||||||
|
this.cache = new MemoryStorage(); |
||||||
|
} |
||||||
|
|
||||||
|
get() { |
||||||
|
return Promise.resolve(this.cache.get(CACHED_SETTING_KEY)); |
||||||
|
} |
||||||
|
|
||||||
|
update(value) { |
||||||
|
return this.cache.set(CACHED_SETTING_KEY, value); |
||||||
|
} |
||||||
|
|
||||||
|
async setProperty(name, value) { |
||||||
|
let current = await this.get(); |
||||||
|
current.properties[name] = value; |
||||||
|
return this.update(current); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
export default class VersionRepository { |
||||||
|
async get() { |
||||||
|
let { version } = await browser.storage.local.get('version'); |
||||||
|
return version; |
||||||
|
} |
||||||
|
|
||||||
|
update(version) { |
||||||
|
return browser.storage.local.set({ version }); |
||||||
|
} |
||||||
|
} |
@ -1,9 +0,0 @@ |
|||||||
const create = (title, url) => { |
|
||||||
return browser.bookmarks.create({ |
|
||||||
type: 'bookmark', |
|
||||||
title, |
|
||||||
url, |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
export { create }; |
|
@ -1,14 +0,0 @@ |
|||||||
const getCompletions = async(keywords) => { |
|
||||||
let items = await browser.bookmarks.search({ query: keywords }); |
|
||||||
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 }; |
|
@ -1,82 +0,0 @@ |
|||||||
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 = async(keyword) => { |
|
||||||
let historyItems = await browser.history.search({ |
|
||||||
text: keyword, |
|
||||||
startTime: 0, |
|
||||||
}); |
|
||||||
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 }; |
|
@ -1,173 +0,0 @@ |
|||||||
import commandDocs from 'shared/commands/docs'; |
|
||||||
import * as tabs from './tabs'; |
|
||||||
import * as histories from './histories'; |
|
||||||
import * as bookmarks from './bookmarks'; |
|
||||||
import * as properties from 'shared/settings/properties'; |
|
||||||
|
|
||||||
const completeCommands = (typing) => { |
|
||||||
let keys = Object.keys(commandDocs); |
|
||||||
return keys |
|
||||||
.filter(name => name.startsWith(typing)) |
|
||||||
.map(name => ({ |
|
||||||
caption: name, |
|
||||||
content: name, |
|
||||||
url: commandDocs[name], |
|
||||||
})); |
|
||||||
}; |
|
||||||
|
|
||||||
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 = async(command, keywords) => { |
|
||||||
let items = await histories.getCompletions(keywords); |
|
||||||
return items.map((page) => { |
|
||||||
return { |
|
||||||
caption: page.title, |
|
||||||
content: command + ' ' + page.url, |
|
||||||
url: page.url |
|
||||||
}; |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
const getBookmarksCompletions = async(command, keywords) => { |
|
||||||
let items = await bookmarks.getCompletions(keywords); |
|
||||||
return items.map(item => ({ |
|
||||||
caption: item.title, |
|
||||||
content: command + ' ' + item.url, |
|
||||||
url: item.url, |
|
||||||
})); |
|
||||||
}; |
|
||||||
|
|
||||||
const getOpenCompletions = async(command, keywords, searchConfig) => { |
|
||||||
let engineItems = await getSearchCompletions(command, keywords, searchConfig); |
|
||||||
let historyItems = await getHistoryCompletions(command, keywords); |
|
||||||
let bookmarkItems = await getBookmarksCompletions(command, keywords); |
|
||||||
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 = async(command, keywords, excludePinned) => { |
|
||||||
let items = await tabs.getCompletions(keywords, excludePinned); |
|
||||||
items = items.map(tab => ({ |
|
||||||
caption: tab.title, |
|
||||||
content: command + ' ' + tab.title, |
|
||||||
url: tab.url, |
|
||||||
icon: tab.favIconUrl |
|
||||||
})); |
|
||||||
return [ |
|
||||||
{ |
|
||||||
name: 'Buffers', |
|
||||||
items: items |
|
||||||
} |
|
||||||
]; |
|
||||||
}; |
|
||||||
|
|
||||||
const getSetCompletions = (command, keywords) => { |
|
||||||
let keys = Object.keys(properties.docs).filter( |
|
||||||
name => name.startsWith(keywords) |
|
||||||
); |
|
||||||
let items = keys.map((key) => { |
|
||||||
if (properties.types[key] === 'boolean') { |
|
||||||
return [ |
|
||||||
{ |
|
||||||
caption: key, |
|
||||||
content: command + ' ' + key, |
|
||||||
url: 'Enable ' + properties.docs[key], |
|
||||||
}, { |
|
||||||
caption: 'no' + key, |
|
||||||
content: command + ' no' + key, |
|
||||||
url: 'Disable ' + properties.docs[key], |
|
||||||
} |
|
||||||
]; |
|
||||||
} |
|
||||||
return [ |
|
||||||
{ |
|
||||||
caption: key, |
|
||||||
content: command + ' ' + key, |
|
||||||
url: 'Set ' + properties.docs[key], |
|
||||||
} |
|
||||||
]; |
|
||||||
}); |
|
||||||
items = items.reduce((acc, val) => acc.concat(val), []); |
|
||||||
if (items.length === 0) { |
|
||||||
return Promise.resolve([]); |
|
||||||
} |
|
||||||
return Promise.resolve([ |
|
||||||
{ |
|
||||||
name: 'Properties', |
|
||||||
items, |
|
||||||
} |
|
||||||
]); |
|
||||||
}; |
|
||||||
|
|
||||||
const complete = (line, settings) => { |
|
||||||
let trimmed = line.trimStart(); |
|
||||||
let words = trimmed.split(/ +/); |
|
||||||
let name = words[0]; |
|
||||||
if (words.length === 1) { |
|
||||||
let items = completeCommands(name); |
|
||||||
if (items.length === 0) { |
|
||||||
return Promise.resolve([]); |
|
||||||
} |
|
||||||
return Promise.resolve([ |
|
||||||
{ |
|
||||||
name: 'Console Command', |
|
||||||
items: completeCommands(name), |
|
||||||
} |
|
||||||
]); |
|
||||||
} |
|
||||||
let keywords = trimmed.slice(name.length).trimStart(); |
|
||||||
|
|
||||||
switch (words[0]) { |
|
||||||
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); |
|
||||||
case 'set': |
|
||||||
return getSetCompletions(name, keywords); |
|
||||||
} |
|
||||||
return Promise.resolve([]); |
|
||||||
}; |
|
||||||
|
|
||||||
export { complete }; |
|
@ -1,8 +0,0 @@ |
|||||||
import * as tabs from '../tabs'; |
|
||||||
|
|
||||||
const getCompletions = (keyword, excludePinned) => { |
|
||||||
return tabs.queryByKeyword(keyword, excludePinned); |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
export { getCompletions }; |
|
@ -1,13 +0,0 @@ |
|||||||
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 }; |
|
@ -1,158 +0,0 @@ |
|||||||
import * as tabCompletions from './completions/tabs'; |
|
||||||
|
|
||||||
const closeTab = async(id) => { |
|
||||||
let tab = await browser.tabs.get(id); |
|
||||||
if (!tab.pinned) { |
|
||||||
return browser.tabs.remove(id); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const closeTabForce = (id) => { |
|
||||||
return browser.tabs.remove(id); |
|
||||||
}; |
|
||||||
|
|
||||||
const queryByKeyword = async(keyword, excludePinned = false) => { |
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true }); |
|
||||||
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 = async(keyword) => { |
|
||||||
let tabs = await queryByKeyword(keyword, true); |
|
||||||
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); |
|
||||||
} |
|
||||||
return browser.tabs.remove(tabs[0].id); |
|
||||||
}; |
|
||||||
|
|
||||||
const closeTabByKeywordsForce = async(keyword) => { |
|
||||||
let tabs = await queryByKeyword(keyword, false); |
|
||||||
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); |
|
||||||
} |
|
||||||
return browser.tabs.remove(tabs[0].id); |
|
||||||
}; |
|
||||||
|
|
||||||
const closeTabsByKeywords = async(keyword) => { |
|
||||||
let tabs = await tabCompletions.getCompletions(keyword); |
|
||||||
tabs = tabs.filter(tab => !tab.pinned); |
|
||||||
return browser.tabs.remove(tabs.map(tab => tab.id)); |
|
||||||
}; |
|
||||||
|
|
||||||
const closeTabsByKeywordsForce = async(keyword) => { |
|
||||||
let tabs = await tabCompletions.getCompletions(keyword); |
|
||||||
return browser.tabs.remove(tabs.map(tab => tab.id)); |
|
||||||
}; |
|
||||||
|
|
||||||
const reopenTab = async() => { |
|
||||||
let window = await browser.windows.getCurrent(); |
|
||||||
let sessions = await browser.sessions.getRecentlyClosed(); |
|
||||||
let session = sessions.find((s) => { |
|
||||||
return s.tab && s.tab.windowId === window.id; |
|
||||||
}); |
|
||||||
if (!session) { |
|
||||||
return; |
|
||||||
} |
|
||||||
if (session.tab) { |
|
||||||
return browser.sessions.restore(session.tab.sessionId); |
|
||||||
} |
|
||||||
return browser.sessions.restore(session.window.sessionId); |
|
||||||
}; |
|
||||||
|
|
||||||
const selectAt = async(index) => { |
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true }); |
|
||||||
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 = async(current, keyword) => { |
|
||||||
let tabs = await queryByKeyword(keyword); |
|
||||||
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 = async(current, count) => { |
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true }); |
|
||||||
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 = async(current, count) => { |
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true }); |
|
||||||
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 = async() => { |
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true }); |
|
||||||
let id = tabs[0].id; |
|
||||||
return browser.tabs.update(id, { active: true }); |
|
||||||
}; |
|
||||||
|
|
||||||
const selectLastTab = async() => { |
|
||||||
let tabs = await browser.tabs.query({ currentWindow: true }); |
|
||||||
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.update(current.id, { pinned }); |
|
||||||
}; |
|
||||||
|
|
||||||
const toggleTabPinned = (current) => { |
|
||||||
return 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 |
|
||||||
}; |
|
@ -1,38 +0,0 @@ |
|||||||
import * as storage from './storage'; |
|
||||||
import * as releaseNotes from './release-notes'; |
|
||||||
import manifest from '../../../../manifest.json'; |
|
||||||
|
|
||||||
const NOTIFICATION_ID = 'vimvixen-update'; |
|
||||||
|
|
||||||
const notificationClickListener = (id) => { |
|
||||||
if (id !== NOTIFICATION_ID) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
browser.tabs.create({ url: releaseNotes.url(manifest.version) }); |
|
||||||
browser.notifications.onClicked.removeListener(notificationClickListener); |
|
||||||
}; |
|
||||||
|
|
||||||
const checkUpdated = async() => { |
|
||||||
let prev = await storage.load(); |
|
||||||
if (!prev) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
return manifest.version !== prev; |
|
||||||
}; |
|
||||||
|
|
||||||
const notify = () => { |
|
||||||
browser.notifications.onClicked.addListener(notificationClickListener); |
|
||||||
return browser.notifications.create(NOTIFICATION_ID, { |
|
||||||
'type': 'basic', |
|
||||||
'iconUrl': browser.extension.getURL('resources/icon_48x48.png'), |
|
||||||
'title': 'Vim Vixen ' + manifest.version + ' has been installed', |
|
||||||
'message': 'Click here to see release notes', |
|
||||||
}); |
|
||||||
}; |
|
||||||
|
|
||||||
const commit = () => { |
|
||||||
storage.save(manifest.version); |
|
||||||
}; |
|
||||||
|
|
||||||
export { checkUpdated, notify, commit }; |
|
@ -1,8 +0,0 @@ |
|||||||
const url = (version) => { |
|
||||||
if (version) { |
|
||||||
return 'https://github.com/ueokande/vim-vixen/releases/tag/' + version; |
|
||||||
} |
|
||||||
return 'https://github.com/ueokande/vim-vixen/releases/'; |
|
||||||
}; |
|
||||||
|
|
||||||
export { url }; |
|
@ -1,10 +0,0 @@ |
|||||||
const load = async() => { |
|
||||||
let { version } = await browser.storage.local.get('version'); |
|
||||||
return version; |
|
||||||
}; |
|
||||||
|
|
||||||
const save = (version) => { |
|
||||||
return browser.storage.local.set({ version }); |
|
||||||
}; |
|
||||||
|
|
||||||
export { load, save }; |
|
@ -1,32 +0,0 @@ |
|||||||
// 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 = async(tabId = undefined) => { |
|
||||||
let current = await browser.tabs.getZoom(tabId); |
|
||||||
let factor = ZOOM_SETTINGS.find(f => f > current); |
|
||||||
if (factor) { |
|
||||||
return browser.tabs.setZoom(tabId, factor); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const zoomOut = async(tabId = undefined) => { |
|
||||||
let current = await browser.tabs.getZoom(tabId); |
|
||||||
let factor = [].concat(ZOOM_SETTINGS).reverse().find(f => f < current); |
|
||||||
if (factor) { |
|
||||||
return browser.tabs.setZoom(tabId, factor); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
const neutral = (tabId = undefined) => { |
|
||||||
return browser.tabs.setZoom(tabId, 1); |
|
||||||
}; |
|
||||||
|
|
||||||
export { zoomIn, zoomOut, neutral }; |
|
@ -0,0 +1,29 @@ |
|||||||
|
import IndicatorPresenter from '../presenters/indicator'; |
||||||
|
import TabPresenter from '../presenters/tab'; |
||||||
|
import ContentMessageClient from '../infrastructures/content-message-client'; |
||||||
|
|
||||||
|
export default class AddonEnabledInteractor { |
||||||
|
constructor() { |
||||||
|
this.indicatorPresentor = new IndicatorPresenter(); |
||||||
|
|
||||||
|
this.indicatorPresentor.onClick(tab => this.onIndicatorClick(tab.id)); |
||||||
|
|
||||||
|
this.tabPresenter = new TabPresenter(); |
||||||
|
this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId)); |
||||||
|
|
||||||
|
this.contentMessageClient = new ContentMessageClient(); |
||||||
|
} |
||||||
|
|
||||||
|
indicate(enabled) { |
||||||
|
return this.indicatorPresentor.indicate(enabled); |
||||||
|
} |
||||||
|
|
||||||
|
onIndicatorClick(tabId) { |
||||||
|
return this.contentMessageClient.toggleAddonEnabled(tabId); |
||||||
|
} |
||||||
|
|
||||||
|
async onTabSelected(tabId) { |
||||||
|
let enabled = await this.contentMessageClient.getAddonEnabled(tabId); |
||||||
|
return this.indicatorPresentor.indicate(enabled); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,108 @@ |
|||||||
|
import * as parsers from './parsers'; |
||||||
|
import TabPresenter from '../presenters/tab'; |
||||||
|
import WindowPresenter from '../presenters/window'; |
||||||
|
import SettingRepository from '../repositories/setting'; |
||||||
|
import BookmarkRepository from '../repositories/bookmark'; |
||||||
|
import ConsolePresenter from '../presenters/console'; |
||||||
|
import ContentMessageClient from '../infrastructures/content-message-client'; |
||||||
|
import * as properties from 'shared/settings/properties'; |
||||||
|
|
||||||
|
export default class CommandIndicator { |
||||||
|
constructor() { |
||||||
|
this.tabPresenter = new TabPresenter(); |
||||||
|
this.windowPresenter = new WindowPresenter(); |
||||||
|
this.settingRepository = new SettingRepository(); |
||||||
|
this.bookmarkRepository = new BookmarkRepository(); |
||||||
|
this.consolePresenter = new ConsolePresenter(); |
||||||
|
|
||||||
|
this.contentMessageClient = new ContentMessageClient(); |
||||||
|
} |
||||||
|
|
||||||
|
async open(keywords) { |
||||||
|
let url = await this.urlOrSearch(keywords); |
||||||
|
return this.tabPresenter.open(url); |
||||||
|
} |
||||||
|
|
||||||
|
async tabopen(keywords) { |
||||||
|
let url = await this.urlOrSearch(keywords); |
||||||
|
return this.tabPresenter.create(url); |
||||||
|
} |
||||||
|
|
||||||
|
async winopen(keywords) { |
||||||
|
let url = await this.urlOrSearch(keywords); |
||||||
|
return this.windowPresenter.create(url); |
||||||
|
} |
||||||
|
|
||||||
|
async buffer(keywords) { |
||||||
|
if (keywords.length === 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!isNaN(keywords)) { |
||||||
|
let index = parseInt(keywords, 10) - 1; |
||||||
|
return tabs.selectAt(index); |
||||||
|
} |
||||||
|
|
||||||
|
let current = await this.tabPresenter.getCurrent(); |
||||||
|
let tabs = await this.tabPresenter.getByKeyword(keywords); |
||||||
|
if (tabs.length === 0) { |
||||||
|
throw new RangeError('No matching buffer for ' + keywords); |
||||||
|
} |
||||||
|
for (let tab of tabs) { |
||||||
|
if (tab.index > current.index) { |
||||||
|
return this.tabPresenter.select(tab.id); |
||||||
|
} |
||||||
|
} |
||||||
|
return this.tabPresenter.select(tabs[0].id); |
||||||
|
} |
||||||
|
|
||||||
|
async bdelete(force, keywords) { |
||||||
|
let excludePinned = !force; |
||||||
|
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned); |
||||||
|
if (tabs.length === 0) { |
||||||
|
throw new Error('No matching buffer for ' + keywords); |
||||||
|
} else if (tabs.length > 1) { |
||||||
|
throw new Error('More than one match for ' + keywords); |
||||||
|
} |
||||||
|
return this.tabPresenter.remove([tabs[0].id]); |
||||||
|
} |
||||||
|
|
||||||
|
async bdeletes(force, keywords) { |
||||||
|
let excludePinned = !force; |
||||||
|
let tabs = await this.tabPresenter.getByKeyword(keywords, excludePinned); |
||||||
|
let ids = tabs.map(tab => tab.id); |
||||||
|
return this.tabPresenter.remove(ids); |
||||||
|
} |
||||||
|
|
||||||
|
async quit() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
return this.tabPresenter.remove([tab.id]); |
||||||
|
} |
||||||
|
|
||||||
|
async quitAll() { |
||||||
|
let tabs = await this.tabPresenter.getAll(); |
||||||
|
let ids = tabs.map(tab => tab.id); |
||||||
|
this.tabPresenter.remove(ids); |
||||||
|
} |
||||||
|
|
||||||
|
async addbookmark(title) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let item = await this.bookmarkRepository.create(title, tab.url); |
||||||
|
let message = 'Saved current page: ' + item.url; |
||||||
|
return this.consolePresenter.showInfo(tab.id, message); |
||||||
|
} |
||||||
|
|
||||||
|
async set(keywords) { |
||||||
|
if (keywords.length === 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
let [name, value] = parsers.parseSetOption(keywords, properties.types); |
||||||
|
await this.settingRepository.setProperty(name, value); |
||||||
|
|
||||||
|
return this.contentMessageClient.broadcastSettingsChanged(); |
||||||
|
} |
||||||
|
|
||||||
|
async urlOrSearch(keywords) { |
||||||
|
let settings = await this.settingRepository.get(); |
||||||
|
return parsers.normalizeUrl(keywords, settings.search); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,150 @@ |
|||||||
|
import CompletionItem from '../domains/completion-item'; |
||||||
|
import CompletionGroup from '../domains/completion-group'; |
||||||
|
import Completions from '../domains/completions'; |
||||||
|
import CommandDocs from '../domains/command-docs'; |
||||||
|
import CompletionRepository from '../repositories/completions'; |
||||||
|
import * as filters from './filters'; |
||||||
|
import SettingRepository from '../repositories/setting'; |
||||||
|
import * as properties from '../../shared/settings/properties'; |
||||||
|
|
||||||
|
const COMPLETION_ITEM_LIMIT = 10; |
||||||
|
|
||||||
|
export default class CompletionsInteractor { |
||||||
|
constructor() { |
||||||
|
this.completionRepository = new CompletionRepository(); |
||||||
|
this.settingRepository = new SettingRepository(); |
||||||
|
} |
||||||
|
|
||||||
|
queryConsoleCommand(prefix) { |
||||||
|
let keys = Object.keys(CommandDocs); |
||||||
|
let items = keys |
||||||
|
.filter(name => name.startsWith(prefix)) |
||||||
|
.map(name => ({ |
||||||
|
caption: name, |
||||||
|
content: name, |
||||||
|
url: CommandDocs[name], |
||||||
|
})); |
||||||
|
|
||||||
|
if (items.length === 0) { |
||||||
|
return Promise.resolve(Completions.empty()); |
||||||
|
} |
||||||
|
return Promise.resolve( |
||||||
|
new Completions([new CompletionGroup('Console Command', items)]) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
async queryOpen(name, keywords) { |
||||||
|
let groups = []; |
||||||
|
let engines = await this.querySearchEngineItems(name, keywords); |
||||||
|
if (engines.length > 0) { |
||||||
|
groups.push(new CompletionGroup('Search Engines', engines)); |
||||||
|
} |
||||||
|
let histories = await this.queryHistoryItems(name, keywords); |
||||||
|
if (histories.length > 0) { |
||||||
|
groups.push(new CompletionGroup('History', histories)); |
||||||
|
} |
||||||
|
let bookmarks = await this.queryBookmarkItems(name, keywords); |
||||||
|
if (bookmarks.length > 0) { |
||||||
|
groups.push(new CompletionGroup('Bookmarks', bookmarks)); |
||||||
|
} |
||||||
|
return new Completions(groups); |
||||||
|
} |
||||||
|
|
||||||
|
queryBuffer(name, keywords) { |
||||||
|
return this.queryTabs(name, false, keywords); |
||||||
|
} |
||||||
|
|
||||||
|
queryBdelete(name, keywords) { |
||||||
|
return this.queryTabs(name, true, keywords); |
||||||
|
} |
||||||
|
|
||||||
|
queryBdeleteForce(name, keywords) { |
||||||
|
return this.queryTabs(name, false, keywords); |
||||||
|
} |
||||||
|
|
||||||
|
querySet(name, keywords) { |
||||||
|
let items = Object.keys(properties.docs).map((key) => { |
||||||
|
if (properties.types[key] === 'boolean') { |
||||||
|
return [ |
||||||
|
new CompletionItem({ |
||||||
|
caption: key, |
||||||
|
content: name + ' ' + key, |
||||||
|
url: 'Enable ' + properties.docs[key], |
||||||
|
}), |
||||||
|
new CompletionItem({ |
||||||
|
caption: 'no' + key, |
||||||
|
content: name + ' no' + key, |
||||||
|
url: 'Disable ' + properties.docs[key], |
||||||
|
}), |
||||||
|
]; |
||||||
|
} |
||||||
|
return [ |
||||||
|
new CompletionItem({ |
||||||
|
caption: key, |
||||||
|
content: name + ' ' + key, |
||||||
|
url: 'Set ' + properties.docs[key], |
||||||
|
}) |
||||||
|
]; |
||||||
|
}); |
||||||
|
items = items.reduce((acc, val) => acc.concat(val), []); |
||||||
|
items = items.filter((item) => { |
||||||
|
return item.caption.startsWith(keywords); |
||||||
|
}); |
||||||
|
if (items.length === 0) { |
||||||
|
return Promise.resolve(Completions.empty()); |
||||||
|
} |
||||||
|
return Promise.resolve( |
||||||
|
new Completions([new CompletionGroup('Properties', items)]) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
async queryTabs(name, excludePinned, args) { |
||||||
|
let tabs = await this.completionRepository.queryTabs(args, excludePinned); |
||||||
|
let items = tabs.map(tab => new CompletionItem({ |
||||||
|
caption: tab.title, |
||||||
|
content: name + ' ' + tab.title, |
||||||
|
url: tab.url, |
||||||
|
icon: tab.favIconUrl |
||||||
|
})); |
||||||
|
if (items.length === 0) { |
||||||
|
return Promise.resolve(Completions.empty()); |
||||||
|
} |
||||||
|
return new Completions([new CompletionGroup('Buffers', items)]); |
||||||
|
} |
||||||
|
|
||||||
|
async querySearchEngineItems(name, keywords) { |
||||||
|
let settings = await this.settingRepository.get(); |
||||||
|
let engines = Object.keys(settings.search.engines) |
||||||
|
.filter(key => key.startsWith(keywords)); |
||||||
|
return engines.map(key => new CompletionItem({ |
||||||
|
caption: key, |
||||||
|
content: name + ' ' + key, |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
async queryHistoryItems(name, keywords) { |
||||||
|
let histories = await this.completionRepository.queryHistories(keywords); |
||||||
|
histories = [histories] |
||||||
|
.map(filters.filterBlankTitle) |
||||||
|
.map(filters.filterHttp) |
||||||
|
.map(filters.filterByTailingSlash) |
||||||
|
.map(pages => filters.filterByPathname(pages, COMPLETION_ITEM_LIMIT)) |
||||||
|
.map(pages => filters.filterByOrigin(pages, COMPLETION_ITEM_LIMIT))[0] |
||||||
|
.sort((x, y) => x.visitCount < y.visitCount) |
||||||
|
.slice(0, COMPLETION_ITEM_LIMIT); |
||||||
|
return histories.map(page => new CompletionItem({ |
||||||
|
caption: page.title, |
||||||
|
content: name + ' ' + page.url, |
||||||
|
url: page.url |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
async queryBookmarkItems(name, keywords) { |
||||||
|
let bookmarks = await this.completionRepository.queryBookmarks(keywords); |
||||||
|
return bookmarks.map(page => new CompletionItem({ |
||||||
|
caption: page.title, |
||||||
|
content: name + ' ' + page.url, |
||||||
|
url: page.url |
||||||
|
})); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
const filterHttp = (items) => { |
||||||
|
let httpsHosts = items.map(x => new URL(x.url)) |
||||||
|
.filter(x => x.protocol === 'https:') |
||||||
|
.map(x => x.host); |
||||||
|
httpsHosts = new Set(httpsHosts); |
||||||
|
|
||||||
|
return items.filter((item) => { |
||||||
|
let url = new URL(item.url); |
||||||
|
return url.protocol === 'https:' || !httpsHosts.has(url.host); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const filterBlankTitle = (items) => { |
||||||
|
return items.filter(item => item.title && item.title !== ''); |
||||||
|
}; |
||||||
|
|
||||||
|
const filterByTailingSlash = (items) => { |
||||||
|
let urls = items.map(item => new URL(item.url)); |
||||||
|
let simplePaths = urls |
||||||
|
.filter(url => url.hash === '' && url.search === '') |
||||||
|
.map(url => url.origin + url.pathname); |
||||||
|
simplePaths = new Set(simplePaths); |
||||||
|
|
||||||
|
return items.filter((item) => { |
||||||
|
let url = new URL(item.url); |
||||||
|
if (url.hash !== '' || url.search !== '' || |
||||||
|
url.pathname.slice(-1) !== '/') { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return !simplePaths.has(url.origin + url.pathname.slice(0, -1)); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const filterByPathname = (items, min) => { |
||||||
|
let hash = {}; |
||||||
|
for (let item of items) { |
||||||
|
let url = new URL(item.url); |
||||||
|
let pathname = url.origin + url.pathname; |
||||||
|
if (!hash[pathname]) { |
||||||
|
hash[pathname] = item; |
||||||
|
} else if (hash[pathname].url.length > item.url.length) { |
||||||
|
hash[pathname] = item; |
||||||
|
} |
||||||
|
} |
||||||
|
let filtered = Object.values(hash); |
||||||
|
if (filtered.length < min) { |
||||||
|
return items; |
||||||
|
} |
||||||
|
return filtered; |
||||||
|
}; |
||||||
|
|
||||||
|
const filterByOrigin = (items, min) => { |
||||||
|
let hash = {}; |
||||||
|
for (let item of items) { |
||||||
|
let origin = new URL(item.url).origin; |
||||||
|
if (!hash[origin]) { |
||||||
|
hash[origin] = item; |
||||||
|
} else if (hash[origin].url.length > item.url.length) { |
||||||
|
hash[origin] = item; |
||||||
|
} |
||||||
|
} |
||||||
|
let filtered = Object.values(hash); |
||||||
|
if (filtered.length < min) { |
||||||
|
return items; |
||||||
|
} |
||||||
|
return filtered; |
||||||
|
}; |
||||||
|
|
||||||
|
export { |
||||||
|
filterHttp, filterBlankTitle, filterByTailingSlash, |
||||||
|
filterByPathname, filterByOrigin |
||||||
|
}; |
@ -0,0 +1,15 @@ |
|||||||
|
import FindRepository from '../repositories/find'; |
||||||
|
|
||||||
|
export default class FindInteractor { |
||||||
|
constructor() { |
||||||
|
this.findRepository = new FindRepository(); |
||||||
|
} |
||||||
|
|
||||||
|
getKeyword() { |
||||||
|
return this.findRepository.getKeyword(); |
||||||
|
} |
||||||
|
|
||||||
|
setKeyword(keyword) { |
||||||
|
return this.findRepository.setKeyword(keyword); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
import SettingRepository from '../repositories/setting'; |
||||||
|
import TabPresenter from '../presenters/tab'; |
||||||
|
|
||||||
|
export default class LinkInteractor { |
||||||
|
constructor() { |
||||||
|
this.settingRepository = new SettingRepository(); |
||||||
|
this.tabPresenter = new TabPresenter(); |
||||||
|
} |
||||||
|
|
||||||
|
openToTab(url, tabId) { |
||||||
|
this.tabPresenter.open(url, tabId); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
async openNewTab(url, openerId, background) { |
||||||
|
let settings = await this.settingRepository.get(); |
||||||
|
let { adjacenttab } = settings.properties; |
||||||
|
if (adjacenttab) { |
||||||
|
return this.tabPresenter.create(url, { |
||||||
|
openerTabId: openerId, active: !background |
||||||
|
}); |
||||||
|
} |
||||||
|
return this.tabPresenter.create(url, { |
||||||
|
openerTabId: openerId, active: !background |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,192 @@ |
|||||||
|
import MemoryStorage from '../infrastructures/memory-storage'; |
||||||
|
import TabPresenter from '../presenters/tab'; |
||||||
|
import ConsolePresenter from '../presenters/console'; |
||||||
|
|
||||||
|
const CURRENT_SELECTED_KEY = 'tabs.current.selected'; |
||||||
|
const LAST_SELECTED_KEY = 'tabs.last.selected'; |
||||||
|
|
||||||
|
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 |
||||||
|
]; |
||||||
|
|
||||||
|
export default class OperationInteractor { |
||||||
|
constructor() { |
||||||
|
this.tabPresenter = new TabPresenter(); |
||||||
|
this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId)); |
||||||
|
|
||||||
|
this.consolePresenter = new ConsolePresenter(); |
||||||
|
|
||||||
|
this.cache = new MemoryStorage(); |
||||||
|
} |
||||||
|
|
||||||
|
async close(force) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
if (!force && tab.pinned) { |
||||||
|
return; |
||||||
|
} |
||||||
|
return this.tabPresenter.remove([tab.id]); |
||||||
|
} |
||||||
|
|
||||||
|
reopen() { |
||||||
|
return this.tabPresenter.reopen(); |
||||||
|
} |
||||||
|
|
||||||
|
async selectPrev(count) { |
||||||
|
let tabs = await this.tabPresenter.getAll(); |
||||||
|
if (tabs.length < 2) { |
||||||
|
return; |
||||||
|
} |
||||||
|
let tab = tabs.find(t => t.active); |
||||||
|
if (!tab) { |
||||||
|
return; |
||||||
|
} |
||||||
|
let select = (tab.index - count + tabs.length) % tabs.length; |
||||||
|
return this.tabPresenter.select(tabs[select].id); |
||||||
|
} |
||||||
|
|
||||||
|
async selectNext(count) { |
||||||
|
let tabs = await this.tabPresenter.getAll(); |
||||||
|
if (tabs.length < 2) { |
||||||
|
return; |
||||||
|
} |
||||||
|
let tab = tabs.find(t => t.active); |
||||||
|
if (!tab) { |
||||||
|
return; |
||||||
|
} |
||||||
|
let select = (tab.index + count) % tabs.length; |
||||||
|
return this.tabPresenter.select(tabs[select].id); |
||||||
|
} |
||||||
|
|
||||||
|
async selectFirst() { |
||||||
|
let tabs = await this.tabPresenter.getAll(); |
||||||
|
return this.tabPresenter.select(tabs[0].id); |
||||||
|
} |
||||||
|
|
||||||
|
async selectLast() { |
||||||
|
let tabs = await this.tabPresenter.getAll(); |
||||||
|
return this.tabPresenter.select(tabs[tabs.length - 1].id); |
||||||
|
} |
||||||
|
|
||||||
|
async selectPrevSelected() { |
||||||
|
let tabId = await this.cache.get(LAST_SELECTED_KEY); |
||||||
|
if (tabId === null || typeof tabId === 'undefined') { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.tabPresenter.select(tabId); |
||||||
|
} |
||||||
|
|
||||||
|
async reload(cache) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
return this.tabPresenter.reload(tab.id, cache); |
||||||
|
} |
||||||
|
|
||||||
|
async setPinned(pinned) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
return this.tabPresenter.setPinned(tab.id, pinned); |
||||||
|
} |
||||||
|
|
||||||
|
async togglePinned() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
return this.tabPresenter.setPinned(tab.id, !tab.pinned); |
||||||
|
} |
||||||
|
|
||||||
|
async duplicate() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
return this.tabPresenter.duplicate(tab.id); |
||||||
|
} |
||||||
|
|
||||||
|
async openPageSource() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let url = 'view-source:' + tab.url; |
||||||
|
return this.tabPresenter.create(url); |
||||||
|
} |
||||||
|
|
||||||
|
async zoomIn(tabId) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let current = await this.tabPresenter.getZoom(tab.id); |
||||||
|
let factor = ZOOM_SETTINGS.find(f => f > current); |
||||||
|
if (factor) { |
||||||
|
return this.tabPresenter.setZoom(tabId, factor); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async zoomOut(tabId) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let current = await this.tabPresenter.getZoom(tab.id); |
||||||
|
let factor = [].concat(ZOOM_SETTINGS).reverse().find(f => f < current); |
||||||
|
if (factor) { |
||||||
|
return this.tabPresenter.setZoom(tabId, factor); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
zoomNutoral(tabId) { |
||||||
|
return this.tabPresenter.setZoom(tabId, 1); |
||||||
|
} |
||||||
|
|
||||||
|
async showCommand() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
this.consolePresenter.showCommand(tab.id, ''); |
||||||
|
} |
||||||
|
|
||||||
|
async showOpenCommand(alter) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let command = 'open '; |
||||||
|
if (alter) { |
||||||
|
command += tab.url; |
||||||
|
} |
||||||
|
return this.consolePresenter.showCommand(tab.id, command); |
||||||
|
} |
||||||
|
|
||||||
|
async showTabopenCommand(alter) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let command = 'tabopen '; |
||||||
|
if (alter) { |
||||||
|
command += tab.url; |
||||||
|
} |
||||||
|
return this.consolePresenter.showCommand(tab.id, command); |
||||||
|
} |
||||||
|
|
||||||
|
async showWinopenCommand(alter) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let command = 'winopen '; |
||||||
|
if (alter) { |
||||||
|
command += tab.url; |
||||||
|
} |
||||||
|
return this.consolePresenter.showCommand(tab.id, command); |
||||||
|
} |
||||||
|
|
||||||
|
async showBufferCommand() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let command = 'buffer '; |
||||||
|
return this.consolePresenter.showCommand(tab.id, command); |
||||||
|
} |
||||||
|
|
||||||
|
async showAddbookmarkCommand(alter) { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
let command = 'addbookmark '; |
||||||
|
if (alter) { |
||||||
|
command += tab.title; |
||||||
|
} |
||||||
|
return this.consolePresenter.showCommand(tab.id, command); |
||||||
|
} |
||||||
|
|
||||||
|
async findStart() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
this.consolePresenter.showFind(tab.id); |
||||||
|
} |
||||||
|
|
||||||
|
async hideConsole() { |
||||||
|
let tab = await this.tabPresenter.getCurrent(); |
||||||
|
this.consolePresenter.hide(tab.id); |
||||||
|
} |
||||||
|
|
||||||
|
onTabSelected(tabId) { |
||||||
|
let lastId = this.cache.get(CURRENT_SELECTED_KEY); |
||||||
|
if (lastId) { |
||||||
|
this.cache.set(LAST_SELECTED_KEY, lastId); |
||||||
|
} |
||||||
|
this.cache.set(CURRENT_SELECTED_KEY, tabId); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,31 @@ |
|||||||
|
import Setting from '../domains/setting'; |
||||||
|
import PersistentSettingRepository from '../repositories/persistent-setting'; |
||||||
|
import SettingRepository from '../repositories/setting'; |
||||||
|
|
||||||
|
export default class SettingInteractor { |
||||||
|
constructor() { |
||||||
|
this.persistentSettingRepository = new PersistentSettingRepository(); |
||||||
|
this.settingRepository = new SettingRepository(); |
||||||
|
} |
||||||
|
|
||||||
|
save(settings) { |
||||||
|
this.persistentSettingRepository.save(settings); |
||||||
|
} |
||||||
|
|
||||||
|
get() { |
||||||
|
return this.settingRepository.get(); |
||||||
|
} |
||||||
|
|
||||||
|
async reload() { |
||||||
|
let settings = await this.persistentSettingRepository.load(); |
||||||
|
if (!settings) { |
||||||
|
settings = Setting.defaultSettings(); |
||||||
|
} |
||||||
|
|
||||||
|
let value = settings.value(); |
||||||
|
|
||||||
|
this.settingRepository.update(value); |
||||||
|
|
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
import manifest from '../../../manifest.json'; |
||||||
|
import VersionRepository from '../repositories/version'; |
||||||
|
import TabPresenter from '../presenters/tab'; |
||||||
|
import Notifier from '../infrastructures/notifier'; |
||||||
|
|
||||||
|
export default class VersionInteractor { |
||||||
|
constructor() { |
||||||
|
this.versionRepository = new VersionRepository(); |
||||||
|
this.tabPresenter = new TabPresenter(); |
||||||
|
this.notifier = new Notifier(); |
||||||
|
} |
||||||
|
|
||||||
|
async notifyIfUpdated() { |
||||||
|
if (!await this.checkUpdated()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let title = 'Vim Vixen ' + manifest.version + ' has been installed'; |
||||||
|
let message = 'Click here to see release notes'; |
||||||
|
this.notifier.notify(title, message, () => { |
||||||
|
let url = this.releaseNoteUrl(manifest.version); |
||||||
|
this.tabPresenter.create(url); |
||||||
|
}); |
||||||
|
this.versionRepository.update(manifest.version); |
||||||
|
} |
||||||
|
|
||||||
|
async checkUpdated() { |
||||||
|
let prev = await this.versionRepository.get(); |
||||||
|
if (!prev) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return manifest.version !== prev; |
||||||
|
} |
||||||
|
|
||||||
|
releaseNoteUrl(version) { |
||||||
|
if (version) { |
||||||
|
return 'https://github.com/ueokande/vim-vixen/releases/tag/' + version; |
||||||
|
} |
||||||
|
return 'https://github.com/ueokande/vim-vixen/releases/'; |
||||||
|
} |
||||||
|
} |
@ -1,12 +0,0 @@ |
|||||||
import actions from 'background/actions'; |
|
||||||
import * as findActions from 'background/actions/find'; |
|
||||||
|
|
||||||
describe("find actions", () => { |
|
||||||
describe("setKeyword", () => { |
|
||||||
it('create FIND_SET_KEYWORD action', () => { |
|
||||||
let action = findActions.setKeyword('banana'); |
|
||||||
expect(action.type).to.equal(actions.FIND_SET_KEYWORD); |
|
||||||
expect(action.keyword).to.equal('banana'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,13 +0,0 @@ |
|||||||
import actions from 'background/actions'; |
|
||||||
import * as tabActions from 'background/actions/tab'; |
|
||||||
|
|
||||||
describe("tab actions", () => { |
|
||||||
describe("selected", () => { |
|
||||||
it('create TAB_SELECTED action', () => { |
|
||||||
let action = tabActions.selected(123); |
|
||||||
expect(action.type).to.equal(actions.TAB_SELECTED); |
|
||||||
expect(action.tabId).to.equal(123); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
@ -0,0 +1,46 @@ |
|||||||
|
import MemoryStorage from 'background/infrastructures/memory-storage'; |
||||||
|
|
||||||
|
describe("background/infrastructures/memory-storage", () => { |
||||||
|
let versionRepository; |
||||||
|
|
||||||
|
it('stores values', () => { |
||||||
|
let cache = new MemoryStorage(); |
||||||
|
cache.set('number', 123); |
||||||
|
expect(cache.get('number')).to.equal(123); |
||||||
|
|
||||||
|
cache.set('string', '123'); |
||||||
|
expect(cache.get('string')).to.equal('123'); |
||||||
|
|
||||||
|
cache.set('object', { hello: '123' }); |
||||||
|
expect(cache.get('object')).to.deep.equal({ hello: '123' }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns undefined if no keys', () => { |
||||||
|
let cache = new MemoryStorage(); |
||||||
|
expect(cache.get('no-keys')).to.be.undefined; |
||||||
|
}) |
||||||
|
|
||||||
|
it('stored on shared memory', () => { |
||||||
|
let cache = new MemoryStorage(); |
||||||
|
cache.set('red', 'apple'); |
||||||
|
|
||||||
|
cache = new MemoryStorage(); |
||||||
|
let got = cache.get('red'); |
||||||
|
expect(got).to.equal('apple'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('stored cloned objects', () => { |
||||||
|
let cache = new MemoryStorage(); |
||||||
|
let recipe = { sugar: '300g' }; |
||||||
|
cache.set('recipe', recipe); |
||||||
|
|
||||||
|
recipe.salt = '20g' |
||||||
|
let got = cache.get('recipe', recipe); |
||||||
|
expect(got).to.deep.equal({ sugar: '300g' }); |
||||||
|
}); |
||||||
|
|
||||||
|
it('throws an error with unserializable objects', () => { |
||||||
|
let cache = new MemoryStorage(); |
||||||
|
expect(() => cache.set('fn', setTimeout)).to.throw(); |
||||||
|
}) |
||||||
|
}); |
@ -1,18 +0,0 @@ |
|||||||
import actions from 'background/actions'; |
|
||||||
import findReducer from 'background/reducers/find'; |
|
||||||
|
|
||||||
describe("find reducer", () => { |
|
||||||
it('return the initial state', () => { |
|
||||||
let state = findReducer(undefined, {}); |
|
||||||
expect(state).to.have.deep.property('keyword', null); |
|
||||||
}); |
|
||||||
|
|
||||||
it('return next state for FIND_SET_KEYWORD', () => { |
|
||||||
let action = { |
|
||||||
type: actions.FIND_SET_KEYWORD, |
|
||||||
keyword: 'cherry', |
|
||||||
}; |
|
||||||
let state = findReducer(undefined, action); |
|
||||||
expect(state).to.have.deep.property('keyword', 'cherry') |
|
||||||
}); |
|
||||||
}); |
|
@ -1,35 +0,0 @@ |
|||||||
import actions from 'background/actions'; |
|
||||||
import settingReducer from 'background/reducers/setting'; |
|
||||||
|
|
||||||
describe("setting reducer", () => { |
|
||||||
it('return the initial state', () => { |
|
||||||
let state = settingReducer(undefined, {}); |
|
||||||
expect(state).to.have.deep.property('value', {}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('return next state for SETTING_SET_SETTINGS', () => { |
|
||||||
let action = { |
|
||||||
type: actions.SETTING_SET_SETTINGS, |
|
||||||
value: { key: 123 }, |
|
||||||
}; |
|
||||||
let state = settingReducer(undefined, action); |
|
||||||
expect(state).to.have.deep.property('value', { key: 123 }); |
|
||||||
}); |
|
||||||
|
|
||||||
it('return next state for SETTING_SET_PROPERTY', () => { |
|
||||||
let state = { |
|
||||||
value: { |
|
||||||
properties: { smoothscroll: true } |
|
||||||
} |
|
||||||
} |
|
||||||
let action = { |
|
||||||
type: actions.SETTING_SET_PROPERTY, |
|
||||||
name: 'encoding', |
|
||||||
value: 'utf-8', |
|
||||||
}; |
|
||||||
state = settingReducer(state, action); |
|
||||||
|
|
||||||
expect(state.value.properties).to.have.property('smoothscroll', true); |
|
||||||
expect(state.value.properties).to.have.property('encoding', 'utf-8'); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,22 +0,0 @@ |
|||||||
import actions from 'background/actions'; |
|
||||||
import tabReducer from 'background/reducers/tab'; |
|
||||||
|
|
||||||
describe("tab reducer", () => { |
|
||||||
it('return the initial state', () => { |
|
||||||
let state = tabReducer(undefined, {}); |
|
||||||
expect(state.previousSelected).to.equal(-1); |
|
||||||
expect(state.currentSelected).to.equal(-1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('return next state for TAB_SELECTED', () => { |
|
||||||
let state = undefined; |
|
||||||
|
|
||||||
state = tabReducer(state, { type: actions.TAB_SELECTED, tabId: 123 }); |
|
||||||
expect(state.previousSelected).to.equal(-1); |
|
||||||
expect(state.currentSelected).to.equal(123); |
|
||||||
|
|
||||||
state = tabReducer(state, { type: actions.TAB_SELECTED, tabId: 456 }); |
|
||||||
expect(state.previousSelected).to.equal(123); |
|
||||||
expect(state.currentSelected).to.equal(456); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,40 +0,0 @@ |
|||||||
import * as versions from 'background/shared/versions'; |
|
||||||
import manifest from '../../../../manifest.json'; |
|
||||||
|
|
||||||
describe("shared/versions/storage", () => { |
|
||||||
describe('#checkUpdated', () => { |
|
||||||
beforeEach(() => { |
|
||||||
return browser.storage.local.remove('version'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('return true if no previous versions', async() => { |
|
||||||
let updated = await versions.checkUpdated(); |
|
||||||
expect(updated).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it('return true if updated', async() => { |
|
||||||
await browser.storage.local.set({ version: '0.001' }); |
|
||||||
let updated = await versions.checkUpdated(); |
|
||||||
expect(updated).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it('return false if not updated', async() => { |
|
||||||
await browser.storage.local.set({ version: manifest.version }); |
|
||||||
let updated = await versions.checkUpdated(); |
|
||||||
expect(updated).to.be.false; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#commit', () => { |
|
||||||
beforeEach(() => { |
|
||||||
return browser.storage.local.remove('version'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('saves current version from manifest.json', async() => { |
|
||||||
await versions.commit(); |
|
||||||
let { version } = await browser.storage.local.get('version'); |
|
||||||
expect(version).to.be.a('string'); |
|
||||||
expect(version).to.equal(manifest.version); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -0,0 +1,113 @@ |
|||||||
|
import * as filters from 'background/usecases/filters'; |
||||||
|
|
||||||
|
describe("background/usecases/filters", () => { |
||||||
|
describe('filterHttp', () => { |
||||||
|
it('filters http URLs duplicates to https hosts', () => { |
||||||
|
let pages = [ |
||||||
|
{ url: 'http://i-beam.org/foo' }, |
||||||
|
{ url: 'https://i-beam.org/bar' }, |
||||||
|
{ url: 'http://i-beam.net/hoge' }, |
||||||
|
{ url: 'http://i-beam.net/fuga' }, |
||||||
|
]; |
||||||
|
let filtered = filters.filterHttp(pages); |
||||||
|
|
||||||
|
let urls = filtered.map(x => x.url); |
||||||
|
expect(urls).to.deep.equal([ |
||||||
|
'https://i-beam.org/bar', 'http://i-beam.net/hoge', 'http://i-beam.net/fuga' |
||||||
|
]); |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
describe('filterBlankTitle', () => { |
||||||
|
it('filters blank titles', () => { |
||||||
|
let pages = [ |
||||||
|
{ title: 'hello' }, |
||||||
|
{ title: '' }, |
||||||
|
{}, |
||||||
|
]; |
||||||
|
let filtered = filters.filterBlankTitle(pages); |
||||||
|
|
||||||
|
expect(filtered).to.deep.equal([{ title: 'hello' }]); |
||||||
|
}); |
||||||
|
}) |
||||||
|
|
||||||
|
describe('filterByTailingSlash', () => { |
||||||
|
it('filters duplicated pathname on tailing slash', () => { |
||||||
|
let pages = [ |
||||||
|
{ url: 'http://i-beam.org/content' }, |
||||||
|
{ url: 'http://i-beam.org/content/' }, |
||||||
|
{ url: 'http://i-beam.org/search' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana_cherry' }, |
||||||
|
]; |
||||||
|
let filtered = filters.filterByTailingSlash(pages); |
||||||
|
|
||||||
|
let urls = filtered.map(x => x.url); |
||||||
|
expect(urls).to.deep.equal([ |
||||||
|
'http://i-beam.org/content', |
||||||
|
'http://i-beam.org/search', |
||||||
|
'http://i-beam.org/search?q=apple_banana_cherry', |
||||||
|
]); |
||||||
|
}); |
||||||
|
}) |
||||||
|
|
||||||
|
describe('filterByPathname', () => { |
||||||
|
it('remains items less than minimam length', () => { |
||||||
|
let pages = [ |
||||||
|
{ url: 'http://i-beam.org/search?q=apple' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana_cherry' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple_banana_cherry' }, |
||||||
|
]; |
||||||
|
let filtered = filters.filterByPathname(pages, 10); |
||||||
|
expect(filtered).to.have.lengthOf(6); |
||||||
|
}); |
||||||
|
|
||||||
|
it('filters by length of pathname', () => { |
||||||
|
let pages = [ |
||||||
|
{ url: 'http://i-beam.org/search?q=apple' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana_cherry' }, |
||||||
|
{ url: 'http://i-beam.net/search?q=apple' }, |
||||||
|
{ url: 'http://i-beam.net/search?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.net/search?q=apple_banana_cherry' }, |
||||||
|
]; |
||||||
|
let filtered = filters.filterByPathname(pages, 0); |
||||||
|
expect(filtered).to.deep.equal([ |
||||||
|
{ url: 'http://i-beam.org/search?q=apple' }, |
||||||
|
{ url: 'http://i-beam.net/search?q=apple' }, |
||||||
|
]); |
||||||
|
}); |
||||||
|
}) |
||||||
|
|
||||||
|
describe('filterByOrigin', () => { |
||||||
|
it('remains items less than minimam length', () => { |
||||||
|
let pages = [ |
||||||
|
{ url: 'http://i-beam.org/search?q=apple' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana_cherry' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple_banana_cherry' }, |
||||||
|
]; |
||||||
|
let filtered = filters.filterByOrigin(pages, 10); |
||||||
|
expect(filtered).to.have.lengthOf(6); |
||||||
|
}); |
||||||
|
|
||||||
|
it('filters by length of pathname', () => { |
||||||
|
let pages = [ |
||||||
|
{ url: 'http://i-beam.org/search?q=apple' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.org/search?q=apple_banana_cherry' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple_banana' }, |
||||||
|
{ url: 'http://i-beam.org/request?q=apple_banana_cherry' }, |
||||||
|
]; |
||||||
|
let filtered = filters.filterByOrigin(pages, 0); |
||||||
|
expect(filtered).to.deep.equal([ |
||||||
|
{ url: 'http://i-beam.org/search?q=apple' }, |
||||||
|
]); |
||||||
|
}); |
||||||
|
}) |
||||||
|
}); |
Reference in new issue