aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
25 files changed, 412 insertions, 437 deletions
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;