Merge pull request #386 from ueokande/addon-enabled-indicator

Addon enabled indicator
jh-changes
Shin'ya Ueoka 7 years ago committed by GitHub
commit 104a9666ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      manifest.json
  2. BIN
      resources/disabled_32x32.png
  3. BIN
      resources/enabled_32x32.png
  4. 2
      src/background/actions/command.js
  5. 3
      src/background/actions/index.js
  6. 92
      src/background/actions/operation.js
  7. 11
      src/background/actions/tab.js
  8. 9
      src/background/components/background.js
  9. 45
      src/background/components/indicator.js
  10. 118
      src/background/components/operation.js
  11. 17
      src/background/components/tab.js
  12. 10
      src/background/index.js
  13. 3
      src/background/reducers/index.js
  14. 19
      src/background/reducers/tab.js
  15. 0
      src/background/shared/completions/histories.js
  16. 6
      src/background/shared/completions/index.js
  17. 10
      src/background/shared/completions/tabs.js
  18. 13
      src/background/shared/indicators.js
  19. 27
      src/background/shared/tabs.js
  20. 0
      src/background/shared/zooms.js
  21. 19
      src/content/components/common/index.js
  22. 9
      src/content/components/top-content/index.js
  23. 3
      src/shared/commands/index.js
  24. 4
      src/shared/messages.js
  25. 13
      test/background/actions/tab.test.js
  26. 22
      test/background/reducers/tab.test.js

