From 6083e70ea089fa2683741a1118be0e4e6b76f858 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 8 Jan 2018 21:54:16 +0900 Subject: separate setting actions and reducers --- src/background/actions/index.js | 4 ++++ src/background/actions/setting.js | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/background/actions/index.js create mode 100644 src/background/actions/setting.js (limited to 'src/background/actions') diff --git a/src/background/actions/index.js b/src/background/actions/index.js new file mode 100644 index 0000000..8c212c2 --- /dev/null +++ b/src/background/actions/index.js @@ -0,0 +1,4 @@ +export default { + // Settings + SETTING_SET_SETTINGS: 'setting.set.settings', +}; diff --git a/src/background/actions/setting.js b/src/background/actions/setting.js new file mode 100644 index 0000000..0454a68 --- /dev/null +++ b/src/background/actions/setting.js @@ -0,0 +1,13 @@ +import actions from '../actions'; +import * as settingsStorage from 'shared/settings/storage'; + +const load = () => { + return settingsStorage.loadValue().then((value) => { + return { + type: actions.SETTING_SET_SETTINGS, + value, + }; + }); +}; + +export { load }; -- cgit v1.2.3 From 22c34a0a6f9721fb9d907ab10de91cbbc40d6bbe Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 10 Jan 2018 21:34:40 +0900 Subject: add set property action in background --- src/background/actions/index.js | 1 + src/background/actions/setting.js | 10 +++++++++- src/background/reducers/setting.js | 9 ++++++++- test/background/reducers/setting.test.js | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) (limited to 'src/background/actions') diff --git a/src/background/actions/index.js b/src/background/actions/index.js index 8c212c2..efe4074 100644 --- a/src/background/actions/index.js +++ b/src/background/actions/index.js @@ -1,4 +1,5 @@ export default { // Settings SETTING_SET_SETTINGS: 'setting.set.settings', + SETTING_SET_PROPERTY: 'setting.set.property', }; diff --git a/src/background/actions/setting.js b/src/background/actions/setting.js index 0454a68..773142f 100644 --- a/src/background/actions/setting.js +++ b/src/background/actions/setting.js @@ -10,4 +10,12 @@ const load = () => { }); }; -export { load }; +const setProperty = (name, value) => { + return { + type: actions.SETTING_SET_PROPERTY, + name, + value, + }; +}; + +export { load, setProperty }; diff --git a/src/background/reducers/setting.js b/src/background/reducers/setting.js index 70bf8ea..045a654 100644 --- a/src/background/reducers/setting.js +++ b/src/background/reducers/setting.js @@ -1,4 +1,4 @@ -import actions from 'settings/actions'; +import actions from 'background/actions'; const defaultState = { value: {}, @@ -10,6 +10,13 @@ export default function reducer(state = defaultState, action = {}) { return { value: action.value, }; + case actions.SETTING_SET_PROPERTY: + return { + value: Object.assign({}, state.value, { + properties: Object.assign({}, state.value.properties, + { [action.name]: action.value }) + }) + }; default: return state; } diff --git a/test/background/reducers/setting.test.js b/test/background/reducers/setting.test.js index a6a5573..2ef98cb 100644 --- a/test/background/reducers/setting.test.js +++ b/test/background/reducers/setting.test.js @@ -16,4 +16,22 @@ describe("setting reducer", () => { let state = settingReducer(undefined, action); expect(state).to.have.deep.property('value', { key: 123 }); }); + + it('return next state for SETTING_SET_PROPERTY', () => { + let state = { + value: { + properties: { smoothscroll: true } + } + } + let action = { + type: actions.SETTING_SET_PROPERTY, + name: 'encoding', + value: 'utf-8', + }; + state = settingReducer(state, action); + + console.log(state); + expect(state.value.properties).to.have.property('smoothscroll', true); + expect(state.value.properties).to.have.property('encoding', 'utf-8'); + }); }); -- cgit v1.2.3 From dda4e7475cdd092d00441c7cd0ceb194ee5dee3d Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Jan 2018 20:07:25 +0900 Subject: move commands to background action --- src/background/actions/command.js | 62 ++++++++++++++++++++++++ src/background/components/background.js | 3 +- src/shared/commands/exec.js | 85 --------------------------------- src/shared/commands/index.js | 3 +- src/shared/commands/parsers.js | 59 +++++++++++++++++++++++ src/shared/commands/properties.js | 31 ------------ test/shared/commands/parsers.test.js | 78 ++++++++++++++++++++++++++++++ test/shared/commands/property.test.js | 41 ---------------- 8 files changed, 202 insertions(+), 160 deletions(-) create mode 100644 src/background/actions/command.js delete mode 100644 src/shared/commands/exec.js create mode 100644 src/shared/commands/parsers.js delete mode 100644 src/shared/commands/properties.js create mode 100644 test/shared/commands/parsers.test.js delete mode 100644 test/shared/commands/property.test.js (limited to 'src/background/actions') diff --git a/src/background/actions/command.js b/src/background/actions/command.js new file mode 100644 index 0000000..f11c61b --- /dev/null +++ b/src/background/actions/command.js @@ -0,0 +1,62 @@ +import * as tabs from 'background/tabs'; +import * as parsers from 'shared/commands/parsers'; + +const openCommand = (url) => { + return browser.tabs.query({ + active: true, currentWindow: true + }).then((gotTabs) => { + if (gotTabs.length > 0) { + return browser.tabs.update(gotTabs[0].id, { url: url }); + } + }); +}; + +const tabopenCommand = (url) => { + return browser.tabs.create({ url: url }); +}; + +const winopenCommand = (url) => { + return browser.windows.create({ url }); +}; + +const bufferCommand = (keywords) => { + if (keywords.length === 0) { + return Promise.resolve([]); + } + let keywordsStr = keywords.join(' '); + return browser.tabs.query({ + active: true, currentWindow: true + }).then((gotTabs) => { + if (gotTabs.length > 0) { + if (isNaN(keywordsStr)) { + return tabs.selectByKeyword(gotTabs[0], keywordsStr); + } + let index = parseInt(keywordsStr, 10) - 1; + return tabs.selectAt(index); + } + }); +}; + +const exec = (line, settings) => { + let [name, args] = parsers.parseCommandLine(line); + + switch (name) { + case 'o': + case 'open': + return openCommand(parsers.normalizeUrl(args, settings.search)); + case 't': + case 'tabopen': + return tabopenCommand(parsers.normalizeUrl(args, settings.search)); + case 'w': + case 'winopen': + return winopenCommand(parsers.normalizeUrl(args, settings.search)); + case 'b': + case 'buffer': + return bufferCommand(args); + case '': + return Promise.resolve(); + } + throw new Error(name + ' command is not defined'); +}; + +export { exec }; diff --git a/src/background/components/background.js b/src/background/components/background.js index 22c6693..19bf27f 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -1,5 +1,6 @@ import messages from 'shared/messages'; import * as operationActions from 'background/actions/operation'; +import * as commandActions from 'background/actions/command'; import * as settingActions from 'background/actions/setting'; import * as tabActions from 'background/actions/tab'; import * as commands from 'shared/commands'; @@ -35,7 +36,7 @@ export default class BackgroundComponent { return this.store.dispatch( tabActions.openToTab(message.url, sender.tab), sender); case messages.CONSOLE_ENTER_COMMAND: - return commands.exec(message.text, settings.value).catch((e) => { + return commandActions.exec(message.text, settings.value).catch((e) => { return browser.tabs.sendMessage(sender.tab.id, { type: messages.CONSOLE_SHOW_ERROR, text: e.message, diff --git a/src/shared/commands/exec.js b/src/shared/commands/exec.js deleted file mode 100644 index 7248827..0000000 --- a/src/shared/commands/exec.js +++ /dev/null @@ -1,85 +0,0 @@ -import * as tabs from 'background/tabs'; -import * as histories from 'background/histories'; - -const normalizeUrl = (args, searchConfig) => { - let concat = args.join(' '); - try { - return new URL(concat).href; - } catch (e) { - if (concat.includes('.') && !concat.includes(' ')) { - return 'http://' + concat; - } - let query = concat; - let template = searchConfig.engines[ - searchConfig.default - ]; - for (let key in searchConfig.engines) { - if (args[0] === key) { - query = args.slice(1).join(' '); - template = searchConfig.engines[key]; - } - } - return template.replace('{}', encodeURIComponent(query)); - } -}; - -const openCommand = (url) => { - return browser.tabs.query({ - active: true, currentWindow: true - }).then((gotTabs) => { - if (gotTabs.length > 0) { - return browser.tabs.update(gotTabs[0].id, { url: url }); - } - }); -}; - -const tabopenCommand = (url) => { - return browser.tabs.create({ url: url }); -}; - -const winopenCommand = (url) => { - return browser.windows.create({ url }); -}; - -const bufferCommand = (keywords) => { - if (keywords.length === 0) { - return Promise.resolve([]); - } - let keywordsStr = keywords.join(' '); - return browser.tabs.query({ - active: true, currentWindow: true - }).then((gotTabs) => { - if (gotTabs.length > 0) { - if (isNaN(keywordsStr)) { - return tabs.selectByKeyword(gotTabs[0], keywordsStr); - } - let index = parseInt(keywordsStr, 10) - 1; - return tabs.selectAt(index); - } - }); -}; - -const exec = (line, settings) => { - let words = line.trim().split(/ +/); - let name = words.shift(); - - switch (name) { - case 'o': - case 'open': - return openCommand(normalizeUrl(words, settings.search)); - case 't': - case 'tabopen': - return tabopenCommand(normalizeUrl(words, settings.search)); - case 'w': - case 'winopen': - return winopenCommand(normalizeUrl(words, settings.search)); - case 'b': - case 'buffer': - return bufferCommand(words); - case '': - return Promise.resolve(); - } - throw new Error(name + ' command is not defined'); -}; - -export default exec; diff --git a/src/shared/commands/index.js b/src/shared/commands/index.js index c2cea3e..78cb4df 100644 --- a/src/shared/commands/index.js +++ b/src/shared/commands/index.js @@ -1,4 +1,3 @@ -import exec from './exec'; import complete from './complete'; -export { exec, complete }; +export { complete }; diff --git a/src/shared/commands/parsers.js b/src/shared/commands/parsers.js new file mode 100644 index 0000000..af51338 --- /dev/null +++ b/src/shared/commands/parsers.js @@ -0,0 +1,59 @@ +const normalizeUrl = (args, searchConfig) => { + let concat = args.join(' '); + try { + return new URL(concat).href; + } catch (e) { + if (concat.includes('.') && !concat.includes(' ')) { + return 'http://' + concat; + } + let query = concat; + let template = searchConfig.engines[ + searchConfig.default + ]; + for (let key in searchConfig.engines) { + if (args[0] === key) { + query = args.slice(1).join(' '); + template = searchConfig.engines[key]; + } + } + return template.replace('{}', encodeURIComponent(query)); + } +}; + +const mustNumber = (v) => { + let num = Number(v); + if (isNaN(num)) { + throw new Error('Not number: ' + v); + } + return num; +}; + +const parseSetOption = (word, types) => { + let [key, value] = word.split('='); + if (!value) { + value = !key.startsWith('no'); + key = value ? key : key.slice(2); + } + let type = types[key]; + if (!type) { + throw new Error('Unknown property: ' + key); + } + if (type === 'boolean' && typeof value !== 'boolean' || + type !== 'boolean' && typeof value === 'boolean') { + throw new Error('Invalid argument: ' + word); + } + + switch (type) { + case 'string': return [key, value]; + case 'number': return [key, mustNumber(value)]; + case 'boolean': return [key, value]; + } +}; + +const parseCommandLine = (line) => { + let words = line.trim().split(/ +/); + let name = words.shift(); + return [name, words]; +}; + +export { normalizeUrl, parseCommandLine, parseSetOption }; diff --git a/src/shared/commands/properties.js b/src/shared/commands/properties.js deleted file mode 100644 index 8a3213d..0000000 --- a/src/shared/commands/properties.js +++ /dev/null @@ -1,31 +0,0 @@ -const mustNumber = (v) => { - let num = Number(v); - if (isNaN(num)) { - throw new Error('Not number: ' + v); - } - return num; -}; - -const parseProperty = (word, types) => { - let [key, value] = word.split('='); - if (!value) { - value = !key.startsWith('no'); - key = value ? key : key.slice(2); - } - let type = types[key]; - if (!type) { - throw new Error('Unknown property: ' + key); - } - if (type === 'boolean' && typeof value !== 'boolean' || - type !== 'boolean' && typeof value === 'boolean') { - throw new Error('Invalid argument: ' + word); - } - - switch (type) { - case 'string': return [key, value]; - case 'number': return [key, mustNumber(value)]; - case 'boolean': return [key, value]; - } -}; - -export { parseProperty }; diff --git a/test/shared/commands/parsers.test.js b/test/shared/commands/parsers.test.js new file mode 100644 index 0000000..200323c --- /dev/null +++ b/test/shared/commands/parsers.test.js @@ -0,0 +1,78 @@ +import { expect } from "chai"; +import * as parsers from 'shared/commands/parsers'; + +describe("shared/commands/parsers", () => { + describe("#parsers.parseSetOption", () => { + it('parse set string', () => { + let [key, value] = parsers.parseSetOption('encoding=utf-8', { encoding: 'string' }); + expect(key).to.equal('encoding'); + expect(value).to.equal('utf-8'); + }); + + it('parse set string', () => { + let [key, value] = parsers.parseSetOption('history=50', { history: 'number' }); + expect(key).to.equal('history'); + expect(value).to.equal(50); + }); + + it('parse set boolean', () => { + let [key, value] = parsers.parseSetOption('paste', { paste: 'boolean' }); + expect(key).to.equal('paste'); + expect(value).to.be.true; + + [key, value] = parsers.parseSetOption('nopaste', { paste: 'boolean' }); + expect(key).to.equal('paste'); + expect(value).to.be.false; + }); + + it('throws error on unknown property', () => { + expect(() => parsers.parseSetOption('charset=utf-8', {})).to.throw(Error, 'Unknown'); + expect(() => parsers.parseSetOption('smoothscroll', {})).to.throw(Error, 'Unknown'); + expect(() => parsers.parseSetOption('nosmoothscroll', {})).to.throw(Error, 'Unknown'); + }) + + it('throws error on invalid property', () => { + expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number'); + expect(() => parsers.parseSetOption('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid'); + expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid'); + expect(() => parsers.parseSetOption('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid'); + }) + }); + + describe('#normalizeUrl', () => { + const config = { + default: 'google', + engines: { + google: 'https://google.com/search?q={}', + yahoo: 'https://yahoo.com/search?q={}', + } + }; + + it('convertes search url', () => { + expect(parsers.normalizeUrl(['google', 'apple'], config)) + .to.equal('https://google.com/search?q=apple'); + expect(parsers.normalizeUrl(['yahoo', 'apple'], config)) + .to.equal('https://yahoo.com/search?q=apple'); + expect(parsers.normalizeUrl(['google', 'apple', 'banana'], config)) + .to.equal('https://google.com/search?q=apple%20banana'); + expect(parsers.normalizeUrl(['yahoo', 'C++CLI'], config)) + .to.equal('https://yahoo.com/search?q=C%2B%2BCLI'); + }); + + it('user default search engine', () => { + expect(parsers.normalizeUrl(['apple', 'banana'], config)) + .to.equal('https://google.com/search?q=apple%20banana'); + }); + }); + + describe('#parseCommandLine', () => { + it('parse command line as name and args', () => { + expect(parsers.parseCommandLine('open google apple')).to.deep.equal(['open', ['google', 'apple']]); + expect(parsers.parseCommandLine(' open google apple ')).to.deep.equal(['open', ['google', 'apple']]); + expect(parsers.parseCommandLine('')).to.deep.equal(['', []]); + expect(parsers.parseCommandLine(' ')).to.deep.equal(['', []]); + expect(parsers.parseCommandLine('exit')).to.deep.equal(['exit', []]); + expect(parsers.parseCommandLine(' exit ')).to.deep.equal(['exit', []]); + }); + }); +}); diff --git a/test/shared/commands/property.test.js b/test/shared/commands/property.test.js deleted file mode 100644 index d949482..0000000 --- a/test/shared/commands/property.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { expect } from "chai"; -import { parseProperty } from 'shared/commands/properties'; - -describe("shared/commands/properties", () => { - describe("#parseProperty", () => { - it('parse set string', () => { - let [key, value] = parseProperty('encoding=utf-8', { encoding: 'string' }); - expect(key).to.equal('encoding'); - expect(value).to.equal('utf-8'); - }); - - it('parse set string', () => { - let [key, value] = parseProperty('history=50', { history: 'number' }); - expect(key).to.equal('history'); - expect(value).to.equal(50); - }); - - it('parse set boolean', () => { - let [key, value] = parseProperty('paste', { paste: 'boolean' }); - expect(key).to.equal('paste'); - expect(value).to.be.true; - - [key, value] = parseProperty('nopaste', { paste: 'boolean' }); - expect(key).to.equal('paste'); - expect(value).to.be.false; - }); - - it('throws error on unknown property', () => { - expect(() => parseProperty('charset=utf-8', {})).to.throw(Error, 'Unknown'); - expect(() => parseProperty('smoothscroll', {})).to.throw(Error, 'Unknown'); - expect(() => parseProperty('nosmoothscroll', {})).to.throw(Error, 'Unknown'); - }) - - it('throws error on invalid property', () => { - expect(() => parseProperty('charset=utf-8', { charset: 'number' })).to.throw(Error, 'Not number'); - expect(() => parseProperty('charset=utf-8', { charset: 'boolean' })).to.throw(Error, 'Invalid'); - expect(() => parseProperty('smoothscroll', { smoothscroll: 'string' })).to.throw(Error, 'Invalid'); - expect(() => parseProperty('smoothscroll', { smoothscroll: 'number' })).to.throw(Error, 'Invalid'); - }) - }); -}); -- cgit v1.2.3 From fad8f96a663d83792138cc986474ec4228b6c6c9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 11 Jan 2018 20:22:49 +0900 Subject: implement set option --- src/background/actions/command.js | 17 +++++++++++++++++ src/background/components/background.js | 11 +++++------ 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src/background/actions') diff --git a/src/background/actions/command.js b/src/background/actions/command.js index f11c61b..4c52bca 100644 --- a/src/background/actions/command.js +++ b/src/background/actions/command.js @@ -1,5 +1,7 @@ +import actions from '../actions'; import * as tabs from 'background/tabs'; import * as parsers from 'shared/commands/parsers'; +import * as properties from 'shared/settings/properties'; const openCommand = (url) => { return browser.tabs.query({ @@ -37,6 +39,19 @@ const bufferCommand = (keywords) => { }); }; +const setCommand = (args) => { + if (!args[0]) { + return Promise.resolve(); + } + + let [name, value] = parsers.parseSetOption(args[0], properties.types); + return { + type: actions.SETTING_SET_PROPERTY, + name, + value + }; +}; + const exec = (line, settings) => { let [name, args] = parsers.parseCommandLine(line); @@ -53,6 +68,8 @@ const exec = (line, settings) => { case 'b': case 'buffer': return bufferCommand(args); + case 'set': + return setCommand(args); case '': return Promise.resolve(); } diff --git a/src/background/components/background.js b/src/background/components/background.js index 19bf27f..9578e78 100644 --- a/src/background/components/background.js +++ b/src/background/components/background.js @@ -36,12 +36,11 @@ export default class BackgroundComponent { return this.store.dispatch( tabActions.openToTab(message.url, sender.tab), sender); case messages.CONSOLE_ENTER_COMMAND: - return commandActions.exec(message.text, settings.value).catch((e) => { - return browser.tabs.sendMessage(sender.tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text: e.message, - }); - }); + this.store.dispatch( + commandActions.exec(message.text, settings.value), + sender + ); + return this.broadcastSettingsChanged(); case messages.SETTINGS_QUERY: return Promise.resolve(this.store.getState().setting.value); case messages.CONSOLE_QUERY_COMPLETIONS: -- cgit v1.2.3