aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json43
-rw-r--r--package.json3
-rw-r--r--src/background/actions/command.js68
-rw-r--r--src/background/actions/console.js41
-rw-r--r--src/background/actions/tab.js11
-rw-r--r--src/background/components/operation.js94
-rw-r--r--src/background/index.js17
-rw-r--r--src/console/actions/console.js30
-rw-r--r--src/console/components/console.js61
-rw-r--r--src/console/index.js8
-rw-r--r--src/content/actions/addon.js22
-rw-r--r--src/content/actions/index.js4
-rw-r--r--src/content/actions/operation.js62
-rw-r--r--src/content/actions/setting.js10
-rw-r--r--src/content/components/common/index.js29
-rw-r--r--src/content/components/common/keymapper.js7
-rw-r--r--src/content/components/top-content/index.js29
-rw-r--r--src/content/index.js8
-rw-r--r--src/content/reducers/addon.js10
-rw-r--r--src/settings/actions/index.js3
-rw-r--r--src/settings/actions/setting.js51
-rw-r--r--src/settings/components/index.jsx168
-rw-r--r--src/settings/index.jsx12
-rw-r--r--src/settings/reducers/setting.js23
-rw-r--r--src/shared/blacklists.js13
-rw-r--r--src/shared/store/index.js53
-rw-r--r--src/shared/store/provider.jsx15
-rw-r--r--test/console/actions/console.test.js25
-rw-r--r--test/content/actions/addon.test.js25
-rw-r--r--test/content/reducers/addon.test.js24
-rw-r--r--test/settings/reducers/setting.test.js42
-rw-r--r--test/shared/blacklists.test.js42
-rw-r--r--test/shared/store/index.test.js110
33 files changed, 548 insertions, 615 deletions
diff --git a/package-lock.json b/package-lock.json
index 7c59307..13dd81f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5352,6 +5352,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",
@@ -10869,6 +10878,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",
@@ -11418,6 +11433,34 @@
}
}
},
+ "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",
diff --git a/package.json b/package.json
index cf0d241..b5c23ff 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/background/actions/command.js b/src/background/actions/command.js
index fb8ff98..a7f619b 100644
--- a/src/background/actions/command.js
+++ b/src/background/actions/command.js
@@ -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 };
diff --git a/src/background/actions/console.js b/src/background/actions/console.js
new file mode 100644
index 0000000..d385b2d
--- /dev/null
+++ b/src/background/actions/console.js
@@ -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 };
diff --git a/src/background/actions/tab.js b/src/background/actions/tab.js
index 5cf1e8c..0f32a90 100644
--- a/src/background/actions/tab.js
+++ b/src/background/actions/tab.js
@@ -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) => {
diff --git a/src/background/components/operation.js b/src/background/components/operation.js
index 465baf0..ce93270 100644
--- a/src/background/components/operation.js
+++ b/src/background/components/operation.js
@@ -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: '' };
}
}
diff --git a/src/background/index.js b/src/background/index.js
index 02de53f..8c4eafc 100644
--- a/src/background/index.js
+++ b/src/background/index.js
@@ -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 { 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();
diff --git a/src/console/actions/console.js b/src/console/actions/console.js
index f80045f..3713a76 100644
--- a/src/console/actions/console.js
+++ b/src/console/actions/console.js
@@ -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
};
diff --git a/src/console/components/console.js b/src/console/components/console.js
index 417c9f6..bd3e344 100644
--- a/src/console/components/console.js
+++ b/src/console/components/console.js
@@ -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();
diff --git a/src/console/index.js b/src/console/index.js
index 156456c..8724a44 100644
--- a/src/console/index.js
+++ b/src/console/index.js
@@ -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');
diff --git a/src/content/actions/addon.js b/src/content/actions/addon.js
index 8d38025..b30cf16 100644
--- a/src/content/actions/addon.js
+++ b/src/content/actions/addon.js
@@ -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 = () => {
- return { type: actions.ADDON_DISABLE };
-};
+const disable = () => setEnabled(false);
-const toggleEnabled = () => {
- return { type: actions.ADDON_TOGGLE_ENABLED };
+const setEnabled = async(enabled) => {
+ await browser.runtime.sendMessage({
+ type: messages.ADDON_ENABLED_RESPONSE,
+ enabled,
+ });
+ return {
+ type: actions.ADDON_SET_ENABLED,
+ enabled,
+ };
};
-export { enable, disable, toggleEnabled };
+export { enable, disable, setEnabled };
diff --git a/src/content/actions/index.js b/src/content/actions/index.js
index 7e32e12..1c51ab0 100644
--- a/src/content/actions/index.js
+++ b/src/content/actions/index.js
@@ -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',
diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js
index 40ac52d..c1bd1c8 100644
--- a/src/content/actions/operation.js
+++ b/src/content/actions/operation.js
@@ -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 };
diff --git a/src/content/actions/setting.js b/src/content/actions/setting.js
index e34b6e0..1c15dd7 100644
--- a/src/content/actions/setting.js
+++ b/src/content/actions/setting.js
@@ -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 };
diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js
index 6437011..a1e71a1 100644
--- a/src/content/components/common/index.js
+++ b/src/content/components/common/index.js
@@ -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);
diff --git a/src/content/components/common/keymapper.js b/src/content/components/common/keymapper.js
index d9d1b2f..4c294b4 100644
--- a/src/content/components/common/keymapper.js
+++ b/src/content/components/common/keymapper.js
@@ -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;
}
diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.js
index a0d0480..e22e957 100644
--- a/src/content/components/top-content/index.js
+++ b/src/content/components/top-content/index.js
@@ -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) {
diff --git a/src/content/index.js b/src/content/index.js
index 97a8b30..3b0b49b 100644
--- a/src/content/index.js
+++ b/src/content/index.js
@@ -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
diff --git a/src/content/reducers/addon.js b/src/content/reducers/addon.js
index b881ca0..0def55a 100644
--- a/src/content/reducers/addon.js
+++ b/src/content/reducers/addon.js
@@ -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;
}
diff --git a/src/settings/actions/index.js b/src/settings/actions/index.js
index 8c212c2..016f2a5 100644
--- a/src/settings/actions/index.js
+++ b/src/settings/actions/index.js
@@ -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',
};
diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js
index 1219ba5..3bb24be 100644
--- a/src/settings/actions/setting.js
+++ b/src/settings/actions/setting.js
@@ -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 };
diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx
index c479986..66dc940 100644
--- a/src/settings/components/index.jsx
+++ b/src/settings/components/index.jsx
@@ -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());
- }
-
- stateChanged() {
- let settings = this.context.store.getState();
- this.setState({
- settings: {
- source: settings.source,
- json: settings.json,
- form: settings.form,
- }
- });
+ this.props.dispatch(settingActions.load());
}
- 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));
- }
-
- 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));
+ let settings = {
+ source: this.props.source,
+ json: this.props.json,
+ form: { ...this.props.form },
+ };
+ settings.form[name] = value;
+ this.props.dispatch(settingActions.save(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);
diff --git a/src/settings/index.jsx b/src/settings/index.jsx
index eb251b4..8097d31 100644
--- a/src/settings/index.jsx
+++ b/src/settings/index.jsx
@@ -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');
diff --git a/src/settings/reducers/setting.js b/src/settings/reducers/setting.js
index 70c6183..8e4a415 100644
--- a/src/settings/reducers/setting.js
+++ b/src/settings/reducers/setting.js
@@ -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;
}
}
-
diff --git a/src/shared/blacklists.js b/src/shared/blacklists.js
new file mode 100644
index 0000000..19ed3f1
--- /dev/null
+++ b/src/shared/blacklists.js
@@ -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 };
diff --git a/src/shared/store/index.js b/src/shared/store/index.js
deleted file mode 100644
index 2fafdf1..0000000
--- a/src/shared/store/index.js
+++ /dev/null
@@ -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 };
diff --git a/src/shared/store/provider.jsx b/src/shared/store/provider.jsx
deleted file mode 100644
index fe925aa..0000000
--- a/src/shared/store/provider.jsx
+++ /dev/null
@@ -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;
diff --git a/test/console/actions/console.test.js b/test/console/actions/console.test.js
index 77855cd..10cd9fe 100644
--- a/test/console/actions/console.test.js
+++ b/test/console/actions/console.test.js
@@ -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();
diff --git a/test/content/actions/addon.test.js b/test/content/actions/addon.test.js
deleted file mode 100644
index 5f96372..0000000
--- a/test/content/actions/addon.test.js
+++ /dev/null
@@ -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);
- });
- });
-});
diff --git a/test/content/reducers/addon.test.js b/test/content/reducers/addon.test.js
index 8c546d2..d4eb845 100644
--- a/test/content/reducers/addon.test.js
+++ b/test/content/reducers/addon.test.js
@@ -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);
- });
-
});
diff --git a/test/settings/reducers/setting.test.js b/test/settings/reducers/setting.test.js
index b9579cf..d800394 100644
--- a/test/settings/reducers/setting.test.js
+++ b/test/settings/reducers/setting.test.js
@@ -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');
});
});
diff --git a/test/shared/blacklists.test.js b/test/shared/blacklists.test.js
new file mode 100644
index 0000000..87e89c5
--- /dev/null
+++ b/test/shared/blacklists.test.js
@@ -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;
+ })
+});
diff --git a/test/shared/store/index.test.js b/test/shared/store/index.test.js
deleted file mode 100644
index 5b69b40..0000000
--- a/test/shared/store/index.test.js
+++ /dev/null
@@ -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() }));
- });
- })
-});
-