From c934550a020259ae3c5fc8b05eca7fc7a0fb54f4 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Sun, 26 Nov 2017 20:19:14 +0900
Subject: remove <C-Y>/<C-E> keymaps

---
 src/shared/settings/default.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js
index 69238e3..71465c9 100644
--- a/src/shared/settings/default.js
+++ b/src/shared/settings/default.js
@@ -15,8 +15,6 @@ export default {
     "j": { "type": "scroll.vertically", "count": 1 },
     "h": { "type": "scroll.horizonally", "count": -1 },
     "l": { "type": "scroll.horizonally", "count": 1 },
-    "<C-Y>": { "type": "scroll.vertically", "count": -1 },
-    "<C-E>": { "type": "scroll.vertically", "count": 1 },
     "<C-U>": { "type": "scroll.pages", "count": -0.5 },
     "<C-D>": { "type": "scroll.pages", "count": 0.5 },
     "<C-B>": { "type": "scroll.pages", "count": -1 },
-- 
cgit v1.2.3


From 8c79b2c5e7e3b25ce5eb0fa94e856b5b9b1f77f1 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Sun, 26 Nov 2017 21:06:39 +0900
Subject: fix keymap field

---
 src/settings/components/form/keymaps-form.jsx  | 4 ++--
 src/settings/components/form/keymaps-form.scss | 8 +++++---
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx
index f64320c..8ec0456 100644
--- a/src/settings/components/form/keymaps-form.jsx
+++ b/src/settings/components/form/keymaps-form.jsx
@@ -62,10 +62,10 @@ class KeymapsForm extends Component {
     if (!values) {
       values = {};
     }
-    return <div className='keymap-fields'>
+    return <div className='form-keymaps-form'>
       {
         KeyMapFields.map((group, index) => {
-          return <div key={index} className='form-keymaps-form'>
+          return <div key={index} className='form-keymaps-form-field-group'>
             {
               group.map((field) => {
                 let name = field[0];
diff --git a/src/settings/components/form/keymaps-form.scss b/src/settings/components/form/keymaps-form.scss
index 3a83910..1a4e5cd 100644
--- a/src/settings/components/form/keymaps-form.scss
+++ b/src/settings/components/form/keymaps-form.scss
@@ -1,9 +1,11 @@
 .form-keymaps-form {
   column-count: 3;
-  .keymap-fields-group {
+
+  &-field-group {
     margin-top: 24px;
   }
-  .keymap-fields-group:first-of-type {
-    margin-top: 0;
+
+  &-field-group:first-of-type {
+    margin-top: 24px;
   }
 }
-- 
cgit v1.2.3


From 556795585e9e60c1ffd5514c7fc1b6ff00841ae8 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Sun, 26 Nov 2017 21:19:18 +0900
Subject: update qa

---
 QA.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 90 insertions(+), 8 deletions(-)

diff --git a/QA.md b/QA.md
index 8cba39f..404f50f 100644
--- a/QA.md
+++ b/QA.md
@@ -1,12 +1,12 @@
 ## Checklist for testing Vim Vixen
 
-### Operations
+### Keybindings in JSON settings
 
 Test operations with default key maps.
 
 #### Scrolling
 
-- [ ] <kbd>k</kbd> or <kbd>Ctrl</kbd>+<kbd>Y</kbd>, <kbd>j</kbd> or <kbd>Ctrl</kbd>+<kbd>E</kbd>: scroll up and down
+- [ ] <kbd>k</kbd>, <kbd>j</kbd>: scroll up and down
 - [ ] <kbd>h</kbd>, <kbd>l</kbd>: scroll left and right
 - [ ] <kbd>Ctrl</kbd>+<kbd>U</kbd>, <kbd>Ctrl</kbd>+<kbd>D</kbd>: scroll up and down by half of screen
 - [ ] <kbd>Ctrl</kbd>+<kbd>B</kbd>, <kbd>Ctrl</kbd>+<kbd>F</kbd>: scroll up and down by a screen
@@ -48,6 +48,55 @@ The behaviors of the console are tested in [Console section](#consoles).
 - [ ] <kbd>y</kbd>: yank current URL and show a message
 - [ ] Toggle enabled/disabled of plugin bu <kbd>Shift</kbd>+<kbd>Esc</kbd>
 
+### Keybindings in form settings
+
+Test operations with default key maps.
+
+#### Scrolling
+
+- [ ] <kbd>k</kbd>, <kbd>j</kbd>: scroll up and down
+- [ ] <kbd>h</kbd>, <kbd>l</kbd>: scroll left and right
+- [ ] <kbd>Ctrl</kbd>+<kbd>U</kbd>, <kbd>Ctrl</kbd>+<kbd>D</kbd>: scroll up and down by half of screen
+- [ ] <kbd>Ctrl</kbd>+<kbd>B</kbd>, <kbd>Ctrl</kbd>+<kbd>F</kbd>: scroll up and down by a screen
+- [ ] <kbd>0</kbd>, <kbd>$</kbd>: scroll to leftmost and rightmost
+- [ ] <kbd>g</kbd><kbd>g</kbd>, <kbd>G</kbd>: scroll to top and bottom
+
+#### Console
+
+The behaviors of the console are tested in [Console section](#consoles).
+
+- [ ] <kbd>:</kbd>: open empty console
+- [ ] <kbd>o</kbd>, <kbd>t</kbd>, <kbd>w</kbd>: open a console with `open`, `tabopen`, `winopen`
+- [ ] <kbd>O</kbd>, <kbd>T</kbd>, <kbd>W</kbd>: open a console with `open`, `tabopen`, `winopen` and current URL
+- [ ] <kbd>b</kbd>: open a consolw with `buffer`
+
+#### Tabs
+
+- [ ] <kbd>d</kbd>: delete current tab
+- [ ] <kbd>u</kbd>: reopen close tab
+- [ ] <kbd>K</kbd>, <kbd>J</kbd>: select prev and next tab
+- [ ] <kbd>g0</kbd>, <kbd>g$</kbd>: select first and last tab
+- [ ] <kbd>r</kbd>: reload current tab
+- [ ] <kbd>R</kbd>: reload current tab without cache
+- [ ] <kbd>zd</kbd>: duplicate current tab
+- [ ] <kbd>zp</kbd>: toggle pin/unpin state on current tab
+
+#### Navigation
+
+- [ ] <kbd>H</kbd>, <kbd>L</kbd>: go back and forward in histories
+- [ ] <kbd>[</kbd><kbd>[</kbd>, <kbd>]</kbd><kbd>]</kbd>: Open next/prev link in `<link>` tags.
+- [ ] <kbd>[</kbd><kbd>[</kbd>, <kbd>]</kbd><kbd>]</kbd>: find prev and next links and open it
+- [ ] <kbd>g</kbd><kbd>u</kbd>: go to parent directory
+- [ ] <kbd>g</kbd><kbd>U</kbd>: go to root directory
+
+#### Misc
+
+- [ ] <kbd>z</kbd><kbd>i</kbd>, <kbd>z</kbd><kbd>o</kbd>: zoom-in and zoom-out
+- [ ] <kbd>z</kbd><kbd>z</kbd>: set zoom level as default
+- [ ] <kbd>y</kbd>: yank current URL and show a message
+- [ ] Toggle enabled/disabled of plugin bu <kbd>Shift</kbd>+<kbd>Esc</kbd>
+
+
 ### Following links
 
 - [ ] <kbd>f</kbd>: start following links
@@ -83,7 +132,7 @@ The behaviors of the console are tested in [Console section](#consoles).
 - [ ] `buffer`,`buffer<SP>`: do nothing
 - [ ] `buffer <title>`, `buffer <url>`: select tab which has an title matched with
 - [ ] `buffer 1`: select leftmost tab
-- [ ] `buffer 0`, `buffer 99`: shows an error
+- [ ] `buffer 0`, `buffer <a number more than count of tabs>`: shows an error
 - [ ] select tabs rotationally when more than two tabs are matched
 
 ### Completions
@@ -110,20 +159,22 @@ The behaviors of the console are tested in [Console section](#consoles).
 
 ### Settings
 
-#### Validations
+#### JSON Settings
+
+##### Validations
 
 - [ ] show error on invalid json
 - [ ] show error when top-level keys has keys other than `keymaps`, `search`, and `blacklist`
 
-##### `"keymaps"` section
+###### `"keymaps"` section
 
 - [ ] show error on unknown operation name in `"keymaps"`
 
-##### `"search"` section
+###### `"search"` section
 
 - validations in `"search"` section are not tested in this release
 
-#### `"blacklist"` section
+##### `"blacklist"` section
 
 - [ ] `github.com/a` blocks `github.com/a`, and not blocks `github.com/aa`
 - [ ] `github.com/a*` blocks both `github.com/a` and `github.com/aa`
@@ -131,13 +182,44 @@ The behaviors of the console are tested in [Console section](#consoles).
 - [ ] `github.com` blocks both `github.com/` and `github.com/a`
 - [ ] `*.github.com` blocks `gist.github.com/`, and not `github.com`
 
-#### Updating
+##### Updating
 
 - [ ] changes are updated on textarea blure when no errors
 - [ ] changes are not updated on textarea blure when errors occurs
 - [ ] keymap settings are applied to open tabs without reload
 - [ ] search settings are applied to open tabs without reload
 
+#### Form Settings
+
+<!-- validation on form settings does not implement in 0.7 -->
+
+##### Search Engines
+
+- [ ] able to change default
+- [ ] able to remove item
+- [ ] able to add item
+
+##### `"blacklist"` section
+
+- [ ] able to add item
+- [ ] able to remove item
+- [ ] `github.com/a` blocks `github.com/a`, and not blocks `github.com/aa`
+- [ ] `github.com/a*` blocks both `github.com/a` and `github.com/aa`
+- [ ] `github.com/` blocks `github.com/`, and not blocks `github.com/a`
+- [ ] `github.com` blocks both `github.com/` and `github.com/a`
+- [ ] `*.github.com` blocks `gist.github.com/`, and not `github.com`
+
+##### Updating
+
+- [ ] keymap settings are applied to open tabs without reload
+- [ ] search settings are applied to open tabs without reload
+
+### Settings source
+
+- [ ] show confirmation dialog on switched from json to form
+- [ ] state is saved on source changed
+- [ ] on switching form -> json -> form, first and last form setting is equivalent to first one
+
 ### For certain sites
 
 - [ ] scoll on Hacker News
-- 
cgit v1.2.3


From 6b7fad3e494ddca1133fd9e9e6c7ebe7479b7f03 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Sun, 26 Nov 2017 21:54:27 +0900
Subject: fix form key bindings

---
 src/settings/components/form/keymaps-form.jsx | 6 ++++--
 src/shared/settings/default.js                | 2 ++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx
index 8ec0456..ac24689 100644
--- a/src/settings/components/form/keymaps-form.jsx
+++ b/src/settings/components/form/keymaps-form.jsx
@@ -8,8 +8,10 @@ const KeyMapFields = [
     ['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.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 up by half of screen'],
     ['scroll.pages?{"count":-1}', 'Scroll up by a screen'],
diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js
index 71465c9..527725a 100644
--- a/src/shared/settings/default.js
+++ b/src/shared/settings/default.js
@@ -70,6 +70,8 @@ export default {
       'scroll.horizonally?{"count":1}': 'l',
       'scroll.home': '0',
       'scroll.end': '$',
+      'scroll.top': 'gg',
+      'scroll.bottom': 'G',
       'scroll.pages?{"count":-0.5}': '<C-U>',
       'scroll.pages?{"count":0.5}': '<C-D>',
       'scroll.pages?{"count":-1}': '<C-B>',
-- 
cgit v1.2.3


From 6821372fc71779b458eed52f614d5a15571129d0 Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Sun, 26 Nov 2017 21:59:29 +0900
Subject: fix form key bindings

---
 src/settings/components/form/keymaps-form.jsx | 3 ++-
 src/shared/settings/default.js                | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx
index ac24689..f99318f 100644
--- a/src/settings/components/form/keymaps-form.jsx
+++ b/src/settings/components/form/keymaps-form.jsx
@@ -23,7 +23,8 @@ const KeyMapFields = [
     ['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.reload?{"cache":false}', 'Reload current tab'],
+    ['tabs.reload?{"cache":true}', 'Reload with no caches'],
     ['tabs.pin.toggle', 'Toggle pinned state'],
     ['tabs.duplicate', 'Dupplicate a tab'],
   ], [
diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js
index 527725a..14ed548 100644
--- a/src/shared/settings/default.js
+++ b/src/shared/settings/default.js
@@ -83,7 +83,8 @@ export default {
       'tabs.prev?{"count":1}': 'K',
       'tabs.first': 'g0',
       'tabs.last': 'g$',
-      'tabs.reload?{"cache":true}': 'r',
+      'tabs.reload?{"cache":false}': 'r',
+      'tabs.reload?{"cache":true}': 'R',
       'tabs.pin.toggle': 'zp',
       'tabs.duplicate': 'zd',
 
-- 
cgit v1.2.3


From e1060f9bb218202d13a4382584f220d47173194c Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Tue, 28 Nov 2017 20:45:22 +0900
Subject: remove default form settings

---
 src/settings/components/form/keymaps-form.jsx |  4 ++
 src/settings/components/index.jsx             | 15 +++++-
 src/shared/settings/default.js                | 68 ---------------------------
 src/shared/settings/values.js                 | 14 +++---
 test/shared/settings/values.test.js           |  4 +-
 5 files changed, 27 insertions(+), 78 deletions(-)

diff --git a/src/settings/components/form/keymaps-form.jsx b/src/settings/components/form/keymaps-form.jsx
index f99318f..f3b6abe 100644
--- a/src/settings/components/form/keymaps-form.jsx
+++ b/src/settings/components/form/keymaps-form.jsx
@@ -58,6 +58,8 @@ const KeyMapFields = [
   ]
 ];
 
+const AllowdOps = [].concat(...KeyMapFields.map(group => group.map(e => e[0])));
+
 class KeymapsForm extends Component {
 
   render() {
@@ -99,4 +101,6 @@ class KeymapsForm extends Component {
   }
 }
 
+KeymapsForm.AllowdOps = AllowdOps;
+
 export default KeymapsForm;
diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx
index 3961982..38f7db8 100644
--- a/src/settings/components/index.jsx
+++ b/src/settings/components/index.jsx
@@ -123,6 +123,18 @@ class SettingsComponent extends Component {
     }
   }
 
+  validateValue(e) {
+    let next = Object.assign({}, this.state);
+
+    next.errors.json = '';
+    try {
+      this.validate(e.target);
+    } catch (err) {
+      next.errors.json = err.message;
+    }
+    next.settings[e.target.name] = e.target.value;
+  }
+
   bindForm(name, value) {
     let next = Object.assign({}, this.state, {
       settings: Object.assign({}, this.state.settings, {
@@ -164,7 +176,8 @@ class SettingsComponent extends Component {
         return;
       }
       next.settings.form =
-        settingsValues.formFromJson(this.state.settings.json);
+        settingsValues.formFromJson(
+          this.state.settings.json, KeymapsForm.AllowdOps);
     }
     next.settings.source = to;
 
diff --git a/src/shared/settings/default.js b/src/shared/settings/default.js
index 14ed548..d187565 100644
--- a/src/shared/settings/default.js
+++ b/src/shared/settings/default.js
@@ -61,72 +61,4 @@ export default {
     }
   }
 }`,
-
-  '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.top': 'gg',
-      'scroll.bottom': 'G',
-      '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":false}': 'r',
-      'tabs.reload?{"cache":true}': 'R',
-      'tabs.pin.toggle': 'zp',
-      'tabs.duplicate': 'zd',
-
-      'follow.start?{"newTab":false}': 'f',
-      'follow.start?{"newTab":true}': 'F',
-      'navigate.history.prev': 'H',
-      'navigate.history.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={}'],
-      ]
-    },
-    'blacklist': [],
-  }
 };
diff --git a/src/shared/settings/values.js b/src/shared/settings/values.js
index 4482fbb..4e55fa0 100644
--- a/src/shared/settings/values.js
+++ b/src/shared/settings/values.js
@@ -1,5 +1,3 @@
-import DefaultSettings from './default';
-
 const operationFromFormName = (name) => {
   let [type, argStr] = name.split('?');
   let args = {};
@@ -55,16 +53,16 @@ const jsonFromValue = (value) => {
   return JSON.stringify(value, undefined, 2);
 };
 
-const formFromValue = (value) => {
-
+const formFromValue = (value, allowedOps) => {
   let keymaps = undefined;
+
   if (value.keymaps) {
-    let allowedOps = new Set(Object.keys(DefaultSettings.form.keymaps));
+    let allowedSet = new Set(allowedOps);
 
     keymaps = {};
     for (let keys of Object.keys(value.keymaps)) {
       let op = operationToFormName(value.keymaps[keys]);
-      if (allowedOps.has(op)) {
+      if (allowedSet.has(op)) {
         keymaps[op] = keys;
       }
     }
@@ -89,9 +87,9 @@ const jsonFromForm = (form) => {
   return jsonFromValue(valueFromForm(form));
 };
 
-const formFromJson = (json) => {
+const formFromJson = (json, allowedOps) => {
   let value = valueFromJson(json);
-  return formFromValue(value);
+  return formFromValue(value, allowedOps);
 };
 
 export {
diff --git a/test/shared/settings/values.test.js b/test/shared/settings/values.test.js
index 2c222b6..2632cd7 100644
--- a/test/shared/settings/values.test.js
+++ b/test/shared/settings/values.test.js
@@ -98,9 +98,11 @@ describe("settings values", () => {
         search: { default: 'google', engines: { google: 'https://google.com/search?q={}' }},
         blacklist: [ '*.slack.com']
       };
-      let form = values.formFromValue(value);
+      let allowed = ['scroll.vertically?{"count":1}', 'scroll.home' ];
+      let form = values.formFromValue(value, allowed);
 
       expect(form.keymaps).to.have.property('scroll.vertically?{"count":1}', 'j');
+      expect(form.keymaps).to.not.have.property('scroll.vertically?{"count":100}');
       expect(form.keymaps).to.have.property('scroll.home', '0');
       expect(Object.keys(form.keymaps)).to.have.lengthOf(2);
       expect(form.search).to.have.property('default', 'google');
-- 
cgit v1.2.3


From a69f33f569f97791c712234971f943288df8778a Mon Sep 17 00:00:00 2001
From: Shin'ya Ueoka <ueokande@i-beam.org>
Date: Tue, 28 Nov 2017 21:26:01 +0900
Subject: cancel migrate on failure

---
 src/settings/components/index.jsx | 56 ++++++++++++++++++++++++++++-----------
 1 file changed, 41 insertions(+), 15 deletions(-)

diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx
index 38f7db8..73520ca 100644
--- a/src/settings/components/index.jsx
+++ b/src/settings/components/index.jsx
@@ -148,15 +148,54 @@ class SettingsComponent extends Component {
 
   bindValue(e) {
     let next = Object.assign({}, this.state);
+    let error = false;
 
     next.errors.json = '';
     try {
       this.validate(e.target);
     } catch (err) {
       next.errors.json = err.message;
+      error = true;
     }
     next.settings[e.target.name] = e.target.value;
 
+    this.setState(this.state);
+    if (!error) {
+      this.context.store.dispatch(settingActions.save(next.settings));
+    }
+  }
+
+  migrateToForm() {
+    let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
+    if (!b) {
+      this.setState(this.state);
+      return;
+    }
+    try {
+      validator.validate(JSON.parse(this.state.settings.json));
+    } catch (err) {
+      this.setState(this.state);
+      return;
+    }
+
+    let form = settingsValues.formFromJson(
+      this.state.settings.json, KeymapsForm.AllowdOps);
+    let next = Object.assign({}, this.state);
+    next.settings.form = form;
+    next.settings.source = 'form';
+    next.errors.json = '';
+
+    this.setState(next);
+    this.context.store.dispatch(settingActions.save(next.settings));
+  }
+
+  migrateToJson() {
+    let json = settingsValues.jsonFromForm(this.state.settings.form);
+    let next = Object.assign({}, this.state);
+    next.settings.json = json;
+    next.settings.source = 'json';
+    next.errors.json = '';
+
     this.setState(next);
     this.context.store.dispatch(settingActions.save(next.settings));
   }
@@ -165,24 +204,11 @@ class SettingsComponent extends Component {
     let from = this.state.settings.source;
     let to = e.target.value;
 
-    let next = Object.assign({}, this.state);
     if (from === 'form' && to === 'json') {
-      next.settings.json =
-        settingsValues.jsonFromForm(this.state.settings.form);
+      this.migrateToJson();
     } else if (from === 'json' && to === 'form') {
-      let b = window.confirm(DO_YOU_WANT_TO_CONTINUE);
-      if (!b) {
-        this.setState(this.state);
-        return;
-      }
-      next.settings.form =
-        settingsValues.formFromJson(
-          this.state.settings.json, KeymapsForm.AllowdOps);
+      this.migrateToForm();
     }
-    next.settings.source = to;
-
-    this.setState(next);
-    this.context.store.dispatch(settingActions.save(next.settings));
   }
 }
 
-- 
cgit v1.2.3