commit
1b2554adee
8 changed files with 184 additions and 19 deletions
@ -0,0 +1,27 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
import messages from '../content/messages'; |
||||||
|
|
||||||
|
const load = () => { |
||||||
|
return browser.storage.local.get('settings').then((value) => { |
||||||
|
return set(value.settings); |
||||||
|
}, console.error); |
||||||
|
}; |
||||||
|
|
||||||
|
const save = (settings) => { |
||||||
|
return browser.storage.local.set({ |
||||||
|
settings |
||||||
|
}).then(() => { |
||||||
|
return browser.runtime.sendMessage({ |
||||||
|
type: messages.SETTINGS_RELOAD |
||||||
|
}); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const set = (settings) => { |
||||||
|
return { |
||||||
|
type: actions.SETTING_SET_SETTINGS, |
||||||
|
settings, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export { load, save, set }; |
@ -0,0 +1,45 @@ |
|||||||
|
import * as settingActions from '../actions/setting'; |
||||||
|
import { validate } from '../shared/validators/setting'; |
||||||
|
|
||||||
|
export default class SettingComponent { |
||||||
|
constructor(wrapper, store) { |
||||||
|
this.wrapper = wrapper; |
||||||
|
this.store = store; |
||||||
|
|
||||||
|
let doc = wrapper.ownerDocument; |
||||||
|
let form = doc.getElementById('vimvixen-settings-form'); |
||||||
|
form.addEventListener('submit', this.onSubmit.bind(this)); |
||||||
|
|
||||||
|
let plainJson = form.elements['plain-json']; |
||||||
|
plainJson.addEventListener('input', this.onPlainJsonChanged.bind(this)); |
||||||
|
|
||||||
|
store.dispatch(settingActions.load()); |
||||||
|
} |
||||||
|
|
||||||
|
onSubmit(e) { |
||||||
|
let settings = { |
||||||
|
json: e.target.elements['plain-json'].value, |
||||||
|
}; |
||||||
|
this.store.dispatch(settingActions.save(settings)); |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
|
||||||
|
onPlainJsonChanged(e) { |
||||||
|
try { |
||||||
|
let settings = JSON.parse(e.target.value); |
||||||
|
validate(settings); |
||||||
|
e.target.setCustomValidity(''); |
||||||
|
} catch (err) { |
||||||
|
e.target.setCustomValidity(err.message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
update() { |
||||||
|
let { settings } = this.store.getState(); |
||||||
|
|
||||||
|
let doc = this.wrapper.ownerDocument; |
||||||
|
let form = doc.getElementById('vimvixen-settings-form'); |
||||||
|
let plainJsonInput = form.elements['plain-json']; |
||||||
|
plainJsonInput.value = settings.json; |
||||||
|
} |
||||||
|
} |
@ -1,22 +1,15 @@ |
|||||||
import './settings.scss'; |
import './settings.scss'; |
||||||
import messages from '../content/messages'; |
import SettingComponent from '../components/setting'; |
||||||
|
import settingReducer from '../reducers/setting'; |
||||||
|
import * as store from '../store'; |
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => { |
const settingStore = store.createStore(settingReducer); |
||||||
let form = document.getElementById('vimvixen-settings-form'); |
let settingComponent = null; |
||||||
form.addEventListener('submit', (e) => { |
|
||||||
e.preventDefault(); |
settingStore.subscribe(() => { |
||||||
browser.storage.local.set({ |
settingComponent.update(); |
||||||
settings: { |
|
||||||
json: e.target.elements['plain-json'].value |
|
||||||
} |
|
||||||
}).then(() => { |
|
||||||
return browser.runtime.sendMessage({ |
|
||||||
type: messages.SETTINGS_RELOAD |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
}); |
||||||
|
|
||||||
browser.storage.local.get('settings').then((value) => { |
document.addEventListener('DOMContentLoaded', () => { |
||||||
form.elements['plain-json'].value = value.settings.json; |
settingComponent = new SettingComponent(document.body, settingStore); |
||||||
}, console.error); |
|
||||||
}); |
}); |
||||||
|
@ -0,0 +1,17 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const defaultState = { |
||||||
|
settings: {} |
||||||
|
}; |
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action = {}) { |
||||||
|
switch (action.type) { |
||||||
|
case actions.SETTING_SET_SETTINGS: |
||||||
|
return Object.assign({}, state, { |
||||||
|
settings: action.settings, |
||||||
|
}); |
||||||
|
default: |
||||||
|
return state; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,33 @@ |
|||||||
|
import operations from '../../operations'; |
||||||
|
|
||||||
|
const VALID_TOP_KEYS = ['keymaps']; |
||||||
|
const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { |
||||||
|
return operations[key]; |
||||||
|
}); |
||||||
|
|
||||||
|
const validateInvalidTopKeys = (settings) => { |
||||||
|
let invalidKey = Object.keys(settings).find((key) => { |
||||||
|
return !VALID_TOP_KEYS.includes(key); |
||||||
|
}); |
||||||
|
if (invalidKey) { |
||||||
|
throw Error(`Unknown key: "${invalidKey}"`); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const validateKeymaps = (keymaps) => { |
||||||
|
for (let key of Object.keys(keymaps)) { |
||||||
|
let value = keymaps[key]; |
||||||
|
if (!VALID_OPERATION_VALUES.includes(value.type)) { |
||||||
|
throw Error(`Unknown operation: "${value.type}"`); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const validate = (settings) => { |
||||||
|
validateInvalidTopKeys(settings); |
||||||
|
if (settings.keymaps) { |
||||||
|
validateKeymaps(settings.keymaps); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
export { validate }; |
@ -0,0 +1,22 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from '../../src/actions'; |
||||||
|
import settingReducer from '../../src/reducers/setting'; |
||||||
|
|
||||||
|
describe("setting reducer", () => { |
||||||
|
it('return the initial state', () => { |
||||||
|
let state = settingReducer(undefined, {}); |
||||||
|
expect(state).to.have.deep.property('settings', {}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for SETTING_SET_SETTINGS', () => { |
||||||
|
let action = { |
||||||
|
type: actions.SETTING_SET_SETTINGS, |
||||||
|
settings: { value1: 'hello', value2: 'world' }, |
||||||
|
}; |
||||||
|
let state = settingReducer(undefined, action); |
||||||
|
expect(state).to.have.deep.property('settings', { |
||||||
|
value1: 'hello', |
||||||
|
value2: 'world', |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,25 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import { validate } from '../../../src/shared/validators/setting'; |
||||||
|
|
||||||
|
describe("setting validator", () => { |
||||||
|
describe("unknown top keys", () => { |
||||||
|
it('throws an error for unknown settings', () => { |
||||||
|
let settings = { keymaps: {}, poison: 123 }; |
||||||
|
let fn = validate.bind(undefined, settings) |
||||||
|
expect(fn).to.throw(Error, 'poison'); |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
describe("keymaps settings", () => { |
||||||
|
it('throws an error for unknown operation', () => { |
||||||
|
let settings = { |
||||||
|
keymaps: { |
||||||
|
a: { 'type': 'scroll.home' }, |
||||||
|
b: { 'type': 'poison.dressing' }, |
||||||
|
} |
||||||
|
}; |
||||||
|
let fn = validate.bind(undefined, settings) |
||||||
|
expect(fn).to.throw(Error, 'poison.dressing'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
Reference in new issue