@ -40,5 +40,11 @@
], ],
"options_ui": { "options_ui": {
"page": "build/settings.html" "page": "build/settings.html"
},
"browser_action": {
"default_icon": {
"32": "resources/enabled_32x32.png"
},
"default_title": "Vim Vixen"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -1,5 +1,5 @@
import actions from '../actions'; import actions from '../actions';
import * as tabs from 'background/tabs'; import * as tabs from '../shared/tabs';
import * as parsers from 'shared/commands/parsers'; import * as parsers from 'shared/commands/parsers';
import * as properties from 'shared/settings/properties'; import * as properties from 'shared/settings/properties';

@ -5,4 +5,7 @@ export default {
// Find // Find
FIND_SET_KEYWORD: 'find.set.keyword', FIND_SET_KEYWORD: 'find.set.keyword',
// Tab
TAB_SELECTED: 'tab.selected',
}; };

@ -1,92 +0,0 @@
import operations from 'shared/operations';
import messages from 'shared/messages';
import * as tabs from 'background/tabs';
import * as zooms from 'background/zooms';
const sendConsoleShowCommand = (tab, command) => {
return browser.tabs.sendMessage(tab.id, {
type: messages.CONSOLE_SHOW_COMMAND,
command,
});
};
// This switch statement is only gonna get longer as more
// features are added, so disable complexity check
/* eslint-disable complexity */
const exec = (operation, tab) => {
switch (operation.type) {
case operations.TAB_CLOSE:
return tabs.closeTab(tab.id);
case operations.TAB_CLOSE_FORCE:
return tabs.closeTabForce(tab.id);
case operations.TAB_REOPEN:
return tabs.reopenTab();
case operations.TAB_PREV:
return tabs.selectPrevTab(tab.index, operation.count);
case operations.TAB_NEXT:
return tabs.selectNextTab(tab.index, operation.count);
case operations.TAB_FIRST:
return tabs.selectFirstTab();
case operations.TAB_LAST:
return tabs.selectLastTab();
case operations.TAB_PREV_SEL:
return tabs.selectPrevSelTab();
case operations.TAB_RELOAD:
return tabs.reload(tab, operation.cache);
case operations.TAB_PIN:
return tabs.updateTabPinned(tab, true);
case operations.TAB_UNPIN:
return tabs.updateTabPinned(tab, false);
case operations.TAB_TOGGLE_PINNED:
return tabs.toggleTabPinned(tab);
case operations.TAB_DUPLICATE:
return tabs.duplicate(tab.id);
case operations.ZOOM_IN:
return zooms.zoomIn();
case operations.ZOOM_OUT:
return zooms.zoomOut();
case operations.ZOOM_NEUTRAL:
return zooms.neutral();
case operations.COMMAND_SHOW:
return sendConsoleShowCommand(tab, '');
case operations.COMMAND_SHOW_OPEN:
if (operation.alter) {
// alter url
return sendConsoleShowCommand(tab, 'open ' + tab.url);
}
return sendConsoleShowCommand(tab, 'open ');
case operations.COMMAND_SHOW_TABOPEN:
if (operation.alter) {
// alter url
return sendConsoleShowCommand(tab, 'tabopen ' + tab.url);
}
return sendConsoleShowCommand(tab, 'tabopen ');
case operations.COMMAND_SHOW_WINOPEN:
if (operation.alter) {
// alter url
return sendConsoleShowCommand(tab, 'winopen ' + tab.url);
}
return sendConsoleShowCommand(tab, 'winopen ');
case operations.COMMAND_SHOW_BUFFER:
return sendConsoleShowCommand(tab, 'buffer ');
case operations.FIND_START:
return browser.tabs.sendMessage(tab.id, {
type: messages.CONSOLE_SHOW_FIND
});
case operations.CANCEL:
return browser.tabs.sendMessage(tab.id, {
type: messages.CONSOLE_HIDE,
});
case operations.PAGE_SOURCE:
return browser.tabs.create({
url: 'view-source:' + tab.url,
index: tab.index + 1,
openerTabId: tab.id,
});
default:
return Promise.resolve();
}
};
/* eslint-enable complexity */
export { exec };

@ -1,3 +1,5 @@
import actions from './index';
const openNewTab = (url, openerTabId, background = false, adjacent = false) => { const openNewTab = (url, openerTabId, background = false, adjacent = false) => {
if (adjacent) { if (adjacent) {
return browser.tabs.query({ return browser.tabs.query({
@ -18,4 +20,11 @@ const openToTab = (url, tab) => {
return browser.tabs.update(tab.id, { url: url }); return browser.tabs.update(tab.id, { url: url });
}; };
export { openNewTab, openToTab }; const selected = (tabId) => {
return {
type: actions.TAB_SELECTED,
tabId,
};
};
export { openNewTab, openToTab, selected };

@ -1,10 +1,9 @@
import messages from 'shared/messages'; import messages from 'shared/messages';
import * as operationActions from 'background/actions/operation';
import * as commandActions from 'background/actions/command'; import * as commandActions from 'background/actions/command';
import * as settingActions from 'background/actions/setting'; import * as settingActions from 'background/actions/setting';
import * as findActions from 'background/actions/find'; import * as findActions from 'background/actions/find';
import * as tabActions from 'background/actions/tab'; import * as tabActions from 'background/actions/tab';
import * as commands from 'shared/commands'; import * as completions from '../shared/completions';
export default class BackgroundComponent { export default class BackgroundComponent {
constructor(store) { constructor(store) {
@ -27,10 +26,6 @@ export default class BackgroundComponent {
let find = this.store.getState().find; let find = this.store.getState().find;
switch (message.type) { switch (message.type) {
case messages.BACKGROUND_OPERATION:
return this.store.dispatch(
operationActions.exec(message.operation, sender.tab),
sender);
case messages.OPEN_URL: case messages.OPEN_URL:
if (message.newTab) { if (message.newTab) {
let action = tabActions.openNewTab( let action = tabActions.openNewTab(
@ -49,7 +44,7 @@ export default class BackgroundComponent {
case messages.SETTINGS_QUERY: case messages.SETTINGS_QUERY:
return Promise.resolve(this.store.getState().setting.value); return Promise.resolve(this.store.getState().setting.value);
case messages.CONSOLE_QUERY_COMPLETIONS: case messages.CONSOLE_QUERY_COMPLETIONS:
return commands.complete(message.text, settings.value); return completions.complete(message.text, settings.value);
case messages.SETTINGS_RELOAD: case messages.SETTINGS_RELOAD:
this.store.dispatch(settingActions.load()); this.store.dispatch(settingActions.load());
return this.broadcastSettingsChanged(); return this.broadcastSettingsChanged();

@ -0,0 +1,45 @@
import * as indicators from '../shared/indicators';
import messages from 'shared/messages';
export default class IndicatorComponent {
constructor(store) {
this.store = store;
messages.onMessage(this.onMessage.bind(this));
browser.browserAction.onClicked.addListener(this.onClicked);
browser.tabs.onActivated.addListener((info) => {
return browser.tabs.query({ currentWindow: true }).then(() => {
return this.onTabActivated(info);
});
});
}
onTabActivated(info) {
return browser.tabs.sendMessage(info.tabId, {
type: messages.ADDON_ENABLED_QUERY,
}).then((resp) => {
return this.updateIndicator(resp.enabled);
});
}
onClicked(tab) {
browser.tabs.sendMessage(tab.id, {
type: messages.ADDON_TOGGLE_ENABLED,
});
}
onMessage(message) {
switch (message.type) {
case messages.ADDON_ENABLED_RESPONSE:
return this.updateIndicator(message.enabled);
}
}
updateIndicator(enabled) {
if (enabled) {
return indicators.enable();
}
return indicators.disable();
}
}

@ -0,0 +1,118 @@
import messages from 'shared/messages';
import operations from 'shared/operations';
import * as tabs from '../shared//tabs';
import * as zooms from '../shared/zooms';
export default class BackgroundComponent {
constructor(store) {
this.store = store;
browser.runtime.onMessage.addListener((message, sender) => {
try {
return this.onMessage(message, sender);
} catch (e) {
return browser.tabs.sendMessage(sender.tab.id, {
type: messages.CONSOLE_SHOW_ERROR,
text: e.message,
});
}
});
}
onMessage(message, sender) {
switch (message.type) {
case messages.BACKGROUND_OPERATION:
return this.store.dispatch(
this.exec(message.operation, sender.tab),
sender);
}
}
// eslint-disable-next-line complexity
exec(operation, tab) {
let tabState = this.store.getState().tab;
switch (operation.type) {
case operations.TAB_CLOSE:
return tabs.closeTab(tab.id);
case operations.TAB_CLOSE_FORCE:
return tabs.closeTabForce(tab.id);
case operations.TAB_REOPEN:
return tabs.reopenTab();
case operations.TAB_PREV:
return tabs.selectPrevTab(tab.index, operation.count);
case operations.TAB_NEXT:
return tabs.selectNextTab(tab.index, operation.count);
case operations.TAB_FIRST:
return tabs.selectFirstTab();
case operations.TAB_LAST:
return tabs.selectLastTab();
case operations.TAB_PREV_SEL:
if (tabState.previousSelected > 0) {
return tabs.selectTab(tabState.previousSelected);
}
break;
case operations.TAB_RELOAD:
return tabs.reload(tab, operation.cache);
case operations.TAB_PIN:
return tabs.updateTabPinned(tab, true);
case operations.TAB_UNPIN:
return tabs.updateTabPinned(tab, false);
case operations.TAB_TOGGLE_PINNED:
return tabs.toggleTabPinned(tab);
case operations.TAB_DUPLICATE:
return tabs.duplicate(tab.id);
case operations.ZOOM_IN:
return zooms.zoomIn();
case operations.ZOOM_OUT:
return zooms.zoomOut();
case operations.ZOOM_NEUTRAL:
return zooms.neutral();
case operations.COMMAND_SHOW:
return this.sendConsoleShowCommand(tab, '');
case operations.COMMAND_SHOW_OPEN:
if (operation.alter) {
// alter url
return this.sendConsoleShowCommand(tab, 'open ' + tab.url);
}
return this.sendConsoleShowCommand(tab, 'open ');
case operations.COMMAND_SHOW_TABOPEN:
if (operation.alter) {
// alter url
return this.sendConsoleShowCommand(tab, 'tabopen ' + tab.url);
}
return this.sendConsoleShowCommand(tab, 'tabopen ');
case operations.COMMAND_SHOW_WINOPEN:
if (operation.alter) {
// alter url
return this.sendConsoleShowCommand(tab, 'winopen ' + tab.url);
}
return this.sendConsoleShowCommand(tab, 'winopen ');
case operations.COMMAND_SHOW_BUFFER:
return this.sendConsoleShowCommand(tab, 'buffer ');
case operations.FIND_START:
return browser.tabs.sendMessage(tab.id, {
type: messages.CONSOLE_SHOW_FIND
});
case operations.CANCEL:
return browser.tabs.sendMessage(tab.id, {
type: messages.CONSOLE_HIDE,
});
case operations.PAGE_SOURCE:
return browser.tabs.create({
url: 'view-source:' + tab.url,
index: tab.index + 1,
openerTabId: tab.id,
});
default:
return Promise.resolve();
}
}
sendConsoleShowCommand(tab, command) {
return browser.tabs.sendMessage(tab.id, {
type: messages.CONSOLE_SHOW_COMMAND,
command,
});
}
}

@ -0,0 +1,17 @@
import * as tabActions from '../actions/tab';
export default class TabComponent {
constructor(store) {
this.store = store;
browser.tabs.onActivated.addListener((info) => {
return browser.tabs.query({ currentWindow: true }).then(() => {
return this.onTabActivated(info);
});
});
}
onTabActivated(info) {
return this.store.dispatch(tabActions.selected(info.tabId));
}
}

@ -1,6 +1,9 @@
import * as settingActions from 'background/actions/setting'; import * as settingActions from 'background/actions/setting';
import messages from 'shared/messages'; import messages from 'shared/messages';
import BackgroundComponent from 'background/components/background'; 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 reducers from 'background/reducers';
import { createStore } from 'shared/store'; import { createStore } from 'shared/store';
import * as versions from 'shared/versions'; import * as versions from 'shared/versions';
@ -14,8 +17,13 @@ const store = createStore(reducers, (e, sender) => {
}); });
} }
}); });
// eslint-disable-next-line no-unused-vars
/* eslint-disable no-unused-vars */
const backgroundComponent = new BackgroundComponent(store); const backgroundComponent = new BackgroundComponent(store);
const operationComponent = new OperationComponent(store);
const tabComponent = new TabComponent(store);
const indicatorComponent = new IndicatorComponent(store);
/* eslint-enable no-unused-vars */
store.dispatch(settingActions.load()); store.dispatch(settingActions.load());

@ -1,15 +1,18 @@
import settingReducer from './setting'; import settingReducer from './setting';
import findReducer from './find'; import findReducer from './find';
import tabReducer from './tab';
// Make setting reducer instead of re-use // Make setting reducer instead of re-use
const defaultState = { const defaultState = {
setting: settingReducer(undefined, {}), setting: settingReducer(undefined, {}),
find: findReducer(undefined, {}), find: findReducer(undefined, {}),
tab: tabReducer(undefined, {}),
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
setting: settingReducer(state.setting, action), setting: settingReducer(state.setting, action),
find: findReducer(state.find, action), find: findReducer(state.find, action),
tab: tabReducer(state.tab, action),
}); });
} }

@ -0,0 +1,19 @@
import actions from 'background/actions';
const defaultState = {
previousSelected: -1,
currentSelected: -1,
};
export default function reducer(state = defaultState, action = {}) {
switch (action.type) {
case actions.TAB_SELECTED:
return {
previousSelected: state.currentSelected,
currentSelected: action.tabId,
};
default:
return state;
}
}

@ -1,5 +1,5 @@
import * as tabs from 'background/tabs'; import * as tabs from './tabs';
import * as histories from 'background/histories'; import * as histories from './histories';
const getOpenCompletions = (command, keywords, searchConfig) => { const getOpenCompletions = (command, keywords, searchConfig) => {
return histories.getCompletions(keywords).then((pages) => { return histories.getCompletions(keywords).then((pages) => {
@ -81,4 +81,4 @@ const complete = (line, settings) => {
return getCompletions(line, settings); return getCompletions(line, settings);
}; };
export default complete; export { complete };

@ -0,0 +1,10 @@
const getCompletions = (keyword) => {
return browser.tabs.query({ currentWindow: true }).then((tabs) => {
let matched = tabs.filter((t) => {
return t.url.includes(keyword) || t.title && t.title.includes(keyword);
});
return matched;
});
};
export { getCompletions };

@ -0,0 +1,13 @@
const enable = () => {
return browser.browserAction.setIcon({
path: 'resources/enabled_32x32.png',
});
};
const disable = () => {
return browser.browserAction.setIcon({
path: 'resources/disabled_32x32.png',
});
};
export { enable, disable };

@ -1,13 +1,3 @@
let prevSelTab = 1;
let currSelTab = 1;
browser.tabs.onActivated.addListener((activeInfo) => {
return browser.tabs.query({ currentWindow: true }).then(() => {
prevSelTab = currSelTab;
currSelTab = activeInfo.tabId;
});
});
const closeTab = (id) => { const closeTab = (id) => {
return browser.tabs.get(id).then((tab) => { return browser.tabs.get(id).then((tab) => {
if (!tab.pinned) { if (!tab.pinned) {
@ -66,15 +56,6 @@ const selectByKeyword = (current, keyword) => {
}); });
}; };
const getCompletions = (keyword) => {
return browser.tabs.query({ currentWindow: true }).then((tabs) => {
let matched = tabs.filter((t) => {
return t.url.includes(keyword) || t.title && t.title.includes(keyword);
});
return matched;
});
};
const selectPrevTab = (current, count) => { const selectPrevTab = (current, count) => {
return browser.tabs.query({ currentWindow: true }).then((tabs) => { return browser.tabs.query({ currentWindow: true }).then((tabs) => {
if (tabs.length < 2) { if (tabs.length < 2) {
@ -111,8 +92,8 @@ const selectLastTab = () => {
}); });
}; };
const selectPrevSelTab = () => { const selectTab = (id) => {
return browser.tabs.update(prevSelTab, { active: true }); return browser.tabs.update(id, { active: true });
}; };
const reload = (current, cache) => { const reload = (current, cache) => {
@ -139,7 +120,7 @@ const duplicate = (id) => {
export { export {
closeTab, closeTabForce, reopenTab, selectAt, selectByKeyword, closeTab, closeTabForce, reopenTab, selectAt, selectByKeyword,
getCompletions, selectPrevTab, selectNextTab, selectFirstTab, selectPrevTab, selectNextTab, selectFirstTab,
selectLastTab, selectPrevSelTab, reload, updateTabPinned, selectLastTab, selectTab, reload, updateTabPinned,
toggleTabPinned, duplicate toggleTabPinned, duplicate
}; };

@ -3,6 +3,7 @@ import KeymapperComponent from './keymapper';
import FollowComponent from './follow'; import FollowComponent from './follow';
import * as settingActions from 'content/actions/setting'; import * as settingActions from 'content/actions/setting';
import messages from 'shared/messages'; import messages from 'shared/messages';
import * as addonActions from '../../actions/addon';
export default class Common { export default class Common {
constructor(win, store) { constructor(win, store) {
@ -14,16 +15,32 @@ export default class Common {
input.onKey(key => keymapper.key(key)); input.onKey(key => keymapper.key(key));
this.store = store; this.store = store;
this.prevEnabled = undefined;
this.reloadSettings(); this.reloadSettings();
messages.onMessage(this.onMessage.bind(this)); messages.onMessage(this.onMessage.bind(this));
store.subscribe(() => this.update());
} }
onMessage(message) { onMessage(message) {
switch (message.type) { switch (message.type) {
case messages.SETTINGS_CHANGED: case messages.SETTINGS_CHANGED:
this.reloadSettings(); return this.reloadSettings();
case messages.ADDON_TOGGLE_ENABLED:
return this.store.dispatch(addonActions.toggleEnabled());
}
}
update() {
let enabled = this.store.getState().addon.enabled;
if (enabled !== this.prevEnabled) {
this.prevEnabled = enabled;
browser.runtime.sendMessage({
type: messages.ADDON_ENABLED_RESPONSE,
enabled,
});
} }
} }

@ -44,15 +44,24 @@ export default class TopContent {
.some(regex => regex.test(partial)); .some(regex => regex.test(partial));
if (matched) { if (matched) {
this.store.dispatch(addonActions.disable()); this.store.dispatch(addonActions.disable());
} else {
this.store.dispatch(addonActions.enable());
} }
} }
onMessage(message) { onMessage(message) {
let addonState = this.store.getState().addon;
switch (message.type) { switch (message.type) {
case messages.CONSOLE_UNFOCUS: case messages.CONSOLE_UNFOCUS:
this.win.focus(); this.win.focus();
consoleFrames.blur(window.document); consoleFrames.blur(window.document);
return Promise.resolve(); return Promise.resolve();
case messages.ADDON_ENABLED_QUERY:
return Promise.resolve({
type: messages.ADDON_ENABLED_RESPONSE,
enabled: addonState.enabled,
});
} }
} }
} }

@ -1,3 +0,0 @@
import complete from './complete';
export { complete };

@ -48,6 +48,10 @@ export default {
FIND_GET_KEYWORD: 'find.get.keyword', FIND_GET_KEYWORD: 'find.get.keyword',
FIND_SET_KEYWORD: 'find.set.keyword', FIND_SET_KEYWORD: 'find.set.keyword',
ADDON_ENABLED_QUERY: 'addon.enabled.query',
ADDON_ENABLED_RESPONSE: 'addon.enabled.response',
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled',
OPEN_URL: 'open.url', OPEN_URL: 'open.url',
SETTINGS_RELOAD: 'settings.reload', SETTINGS_RELOAD: 'settings.reload',

@ -0,0 +1,13 @@
import actions from 'background/actions';
import * as tabActions from 'background/actions/tab';
describe("tab actions", () => {
describe("selected", () => {
it('create TAB_SELECTED action', () => {
let action = tabActions.selected(123);
expect(action.type).to.equal(actions.TAB_SELECTED);
expect(action.tabId).to.equal(123);
});
});
});

@ -0,0 +1,22 @@
import actions from 'background/actions';
import tabReducer from 'background/reducers/tab';
describe("tab reducer", () => {
it('return the initial state', () => {
let state = tabReducer(undefined, {});
expect(state.previousSelected).to.equal(-1);
expect(state.currentSelected).to.equal(-1);
});
it('return next state for TAB_SELECTED', () => {
let state = undefined;
state = tabReducer(state, { type: actions.TAB_SELECTED, tabId: 123 });
expect(state.previousSelected).to.equal(-1);
expect(state.currentSelected).to.equal(123);
state = tabReducer(state, { type: actions.TAB_SELECTED, tabId: 456 });
expect(state.previousSelected).to.equal(123);
expect(state.currentSelected).to.equal(456);
});
});