diff options
-rw-r--r-- | src/settings/actions/setting.js | 7 | ||||
-rw-r--r-- | src/settings/components/index.jsx | 139 | ||||
-rw-r--r-- | src/settings/components/site.scss | 27 | ||||
-rw-r--r-- | src/settings/components/ui/input.jsx | 35 | ||||
-rw-r--r-- | src/settings/components/ui/input.scss | 37 | ||||
-rw-r--r-- | src/settings/reducers/setting.js | 2 | ||||
-rw-r--r-- | src/shared/default-settings.js | 66 |
7 files changed, 268 insertions, 45 deletions
diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js index c1b27c8..fa158a3 100644 --- a/src/settings/actions/setting.js +++ b/src/settings/actions/setting.js @@ -4,10 +4,10 @@ import DefaultSettings from 'shared/default-settings'; const load = () => { return browser.storage.local.get('settings').then(({ settings }) => { - if (settings) { - return set(settings); + if (!settings) { + return set(DefaultSettings); } - return set(DefaultSettings); + return set(Object.assign({}, DefaultSettings, settings)); }, console.error); }; @@ -28,6 +28,7 @@ const set = (settings) => { type: actions.SETTING_SET_SETTINGS, source: settings.source, json: settings.json, + form: settings.form, value: JSON.parse(settings.json), }; }; diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index 98d8fb2..11411bd 100644 --- a/src/settings/components/index.jsx +++ b/src/settings/components/index.jsx @@ -4,6 +4,59 @@ import Input from './ui/input'; import * as settingActions from 'settings/actions/setting'; import * as validator from 'shared/validators/setting'; +const KeyMapFields = [ + [ + ['scroll.vertically?{count:-1}', 'Scroll down'], + ['scroll.vertically?{count:1}', 'Scroll up'], + ['scroll.horizonally?{count:-1}', 'Scroll left'], + ['scroll.horizonally?{count:1}', 'Scroll right'], + ['scroll.home', 'Scroll leftmost'], + ['scroll.end', 'Scroll last'], + ['scroll.pages?{count:-0.5}', 'Scroll up by half of screen'], + ['scroll.pages?{count:0.5}', 'Scroll up by half of screen'], + ['scroll.pages?{count:-1}', 'Scroll up by a screen'], + ['scroll.pages?{count:1}', 'Scroll up by a screen'], + ], [ + ['tabs.close', 'Close a tab'], + ['tabs.reopen', 'Reopen closed tab'], + ['tabs.next?{count:1}', 'Select next Tab'], + ['tabs.prev?{count:1}', 'Select prev Tab'], + ['tabs.first', 'Select first tab'], + ['tabs.last', 'Select last tab'], + ['tabs.reload?{cache:true}', 'Reload current tab'], + ['tabs.pin.toggle', 'Toggle pinned state'], + ['tabs.duplicate', 'Dupplicate a tab'], + ], [ + ['follow.start?{newTab:false}', 'Follow a link'], + ['follow.start?{newTab:true}', 'Follow a link in new tab'], + ['navigate.histories.prev', 'Go back in histories'], + ['navigate.histories.next', 'Go forward in histories'], + ['navigate.link.next', 'Open next link'], + ['navigate.link.prev', 'Open previous link'], + ['navigate.parent', 'Go to parent directory'], + ['navigate.root', 'Go to root directory'], + ], [ + ['find.start', 'Start find mode'], + ['find.next', 'Find next word'], + ['find.prev', 'Find previous word'], + ], [ + ['command.show', 'Open console'], + ['command.show.open?{alter:false}', 'Open URL'], + ['command.show.open?{alter:true}', 'Alter URL'], + ['command.show.tabopen?{alter:false}', 'Open URL in new Tab'], + ['command.show.tabopen?{alter:true}', 'Alter URL in new Tab'], + ['command.show.winopen?{alter:false}', 'Open URL in new window'], + ['command.show.winopen?{alter:true}', 'Alter URL in new window'], + ['command.show.buffer', 'Open buffer command'], + ], [ + ['addon.toggle.enabled', 'Enable or disable'], + ['urls.yank', 'Copy current URL'], + ['zoom.in', 'Zoom-in'], + ['zoom.out', 'Zoom-out'], + ['zoom.neutral', 'Reset zoom level'], + ] +]; + class SettingsComponent extends Component { constructor(props, context) { super(props, context); @@ -29,33 +82,87 @@ class SettingsComponent extends Component { settings: { source: settings.source, json: settings.json, + form: settings.form, } }); } + renderFormFields() { + let keymapFields = KeyMapFields.map((group, index) => { + return <div key={index} className='keymap-fields-group'> + { + group.map((field) => { + let name = field[0]; + let label = field[1]; + let value = this.state.settings.form.keymaps[name]; + return <Input + type='text' + id={name} + name={name} + key={name} + label={label} + value={value} + onChange={this.bindFormKeymapsValue.bind(this)} + />; + }) + } + </div>; + }); + + return <div> + <fieldset> + <legend>Keybindings</legend> + <div className='keymap-fields'> + { keymapFields } + </div> + </fieldset> + </div>; + } + + renderJsonFields() { + return <div> + <Input + type='textarea' + name='json' + label='Plane JSON' + spellCheck='false' + error={this.state.errors.json} + onChange={this.bindValue.bind(this)} + onBlur={this.save.bind(this)} + value={this.state.settings.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(); + } return ( <div> <h1>Configure Vim-Vixen</h1> <form className='vimvixen-settings-form'> + <Input + type='radio' + id='setting-source-form' + name='source' + label='Use form' + checked={this.state.settings.source === 'form'} + value='form' + onChange={this.bindValue.bind(this)} /> <Input type='radio' name='source' label='Use plain JSON' checked={this.state.settings.source === 'json'} - value='json' /> + value='json' + onChange={this.bindValue.bind(this)} /> - <Input - type='textarea' - name='json' - label='Plane JSON' - spellCheck='false' - error={this.state.errors.json} - onChange={this.bindValue.bind(this)} - onBlur={this.bindAndSave.bind(this)} - value={this.state.settings.json} - /> + { fields } </form> </div> ); @@ -82,9 +189,15 @@ class SettingsComponent extends Component { this.setState(next); } - bindAndSave(e) { - this.bindValue(e); + bindFormKeymapsValue(e) { + let next = Object.assign({}, this.state); + + next.settings.form.keymaps[e.target.name] = e.target.value; + + this.context.store.dispatch(settingActions.save(next.settings)); + } + save() { try { let json = this.state.settings.json; validator.validate(JSON.parse(json)); diff --git a/src/settings/components/site.scss b/src/settings/components/site.scss index aac4319..dcc52ce 100644 --- a/src/settings/components/site.scss +++ b/src/settings/components/site.scss @@ -7,4 +7,31 @@ min-height: 64ex; resize: vertical; } + + fieldset { + margin: 0; + padding: 0; + border: none; + margin-top: 1rem; + + fieldset:first-of-type { + margin-top: 1rem; + } + + legend { + font-size: 1.5rem; + line-height: 1.5rem; + font-weight: bold; + } + } + + .keymap-fields{ + column-count: 2; + .keymap-fields-group { + margin-top: 24px; + } + .keymap-fields-group:first-of-type { + margin-top: 0; + } + } } diff --git a/src/settings/components/ui/input.jsx b/src/settings/components/ui/input.jsx index 9b6c229..5138411 100644 --- a/src/settings/components/ui/input.jsx +++ b/src/settings/components/ui/input.jsx @@ -3,32 +3,35 @@ import './input.scss'; class Input extends Component { + renderText(props) { + let inputClassName = props.error ? 'input-error' : ''; + return <div className='settings-ui-input'> + <label + type='text' + htmlFor={props.id} + >{ props.label }</label> + <input type='text' className={inputClassName} {...props} /> + </div>; + } + renderRadio(props) { - let inputClasses = 'form-field-input'; - if (props.error) { - inputClasses += ' input-error'; - } - return <div> + let inputClassName = props.error ? 'input-error' : ''; + return <div className='settings-ui-input'> <label> - <input className={ inputClasses } type='radio' {...props} /> + <input type='radio' className={inputClassName} {...props} /> { props.label } </label> </div>; } renderTextArea(props) { - let inputClasses = 'form-field-input'; - if (props.error) { - inputClasses += ' input-error'; - } - return <div> + let inputClassName = props.error ? 'input-error' : ''; + return <div className='settings-ui-input'> <label - className='form-field-label' htmlFor={props.id} >{ props.label }</label> - <textarea className={inputClasses} {...props} /> - - <p className='form-field-error'>{ this.props.error }</p> + <textarea className={inputClassName} {...props} /> + <p className='settings-ui-input-error'>{ this.props.error }</p> </div>; } @@ -36,6 +39,8 @@ class Input extends Component { let { type } = this.props; switch (this.props.type) { + case 'text': + return this.renderText(this.props); case 'radio': return this.renderRadio(this.props); case 'textarea': diff --git a/src/settings/components/ui/input.scss b/src/settings/components/ui/input.scss index 6187138..92df712 100644 --- a/src/settings/components/ui/input.scss +++ b/src/settings/components/ui/input.scss @@ -1,17 +1,28 @@ -.form-field-label { - font-weight: bold -} +.settings-ui-input { + page-break-inside: avoid; -.form-field-error { - font-weight: bold; - color: red; - min-height: 1.5em; -} + * { + page-break-inside: avoid; + } -.form-field-input { - padding: 4px; -} + label { + font-weight: bold; + min-width: 14rem; + display: inline-block; + } + + input { + padding: 4px; + } + + input.input-crror, + textarea.input-error { + box-shadow: 0 0 2px red; + } -.form-field-input.input-error { - box-shadow: 0 0 2px red; + &-error { + font-weight: bold; + color: red; + min-height: 1.5em; + } } diff --git a/src/settings/reducers/setting.js b/src/settings/reducers/setting.js index a61c09f..70c6183 100644 --- a/src/settings/reducers/setting.js +++ b/src/settings/reducers/setting.js @@ -3,6 +3,7 @@ import actions from 'settings/actions'; const defaultState = { source: '', json: '', + form: null, value: {} }; @@ -12,6 +13,7 @@ export default function reducer(state = defaultState, action = {}) { return { source: action.source, json: action.json, + form: action.form, value: action.value, }; default: diff --git a/src/shared/default-settings.js b/src/shared/default-settings.js index 608890b..c1402b7 100644 --- a/src/shared/default-settings.js +++ b/src/shared/default-settings.js @@ -62,5 +62,69 @@ export default { "wikipedia": "https://en.wikipedia.org/w/index.php?search={}" } } -}` +}`, + + 'form': { + 'keymaps': { + 'scroll.vertically?{count:-1}': 'j', + 'scroll.vertically?{count:1}': 'k', + 'scroll.horizonally?{count:-1}': 'h', + 'scroll.horizonally?{count:1}': 'l', + 'scroll.home': '0', + 'scroll.end': '$', + 'scroll.pages?{count:-0.5}': '<C-U>', + 'scroll.pages?{count:0.5}': '<C-D>', + 'scroll.pages?{count:-1}': '<C-B>', + 'scroll.pages?{count:1}': '<C-F>', + + 'tabs.close': 'd', + 'tabs.reopen': 'u', + 'tabs.next?{count:1}': 'J', + 'tabs.prev?{count:1}': 'K', + 'tabs.first': 'g0', + 'tabs.last': 'g$', + 'tabs.reload?{cache:true}': 'r', + 'tabs.pin.toggle': 'zp', + 'tabs.duplicate': 'zd', + + 'follow.start?{newTab:false}': 'f', + 'follow.start?{newTab:true}': 'F', + 'navigate.histories.prev': 'H', + 'navigate.histories.next': 'L', + 'navigate.link.next': ']]', + 'navigate.link.prev': '[[', + 'navigate.parent': 'gu', + 'navigate.root': 'gU', + + 'find.start': '/', + 'find.next': 'n', + 'find.prev': 'N', + + 'command.show': ':', + 'command.show.open?{alter:false}': 'o', + 'command.show.open?{alter:true}': 'O', + 'command.show.tabopen?{alter:false}': 't', + 'command.show.tabopen?{alter:true}': 'T', + 'command.show.winopen?{alter:false}': 'w', + 'command.show.winopen?{alter:true}': 'W', + 'command.show.buffer': 'b', + + 'addon.toggle.enabled': '<S-Esc>', + 'urls.yank': 'y', + 'zoom.in': 'zi', + 'zoom.out': 'zo', + 'zoom.neutral': 'zz', + }, + 'search': { + 'default': 'google', + 'engines': { + 'google': 'https://google.com/search?q={}', + 'yahoo': 'https://search.yahoo.com/search?p={}', + 'bing': 'https://www.bing.com/search?q={}', + 'duckduckgo': 'https://duckduckgo.com/?q={}', + 'twitter': 'https://twitter.com/search?q={}', + 'wikipedia': 'https://en.wikipedia.org/w/index.php?search={}' + } + } + } }; |