Merge remote-tracking branch 'origin/master' into greenkeeper/css-loader-1.0.0
This commit is contained in:
commit
944dea5919
44 changed files with 661 additions and 678 deletions
43
package-lock.json
generated
43
package-lock.json
generated
|
@ -5179,6 +5179,15 @@
|
|||
"readable-stream": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"flux-standard-action": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/flux-standard-action/-/flux-standard-action-2.0.3.tgz",
|
||||
"integrity": "sha512-HR2IjMkqJreoFm1Hx7hmMAtUFeo+ad8hPMYPo8o3YSWjbSq0sMwuXMbv4giB3TXngYB7+svkAJewQwwvwsE6xw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz",
|
||||
|
@ -10116,6 +10125,12 @@
|
|||
"integrity": "sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==",
|
||||
"dev": true
|
||||
},
|
||||
"preact-redux": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/preact-redux/-/preact-redux-2.0.3.tgz",
|
||||
"integrity": "sha1-lgpTXDImQ801mY8z8MLme8Hn6qs=",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
|
@ -10613,6 +10628,34 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz",
|
||||
"integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"symbol-observable": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"redux-promise": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-promise/-/redux-promise-0.6.0.tgz",
|
||||
"integrity": "sha512-R2mGxJbPFgXyCNbFDE6LjTZhCEuACF54g1bxld3nqBhnRMX0OsUyWk77moF7UMGkUdl5WOAwc4BC5jOd1dunqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flux-standard-action": "^2.0.3",
|
||||
"is-promise": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
|
||||
|
|
|
@ -44,6 +44,9 @@
|
|||
"node-firefox-connect": "^1.2.0",
|
||||
"node-sass": "^4.9.0",
|
||||
"preact": "^8.2.9",
|
||||
"preact-redux": "^2.0.3",
|
||||
"redux": "^4.0.0",
|
||||
"redux-promise": "^0.6.0",
|
||||
"sass-loader": "^7.0.1",
|
||||
"sinon-chrome": "^2.3.2",
|
||||
"style-loader": "^0.21.0",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import messages from 'shared/messages';
|
||||
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';
|
||||
|
@ -39,7 +39,7 @@ const winopenCommand = (url) => {
|
|||
|
||||
const bufferCommand = async(keywords) => {
|
||||
if (keywords.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
return;
|
||||
}
|
||||
let keywordsStr = keywords.join(' ');
|
||||
let got = await browser.tabs.query({
|
||||
|
@ -57,24 +57,18 @@ const bufferCommand = async(keywords) => {
|
|||
|
||||
const addbookmarkCommand = async(tab, args) => {
|
||||
if (!args[0]) {
|
||||
return;
|
||||
return { type: '' };
|
||||
}
|
||||
let item = await bookmarks.create(args.join(' '), tab.url);
|
||||
if (!item) {
|
||||
return browser.tabs.sendMessage(tab.id, {
|
||||
type: messages.CONSOLE_SHOW_ERROR,
|
||||
text: 'Could not create a bookmark',
|
||||
});
|
||||
return consoleActions.error(tab, 'Could not create a bookmark');
|
||||
}
|
||||
return browser.tabs.sendMessage(tab.id, {
|
||||
type: messages.CONSOLE_SHOW_INFO,
|
||||
text: 'Saved current page: ' + item.url,
|
||||
});
|
||||
return consoleActions.info(tab, 'Saved current page: ' + item.url);
|
||||
};
|
||||
|
||||
const setCommand = (args) => {
|
||||
if (!args[0]) {
|
||||
return Promise.resolve();
|
||||
return { type: '' };
|
||||
}
|
||||
|
||||
let [name, value] = parsers.parseSetOption(args[0], properties.types);
|
||||
|
@ -85,49 +79,69 @@ const setCommand = (args) => {
|
|||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const exec = (tab, line, settings) => {
|
||||
// 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':
|
||||
return openCommand(parsers.normalizeUrl(args, settings.search));
|
||||
await openCommand(parsers.normalizeUrl(args, settings.search));
|
||||
break;
|
||||
case 't':
|
||||
case 'tabopen':
|
||||
return tabopenCommand(parsers.normalizeUrl(args, settings.search));
|
||||
await tabopenCommand(parsers.normalizeUrl(args, settings.search));
|
||||
break;
|
||||
case 'w':
|
||||
case 'winopen':
|
||||
return winopenCommand(parsers.normalizeUrl(args, settings.search));
|
||||
await winopenCommand(parsers.normalizeUrl(args, settings.search));
|
||||
break;
|
||||
case 'b':
|
||||
case 'buffer':
|
||||
return bufferCommand(args);
|
||||
await bufferCommand(args);
|
||||
break;
|
||||
case 'bd':
|
||||
case 'bdel':
|
||||
case 'bdelete':
|
||||
return tabs.closeTabByKeywords(args.join(' '));
|
||||
await tabs.closeTabByKeywords(args.join(' '));
|
||||
break;
|
||||
case 'bd!':
|
||||
case 'bdel!':
|
||||
case 'bdelete!':
|
||||
return tabs.closeTabByKeywordsForce(args.join(' '));
|
||||
await tabs.closeTabByKeywordsForce(args.join(' '));
|
||||
break;
|
||||
case 'bdeletes':
|
||||
return tabs.closeTabsByKeywords(args.join(' '));
|
||||
await tabs.closeTabsByKeywords(args.join(' '));
|
||||
break;
|
||||
case 'bdeletes!':
|
||||
return tabs.closeTabsByKeywordsForce(args.join(' '));
|
||||
await tabs.closeTabsByKeywordsForce(args.join(' '));
|
||||
break;
|
||||
case 'addbookmark':
|
||||
return addbookmarkCommand(tab, args);
|
||||
case 'set':
|
||||
return setCommand(args);
|
||||
case 'q':
|
||||
case 'quit':
|
||||
return tabcloseCommand();
|
||||
await tabcloseCommand();
|
||||
break;
|
||||
case 'qa':
|
||||
case 'quitall':
|
||||
return tabcloseAllCommand()
|
||||
case '':
|
||||
return Promise.resolve();
|
||||
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());
|
||||
}
|
||||
throw new Error(name + ' command is not defined');
|
||||
};
|
||||
|
||||
export { exec };
|
||||
|
|
41
src/background/actions/console.js
Normal file
41
src/background/actions/console.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
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 };
|
|
@ -4,21 +4,24 @@ const openNewTab = async(
|
|||
url, openerTabId, background = false, adjacent = false
|
||||
) => {
|
||||
if (!adjacent) {
|
||||
return browser.tabs.create({ url, active: !background });
|
||||
await browser.tabs.create({ url, active: !background });
|
||||
return { type: '' };
|
||||
}
|
||||
let tabs = await browser.tabs.query({
|
||||
active: true, currentWindow: true
|
||||
});
|
||||
return browser.tabs.create({
|
||||
await browser.tabs.create({
|
||||
url,
|
||||
openerTabId,
|
||||
active: !background,
|
||||
index: tabs[0].index + 1
|
||||
});
|
||||
return { type: '' };
|
||||
};
|
||||
|
||||
const openToTab = (url, tab) => {
|
||||
return browser.tabs.update(tab.id, { url: url });
|
||||
const openToTab = async(url, tab) => {
|
||||
await browser.tabs.update(tab.id, { url: url });
|
||||
return { type: '' };
|
||||
};
|
||||
|
||||
const selected = (tabId) => {
|
||||
|
|
|
@ -2,6 +2,7 @@ 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) {
|
||||
|
@ -23,101 +24,104 @@ export default class BackgroundComponent {
|
|||
switch (message.type) {
|
||||
case messages.BACKGROUND_OPERATION:
|
||||
return this.store.dispatch(
|
||||
this.exec(message.operation, sender.tab),
|
||||
sender);
|
||||
this.exec(message.operation, sender.tab));
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity, max-lines-per-function
|
||||
exec(operation, tab) {
|
||||
async exec(operation, tab) {
|
||||
let tabState = this.store.getState().tab;
|
||||
|
||||
switch (operation.type) {
|
||||
case operations.TAB_CLOSE:
|
||||
return tabs.closeTab(tab.id);
|
||||
await tabs.closeTab(tab.id);
|
||||
break;
|
||||
case operations.TAB_CLOSE_FORCE:
|
||||
return tabs.closeTabForce(tab.id);
|
||||
await tabs.closeTabForce(tab.id);
|
||||
break;
|
||||
case operations.TAB_REOPEN:
|
||||
return tabs.reopenTab();
|
||||
await tabs.reopenTab();
|
||||
break;
|
||||
case operations.TAB_PREV:
|
||||
return tabs.selectPrevTab(tab.index, operation.count);
|
||||
await tabs.selectPrevTab(tab.index, operation.count);
|
||||
break;
|
||||
case operations.TAB_NEXT:
|
||||
return tabs.selectNextTab(tab.index, operation.count);
|
||||
await tabs.selectNextTab(tab.index, operation.count);
|
||||
break;
|
||||
case operations.TAB_FIRST:
|
||||
return tabs.selectFirstTab();
|
||||
await tabs.selectFirstTab();
|
||||
break;
|
||||
case operations.TAB_LAST:
|
||||
return tabs.selectLastTab();
|
||||
await tabs.selectLastTab();
|
||||
break;
|
||||
case operations.TAB_PREV_SEL:
|
||||
if (tabState.previousSelected > 0) {
|
||||
return tabs.selectTab(tabState.previousSelected);
|
||||
await tabs.selectTab(tabState.previousSelected);
|
||||
}
|
||||
break;
|
||||
case operations.TAB_RELOAD:
|
||||
return tabs.reload(tab, operation.cache);
|
||||
await tabs.reload(tab, operation.cache);
|
||||
break;
|
||||
case operations.TAB_PIN:
|
||||
return tabs.updateTabPinned(tab, true);
|
||||
await tabs.updateTabPinned(tab, true);
|
||||
break;
|
||||
case operations.TAB_UNPIN:
|
||||
return tabs.updateTabPinned(tab, false);
|
||||
await tabs.updateTabPinned(tab, false);
|
||||
break;
|
||||
case operations.TAB_TOGGLE_PINNED:
|
||||
return tabs.toggleTabPinned(tab);
|
||||
await tabs.toggleTabPinned(tab);
|
||||
break;
|
||||
case operations.TAB_DUPLICATE:
|
||||
return tabs.duplicate(tab.id);
|
||||
await tabs.duplicate(tab.id);
|
||||
break;
|
||||
case operations.ZOOM_IN:
|
||||
return zooms.zoomIn();
|
||||
await zooms.zoomIn();
|
||||
break;
|
||||
case operations.ZOOM_OUT:
|
||||
return zooms.zoomOut();
|
||||
await zooms.zoomOut();
|
||||
break;
|
||||
case operations.ZOOM_NEUTRAL:
|
||||
return zooms.neutral();
|
||||
await zooms.neutral();
|
||||
break;
|
||||
case operations.COMMAND_SHOW:
|
||||
return this.sendConsoleShowCommand(tab, '');
|
||||
return consoleActions.showCommand(tab, '');
|
||||
case operations.COMMAND_SHOW_OPEN:
|
||||
if (operation.alter) {
|
||||
// alter url
|
||||
return this.sendConsoleShowCommand(tab, 'open ' + tab.url);
|
||||
return consoleActions.showCommand(tab, 'open ' + tab.url);
|
||||
}
|
||||
return this.sendConsoleShowCommand(tab, 'open ');
|
||||
return consoleActions.showCommand(tab, 'open ');
|
||||
case operations.COMMAND_SHOW_TABOPEN:
|
||||
if (operation.alter) {
|
||||
// alter url
|
||||
return this.sendConsoleShowCommand(tab, 'tabopen ' + tab.url);
|
||||
return consoleActions.showCommand(tab, 'tabopen ' + tab.url);
|
||||
}
|
||||
return this.sendConsoleShowCommand(tab, 'tabopen ');
|
||||
return consoleActions.showCommand(tab, 'tabopen ');
|
||||
case operations.COMMAND_SHOW_WINOPEN:
|
||||
if (operation.alter) {
|
||||
// alter url
|
||||
return this.sendConsoleShowCommand(tab, 'winopen ' + tab.url);
|
||||
return consoleActions.showCommand(tab, 'winopen ' + tab.url);
|
||||
}
|
||||
return this.sendConsoleShowCommand(tab, 'winopen ');
|
||||
return consoleActions.showCommand(tab, 'winopen ');
|
||||
case operations.COMMAND_SHOW_BUFFER:
|
||||
return this.sendConsoleShowCommand(tab, 'buffer ');
|
||||
return consoleActions.showCommand(tab, 'buffer ');
|
||||
case operations.COMMAND_SHOW_ADDBOOKMARK:
|
||||
if (operation.alter) {
|
||||
return this.sendConsoleShowCommand(tab, 'addbookmark ' + tab.title);
|
||||
return consoleActions.showCommand(tab, 'addbookmark ' + tab.title);
|
||||
}
|
||||
return this.sendConsoleShowCommand(tab, 'addbookmark ');
|
||||
return consoleActions.showCommand(tab, 'addbookmark ');
|
||||
case operations.FIND_START:
|
||||
return browser.tabs.sendMessage(tab.id, {
|
||||
type: messages.CONSOLE_SHOW_FIND
|
||||
});
|
||||
return consoleActions.showFind(tab);
|
||||
case operations.CANCEL:
|
||||
return browser.tabs.sendMessage(tab.id, {
|
||||
type: messages.CONSOLE_HIDE,
|
||||
});
|
||||
return consoleActions.hide(tab);
|
||||
case operations.PAGE_SOURCE:
|
||||
return browser.tabs.create({
|
||||
await browser.tabs.create({
|
||||
url: 'view-source:' + tab.url,
|
||||
index: tab.index + 1,
|
||||
openerTabId: tab.id,
|
||||
});
|
||||
default:
|
||||
return Promise.resolve();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sendConsoleShowCommand(tab, command) {
|
||||
return browser.tabs.sendMessage(tab.id, {
|
||||
type: messages.CONSOLE_SHOW_COMMAND,
|
||||
command,
|
||||
});
|
||||
return { type: '' };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
import * as settingActions from 'background/actions/setting';
|
||||
import messages from 'shared/messages';
|
||||
import BackgroundComponent from 'background/components/background';
|
||||
import OperationComponent from 'background/components/operation';
|
||||
import TabComponent from 'background/components/tab';
|
||||
import IndicatorComponent from 'background/components/indicator';
|
||||
import reducers from 'background/reducers';
|
||||
import { createStore } from 'shared/store';
|
||||
import * as versions from 'shared/versions';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import promise from 'redux-promise';
|
||||
import * as versions from './shared/versions';
|
||||
|
||||
const store = createStore(reducers, (e, sender) => {
|
||||
console.error('Vim-Vixen:', e);
|
||||
if (sender) {
|
||||
return browser.tabs.sendMessage(sender.tab.id, {
|
||||
type: messages.CONSOLE_SHOW_ERROR,
|
||||
text: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
const store = createStore(
|
||||
reducers,
|
||||
applyMiddleware(promise),
|
||||
);
|
||||
|
||||
const checkAndNotifyUpdated = async() => {
|
||||
let updated = await versions.checkUpdated();
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
import settingReducer from './setting';
|
||||
import findReducer from './find';
|
||||
import tabReducer from './tab';
|
||||
import { combineReducers } from 'redux';
|
||||
import setting from './setting';
|
||||
import find from './find';
|
||||
import tab from './tab';
|
||||
|
||||
// Make setting reducer instead of re-use
|
||||
const defaultState = {
|
||||
setting: settingReducer(undefined, {}),
|
||||
find: findReducer(undefined, {}),
|
||||
tab: tabReducer(undefined, {}),
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action = {}) {
|
||||
return { ...state,
|
||||
setting: settingReducer(state.setting, action),
|
||||
find: findReducer(state.find, action),
|
||||
tab: tabReducer(state.tab, action), };
|
||||
}
|
||||
export default combineReducers({
|
||||
setting, find, tab,
|
||||
});
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
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);
|
||||
|
@ -74,20 +87,63 @@ const getBufferCompletions = async(command, keywords, excludePinned) => {
|
|||
];
|
||||
};
|
||||
|
||||
const getCompletions = (line, settings) => {
|
||||
let typedWords = line.trim().split(/ +/);
|
||||
let typing = '';
|
||||
if (!line.endsWith(' ')) {
|
||||
typing = typedWords.pop();
|
||||
}
|
||||
|
||||
if (typedWords.length === 0) {
|
||||
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([]);
|
||||
}
|
||||
let name = typedWords.shift();
|
||||
let keywords = typedWords.concat(typing).join(' ');
|
||||
return Promise.resolve([
|
||||
{
|
||||
name: 'Properties',
|
||||
items,
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
switch (name) {
|
||||
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':
|
||||
|
@ -108,12 +164,10 @@ const getCompletions = (line, settings) => {
|
|||
case 'bdelete':
|
||||
case 'bdeletes':
|
||||
return getBufferCompletions(name, keywords, true);
|
||||
case 'set':
|
||||
return getSetCompletions(name, keywords);
|
||||
}
|
||||
return Promise.resolve([]);
|
||||
};
|
||||
|
||||
const complete = (line, settings) => {
|
||||
return getCompletions(line, settings);
|
||||
};
|
||||
|
||||
export { complete };
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as storage from './storage';
|
||||
import * as releaseNotes from './release-notes';
|
||||
import manifest from '../../../manifest.json';
|
||||
import manifest from '../../../../manifest.json';
|
||||
|
||||
const NOTIFICATION_ID = 'vimvixen-update';
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import messages from 'shared/messages';
|
||||
import actions from 'console/actions';
|
||||
|
||||
const hide = () => {
|
||||
|
@ -34,11 +35,30 @@ const showInfo = (text) => {
|
|||
};
|
||||
|
||||
const hideCommand = () => {
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.CONSOLE_UNFOCUS,
|
||||
}), '*');
|
||||
return {
|
||||
type: actions.CONSOLE_HIDE_COMMAND,
|
||||
};
|
||||
};
|
||||
|
||||
const enterCommand = async(text) => {
|
||||
await browser.runtime.sendMessage({
|
||||
type: messages.CONSOLE_ENTER_COMMAND,
|
||||
text,
|
||||
});
|
||||
return hideCommand(text);
|
||||
};
|
||||
|
||||
const enterFind = (text) => {
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.CONSOLE_ENTER_FIND,
|
||||
text,
|
||||
}), '*');
|
||||
return hideCommand();
|
||||
};
|
||||
|
||||
const setConsoleText = (consoleText) => {
|
||||
return {
|
||||
type: actions.CONSOLE_SET_CONSOLE_TEXT,
|
||||
|
@ -46,11 +66,15 @@ const setConsoleText = (consoleText) => {
|
|||
};
|
||||
};
|
||||
|
||||
const setCompletions = (completionSource, completions) => {
|
||||
const getCompletions = async(text) => {
|
||||
let completions = await browser.runtime.sendMessage({
|
||||
type: messages.CONSOLE_QUERY_COMPLETIONS,
|
||||
text,
|
||||
});
|
||||
return {
|
||||
type: actions.CONSOLE_SET_COMPLETIONS,
|
||||
completionSource,
|
||||
completions,
|
||||
completionSource: text,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -68,5 +92,5 @@ const completionPrev = () => {
|
|||
|
||||
export {
|
||||
hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText,
|
||||
setCompletions, completionNext, completionPrev
|
||||
enterCommand, enterFind, getCompletions, completionNext, completionPrev
|
||||
};
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import messages from 'shared/messages';
|
||||
import * as consoleActions from 'console/actions/console';
|
||||
|
||||
const inputShownMode = (state) => {
|
||||
|
@ -26,15 +25,22 @@ export default class ConsoleComponent {
|
|||
|
||||
onBlur() {
|
||||
let state = this.store.getState();
|
||||
if (state.mode === 'command') {
|
||||
this.hideCommand();
|
||||
if (state.mode === 'command' || state.mode === 'find') {
|
||||
return this.store.dispatch(consoleActions.hideCommand());
|
||||
}
|
||||
}
|
||||
|
||||
doEnter(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return this.onEntered(e.target.value);
|
||||
|
||||
let state = this.store.getState();
|
||||
let value = e.target.value;
|
||||
if (state.mode === 'command') {
|
||||
return this.store.dispatch(consoleActions.enterCommand(value));
|
||||
} else if (state.mode === 'find') {
|
||||
return this.store.dispatch(consoleActions.enterFind(value));
|
||||
}
|
||||
}
|
||||
|
||||
selectNext(e) {
|
||||
|
@ -51,11 +57,11 @@ export default class ConsoleComponent {
|
|||
|
||||
onKeyDown(e) {
|
||||
if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) {
|
||||
return this.hideCommand();
|
||||
this.store.dispatch(consoleActions.hideCommand());
|
||||
}
|
||||
switch (e.keyCode) {
|
||||
case KeyboardEvent.DOM_VK_ESCAPE:
|
||||
return this.hideCommand();
|
||||
return this.store.dispatch(consoleActions.hideCommand());
|
||||
case KeyboardEvent.DOM_VK_RETURN:
|
||||
return this.doEnter(e);
|
||||
case KeyboardEvent.DOM_VK_TAB:
|
||||
|
@ -69,7 +75,7 @@ export default class ConsoleComponent {
|
|||
break;
|
||||
case KeyboardEvent.DOM_VK_OPEN_BRACKET:
|
||||
if (e.ctrlKey) {
|
||||
return this.hideCommand();
|
||||
return this.store.dispatch(consoleActions.hideCommand());
|
||||
}
|
||||
break;
|
||||
case KeyboardEvent.DOM_VK_M:
|
||||
|
@ -90,32 +96,10 @@ export default class ConsoleComponent {
|
|||
}
|
||||
}
|
||||
|
||||
onEntered(value) {
|
||||
let state = this.store.getState();
|
||||
if (state.mode === 'command') {
|
||||
browser.runtime.sendMessage({
|
||||
type: messages.CONSOLE_ENTER_COMMAND,
|
||||
text: value,
|
||||
});
|
||||
this.hideCommand();
|
||||
} else if (state.mode === 'find') {
|
||||
this.hideCommand();
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.CONSOLE_ENTER_FIND,
|
||||
text: value,
|
||||
}), '*');
|
||||
}
|
||||
}
|
||||
|
||||
async onInput(e) {
|
||||
this.store.dispatch(consoleActions.setConsoleText(e.target.value));
|
||||
|
||||
let source = e.target.value;
|
||||
let completions = await browser.runtime.sendMessage({
|
||||
type: messages.CONSOLE_QUERY_COMPLETIONS,
|
||||
text: source,
|
||||
});
|
||||
this.store.dispatch(consoleActions.setCompletions(source, completions));
|
||||
onInput(e) {
|
||||
let text = e.target.value;
|
||||
this.store.dispatch(consoleActions.setConsoleText(text));
|
||||
this.store.dispatch(consoleActions.getCompletions(text));
|
||||
}
|
||||
|
||||
onInputShown(state) {
|
||||
|
@ -126,17 +110,12 @@ export default class ConsoleComponent {
|
|||
window.focus();
|
||||
|
||||
if (state.mode === 'command') {
|
||||
this.onInput({ target: input });
|
||||
let text = state.consoleText;
|
||||
input.value = text;
|
||||
this.store.dispatch(consoleActions.getCompletions(text));
|
||||
}
|
||||
}
|
||||
|
||||
hideCommand() {
|
||||
this.store.dispatch(consoleActions.hideCommand());
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.CONSOLE_UNFOCUS,
|
||||
}), '*');
|
||||
}
|
||||
|
||||
update() {
|
||||
let state = this.store.getState();
|
||||
|
||||
|
|
|
@ -3,10 +3,14 @@ import messages from 'shared/messages';
|
|||
import CompletionComponent from 'console/components/completion';
|
||||
import ConsoleComponent from 'console/components/console';
|
||||
import reducers from 'console/reducers';
|
||||
import { createStore } from 'shared/store';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import promise from 'redux-promise';
|
||||
import * as consoleActions from 'console/actions/console';
|
||||
|
||||
const store = createStore(reducers);
|
||||
const store = createStore(
|
||||
reducers,
|
||||
applyMiddleware(promise),
|
||||
);
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
let wrapper = document.querySelector('#vimvixen-console-completion');
|
||||
|
|
|
@ -11,6 +11,9 @@ const defaultState = {
|
|||
};
|
||||
|
||||
const nextSelection = (state) => {
|
||||
if (state.completions.length === 0) {
|
||||
return [-1, -1];
|
||||
}
|
||||
if (state.groupSelection < 0) {
|
||||
return [0, 0];
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import messages from 'shared/messages';
|
||||
import actions from 'content/actions';
|
||||
|
||||
const enable = () => {
|
||||
return { type: actions.ADDON_ENABLE };
|
||||
const enable = () => setEnabled(true);
|
||||
|
||||
const disable = () => setEnabled(false);
|
||||
|
||||
const setEnabled = async(enabled) => {
|
||||
await browser.runtime.sendMessage({
|
||||
type: messages.ADDON_ENABLED_RESPONSE,
|
||||
enabled,
|
||||
});
|
||||
return {
|
||||
type: actions.ADDON_SET_ENABLED,
|
||||
enabled,
|
||||
};
|
||||
};
|
||||
|
||||
const disable = () => {
|
||||
return { type: actions.ADDON_DISABLE };
|
||||
};
|
||||
|
||||
const toggleEnabled = () => {
|
||||
return { type: actions.ADDON_TOGGLE_ENABLED };
|
||||
};
|
||||
|
||||
export { enable, disable, toggleEnabled };
|
||||
export { enable, disable, setEnabled };
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
export default {
|
||||
// Enable/disable
|
||||
ADDON_ENABLE: 'addon.enable',
|
||||
ADDON_DISABLE: 'addon.disable',
|
||||
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled',
|
||||
ADDON_SET_ENABLED: 'addon.set.enabled',
|
||||
|
||||
// Settings
|
||||
SETTING_SET: 'setting.set',
|
||||
|
|
|
@ -9,7 +9,7 @@ import * as addonActions from './addon';
|
|||
import * as properties from 'shared/settings/properties';
|
||||
|
||||
// eslint-disable-next-line complexity, max-lines-per-function
|
||||
const exec = (operation, repeat, settings) => {
|
||||
const exec = (operation, repeat, settings, addonEnabled) => {
|
||||
let smoothscroll = settings.properties.smoothscroll ||
|
||||
properties.defaults.smoothscroll;
|
||||
switch (operation.type) {
|
||||
|
@ -18,60 +18,80 @@ const exec = (operation, repeat, settings) => {
|
|||
case operations.ADDON_DISABLE:
|
||||
return addonActions.disable();
|
||||
case operations.ADDON_TOGGLE_ENABLED:
|
||||
return addonActions.toggleEnabled();
|
||||
return addonActions.setEnabled(!addonEnabled);
|
||||
case operations.FIND_NEXT:
|
||||
return window.top.postMessage(JSON.stringify({
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.FIND_NEXT,
|
||||
}), '*');
|
||||
break;
|
||||
case operations.FIND_PREV:
|
||||
return window.top.postMessage(JSON.stringify({
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.FIND_PREV,
|
||||
}), '*');
|
||||
break;
|
||||
case operations.SCROLL_VERTICALLY:
|
||||
return scrolls.scrollVertically(operation.count, smoothscroll, repeat);
|
||||
scrolls.scrollVertically(operation.count, smoothscroll, repeat);
|
||||
break;
|
||||
case operations.SCROLL_HORIZONALLY:
|
||||
return scrolls.scrollHorizonally(operation.count, smoothscroll, repeat);
|
||||
scrolls.scrollHorizonally(operation.count, smoothscroll, repeat);
|
||||
break;
|
||||
case operations.SCROLL_PAGES:
|
||||
return scrolls.scrollPages(operation.count, smoothscroll, repeat);
|
||||
scrolls.scrollPages(operation.count, smoothscroll, repeat);
|
||||
break;
|
||||
case operations.SCROLL_TOP:
|
||||
return scrolls.scrollTop(smoothscroll, repeat);
|
||||
scrolls.scrollTop(smoothscroll, repeat);
|
||||
break;
|
||||
case operations.SCROLL_BOTTOM:
|
||||
return scrolls.scrollBottom(smoothscroll, repeat);
|
||||
scrolls.scrollBottom(smoothscroll, repeat);
|
||||
break;
|
||||
case operations.SCROLL_HOME:
|
||||
return scrolls.scrollHome(smoothscroll, repeat);
|
||||
scrolls.scrollHome(smoothscroll, repeat);
|
||||
break;
|
||||
case operations.SCROLL_END:
|
||||
return scrolls.scrollEnd(smoothscroll, repeat);
|
||||
scrolls.scrollEnd(smoothscroll, repeat);
|
||||
break;
|
||||
case operations.FOLLOW_START:
|
||||
return window.top.postMessage(JSON.stringify({
|
||||
window.top.postMessage(JSON.stringify({
|
||||
type: messages.FOLLOW_START,
|
||||
newTab: operation.newTab,
|
||||
background: operation.background,
|
||||
}), '*');
|
||||
break;
|
||||
case operations.NAVIGATE_HISTORY_PREV:
|
||||
return navigates.historyPrev(window);
|
||||
navigates.historyPrev(window);
|
||||
break;
|
||||
case operations.NAVIGATE_HISTORY_NEXT:
|
||||
return navigates.historyNext(window);
|
||||
navigates.historyNext(window);
|
||||
break;
|
||||
case operations.NAVIGATE_LINK_PREV:
|
||||
return navigates.linkPrev(window);
|
||||
navigates.linkPrev(window);
|
||||
break;
|
||||
case operations.NAVIGATE_LINK_NEXT:
|
||||
return navigates.linkNext(window);
|
||||
navigates.linkNext(window);
|
||||
break;
|
||||
case operations.NAVIGATE_PARENT:
|
||||
return navigates.parent(window);
|
||||
navigates.parent(window);
|
||||
break;
|
||||
case operations.NAVIGATE_ROOT:
|
||||
return navigates.root(window);
|
||||
navigates.root(window);
|
||||
break;
|
||||
case operations.FOCUS_INPUT:
|
||||
return focuses.focusInput();
|
||||
focuses.focusInput();
|
||||
break;
|
||||
case operations.URLS_YANK:
|
||||
urls.yank(window);
|
||||
return consoleFrames.postInfo(window.document, 'Current url yanked');
|
||||
consoleFrames.postInfo(window.document, 'Current url yanked');
|
||||
break;
|
||||
case operations.URLS_PASTE:
|
||||
return urls.paste(window, operation.newTab ? operation.newTab : false);
|
||||
urls.paste(window, operation.newTab ? operation.newTab : false);
|
||||
break;
|
||||
default:
|
||||
browser.runtime.sendMessage({
|
||||
type: messages.BACKGROUND_OPERATION,
|
||||
operation,
|
||||
});
|
||||
}
|
||||
return { type: '' };
|
||||
};
|
||||
|
||||
export { exec };
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import actions from 'content/actions';
|
||||
import * as keyUtils from 'shared/utils/keys';
|
||||
import operations from 'shared/operations';
|
||||
import messages from 'shared/messages';
|
||||
|
||||
const reservedKeymaps = {
|
||||
'<Esc>': { type: operations.CANCEL },
|
||||
|
@ -26,4 +27,11 @@ const set = (value) => {
|
|||
};
|
||||
};
|
||||
|
||||
export { set };
|
||||
const load = async() => {
|
||||
let settings = await browser.runtime.sendMessage({
|
||||
type: messages.SETTINGS_QUERY,
|
||||
});
|
||||
return set(settings);
|
||||
};
|
||||
|
||||
export { set, load };
|
||||
|
|
|
@ -4,6 +4,7 @@ import FollowComponent from './follow';
|
|||
import * as settingActions from 'content/actions/setting';
|
||||
import messages from 'shared/messages';
|
||||
import * as addonActions from '../../actions/addon';
|
||||
import * as blacklists from 'shared/blacklists';
|
||||
|
||||
export default class Common {
|
||||
constructor(win, store) {
|
||||
|
@ -14,42 +15,34 @@ export default class Common {
|
|||
input.onKey(key => follow.key(key));
|
||||
input.onKey(key => keymapper.key(key));
|
||||
|
||||
this.win = win;
|
||||
this.store = store;
|
||||
this.prevEnabled = undefined;
|
||||
this.prevBlacklist = undefined;
|
||||
|
||||
this.reloadSettings();
|
||||
|
||||
messages.onMessage(this.onMessage.bind(this));
|
||||
store.subscribe(() => this.update());
|
||||
}
|
||||
|
||||
onMessage(message) {
|
||||
let { enabled } = this.store.getState().addon;
|
||||
switch (message.type) {
|
||||
case messages.SETTINGS_CHANGED:
|
||||
return this.reloadSettings();
|
||||
case messages.ADDON_TOGGLE_ENABLED:
|
||||
return this.store.dispatch(addonActions.toggleEnabled());
|
||||
this.store.dispatch(addonActions.setEnabled(!enabled));
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
let enabled = this.store.getState().addon.enabled;
|
||||
if (enabled !== this.prevEnabled) {
|
||||
this.prevEnabled = enabled;
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
type: messages.ADDON_ENABLED_RESPONSE,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async reloadSettings() {
|
||||
reloadSettings() {
|
||||
try {
|
||||
let settings = await browser.runtime.sendMessage({
|
||||
type: messages.SETTINGS_QUERY,
|
||||
this.store.dispatch(settingActions.load()).then(({ value: settings }) => {
|
||||
let enabled = !blacklists.includes(
|
||||
settings.blacklist, this.win.location.href
|
||||
);
|
||||
this.store.dispatch(addonActions.setEnabled(enabled));
|
||||
});
|
||||
this.store.dispatch(settingActions.set(settings));
|
||||
} catch (e) {
|
||||
// Sometime sendMessage fails when background script is not ready.
|
||||
console.warn(e);
|
||||
|
|
|
@ -20,6 +20,7 @@ export default class KeymapperComponent {
|
|||
this.store = store;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-statements
|
||||
key(key) {
|
||||
this.store.dispatch(inputActions.keyPress(key));
|
||||
|
||||
|
@ -47,8 +48,10 @@ export default class KeymapperComponent {
|
|||
return true;
|
||||
}
|
||||
let operation = keymaps.get(matched[0]);
|
||||
this.store.dispatch(operationActions.exec(
|
||||
operation, key.repeat, state.setting));
|
||||
let act = operationActions.exec(
|
||||
operation, key.repeat, state.setting, state.addon.enabled
|
||||
);
|
||||
this.store.dispatch(act);
|
||||
this.store.dispatch(inputActions.clearKeys());
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2,16 +2,13 @@ import CommonComponent from '../common';
|
|||
import FollowController from './follow-controller';
|
||||
import FindComponent from './find';
|
||||
import * as consoleFrames from '../../console-frames';
|
||||
import * as addonActions from '../../actions/addon';
|
||||
import messages from 'shared/messages';
|
||||
import * as re from 'shared/utils/re';
|
||||
|
||||
export default class TopContent {
|
||||
|
||||
constructor(win, store) {
|
||||
this.win = win;
|
||||
this.store = store;
|
||||
this.prevBlacklist = undefined;
|
||||
|
||||
new CommonComponent(win, store); // eslint-disable-line no-new
|
||||
new FollowController(win, store); // eslint-disable-line no-new
|
||||
|
@ -21,32 +18,6 @@ export default class TopContent {
|
|||
consoleFrames.initialize(this.win.document);
|
||||
|
||||
messages.onMessage(this.onMessage.bind(this));
|
||||
|
||||
this.store.subscribe(() => this.update());
|
||||
}
|
||||
|
||||
update() {
|
||||
let blacklist = this.store.getState().setting.blacklist;
|
||||
if (JSON.stringify(this.prevBlacklist) !== JSON.stringify(blacklist)) {
|
||||
this.disableIfBlack(blacklist);
|
||||
this.prevBlacklist = blacklist;
|
||||
}
|
||||
}
|
||||
|
||||
disableIfBlack(blacklist) {
|
||||
let loc = this.win.location;
|
||||
let partial = loc.host + loc.pathname;
|
||||
let matched = blacklist
|
||||
.map((item) => {
|
||||
let pattern = item.includes('/') ? item : item + '/*';
|
||||
return re.fromWildcard(pattern);
|
||||
})
|
||||
.some(regex => regex.test(partial));
|
||||
if (matched) {
|
||||
this.store.dispatch(addonActions.disable());
|
||||
} else {
|
||||
this.store.dispatch(addonActions.enable());
|
||||
}
|
||||
}
|
||||
|
||||
onMessage(message) {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import './console-frame.scss';
|
||||
import { createStore } from 'shared/store';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import promise from 'redux-promise';
|
||||
import reducers from 'content/reducers';
|
||||
import TopContentComponent from './components/top-content';
|
||||
import FrameContentComponent from './components/frame-content';
|
||||
|
||||
const store = createStore(reducers);
|
||||
const store = createStore(
|
||||
reducers,
|
||||
applyMiddleware(promise),
|
||||
);
|
||||
|
||||
if (window.self === window.top) {
|
||||
new TopContentComponent(window, store); // eslint-disable-line no-new
|
||||
|
|
|
@ -6,15 +6,9 @@ const defaultState = {
|
|||
|
||||
export default function reducer(state = defaultState, action = {}) {
|
||||
switch (action.type) {
|
||||
case actions.ADDON_ENABLE:
|
||||
case actions.ADDON_SET_ENABLED:
|
||||
return { ...state,
|
||||
enabled: true, };
|
||||
case actions.ADDON_DISABLE:
|
||||
return { ...state,
|
||||
enabled: false, };
|
||||
case actions.ADDON_TOGGLE_ENABLED:
|
||||
return { ...state,
|
||||
enabled: !state.enabled, };
|
||||
enabled: action.enabled, };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,10 @@
|
|||
import addonReducer from './addon';
|
||||
import findReducer from './find';
|
||||
import settingReducer from './setting';
|
||||
import inputReducer from './input';
|
||||
import followControllerReducer from './follow-controller';
|
||||
import { combineReducers } from 'redux';
|
||||
import addon from './addon';
|
||||
import find from './find';
|
||||
import setting from './setting';
|
||||
import input from './input';
|
||||
import followController from './follow-controller';
|
||||
|
||||
// Make setting reducer instead of re-use
|
||||
const defaultState = {
|
||||
addon: addonReducer(undefined, {}),
|
||||
find: findReducer(undefined, {}),
|
||||
setting: settingReducer(undefined, {}),
|
||||
input: inputReducer(undefined, {}),
|
||||
followController: followControllerReducer(undefined, {}),
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action = {}) {
|
||||
return {
|
||||
...state,
|
||||
addon: addonReducer(state.addon, action),
|
||||
find: findReducer(state.find, action),
|
||||
setting: settingReducer(state.setting, action),
|
||||
input: inputReducer(state.input, action),
|
||||
followController: followControllerReducer(state.followController, action),
|
||||
};
|
||||
}
|
||||
export default combineReducers({
|
||||
addon, find, setting, input, followController,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
export default {
|
||||
// Settings
|
||||
SETTING_SET_SETTINGS: 'setting.set.settings',
|
||||
SETTING_SHOW_ERROR: 'setting.show.error',
|
||||
SETTING_SWITCH_TO_FORM: 'setting.switch.to.form',
|
||||
SETTING_SWITCH_TO_JSON: 'setting.switch.to.json',
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import actions from 'settings/actions';
|
||||
import messages from 'shared/messages';
|
||||
import DefaultSettings from 'shared/settings/default';
|
||||
import * as settingsStorage from 'shared/settings/storage';
|
||||
import * as validator from 'shared/settings/validator';
|
||||
import KeymapsForm from '../components/form/keymaps-form';
|
||||
import * as settingsValues from 'shared/settings/values';
|
||||
import * as settingsStorage from 'shared/settings/storage';
|
||||
|
||||
const load = async() => {
|
||||
let settings = await settingsStorage.loadRaw();
|
||||
|
@ -10,6 +11,18 @@ const load = async() => {
|
|||
};
|
||||
|
||||
const save = async(settings) => {
|
||||
try {
|
||||
if (settings.source === 'json') {
|
||||
let value = JSON.parse(settings.json);
|
||||
validator.validate(value);
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
type: actions.SETTING_SHOW_ERROR,
|
||||
error: e.toString(),
|
||||
json: settings.json,
|
||||
};
|
||||
}
|
||||
await settingsStorage.save(settings);
|
||||
await browser.runtime.sendMessage({
|
||||
type: messages.SETTINGS_RELOAD
|
||||
|
@ -17,21 +30,39 @@ const save = async(settings) => {
|
|||
return set(settings);
|
||||
};
|
||||
|
||||
const set = (settings) => {
|
||||
let value = JSON.parse(DefaultSettings.json);
|
||||
if (settings.source === 'json') {
|
||||
value = settingsValues.valueFromJson(settings.json);
|
||||
} else if (settings.source === 'form') {
|
||||
value = settingsValues.valueFromForm(settings.form);
|
||||
const switchToForm = (json) => {
|
||||
try {
|
||||
validator.validate(JSON.parse(json));
|
||||
// AllowdOps filters operations, this is dirty dependency
|
||||
let form = settingsValues.formFromJson(json, KeymapsForm.AllowdOps);
|
||||
return {
|
||||
type: actions.SETTING_SWITCH_TO_FORM,
|
||||
form,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
type: actions.SETTING_SHOW_ERROR,
|
||||
error: e.toString(),
|
||||
json,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const switchToJson = (form) => {
|
||||
let json = settingsValues.jsonFromForm(form);
|
||||
return {
|
||||
type: actions.SETTING_SWITCH_TO_JSON,
|
||||
json,
|
||||
};
|
||||
};
|
||||
|
||||
const set = (settings) => {
|
||||
return {
|
||||
type: actions.SETTING_SET_SETTINGS,
|
||||
source: settings.source,
|
||||
json: settings.json,
|
||||
form: settings.form,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export { load, save };
|
||||
export { load, save, switchToForm, switchToJson };
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import './site.scss';
|
||||
import { h, Component } from 'preact';
|
||||
import { connect } from 'preact-redux';
|
||||
import Input from './ui/input';
|
||||
import SearchForm from './form/search-form';
|
||||
import KeymapsForm from './form/keymaps-form';
|
||||
|
@ -7,63 +8,36 @@ import BlacklistForm from './form/blacklist-form';
|
|||
import PropertiesForm from './form/properties-form';
|
||||
import * as properties from 'shared/settings/properties';
|
||||
import * as settingActions from 'settings/actions/setting';
|
||||
import * as validator from 'shared/settings/validator';
|
||||
import * as settingsValues from 'shared/settings/values';
|
||||
|
||||
const DO_YOU_WANT_TO_CONTINUE =
|
||||
'Some settings in JSON can be lost when migrating. ' +
|
||||
'Do you want to continue?';
|
||||
|
||||
class SettingsComponent extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
settings: {
|
||||
json: '',
|
||||
},
|
||||
errors: {
|
||||
json: '',
|
||||
}
|
||||
};
|
||||
this.context.store.subscribe(this.stateChanged.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.context.store.dispatch(settingActions.load());
|
||||
this.props.dispatch(settingActions.load());
|
||||
}
|
||||
|
||||
stateChanged() {
|
||||
let settings = this.context.store.getState();
|
||||
this.setState({
|
||||
settings: {
|
||||
source: settings.source,
|
||||
json: settings.json,
|
||||
form: settings.form,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderFormFields() {
|
||||
renderFormFields(form) {
|
||||
return <div>
|
||||
<fieldset>
|
||||
<legend>Keybindings</legend>
|
||||
<KeymapsForm
|
||||
value={this.state.settings.form.keymaps}
|
||||
value={form.keymaps}
|
||||
onChange={value => this.bindForm('keymaps', value)}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Search Engines</legend>
|
||||
<SearchForm
|
||||
value={this.state.settings.form.search}
|
||||
value={form.search}
|
||||
onChange={value => this.bindForm('search', value)}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Blacklist</legend>
|
||||
<BlacklistForm
|
||||
value={this.state.settings.form.blacklist}
|
||||
value={form.blacklist}
|
||||
onChange={value => this.bindForm('blacklist', value)}
|
||||
/>
|
||||
</fieldset>
|
||||
|
@ -71,33 +45,33 @@ class SettingsComponent extends Component {
|
|||
<legend>Properties</legend>
|
||||
<PropertiesForm
|
||||
types={properties.types}
|
||||
value={this.state.settings.form.properties}
|
||||
value={form.properties}
|
||||
onChange={value => this.bindForm('properties', value)}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>;
|
||||
}
|
||||
|
||||
renderJsonFields() {
|
||||
renderJsonFields(json, error) {
|
||||
return <div>
|
||||
<Input
|
||||
type='textarea'
|
||||
name='json'
|
||||
label='Plain JSON'
|
||||
spellCheck='false'
|
||||
error={this.state.errors.json}
|
||||
onChange={this.bindValue.bind(this)}
|
||||
value={this.state.settings.json}
|
||||
error={error}
|
||||
onChange={this.bindJson.bind(this)}
|
||||
value={json}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
let fields = null;
|
||||
if (this.state.settings.source === 'form') {
|
||||
fields = this.renderFormFields();
|
||||
} else if (this.state.settings.source === 'json') {
|
||||
fields = this.renderJsonFields();
|
||||
if (this.props.source === 'form') {
|
||||
fields = this.renderFormFields(this.props.form);
|
||||
} else if (this.props.source === 'json') {
|
||||
fields = this.renderJsonFields(this.props.json, this.props.error);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
|
@ -108,7 +82,7 @@ class SettingsComponent extends Component {
|
|||
id='setting-source-form'
|
||||
name='source'
|
||||
label='Use form'
|
||||
checked={this.state.settings.source === 'form'}
|
||||
checked={this.props.source === 'form'}
|
||||
value='form'
|
||||
onChange={this.bindSource.bind(this)} />
|
||||
|
||||
|
@ -116,7 +90,7 @@ class SettingsComponent extends Component {
|
|||
type='radio'
|
||||
name='source'
|
||||
label='Use plain JSON'
|
||||
checked={this.state.settings.source === 'json'}
|
||||
checked={this.props.source === 'json'}
|
||||
value='json'
|
||||
onChange={this.bindSource.bind(this)} />
|
||||
|
||||
|
@ -126,98 +100,44 @@ class SettingsComponent extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
validate(target) {
|
||||
if (target.name === 'json') {
|
||||
let settings = JSON.parse(target.value);
|
||||
validator.validate(settings);
|
||||
}
|
||||
}
|
||||
|
||||
validateValue(e) {
|
||||
let next = { ...this.state };
|
||||
|
||||
next.errors.json = '';
|
||||
try {
|
||||
this.validate(e.target);
|
||||
} catch (err) {
|
||||
next.errors.json = err.message;
|
||||
}
|
||||
next.settings[e.target.name] = e.target.value;
|
||||
}
|
||||
|
||||
bindForm(name, value) {
|
||||
let next = { ...this.state,
|
||||
settings: { ...this.state.settings,
|
||||
form: { ...this.state.settings.form }}};
|
||||
next.settings.form[name] = value;
|
||||
this.setState(next);
|
||||
this.context.store.dispatch(settingActions.save(next.settings));
|
||||
let settings = {
|
||||
source: this.props.source,
|
||||
json: this.props.json,
|
||||
form: { ...this.props.form },
|
||||
};
|
||||
settings.form[name] = value;
|
||||
this.props.dispatch(settingActions.save(settings));
|
||||
}
|
||||
|
||||
bindValue(e) {
|
||||
let next = { ...this.state };
|
||||
let error = false;
|
||||
|
||||
next.errors.json = '';
|
||||
try {
|
||||
this.validate(e.target);
|
||||
} catch (err) {
|
||||
next.errors.json = err.message;
|
||||
error = true;
|
||||
}
|
||||
next.settings[e.target.name] = e.target.value;
|
||||
|
||||
this.setState(this.state);
|
||||
if (!error) {
|
||||
this.context.store.dispatch(settingActions.save(next.settings));
|
||||
}
|
||||
}
|
||||
|
||||
migrateToForm() {
|
||||
let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
|
||||
if (!b) {
|
||||
this.setState(this.state);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
validator.validate(JSON.parse(this.state.settings.json));
|
||||
} catch (err) {
|
||||
this.setState(this.state);
|
||||
return;
|
||||
}
|
||||
|
||||
let form = settingsValues.formFromJson(
|
||||
this.state.settings.json, KeymapsForm.AllowdOps);
|
||||
let next = { ...this.state };
|
||||
next.settings.form = form;
|
||||
next.settings.source = 'form';
|
||||
next.errors.json = '';
|
||||
|
||||
this.setState(next);
|
||||
this.context.store.dispatch(settingActions.save(next.settings));
|
||||
}
|
||||
|
||||
migrateToJson() {
|
||||
let json = settingsValues.jsonFromForm(this.state.settings.form);
|
||||
let next = { ...this.state };
|
||||
next.settings.json = json;
|
||||
next.settings.source = 'json';
|
||||
next.errors.json = '';
|
||||
|
||||
this.setState(next);
|
||||
this.context.store.dispatch(settingActions.save(next.settings));
|
||||
bindJson(e) {
|
||||
let settings = {
|
||||
source: this.props.source,
|
||||
json: e.target.value,
|
||||
form: this.props.form,
|
||||
};
|
||||
this.props.dispatch(settingActions.save(settings));
|
||||
}
|
||||
|
||||
bindSource(e) {
|
||||
let from = this.state.settings.source;
|
||||
let from = this.props.source;
|
||||
let to = e.target.value;
|
||||
|
||||
if (from === 'form' && to === 'json') {
|
||||
this.migrateToJson();
|
||||
this.props.dispatch(settingActions.switchToJson(this.props.form));
|
||||
} else if (from === 'json' && to === 'form') {
|
||||
this.migrateToForm();
|
||||
let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
|
||||
if (!b) {
|
||||
return;
|
||||
}
|
||||
this.props.dispatch(settingActions.switchToForm(this.props.json));
|
||||
}
|
||||
|
||||
let settings = this.context.store.getState();
|
||||
this.props.dispatch(settingActions.save(settings));
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsComponent;
|
||||
const mapStateToProps = state => state;
|
||||
|
||||
export default connect(mapStateToProps)(SettingsComponent);
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import { h, render } from 'preact';
|
||||
import SettingsComponent from './components';
|
||||
import reducer from 'settings/reducers/setting';
|
||||
import Provider from 'shared/store/provider';
|
||||
import { createStore } from 'shared/store';
|
||||
import reducer from './reducers/setting';
|
||||
import { Provider } from 'preact-redux';
|
||||
import promise from 'redux-promise';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
|
||||
const store = createStore(reducer);
|
||||
const store = createStore(
|
||||
reducer,
|
||||
applyMiddleware(promise),
|
||||
);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let wrapper = document.getElementById('vimvixen-settings');
|
||||
|
|
|
@ -4,20 +4,33 @@ const defaultState = {
|
|||
source: '',
|
||||
json: '',
|
||||
form: null,
|
||||
value: {}
|
||||
error: '',
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action = {}) {
|
||||
switch (action.type) {
|
||||
case actions.SETTING_SET_SETTINGS:
|
||||
return {
|
||||
return { ...state,
|
||||
source: action.source,
|
||||
json: action.json,
|
||||
form: action.form,
|
||||
value: action.value,
|
||||
};
|
||||
errors: '',
|
||||
error: '', };
|
||||
case actions.SETTING_SHOW_ERROR:
|
||||
return { ...state,
|
||||
error: action.text,
|
||||
json: action.json, };
|
||||
case actions.SETTING_SWITCH_TO_FORM:
|
||||
return { ...state,
|
||||
error: '',
|
||||
source: 'form',
|
||||
form: action.form, };
|
||||
case actions.SETTING_SWITCH_TO_JSON:
|
||||
return { ...state,
|
||||
error: '',
|
||||
source: 'json',
|
||||
json: action.json, };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
src/shared/blacklists.js
Normal file
13
src/shared/blacklists.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import * as re from 'shared/utils/re';
|
||||
|
||||
const includes = (blacklist, url) => {
|
||||
let u = new URL(url);
|
||||
return blacklist.some((item) => {
|
||||
if (!item.includes('/')) {
|
||||
return re.fromWildcard(item).test(u.hostname);
|
||||
}
|
||||
return re.fromWildcard(item).test(u.hostname + u.pathname);
|
||||
});
|
||||
};
|
||||
|
||||
export { includes };
|
11
src/shared/commands/docs.js
Normal file
11
src/shared/commands/docs.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export default {
|
||||
set: 'Set a value of the property',
|
||||
open: 'Open a URL or search by keywords in current tab',
|
||||
tabopen: 'Open a URL or search by keywords in new tab',
|
||||
winopen: 'Open a URL or search by keywords in new window',
|
||||
buffer: 'Sekect tabs by matched keywords',
|
||||
bdelete: 'Close a certain tab matched by keywords',
|
||||
bdeletes: 'Close all tabs matched by keywords',
|
||||
quit: 'Close the current tab',
|
||||
quitall: 'Close all tabs',
|
||||
};
|
|
@ -15,4 +15,10 @@ const defaults = {
|
|||
adjacenttab: true,
|
||||
};
|
||||
|
||||
export { types, defaults };
|
||||
const docs = {
|
||||
hintchars: 'Hint characters on follow mode',
|
||||
smoothscroll: 'smooth scroll',
|
||||
adjacenttab: 'open adjacent tabs',
|
||||
};
|
||||
|
||||
export { types, defaults, docs };
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
class Store {
|
||||
constructor(reducer, catcher) {
|
||||
this.reducer = reducer;
|
||||
this.catcher = catcher;
|
||||
this.subscribers = [];
|
||||
try {
|
||||
this.state = this.reducer(undefined, {});
|
||||
} catch (e) {
|
||||
catcher(e);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(action, sender) {
|
||||
if (action instanceof Promise) {
|
||||
action.then((a) => {
|
||||
this.transitNext(a, sender);
|
||||
}).catch((e) => {
|
||||
this.catcher(e, sender);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
this.transitNext(action, sender);
|
||||
} catch (e) {
|
||||
this.catcher(e, sender);
|
||||
}
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
subscribe(callback) {
|
||||
this.subscribers.push(callback);
|
||||
}
|
||||
|
||||
transitNext(action, sender) {
|
||||
let newState = this.reducer(this.state, action);
|
||||
if (JSON.stringify(this.state) !== JSON.stringify(newState)) {
|
||||
this.state = newState;
|
||||
this.subscribers.forEach(f => f(sender));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const empty = () => {};
|
||||
|
||||
const createStore = (reducer, catcher = empty) => {
|
||||
return new Store(reducer, catcher);
|
||||
};
|
||||
|
||||
export { createStore };
|
|
@ -1,15 +0,0 @@
|
|||
import { h, Component } from 'preact';
|
||||
|
||||
class Provider extends Component {
|
||||
getChildContext() {
|
||||
return { store: this.props.store };
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
{ this.props.children }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Provider;
|
|
@ -1,5 +1,5 @@
|
|||
import * as versions from 'shared/versions';
|
||||
import manifest from '../../../manifest.json';
|
||||
import * as versions from 'background/shared/versions';
|
||||
import manifest from '../../../../manifest.json';
|
||||
|
||||
describe("shared/versions/storage", () => {
|
||||
describe('#checkUpdated', () => {
|
|
@ -1,4 +1,4 @@
|
|||
import * as storage from 'shared/versions/storage';
|
||||
import * as storage from 'background/shared/versions/storage';
|
||||
|
||||
describe("shared/versions/storage", () => {
|
||||
describe('#load', () => {
|
|
@ -23,14 +23,6 @@ describe("console actions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("showInfo", () => {
|
||||
it('create CONSOLE_SHOW_INFO action', () => {
|
||||
let action = consoleActions.showInfo('an info');
|
||||
expect(action.type).to.equal(actions.CONSOLE_SHOW_INFO);
|
||||
expect(action.text).to.equal('an info');
|
||||
});
|
||||
});
|
||||
|
||||
describe("showError", () => {
|
||||
it('create CONSOLE_SHOW_ERROR action', () => {
|
||||
let action = consoleActions.showError('an error');
|
||||
|
@ -39,6 +31,14 @@ describe("console actions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("showInfo", () => {
|
||||
it('create CONSOLE_SHOW_INFO action', () => {
|
||||
let action = consoleActions.showInfo('an info');
|
||||
expect(action.type).to.equal(actions.CONSOLE_SHOW_INFO);
|
||||
expect(action.text).to.equal('an info');
|
||||
});
|
||||
});
|
||||
|
||||
describe("hideCommand", () => {
|
||||
it('create CONSOLE_HIDE_COMMAND action', () => {
|
||||
let action = consoleActions.hideCommand();
|
||||
|
@ -54,15 +54,6 @@ describe("console actions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("setCompletions", () => {
|
||||
it('create CONSOLE_SET_COMPLETIONS action', () => {
|
||||
let action = consoleActions.setCompletions('query', [1, 2, 3]);
|
||||
expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS);
|
||||
expect(action.completionSource).to.deep.equal('query');
|
||||
expect(action.completions).to.deep.equal([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("completionPrev", () => {
|
||||
it('create CONSOLE_COMPLETION_PREV action', () => {
|
||||
let action = consoleActions.completionPrev();
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import actions from 'content/actions';
|
||||
import * as addonActions from 'content/actions/addon';
|
||||
|
||||
describe("addon actions", () => {
|
||||
describe("enable", () => {
|
||||
it('create ADDON_ENABLE action', () => {
|
||||
let action = addonActions.enable();
|
||||
expect(action.type).to.equal(actions.ADDON_ENABLE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("disable", () => {
|
||||
it('create ADDON_DISABLE action', () => {
|
||||
let action = addonActions.disable();
|
||||
expect(action.type).to.equal(actions.ADDON_DISABLE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggle", () => {
|
||||
it('create ADDON_TOGGLE_ENABLED action', () => {
|
||||
let action = addonActions.toggleEnabled();
|
||||
expect(action.type).to.equal(actions.ADDON_TOGGLE_ENABLED);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,31 +7,11 @@ describe("addon reducer", () => {
|
|||
expect(state).to.have.property('enabled', true);
|
||||
});
|
||||
|
||||
it('return next state for ADDON_ENABLE', () => {
|
||||
let action = { type: actions.ADDON_ENABLE};
|
||||
it('return next state for ADDON_SET_ENABLED', () => {
|
||||
let action = { type: actions.ADDON_SET_ENABLED, enabled: true };
|
||||
let prev = { enabled: false };
|
||||
let state = addonReducer(prev, action);
|
||||
|
||||
expect(state.enabled).is.equal(true);
|
||||
});
|
||||
|
||||
it('return next state for ADDON_DISABLE', () => {
|
||||
let action = { type: actions.ADDON_DISABLE};
|
||||
let prev = { enabled: true };
|
||||
let state = addonReducer(prev, action);
|
||||
|
||||
expect(state.enabled).is.equal(false);
|
||||
});
|
||||
|
||||
it('return next state for ADDON_TOGGLE_ENABLED', () => {
|
||||
let action = { type: actions.ADDON_TOGGLE_ENABLED };
|
||||
let state = { enabled: false };
|
||||
|
||||
state = addonReducer(state, action);
|
||||
expect(state.enabled).is.equal(true);
|
||||
|
||||
state = addonReducer(state, action);
|
||||
expect(state.enabled).is.equal(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,21 +1,55 @@
|
|||
import actions from 'settings/actions';
|
||||
import settingReducer from 'settings/reducers/setting';
|
||||
|
||||
describe("setting reducer", () => {
|
||||
describe("settings setting reducer", () => {
|
||||
it('return the initial state', () => {
|
||||
let state = settingReducer(undefined, {});
|
||||
expect(state).to.have.deep.property('json', '');
|
||||
expect(state).to.have.deep.property('value', {});
|
||||
expect(state).to.have.deep.property('form', null);
|
||||
expect(state).to.have.deep.property('error', '');
|
||||
});
|
||||
|
||||
it('return next state for SETTING_SET_SETTINGS', () => {
|
||||
let action = {
|
||||
type: actions.SETTING_SET_SETTINGS,
|
||||
source: 'json',
|
||||
json: '{ "key": "value" }',
|
||||
value: { key: 123 },
|
||||
form: {},
|
||||
};
|
||||
let state = settingReducer(undefined, action);
|
||||
expect(state).to.have.deep.property('source', 'json');
|
||||
expect(state).to.have.deep.property('json', '{ "key": "value" }');
|
||||
expect(state).to.have.deep.property('value', { key: 123 });
|
||||
expect(state).to.have.deep.property('form', {});
|
||||
});
|
||||
|
||||
it('return next state for SETTING_SHOW_ERROR', () => {
|
||||
let action = {
|
||||
type: actions.SETTING_SHOW_ERROR,
|
||||
text: 'bad value',
|
||||
json: '{}',
|
||||
};
|
||||
let state = settingReducer(undefined, action);
|
||||
expect(state).to.have.deep.property('error', 'bad value');
|
||||
expect(state).to.have.deep.property('json', '{}');
|
||||
});
|
||||
|
||||
it('return next state for SETTING_SWITCH_TO_FORM', () => {
|
||||
let action = {
|
||||
type: actions.SETTING_SWITCH_TO_FORM,
|
||||
form: {},
|
||||
};
|
||||
let state = settingReducer(undefined, action);
|
||||
expect(state).to.have.deep.property('form', {});
|
||||
expect(state).to.have.deep.property('source', 'form');
|
||||
});
|
||||
|
||||
it('return next state for SETTING_SWITCH_TO_JSON', () => {
|
||||
let action = {
|
||||
type: actions.SETTING_SWITCH_TO_JSON,
|
||||
json: '{}',
|
||||
};
|
||||
let state = settingReducer(undefined, action);
|
||||
expect(state).to.have.deep.property('json', '{}');
|
||||
expect(state).to.have.deep.property('source', 'json');
|
||||
});
|
||||
});
|
||||
|
|
42
test/shared/blacklists.test.js
Normal file
42
test/shared/blacklists.test.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { includes } from 'shared/blacklists';
|
||||
|
||||
describe("shared/blacklist", () => {
|
||||
it('matches by *', () => {
|
||||
let blacklist = ['*'];
|
||||
|
||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
|
||||
})
|
||||
|
||||
it('matches by hostname', () => {
|
||||
let blacklist = ['github.com'];
|
||||
|
||||
expect(includes(blacklist, 'https://github.com')).to.be.true;
|
||||
expect(includes(blacklist, 'https://gist.github.com')).to.be.false;
|
||||
expect(includes(blacklist, 'https://github.com/ueokande')).to.be.true;
|
||||
expect(includes(blacklist, 'https://github.org')).to.be.false;
|
||||
expect(includes(blacklist, 'https://google.com/search?q=github.org')).to.be.false;
|
||||
})
|
||||
|
||||
it('matches by hostname with wildcard', () => {
|
||||
let blacklist = ['*.github.com'];
|
||||
|
||||
expect(includes(blacklist, 'https://github.com')).to.be.false;
|
||||
expect(includes(blacklist, 'https://gist.github.com')).to.be.true;
|
||||
})
|
||||
|
||||
it('matches by path', () => {
|
||||
let blacklist = ['github.com/abc'];
|
||||
|
||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
|
||||
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.false;
|
||||
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false;
|
||||
})
|
||||
|
||||
it('matches by path with wildcard', () => {
|
||||
let blacklist = ['github.com/abc*'];
|
||||
|
||||
expect(includes(blacklist, 'https://github.com/abc')).to.be.true;
|
||||
expect(includes(blacklist, 'https://github.com/abcdef')).to.be.true;
|
||||
expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false;
|
||||
})
|
||||
});
|
|
@ -1,110 +0,0 @@
|
|||
import { createStore } from 'shared/store';
|
||||
|
||||
describe("Store class", () => {
|
||||
const reducer = (state, action) => {
|
||||
if (state == undefined) {
|
||||
return 0;
|
||||
}
|
||||
return state + action;
|
||||
};
|
||||
|
||||
describe("#dispatch", () => {
|
||||
it('transit status by immediate action', () => {
|
||||
let store = createStore(reducer);
|
||||
store.dispatch(10);
|
||||
expect(store.getState()).to.equal(10);
|
||||
|
||||
store.dispatch(-20);
|
||||
expect(store.getState()).to.equal(-10);
|
||||
});
|
||||
|
||||
it('returns next state by immediate action', () => {
|
||||
let store = createStore(reducer);
|
||||
let dispatchedAction = store.dispatch(11);
|
||||
expect(dispatchedAction).to.equal(11);
|
||||
});
|
||||
|
||||
it('transit status by Promise action', () => {
|
||||
let store = createStore(reducer);
|
||||
let p1 = Promise.resolve(10);
|
||||
|
||||
return store.dispatch(p1).then(() => {
|
||||
expect(store.getState()).to.equal(10);
|
||||
}).then(() => {
|
||||
store.dispatch(Promise.resolve(-20));
|
||||
}).then(() => {
|
||||
expect(store.getState()).to.equal(-10);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns next state by promise action', () => {
|
||||
let store = createStore(reducer);
|
||||
let dispatchedAction = store.dispatch(Promise.resolve(11));
|
||||
return dispatchedAction.then((value) => {
|
||||
expect(value).to.equal(11);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#subscribe", () => {
|
||||
it('invoke callback', (done) => {
|
||||
let store = createStore(reducer);
|
||||
store.subscribe(() => {
|
||||
expect(store.getState()).to.equal(15);
|
||||
done();
|
||||
});
|
||||
store.dispatch(15);
|
||||
});
|
||||
|
||||
it('propagate sender object', (done) => {
|
||||
let store = createStore(reducer);
|
||||
store.subscribe((sender) => {
|
||||
expect(sender).to.equal('sender');
|
||||
done();
|
||||
});
|
||||
store.dispatch(15, 'sender');
|
||||
});
|
||||
})
|
||||
|
||||
describe("catcher", () => {
|
||||
it('catch an error in reducer on initializing by immediate action', (done) => {
|
||||
let store = createStore(() => {
|
||||
throw new Error();
|
||||
}, (e) => {
|
||||
expect(e).to.be.an('error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('catch an error in reducer on initializing by immediate action', (done) => {
|
||||
let store = createStore((state, action) => {
|
||||
if (state === undefined) return 0;
|
||||
throw new Error();
|
||||
}, (e) => {
|
||||
expect(e).to.be.an('error');
|
||||
done();
|
||||
});
|
||||
store.dispatch(20);
|
||||
});
|
||||
|
||||
it('catch an error in reducer on initializing by promise action', (done) => {
|
||||
let store = createStore((state, action) => {
|
||||
if (state === undefined) return 0;
|
||||
throw new Error();
|
||||
}, (e) => {
|
||||
expect(e).to.be.an('error');
|
||||
done();
|
||||
});
|
||||
store.dispatch(Promise.resolve(20));
|
||||
});
|
||||
|
||||
it('catch an error in promise action', (done) => {
|
||||
let store = createStore((state, action) => 0, (e) => {
|
||||
expect(e).to.be.an('error');
|
||||
done();
|
||||
});
|
||||
store.dispatch(new Promise(() => { throw new Error() }));
|
||||
});
|
||||
})
|
||||
});
|
||||
|
Reference in a new issue