From 55f15c9350cc531324e6182895f494eeb8174bc6 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 29 Apr 2019 09:13:59 +0900
Subject: Move to react

---
 src/settings/components/form/keymaps-form.jsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/settings/components/form/keymaps-form.jsx')

diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx
index ca51c96..8c0a83f 100644
--- a/src/settings/components/form/keymaps-form.jsx
+++ b/src/settings/components/form/keymaps-form.jsx
@@ -1,5 +1,5 @@
 import './keymaps-form.scss';
-import { h, Component } from 'preact';
+import React from 'react';
 import Input from '../ui/input';
 
 const KeyMapFields = [
@@ -72,7 +72,7 @@ const KeyMapFields = [
 
 const AllowdOps = [].concat(...KeyMapFields.map(group => group.map(e => e[0])));
 
-class KeymapsForm extends Component {
+class KeymapsForm extends React.Component {
 
   render() {
     let values = this.props.value;
-- 
cgit v1.2.3


From be900aa25ca205c467dcbbab9c284ef680441996 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Mon, 29 Apr 2019 15:27:23 +0900
Subject: Clean settings components

---
 src/settings/actions/setting.js                   |   5 +-
 src/settings/components/form/BlacklistForm.jsx    |  52 ++++++++++
 src/settings/components/form/BlacklistForm.scss   |   9 ++
 src/settings/components/form/KeymapsForm.jsx      |  47 +++++++++
 src/settings/components/form/KeymapsForm.scss     |  11 ++
 src/settings/components/form/PropertiesForm.jsx   |  60 +++++++++++
 src/settings/components/form/PropertiesForm.scss  |  12 +++
 src/settings/components/form/SearchForm.jsx       |  78 ++++++++++++++
 src/settings/components/form/SearchForm.scss      |  28 +++++
 src/settings/components/form/blacklist-form.jsx   |  52 ----------
 src/settings/components/form/blacklist-form.scss  |   9 --
 src/settings/components/form/keymaps-form.jsx     | 118 ----------------------
 src/settings/components/form/keymaps-form.scss    |  11 --
 src/settings/components/form/properties-form.jsx  |  60 -----------
 src/settings/components/form/properties-form.scss |  12 ---
 src/settings/components/form/search-form.jsx      |  78 --------------
 src/settings/components/form/search-form.scss     |  28 -----
 src/settings/components/index.jsx                 |  12 +--
 src/settings/components/ui/AddButton.jsx          |  12 +++
 src/settings/components/ui/AddButton.scss         |  13 +++
 src/settings/components/ui/DeleteButton.jsx       |  12 +++
 src/settings/components/ui/DeleteButton.scss      |  13 +++
 src/settings/components/ui/Input.jsx              |  52 ++++++++++
 src/settings/components/ui/Input.scss             |  29 ++++++
 src/settings/components/ui/add-button.jsx         |  12 ---
 src/settings/components/ui/add-button.scss        |  13 ---
 src/settings/components/ui/delete-button.jsx      |  12 ---
 src/settings/components/ui/delete-button.scss     |  13 ---
 src/settings/components/ui/input.jsx              |  52 ----------
 src/settings/components/ui/input.scss             |  29 ------
 src/settings/index.jsx                            |   7 +-
 src/settings/keymaps.js                           |  74 ++++++++++++++
 32 files changed, 514 insertions(+), 511 deletions(-)
 create mode 100644 src/settings/components/form/BlacklistForm.jsx
 create mode 100644 src/settings/components/form/BlacklistForm.scss
 create mode 100644 src/settings/components/form/KeymapsForm.jsx
 create mode 100644 src/settings/components/form/KeymapsForm.scss
 create mode 100644 src/settings/components/form/PropertiesForm.jsx
 create mode 100644 src/settings/components/form/PropertiesForm.scss
 create mode 100644 src/settings/components/form/SearchForm.jsx
 create mode 100644 src/settings/components/form/SearchForm.scss
 delete mode 100644 src/settings/components/form/blacklist-form.jsx
 delete mode 100644 src/settings/components/form/blacklist-form.scss
 delete mode 100644 src/settings/components/form/keymaps-form.jsx
 delete mode 100644 src/settings/components/form/keymaps-form.scss
 delete mode 100644 src/settings/components/form/properties-form.jsx
 delete mode 100644 src/settings/components/form/properties-form.scss
 delete mode 100644 src/settings/components/form/search-form.jsx
 delete mode 100644 src/settings/components/form/search-form.scss
 create mode 100644 src/settings/components/ui/AddButton.jsx
 create mode 100644 src/settings/components/ui/AddButton.scss
 create mode 100644 src/settings/components/ui/DeleteButton.jsx
 create mode 100644 src/settings/components/ui/DeleteButton.scss
 create mode 100644 src/settings/components/ui/Input.jsx
 create mode 100644 src/settings/components/ui/Input.scss
 delete mode 100644 src/settings/components/ui/add-button.jsx
 delete mode 100644 src/settings/components/ui/add-button.scss
 delete mode 100644 src/settings/components/ui/delete-button.jsx
 delete mode 100644 src/settings/components/ui/delete-button.scss
 delete mode 100644 src/settings/components/ui/input.jsx
 delete mode 100644 src/settings/components/ui/input.scss
 create mode 100644 src/settings/keymaps.js

(limited to 'src/settings/components/form/keymaps-form.jsx')

diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js
index 0277159..8844252 100644
--- a/src/settings/actions/setting.js
+++ b/src/settings/actions/setting.js
@@ -1,8 +1,8 @@
 import actions from 'settings/actions';
 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';
+import keymaps from '../keymaps';
 
 const load = async() => {
   let settings = await settingsStorage.loadRaw();
@@ -29,8 +29,7 @@ const save = async(settings) => {
 const switchToForm = (json) => {
   try {
     validator.validate(JSON.parse(json));
-    // AllowdOps filters operations, this is dirty dependency
-    let form = settingsValues.formFromJson(json, KeymapsForm.AllowdOps);
+    let form = settingsValues.formFromJson(json, keymaps.allowedOps);
     return {
       type: actions.SETTING_SWITCH_TO_FORM,
       form,
diff --git a/src/settings/components/form/BlacklistForm.jsx b/src/settings/components/form/BlacklistForm.jsx
new file mode 100644
index 0000000..13571f1
--- /dev/null
+++ b/src/settings/components/form/BlacklistForm.jsx
@@ -0,0 +1,52 @@
+import './BlacklistForm.scss';
+import AddButton from '../ui/AddButton';
+import DeleteButton from '../ui/DeleteButton';
+import React from 'react';
+
+class BlacklistForm extends React.Component {
+
+  render() {
+    let value = this.props.value;
+    if (!value) {
+      value = [];
+    }
+
+    return <div className='form-blacklist-form'>
+      {
+        value.map((url, index) => {
+          return <div key={index} className='form-blacklist-form-row'>
+            <input data-index={index} type='text' name='url'
+              className='column-url' value={url}
+              onChange={this.bindValue.bind(this)} />
+            <DeleteButton data-index={index} name='delete'
+              onClick={this.bindValue.bind(this)} />
+          </div>;
+        })
+      }
+      <AddButton name='add' style={{ float: 'right' }}
+        onClick={this.bindValue.bind(this)} />
+    </div>;
+  }
+
+  bindValue(e) {
+    if (!this.props.onChange) {
+      return;
+    }
+
+    let name = e.target.name;
+    let index = e.target.getAttribute('data-index');
+    let next = this.props.value ? this.props.value.slice() : [];
+
+    if (name === 'url') {
+      next[index] = e.target.value;
+    } else if (name === 'add') {
+      next.push('');
+    } else if (name === 'delete') {
+      next.splice(index, 1);
+    }
+
+    this.props.onChange(next);
+  }
+}
+
+export default BlacklistForm;
diff --git a/src/settings/components/form/BlacklistForm.scss b/src/settings/components/form/BlacklistForm.scss
new file mode 100644
index 0000000..a230d0d
--- /dev/null
+++ b/src/settings/components/form/BlacklistForm.scss
@@ -0,0 +1,9 @@
+.form-blacklist-form {
+  &-row {
+    display: flex;
+
+    .column-url {
+      flex: 1;
+    }
+  }
+}
diff --git a/src/settings/components/form/KeymapsForm.jsx b/src/settings/components/form/KeymapsForm.jsx
new file mode 100644
index 0000000..25a3710
--- /dev/null
+++ b/src/settings/components/form/KeymapsForm.jsx
@@ -0,0 +1,47 @@
+import './KeymapsForm.scss';
+import React from 'react';
+import Input from '../ui/Input';
+import keymaps from '../../keymaps';
+
+class KeymapsForm extends React.Component {
+
+  render() {
+    let values = this.props.value;
+    if (!values) {
+      values = {};
+    }
+    return <div className='form-keymaps-form'>
+      {
+        keymaps.fields.map((group, index) => {
+          return <div key={index} className='form-keymaps-form-field-group'>
+            {
+              group.map((field) => {
+                let name = field[0];
+                let label = field[1];
+                let value = values[name];
+                return <Input
+                  type='text' id={name} name={name} key={name}
+                  label={label} value={value}
+                  onChange={this.bindValue.bind(this)}
+                />;
+              })
+            }
+          </div>;
+        })
+      }
+    </div>;
+  }
+
+  bindValue(e) {
+    if (!this.props.onChange) {
+      return;
+    }
+
+    let next = { ...this.props.value };
+    next[e.target.name] = e.target.value;
+
+    this.props.onChange(next);
+  }
+}
+
+export default KeymapsForm;
diff --git a/src/settings/components/form/KeymapsForm.scss b/src/settings/components/form/KeymapsForm.scss
new file mode 100644
index 0000000..1a4e5cd
--- /dev/null
+++ b/src/settings/components/form/KeymapsForm.scss
@@ -0,0 +1,11 @@
+.form-keymaps-form {
+  column-count: 3;
+
+  &-field-group {
+    margin-top: 24px;
+  }
+
+  &-field-group:first-of-type {
+    margin-top: 24px;
+  }
+}
diff --git a/src/settings/components/form/PropertiesForm.jsx b/src/settings/components/form/PropertiesForm.jsx
new file mode 100644
index 0000000..77991fc
--- /dev/null
+++ b/src/settings/components/form/PropertiesForm.jsx
@@ -0,0 +1,60 @@
+import './PropertiesForm.scss';
+import React from 'react';
+
+class PropertiesForm extends React.Component {
+
+  render() {
+    let types = this.props.types;
+    let value = this.props.value;
+    if (!value) {
+      value = {};
+    }
+
+    return <div className='form-properties-form'>
+      {
+        Object.keys(types).map((name) => {
+          let type = types[name];
+          let inputType = null;
+          if (type === 'string') {
+            inputType = 'text';
+          } else if (type === 'number') {
+            inputType = 'number';
+          } else if (type === 'boolean') {
+            inputType = 'checkbox';
+          }
+          return <div key={name} className='form-properties-form-row'>
+            <label>
+              <span className='column-name'>{name}</span>
+              <input type={inputType} name={name}
+                className='column-input'
+                value={value[name] ? value[name] : ''}
+                onChange={this.bindValue.bind(this)}
+                checked={value[name]}
+              />
+            </label>
+          </div>;
+        })
+      }
+    </div>;
+  }
+
+  bindValue(e) {
+    if (!this.props.onChange) {
+      return;
+    }
+
+    let name = e.target.name;
+    let next = { ...this.props.value };
+    if (e.target.type.toLowerCase() === 'checkbox') {
+      next[name] = e.target.checked;
+    } else if (e.target.type.toLowerCase() === 'number') {
+      next[name] = Number(e.target.value);
+    } else {
+      next[name] = e.target.value;
+    }
+
+    this.props.onChange(next);
+  }
+}
+
+export default PropertiesForm;
diff --git a/src/settings/components/form/PropertiesForm.scss b/src/settings/components/form/PropertiesForm.scss
new file mode 100644
index 0000000..7c9e167
--- /dev/null
+++ b/src/settings/components/form/PropertiesForm.scss
@@ -0,0 +1,12 @@
+.form-properties-form {
+  &-row {
+    .column-name {
+      display: inline-block;
+      min-width: 5rem;
+      font-weight: bold;
+    }
+    .column-input {
+      line-height: 2.2rem;
+    }
+  }
+}
diff --git a/src/settings/components/form/SearchForm.jsx b/src/settings/components/form/SearchForm.jsx
new file mode 100644
index 0000000..f52fd5f
--- /dev/null
+++ b/src/settings/components/form/SearchForm.jsx
@@ -0,0 +1,78 @@
+import './SearchForm.scss';
+import React from 'react';
+import AddButton from '../ui/AddButton';
+import DeleteButton from '../ui/DeleteButton';
+
+class SearchForm extends React.Component {
+
+  render() {
+    let value = this.props.value;
+    if (!value) {
+      value = { default: '', engines: []};
+    }
+    if (!value.engines) {
+      value.engines = [];
+    }
+
+    return <div className='form-search-form'>
+      <div className='form-search-form-header'>
+        <div className='column-name'>Name</div>
+        <div className='column-url'>URL</div>
+        <div className='column-option'>Default</div>
+      </div>
+      {
+        value.engines.map((engine, index) => {
+          return <div key={index} className='form-search-form-row'>
+            <input data-index={index} type='text' name='name'
+              className='column-name' value={engine[0]}
+              onChange={this.bindValue.bind(this)} />
+            <input data-index={index} type='text' name='url'
+              placeholder='http://example.com/?q={}'
+              className='column-url' value={engine[1]}
+              onChange={this.bindValue.bind(this)} />
+            <div className='column-option'>
+              <input data-index={index} type='radio' name='default'
+                checked={value.default === engine[0]}
+                onChange={this.bindValue.bind(this)} />
+              <DeleteButton data-index={index} name='delete'
+                onClick={this.bindValue.bind(this)} />
+            </div>
+          </div>;
+        })
+      }
+      <AddButton name='add' style={{ float: 'right' }}
+        onClick={this.bindValue.bind(this)} />
+    </div>;
+  }
+
+  bindValue(e) {
+    if (!this.props.onChange) {
+      return;
+    }
+
+    let value = this.props.value;
+    let name = e.target.name;
+    let index = e.target.getAttribute('data-index');
+    let next = {
+      default: value.default,
+      engines: value.engines ? value.engines.slice() : [],
+    };
+
+    if (name === 'name') {
+      next.engines[index][0] = e.target.value;
+      next.default = this.props.value.engines[index][0];
+    } else if (name === 'url') {
+      next.engines[index][1] = e.target.value;
+    } else if (name === 'default') {
+      next.default = this.props.value.engines[index][0];
+    } else if (name === 'add') {
+      next.engines.push(['', '']);
+    } else if (name === 'delete') {
+      next.engines.splice(index, 1);
+    }
+
+    this.props.onChange(next);
+  }
+}
+
+export default SearchForm;
diff --git a/src/settings/components/form/SearchForm.scss b/src/settings/components/form/SearchForm.scss
new file mode 100644
index 0000000..26b2f44
--- /dev/null
+++ b/src/settings/components/form/SearchForm.scss
@@ -0,0 +1,28 @@
+.form-search-form {
+  @mixin row-base {
+    display: flex;
+
+    .column-name {
+      flex: 1;
+      min-width: 0;
+    }
+    .column-url {
+      flex: 5;
+      min-width: 0;
+    }
+    .column-option {
+      text-align: right;
+      flex-basis: 5rem;
+    }
+  }
+
+  &-header {
+    @include row-base;
+
+    font-weight: bold;
+  }
+
+  &-row {
+    @include row-base;
+  }
+}
diff --git a/src/settings/components/form/blacklist-form.jsx b/src/settings/components/form/blacklist-form.jsx
deleted file mode 100644
index 492c3af..0000000
--- a/src/settings/components/form/blacklist-form.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import './blacklist-form.scss';
-import AddButton from '../ui/add-button';
-import DeleteButton from '../ui/delete-button';
-import React from 'react';
-
-class BlacklistForm extends React.Component {
-
-  render() {
-    let value = this.props.value;
-    if (!value) {
-      value = [];
-    }
-
-    return <div className='form-blacklist-form'>
-      {
-        value.map((url, index) => {
-          return <div key={index} className='form-blacklist-form-row'>
-            <input data-index={index} type='text' name='url'
-              className='column-url' value={url}
-              onChange={this.bindValue.bind(this)} />
-            <DeleteButton data-index={index} name='delete'
-              onClick={this.bindValue.bind(this)} />
-          </div>;
-        })
-      }
-      <AddButton name='add' style='float:right'
-        onClick={this.bindValue.bind(this)} />
-    </div>;
-  }
-
-  bindValue(e) {
-    if (!this.props.onChange) {
-      return;
-    }
-
-    let name = e.target.name;
-    let index = e.target.getAttribute('data-index');
-    let next = this.props.value ? this.props.value.slice() : [];
-
-    if (name === 'url') {
-      next[index] = e.target.value;
-    } else if (name === 'add') {
-      next.push('');
-    } else if (name === 'delete') {
-      next.splice(index, 1);
-    }
-
-    this.props.onChange(next);
-  }
-}
-
-export default BlacklistForm;
diff --git a/src/settings/components/form/blacklist-form.scss b/src/settings/components/form/blacklist-form.scss
deleted file mode 100644
index a230d0d..0000000
--- a/src/settings/components/form/blacklist-form.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-.form-blacklist-form {
-  &-row {
-    display: flex;
-
-    .column-url {
-      flex: 1;
-    }
-  }
-}
diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx
deleted file mode 100644
index 8c0a83f..0000000
--- a/src/settings/components/form/keymaps-form.jsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import './keymaps-form.scss';
-import React from 'react';
-import Input from '../ui/input';
-
-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 to leftmost'],
-    ['scroll.end', 'Scroll to rightmost'],
-    ['scroll.top', 'Scroll to top'],
-    ['scroll.bottom', 'Scroll to bottom'],
-    ['scroll.pages?{"count":-0.5}', 'Scroll up by half of screen'],
-    ['scroll.pages?{"count":0.5}', 'Scroll down by half of screen'],
-    ['scroll.pages?{"count":-1}', 'Scroll up by a screen'],
-    ['scroll.pages?{"count":1}', 'Scroll down by a screen'],
-  ], [
-    ['mark.set.prefix', 'Set mark at current position'],
-    ['mark.jump.prefix', 'Jump to the mark'],
-  ], [
-    ['tabs.close', 'Close a tab'],
-    ['tabs.close.right', 'Close tabs to the right'],
-    ['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":false}', 'Reload current tab'],
-    ['tabs.reload?{"cache":true}', 'Reload with no caches'],
-    ['tabs.pin.toggle', 'Toggle pinned state'],
-    ['tabs.duplicate', 'Duplicate a tab'],
-  ], [
-    ['follow.start?{"newTab":false}', 'Follow a link'],
-    ['follow.start?{"newTab":true}', 'Follow a link in new tab'],
-    ['navigate.history.prev', 'Go back in histories'],
-    ['navigate.history.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'],
-    ['page.source', 'Open page source'],
-    ['page.home', 'Open start page to current tab'],
-    ['page.home?{"newTab":true}', 'Open start page in new tab'],
-    ['focus.input', 'Focus input'],
-  ], [
-    ['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'],
-    ['command.show.addbookmark?{"alter":true}', 'Open addbookmark command'],
-  ], [
-    ['addon.toggle.enabled', 'Enable or disable'],
-    ['urls.yank', 'Copy current URL'],
-    ['urls.paste?{"newTab":false}', 'Open clipboard\'s URL in current tab'],
-    ['urls.paste?{"newTab":true}', 'Open clipboard\'s URL in new tab'],
-    ['zoom.in', 'Zoom-in'],
-    ['zoom.out', 'Zoom-out'],
-    ['zoom.neutral', 'Reset zoom level'],
-    ['page.source', 'Open a page source'],
-  ]
-];
-
-const AllowdOps = [].concat(...KeyMapFields.map(group => group.map(e => e[0])));
-
-class KeymapsForm extends React.Component {
-
-  render() {
-    let values = this.props.value;
-    if (!values) {
-      values = {};
-    }
-    return <div className='form-keymaps-form'>
-      {
-        KeyMapFields.map((group, index) => {
-          return <div key={index} className='form-keymaps-form-field-group'>
-            {
-              group.map((field) => {
-                let name = field[0];
-                let label = field[1];
-                let value = values[name];
-                return <Input
-                  type='text' id={name} name={name} key={name}
-                  label={label} value={value}
-                  onChange={this.bindValue.bind(this)}
-                />;
-              })
-            }
-          </div>;
-        })
-      }
-    </div>;
-  }
-
-  bindValue(e) {
-    if (!this.props.onChange) {
-      return;
-    }
-
-    let next = { ...this.props.value };
-    next[e.target.name] = e.target.value;
-
-    this.props.onChange(next);
-  }
-}
-
-KeymapsForm.AllowdOps = AllowdOps;
-
-export default KeymapsForm;
diff --git a/src/settings/components/form/keymaps-form.scss b/src/settings/components/form/keymaps-form.scss
deleted file mode 100644
index 1a4e5cd..0000000
--- a/src/settings/components/form/keymaps-form.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.form-keymaps-form {
-  column-count: 3;
-
-  &-field-group {
-    margin-top: 24px;
-  }
-
-  &-field-group:first-of-type {
-    margin-top: 24px;
-  }
-}
diff --git a/src/settings/components/form/properties-form.jsx b/src/settings/components/form/properties-form.jsx
deleted file mode 100644
index 7d591d5..0000000
--- a/src/settings/components/form/properties-form.jsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import './properties-form.scss';
-import React from 'react';
-
-class PropertiesForm extends React.Component {
-
-  render() {
-    let types = this.props.types;
-    let value = this.props.value;
-    if (!value) {
-      value = {};
-    }
-
-    return <div className='form-properties-form'>
-      {
-        Object.keys(types).map((name) => {
-          let type = types[name];
-          let inputType = null;
-          if (type === 'string') {
-            inputType = 'text';
-          } else if (type === 'number') {
-            inputType = 'number';
-          } else if (type === 'boolean') {
-            inputType = 'checkbox';
-          }
-          return <div key={name} className='form-properties-form-row'>
-            <label>
-              <span className='column-name'>{name}</span>
-              <input type={inputType} name={name}
-                className='column-input'
-                value={value[name] ? value[name] : ''}
-                onChange={this.bindValue.bind(this)}
-                checked={value[name]}
-              />
-            </label>
-          </div>;
-        })
-      }
-    </div>;
-  }
-
-  bindValue(e) {
-    if (!this.props.onChange) {
-      return;
-    }
-
-    let name = e.target.name;
-    let next = { ...this.props.value };
-    if (e.target.type.toLowerCase() === 'checkbox') {
-      next[name] = e.target.checked;
-    } else if (e.target.type.toLowerCase() === 'number') {
-      next[name] = Number(e.target.value);
-    } else {
-      next[name] = e.target.value;
-    }
-
-    this.props.onChange(next);
-  }
-}
-
-export default PropertiesForm;
diff --git a/src/settings/components/form/properties-form.scss b/src/settings/components/form/properties-form.scss
deleted file mode 100644
index 7c9e167..0000000
--- a/src/settings/components/form/properties-form.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.form-properties-form {
-  &-row {
-    .column-name {
-      display: inline-block;
-      min-width: 5rem;
-      font-weight: bold;
-    }
-    .column-input {
-      line-height: 2.2rem;
-    }
-  }
-}
diff --git a/src/settings/components/form/search-form.jsx b/src/settings/components/form/search-form.jsx
deleted file mode 100644
index 81204f3..0000000
--- a/src/settings/components/form/search-form.jsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import './search-form.scss';
-import React from 'react';
-import AddButton from '../ui/add-button';
-import DeleteButton from '../ui/delete-button';
-
-class SearchForm extends React.Component {
-
-  render() {
-    let value = this.props.value;
-    if (!value) {
-      value = { default: '', engines: []};
-    }
-    if (!value.engines) {
-      value.engines = [];
-    }
-
-    return <div className='form-search-form'>
-      <div className='form-search-form-header'>
-        <div className='column-name'>Name</div>
-        <div className='column-url'>URL</div>
-        <div className='column-option'>Default</div>
-      </div>
-      {
-        value.engines.map((engine, index) => {
-          return <div key={index} className='form-search-form-row'>
-            <input data-index={index} type='text' name='name'
-              className='column-name' value={engine[0]}
-              onChange={this.bindValue.bind(this)} />
-            <input data-index={index} type='text' name='url'
-              placeholder='http://example.com/?q={}'
-              className='column-url' value={engine[1]}
-              onChange={this.bindValue.bind(this)} />
-            <div className='column-option'>
-              <input data-index={index} type='radio' name='default'
-                checked={value.default === engine[0]}
-                onChange={this.bindValue.bind(this)} />
-              <DeleteButton data-index={index} name='delete'
-                onClick={this.bindValue.bind(this)} />
-            </div>
-          </div>;
-        })
-      }
-      <AddButton name='add' style='float:right'
-        onClick={this.bindValue.bind(this)} />
-    </div>;
-  }
-
-  bindValue(e) {
-    if (!this.props.onChange) {
-      return;
-    }
-
-    let value = this.props.value;
-    let name = e.target.name;
-    let index = e.target.getAttribute('data-index');
-    let next = {
-      default: value.default,
-      engines: value.engines ? value.engines.slice() : [],
-    };
-
-    if (name === 'name') {
-      next.engines[index][0] = e.target.value;
-      next.default = this.props.value.engines[index][0];
-    } else if (name === 'url') {
-      next.engines[index][1] = e.target.value;
-    } else if (name === 'default') {
-      next.default = this.props.value.engines[index][0];
-    } else if (name === 'add') {
-      next.engines.push(['', '']);
-    } else if (name === 'delete') {
-      next.engines.splice(index, 1);
-    }
-
-    this.props.onChange(next);
-  }
-}
-
-export default SearchForm;
diff --git a/src/settings/components/form/search-form.scss b/src/settings/components/form/search-form.scss
deleted file mode 100644
index 26b2f44..0000000
--- a/src/settings/components/form/search-form.scss
+++ /dev/null
@@ -1,28 +0,0 @@
-.form-search-form {
-  @mixin row-base {
-    display: flex;
-
-    .column-name {
-      flex: 1;
-      min-width: 0;
-    }
-    .column-url {
-      flex: 5;
-      min-width: 0;
-    }
-    .column-option {
-      text-align: right;
-      flex-basis: 5rem;
-    }
-  }
-
-  &-header {
-    @include row-base;
-
-    font-weight: bold;
-  }
-
-  &-row {
-    @include row-base;
-  }
-}
diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx
index 9dae25e..affa2e9 100644
--- a/src/settings/components/index.jsx
+++ b/src/settings/components/index.jsx
@@ -1,11 +1,11 @@
 import './site.scss';
 import React from 'react';
 import { connect } from 'react-redux';
-import Input from './ui/input';
-import SearchForm from './form/search-form';
-import KeymapsForm from './form/keymaps-form';
-import BlacklistForm from './form/blacklist-form';
-import PropertiesForm from './form/properties-form';
+import Input from './ui/Input';
+import SearchForm from './form/SearchForm';
+import KeymapsForm from './form/KeymapsForm';
+import BlacklistForm from './form/BlacklistForm';
+import PropertiesForm from './form/PropertiesForm';
 import * as properties from 'shared/settings/properties';
 import * as settingActions from 'settings/actions/setting';
 
@@ -136,7 +136,7 @@ class SettingsComponent extends React.Component {
       this.props.dispatch(settingActions.switchToForm(this.props.json));
     }
 
-    let settings = this.props.getState();
+    let settings = this.props.store.getState();
     this.props.dispatch(settingActions.save(settings));
   }
 }
diff --git a/src/settings/components/ui/AddButton.jsx b/src/settings/components/ui/AddButton.jsx
new file mode 100644
index 0000000..185a03b
--- /dev/null
+++ b/src/settings/components/ui/AddButton.jsx
@@ -0,0 +1,12 @@
+import './AddButton.scss';
+import React from 'react';
+
+class AddButton extends React.Component {
+  render() {
+    return <input
+      className='ui-add-button' type='button' value='&#x271a;'
+      {...this.props} />;
+  }
+}
+
+export default AddButton;
diff --git a/src/settings/components/ui/AddButton.scss b/src/settings/components/ui/AddButton.scss
new file mode 100644
index 0000000..beb5688
--- /dev/null
+++ b/src/settings/components/ui/AddButton.scss
@@ -0,0 +1,13 @@
+.ui-add-button {
+  border: none;
+  padding: 4;
+  display: inline;
+  background: none;
+  font-weight: bold;
+  color: green;
+  cursor: pointer;
+
+  &:hover {
+    color: darkgreen;
+  }
+}
diff --git a/src/settings/components/ui/DeleteButton.jsx b/src/settings/components/ui/DeleteButton.jsx
new file mode 100644
index 0000000..75811cd
--- /dev/null
+++ b/src/settings/components/ui/DeleteButton.jsx
@@ -0,0 +1,12 @@
+import './DeleteButton.scss';
+import React from 'react';
+
+class DeleteButton extends React.Component {
+  render() {
+    return <input
+      className='ui-delete-button' type='button' value='&#x2716;'
+      {...this.props} />;
+  }
+}
+
+export default DeleteButton;
diff --git a/src/settings/components/ui/DeleteButton.scss b/src/settings/components/ui/DeleteButton.scss
new file mode 100644
index 0000000..5932a72
--- /dev/null
+++ b/src/settings/components/ui/DeleteButton.scss
@@ -0,0 +1,13 @@
+
+.ui-delete-button {
+  border: none;
+  padding: 4;
+  display: inline;
+  background: none;
+  color: red;
+  cursor: pointer;
+
+  &:hover {
+    color: darkred;
+  }
+}
diff --git a/src/settings/components/ui/Input.jsx b/src/settings/components/ui/Input.jsx
new file mode 100644
index 0000000..d090f5b
--- /dev/null
+++ b/src/settings/components/ui/Input.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import './Input.scss';
+
+class Input extends React.Component {
+
+  renderText(props) {
+    let inputClassName = props.error ? 'input-error' : '';
+    return <div className='settings-ui-input'>
+      <label htmlFor={props.id}>{ props.label }</label>
+      <input type='text' className={inputClassName} {...props} />
+    </div>;
+  }
+
+  renderRadio(props) {
+    let inputClassName = props.error ? 'input-error' : '';
+    return <div className='settings-ui-input'>
+      <label>
+        <input type='radio' className={inputClassName} {...props} />
+        { props.label }
+      </label>
+    </div>;
+  }
+
+  renderTextArea(props) {
+    let inputClassName = props.error ? 'input-error' : '';
+    return <div className='settings-ui-input'>
+      <label
+        htmlFor={props.id}
+      >{ props.label }</label>
+      <textarea className={inputClassName} {...props} />
+      <p className='settings-ui-input-error'>{ this.props.error }</p>
+    </div>;
+  }
+
+  render() {
+    let { type } = this.props;
+
+    switch (this.props.type) {
+    case 'text':
+      return this.renderText(this.props);
+    case 'radio':
+      return this.renderRadio(this.props);
+    case 'textarea':
+      return this.renderTextArea(this.props);
+    default:
+      console.warn(`Unsupported input type ${type}`);
+    }
+    return null;
+  }
+}
+
+export default Input;
diff --git a/src/settings/components/ui/Input.scss b/src/settings/components/ui/Input.scss
new file mode 100644
index 0000000..ad4daf8
--- /dev/null
+++ b/src/settings/components/ui/Input.scss
@@ -0,0 +1,29 @@
+.settings-ui-input {
+  page-break-inside: avoid;
+
+  * {
+    page-break-inside: avoid;
+  }
+
+  label {
+    font-weight: bold;
+    min-width: 14rem;
+    display: inline-block;
+  }
+
+  input[type='text'] {
+    padding: 4px;
+    width: 8rem;
+  }
+
+  input.input-crror,
+  textarea.input-error {
+    box-shadow: 0 0 2px red;
+  }
+
+  &-error {
+    font-weight: bold;
+    color: red;
+    min-height: 1.5em;
+  }
+}
diff --git a/src/settings/components/ui/add-button.jsx b/src/settings/components/ui/add-button.jsx
deleted file mode 100644
index 031d28b..0000000
--- a/src/settings/components/ui/add-button.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import './add-button.scss';
-import React from 'react';
-
-class AddButton extends React.Component {
-  render() {
-    return <input
-      className='ui-add-button' type='button' value='&#x271a;'
-      {...this.props} />;
-  }
-}
-
-export default AddButton;
diff --git a/src/settings/components/ui/add-button.scss b/src/settings/components/ui/add-button.scss
deleted file mode 100644
index beb5688..0000000
--- a/src/settings/components/ui/add-button.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-.ui-add-button {
-  border: none;
-  padding: 4;
-  display: inline;
-  background: none;
-  font-weight: bold;
-  color: green;
-  cursor: pointer;
-
-  &:hover {
-    color: darkgreen;
-  }
-}
diff --git a/src/settings/components/ui/delete-button.jsx b/src/settings/components/ui/delete-button.jsx
deleted file mode 100644
index 3dfe14a..0000000
--- a/src/settings/components/ui/delete-button.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import './delete-button.scss';
-import React from 'react';
-
-class DeleteButton extends React.Component {
-  render() {
-    return <input
-      className='ui-delete-button' type='button' value='&#x2716;'
-      {...this.props} />;
-  }
-}
-
-export default DeleteButton;
diff --git a/src/settings/components/ui/delete-button.scss b/src/settings/components/ui/delete-button.scss
deleted file mode 100644
index 5932a72..0000000
--- a/src/settings/components/ui/delete-button.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-
-.ui-delete-button {
-  border: none;
-  padding: 4;
-  display: inline;
-  background: none;
-  color: red;
-  cursor: pointer;
-
-  &:hover {
-    color: darkred;
-  }
-}
diff --git a/src/settings/components/ui/input.jsx b/src/settings/components/ui/input.jsx
deleted file mode 100644
index 2f42b62..0000000
--- a/src/settings/components/ui/input.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from 'react';
-import './input.scss';
-
-class Input extends React.Component {
-
-  renderText(props) {
-    let inputClassName = props.error ? 'input-error' : '';
-    return <div className='settings-ui-input'>
-      <label htmlFor={props.id}>{ props.label }</label>
-      <input type='text' className={inputClassName} {...props} />
-    </div>;
-  }
-
-  renderRadio(props) {
-    let inputClassName = props.error ? 'input-error' : '';
-    return <div className='settings-ui-input'>
-      <label>
-        <input type='radio' className={inputClassName} {...props} />
-        { props.label }
-      </label>
-    </div>;
-  }
-
-  renderTextArea(props) {
-    let inputClassName = props.error ? 'input-error' : '';
-    return <div className='settings-ui-input'>
-      <label
-        htmlFor={props.id}
-      >{ props.label }</label>
-      <textarea className={inputClassName} {...props} />
-      <p className='settings-ui-input-error'>{ this.props.error }</p>
-    </div>;
-  }
-
-  render() {
-    let { type } = this.props;
-
-    switch (this.props.type) {
-    case 'text':
-      return this.renderText(this.props);
-    case 'radio':
-      return this.renderRadio(this.props);
-    case 'textarea':
-      return this.renderTextArea(this.props);
-    default:
-      console.warn(`Unsupported input type ${type}`);
-    }
-    return null;
-  }
-}
-
-export default Input;
diff --git a/src/settings/components/ui/input.scss b/src/settings/components/ui/input.scss
deleted file mode 100644
index ad4daf8..0000000
--- a/src/settings/components/ui/input.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-.settings-ui-input {
-  page-break-inside: avoid;
-
-  * {
-    page-break-inside: avoid;
-  }
-
-  label {
-    font-weight: bold;
-    min-width: 14rem;
-    display: inline-block;
-  }
-
-  input[type='text'] {
-    padding: 4px;
-    width: 8rem;
-  }
-
-  input.input-crror,
-  textarea.input-error {
-    box-shadow: 0 0 2px red;
-  }
-
-  &-error {
-    font-weight: bold;
-    color: red;
-    min-height: 1.5em;
-  }
-}
diff --git a/src/settings/index.jsx b/src/settings/index.jsx
index 9df1073..6aec7a0 100644
--- a/src/settings/index.jsx
+++ b/src/settings/index.jsx
@@ -1,4 +1,5 @@
-import { render } from 'react';
+import React from 'react';
+import ReactDOM from 'react-dom';
 import SettingsComponent from './components';
 import reducer from './reducers/setting';
 import { Provider } from 'react-redux';
@@ -12,9 +13,9 @@ const store = createStore(
 
 document.addEventListener('DOMContentLoaded', () => {
   let wrapper = document.getElementById('vimvixen-settings');
-  render(
+  ReactDOM.render(
     <Provider store={store}>
-      <SettingsComponent />
+      <SettingsComponent store={store} />
     </Provider>,
     wrapper
   );
diff --git a/src/settings/keymaps.js b/src/settings/keymaps.js
new file mode 100644
index 0000000..ccfc74c
--- /dev/null
+++ b/src/settings/keymaps.js
@@ -0,0 +1,74 @@
+const fields = [
+  [
+    ['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 to leftmost'],
+    ['scroll.end', 'Scroll to rightmost'],
+    ['scroll.top', 'Scroll to top'],
+    ['scroll.bottom', 'Scroll to bottom'],
+    ['scroll.pages?{"count":-0.5}', 'Scroll up by half of screen'],
+    ['scroll.pages?{"count":0.5}', 'Scroll down by half of screen'],
+    ['scroll.pages?{"count":-1}', 'Scroll up by a screen'],
+    ['scroll.pages?{"count":1}', 'Scroll down by a screen'],
+  ], [
+    ['mark.set.prefix', 'Set mark at current position'],
+    ['mark.jump.prefix', 'Jump to the mark'],
+  ], [
+    ['tabs.close', 'Close a tab'],
+    ['tabs.close.right', 'Close tabs to the right'],
+    ['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":false}', 'Reload current tab'],
+    ['tabs.reload?{"cache":true}', 'Reload with no caches'],
+    ['tabs.pin.toggle', 'Toggle pinned state'],
+    ['tabs.duplicate', 'Duplicate a tab'],
+  ], [
+    ['follow.start?{"newTab":false}', 'Follow a link'],
+    ['follow.start?{"newTab":true}', 'Follow a link in new tab'],
+    ['navigate.history.prev', 'Go back in histories'],
+    ['navigate.history.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'],
+    ['page.source', 'Open page source'],
+    ['page.home', 'Open start page to current tab'],
+    ['page.home?{"newTab":true}', 'Open start page in new tab'],
+    ['focus.input', 'Focus input'],
+  ], [
+    ['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'],
+    ['command.show.addbookmark?{"alter":true}', 'Open addbookmark command'],
+  ], [
+    ['addon.toggle.enabled', 'Enable or disable'],
+    ['urls.yank', 'Copy current URL'],
+    ['urls.paste?{"newTab":false}', 'Open clipboard\'s URL in current tab'],
+    ['urls.paste?{"newTab":true}', 'Open clipboard\'s URL in new tab'],
+    ['zoom.in', 'Zoom-in'],
+    ['zoom.out', 'Zoom-out'],
+    ['zoom.neutral', 'Reset zoom level'],
+    ['page.source', 'Open a page source'],
+  ]
+];
+
+const allowedOps = [].concat(...fields.map(group => group.map(e => e[0])));
+
+export default {
+  fields,
+  allowedOps,
+};
-- 
cgit v1.2.3

