From 6184c88ac70b33e66a87222142344f693406bd87 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 3 Mar 2019 15:15:41 +0200
Subject: Initial work on deprecating scopeModesEnabled in favor of
 minimalScopeMode

---
 src/components/user_settings/user_settings.vue | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index a1123638..7bd391ba 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -29,13 +29,13 @@
               <input type="checkbox" v-model="newLocked" id="account-locked">
               <label for="account-locked">{{$t('settings.lock_account_description')}}</label>
             </p>
-            <div v-if="scopeOptionsEnabled">
+            <div>
               <label for="default-vis">{{$t('settings.default_vis')}}</label>
               <div id="default-vis" class="visibility-tray">
-                <i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct" :title="$t('post_status.scope.direct')" ></i>
-                <i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private" :title="$t('post_status.scope.private')"></i>
-                <i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted" :title="$t('post_status.scope.unlisted')"></i>
-                <i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public" :title="$t('post_status.scope.public')"></i>
+                <scope-selector
+                  :showAll="true"
+                  :userDefault="newDefaultScope"
+                  :onScopeChange="changeVis"/>
               </div>
             </div>
             <p>
-- 
cgit v1.2.3-70-g09d2


From da5844205c5e2ead4da42f89f1f9586683af693e Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 06:12:31 -0400
Subject: add user autosuggest component

---
 .../user_autosuggest/user_autosuggest.js           | 41 ++++++++++
 .../user_autosuggest/user_autosuggest.vue          | 93 ++++++++++++++++++++++
 src/components/user_settings/user_settings.js      |  4 +-
 src/components/user_settings/user_settings.vue     |  3 +-
 4 files changed, 139 insertions(+), 2 deletions(-)
 create mode 100644 src/components/user_autosuggest/user_autosuggest.js
 create mode 100644 src/components/user_autosuggest/user_autosuggest.vue

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_autosuggest/user_autosuggest.js b/src/components/user_autosuggest/user_autosuggest.js
new file mode 100644
index 00000000..6612c2f3
--- /dev/null
+++ b/src/components/user_autosuggest/user_autosuggest.js
@@ -0,0 +1,41 @@
+import { VueAutosuggest } from 'vue-autosuggest'
+import BasicUserCard from '../basic_user_card/basic_user_card.vue'
+import userSearchApi from '../../services/new_api/user_search.js'
+
+export default {
+  components: {
+    VueAutosuggest,
+    BasicUserCard
+  },
+  data () {
+    return {
+      results: [],
+      timeout: null,
+      selected: null,
+      debounceMilliseconds: 500,
+      inputProps: {
+        id: 'autosuggest__input',
+        onInputChange: this.fetchResults,
+        placeholder: 'Search...',
+        class: 'form-control'
+      },
+      suggestions: []
+    }
+  },
+  methods: {
+    fetchResults (query) {
+      clearTimeout(this.timeout)
+      this.timeout = setTimeout(() => {
+        userSearchApi.search({query, store: this.$store})
+          .then((data) => { this.suggestions = [{ data }] })
+      }, this.debounceMilliseconds)
+    },
+    clickHandler (item) {
+      return false
+    },
+    clickUserHandler () {
+      console.log('clickUserHandler')
+      return false
+    }
+  }
+}
diff --git a/src/components/user_autosuggest/user_autosuggest.vue b/src/components/user_autosuggest/user_autosuggest.vue
new file mode 100644
index 00000000..48fe350d
--- /dev/null
+++ b/src/components/user_autosuggest/user_autosuggest.vue
@@ -0,0 +1,93 @@
+<template>
+  <div class="user-autosuggest">
+    <VueAutosuggest
+      :suggestions="suggestions"
+      :inputProps="inputProps"
+      @click="clickHandler"
+		>
+      <template slot-scope="{suggestion}">
+        <BasicUserCard :user="suggestion.item" />
+      </template>
+    </VueAutosuggest>
+  </div>
+</template>
+
+<script src="./user_autosuggest.js"></script>
+
+<style lang="scss">
+.user-autosuggest {
+  .avatar {
+    height: 25px;
+    width: 25px;
+    border-radius: 20px;
+    margin-right: 10px;
+  }
+  #autosuggest__input {
+    outline: none;
+    position: relative;
+    display: block;
+    border: 1px solid #616161;
+    padding: 10px;
+    width: 100%;
+    box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+  }
+
+  #autosuggest__input.autosuggest__input-open {
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+
+  .autosuggest__results-container {
+    position: relative;
+    width: 100%;
+  }
+
+  .autosuggest__results {
+    font-weight: 300;
+    margin: 0;
+    position: absolute;
+    z-index: 10000001;
+    width: 100%;
+    border: 1px solid #e0e0e0;
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    background: white;
+    padding: 0px;
+    max-height: 400px;
+    overflow-y: scroll;
+  }
+
+  .autosuggest__results ul {
+    list-style: none;
+    padding-left: 0;
+    margin: 0;
+  }
+
+  .autosuggest__results .autosuggest__results_item {
+    cursor: pointer;
+    padding: 15px;
+  }
+
+  #autosuggest ul:nth-child(1) > .autosuggest__results_title {
+    border-top: none;
+  }
+
+  .autosuggest__results .autosuggest__results_title {
+    color: gray;
+    font-size: 11px;
+    margin-left: 0;
+    padding: 15px 13px 5px;
+    border-top: 1px solid lightgray;
+  }
+
+  .autosuggest__results .autosuggest__results_item:active,
+  .autosuggest__results .autosuggest__results_item:hover,
+  .autosuggest__results .autosuggest__results_item:focus,
+  .autosuggest__results
+    .autosuggest__results_item.autosuggest__results_item-highlighted {
+    background-color: #f6f6f6;
+  }
+}
+</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index b6a0479d..d5772082 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -9,6 +9,7 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
 import BlockCard from '../block_card/block_card.vue'
 import MuteCard from '../mute_card/mute_card.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
+import UserAutoSuggest from '../user_autosuggest/user_autosuggest.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
 import withList from '../../hocs/with_list/with_list'
 
@@ -73,7 +74,8 @@ const UserSettings = {
     ImageCropper,
     BlockList,
     MuteList,
-    EmojiInput
+    EmojiInput,
+    UserAutoSuggest
   },
   computed: {
     user () {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index c08698dc..d98a8eba 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -22,7 +22,7 @@
           <div class="setting-item" >
             <h2>{{$t('settings.name_bio')}}</h2>
             <p>{{$t('settings.name')}}</p>
-            <EmojiInput 
+            <EmojiInput
               type="text"
               v-model="newName"
               id="username"
@@ -195,6 +195,7 @@
         </div>
 
         <div :label="$t('settings.blocks_tab')">
+          <UserAutoSuggest />
           <block-list :refresh="true">
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
           </block-list>
-- 
cgit v1.2.3-70-g09d2


From fd4289e45a1d9f952b44251110abe01081b92f8a Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 13:18:41 -0400
Subject: improve ui

---
 src/components/user_autosuggest/user_autosuggest.vue | 20 ++++++++++++++++++--
 src/components/user_settings/user_settings.vue       |  8 +++++++-
 2 files changed, 25 insertions(+), 3 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_autosuggest/user_autosuggest.vue b/src/components/user_autosuggest/user_autosuggest.vue
index eb5aa41b..2840941f 100644
--- a/src/components/user_autosuggest/user_autosuggest.vue
+++ b/src/components/user_autosuggest/user_autosuggest.vue
@@ -1,6 +1,6 @@
 <template>
-    <input v-model="query" placeholder="Search..." />
   <div class="user-autosuggest" v-click-outside="onClickOutside">
+    <input v-model="query" placeholder="Search whom you want to block" @click="onInputClick" class="user-autosuggest-input" />
     <div class="user-autosuggest-results" v-if="resultsVisible">
       <BasicUserCard v-for="user in results" :key="user.id" :user="user"/>
     </div>
@@ -10,15 +10,31 @@
 <script src="./user_autosuggest.js"></script>
 
 <style lang="scss">
+@import '../../_variables.scss';
+
 .user-autosuggest {
   position: relative;
 
+  &-input {
+    display: block;
+    width: 100%;
+  }
+
   &-results {
     position: absolute;
     left: 0;
     top: 100%;
     right: 0;
-    background: #FFF;
+    background-color: $fallback--lightBg;
+    background-color: var(--lightBg, $fallback--lightBg);
+    border-style: solid;
+    border-width: 1px;
+    border-color: $fallback--border;
+    border-color: var(--border, $fallback--border);
+    border-radius: $fallback--inputRadius;
+    border-radius: var(--inputRadius, $fallback--inputRadius);
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
     max-height: 400px;
     overflow-y: auto;
     z-index: 1;
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index d98a8eba..811f63aa 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -195,7 +195,9 @@
         </div>
 
         <div :label="$t('settings.blocks_tab')">
-          <UserAutoSuggest />
+          <div class="profile-edit-usersearch-wrapper">
+            <UserAutoSuggest />
+          </div>
           <block-list :refresh="true">
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
           </block-list>
@@ -263,5 +265,9 @@
       text-align: right;
     }
   }
+
+  &-usersearch-wrapper {
+    padding: 1em;
+  }
 }
 </style>
-- 
cgit v1.2.3-70-g09d2


From 8410add39457ef075a14096fbcbceab24757588f Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 15:41:26 -0400
Subject: make UserAutoSuggest component more generic

---
 .../user_autosuggest/user_autosuggest.js           | 33 +++++++++++-----------
 .../user_autosuggest/user_autosuggest.vue          |  2 +-
 src/components/user_settings/user_settings.js      | 16 +++++++++++
 src/components/user_settings/user_settings.vue     |  2 +-
 4 files changed, 34 insertions(+), 19 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_autosuggest/user_autosuggest.js b/src/components/user_autosuggest/user_autosuggest.js
index 75798b7e..6b95fc15 100644
--- a/src/components/user_autosuggest/user_autosuggest.js
+++ b/src/components/user_autosuggest/user_autosuggest.js
@@ -1,46 +1,45 @@
-import reject from 'lodash/reject'
-import map from 'lodash/map'
 import BlockCard from '../block_card/block_card.vue'
-import userSearchApi from '../../services/new_api/user_search.js'
 
 const debounceMilliseconds = 500
 
 export default {
+  props: {
+    query: {    // function to query results and return a promise
+      type: Function,
+      required: true
+    },
+    filter: {   // function to filter results in real time
+      type: Function
+    }
+  },
   components: {
     BlockCard
   },
   data () {
     return {
-      query: '',
-      results: [],
+      term: '',
       timeout: null,
+      results: [],
       resultsVisible: false
     }
   },
   computed: {
     filtered () {
-      return reject(this.results, (userId) => {
-        const user = this.$store.getters.findUser(userId)
-        return !user || user.statusnet_blocking || user.id === this.$store.state.users.currentUser.id
-      })
+      return this.filter ? this.filter(this.results) : this.results
     }
   },
   watch: {
-    query (val) {
+    term (val) {
       this.fetchResults(val)
     }
   },
   methods: {
-    fetchResults (query) {
+    fetchResults (term) {
       clearTimeout(this.timeout)
       this.timeout = setTimeout(() => {
         this.results = []
-        if (query) {
-          userSearchApi.search({query, store: this.$store})
-            .then((users) => {
-              this.$store.dispatch('addNewUsers', users)
-              this.results = map(users, 'id')
-            })
+        if (term) {
+          this.query(term).then((results) => { this.results = results })
         }
       }, debounceMilliseconds)
     },
diff --git a/src/components/user_autosuggest/user_autosuggest.vue b/src/components/user_autosuggest/user_autosuggest.vue
index 930216a7..48cffc78 100644
--- a/src/components/user_autosuggest/user_autosuggest.vue
+++ b/src/components/user_autosuggest/user_autosuggest.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="user-autosuggest" v-click-outside="onClickOutside">
-    <input v-model="query" placeholder="Search whom you want to block" @click="onInputClick" class="user-autosuggest-input" />
+    <input v-model="term" placeholder="Search whom you want to block" @click="onInputClick" class="user-autosuggest-input" />
     <div class="user-autosuggest-results" v-if="resultsVisible && filtered.length > 0">
       <BlockCard v-for="userId in filtered" :key="userId" :userId="userId"/>
     </div>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index d5772082..259e3418 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -1,6 +1,8 @@
 import { compose } from 'vue-compose'
 import unescape from 'lodash/unescape'
 import get from 'lodash/get'
+import map from 'lodash/map'
+import reject from 'lodash/reject'
 import TabSwitcher from '../tab_switcher/tab_switcher.js'
 import ImageCropper from '../image_cropper/image_cropper.vue'
 import StyleSwitcher from '../style_switcher/style_switcher.vue'
@@ -12,6 +14,7 @@ import EmojiInput from '../emoji-input/emoji-input.vue'
 import UserAutoSuggest from '../user_autosuggest/user_autosuggest.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
 import withList from '../../hocs/with_list/with_list'
+import userSearchApi from '../../services/new_api/user_search.js'
 
 const BlockList = compose(
   withSubscription({
@@ -336,6 +339,19 @@ const UserSettings = {
       if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
         this.$store.dispatch('revokeToken', id)
       }
+    },
+    filterUnblockedUsers (userIds) {
+      return reject(userIds, (userId) => {
+        const user = this.$store.getters.findUser(userId)
+        return !user || user.statusnet_blocking || user.id === this.$store.state.users.currentUser.id
+      })
+    },
+    queryUserIds (query) {
+      return userSearchApi.search({query, store: this.$store})
+        .then((users) => {
+          this.$store.dispatch('addNewUsers', users)
+          return map(users, 'id')
+        })
     }
   }
 }
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 811f63aa..6018cc30 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -196,7 +196,7 @@
 
         <div :label="$t('settings.blocks_tab')">
           <div class="profile-edit-usersearch-wrapper">
-            <UserAutoSuggest />
+            <UserAutoSuggest :filter="filterUnblockedUsers" :query="queryUserIds" />
           </div>
           <block-list :refresh="true">
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
-- 
cgit v1.2.3-70-g09d2


From fc5f4fba8c4af713855b881d7b178623b1e77b01 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 15:43:41 -0400
Subject: make more generic

---
 src/components/user_autosuggest/user_autosuggest.js  | 4 ++++
 src/components/user_autosuggest/user_autosuggest.vue | 2 +-
 src/components/user_settings/user_settings.vue       | 2 +-
 3 files changed, 6 insertions(+), 2 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_autosuggest/user_autosuggest.js b/src/components/user_autosuggest/user_autosuggest.js
index 6b95fc15..12a6964a 100644
--- a/src/components/user_autosuggest/user_autosuggest.js
+++ b/src/components/user_autosuggest/user_autosuggest.js
@@ -10,6 +10,10 @@ export default {
     },
     filter: {   // function to filter results in real time
       type: Function
+    },
+    placeholder: {
+      type: String,
+      default: 'Search...'
     }
   },
   components: {
diff --git a/src/components/user_autosuggest/user_autosuggest.vue b/src/components/user_autosuggest/user_autosuggest.vue
index 48cffc78..db3e1bdf 100644
--- a/src/components/user_autosuggest/user_autosuggest.vue
+++ b/src/components/user_autosuggest/user_autosuggest.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="user-autosuggest" v-click-outside="onClickOutside">
-    <input v-model="term" placeholder="Search whom you want to block" @click="onInputClick" class="user-autosuggest-input" />
+    <input v-model="term" :placeholder="placeholder" @click="onInputClick" class="user-autosuggest-input" />
     <div class="user-autosuggest-results" v-if="resultsVisible && filtered.length > 0">
       <BlockCard v-for="userId in filtered" :key="userId" :userId="userId"/>
     </div>
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 6018cc30..3dde5ae1 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -196,7 +196,7 @@
 
         <div :label="$t('settings.blocks_tab')">
           <div class="profile-edit-usersearch-wrapper">
-            <UserAutoSuggest :filter="filterUnblockedUsers" :query="queryUserIds" />
+            <UserAutoSuggest :filter="filterUnblockedUsers" :query="queryUserIds" placeholder="Search whom you want to block" />
           </div>
           <block-list :refresh="true">
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
-- 
cgit v1.2.3-70-g09d2


From 1cf9780e05957c96b6d6b6b898b83ff1987b2703 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 16:02:02 -0400
Subject: make more generic

---
 src/components/user_autosuggest/user_autosuggest.js  | 5 -----
 src/components/user_autosuggest/user_autosuggest.vue | 3 ++-
 src/components/user_settings/user_settings.js        | 3 ++-
 src/components/user_settings/user_settings.vue       | 4 +++-
 4 files changed, 7 insertions(+), 8 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_autosuggest/user_autosuggest.js b/src/components/user_autosuggest/user_autosuggest.js
index 12a6964a..d4efe912 100644
--- a/src/components/user_autosuggest/user_autosuggest.js
+++ b/src/components/user_autosuggest/user_autosuggest.js
@@ -1,5 +1,3 @@
-import BlockCard from '../block_card/block_card.vue'
-
 const debounceMilliseconds = 500
 
 export default {
@@ -16,9 +14,6 @@ export default {
       default: 'Search...'
     }
   },
-  components: {
-    BlockCard
-  },
   data () {
     return {
       term: '',
diff --git a/src/components/user_autosuggest/user_autosuggest.vue b/src/components/user_autosuggest/user_autosuggest.vue
index db3e1bdf..6eaecbaa 100644
--- a/src/components/user_autosuggest/user_autosuggest.vue
+++ b/src/components/user_autosuggest/user_autosuggest.vue
@@ -2,7 +2,8 @@
   <div class="user-autosuggest" v-click-outside="onClickOutside">
     <input v-model="term" :placeholder="placeholder" @click="onInputClick" class="user-autosuggest-input" />
     <div class="user-autosuggest-results" v-if="resultsVisible && filtered.length > 0">
-      <BlockCard v-for="userId in filtered" :key="userId" :userId="userId"/>
+      <slot v-for="item in filtered" :item="item">
+      </slot>
     </div>
   </div>
 </template>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 259e3418..2cae2d00 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -78,7 +78,8 @@ const UserSettings = {
     BlockList,
     MuteList,
     EmojiInput,
-    UserAutoSuggest
+    UserAutoSuggest,
+    BlockCard
   },
   computed: {
     user () {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 3dde5ae1..69d3dc53 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -196,7 +196,9 @@
 
         <div :label="$t('settings.blocks_tab')">
           <div class="profile-edit-usersearch-wrapper">
-            <UserAutoSuggest :filter="filterUnblockedUsers" :query="queryUserIds" placeholder="Search whom you want to block" />
+            <UserAutoSuggest :filter="filterUnblockedUsers" :query="queryUserIds" placeholder="Search whom you want to block">
+              <BlockCard slot-scope="row" :userId="row.item"/>
+            </UserAutoSuggest>
           </div>
           <block-list :refresh="true">
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
-- 
cgit v1.2.3-70-g09d2


From 0bdb0e5a8124508edff12a265174bd51a09c37e6 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 16:10:03 -0400
Subject: rename Autosuggest component

---
 src/components/autosuggest/autosuggest.js          | 52 ++++++++++++++++++++++
 src/components/autosuggest/autosuggest.vue         | 46 +++++++++++++++++++
 .../user_autosuggest/user_autosuggest.js           | 52 ----------------------
 .../user_autosuggest/user_autosuggest.vue          | 46 -------------------
 src/components/user_settings/user_settings.js      |  4 +-
 src/components/user_settings/user_settings.vue     |  4 +-
 6 files changed, 102 insertions(+), 102 deletions(-)
 create mode 100644 src/components/autosuggest/autosuggest.js
 create mode 100644 src/components/autosuggest/autosuggest.vue
 delete mode 100644 src/components/user_autosuggest/user_autosuggest.js
 delete mode 100644 src/components/user_autosuggest/user_autosuggest.vue

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/autosuggest/autosuggest.js b/src/components/autosuggest/autosuggest.js
new file mode 100644
index 00000000..d4efe912
--- /dev/null
+++ b/src/components/autosuggest/autosuggest.js
@@ -0,0 +1,52 @@
+const debounceMilliseconds = 500
+
+export default {
+  props: {
+    query: {    // function to query results and return a promise
+      type: Function,
+      required: true
+    },
+    filter: {   // function to filter results in real time
+      type: Function
+    },
+    placeholder: {
+      type: String,
+      default: 'Search...'
+    }
+  },
+  data () {
+    return {
+      term: '',
+      timeout: null,
+      results: [],
+      resultsVisible: false
+    }
+  },
+  computed: {
+    filtered () {
+      return this.filter ? this.filter(this.results) : this.results
+    }
+  },
+  watch: {
+    term (val) {
+      this.fetchResults(val)
+    }
+  },
+  methods: {
+    fetchResults (term) {
+      clearTimeout(this.timeout)
+      this.timeout = setTimeout(() => {
+        this.results = []
+        if (term) {
+          this.query(term).then((results) => { this.results = results })
+        }
+      }, debounceMilliseconds)
+    },
+    onInputClick () {
+      this.resultsVisible = true
+    },
+    onClickOutside () {
+      this.resultsVisible = false
+    }
+  }
+}
diff --git a/src/components/autosuggest/autosuggest.vue b/src/components/autosuggest/autosuggest.vue
new file mode 100644
index 00000000..ae1e312d
--- /dev/null
+++ b/src/components/autosuggest/autosuggest.vue
@@ -0,0 +1,46 @@
+<template>
+  <div class="autosuggest" v-click-outside="onClickOutside">
+    <input v-model="term" :placeholder="placeholder" @click="onInputClick" class="autosuggest-input" />
+    <div class="autosuggest-results" v-if="resultsVisible && filtered.length > 0">
+      <slot v-for="item in filtered" :item="item">
+      </slot>
+    </div>
+  </div>
+</template>
+
+<script src="./autosuggest.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.autosuggest {
+  position: relative;
+
+  &-input {
+    display: block;
+    width: 100%;
+  }
+
+  &-results {
+    position: absolute;
+    left: 0;
+    top: 100%;
+    right: 0;
+    max-height: 400px;
+    background-color: $fallback--lightBg;
+    background-color: var(--lightBg, $fallback--lightBg);
+    border-style: solid;
+    border-width: 1px;
+    border-color: $fallback--border;
+    border-color: var(--border, $fallback--border);
+    border-radius: $fallback--inputRadius;
+    border-radius: var(--inputRadius, $fallback--inputRadius);
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
+    box-shadow: var(--panelShadow);
+    overflow-y: auto;
+    z-index: 1;
+  }
+}
+</style>
diff --git a/src/components/user_autosuggest/user_autosuggest.js b/src/components/user_autosuggest/user_autosuggest.js
deleted file mode 100644
index d4efe912..00000000
--- a/src/components/user_autosuggest/user_autosuggest.js
+++ /dev/null
@@ -1,52 +0,0 @@
-const debounceMilliseconds = 500
-
-export default {
-  props: {
-    query: {    // function to query results and return a promise
-      type: Function,
-      required: true
-    },
-    filter: {   // function to filter results in real time
-      type: Function
-    },
-    placeholder: {
-      type: String,
-      default: 'Search...'
-    }
-  },
-  data () {
-    return {
-      term: '',
-      timeout: null,
-      results: [],
-      resultsVisible: false
-    }
-  },
-  computed: {
-    filtered () {
-      return this.filter ? this.filter(this.results) : this.results
-    }
-  },
-  watch: {
-    term (val) {
-      this.fetchResults(val)
-    }
-  },
-  methods: {
-    fetchResults (term) {
-      clearTimeout(this.timeout)
-      this.timeout = setTimeout(() => {
-        this.results = []
-        if (term) {
-          this.query(term).then((results) => { this.results = results })
-        }
-      }, debounceMilliseconds)
-    },
-    onInputClick () {
-      this.resultsVisible = true
-    },
-    onClickOutside () {
-      this.resultsVisible = false
-    }
-  }
-}
diff --git a/src/components/user_autosuggest/user_autosuggest.vue b/src/components/user_autosuggest/user_autosuggest.vue
deleted file mode 100644
index 6eaecbaa..00000000
--- a/src/components/user_autosuggest/user_autosuggest.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<template>
-  <div class="user-autosuggest" v-click-outside="onClickOutside">
-    <input v-model="term" :placeholder="placeholder" @click="onInputClick" class="user-autosuggest-input" />
-    <div class="user-autosuggest-results" v-if="resultsVisible && filtered.length > 0">
-      <slot v-for="item in filtered" :item="item">
-      </slot>
-    </div>
-  </div>
-</template>
-
-<script src="./user_autosuggest.js"></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.user-autosuggest {
-  position: relative;
-
-  &-input {
-    display: block;
-    width: 100%;
-  }
-
-  &-results {
-    position: absolute;
-    left: 0;
-    top: 100%;
-    right: 0;
-    max-height: 400px;
-    background-color: $fallback--lightBg;
-    background-color: var(--lightBg, $fallback--lightBg);
-    border-style: solid;
-    border-width: 1px;
-    border-color: $fallback--border;
-    border-color: var(--border, $fallback--border);
-    border-radius: $fallback--inputRadius;
-    border-radius: var(--inputRadius, $fallback--inputRadius);
-    border-top-left-radius: 0;
-    border-top-right-radius: 0;
-    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
-    box-shadow: var(--panelShadow);
-    overflow-y: auto;
-    z-index: 1;
-  }
-}
-</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 2cae2d00..a0dfbc67 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -11,7 +11,7 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
 import BlockCard from '../block_card/block_card.vue'
 import MuteCard from '../mute_card/mute_card.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
-import UserAutoSuggest from '../user_autosuggest/user_autosuggest.vue'
+import Autosuggest from '../autosuggest/autosuggest.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
 import withList from '../../hocs/with_list/with_list'
 import userSearchApi from '../../services/new_api/user_search.js'
@@ -78,7 +78,7 @@ const UserSettings = {
     BlockList,
     MuteList,
     EmojiInput,
-    UserAutoSuggest,
+    Autosuggest,
     BlockCard
   },
   computed: {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 69d3dc53..d79abd0f 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -196,9 +196,9 @@
 
         <div :label="$t('settings.blocks_tab')">
           <div class="profile-edit-usersearch-wrapper">
-            <UserAutoSuggest :filter="filterUnblockedUsers" :query="queryUserIds" placeholder="Search whom you want to block">
+            <Autosuggest :filter="filterUnblockedUsers" :query="queryUserIds" placeholder="Search whom you want to block">
               <BlockCard slot-scope="row" :userId="row.item"/>
-            </UserAutoSuggest>
+            </Autosuggest>
           </div>
           <block-list :refresh="true">
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
-- 
cgit v1.2.3-70-g09d2


From d58e08cf52c5953914f0bf1b6c9fb120168c6160 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 16:14:45 -0400
Subject: add search to the mutes tab as well

---
 src/components/user_settings/user_settings.js  | 9 ++++++++-
 src/components/user_settings/user_settings.vue | 5 +++++
 2 files changed, 13 insertions(+), 1 deletion(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index a0dfbc67..dd931e67 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -79,7 +79,8 @@ const UserSettings = {
     MuteList,
     EmojiInput,
     Autosuggest,
-    BlockCard
+    BlockCard,
+    MuteCard
   },
   computed: {
     user () {
@@ -347,6 +348,12 @@ const UserSettings = {
         return !user || user.statusnet_blocking || user.id === this.$store.state.users.currentUser.id
       })
     },
+    filterUnMutedUsers (userIds) {
+      return reject(userIds, (userId) => {
+        const user = this.$store.getters.findUser(userId)
+        return !user || user.muted || user.id === this.$store.state.users.currentUser.id
+      })
+    },
     queryUserIds (query) {
       return userSearchApi.search({query, store: this.$store})
         .then((users) => {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index d79abd0f..8fcf4c07 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -206,6 +206,11 @@
         </div>
 
         <div :label="$t('settings.mutes_tab')">
+          <div class="profile-edit-usersearch-wrapper">
+            <Autosuggest :filter="filterUnMutedUsers" :query="queryUserIds" placeholder="Search whom you want to mute">
+              <MuteCard slot-scope="row" :userId="row.item"/>
+            </Autosuggest>
+          </div>
           <mute-list :refresh="true">
             <template slot="empty">{{$t('settings.no_mutes')}}</template>
           </mute-list>
-- 
cgit v1.2.3-70-g09d2


From d874ca905cbf90a4adb070bbe9b307e5bf9ad82f Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Tue, 2 Apr 2019 16:28:51 -0400
Subject: support i18n

---
 src/components/user_settings/user_settings.vue | 4 ++--
 src/i18n/en.json                               | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 8fcf4c07..9d37ffc4 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -196,7 +196,7 @@
 
         <div :label="$t('settings.blocks_tab')">
           <div class="profile-edit-usersearch-wrapper">
-            <Autosuggest :filter="filterUnblockedUsers" :query="queryUserIds" placeholder="Search whom you want to block">
+            <Autosuggest :filter="filterUnblockedUsers" :query="queryUserIds" :placeholder="$t('settings.search_user_to_block')">
               <BlockCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
@@ -207,7 +207,7 @@
 
         <div :label="$t('settings.mutes_tab')">
           <div class="profile-edit-usersearch-wrapper">
-            <Autosuggest :filter="filterUnMutedUsers" :query="queryUserIds" placeholder="Search whom you want to mute">
+            <Autosuggest :filter="filterUnMutedUsers" :query="queryUserIds" :placeholder="$t('settings.search_user_to_mute')">
               <MuteCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 9188c6f7..c71c9036 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -217,6 +217,8 @@
     "reply_visibility_self": "Only show replies directed at me",
     "saving_err": "Error saving settings",
     "saving_ok": "Settings saved",
+    "search_user_to_block": "Search whom you want to block",
+    "search_user_to_mute": "Search whom you want to mute",
     "security_tab": "Security",
     "scope_copy": "Copy scope when replying (DMs are always copied)",
     "minimal_scopes_mode": "Minimize post scope selection options",
-- 
cgit v1.2.3-70-g09d2


From 1cec2b696952ef2c472219329961bc102ed315df Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Wed, 3 Apr 2019 22:17:42 -0400
Subject: use reusable List vue component instead of withList hoc

Using slot is the preferred way in vue
---
 src/components/list/list.vue                   | 28 ++++++++++++++++++++++++++
 src/components/user_settings/user_settings.js  | 27 ++++++++++---------------
 src/components/user_settings/user_settings.vue | 14 +++++++++----
 3 files changed, 49 insertions(+), 20 deletions(-)
 create mode 100644 src/components/list/list.vue

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/list/list.vue b/src/components/list/list.vue
new file mode 100644
index 00000000..f361e3b1
--- /dev/null
+++ b/src/components/list/list.vue
@@ -0,0 +1,28 @@
+<template>
+  <div class="list">
+    <slot name="item" v-for="item in items" :item="item" />
+    <div class="list-empty-content faint" v-if="items.length === 0">
+      <slot name="empty" />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    items: {
+      type: Array,
+      default: () => []
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.list {
+  &-empty-content {
+    text-align: center;
+    padding: 10px;
+  }
+}
+</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index dd931e67..057d1cec 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -10,29 +10,24 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
 import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
 import BlockCard from '../block_card/block_card.vue'
 import MuteCard from '../mute_card/mute_card.vue'
+import List from '../list/list.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
 import Autosuggest from '../autosuggest/autosuggest.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
 import withList from '../../hocs/with_list/with_list'
 import userSearchApi from '../../services/new_api/user_search.js'
 
-const BlockList = compose(
-  withSubscription({
-    fetch: (props, $store) => $store.dispatch('fetchBlocks'),
-    select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
-    childPropName: 'entries'
-  }),
-  withList({ getEntryProps: userId => ({ userId }) })
-)(BlockCard)
+const BlockList = withSubscription({
+  fetch: (props, $store) => $store.dispatch('fetchBlocks'),
+  select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
+  childPropName: 'items'
+})(List)
 
-const MuteList = compose(
-  withSubscription({
-    fetch: (props, $store) => $store.dispatch('fetchMutes'),
-    select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
-    childPropName: 'entries'
-  }),
-  withList({ getEntryProps: userId => ({ userId }) })
-)(MuteCard)
+const MuteList = withSubscription({
+  fetch: (props, $store) => $store.dispatch('fetchMutes'),
+  select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
+  childPropName: 'items'
+})(List)
 
 const UserSettings = {
   data () {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 9d37ffc4..741fbe3f 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -200,9 +200,12 @@
               <BlockCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <block-list :refresh="true">
+          <BlockList :refresh="true">
+            <template slot="item" scope="p">
+              <BlockCard :key="p.item" :userId="p.item" />
+            </template>
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
-          </block-list>
+          </BlockList>
         </div>
 
         <div :label="$t('settings.mutes_tab')">
@@ -211,9 +214,12 @@
               <MuteCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <mute-list :refresh="true">
+          <MuteList :refresh="true">
+            <template slot="item" scope="p">
+              <MuteCard :key="p.item" :userId="p.item" />
+            </template>
             <template slot="empty">{{$t('settings.no_mutes')}}</template>
-          </mute-list>
+          </MuteList>
         </div>
       </tab-switcher>
     </div>
-- 
cgit v1.2.3-70-g09d2


From 32035217b8e04b0f178409f0f1f49d54296ce80c Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 4 Apr 2019 00:00:21 -0400
Subject: clean up

---
 src/components/list/list.vue                      |  2 +-
 src/components/selectable_list/selectable_list.js | 11 +----------
 src/components/user_profile/user_profile.vue      |  4 ++--
 src/components/user_settings/user_settings.vue    |  8 ++++----
 4 files changed, 8 insertions(+), 17 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/list/list.vue b/src/components/list/list.vue
index 1159898d..7d62283a 100644
--- a/src/components/list/list.vue
+++ b/src/components/list/list.vue
@@ -18,7 +18,7 @@ export default {
     },
     getKey: {
       type: Function,
-      default: item => item
+      default: item => item.id
     }
   }
 }
diff --git a/src/components/selectable_list/selectable_list.js b/src/components/selectable_list/selectable_list.js
index 1fdd5a67..7856d725 100644
--- a/src/components/selectable_list/selectable_list.js
+++ b/src/components/selectable_list/selectable_list.js
@@ -6,16 +6,7 @@ const SelectableList = {
     List,
     Checkbox
   },
-  props: {
-    items: {
-      type: Array,
-      default: () => []
-    },
-    getKey: {
-      type: Function,
-      default: item => item
-    }
-  },
+  props: List.props,
   data () {
     return {
       selected: []
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 39f3c381..8d78d2d2 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -16,14 +16,14 @@
       <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
         <FriendList :userId="userId">
           <template slot="item" scope="p">
-            <FollowCard :key="p.item.id" :user="p.item" />
+            <FollowCard :user="p.item" />
           </template>
         </FriendList>
       </div>
       <div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
         <FollowerList :userId="userId">
           <template slot="item" scope="p">
-            <FollowCard :key="p.item.id" :user="p.item" :noFollowsYou="isUs" />
+            <FollowCard :user="p.item" :noFollowsYou="isUs" />
           </template>
         </FollowerList>
       </div>
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 741fbe3f..66ce6cea 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -200,9 +200,9 @@
               <BlockCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <BlockList :refresh="true">
+          <BlockList :refresh="true" :getKey="item => item">
             <template slot="item" scope="p">
-              <BlockCard :key="p.item" :userId="p.item" />
+              <BlockCard :userId="p.item" />
             </template>
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
           </BlockList>
@@ -214,9 +214,9 @@
               <MuteCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <MuteList :refresh="true">
+          <MuteList :refresh="true" :getKey="item => item">
             <template slot="item" scope="p">
-              <MuteCard :key="p.item" :userId="p.item" />
+              <MuteCard :userId="p.item" />
             </template>
             <template slot="empty">{{$t('settings.no_mutes')}}</template>
           </MuteList>
-- 
cgit v1.2.3-70-g09d2


From fe7766bc618f9a994e2c0a9337deb7cce8b162cf Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 4 Apr 2019 12:12:27 -0400
Subject: replace scope attributes by slot-scope

---
 src/components/follow_requests/follow_requests.vue | 2 +-
 src/components/selectable_list/selectable_list.vue | 2 +-
 src/components/user_profile/user_profile.vue       | 4 ++--
 src/components/user_search/user_search.vue         | 2 +-
 src/components/user_settings/user_settings.vue     | 4 ++--
 src/components/who_to_follow/who_to_follow.vue     | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/follow_requests/follow_requests.vue b/src/components/follow_requests/follow_requests.vue
index 0970713b..8b1e8f94 100644
--- a/src/components/follow_requests/follow_requests.vue
+++ b/src/components/follow_requests/follow_requests.vue
@@ -5,7 +5,7 @@
     </div>
     <div class="panel-body">
       <List :items="requests">
-        <template slot="item" scope="p">
+        <template slot="item" slot-scope="p">
           <FollowRequestCard :user="p.item" />
         </template>
       </List>
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index f3dd5e88..25781d06 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -9,7 +9,7 @@
       </div>
     </div>
     <List :items="items" :getKey="getKey">
-      <template slot="item" scope="p">
+      <template slot="item" slot-scope="p">
         <div class="selectable-list-item-inner" :class="{ 'selectable-list-item-selected-inner': isSelected(p.item) }">
           <div class="selectable-list-checkbox-wrapper">
             <Checkbox :checked="isSelected(p.item)" @change="checked => toggle(checked, p.item)" />
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 8d78d2d2..e67231b6 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -15,14 +15,14 @@
       />
       <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
         <FriendList :userId="userId">
-          <template slot="item" scope="p">
+          <template slot="item" slot-scope="p">
             <FollowCard :user="p.item" />
           </template>
         </FriendList>
       </div>
       <div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
         <FollowerList :userId="userId">
-          <template slot="item" scope="p">
+          <template slot="item" slot-scope="p">
             <FollowCard :user="p.item" :noFollowsYou="isUs" />
           </template>
         </FollowerList>
diff --git a/src/components/user_search/user_search.vue b/src/components/user_search/user_search.vue
index 06bfc35f..12aa4289 100644
--- a/src/components/user_search/user_search.vue
+++ b/src/components/user_search/user_search.vue
@@ -14,7 +14,7 @@
     </div>
     <div v-else class="panel-body">
       <List :items="users">
-        <template slot="item" scope="p">
+        <template slot="item" slot-scope="p">
           <FollowCard :user="p.item" />
         </template>
       </List>
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 66ce6cea..e6100ced 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -201,7 +201,7 @@
             </Autosuggest>
           </div>
           <BlockList :refresh="true" :getKey="item => item">
-            <template slot="item" scope="p">
+            <template slot="item" slot-scope="p">
               <BlockCard :userId="p.item" />
             </template>
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
@@ -215,7 +215,7 @@
             </Autosuggest>
           </div>
           <MuteList :refresh="true" :getKey="item => item">
-            <template slot="item" scope="p">
+            <template slot="item" slot-scope="p">
               <MuteCard :userId="p.item" />
             </template>
             <template slot="empty">{{$t('settings.no_mutes')}}</template>
diff --git a/src/components/who_to_follow/who_to_follow.vue b/src/components/who_to_follow/who_to_follow.vue
index 4812dd81..2752d519 100644
--- a/src/components/who_to_follow/who_to_follow.vue
+++ b/src/components/who_to_follow/who_to_follow.vue
@@ -5,7 +5,7 @@
     </div>
     <div class="panel-body">
       <List :items="users">
-        <template slot="item" scope="p">
+        <template slot="item" slot-scope="p">
           <FollowCard :user="p.item" />
         </template>
       </List>
-- 
cgit v1.2.3-70-g09d2


From 8fa639f252b20ddb550fe0cf71ba7ea2dfe47168 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 4 Apr 2019 13:30:34 -0400
Subject: add bulk action buttons

---
 src/components/user_settings/user_settings.js  |  4 +++-
 src/components/user_settings/user_settings.vue | 26 ++++++++++++++++++++++++++
 2 files changed, 29 insertions(+), 1 deletion(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index f8742cd4..022c847b 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -10,6 +10,7 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
 import BlockCard from '../block_card/block_card.vue'
 import MuteCard from '../mute_card/mute_card.vue'
 import SelectableList from '../selectable_list/selectable_list.vue'
+import ProgressButton from '../progress_button/progress_button.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
 import Autosuggest from '../autosuggest/autosuggest.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
@@ -73,7 +74,8 @@ const UserSettings = {
     EmojiInput,
     Autosuggest,
     BlockCard,
-    MuteCard
+    MuteCard,
+    ProgressButton
   },
   computed: {
     user () {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index e6100ced..8bce431a 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -201,6 +201,22 @@
             </Autosuggest>
           </div>
           <BlockList :refresh="true" :getKey="item => item">
+            <template slot="header" slot-scope="p">
+              <div class="bulk-actions-wrapper">
+                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0">
+                  {{ $t('user_card.block') }}
+                  <template slot="progress">
+                    {{ $t('user_card.block_progress') }}
+                  </template>
+                </ProgressButton>
+                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0">
+                  {{ $t('user_card.unblock') }}
+                  <template slot="progress">
+                    {{ $t('user_card.unblock_progress') }}
+                  </template>
+                </ProgressButton>
+              </div>
+            </template>
             <template slot="item" slot-scope="p">
               <BlockCard :userId="p.item" />
             </template>
@@ -282,5 +298,15 @@
   &-usersearch-wrapper {
     padding: 1em;
   }
+
+  .bulk-actions-wrapper {
+    text-align: right;
+    padding: 0 1em;
+    min-height: 28px;
+
+    button {
+      width: 10em;
+    }
+  }
 }
 </style>
-- 
cgit v1.2.3-70-g09d2


From 13c8f10f4b42c1d698bc99694cd3556c691ac880 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 4 Apr 2019 13:54:52 -0400
Subject: wire up bulk action buttons to vuex

---
 src/components/user_settings/user_settings.js  |  6 +++++
 src/components/user_settings/user_settings.vue |  4 +--
 src/modules/users.js                           | 36 ++++++++++++++++++--------
 3 files changed, 33 insertions(+), 13 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 022c847b..a9fe84b8 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -355,6 +355,12 @@ const UserSettings = {
           this.$store.dispatch('addNewUsers', users)
           return map(users, 'id')
         })
+    },
+    blockUsers (ids) {
+      return this.$store.dispatch('blockUsers', ids)
+    },
+    unblockUsers (ids) {
+      return this.$store.dispatch('unblockUsers', ids)
     }
   }
 }
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 8bce431a..5e0b71bf 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -203,13 +203,13 @@
           <BlockList :refresh="true" :getKey="item => item">
             <template slot="header" slot-scope="p">
               <div class="bulk-actions-wrapper">
-                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0">
+                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => blockUsers(p.selected)">
                   {{ $t('user_card.block') }}
                   <template slot="progress">
                     {{ $t('user_card.block_progress') }}
                   </template>
                 </ProgressButton>
-                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0">
+                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => unblockUsers(p.selected)">
                   {{ $t('user_card.unblock') }}
                   <template slot="progress">
                     {{ $t('user_card.unblock_progress') }}
diff --git a/src/modules/users.js b/src/modules/users.js
index 58db8b71..a458f681 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -32,6 +32,22 @@ const getNotificationPermission = () => {
   return Promise.resolve(Notification.permission)
 }
 
+const blockUser = (store, userId) => {
+  return store.rootState.api.backendInteractor.blockUser(userId)
+    .then((relationship) => {
+      store.commit('updateUserRelationship', [relationship])
+      store.commit('addBlockId', userId)
+      store.commit('removeStatus', { timeline: 'friends', userId })
+      store.commit('removeStatus', { timeline: 'public', userId })
+      store.commit('removeStatus', { timeline: 'publicAndExternal', userId })
+    })
+}
+
+const unblockUser = (store, userId) => {
+  return store.rootState.api.backendInteractor.unblockUser(userId)
+    .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+}
+
 export const mutations = {
   setMuted (state, { user: { id }, muted }) {
     const user = state.usersObject[id]
@@ -207,18 +223,16 @@ const users = {
         })
     },
     blockUser (store, userId) {
-      return store.rootState.api.backendInteractor.blockUser(userId)
-        .then((relationship) => {
-          store.commit('updateUserRelationship', [relationship])
-          store.commit('addBlockId', userId)
-          store.commit('removeStatus', { timeline: 'friends', userId })
-          store.commit('removeStatus', { timeline: 'public', userId })
-          store.commit('removeStatus', { timeline: 'publicAndExternal', userId })
-        })
+      return blockUser(store, userId)
     },
-    unblockUser (store, id) {
-      return store.rootState.api.backendInteractor.unblockUser(id)
-        .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+    unblockUser (store, userId) {
+      return unblockUser(store, userId)
+    },
+    blockUsers (store, userIds = []) {
+      return Promise.all(userIds.map(userId => blockUser(store, userId)))
+    },
+    unblockUsers (store, userIds = []) {
+      return Promise.all(userIds.map(userId => unblockUser(store, userId)))
     },
     fetchMutes (store) {
       return store.rootState.api.backendInteractor.fetchMutes()
-- 
cgit v1.2.3-70-g09d2


From d3cad54aa3e72fd83da89b483a9c7522a2695bba Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 4 Apr 2019 14:02:46 -0400
Subject: add bulk mute/unmute buttons and wire up to vuex

---
 src/components/user_settings/user_settings.js  |  6 +++
 src/components/user_settings/user_settings.vue | 16 +++++++
 src/modules/users.js                           | 60 ++++++++++++++++----------
 3 files changed, 59 insertions(+), 23 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index a9fe84b8..45614ab5 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -361,6 +361,12 @@ const UserSettings = {
     },
     unblockUsers (ids) {
       return this.$store.dispatch('unblockUsers', ids)
+    },
+    muteUsers (ids) {
+      return this.$store.dispatch('muteUsers', ids)
+    },
+    unmuteUsers (ids) {
+      return this.$store.dispatch('unmuteUsers', ids)
     }
   }
 }
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 5e0b71bf..4ce9c55b 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -231,6 +231,22 @@
             </Autosuggest>
           </div>
           <MuteList :refresh="true" :getKey="item => item">
+            <template slot="header" slot-scope="p">
+              <div class="bulk-actions-wrapper">
+                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => muteUsers(p.selected)">
+                  {{ $t('user_card.mute') }}
+                  <template slot="progress">
+                    {{ $t('user_card.mute_progress') }}
+                  </template>
+                </ProgressButton>
+                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => unmuteUsers(p.selected)">
+                  {{ $t('user_card.unmute') }}
+                  <template slot="progress">
+                    {{ $t('user_card.unmute_progress') }}
+                  </template>
+                </ProgressButton>
+              </div>
+            </template>
             <template slot="item" slot-scope="p">
               <MuteCard :userId="p.item" />
             </template>
diff --git a/src/modules/users.js b/src/modules/users.js
index a458f681..a5dec794 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -32,19 +32,32 @@ const getNotificationPermission = () => {
   return Promise.resolve(Notification.permission)
 }
 
-const blockUser = (store, userId) => {
-  return store.rootState.api.backendInteractor.blockUser(userId)
+const blockUser = (store, id) => {
+  return store.rootState.api.backendInteractor.blockUser(id)
     .then((relationship) => {
       store.commit('updateUserRelationship', [relationship])
-      store.commit('addBlockId', userId)
-      store.commit('removeStatus', { timeline: 'friends', userId })
-      store.commit('removeStatus', { timeline: 'public', userId })
-      store.commit('removeStatus', { timeline: 'publicAndExternal', userId })
+      store.commit('addBlockId', id)
+      store.commit('removeStatus', { timeline: 'friends', userId: id })
+      store.commit('removeStatus', { timeline: 'public', userId: id })
+      store.commit('removeStatus', { timeline: 'publicAndExternal', userId: id })
     })
 }
 
-const unblockUser = (store, userId) => {
-  return store.rootState.api.backendInteractor.unblockUser(userId)
+const unblockUser = (store, id) => {
+  return store.rootState.api.backendInteractor.unblockUser(id)
+    .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+}
+
+const muteUser = (store, id) => {
+  return store.rootState.api.backendInteractor.muteUser(id)
+    .then((relationship) => {
+      store.commit('updateUserRelationship', [relationship])
+      store.commit('addMuteId', id)
+    })
+}
+
+const unmuteUser = (store, id) => {
+  return store.rootState.api.backendInteractor.unmuteUser(id)
     .then((relationship) => store.commit('updateUserRelationship', [relationship]))
 }
 
@@ -222,17 +235,17 @@ const users = {
           return blocks
         })
     },
-    blockUser (store, userId) {
-      return blockUser(store, userId)
+    blockUser (store, id) {
+      return blockUser(store, id)
     },
-    unblockUser (store, userId) {
-      return unblockUser(store, userId)
+    unblockUser (store, id) {
+      return unblockUser(store, id)
     },
-    blockUsers (store, userIds = []) {
-      return Promise.all(userIds.map(userId => blockUser(store, userId)))
+    blockUsers (store, ids = []) {
+      return Promise.all(ids.map(id => blockUser(store, id)))
     },
-    unblockUsers (store, userIds = []) {
-      return Promise.all(userIds.map(userId => unblockUser(store, userId)))
+    unblockUsers (store, ids = []) {
+      return Promise.all(ids.map(id => unblockUser(store, id)))
     },
     fetchMutes (store) {
       return store.rootState.api.backendInteractor.fetchMutes()
@@ -243,15 +256,16 @@ const users = {
         })
     },
     muteUser (store, id) {
-      return store.rootState.api.backendInteractor.muteUser(id)
-        .then((relationship) => {
-          store.commit('updateUserRelationship', [relationship])
-          store.commit('addMuteId', id)
-        })
+      return muteUser(store, id)
     },
     unmuteUser (store, id) {
-      return store.rootState.api.backendInteractor.unmuteUser(id)
-        .then((relationship) => store.commit('updateUserRelationship', [relationship]))
+      return unmuteUser(store, id)
+    },
+    muteUsers (store, ids = []) {
+      return Promise.all(ids.map(id => muteUser(store, id)))
+    },
+    unmuteUsers (store, ids = []) {
+      return Promise.all(ids.map(id => unmuteUser(store, id)))
     },
     fetchFriends ({ rootState, commit }, id) {
       const user = rootState.users.usersObject[id]
-- 
cgit v1.2.3-70-g09d2


From c81bde0c1d772078974208551c36d0f52325a12b Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 4 Apr 2019 14:03:57 -0400
Subject: clean up

---
 src/components/user_settings/user_settings.vue | 24 ++++++------------------
 1 file changed, 6 insertions(+), 18 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 4ce9c55b..0f7fd0db 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -205,21 +205,15 @@
               <div class="bulk-actions-wrapper">
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => blockUsers(p.selected)">
                   {{ $t('user_card.block') }}
-                  <template slot="progress">
-                    {{ $t('user_card.block_progress') }}
-                  </template>
+                  <template slot="progress">{{ $t('user_card.block_progress') }}</template>
                 </ProgressButton>
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => unblockUsers(p.selected)">
                   {{ $t('user_card.unblock') }}
-                  <template slot="progress">
-                    {{ $t('user_card.unblock_progress') }}
-                  </template>
+                  <template slot="progress">{{ $t('user_card.unblock_progress') }}</template>
                 </ProgressButton>
               </div>
             </template>
-            <template slot="item" slot-scope="p">
-              <BlockCard :userId="p.item" />
-            </template>
+            <template slot="item" slot-scope="p"><BlockCard :userId="p.item" /></template>
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
           </BlockList>
         </div>
@@ -235,21 +229,15 @@
               <div class="bulk-actions-wrapper">
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => muteUsers(p.selected)">
                   {{ $t('user_card.mute') }}
-                  <template slot="progress">
-                    {{ $t('user_card.mute_progress') }}
-                  </template>
+                  <template slot="progress">{{ $t('user_card.mute_progress') }}</template>
                 </ProgressButton>
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => unmuteUsers(p.selected)">
                   {{ $t('user_card.unmute') }}
-                  <template slot="progress">
-                    {{ $t('user_card.unmute_progress') }}
-                  </template>
+                  <template slot="progress">{{ $t('user_card.unmute_progress') }}</template>
                 </ProgressButton>
               </div>
             </template>
-            <template slot="item" slot-scope="p">
-              <MuteCard :userId="p.item" />
-            </template>
+            <template slot="item" slot-scope="p"><MuteCard :userId="p.item" /></template>
             <template slot="empty">{{$t('settings.no_mutes')}}</template>
           </MuteList>
         </div>
-- 
cgit v1.2.3-70-g09d2


From 7f6bd538f3e763c19468a2cf7f6f3cf34645ccb0 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 4 Apr 2019 16:20:48 -0400
Subject: ui improvements

---
 src/components/selectable_list/selectable_list.vue |  2 +-
 src/components/user_settings/user_settings.vue     | 14 +++++++++-----
 2 files changed, 10 insertions(+), 6 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index 31b8ac9c..ae7315db 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -2,7 +2,7 @@
   <div class="selectable-list">
     <div class="selectable-list-header" v-if="items.length > 0">
       <div class="selectable-list-checkbox-wrapper">
-        <Checkbox :checked="allSelected" @change="toggleAll" />
+        <Checkbox :checked="allSelected" @change="toggleAll">Select All</Checkbox>
       </div>
       <div class="selectable-list-header-actions">
         <slot name="header" :selected="selected" />
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 0f7fd0db..268a0893 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -200,9 +200,9 @@
               <BlockCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <BlockList :refresh="true" :getKey="item => item">
+          <BlockList :refresh="true" :getKey="item => item" class="profile-edit-user-list">
             <template slot="header" slot-scope="p">
-              <div class="bulk-actions-wrapper">
+              <div class="profile-edit-bulk-actions">
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => blockUsers(p.selected)">
                   {{ $t('user_card.block') }}
                   <template slot="progress">{{ $t('user_card.block_progress') }}</template>
@@ -224,9 +224,9 @@
               <MuteCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <MuteList :refresh="true" :getKey="item => item">
+          <MuteList :refresh="true" :getKey="item => item" class="profile-edit-user-list">
             <template slot="header" slot-scope="p">
-              <div class="bulk-actions-wrapper">
+              <div class="profile-edit-bulk-actions">
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => muteUsers(p.selected)">
                   {{ $t('user_card.mute') }}
                   <template slot="progress">{{ $t('user_card.mute_progress') }}</template>
@@ -303,7 +303,11 @@
     padding: 1em;
   }
 
-  .bulk-actions-wrapper {
+  &-user-list {
+    margin-top: 1.5em;
+  }
+
+  &-bulk-actions {
     text-align: right;
     padding: 0 1em;
     min-height: 28px;
-- 
cgit v1.2.3-70-g09d2


From a89236f67c84723d5e7948beee2d43b0555ab6b1 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sat, 6 Apr 2019 14:56:50 -0400
Subject: clean up

---
 src/components/user_settings/user_settings.js  | 3 +++
 src/components/user_settings/user_settings.vue | 4 ++--
 2 files changed, 5 insertions(+), 2 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 45614ab5..e88ee612 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -367,6 +367,9 @@ const UserSettings = {
     },
     unmuteUsers (ids) {
       return this.$store.dispatch('unmuteUsers', ids)
+    },
+    identity (value) {
+      return value
     }
   }
 }
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 268a0893..0dcae0d7 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -200,7 +200,7 @@
               <BlockCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <BlockList :refresh="true" :getKey="item => item" class="profile-edit-user-list">
+          <BlockList :refresh="true" :getKey="identity" class="profile-edit-user-list">
             <template slot="header" slot-scope="p">
               <div class="profile-edit-bulk-actions">
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => blockUsers(p.selected)">
@@ -224,7 +224,7 @@
               <MuteCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <MuteList :refresh="true" :getKey="item => item" class="profile-edit-user-list">
+          <MuteList :refresh="true" :getKey="identity" class="profile-edit-user-list">
             <template slot="header" slot-scope="p">
               <div class="profile-edit-bulk-actions">
                 <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => muteUsers(p.selected)">
-- 
cgit v1.2.3-70-g09d2


From e67fecff0a5e8c0034b860b8a7a625e35c03b5a3 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Thu, 11 Apr 2019 22:15:58 -0400
Subject: code readability

---
 src/components/follow_requests/follow_requests.vue |  4 ++--
 src/components/selectable_list/selectable_list.vue |  8 ++++----
 src/components/user_profile/user_profile.vue       |  8 ++++----
 src/components/user_search/user_search.vue         |  4 ++--
 src/components/user_settings/user_settings.vue     | 16 ++++++++--------
 src/components/who_to_follow/who_to_follow.vue     |  4 ++--
 6 files changed, 22 insertions(+), 22 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/follow_requests/follow_requests.vue b/src/components/follow_requests/follow_requests.vue
index 8b1e8f94..d5e45b9d 100644
--- a/src/components/follow_requests/follow_requests.vue
+++ b/src/components/follow_requests/follow_requests.vue
@@ -5,8 +5,8 @@
     </div>
     <div class="panel-body">
       <List :items="requests">
-        <template slot="item" slot-scope="p">
-          <FollowRequestCard :user="p.item" />
+        <template slot="item" slot-scope="{item}">
+          <FollowRequestCard :user="item" />
         </template>
       </List>
     </div>
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index 80306594..ba1e5266 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -9,12 +9,12 @@
       </div>
     </div>
     <List :items="items" :getKey="getKey">
-      <template slot="item" slot-scope="p">
-        <div class="selectable-list-item-inner" :class="{ 'selectable-list-item-selected-inner': isSelected(p.item) }">
+      <template slot="item" slot-scope="{item}">
+        <div class="selectable-list-item-inner" :class="{ 'selectable-list-item-selected-inner': isSelected(item) }">
           <div class="selectable-list-checkbox-wrapper">
-            <Checkbox :checked="isSelected(p.item)" @change="checked => toggle(checked, p.item)" />
+            <Checkbox :checked="isSelected(item)" @change="checked => toggle(checked, item)" />
           </div>
-          <slot name="item" :item="p.item" />
+          <slot name="item" :item="item" />
         </div>
       </template>
       <template slot="empty"><slot name="empty" /></template>
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index e67231b6..71c625b7 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -15,15 +15,15 @@
       />
       <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
         <FriendList :userId="userId">
-          <template slot="item" slot-scope="p">
-            <FollowCard :user="p.item" />
+          <template slot="item" slot-scope="{item}">
+            <FollowCard :user="item" />
           </template>
         </FriendList>
       </div>
       <div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
         <FollowerList :userId="userId">
-          <template slot="item" slot-scope="p">
-            <FollowCard :user="p.item" :noFollowsYou="isUs" />
+          <template slot="item" slot-scope="{item}">
+            <FollowCard :user="item" :noFollowsYou="isUs" />
           </template>
         </FollowerList>
       </div>
diff --git a/src/components/user_search/user_search.vue b/src/components/user_search/user_search.vue
index 12aa4289..c0636e43 100644
--- a/src/components/user_search/user_search.vue
+++ b/src/components/user_search/user_search.vue
@@ -14,8 +14,8 @@
     </div>
     <div v-else class="panel-body">
       <List :items="users">
-        <template slot="item" slot-scope="p">
-          <FollowCard :user="p.item" />
+        <template slot="item" slot-scope="{item}">
+          <FollowCard :user="item" />
         </template>
       </List>
     </div>
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 0dcae0d7..7e8e45fd 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -201,19 +201,19 @@
             </Autosuggest>
           </div>
           <BlockList :refresh="true" :getKey="identity" class="profile-edit-user-list">
-            <template slot="header" slot-scope="p">
+            <template slot="header" slot-scope="{selected}">
               <div class="profile-edit-bulk-actions">
-                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => blockUsers(p.selected)">
+                <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => blockUsers(selected)">
                   {{ $t('user_card.block') }}
                   <template slot="progress">{{ $t('user_card.block_progress') }}</template>
                 </ProgressButton>
-                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => unblockUsers(p.selected)">
+                <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => unblockUsers(selected)">
                   {{ $t('user_card.unblock') }}
                   <template slot="progress">{{ $t('user_card.unblock_progress') }}</template>
                 </ProgressButton>
               </div>
             </template>
-            <template slot="item" slot-scope="p"><BlockCard :userId="p.item" /></template>
+            <template slot="item" slot-scope="{item}"><BlockCard :userId="item" /></template>
             <template slot="empty">{{$t('settings.no_blocks')}}</template>
           </BlockList>
         </div>
@@ -225,19 +225,19 @@
             </Autosuggest>
           </div>
           <MuteList :refresh="true" :getKey="identity" class="profile-edit-user-list">
-            <template slot="header" slot-scope="p">
+            <template slot="header" slot-scope="{selected}">
               <div class="profile-edit-bulk-actions">
-                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => muteUsers(p.selected)">
+                <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => muteUsers(selected)">
                   {{ $t('user_card.mute') }}
                   <template slot="progress">{{ $t('user_card.mute_progress') }}</template>
                 </ProgressButton>
-                <ProgressButton class="btn btn-default" v-if="p.selected.length > 0" :click="() => unmuteUsers(p.selected)">
+                <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => unmuteUsers(selected)">
                   {{ $t('user_card.unmute') }}
                   <template slot="progress">{{ $t('user_card.unmute_progress') }}</template>
                 </ProgressButton>
               </div>
             </template>
-            <template slot="item" slot-scope="p"><MuteCard :userId="p.item" /></template>
+            <template slot="item" slot-scope="{item}"><MuteCard :userId="item" /></template>
             <template slot="empty">{{$t('settings.no_mutes')}}</template>
           </MuteList>
         </div>
diff --git a/src/components/who_to_follow/who_to_follow.vue b/src/components/who_to_follow/who_to_follow.vue
index 2752d519..412a2fbe 100644
--- a/src/components/who_to_follow/who_to_follow.vue
+++ b/src/components/who_to_follow/who_to_follow.vue
@@ -5,8 +5,8 @@
     </div>
     <div class="panel-body">
       <List :items="users">
-        <template slot="item" slot-scope="p">
-          <FollowCard :user="p.item" />
+        <template slot="item" slot-scope="{item}">
+          <FollowCard :user="item" />
         </template>
       </List>
     </div>
-- 
cgit v1.2.3-70-g09d2


From bd88a481c66ae3dd8f95652fe467fe5fd919b810 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Mon, 15 Apr 2019 16:37:42 -0400
Subject: remove extra spacing

---
 src/components/user_settings/user_settings.vue | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 7e8e45fd..d68e68fa 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -200,7 +200,7 @@
               <BlockCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <BlockList :refresh="true" :getKey="identity" class="profile-edit-user-list">
+          <BlockList :refresh="true" :getKey="identity">
             <template slot="header" slot-scope="{selected}">
               <div class="profile-edit-bulk-actions">
                 <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => blockUsers(selected)">
@@ -224,7 +224,7 @@
               <MuteCard slot-scope="row" :userId="row.item"/>
             </Autosuggest>
           </div>
-          <MuteList :refresh="true" :getKey="identity" class="profile-edit-user-list">
+          <MuteList :refresh="true" :getKey="identity">
             <template slot="header" slot-scope="{selected}">
               <div class="profile-edit-bulk-actions">
                 <ProgressButton class="btn btn-default" v-if="selected.length > 0" :click="() => muteUsers(selected)">
@@ -303,10 +303,6 @@
     padding: 1em;
   }
 
-  &-user-list {
-    margin-top: 1.5em;
-  }
-
   &-bulk-actions {
     text-align: right;
     padding: 0 1em;
-- 
cgit v1.2.3-70-g09d2


From 562120ae48945d87a24f2248f9b16185b49159d0 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Fri, 29 Mar 2019 21:58:20 -0400
Subject: split out follow’s importer as a separate component
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/importer/importer.js            | 36 ++++++++++++++++++++++++++
 src/components/importer/importer.vue           | 19 ++++++++++++++
 src/components/user_settings/user_settings.js  | 31 +++-------------------
 src/components/user_settings/user_settings.vue | 14 +---------
 4 files changed, 59 insertions(+), 41 deletions(-)
 create mode 100644 src/components/importer/importer.js
 create mode 100644 src/components/importer/importer.vue

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js
new file mode 100644
index 00000000..8d6c8b3f
--- /dev/null
+++ b/src/components/importer/importer.js
@@ -0,0 +1,36 @@
+const Importer = {
+  data () {
+    return {
+      file: null,
+      error: false,
+      success: false,
+      uploading: false
+    }
+  },
+  methods: {
+    change () {
+      this.file = this.$refs.input.files[0]
+    },
+    submit () {
+      this.uploading = true
+      // eslint-disable-next-line no-undef
+      const formData = new FormData()
+      formData.append('list', this.file)
+      this.$store.state.api.backendInteractor.followImport({params: formData})
+        .then((status) => {
+          if (status) {
+            this.success = true
+          } else {
+            this.error = true
+          }
+          this.uploading = false
+        })
+    },
+    dismiss () {
+      this.success = false
+      this.error = false
+    }
+  }
+}
+
+export default Importer
diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue
new file mode 100644
index 00000000..0fd83b7c
--- /dev/null
+++ b/src/components/importer/importer.vue
@@ -0,0 +1,19 @@
+<template>
+  <div class="importer">
+    <form>
+      <input type="file" ref="input" v-on:change="change" />
+    </form>
+    <i class="icon-spin4 animate-spin uploading" v-if="uploading"></i>
+    <button class="btn btn-default" v-else @click="submit">{{$t('general.submit')}}</button>
+    <div v-if="success">
+      <i class="icon-cross" @click="dismiss"></i>
+      <p>{{$t('settings.follows_imported')}}</p>
+    </div>
+    <div v-else-if="error">
+      <i class="icon-cross" @click="dismiss"></i>
+      <p>{{$t('settings.follow_import_error')}}</p>
+    </div>
+  </div>
+</template>
+
+<script src="./importer.js"></script>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index e88ee612..a213650b 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -13,6 +13,7 @@ import SelectableList from '../selectable_list/selectable_list.vue'
 import ProgressButton from '../progress_button/progress_button.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
 import Autosuggest from '../autosuggest/autosuggest.vue'
+import Importer from '../importer/importer.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
 import userSearchApi from '../../services/new_api/user_search.js'
 
@@ -40,14 +41,10 @@ const UserSettings = {
       hideFollowers: this.$store.state.users.currentUser.hide_followers,
       showRole: this.$store.state.users.currentUser.show_role,
       role: this.$store.state.users.currentUser.role,
-      followList: null,
-      followImportError: false,
-      followsImported: false,
       enableFollowsExport: true,
       pickAvatarBtnVisible: true,
       bannerUploading: false,
       backgroundUploading: false,
-      followListUploading: false,
       bannerPreview: null,
       backgroundPreview: null,
       bannerUploadError: null,
@@ -75,7 +72,8 @@ const UserSettings = {
     Autosuggest,
     BlockCard,
     MuteCard,
-    ProgressButton
+    ProgressButton,
+    Importer
   },
   computed: {
     user () {
@@ -236,19 +234,6 @@ const UserSettings = {
         this.backgroundUploading = false
       })
     },
-    importFollows () {
-      this.followListUploading = true
-      const followList = this.followList
-      this.$store.state.api.backendInteractor.followImport({params: followList})
-        .then((status) => {
-          if (status) {
-            this.followsImported = true
-          } else {
-            this.followImportError = true
-          }
-          this.followListUploading = false
-        })
-    },
     /* This function takes an Array of Users
      * and outputs a file with all the addresses for the user to download
      */
@@ -283,16 +268,6 @@ const UserSettings = {
           setTimeout(() => { this.enableFollowsExport = true }, 2000)
         })
     },
-    followListChange () {
-      // eslint-disable-next-line no-undef
-      let formData = new FormData()
-      formData.append('list', this.$refs.followlist.files[0])
-      this.followList = formData
-    },
-    dismissImported () {
-      this.followsImported = false
-      this.followImportError = false
-    },
     confirmDelete () {
       this.deletingAccount = true
     },
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index d68e68fa..fb91e269 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -171,19 +171,7 @@
           <div class="setting-item">
             <h2>{{$t('settings.follow_import')}}</h2>
             <p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
-            <form>
-              <input type="file" ref="followlist" v-on:change="followListChange" />
-            </form>
-            <i class=" icon-spin4 animate-spin uploading" v-if="followListUploading"></i>
-            <button class="btn btn-default" v-else @click="importFollows">{{$t('general.submit')}}</button>
-            <div v-if="followsImported">
-              <i class="icon-cross" @click="dismissImported"></i>
-              <p>{{$t('settings.follows_imported')}}</p>
-            </div>
-            <div v-else-if="followImportError">
-              <i class="icon-cross" @click="dismissImported"></i>
-              <p>{{$t('settings.follow_import_error')}}</p>
-            </div>
+            <Importer />
           </div>
           <div class="setting-item" v-if="enableFollowsExport">
             <h2>{{$t('settings.follow_export')}}</h2>
-- 
cgit v1.2.3-70-g09d2


From 6d0e98a1c2c81c587b89736dbd2ac43a8c540d54 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sat, 30 Mar 2019 05:10:57 -0400
Subject: make Importer component reusable

---
 src/components/importer/importer.js            | 42 +++++++++++++++++++-------
 src/components/importer/importer.vue           | 10 +++---
 src/components/user_settings/user_settings.js  |  8 +++++
 src/components/user_settings/user_settings.vue |  2 +-
 src/i18n/en.json                               |  5 +++
 5 files changed, 50 insertions(+), 17 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js
index 44d02c93..c5f9e4d2 100644
--- a/src/components/importer/importer.js
+++ b/src/components/importer/importer.js
@@ -1,10 +1,34 @@
 const Importer = {
+  props: {
+    submitHandler: {
+      type: Function,
+      required: true
+    },
+    submitButtonLabel: {
+      type: String,
+      default () {
+        return this.$t('importer.submit')
+      }
+    },
+    successMessage: {
+      type: String,
+      default () {
+        return this.$t('importer.success')
+      }
+    },
+    errorMessage: {
+      type: String,
+      default () {
+        return this.$t('importer.error')
+      }
+    }
+  },
   data () {
     return {
       file: null,
       error: false,
       success: false,
-      uploading: false
+      submitting: false
     }
   },
   methods: {
@@ -12,16 +36,12 @@ const Importer = {
       this.file = this.$refs.input.files[0]
     },
     submit () {
-      this.uploading = true
-      this.$store.state.api.backendInteractor.followImport(this.file)
-        .then((status) => {
-          if (status) {
-            this.success = true
-          } else {
-            this.error = true
-          }
-          this.uploading = false
-        })
+      this.dismiss()
+      this.submitting = true
+      this.submitHandler(this.file)
+        .then(() => { this.success = true })
+        .catch(() => { this.error = true })
+        .finally(() => { this.submitting = false })
     },
     dismiss () {
       this.success = false
diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue
index d2447e1a..0c5aa93d 100644
--- a/src/components/importer/importer.vue
+++ b/src/components/importer/importer.vue
@@ -3,15 +3,15 @@
     <form>
       <input type="file" ref="input" v-on:change="change" />
     </form>
-    <i class="icon-spin4 animate-spin uploading" v-if="uploading"></i>
-    <button class="btn btn-default" v-else @click="submit">{{$t('general.submit')}}</button>
+    <i class="icon-spin4 animate-spin importer-uploading" v-if="submitting"></i>
+    <button class="btn btn-default" v-else @click="submit">{{submitButtonLabel}}</button>
     <div v-if="success">
       <i class="icon-cross" @click="dismiss"></i>
-      <p>{{$t('settings.follows_imported')}}</p>
+      <p>{{successMessage}}</p>
     </div>
     <div v-else-if="error">
       <i class="icon-cross" @click="dismiss"></i>
-      <p>{{$t('settings.follow_import_error')}}</p>
+      <p>{{errorMessage}}</p>
     </div>
   </div>
 </template>
@@ -20,7 +20,7 @@
 
 <style lang="scss">
 .importer {
-  .uploading {
+  &-uploading {
     font-size: 1.5em;
     margin: 0.25em;
   }
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index a213650b..32e92dc4 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -234,6 +234,14 @@ const UserSettings = {
         this.backgroundUploading = false
       })
     },
+    importFollows (file) {
+      return this.$store.state.api.backendInteractor.followImport(file)
+        .then((status) => {
+          if (!status) {
+            throw new Error('failed')
+          }
+        })
+    },
     /* This function takes an Array of Users
      * and outputs a file with all the addresses for the user to download
      */
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index fb91e269..fc40bdc0 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -171,7 +171,7 @@
           <div class="setting-item">
             <h2>{{$t('settings.follow_import')}}</h2>
             <p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
-            <Importer />
+            <Importer :submitHandler="importFollows" :successMessage="$t('settings.follows_imported')" :errorMessage="$t('settings.follow_import_error')" />
           </div>
           <div class="setting-item" v-if="enableFollowsExport">
             <h2>{{$t('settings.follow_export')}}</h2>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 711e8d31..34d252b2 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -31,6 +31,11 @@
     "save_without_cropping": "Save without cropping",
     "cancel": "Cancel"
   },
+  "importer": {
+    "submit": "Submit",
+    "success": "Imported successfully.",
+    "error": "An error occured while importing this file."
+  },
   "login": {
     "login": "Log in",
     "description": "Log in with OAuth",
-- 
cgit v1.2.3-70-g09d2


From 0ab2f9dfa58c1f4caf01f083175a171d19272cda Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sat, 30 Mar 2019 07:27:53 -0400
Subject: add “block import” feature
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/user_settings/user_settings.js               |  8 ++++++++
 src/components/user_settings/user_settings.vue              |  5 +++++
 src/i18n/en.json                                            |  4 ++++
 src/services/api/api.service.js                             | 13 +++++++++++++
 .../backend_interactor_service.js                           |  2 ++
 5 files changed, 32 insertions(+)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index fa252e59..c4214744 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -242,6 +242,14 @@ const UserSettings = {
           }
         })
     },
+    importBlocks (file) {
+      return this.$store.state.api.backendInteractor.importBlocks(file)
+        .then((status) => {
+          if (!status) {
+            throw new Error('failed')
+          }
+        })
+    },
     /* This function takes an Array of Users
      * and outputs a file with all the addresses for the user to download
      */
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index fc40bdc0..520a3d8a 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -180,6 +180,11 @@
           <div class="setting-item" v-else>
             <h2>{{$t('settings.follow_export_processing')}}</h2>
           </div>
+          <div class="setting-item">
+            <h2>{{$t('settings.block_import')}}</h2>
+            <p>{{$t('settings.import_blocks_from_a_csv_file')}}</p>
+            <Importer :submitHandler="importFollows" :successMessage="$t('settings.blocks_imported')" :errorMessage="$t('settings.block_import_error')" />
+          </div>
         </div>
 
         <div :label="$t('settings.blocks_tab')">
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 34d252b2..d4ec1134 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -131,6 +131,9 @@
     "avatarRadius": "Avatars",
     "background": "Background",
     "bio": "Bio",
+    "block_import": "Block import",
+    "block_import_error": "Error importing blocks",
+    "blocks_imported": "Blocks imported! Processing them will take a while.",
     "blocks_tab": "Blocks",
     "btnRadius": "Buttons",
     "cBlue": "Blue (Reply, follow)",
@@ -174,6 +177,7 @@
     "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
     "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
     "hide_filtered_statuses": "Hide filtered statuses",
+    "import_blocks_from_a_csv_file": "Import blocks from a csv file",
     "import_followers_from_a_csv_file": "Import follows from a csv file",
     "import_theme": "Load preset",
     "inputRadius": "Input fields",
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index dbcde41d..a6892959 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -9,6 +9,7 @@ const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json'
 const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
 const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
 const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
+const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
 const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
 const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
 const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
@@ -634,6 +635,17 @@ const uploadMedia = ({formData, credentials}) => {
     .then((data) => parseAttachment(data))
 }
 
+const importBlocks = ({file, credentials}) => {
+  const formData = new FormData()
+  formData.append('list', file)
+  return fetch(BLOCKS_IMPORT_URL, {
+    body: formData,
+    method: 'POST',
+    headers: authHeaders(credentials)
+  })
+    .then((response) => response.ok)
+}
+
 const importFollows = ({file, credentials}) => {
   const formData = new FormData()
   formData.append('list', file)
@@ -778,6 +790,7 @@ const apiService = {
   updateProfile,
   updateBanner,
   externalProfile,
+  importBlocks,
   importFollows,
   deleteAccount,
   changePassword,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 726c8ced..3256a921 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -107,6 +107,7 @@ const backendInteractorService = (credentials) => {
   const updateProfile = ({params}) => apiService.updateProfile({credentials, params})
 
   const externalProfile = (profileUrl) => apiService.externalProfile({profileUrl, credentials})
+  const importBlocks = (file) => apiService.importBlocks({file, credentials})
   const importFollows = (file) => apiService.importFollows({file, credentials})
 
   const deleteAccount = ({password}) => apiService.deleteAccount({credentials, password})
@@ -147,6 +148,7 @@ const backendInteractorService = (credentials) => {
     updateBanner,
     updateProfile,
     externalProfile,
+    importBlocks,
     importFollows,
     deleteAccount,
     changePassword,
-- 
cgit v1.2.3-70-g09d2


From 08eaf9bd33fff53909e3f240e26f246c5fb96892 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sat, 30 Mar 2019 08:03:40 -0400
Subject: make reusable Exporter component

---
 src/components/exporter/exporter.js            | 47 +++++++++++++++++++++++++
 src/components/exporter/exporter.vue           | 20 +++++++++++
 src/components/user_settings/user_settings.js  | 48 ++++++++------------------
 src/components/user_settings/user_settings.vue |  7 ++--
 src/i18n/en.json                               |  5 ++-
 5 files changed, 88 insertions(+), 39 deletions(-)
 create mode 100644 src/components/exporter/exporter.js
 create mode 100644 src/components/exporter/exporter.vue

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/exporter/exporter.js b/src/components/exporter/exporter.js
new file mode 100644
index 00000000..2ae9492c
--- /dev/null
+++ b/src/components/exporter/exporter.js
@@ -0,0 +1,47 @@
+const Exporter = {
+  props: {
+    getContent: {
+      type: Function,
+      required: true
+    },
+    filename: {
+      type: String,
+      default: 'export.csv'
+    },
+    exportButtonLabel: {
+      type: String,
+      default () {
+        return this.$t('exporter.export')
+      }
+    },
+    processingMessage: {
+      type: String,
+      default () {
+        return this.$t('exporter.processing')
+      }
+    }
+  },
+  data () {
+    return {
+      processing: false
+    }
+  },
+  methods: {
+    process () {
+      this.processing = true
+      this.getContent()
+        .then((content) => {
+          const fileToDownload = document.createElement('a')
+          fileToDownload.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content))
+          fileToDownload.setAttribute('download', this.filename)
+          fileToDownload.style.display = 'none'
+          document.body.appendChild(fileToDownload)
+          fileToDownload.click()
+          document.body.removeChild(fileToDownload)
+          setTimeout(() => { this.processing = false }, 2000)
+        })
+    }
+  }
+}
+
+export default Exporter
diff --git a/src/components/exporter/exporter.vue b/src/components/exporter/exporter.vue
new file mode 100644
index 00000000..f22e579e
--- /dev/null
+++ b/src/components/exporter/exporter.vue
@@ -0,0 +1,20 @@
+<template>
+  <div class="exporter">
+    <div v-if="processing">
+      <i class="icon-spin4 animate-spin exporter-processing"></i>
+      <span>{{processingMessage}}</span>
+    </div>
+    <button class="btn btn-default" @click="process" v-else>{{exportButtonLabel}}</button>
+  </div>
+</template>
+
+<script src="./exporter.js"></script>
+
+<style lang="scss">
+.exporter {
+  &-processing {
+    font-size: 1.5em;
+    margin: 0.25em;
+  }
+}
+</style>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index c4214744..d40301f2 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -14,6 +14,7 @@ import ProgressButton from '../progress_button/progress_button.vue'
 import EmojiInput from '../emoji-input/emoji-input.vue'
 import Autosuggest from '../autosuggest/autosuggest.vue'
 import Importer from '../importer/importer.vue'
+import Exporter from '../exporter/exporter.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
 import userSearchApi from '../../services/new_api/user_search.js'
 
@@ -41,7 +42,6 @@ const UserSettings = {
       hideFollowers: this.$store.state.users.currentUser.hide_followers,
       showRole: this.$store.state.users.currentUser.show_role,
       role: this.$store.state.users.currentUser.role,
-      enableFollowsExport: true,
       pickAvatarBtnVisible: true,
       bannerUploading: false,
       backgroundUploading: false,
@@ -73,7 +73,8 @@ const UserSettings = {
     BlockCard,
     MuteCard,
     ProgressButton,
-    Importer
+    Importer,
+    Exporter
   },
   computed: {
     user () {
@@ -250,38 +251,19 @@ const UserSettings = {
           }
         })
     },
-    /* This function takes an Array of Users
-     * and outputs a file with all the addresses for the user to download
-     */
-    exportPeople (users, filename) {
-      // Get all the friends addresses
-      var UserAddresses = users.map(function (user) {
-        // check is it's a local user
-        if (user && user.is_local) {
-          // append the instance address
-          // eslint-disable-next-line no-undef
-          user.screen_name += '@' + location.hostname
-        }
-        return user.screen_name
-      }).join('\n')
-      // Make the user download the file
-      var fileToDownload = document.createElement('a')
-      fileToDownload.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(UserAddresses))
-      fileToDownload.setAttribute('download', filename)
-      fileToDownload.style.display = 'none'
-      document.body.appendChild(fileToDownload)
-      fileToDownload.click()
-      document.body.removeChild(fileToDownload)
-    },
-    exportFollows () {
-      this.enableFollowsExport = false
-      this.$store.state.api.backendInteractor
-        .exportFriends({
-          id: this.$store.state.users.currentUser.id
-        })
+    getFollowsContent () {
+      return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
         .then((friendList) => {
-          this.exportPeople(friendList, 'friends.csv')
-          setTimeout(() => { this.enableFollowsExport = true }, 2000)
+          // Get all the friends addresses
+          return friendList.map((user) => {
+            // check is it's a local user
+            if (user && user.is_local) {
+              // append the instance address
+              // eslint-disable-next-line no-undef
+              return user.screen_name + '@' + location.hostname
+            }
+            return user.screen_name
+          }).join('\n')
         })
     },
     confirmDelete () {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 520a3d8a..ef77aaba 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -173,12 +173,9 @@
             <p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
             <Importer :submitHandler="importFollows" :successMessage="$t('settings.follows_imported')" :errorMessage="$t('settings.follow_import_error')" />
           </div>
-          <div class="setting-item" v-if="enableFollowsExport">
+          <div class="setting-item">
             <h2>{{$t('settings.follow_export')}}</h2>
-            <button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button>
-          </div>
-          <div class="setting-item" v-else>
-            <h2>{{$t('settings.follow_export_processing')}}</h2>
+            <Exporter :getContent="getFollowsContent" filename="friends.csv" :exportButtonLabel="$t('settings.follow_export_button')" />
           </div>
           <div class="setting-item">
             <h2>{{$t('settings.block_import')}}</h2>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index d4ec1134..10283024 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -2,6 +2,10 @@
   "chat": {
     "title": "Chat"
   },
+  "exporter": {
+    "export": "Export",
+    "processing": "Processing, you'll soon be asked to download your file"
+  },
   "features_panel": {
     "chat": "Chat",
     "gopher": "Gopher",
@@ -161,7 +165,6 @@
     "filtering_explanation": "All statuses containing these words will be muted, one per line",
     "follow_export": "Follow export",
     "follow_export_button": "Export your follows to a csv file",
-    "follow_export_processing": "Processing, you'll soon be asked to download your file",
     "follow_import": "Follow import",
     "follow_import_error": "Error importing followers",
     "follows_imported": "Follows imported! Processing them will take a while.",
-- 
cgit v1.2.3-70-g09d2


From 13abe64f875220eb5ff5424187bc771acf46190e Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sat, 30 Mar 2019 08:07:49 -0400
Subject: fix wrong function binding

---
 src/components/user_settings/user_settings.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index ef77aaba..db8c1ea9 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -180,7 +180,7 @@
           <div class="setting-item">
             <h2>{{$t('settings.block_import')}}</h2>
             <p>{{$t('settings.import_blocks_from_a_csv_file')}}</p>
-            <Importer :submitHandler="importFollows" :successMessage="$t('settings.blocks_imported')" :errorMessage="$t('settings.block_import_error')" />
+            <Importer :submitHandler="importBlocks" :successMessage="$t('settings.blocks_imported')" :errorMessage="$t('settings.block_import_error')" />
           </div>
         </div>
 
-- 
cgit v1.2.3-70-g09d2


From 95bc2d727b8a94e9050a71c27838eb2a0d765011 Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Sat, 30 Mar 2019 08:14:52 -0400
Subject: add “export blocks” feature
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/user_settings/user_settings.js  | 19 +++++++++++++++++--
 src/components/user_settings/user_settings.vue |  4 ++++
 src/i18n/en.json                               |  2 ++
 3 files changed, 23 insertions(+), 2 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index d40301f2..de8b4ebf 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -253,9 +253,24 @@ const UserSettings = {
     },
     getFollowsContent () {
       return this.$store.state.api.backendInteractor.exportFriends({ id: this.$store.state.users.currentUser.id })
-        .then((friendList) => {
+        .then((users) => {
+          // Get all the friends addresses
+          return users.map((user) => {
+            // check is it's a local user
+            if (user && user.is_local) {
+              // append the instance address
+              // eslint-disable-next-line no-undef
+              return user.screen_name + '@' + location.hostname
+            }
+            return user.screen_name
+          }).join('\n')
+        })
+    },
+    getBlocksContent () {
+      return this.$store.state.api.backendInteractor.fetchBlocks()
+        .then((users) => {
           // Get all the friends addresses
-          return friendList.map((user) => {
+          return users.map((user) => {
             // check is it's a local user
             if (user && user.is_local) {
               // append the instance address
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index db8c1ea9..8a94f0b8 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -182,6 +182,10 @@
             <p>{{$t('settings.import_blocks_from_a_csv_file')}}</p>
             <Importer :submitHandler="importBlocks" :successMessage="$t('settings.blocks_imported')" :errorMessage="$t('settings.block_import_error')" />
           </div>
+          <div class="setting-item">
+            <h2>{{$t('settings.block_export')}}</h2>
+            <Exporter :getContent="getBlocksContent" filename="blocks.csv" :exportButtonLabel="$t('settings.block_export_button')" />
+          </div>
         </div>
 
         <div :label="$t('settings.blocks_tab')">
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 10283024..173e0de2 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -135,6 +135,8 @@
     "avatarRadius": "Avatars",
     "background": "Background",
     "bio": "Bio",
+    "block_export": "Block export",
+    "block_export_button": "Export your blocks to a csv file",
     "block_import": "Block import",
     "block_import_error": "Error importing blocks",
     "blocks_imported": "Blocks imported! Processing them will take a while.",
-- 
cgit v1.2.3-70-g09d2


From e01ffc67c687f3f916466f6838d4eefc3f5aa86d Mon Sep 17 00:00:00 2001
From: taehoon <th.dev91@gmail.com>
Date: Wed, 15 May 2019 22:21:06 -0400
Subject: move visibility-tray css in local scope

---
 src/App.scss                                         | 7 -------
 src/components/post_status_form/post_status_form.vue | 9 +++++++++
 src/components/user_settings/user_settings.vue       | 4 ++++
 3 files changed, 13 insertions(+), 7 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/App.scss b/src/App.scss
index 962087b5..4a3ce144 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -625,13 +625,6 @@ nav {
   text-align: right;
 }
 
-.visibility-tray {
-  display: flex;
-  justify-content: space-between;
-  flex-direction: row-reverse;
-  padding-top: 5px;
-}
-
 .visibility-notice {
   padding: .5em;
   border: 1px solid $fallback--faint;
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index bdf15844..a841a17c 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -147,6 +147,15 @@
   }
 }
 
+.post-status-form {
+  .visibility-tray {
+    display: flex;
+    justify-content: space-between;
+    flex-direction: row-reverse;
+    padding-top: 5px;
+  }
+}
+
 .post-status-form, .login {
   .form-bottom {
     display: flex;
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 8a94f0b8..2cb8b37a 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -251,6 +251,10 @@
     margin: 0;
   }
 
+  .visibility-tray {
+    padding-top: 5px;
+  }
+
   input[type=file] {
     padding: 5px;
     height: auto;
-- 
cgit v1.2.3-70-g09d2


From fc12b63fcc41a06ee818428640f962fbea536e1f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Tue, 21 May 2019 23:35:40 +0300
Subject: mastoapi login works

---
 src/components/user_settings/user_settings.vue     |  1 +
 src/services/api/api.service.js                    |  6 +++---
 .../entity_normalizer/entity_normalizer.service.js | 23 ++++++++++++++++++++--
 3 files changed, 25 insertions(+), 5 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 2cb8b37a..377e8295 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -44,6 +44,7 @@
                 <scope-selector
                   :showAll="true"
                   :userDefault="newDefaultScope"
+                  :initialScope="newDefaultScope"
                   :onScopeChange="changeVis"/>
               </div>
             </div>
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index c67eccf1..115c3296 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,5 +1,4 @@
 /* eslint-env browser */
-const LOGIN_URL = '/api/account/verify_credentials.json'
 const REGISTRATION_URL = '/api/account/register.json'
 const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
 const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
@@ -17,6 +16,7 @@ const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenNa
 const ADMIN_USERS_URL = '/api/pleroma/admin/users'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
 
+const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
 const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
 const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
@@ -497,8 +497,7 @@ const fetchPinnedStatuses = ({ id, credentials }) => {
 }
 
 const verifyCredentials = (user) => {
-  return fetch(LOGIN_URL, {
-    method: 'POST',
+  return fetch(MASTODON_LOGIN_URL, {
     headers: authHeaders(user)
   })
     .then((response) => {
@@ -510,6 +509,7 @@ const verifyCredentials = (user) => {
         }
       }
     })
+
     .then((data) => data.error ? data : parseUser(data))
 }
 
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 46ca7602..f14c483c 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -72,6 +72,27 @@ export const parseUser = (data) => {
         moderator: data.pleroma.is_moderator,
         admin: data.pleroma.is_admin
       }
+      // TODO: Clean up in UI? This is duplication from what BE does for qvitterapi
+      if (output.rights.admin) {
+        output.role = 'admin'
+      } else if (output.rights.moderator) {
+        output.role = 'moderator'
+      } else {
+        output.role = 'member'
+      }
+    }
+
+    if (data.source) {
+      output.description = data.source.note
+      output.default_scope = data.source.privacy
+      if (data.source.pleroma) {
+        if (data.source.pleroma.show_role) {
+          console.log(data.source)
+          console.log(data.pleroma)
+        }
+        output.no_rich_text = data.source.pleroma.no_rich_text
+        output.show_role = data.source.pleroma.show_role
+      }
     }
 
     // TODO: handle is_local
@@ -106,8 +127,6 @@ export const parseUser = (data) => {
 
     output.muted = data.muted
 
-    // QVITTER ONLY FOR NOW
-    // Really only applies to logged in user, really.. I THINK
     if (data.rights) {
       output.rights = {
         moderator: data.rights.delete_others_notice,
-- 
cgit v1.2.3-70-g09d2


From 28ca504576134a8dd7b7e90488319c23f23b2300 Mon Sep 17 00:00:00 2001
From: William Pitcock <nenolod@dereferenced.org>
Date: Sat, 25 May 2019 07:01:02 +0000
Subject: wire up notification settings

---
 src/components/user_settings/user_settings.js      |  7 +++-
 src/components/user_settings/user_settings.vue     | 45 ++++++++++++++++++++++
 src/i18n/en.json                                   |  7 ++++
 src/services/api/api.service.js                    | 19 ++++++++-
 .../backend_interactor_service.js                  |  7 +++-
 .../entity_normalizer/entity_normalizer.service.js |  5 +++
 6 files changed, 87 insertions(+), 3 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 2418450c..ae36e5e8 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -55,7 +55,8 @@ const UserSettings = {
       changePasswordInputs: [ '', '', '' ],
       changedPassword: false,
       changePasswordError: false,
-      activeTab: 'profile'
+      activeTab: 'profile',
+      notificationSettings: this.$store.state.users.currentUser.notification_settings
     }
   },
   created () {
@@ -128,6 +129,10 @@ const UserSettings = {
             this.$store.commit('setCurrentUser', user)
           })
     },
+    updateNotificationSettings () {
+      this.$store.state.api.backendInteractor
+        .updateNotificationSettings({ settings: this.notificationSettings })
+    },
     changeVis (visibility) {
       this.newDefaultScope = visibility
     },
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 2cb8b37a..69f1b44f 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -167,6 +167,51 @@
           </div>
         </div>
 
+        <div :label="$t('settings.interactions_tab')" v-if="pleromaBackend">
+          <div class="setting-item">
+            <h2>{{$t('settings.notifications')}}</h2>
+            <ul class="setting-list">
+              <li>
+                <input type="checkbox" id="notification-setting-local" v-model="notificationSettings.local">
+                <label for="notification-setting-local">
+                {{$t('settings.notification_setting_local')}}
+                </label>
+              </li>
+              <li>
+                <input type="checkbox" id="notification-setting-remote" v-model="notificationSettings.remote">
+                <label for="notification-setting-remote">
+                {{$t('settings.notification_setting_remote')}}
+                </label>
+              </li>
+              <li>
+                <input type="checkbox" id="notification-setting-follows" v-model="notificationSettings.follows">
+                <label for="notification-setting-follows">
+                {{$t('settings.notification_setting_follows')}}
+                </label>
+              </li>
+              <li>
+                <input type="checkbox" id="notification-setting-non-follows" v-model="notificationSettings.non_follows">
+                <label for="notification-setting-non-follows">
+                {{$t('settings.notification_setting_non_follows')}}
+                </label>
+              </li>
+              <li>
+                <input type="checkbox" id="notification-setting-followers" v-model="notificationSettings.followers">
+                <label for="notification-setting-followers">
+                {{$t('settings.notification_setting_followers')}}
+                </label>
+              </li>
+              <li>
+                <input type="checkbox" id="notification-setting-non-followers" v-model="notificationSettings.non_followers">
+                <label for="notification-setting-non-followers">
+                {{$t('settings.notification_setting_non_followers')}}
+                </label>
+              </li>
+            </ul>
+            <button class="btn btn-default" @click="updateNotificationSettings">{{$t('general.submit')}}</button>
+          </div>
+        </div>
+
         <div :label="$t('settings.data_import_export_tab')" v-if="pleromaBackend">
           <div class="setting-item">
             <h2>{{$t('settings.follow_import')}}</h2>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 031c93de..2840582b 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -209,6 +209,7 @@
     "loop_video": "Loop videos",
     "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
     "mutes_tab": "Mutes",
+    "interactions_tab": "Interactions",
     "play_videos_in_modal": "Play videos directly in the media viewer",
     "use_contain_fit": "Don't crop the attachment in thumbnails",
     "name": "Name",
@@ -277,6 +278,12 @@
       "true": "yes"
     },
     "notifications": "Notifications",
+    "notification_setting_local": "Show notifications from local users",
+    "notification_setting_remote": "Show notifications from remote users",
+    "notification_setting_follows": "Show notifications from users you follow",
+    "notification_setting_non_follows": "Show notifications from users you do not follow",
+    "notification_setting_followers": "Show notifications from users who follow you",
+    "notification_setting_non_followers": "Show notifications from users who do not follow you",
     "enable_web_push_notifications": "Enable web push notifications",
     "style": {
       "switcher": {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index c67eccf1..d6904d30 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -16,6 +16,7 @@ const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${
 const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status`
 const ADMIN_USERS_URL = '/api/pleroma/admin/users'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
+const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
 
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
 const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
@@ -97,6 +98,21 @@ const promisedRequest = ({ method, url, payload, credentials, headers = {} }) =>
     })
 }
 
+const updateNotificationSettings = ({credentials, settings}) => {
+  const form = new FormData()
+
+  each(settings, (value, key) => {
+    form.append(key, value)
+  })
+
+  return fetch(NOTIFICATION_SETTINGS_URL, {
+    headers: authHeaders(credentials),
+    method: 'PUT',
+    body: form
+  })
+  .then((data) => data.json())
+}
+
 const updateAvatar = ({credentials, avatar}) => {
   const form = new FormData()
   form.append('avatar', avatar)
@@ -767,7 +783,8 @@ const apiService = {
   markNotificationsAsSeen,
   fetchFavoritedByUsers,
   fetchRebloggedByUsers,
-  reportUser
+  reportUser,
+  updateNotificationSettings
 }
 
 export default apiService
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 639bcabc..36152429 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -87,6 +87,10 @@ const backendInteractorService = (credentials) => {
     return apiService.deleteUser({screen_name, credentials})
   }
 
+  const updateNotificationSettings = ({settings}) => {
+    return apiService.updateNotificationSettings({credentials, settings})
+  }
+
   const fetchMutes = () => apiService.fetchMutes({credentials})
   const muteUser = (id) => apiService.muteUser({credentials, id})
   const unmuteUser = (id) => apiService.unmuteUser({credentials, id})
@@ -171,7 +175,8 @@ const backendInteractorService = (credentials) => {
     favorite,
     unfavorite,
     retweet,
-    unretweet
+    unretweet,
+    updateNotificationSettings
   }
 
   return backendInteractorServiceInstance
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index efff23d1..baf58e9b 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -141,8 +141,13 @@ export const parseUser = (data) => {
     output.deactivated = data.pleroma.deactivated
   }
 
+  if (data.pleroma) {
+    output.notification_settings = data.pleroma.notification_settings
+  }
+
   output.tags = output.tags || []
   output.rights = output.rights || {}
+  output.notification_settings = output.notification_settings || {}
 
   return output
 }
-- 
cgit v1.2.3-70-g09d2


From 855566f887cb9f705f62141b929bb9a3d089099c Mon Sep 17 00:00:00 2001
From: William Pitcock <nenolod@dereferenced.org>
Date: Sat, 25 May 2019 23:44:36 +0000
Subject: notification controls: redesign entirely

---
 src/components/user_settings/user_settings.vue | 72 ++++++++++++--------------
 src/i18n/en.json                               | 14 ++---
 2 files changed, 39 insertions(+), 47 deletions(-)

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 69f1b44f..20b10979 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -167,47 +167,39 @@
           </div>
         </div>
 
-        <div :label="$t('settings.interactions_tab')" v-if="pleromaBackend">
+        <div :label="$t('settings.notifications')" v-if="pleromaBackend">
           <div class="setting-item">
-            <h2>{{$t('settings.notifications')}}</h2>
-            <ul class="setting-list">
-              <li>
-                <input type="checkbox" id="notification-setting-local" v-model="notificationSettings.local">
-                <label for="notification-setting-local">
-                {{$t('settings.notification_setting_local')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-setting-remote" v-model="notificationSettings.remote">
-                <label for="notification-setting-remote">
-                {{$t('settings.notification_setting_remote')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-setting-follows" v-model="notificationSettings.follows">
-                <label for="notification-setting-follows">
-                {{$t('settings.notification_setting_follows')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-setting-non-follows" v-model="notificationSettings.non_follows">
-                <label for="notification-setting-non-follows">
-                {{$t('settings.notification_setting_non_follows')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-setting-followers" v-model="notificationSettings.followers">
-                <label for="notification-setting-followers">
-                {{$t('settings.notification_setting_followers')}}
-                </label>
-              </li>
-              <li>
-                <input type="checkbox" id="notification-setting-non-followers" v-model="notificationSettings.non_followers">
-                <label for="notification-setting-non-followers">
-                {{$t('settings.notification_setting_non_followers')}}
-                </label>
-              </li>
-            </ul>
+            <div class="select-multiple">
+              <span class="label">{{$t('settings.notification_setting')}}</span>
+              <ul class="option-list">
+                <li>
+                  <input type="checkbox" id="notification-setting-follows" v-model="notificationSettings.follows">
+                  <label for="notification-setting-follows">
+                  {{$t('settings.notification_setting_follows')}}
+                  </label>
+                </li>
+                <li>
+                  <input type="checkbox" id="notification-setting-followers" v-model="notificationSettings.followers">
+                  <label for="notification-setting-followers">
+                  {{$t('settings.notification_setting_followers')}}
+                  </label>
+                </li>
+                <li>
+                  <input type="checkbox" id="notification-setting-non-follows" v-model="notificationSettings.non_follows">
+                  <label for="notification-setting-non-follows">
+                  {{$t('settings.notification_setting_non_follows')}}
+                  </label>
+                </li>
+                <li>
+                  <input type="checkbox" id="notification-setting-non-followers" v-model="notificationSettings.non_followers">
+                  <label for="notification-setting-non-followers">
+                  {{$t('settings.notification_setting_non_followers')}}
+                  </label>
+                </li>
+              </ul>
+            </div>
+            <p>{{$t('settings.notification_mutes')}}</p>
+            <p>{{$t('settings.notification_blocks')}}</p>
             <button class="btn btn-default" @click="updateNotificationSettings">{{$t('general.submit')}}</button>
           </div>
         </div>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 2840582b..2b5c5468 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -209,7 +209,6 @@
     "loop_video": "Loop videos",
     "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
     "mutes_tab": "Mutes",
-    "interactions_tab": "Interactions",
     "play_videos_in_modal": "Play videos directly in the media viewer",
     "use_contain_fit": "Don't crop the attachment in thumbnails",
     "name": "Name",
@@ -278,12 +277,13 @@
       "true": "yes"
     },
     "notifications": "Notifications",
-    "notification_setting_local": "Show notifications from local users",
-    "notification_setting_remote": "Show notifications from remote users",
-    "notification_setting_follows": "Show notifications from users you follow",
-    "notification_setting_non_follows": "Show notifications from users you do not follow",
-    "notification_setting_followers": "Show notifications from users who follow you",
-    "notification_setting_non_followers": "Show notifications from users who do not follow you",
+    "notification_setting": "Receive notifications from:",
+    "notification_setting_follows": "Users you follow",
+    "notification_setting_non_follows": "Users you do not follow",
+    "notification_setting_followers": "Users who follow you",
+    "notification_setting_non_followers": "Users who do not follow you",
+    "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
+    "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
     "enable_web_push_notifications": "Enable web push notifications",
     "style": {
       "switcher": {
-- 
cgit v1.2.3-70-g09d2


From 77eceedbf7a5b53948d7d91b3d228aa303c02081 Mon Sep 17 00:00:00 2001
From: Maksim <parallel588@gmail.com>
Date: Wed, 12 Jun 2019 20:16:55 +0000
Subject: Revert "add TOTP/Recovery Form for mobile version"

This reverts commit a3811f944819430c278b6da6b08dc322a9b9ff65.
---
 package.json                                       |    1 +
 src/boot/after_store.js                            |    2 +-
 src/boot/routes.js                                 |    4 +-
 src/components/auth_form/auth_form.js              |   26 +
 src/components/login_form/login_form.js            |   63 +-
 src/components/login_form/login_form.vue           |   68 +-
 src/components/mfa_form/recovery_form.js           |   41 +
 src/components/mfa_form/recovery_form.vue          |   42 +
 src/components/mfa_form/totp_form.js               |   40 +
 src/components/mfa_form/totp_form.vue              |   45 +
 src/components/user_panel/user_panel.js            |    8 +-
 src/components/user_panel/user_panel.vue           |   11 +-
 src/components/user_settings/confirm.js            |    9 +
 src/components/user_settings/confirm.vue           |   14 +
 src/components/user_settings/mfa.js                |  152 +++
 src/components/user_settings/mfa.vue               |  121 +++
 src/components/user_settings/mfa_backup_codes.js   |   17 +
 src/components/user_settings/mfa_backup_codes.vue  |   22 +
 src/components/user_settings/mfa_totp.js           |   49 +
 src/components/user_settings/mfa_totp.vue          |   23 +
 src/components/user_settings/user_settings.js      |    4 +-
 src/components/user_settings/user_settings.vue     |    2 +-
 src/i18n/en.json                                   |   39 +-
 src/i18n/ru.json                                   |   38 +-
 src/main.js                                        |    2 +
 src/modules/auth_flow.js                           |   89 ++
 src/modules/instance.js                            |    1 -
 src/services/api/api.service.js                    |   57 ++
 .../backend_interactor_service.js                  |   11 +
 src/services/new_api/mfa.js                        |   38 +
 src/services/new_api/oauth.js                      |   35 +-
 yarn.lock                                          | 1016 +++++++++++++-------
 32 files changed, 1654 insertions(+), 436 deletions(-)
 create mode 100644 src/components/auth_form/auth_form.js
 create mode 100644 src/components/mfa_form/recovery_form.js
 create mode 100644 src/components/mfa_form/recovery_form.vue
 create mode 100644 src/components/mfa_form/totp_form.js
 create mode 100644 src/components/mfa_form/totp_form.vue
 create mode 100644 src/components/user_settings/confirm.js
 create mode 100644 src/components/user_settings/confirm.vue
 create mode 100644 src/components/user_settings/mfa.js
 create mode 100644 src/components/user_settings/mfa.vue
 create mode 100644 src/components/user_settings/mfa_backup_codes.js
 create mode 100644 src/components/user_settings/mfa_backup_codes.vue
 create mode 100644 src/components/user_settings/mfa_totp.js
 create mode 100644 src/components/user_settings/mfa_totp.vue
 create mode 100644 src/modules/auth_flow.js
 create mode 100644 src/services/new_api/mfa.js

(limited to 'src/components/user_settings/user_settings.vue')

diff --git a/package.json b/package.json
index 5409645b..0e7572fa 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
     "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
   },
   "dependencies": {
+    "@chenfengyuan/vue-qrcode": "^1.0.0",
     "babel-plugin-add-module-exports": "^0.2.1",
     "babel-plugin-lodash": "^3.2.11",
     "chromatism": "^3.0.0",
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 603de348..7510e562 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -92,6 +92,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
       ? 0
       : config.logoMargin
   })
+  store.commit('authFlow/setInitialStrategy', config.loginMethod)
 
   copyInstanceOption('redirectRootNoLogin')
   copyInstanceOption('redirectRootLogin')
@@ -100,7 +101,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
   copyInstanceOption('formattingOptionsEnabled')
   copyInstanceOption('hideMutedPosts')
   copyInstanceOption('collapseMessageWithSubject')
-  copyInstanceOption('loginMethod')
   copyInstanceOption('scopeCopy')
   copyInstanceOption('subjectLineBehavior')
   copyInstanceOption('postContentType')
diff --git a/src/boot/routes.js b/src/boot/routes.js
index 1a179099..055a0aea 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -13,7 +13,7 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
 import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
 import UserSearch from 'components/user_search/user_search.vue'
 import Notifications from 'components/notifications/notifications.vue'
-import LoginForm from 'components/login_form/login_form.vue'
+import AuthForm from 'components/auth_form/auth_form.js'
 import ChatPanel from 'components/chat_panel/chat_panel.vue'
 import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
 import About from 'components/about/about.vue'
@@ -42,7 +42,7 @@ export default (store) => {
     { name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
     { name: 'user-settings', path: '/user-settings', component: UserSettings },
     { name: 'notifications', path: '/:username/notifications', component: Notifications },
-    { name: 'login', path: '/login', component: LoginForm },
+    { name: 'login', path: '/login', component: AuthForm },
     { name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) },
     { name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
     { name: 'user-search', path: '/user-search', component: UserSearch, props: (route) => ({ query: route.query.query }) },
diff --git a/src/components/auth_form/auth_form.js b/src/components/auth_form/auth_form.js
new file mode 100644
index 00000000..e9a6e2d5
--- /dev/null
+++ b/src/components/auth_form/auth_form.js
@@ -0,0 +1,26 @@
+import LoginForm from '../login_form/login_form.vue'
+import MFARecoveryForm from '../mfa_form/recovery_form.vue'
+import MFATOTPForm from '../mfa_form/totp_form.vue'
+import { mapGetters } from 'vuex'
+
+const AuthForm = {
+  name: 'AuthForm',
+  render (createElement) {
+    return createElement('component', { is: this.authForm })
+  },
+  computed: {
+    authForm () {
+      if (this.requiredTOTP) { return 'MFATOTPForm' }
+      if (this.requiredRecovery) { return 'MFARecoveryForm' }
+      return 'LoginForm'
+    },
+    ...mapGetters('authFlow', ['requiredTOTP', 'requiredRecovery'])
+  },
+  components: {
+    MFARecoveryForm,
+    MFATOTPForm,
+    LoginForm
+  }
+}
+
+export default AuthForm
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index dc917e47..1119754e 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -1,28 +1,44 @@
+import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
 import oauthApi from '../../services/new_api/oauth.js'
+
 const LoginForm = {
   data: () => ({
     user: {},
-    authError: false
+    error: false
   }),
   computed: {
-    loginMethod () { return this.$store.state.instance.loginMethod },
-    loggingIn () { return this.$store.state.users.loggingIn },
-    registrationOpen () { return this.$store.state.instance.registrationOpen }
+    isPasswordAuth () { return this.requiredPassword },
+    isTokenAuth () { return this.requiredToken },
+    ...mapState({
+      registrationOpen: state => state.instance.registrationOpen,
+      instance: state => state.instance,
+      loggingIn: state => state.users.loggingIn,
+      oauth: state => state.oauth
+    }),
+    ...mapGetters(
+      'authFlow', ['requiredPassword', 'requiredToken', 'requiredMFA']
+    )
   },
   methods: {
-    oAuthLogin () {
+    ...mapMutations('authFlow', ['requireMFA']),
+    ...mapActions({ login: 'authFlow/login' }),
+    submit () {
+      this.isTokenMethod ? this.submitToken() : this.submitPassword()
+    },
+    submitToken () {
       oauthApi.login({
-        oauth: this.$store.state.oauth,
-        instance: this.$store.state.instance.server,
+        oauth: this.oauth,
+        instance: this.instance.server,
         commit: this.$store.commit
       })
     },
-    submit () {
+    submitPassword () {
       const data = {
-        oauth: this.$store.state.oauth,
-        instance: this.$store.state.instance.server
+        oauth: this.oauth,
+        instance: this.instance.server
       }
-      this.clearError()
+      this.error = false
+
       oauthApi.getOrCreateApp(data).then((app) => {
         oauthApi.getTokenWithCredentials(
           {
@@ -31,24 +47,27 @@ const LoginForm = {
             username: this.user.username,
             password: this.user.password
           }
-        ).then(async (result) => {
+        ).then((result) => {
           if (result.error) {
-            this.authError = result.error
-            this.user.password = ''
+            if (result.error === 'mfa_required') {
+              this.requireMFA({app: app, settings: result})
+            } else {
+              this.error = result.error
+              this.focusOnPasswordInput()
+            }
             return
           }
-          this.$store.commit('setToken', result.access_token)
-          try {
-            await this.$store.dispatch('loginUser', result.access_token)
+          this.login(result).then(() => {
             this.$router.push({name: 'friends'})
-          } catch (e) {
-            console.log(e)
-          }
+          })
         })
       })
     },
-    clearError () {
-      this.authError = false
+    clearError () { this.error = false },
+    focusOnPasswordInput () {
+      let passwordInput = this.$refs.passwordInput
+      passwordInput.focus()
+      passwordInput.setSelectionRange(0, passwordInput.value.length)
     }
   }
 }
diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue
index c6be2e00..a2c5cf8f 100644
--- a/src/components/login_form/login_form.vue
+++ b/src/components/login_form/login_form.vue
@@ -1,47 +1,53 @@
 <template>
-  <div class="login panel panel-default">
-    <!-- Default panel contents -->
-    <div class="panel-heading">
-      {{$t('login.login')}}
-    </div>
-    <div class="panel-body">
-      <form v-if="loginMethod == 'password'" v-on:submit.prevent='submit(user)' class='login-form'>
+<div class="login panel panel-default">
+  <!-- Default panel contents -->
+
+  <div class="panel-heading">{{$t('login.login')}}</div>
+
+  <div class="panel-body">
+    <form class='login-form' @submit.prevent='submit'>
+      <template v-if="isPasswordAuth">
         <div class='form-group'>
           <label for='username'>{{$t('login.username')}}</label>
-          <input :disabled="loggingIn" v-model='user.username' class='form-control' id='username' v-bind:placeholder="$t('login.placeholder')">
+          <input :disabled="loggingIn" v-model='user.username'
+                 class='form-control' id='username'
+                 :placeholder="$t('login.placeholder')">
         </div>
         <div class='form-group'>
           <label for='password'>{{$t('login.password')}}</label>
-          <input :disabled="loggingIn" v-model='user.password' class='form-control' id='password' type='password'>
+          <input :disabled="loggingIn" v-model='user.password'
+                 ref='passwordInput' class='form-control' id='password' type='password'>
         </div>
-        <div class='form-group'>
-          <div class='login-bottom'>
-            <div><router-link :to="{name: 'registration'}" v-if='registrationOpen' class='register'>{{$t('login.register')}}</router-link></div>
-            <button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button>
-          </div>
-        </div>
-      </form>
+      </template>
 
-      <form v-if="loginMethod == 'token'" v-on:submit.prevent='oAuthLogin'  class="login-form">
-        <div class="form-group">
-          <p>{{$t('login.description')}}</p>
-        </div>
-        <div class='form-group'>
-          <div class='login-bottom'>
-            <div><router-link :to="{name: 'registration'}" v-if='registrationOpen' class='register'>{{$t('login.register')}}</router-link></div>
-            <button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button>
+      <div class="form-group" v-if="isTokenAuth">
+        <p>{{$t('login.description')}}</p>
+      </div>
+
+      <div class='form-group'>
+        <div class='login-bottom'>
+          <div>
+            <router-link :to="{name: 'registration'}"
+                         v-if='registrationOpen'
+                         class='register'>
+              {{$t('login.register')}}
+            </router-link>
           </div>
-        </div>
-      </form>
-      
-      <div v-if="authError" class='form-group'>
-        <div class='alert error'>
-          {{authError}}
-          <i class="button-icon icon-cancel" @click="clearError"></i>
+          <button :disabled="loggingIn" type='submit' class='btn btn-default'>
+            {{$t('login.login')}}
+          </button>
         </div>
       </div>
+    </form>
+  </div>
+
+  <div v-if="error" class='form-group'>
+    <div class='alert error'>
+      {{error}}
+      <i class="button-icon icon-cancel" @click="clearError"></i>
     </div>
   </div>
+</div>
 </template>
 
 <script src="./login_form.js" ></script>
diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js
new file mode 100644
index 00000000..fbe9b437
--- /dev/null
+++ b/src/components/mfa_form/recovery_form.js
@@ -0,0 +1,41 @@
+import mfaApi from '../../services/new_api/mfa.js'
+import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
+
+export default {
+  data: () => ({
+    code: null,
+    error: false
+  }),
+  computed: {
+    ...mapGetters({
+      authApp: 'authFlow/app',
+      authSettings: 'authFlow/settings'
+    }),
+    ...mapState({ instance: 'instance' })
+  },
+  methods: {
+    ...mapMutations('authFlow', ['requireTOTP', 'abortMFA']),
+    ...mapActions({ login: 'authFlow/login' }),
+    clearError () { this.error = false },
+    submit () {
+      const data = {
+        app: this.authApp,
+        instance: this.instance.server,
+        mfaToken: this.authSettings.mfa_token,
+        code: this.code
+      }
+
+      mfaApi.verifyRecoveryCode(data).then((result) => {
+        if (result.error) {
+          this.error = result.error
+          this.code = null
+          return
+        }
+
+        this.login(result).then(() => {
+          this.$router.push({name: 'friends'})
+        })
+      })
+    }
+  }
+}
diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue
new file mode 100644
index 00000000..e0e2d65b
--- /dev/null
+++ b/src/components/mfa_form/recovery_form.vue
@@ -0,0 +1,42 @@
+<template>
+<div class="login panel panel-default">
+  <!-- Default panel contents -->
+
+  <div class="panel-heading">{{$t('login.heading.recovery')}}</div>
+
+  <div class="panel-body">
+    <form class='login-form' @submit.prevent='submit'>
+      <div class='form-group'>
+        <label for='code'>{{$t('login.recovery_code')}}</label>
+        <input v-model='code' class='form-control' id='code'>
+      </div>
+
+      <div class='form-group'>
+        <div class='login-bottom'>
+          <div>
+            <a href="#" @click.prevent="requireTOTP">
+              {{$t('login.enter_two_factor_code')}}
+            </a>
+            <br />
+            <a href="#" @click.prevent="abortMFA">
+              {{$t('general.cancel')}}
+            </a>
+          </div>
+          <button type='submit' class='btn btn-default'>
+            {{$t('general.verify')}}
+          </button>
+        </div>
+      </div>
+
+    </form>
+  </div>
+
+  <div v-if="error" class='form-group'>
+    <div class='alert error'>
+      {{error}}
+      <i class="button-icon icon-cancel" @click="clearError"></i>
+    </div>
+  </div>
+</div>
+</template>
+<script src="./recovery_form.js" ></script>
diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js
new file mode 100644
index 00000000..6c94fe52
--- /dev/null
+++ b/src/components/mfa_form/totp_form.js
@@ -0,0 +1,40 @@
+import mfaApi from '../../services/new_api/mfa.js'
+import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
+export default {
+  data: () => ({
+    code: null,
+    error: false
+  }),
+  computed: {
+    ...mapGetters({
+      authApp: 'authFlow/app',
+      authSettings: 'authFlow/settings'
+    }),
+    ...mapState({ instance: 'instance' })
+  },
+  methods: {
+    ...mapMutations('authFlow', ['requireRecovery', 'abortMFA']),
+    ...mapActions({ login: 'authFlow/login' }),
+    clearError () { this.error = false },
+    submit () {
+      const data = {
+        app: this.authApp,
+        instance: this.instance.server,
+        mfaToken: this.authSettings.mfa_token,
+        code: this.code
+      }
+
+      mfaApi.verifyOTPCode(data).then((result) => {
+        if (result.error) {
+          this.error = result.error
+          this.code = null
+          return
+        }
+
+        this.login(result).then(() => {
+          this.$router.push({name: 'friends'})
+        })
+      })
+    }
+  }
+}
diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue
new file mode 100644
index 00000000..c547785e
--- /dev/null
+++ b/src/components/mfa_form/totp_form.vue
@@ -0,0 +1,45 @@
+<template>
+<div class="login panel panel-default">
+  <!-- Default panel contents -->
+
+  <div class="panel-heading">
+    {{$t('login.heading.totp')}}
+  </div>
+
+  <div class="panel-body">
+    <form class='login-form' @submit.prevent='submit'>
+      <div class='form-group'>
+        <label for='code'>
+          {{$t('login.authentication_code')}}
+        </label>
+        <input v-model='code' class='form-control' id='code'>
+      </div>
+
+      <div class='form-group'>
+        <div class='login-bottom'>
+          <div>
+            <a href="#" @click.prevent="requireRecovery">
+              {{$t('login.enter_recovery_code')}}
+            </a>
+            <br />
+            <a href="#" @click.prevent="abortMFA">
+              {{$t('general.cancel')}}
+            </a>
+          </div>
+          <button type='submit' class='btn btn-default'>
+            {{$t('general.verify')}}
+          </button>
+        </div>
+      </div>
+    </form>
+  </div>
+
+  <div v-if="error" class='form-group'>
+    <div class='alert error'>
+      {{error}}
+      <i class="button-icon icon-cancel" @click="clearError"></i>
+    </div>
+  </div>
+</div>
+</template>
+<script src="./totp_form.js"></script>
diff --git a/src/components/user_panel/user_panel.js b/src/components/user_panel/user_panel.js
index d4478290..c2f51eb6 100644
--- a/src/components/user_panel/user_panel.js
+++ b/src/components/user_panel/user_panel.js
@@ -1,13 +1,15 @@
-import LoginForm from '../login_form/login_form.vue'
+import AuthForm from '../auth_form/auth_form.js'
 import PostStatusForm from '../post_status_form/post_status_form.vue'
 import UserCard from '../user_card/user_card.vue'
+import { mapState } from 'vuex'
 
 const UserPanel = {
   computed: {
-    user () { return this.$store.state.users.currentUser }
+    signedIn () { return this.user },
+    ...mapState({ user: state => state.users.currentUser })
   },
   components: {
-    LoginForm,
+    AuthForm,
     PostStatusForm,
     UserCard
   }
diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
index 8310f30e..37e28ca5 100644
--- a/src/components/user_panel/user_panel.vue
+++ b/src/components/user_panel/user_panel.vue
@@ -1,13 +1,20 @@
 <template>
   <div class="user-panel">
-    <div v-if='user' class="panel panel-default" style="overflow: visible;">
+
+    <div v-if="signedIn" key="user-panel" class="panel panel-default signed-in">
       <UserCard :user="user" :hideBio="true" rounded="top"/>
       <div class="panel-footer">
         <post-status-form v-if='user'></post-status-form>
       </div>
     </div>
-    <login-form v-if='!user'></login-form>
+    <auth-form v-else key="user-panel"/>
   </div>
 </template>
 
 <script src="./user_panel.js"></script>
+
+<style lang="scss">
+.user-panel .signed-in {
+  overflow: visible;
+}
+</style>
diff --git a/src/components/user_settings/confirm.js b/src/components/user_settings/confirm.js
new file mode 100644
index 00000000..0f4ddfc9
--- /dev/null
+++ b/src/components/user_settings/confirm.js
@@ -0,0 +1,9 @@
+const Confirm = {
+  props: ['disabled'],
+  data: () => ({}),
+  methods: {
+    confirm () { this.$emit('confirm') },
+    cancel () { this.$emit('cancel') }
+  }
+}
+export default Confirm
diff --git a/src/components/user_settings/confirm.vue b/src/components/user_settings/confirm.vue
new file mode 100644
index 00000000..46a42e38
--- /dev/null
+++ b/src/components/user_settings/confirm.vue
@@ -0,0 +1,14 @@
+<template>
+<div>
+  <slot></slot>
+  <button class="btn btn-default" @click="confirm" :disabled="disabled">
+    {{$t('general.confirm')}}
+  </button>
+  <button class="btn btn-default" @click="cancel" :disabled="disabled">
+    {{$t('general.cancel')}}
+  </button>
+</div>
+</template>
+
+<script src="./confirm.js">
+</script>
diff --git a/src/components/user_settings/mfa.js b/src/components/user_settings/mfa.js
new file mode 100644
index 00000000..2acee862
--- /dev/null
+++ b/src/components/user_settings/mfa.js
@@ -0,0 +1,152 @@
+import RecoveryCodes from './mfa_backup_codes.vue'
+import TOTP from './mfa_totp.vue'
+import Confirm from './confirm.vue'
+import VueQrcode from '@chenfengyuan/vue-qrcode'
+import { mapState } from 'vuex'
+
+const Mfa = {
+  data: () => ({
+    settings: { // current settings of MFA
+      enabled: false,
+      totp: false
+    },
+    setupState: { // setup mfa
+      state: '', // state of setup. '' -> 'getBackupCodes' -> 'setupOTP' -> 'complete'
+      setupOTPState: '' // state of setup otp. '' -> 'prepare' -> 'confirm' -> 'complete'
+    },
+    backupCodes: {
+      getNewCodes: false,
+      inProgress: false, //  progress of fetch codes
+      codes: []
+    },
+    otpSettings: { // pre-setup setting of OTP. secret key, qrcode url.
+      provisioning_uri: '',
+      key: ''
+    },
+    currentPassword: null,
+    otpConfirmToken: null,
+    error: null,
+    readyInit: false
+  }),
+  components: {
+    'recovery-codes': RecoveryCodes,
+    'totp-item': TOTP,
+    'qrcode': VueQrcode,
+    'confirm': Confirm
+  },
+  computed: {
+    canSetupOTP () {
+      return (
+        (this.setupInProgress && this.backupCodesPrepared) ||
+          this.settings.enabled
+      ) && !this.settings.totp && !this.setupOTPInProgress
+    },
+    setupInProgress () {
+      return this.setupState.state !== '' && this.setupState.state !== 'complete'
+    },
+    setupOTPInProgress () {
+      return this.setupState.state === 'setupOTP' && !this.completedOTP
+    },
+    prepareOTP () {
+      return this.setupState.setupOTPState === 'prepare'
+    },
+    confirmOTP () {
+      return this.setupState.setupOTPState === 'confirm'
+    },
+    completedOTP () {
+      return this.setupState.setupOTPState === 'completed'
+    },
+    backupCodesPrepared () {
+      return !this.backupCodes.inProgress && this.backupCodes.codes.length > 0
+    },
+    confirmNewBackupCodes () {
+      return this.backupCodes.getNewCodes
+    },
+    ...mapState({
+      backendInteractor: (state) => state.api.backendInteractor
+    })
+  },
+
+  methods: {
+    activateOTP () {
+      if (!this.settings.enabled) {
+        this.setupState.state = 'getBackupcodes'
+        this.fetchBackupCodes()
+      }
+    },
+    fetchBackupCodes () {
+      this.backupCodes.inProgress = true
+      this.backupCodes.codes = []
+
+      return this.backendInteractor.generateMfaBackupCodes()
+        .then((res) => {
+          this.backupCodes.codes = res.codes
+          this.backupCodes.inProgress = false
+        })
+    },
+    getBackupCodes () { // get a new backup codes
+      this.backupCodes.getNewCodes = true
+    },
+    confirmBackupCodes () { // confirm getting new backup codes
+      this.fetchBackupCodes().then((res) => {
+        this.backupCodes.getNewCodes = false
+      })
+    },
+    cancelBackupCodes () { // cancel confirm form of new backup codes
+      this.backupCodes.getNewCodes = false
+    },
+
+    // Setup OTP
+    setupOTP () { // prepare setup OTP
+      this.setupState.state = 'setupOTP'
+      this.setupState.setupOTPState = 'prepare'
+      this.backendInteractor.mfaSetupOTP()
+        .then((res) => {
+          this.otpSettings = res
+          this.setupState.setupOTPState = 'confirm'
+        })
+    },
+    doConfirmOTP () {  // handler confirm enable OTP
+      this.error = null
+      this.backendInteractor.mfaConfirmOTP({
+        token: this.otpConfirmToken,
+        password: this.currentPassword
+      })
+        .then((res) => {
+          if (res.error) {
+            this.error = res.error
+            return
+          }
+          this.completeSetup()
+        })
+    },
+
+    completeSetup () {
+      this.setupState.setupOTPState = 'complete'
+      this.setupState.state = 'complete'
+      this.currentPassword = null
+      this.error = null
+      this.fetchSettings()
+    },
+    cancelSetup () { // cancel setup
+      this.setupState.setupOTPState = ''
+      this.setupState.state = ''
+      this.currentPassword = null
+      this.error = null
+    },
+    // end Setup OTP
+
+    // fetch settings from server
+    async fetchSettings () {
+      let result = await this.backendInteractor.fetchSettingsMFA()
+      this.settings = result.settings
+      return result
+    }
+  },
+  mounted () {
+    this.fetchSettings().then(() => {
+      this.readyInit = true
+    })
+  }
+}
+export default Mfa
diff --git a/src/components/user_settings/mfa.vue b/src/components/user_settings/mfa.vue
new file mode 100644
index 00000000..ded426dd
--- /dev/null
+++ b/src/components/user_settings/mfa.vue
@@ -0,0 +1,121 @@
+<template>
+<div class="setting-item mfa-settings" v-if="readyInit">
+
+  <div class="mfa-heading">
+    <h2>{{$t('settings.mfa.title')}}</h2>
+  </div>
+
+  <div>
+    <div class="setting-item" v-if="!setupInProgress">
+      <!-- Enabled methods -->
+      <h3>{{$t('settings.mfa.authentication_methods')}}</h3>
+      <totp-item :settings="settings" @deactivate="fetchSettings" @activate="activateOTP"/>
+      <br />
+
+      <div v-if="settings.enabled"> <!-- backup codes block-->
+        <recovery-codes :backup-codes="backupCodes" v-if="!confirmNewBackupCodes" />
+        <button class="btn btn-default" @click="getBackupCodes" v-if="!confirmNewBackupCodes">
+          {{$t('settings.mfa.generate_new_recovery_codes')}}
+        </button>
+
+        <div v-if="confirmNewBackupCodes">
+          <confirm @confirm="confirmBackupCodes" @cancel="cancelBackupCodes"
+                   :disabled="backupCodes.inProgress">
+            <p class="warning">{{$t('settings.mfa.warning_of_generate_new_codes')}}</p>
+          </confirm>
+        </div>
+      </div>
+    </div>
+
+    <div v-if="setupInProgress"> <!-- setup block-->
+
+      <h3>{{$t('settings.mfa.setup_otp')}}</h3>
+
+      <recovery-codes :backup-codes="backupCodes" v-if="!setupOTPInProgress"/>
+
+
+      <button class="btn btn-default" @click="cancelSetup" v-if="canSetupOTP">
+        {{$t('general.cancel')}}
+      </button>
+
+      <button class="btn btn-default" v-if="canSetupOTP" @click="setupOTP">
+        {{$t('settings.mfa.setup_otp')}}
+      </button>
+
+      <template v-if="setupOTPInProgress">
+        <i v-if="prepareOTP">{{$t('settings.mfa.wait_pre_setup_otp')}}</i>
+
+        <div v-if="confirmOTP">
+          <div class="setup-otp">
+            <div class="qr-code">
+              <h4>{{$t('settings.mfa.scan.title')}}</h4>
+              <p>{{$t('settings.mfa.scan.desc')}}</p>
+              <qrcode :value="otpSettings.provisioning_uri" :options="{ width: 200 }"></qrcode>
+              <p>
+                {{$t('settings.mfa.scan.secret_code')}}:
+                {{otpSettings.key}}
+              </p>
+            </div>
+
+            <div class="verify">
+              <h4>{{$t('general.verify')}}</h4>
+              <p>{{$t('settings.mfa.verify.desc')}}</p>
+              <input type="text" v-model="otpConfirmToken">
+
+              <p>{{$t('settings.enter_current_password_to_confirm')}}:</p>
+              <input type="password" v-model="currentPassword">
+              <div class="confirm-otp-actions">
+                <button class="btn btn-default" @click="doConfirmOTP">
+                  {{$t('settings.mfa.confirm_and_enable')}}
+                </button>
+                <button class="btn btn-default" @click="cancelSetup">
+                  {{$t('general.cancel')}}
+                </button>
+              </div>
+              <div class="alert error" v-if="error">{{error}}</div>
+            </div>
+          </div>
+        </div>
+      </template>
+    </div>
+
+  </div>
+</div>
+</template>
+
+<script src="./mfa.js"></script>
+<style lang="scss">
+@import '../../_variables.scss';
+.warning {
+  color: $fallback--cOrange;
+  color: var(--cOrange, $fallback--cOrange);
+}
+.mfa-settings {
+  .mfa-heading, .method-item {
+    overflow: hidden;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    align-items: baseline;
+  }
+
+  .setup-otp {
+    display: flex;
+    justify-content: center;
+    flex-wrap: wrap;
+    .qr-code {
+      flex: 1;
+      padding-right: 10px;
+    }
+    .verify { flex: 1; }
+    .error { margin: 4px 0 0 0; }
+    .confirm-otp-actions {
+      button {
+        width: 15em;
+        margin-top: 5px;
+      }
+
+    }
+  }
+}
+</style>
diff --git a/src/components/user_settings/mfa_backup_codes.js b/src/components/user_settings/mfa_backup_codes.js
new file mode 100644
index 00000000..f0a984ec
--- /dev/null
+++ b/src/components/user_settings/mfa_backup_codes.js
@@ -0,0 +1,17 @@
+export default {
+  props: {
+    backupCodes: {
+      type: Object,
+      default: () => ({
+        inProgress: false,
+        codes: []
+      })
+    }
+  },
+  data: () => ({}),
+  computed: {
+    inProgress () { return this.backupCodes.inProgress },
+    ready () { return this.backupCodes.codes.length > 0 },
+    displayTitle () { return this.inProgress || this.ready }
+  }
+}
diff --git a/src/components/user_settings/mfa_backup_codes.vue b/src/components/user_settings/mfa_backup_codes.vue
new file mode 100644
index 00000000..c275bd63
--- /dev/null
+++ b/src/components/user_settings/mfa_backup_codes.vue
@@ -0,0 +1,22 @@
+<template>
+<div>
+  <h4 v-if="displayTitle">{{$t('settings.mfa.recovery_codes')}}</h4>
+  <i v-if="inProgress">{{$t('settings.mfa.waiting_a_recovery_codes')}}</i>
+  <template v-if="ready">
+    <p class="alert warning">{{$t('settings.mfa.recovery_codes_warning')}}</p>
+    <ul class="backup-codes"><li v-for="code in backupCodes.codes">{{code}}</li></ul>
+  </template>
+</div>
+</template>
+<script src="./mfa_backup_codes.js"></script>
+<style lang="scss">
+@import '../../_variables.scss';
+
+.warning {
+  color: $fallback--cOrange;
+  color: var(--cOrange, $fallback--cOrange);
+}
+.backup-codes {
+  font-family: var(--postCodeFont, monospace);
+}
+</style>
diff --git a/src/components/user_settings/mfa_totp.js b/src/components/user_settings/mfa_totp.js
new file mode 100644
index 00000000..8408d8e9
--- /dev/null
+++ b/src/components/user_settings/mfa_totp.js
@@ -0,0 +1,49 @@
+import Confirm from './confirm.vue'
+import { mapState } from 'vuex'
+
+export default {
+  props: ['settings'],
+  data: () => ({
+    error: false,
+    currentPassword: '',
+    deactivate: false,
+    inProgress: false // progress peform request to disable otp method
+  }),
+  components: {
+    'confirm': Confirm
+  },
+  computed: {
+    isActivated () {
+      return this.settings.totp
+    },
+    ...mapState({
+      backendInteractor: (state) => state.api.backendInteractor
+    })
+  },
+  methods: {
+    doActivate () {
+      this.$emit('activate')
+    },
+    cancelDeactivate () { this.deactivate = false },
+    doDeactivate () {
+      this.error = null
+      this.deactivate = true
+    },
+    confirmDeactivate () { // confirm deactivate TOTP method
+      this.error = null
+      this.inProgress = true
+      this.backendInteractor.mfaDisableOTP({
+        password: this.currentPassword
+      })
+        .then((res) => {
+          this.inProgress = false
+          if (res.error) {
+            this.error = res.error
+            return
+          }
+          this.deactivate = false
+          this.$emit('deactivate')
+        })
+    }
+  }
+}
diff --git a/src/components/user_settings/mfa_totp.vue b/src/components/user_settings/mfa_totp.vue
new file mode 100644
index 00000000..6b73c8f4
--- /dev/null
+++ b/src/components/user_settings/mfa_totp.vue
@@ -0,0 +1,23 @@
+<template>
+<div>
+  <div class="method-item">
+    <strong>{{$t('settings.mfa.otp')}}</strong>
+    <button class="btn btn-default" v-if="!isActivated" @click="doActivate">
+      {{$t('general.enable')}}
+    </button>
+
+    <button class="btn btn-default" :disabled="deactivate" @click="doDeactivate"
+            v-if="isActivated">
+      {{$t('general.disable')}}
+    </button>
+  </div>
+
+  <confirm @confirm="confirmDeactivate" @cancel="cancelDeactivate"
+           :disabled="inProgress" v-if="deactivate">
+    {{$t('settings.enter_current_password_to_confirm')}}:
+    <input type="password" v-model="currentPassword">
+  </confirm>
+  <div class="alert error" v-if="error">{{error}}</div>
+</div>
+</template>
+<script src="./mfa_totp.js"></script>
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index ae36e5e8..69505806 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -17,6 +17,7 @@ import Importer from '../importer/importer.vue'
 import Exporter from '../exporter/exporter.vue'
 import withSubscription from '../../hocs/with_subscription/with_subscription'
 import userSearchApi from '../../services/new_api/user_search.js'
+import Mfa from './mfa.vue'
 
 const BlockList = withSubscription({
   fetch: (props, $store) => $store.dispatch('fetchBlocks'),
@@ -75,7 +76,8 @@ const UserSettings = {
     MuteCard,
     ProgressButton,
     Importer,
-    Exporter
+    Exporter,
+    Mfa
   },
   computed: {
     user () {
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 15a5428c..bbe41f11 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -152,7 +152,7 @@
               </tbody>
             </table>
           </div>
-
+          <mfa />
           <div class="setting-item">
             <h2>{{$t('settings.delete_account')}}</h2>
             <p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 2b5c5468..a29f394b 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -27,7 +27,11 @@
     "optional": "optional",
     "show_more": "Show more",
     "show_less": "Show less",
-    "cancel": "Cancel"
+    "cancel": "Cancel",
+    "disable": "Disable",
+    "enable": "Enable",
+    "confirm": "Confirm",
+    "verify": "Verify"
   },
   "image_cropper": {
     "crop_picture": "Crop picture",
@@ -48,7 +52,15 @@
     "placeholder": "e.g. lain",
     "register": "Register",
     "username": "Username",
-    "hint": "Log in to join the discussion"
+    "hint": "Log in to join the discussion",
+    "authentication_code": "Authentication code",
+    "enter_recovery_code": "Enter a recovery code",
+    "enter_two_factor_code": "Enter a two-factor code",
+    "recovery_code": "Recovery code",
+    "heading" : {
+      "totp" : "Two-factor authentication",
+      "recovery" : "Two-factor recovery"
+    }
   },
   "media_modal": {
     "previous": "Previous",
@@ -138,6 +150,29 @@
   },
   "settings": {
     "app_name": "App name",
+    "security": "Security",
+    "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
+    "mfa": {
+      "otp" : "OTP",
+      "setup_otp" : "Setup OTP",
+      "wait_pre_setup_otp" : "presetting OTP",
+      "confirm_and_enable" : "Confirm & enable OTP",
+      "title": "Two-factor Authentication",
+      "generate_new_recovery_codes" : "Generate new recovery codes",
+      "warning_of_generate_new_codes" : "When you generate new recovery codes, your old codes won’t work anymore.",
+      "recovery_codes" : "Recovery codes.",
+      "waiting_a_recovery_codes": "Receiving backup codes...",
+      "recovery_codes_warning" : "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
+      "authentication_methods" : "Authentication methods",
+      "scan": {
+        "title": "Scan",
+        "desc": "Using your two-factor app, scan this QR code or enter text key:",
+        "secret_code": "Key"
+      },
+      "verify": {
+        "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
+      }
+    },
     "attachmentRadius": "Attachments",
     "attachments": "Attachments",
     "autoload": "Enable automatic loading when scrolled to the bottom",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 7a22194a..d24ef0cb 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -9,7 +9,11 @@
   "general": {
     "apply": "Применить",
     "submit": "Отправить",
-    "cancel": "Отмена"
+    "cancel": "Отмена",
+    "disable": "Оключить",
+    "enable": "Включить",
+    "confirm": "Подтвердить",
+    "verify": "Проверить"
   },
   "login": {
     "login": "Войти",
@@ -17,7 +21,15 @@
     "password": "Пароль",
     "placeholder": "e.c. lain",
     "register": "Зарегистрироваться",
-    "username": "Имя пользователя"
+    "username": "Имя пользователя",
+    "authentication_code": "Код аутентификации",
+    "enter_recovery_code": "Ввести код восстановления",
+    "enter_two_factor_code": "Ввести код аутентификации",
+    "recovery_code": "Код восстановления",
+    "heading" : {
+      "TotpForm" : "Двухфакторная аутентификация",
+      "RecoveryForm" : "Two-factor recovery"
+    }
   },
   "nav": {
     "back": "Назад",
@@ -79,6 +91,28 @@
     }
   },
   "settings": {
+    "enter_current_password_to_confirm": "Введите свой текущий пароль",
+    "mfa": {
+      "otp" : "OTP",
+      "setup_otp" : "Настройка OTP",
+      "wait_pre_setup_otp" : "предварительная настройка OTP",
+      "confirm_and_enable" : "Подтвердить и включить OTP",
+      "title": "Двухфакторная аутентификация",
+      "generate_new_recovery_codes" : "Получить новые коды востановления",
+      "warning_of_generate_new_codes" : "После получения новых кодов восстановления, старые больше не будут работать.",
+      "recovery_codes" : "Коды восстановления.",
+      "waiting_a_recovery_codes": "Получение кодов восстановления ...",
+      "recovery_codes_warning" : "Запишите эти коды и держите в безопасном месте - иначе вы их больше не увидите. Если вы потеряете доступ к OTP приложению - без резервных кодов вы больше не сможете залогиниться.",
+      "authentication_methods" : "Методы аутентификации",
+      "scan": {
+        "title": "Сканирование",
+        "desc": "Используйте приложение для двухэтапной аутентификации для сканирования этого QR-код или введите текстовый ключ:",
+        "secret_code": "Ключ"
+      },
+      "verify": {
+        "desc": "Чтобы включить двухэтапную аутентификации, введите код из вашего приложение для двухэтапной аутентификации:"
+      }
+    },
     "attachmentRadius": "Прикреплённые файлы",
     "attachments": "Вложения",
     "autoload": "Включить автоматическую загрузку при прокрутке вниз",
diff --git a/src/main.js b/src/main.js
index e12db6a3..5758c7bd 100644
--- a/src/main.js
+++ b/src/main.js
@@ -10,6 +10,7 @@ import apiModule from './modules/api.js'
 import configModule from './modules/config.js'
 import chatModule from './modules/chat.js'
 import oauthModule from './modules/oauth.js'
+import authFlowModule from './modules/auth_flow.js'
 import mediaViewerModule from './modules/media_viewer.js'
 import oauthTokensModule from './modules/oauth_tokens.js'
 import reportsModule from './modules/reports.js'
@@ -77,6 +78,7 @@ const persistedStateOptions = {
       config: configModule,
       chat: chatModule,
       oauth: oauthModule,
+      authFlow: authFlowModule,
       mediaViewer: mediaViewerModule,
       oauthTokens: oauthTokensModule,
       reports: reportsModule
diff --git a/src/modules/auth_flow.js b/src/modules/auth_flow.js
new file mode 100644
index 00000000..86328cf3
--- /dev/null
+++ b/src/modules/auth_flow.js
@@ -0,0 +1,89 @@
+const PASSWORD_STRATEGY = 'password'
+const TOKEN_STRATEGY = 'token'
+
+// MFA strategies
+const TOTP_STRATEGY = 'totp'
+const RECOVERY_STRATEGY = 'recovery'
+
+// initial state
+const state = {
+  app: null,
+  settings: {},
+  strategy: PASSWORD_STRATEGY,
+  initStrategy: PASSWORD_STRATEGY // default strategy from config
+}
+
+const resetState = (state) => {
+  state.strategy = state.initStrategy
+  state.settings = {}
+  state.app = null
+}
+
+// getters
+const getters = {
+  app: (state, getters) => {
+    return state.app
+  },
+  settings: (state, getters) => {
+    return state.settings
+  },
+  requiredPassword: (state, getters, rootState) => {
+    return state.strategy === PASSWORD_STRATEGY
+  },
+  requiredToken: (state, getters, rootState) => {
+    return state.strategy === TOKEN_STRATEGY
+  },
+  requiredTOTP: (state, getters, rootState) => {
+    return state.strategy === TOTP_STRATEGY
+  },
+  requiredRecovery: (state, getters, rootState) => {
+    return state.strategy === RECOVERY_STRATEGY
+  }
+}
+
+// mutations
+const mutations = {
+  setInitialStrategy (state, strategy) {
+    if (strategy) {
+      state.initStrategy = strategy
+      state.strategy = strategy
+    }
+  },
+  requirePassword (state) {
+    state.strategy = PASSWORD_STRATEGY
+  },
+  requireToken (state) {
+    state.strategy = TOKEN_STRATEGY
+  },
+  requireMFA (state, {app, settings}) {
+    state.settings = settings
+    state.app = app
+    state.strategy = TOTP_STRATEGY // default strategy of MFA
+  },
+  requireRecovery (state) {
+    state.strategy = RECOVERY_STRATEGY
+  },
+  requireTOTP (state) {
+    state.strategy = TOTP_STRATEGY
+  },
+  abortMFA (state) {
+    resetState(state)
+  }
+}
+
+// actions
+const actions = {
+  async login ({state, dispatch, commit}, {access_token}) {
+    commit('setToken', access_token, { root: true })
+    await dispatch('loginUser', access_token, { root: true })
+    resetState(state)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  getters,
+  mutations,
+  actions
+}
diff --git a/src/modules/instance.js b/src/modules/instance.js
index d4185f6a..fc4578ed 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -27,7 +27,6 @@ const defaultState = {
   scopeCopy: true,
   subjectLineBehavior: 'email',
   postContentType: 'text/plain',
-  loginMethod: 'password',
   nsfwCensorImage: undefined,
   vapidPublicKey: undefined,
   noAttachmentLinks: false,
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 04135a48..ac151a0b 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -17,6 +17,13 @@ const ADMIN_USERS_URL = '/api/pleroma/admin/users'
 const SUGGESTIONS_URL = '/api/v1/suggestions'
 const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
 
+const MFA_SETTINGS_URL = '/api/pleroma/profile/mfa'
+const MFA_BACKUP_CODES_URL = '/api/pleroma/profile/mfa/backup_codes'
+
+const MFA_SETUP_OTP_URL = '/api/pleroma/profile/mfa/setup/totp'
+const MFA_CONFIRM_OTP_URL = '/api/pleroma/profile/mfa/confirm/totp'
+const MFA_DISABLE_OTP_URL = '/api/pleroma/profile/mfa/totp'
+
 const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
 const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
@@ -649,6 +656,51 @@ const changePassword = ({credentials, password, newPassword, newPasswordConfirma
     .then((response) => response.json())
 }
 
+const settingsMFA = ({credentials}) => {
+  return fetch(MFA_SETTINGS_URL, {
+    headers: authHeaders(credentials),
+    method: 'GET'
+  }).then((data) => data.json())
+}
+
+const mfaDisableOTP = ({credentials, password}) => {
+  const form = new FormData()
+
+  form.append('password', password)
+
+  return fetch(MFA_DISABLE_OTP_URL, {
+    body: form,
+    method: 'DELETE',
+    headers: authHeaders(credentials)
+  })
+    .then((response) => response.json())
+}
+
+const mfaConfirmOTP = ({credentials, password, token}) => {
+  const form = new FormData()
+
+  form.append('password', password)
+  form.append('code', token)
+
+  return fetch(MFA_CONFIRM_OTP_URL, {
+    body: form,
+    headers: authHeaders(credentials),
+    method: 'POST'
+  }).then((data) => data.json())
+}
+const mfaSetupOTP = ({credentials}) => {
+  return fetch(MFA_SETUP_OTP_URL, {
+    headers: authHeaders(credentials),
+    method: 'GET'
+  }).then((data) => data.json())
+}
+const generateMfaBackupCodes = ({credentials}) => {
+  return fetch(MFA_BACKUP_CODES_URL, {
+    headers: authHeaders(credentials),
+    method: 'GET'
+  }).then((data) => data.json())
+}
+
 const fetchMutes = ({credentials}) => {
   return promisedRequest({ url: MASTODON_USER_MUTES_URL, credentials })
     .then((users) => users.map(parseUser))
@@ -776,6 +828,11 @@ const apiService = {
   importFollows,
   deleteAccount,
   changePassword,
+  settingsMFA,
+  mfaDisableOTP,
+  generateMfaBackupCodes,
+  mfaSetupOTP,
+  mfaConfirmOTP,
   fetchFollowRequests,
   approveUser,
   denyUser,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 36152429..674b7516 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -116,6 +116,12 @@ const backendInteractorService = (credentials) => {
   const deleteAccount = ({password}) => apiService.deleteAccount({credentials, password})
   const changePassword = ({password, newPassword, newPasswordConfirmation}) => apiService.changePassword({credentials, password, newPassword, newPasswordConfirmation})
 
+  const fetchSettingsMFA = () => apiService.settingsMFA({credentials})
+  const generateMfaBackupCodes = () => apiService.generateMfaBackupCodes({credentials})
+  const mfaSetupOTP = () => apiService.mfaSetupOTP({credentials})
+  const mfaConfirmOTP = ({password, token}) => apiService.mfaConfirmOTP({credentials, password, token})
+  const mfaDisableOTP = ({password}) => apiService.mfaDisableOTP({credentials, password})
+
   const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({id})
   const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({id})
   const reportUser = (params) => apiService.reportUser({credentials, ...params})
@@ -166,6 +172,11 @@ const backendInteractorService = (credentials) => {
     importFollows,
     deleteAccount,
     changePassword,
+    fetchSettingsMFA,
+    generateMfaBackupCodes,
+    mfaSetupOTP,
+    mfaConfirmOTP,
+    mfaDisableOTP,
     fetchFollowRequests,
     approveUser,
     denyUser,
diff --git a/src/services/new_api/mfa.js b/src/services/new_api/mfa.js
new file mode 100644
index 00000000..ddf90e6b
--- /dev/null
+++ b/src/services/new_api/mfa.js
@@ -0,0 +1,38 @@
+const verifyOTPCode = ({app, instance, mfaToken, code}) => {
+  const url = `${instance}/oauth/mfa/challenge`
+  const form = new window.FormData()
+
+  form.append('client_id', app.client_id)
+  form.append('client_secret', app.client_secret)
+  form.append('mfa_token', mfaToken)
+  form.append('code', code)
+  form.append('challenge_type', 'totp')
+
+  return window.fetch(url, {
+    method: 'POST',
+    body: form
+  }).then((data) => data.json())
+}
+
+const verifyRecoveryCode = ({app, instance, mfaToken, code}) => {
+  const url = `${instance}/oauth/mfa/challenge`
+  const form = new window.FormData()
+
+  form.append('client_id', app.client_id)
+  form.append('client_secret', app.client_secret)
+  form.append('mfa_token', mfaToken)
+  form.append('code', code)
+  form.append('challenge_type', 'recovery')
+
+  return window.fetch(url, {
+    method: 'POST',
+    body: form
+  }).then((data) => data.json())
+}
+
+const mfa = {
+  verifyOTPCode,
+  verifyRecoveryCode
+}
+
+export default mfa
diff --git a/src/services/new_api/oauth.js b/src/services/new_api/oauth.js
index 9e656507..35c9c236 100644
--- a/src/services/new_api/oauth.js
+++ b/src/services/new_api/oauth.js
@@ -71,12 +71,45 @@ const getToken = ({app, instance, code}) => {
     body: form
   }).then((data) => data.json())
 }
+const verifyOTPCode = ({app, instance, mfaToken, code}) => {
+  const url = `${instance}/oauth/mfa/challenge`
+  const form = new window.FormData()
+
+  form.append('client_id', app.client_id)
+  form.append('client_secret', app.client_secret)
+  form.append('mfa_token', mfaToken)
+  form.append('code', code)
+  form.append('challenge_type', 'totp')
+
+  return window.fetch(url, {
+    method: 'POST',
+    body: form
+  }).then((data) => data.json())
+}
+
+const verifyRecoveryCode = ({app, instance, mfaToken, code}) => {
+  const url = `${instance}/oauth/mfa/challenge`
+  const form = new window.FormData()
+
+  form.append('client_id', app.client_id)
+  form.append('client_secret', app.client_secret)
+  form.append('mfa_token', mfaToken)
+  form.append('code', code)
+  form.append('challenge_type', 'recovery')
+
+  return window.fetch(url, {
+    method: 'POST',
+    body: form
+  }).then((data) => data.json())
+}
 
 const oauth = {
   login,
   getToken,
   getTokenWithCredentials,
-  getOrCreateApp
+  getOrCreateApp,
+  verifyOTPCode,
+  verifyRecoveryCode
 }
 
 export default oauth
diff --git a/yarn.lock b/yarn.lock
index a91b520f..2a4de25e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -26,31 +26,33 @@
     js-tokens "^4.0.0"
 
 "@babel/polyfill@^7.0.0":
-  version "7.4.0"
-  resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.4.0.tgz#90f9d68ae34ac42ab4b4aa03151848f536960218"
-  integrity sha512-bVsjsrtsDflIHp5I6caaAa2V25Kzn50HKPL6g3X0P0ni1ks+58cPB8Mz6AOKVuRPgaVdq/OwEUc/1vKqX+Mo4A==
+  version "7.2.5"
+  resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
+  integrity sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==
   dependencies:
-    core-js "^2.6.5"
-    regenerator-runtime "^0.13.2"
+    core-js "^2.5.7"
+    regenerator-runtime "^0.12.0"
 
 "@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49":
-  version "7.4.0"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c"
-  integrity sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==
+  version "7.2.2"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
+  integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==
   dependencies:
     esutils "^2.0.2"
-    lodash "^4.17.11"
+    lodash "^4.17.10"
     to-fast-properties "^2.0.0"
 
-"@types/node@^8.0.7":
-  version "8.10.45"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.45.tgz#4c49ba34106bc7dced77ff6bae8eb6543cde8351"
-  integrity sha512-tGVTbA+i3qfXsLbq9rEq/hezaHY55QxQLeXQL2ejNgFAxxrgu8eMmYIOsRcl7hN1uTLVsKOOYacV/rcJM3sfgQ==
+"@chenfengyuan/vue-qrcode@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@chenfengyuan/vue-qrcode/-/vue-qrcode-1.0.0.tgz#07103fe2048ce0160cddde836fb5378cc7951d6f"
+  integrity sha512-Ph+W4ltPIyn9YjjITuLewhw8MeEXxJ1L5z6ZM+trmVWcKLfoIDi3yLHDPFKFjesWqsdH8irIX67PCBZ80TY8kw==
+  dependencies:
+    qrcode "^1.3.0"
 
 "@vue/test-utils@^1.0.0-beta.26":
-  version "1.0.0-beta.29"
-  resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.29.tgz#c942cf25e891cf081b6a03332b4ae1ef430726f0"
-  integrity sha512-yX4sxEIHh4M9yAbLA/ikpEnGKMNBCnoX98xE1RwxfhQVcn0MaXNSj1Qmac+ZydTj6VBSEVukchBogXBTwc+9iA==
+  version "1.0.0-beta.28"
+  resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.28.tgz#767c43413df8cde86128735e58923803e444b9a5"
+  integrity sha512-uVbFJG0g/H9hf2pgWUdhvQYItRGzQ44cMFf00wp0YEo85pxuvM9e3mx8QLQfx6R2CogxbK4CvV7qvkLblehXeQ==
   dependencies:
     dom-event-types "^1.0.0"
     lodash "^4.17.4"
@@ -211,17 +213,20 @@
   resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
   integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
 
-abbrev@1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
-  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
-
-abbrev@1.0.x:
+abbrev@1, abbrev@1.0.x:
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
   integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU=
 
-accepts@~1.3.4, accepts@~1.3.5:
+accepts@~1.3.4:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
+  integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
+  dependencies:
+    mime-types "~2.1.24"
+    negotiator "0.6.2"
+
+accepts@~1.3.5:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
   integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I=
@@ -267,7 +272,7 @@ ajv-keywords@^3.1.0:
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d"
   integrity sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==
 
-ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1:
+ajv@^6.1.0, ajv@^6.9.1:
   version "6.10.0"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
   integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==
@@ -277,6 +282,16 @@ ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ajv@^6.5.5:
+  version "6.6.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d"
+  integrity sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==
+  dependencies:
+    fast-deep-equal "^2.0.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
 alphanum-sort@^1.0.1, alphanum-sort@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@@ -394,6 +409,14 @@ array-flatten@1.1.1:
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
 
+array-includes@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
+  integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.7.0"
+
 array-slice@^0.2.3:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
@@ -470,9 +493,9 @@ assign-symbols@^1.0.0:
   integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
 
 ast-types@0.x.x:
-  version "0.12.2"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.12.2.tgz#341656049ee328ac03fc805c156b49ebab1e4462"
-  integrity sha512-8c83xDLJM/dLDyXNLiR6afRRm4dPKN6KAnKqytRK3DBJul9lA+atxdQkNDkSVPdTqea5HiRq3lnnOIZ0MBpvdg==
+  version "0.11.7"
+  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.7.tgz#f318bf44e339db6a320be0009ded64ec1471f46c"
+  integrity sha512-2mP3TwtkY/aTv5X3ZsMpNAbOnyoC/aMJwJSoaELPkHId0nSQgFcnU4dRW3isxiz7+zBexk0ym3WNVjMiQBnJSw==
 
 astral-regex@^1.0.0:
   version "1.0.0"
@@ -480,9 +503,9 @@ astral-regex@^1.0.0:
   integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
 
 async-each@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.2.tgz#8b8a7ca2a658f927e9f307d6d1a42f4199f0f735"
-  integrity sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg==
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
+  integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
 
 async-limiter@~1.0.0:
   version "1.0.0"
@@ -501,6 +524,13 @@ async@^2.0.0:
   dependencies:
     lodash "^4.17.11"
 
+async@^2.5.0:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
+  integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
+  dependencies:
+    lodash "^4.17.10"
+
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -1304,20 +1334,25 @@ big.js@^5.2.2:
   integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
 
 binary-extensions@^1.0.0:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1"
-  integrity sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14"
+  integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==
 
 blob@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
   integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
 
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.3:
+bluebird@^3.1.1, bluebird@^3.3.0:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
   integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
 
+bluebird@^3.5.3:
+  version "3.5.4"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
+  integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==
+
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@@ -1506,6 +1541,11 @@ buffer@^4.3.0:
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
+builtin-modules@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+  integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
+
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -1516,7 +1556,7 @@ bytes@3.0.0:
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
   integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
 
-cacache@^11.0.2:
+cacache@^11.3.2:
   version "11.3.2"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa"
   integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==
@@ -1582,6 +1622,18 @@ camelcase@^2.0.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
   integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
 
+camelcase@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
+  integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
+
+can-promise@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/can-promise/-/can-promise-0.0.1.tgz#7a7597ad801fb14c8b22341dfec314b6bd6ad8d3"
+  integrity sha512-gzVrHyyrvgt0YpDm7pn04MQt8gjh0ZAhN4ZDyCRtGl6YnuuK6b4aiUTD7G52r9l4YNmxfTtEscb92vxtAlL6XQ==
+  dependencies:
+    window-or-global "^1.0.1"
+
 caniuse-api@^1.5.2:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
@@ -1593,14 +1645,14 @@ caniuse-api@^1.5.2:
     lodash.uniq "^4.5.0"
 
 caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
-  version "1.0.30000951"
-  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000951.tgz#64a5d491c8164a4f81ce9f3ab906b61df9a61b1a"
-  integrity sha512-Nb8bgexT3HDxPMFOLcT5kol/dHsXpPiiFLU079+msbSBTsq9zZaCpzqDb122+9B9woZnTG5Z8lt+3adIaxKj2A==
+  version "1.0.30000928"
+  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000928.tgz#2e83d2b14276442da239511615eb7c62fed0cfa7"
+  integrity sha512-nAoeTspAEzLjqGSeibzM09WojORi08faeOOI5GBmFWC3/brydovb9lYJWM+p48rEQsdevfpufK58gPiDtwOWKw==
 
 caniuse-lite@^1.0.30000844:
-  version "1.0.30000951"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000951.tgz#c7c2fd4d71080284c8677dd410368df8d83688fe"
-  integrity sha512-eRhP+nQ6YUkIcNQ6hnvdhMkdc7n3zadog0KXNRxAZTT2kHjUb1yGn71OrPhSn8MOvlX97g5CR97kGVj8fMsXWg==
+  version "1.0.30000928"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000928.tgz#805e828dc72b06498e3683a32e61c7507fd67b88"
+  integrity sha512-aSpMWRXL6ZXNnzm8hgE4QDLibG5pVJ2Ujzsuj3icazlIkxXkPXtL+BWnMx6FBkWmkZgBHGUxPZQvrbRw2ZTxhg==
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -1659,9 +1711,9 @@ chardet@^0.7.0:
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
 
 chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d"
-  integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==
+  version "2.1.6"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
+  integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==
   dependencies:
     anymatch "^2.0.0"
     async-each "^1.0.1"
@@ -1695,9 +1747,9 @@ chrome-trace-event@^1.0.0:
     tslib "^1.9.0"
 
 chromedriver@^2.21.2:
-  version "2.46.0"
-  resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-2.46.0.tgz#3d78e7eb9bb65dd804fe327a6bf76fced12be053"
-  integrity sha512-dLtKIJW3y/PuFrPmcw6Mb8Nh+HwSqgVrK1rWgTARXhHfWvV822X2VRkx2meU/tg2+YQL6/nNgT6n5qWwIDHbwg==
+  version "2.45.0"
+  resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-2.45.0.tgz#8c1b158adbbd3e0ca3f7af19d459082245554378"
+  integrity sha512-Qwmcr+2mU3INeR6mVsQ8gO00vZpL8ZeTJLclX44C0dcs88jrSDgckPqbG+qkVX+m2L/aOPnF0lYgPdOiOiLt5w==
   dependencies:
     del "^3.0.0"
     extract-zip "^1.6.7"
@@ -1766,6 +1818,15 @@ cli-width@^2.0.0:
   resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
   integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
 
+cliui@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
+  integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
+  dependencies:
+    string-width "^2.1.1"
+    strip-ansi "^4.0.0"
+    wrap-ansi "^2.0.0"
+
 clone-deep@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
@@ -1812,16 +1873,11 @@ color-convert@^1.3.0, color-convert@^1.9.0:
   dependencies:
     color-name "1.1.3"
 
-color-name@1.1.3:
+color-name@1.1.3, color-name@^1.0.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@^1.0.0:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
 color-string@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
@@ -1876,7 +1932,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@2.17.x:
+commander@2.17.x, commander@~2.17.1:
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
   integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
@@ -1888,7 +1944,7 @@ commander@2.9.0:
   dependencies:
     graceful-readlink ">= 1.0.0"
 
-commander@^2.19.0, commander@~2.19.0:
+commander@^2.19.0:
   version "2.19.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
   integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
@@ -2016,10 +2072,10 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
-core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5:
-  version "2.6.5"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
-  integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==
+core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.2.tgz#267988d7268323b349e20b4588211655f0e83944"
+  integrity sha512-NdBPF/RVwPW6jr0NCILuyN9RiqLo2b1mddWHkUL+VnvcB7dzlnBJ1bXYntjpTGOgkZiiLWj2JxmOr7eGE3qK6g==
 
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
@@ -2071,9 +2127,9 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     sha.js "^2.4.8"
 
 cropperjs@^1.4.3:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.1.tgz#3bf93c6e89bf46d18cccc27160cd7d9b936cac7a"
-  integrity sha512-b6+gzlkhxEUswBNVwYct6fz+sJYdbmW7F6dAXwNkjTVfZcPcXBYADnVzvxNYcSOPkh/ytzmTRktbNkg5/Uw/PQ==
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.4.3.tgz#dc44d6c9e73269e7f96894c726ab91e8913f9e90"
+  integrity sha512-fsUjHuS9mvKVh2aXVRgNcUDlFplW+v4eEB6sOHVI9kMV4G3GViSD4p1qvNLg1ko4ZhOnF0L8/9uXcY4s2bFQPg==
 
 cross-spawn@^4.0.2:
   version "4.0.2"
@@ -2083,7 +2139,7 @@ cross-spawn@^4.0.2:
     lru-cache "^4.0.1"
     which "^1.2.9"
 
-cross-spawn@^6.0.5:
+cross-spawn@^6.0.0, cross-spawn@^6.0.5:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
   integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -2156,9 +2212,9 @@ css-selector-tokenizer@^0.7.0:
     regexpu-core "^1.0.0"
 
 css-what@2.1:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
-  integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d"
+  integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==
 
 cssesc@^0.1.0:
   version "0.1.0"
@@ -2235,12 +2291,10 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
-data-uri-to-buffer@2:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.0.tgz#0ba23671727349828c32cfafddea411908d13d23"
-  integrity sha512-YbKCNLPPP4inc0E5If4OaalBc7gpaM2MRv77Pv2VThVComLKfbGYtJcdDCViDyp1Wd4SebhHLz94vp91zbK6bw==
-  dependencies:
-    "@types/node" "^8.0.7"
+data-uri-to-buffer@1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835"
+  integrity sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==
 
 date-format@^1.2.0:
   version "1.2.0"
@@ -2286,13 +2340,6 @@ debug@2.6.8:
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.0.1, debug@^4.1.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
-  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
-  dependencies:
-    ms "^2.1.1"
-
 debug@4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87"
@@ -2300,21 +2347,28 @@ debug@4.1.0:
   dependencies:
     ms "^2.1.1"
 
-debug@^3.1.0, debug@^3.2.6:
+debug@=3.1.0, debug@~3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+  dependencies:
+    ms "2.0.0"
+
+debug@^3.1.0:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
   dependencies:
     ms "^2.1.1"
 
-debug@~3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
-  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+debug@^4.0.1, debug@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
   dependencies:
-    ms "2.0.0"
+    ms "^2.1.1"
 
-decamelize@^1.1.2:
+decamelize@^1.1.2, decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -2465,6 +2519,11 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
+dijkstrajs@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b"
+  integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs=
+
 doctrine@1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -2480,7 +2539,7 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
-dom-converter@^0.2:
+dom-converter@~0.2:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
   integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
@@ -2503,23 +2562,35 @@ dom-serialize@^2.2.0:
     void-elements "^2.0.0"
 
 dom-serializer@0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
-  integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
+  integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=
   dependencies:
-    domelementtype "^1.3.0"
-    entities "^1.1.1"
+    domelementtype "~1.1.1"
+    entities "~1.1.1"
 
 domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
   integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
 
-domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
+domelementtype@1, domelementtype@^1.3.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
   integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
 
+domelementtype@~1.1.1:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
+  integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=
+
+domhandler@2.1:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594"
+  integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=
+  dependencies:
+    domelementtype "1"
+
 domhandler@^2.3.0:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
@@ -2527,6 +2598,13 @@ domhandler@^2.3.0:
   dependencies:
     domelementtype "1"
 
+domutils@1.1:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
+  integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=
+  dependencies:
+    domelementtype "1"
+
 domutils@1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
@@ -2572,9 +2650,9 @@ ejs@2.5.7:
   integrity sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=
 
 electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47:
-  version "1.3.119"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.119.tgz#9a7770da667252aeb81f667853f67c2b26e00197"
-  integrity sha512-3mtqcAWa4HgG+Djh/oNXlPH0cOH6MmtwxN1nHSaReb9P0Vn51qYPqYwLeoSuAX9loU1wrOBhFbiX3CkeIxPfgg==
+  version "1.3.100"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13"
+  integrity sha512-cEUzis2g/RatrVf8x26L8lK5VEls1AGnLHk6msluBUg/NTB4wcXzExTsGscFq+Vs4WBBU2zbLLySvD4C0C3hwg==
 
 elliptic@^6.0.0:
   version "6.4.1"
@@ -2665,7 +2743,7 @@ ent@~2.2.0:
   resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
   integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0=
 
-entities@^1.1.1:
+entities@^1.1.1, entities@~1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
   integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
@@ -2684,7 +2762,7 @@ error-ex@^1.2.0:
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.5.1:
+es-abstract@^1.5.1, es-abstract@^1.7.0:
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
   integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
@@ -2728,9 +2806,9 @@ escodegen@1.8.x:
     source-map "~0.2.0"
 
 escodegen@1.x.x, escodegen@^1.6.1:
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510"
-  integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589"
+  integrity sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==
   dependencies:
     esprima "^3.1.3"
     estraverse "^4.2.0"
@@ -2773,10 +2851,10 @@ eslint-loader@^2.1.0:
     object-hash "^1.1.4"
     rimraf "^2.6.1"
 
-eslint-module-utils@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49"
-  integrity sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==
+eslint-module-utils@^2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a"
+  integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==
   dependencies:
     debug "^2.6.8"
     pkg-dir "^2.0.0"
@@ -2790,20 +2868,21 @@ eslint-plugin-es@^1.3.1:
     regexpp "^2.0.1"
 
 eslint-plugin-import@^2.13.0:
-  version "2.16.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f"
-  integrity sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==
+  version "2.17.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz#d227d5c6dc67eca71eb590d2bb62fb38d86e9fcb"
+  integrity sha512-m+cSVxM7oLsIpmwNn2WXTJoReOF9f/CtLMo7qOVmKd1KntBy0hEcuNZ3erTmWjx+DxRO0Zcrm5KwAvI9wHcV5g==
   dependencies:
+    array-includes "^3.0.3"
     contains-path "^0.1.0"
     debug "^2.6.9"
     doctrine "1.5.0"
     eslint-import-resolver-node "^0.3.2"
-    eslint-module-utils "^2.3.0"
+    eslint-module-utils "^2.4.0"
     has "^1.0.3"
     lodash "^4.17.11"
     minimatch "^3.0.4"
     read-pkg-up "^2.0.0"
-    resolve "^1.9.0"
+    resolve "^1.10.0"
 
 eslint-plugin-node@^7.0.0:
   version "7.0.1"
@@ -2984,6 +3063,19 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
     md5.js "^1.3.4"
     safe-buffer "^5.1.1"
 
+execa@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
+  integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
+  dependencies:
+    cross-spawn "^6.0.0"
+    get-stream "^4.0.0"
+    is-stream "^1.1.0"
+    npm-run-path "^2.0.0"
+    p-finally "^1.0.0"
+    signal-exit "^3.0.0"
+    strip-eof "^1.0.0"
+
 exit-hook@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
@@ -3129,16 +3221,11 @@ extract-zip@^1.6.7:
     mkdirp "0.5.1"
     yauzl "2.4.1"
 
-extsprintf@1.3.0:
+extsprintf@1.3.0, extsprintf@^1.2.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
   integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
 
-extsprintf@^1.2.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
-  integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
-
 fast-deep-equal@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
@@ -3327,11 +3414,11 @@ flush-write-stream@^1.0.0:
     readable-stream "^2.3.6"
 
 follow-redirects@^1.0.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
-  integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb"
+  integrity sha512-t2JCjbzxQpWvbhts3l6SH1DKzSrx8a+SsaVf4h6bG4kOXUuPYS/kg2Lr4gQSb7eemaHqJkOThF1BGyjlUkO1GQ==
   dependencies:
-    debug "^3.2.6"
+    debug "=3.1.0"
 
 for-in@^1.0.1, for-in@^1.0.2:
   version "1.0.2"
@@ -3414,12 +3501,12 @@ fs.realpath@^1.0.0:
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
 fsevents@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4"
-  integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==
+  version "1.2.9"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
+  integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
   dependencies:
-    nan "^2.9.2"
-    node-pre-gyp "^0.10.0"
+    nan "^2.12.1"
+    node-pre-gyp "^0.12.0"
 
 ftp@~0.3.10:
   version "0.3.10"
@@ -3453,22 +3540,34 @@ gauge@~2.7.3:
     strip-ansi "^3.0.1"
     wide-align "^1.1.0"
 
+get-caller-file@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
+  integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
+
 get-stdin@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
   integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
 
+get-stream@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+  integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+  dependencies:
+    pump "^3.0.0"
+
 get-uri@2:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.3.tgz#fa13352269781d75162c6fc813c9e905323fbab5"
-  integrity sha512-x5j6Ks7FOgLD/GlvjKwgu7wdmMR55iuRHhn8hj/+gA+eSbxQvZ+AEomq+3MgVEZj1vpi738QahGbCCSIDtXtkw==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.2.tgz#5c795e71326f6ca1286f2fc82575cd2bab2af578"
+  integrity sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==
   dependencies:
-    data-uri-to-buffer "2"
-    debug "4"
-    extend "~3.0.2"
+    data-uri-to-buffer "1"
+    debug "2"
+    extend "3"
     file-uri-to-path "1"
     ftp "~0.3.10"
-    readable-stream "3"
+    readable-stream "2"
 
 get-value@^2.0.3, get-value@^2.0.6:
   version "2.0.6"
@@ -3540,7 +3639,7 @@ glob@^5.0.15:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
+glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3:
   version "7.1.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
   integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
@@ -3552,10 +3651,22 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
+glob@^7.1.2:
+  version "7.1.4"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
+  integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
 globals@^11.7.0:
-  version "11.11.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e"
-  integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==
+  version "11.12.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+  integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
 globals@^9.18.0:
   version "9.18.0"
@@ -3589,11 +3700,11 @@ growl@1.9.2:
   integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=
 
 handlebars@^4.0.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.1.tgz#6e4e41c18ebe7719ae4d38e5aca3d32fa3dd23d3"
-  integrity sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==
+  version "4.0.12"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
+  integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==
   dependencies:
-    neo-async "^2.6.0"
+    async "^2.5.0"
     optimist "^0.6.1"
     source-map "^0.6.1"
   optionalDependencies:
@@ -3783,17 +3894,27 @@ html-webpack-plugin@^3.0.0:
     toposort "^1.0.0"
     util.promisify "1.0.0"
 
-htmlparser2@^3.10.0, htmlparser2@^3.3.0:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
-  integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
+htmlparser2@^3.10.0:
+  version "3.10.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
+  integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==
   dependencies:
-    domelementtype "^1.3.1"
+    domelementtype "^1.3.0"
     domhandler "^2.3.0"
     domutils "^1.5.1"
     entities "^1.1.1"
     inherits "^2.0.1"
-    readable-stream "^3.1.1"
+    readable-stream "^3.0.6"
+
+htmlparser2@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
+  integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=
+  dependencies:
+    domelementtype "1"
+    domhandler "2.1"
+    domutils "1.1"
+    readable-stream "1.0"
 
 http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
   version "1.6.3"
@@ -3856,14 +3977,14 @@ https-proxy-agent@1:
     debug "2"
     extend "3"
 
-iconv-lite@0.4.23:
+iconv-lite@0.4.23, iconv-lite@^0.4.4:
   version "0.4.23"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
   integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-iconv-lite@^0.4.24, iconv-lite@^0.4.4:
+iconv-lite@^0.4.24:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -3970,9 +4091,9 @@ inject-loader@^2.0.1:
     loader-utils "^0.2.3"
 
 inquirer@^6.2.2:
-  version "6.2.2"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406"
-  integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==
+  version "6.3.1"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7"
+  integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==
   dependencies:
     ansi-escapes "^3.2.0"
     chalk "^2.4.2"
@@ -3985,7 +4106,7 @@ inquirer@^6.2.2:
     run-async "^2.2.0"
     rxjs "^6.4.0"
     string-width "^2.1.0"
-    strip-ansi "^5.0.0"
+    strip-ansi "^5.1.0"
     through "^2.3.6"
 
 interpret@^1.0.0:
@@ -4000,6 +4121,11 @@ invariant@^2.2.2:
   dependencies:
     loose-envify "^1.0.0"
 
+invert-kv@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
+  integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
+
 ip-regex@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -4056,6 +4182,13 @@ is-buffer@^1.1.5:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
+is-builtin-module@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+  integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
+  dependencies:
+    builtin-modules "^1.0.0"
+
 is-callable@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
@@ -4171,9 +4304,9 @@ is-glob@^3.1.0:
     is-extglob "^2.1.0"
 
 is-glob@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
-  integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
   dependencies:
     is-extglob "^2.1.1"
 
@@ -4254,6 +4387,11 @@ is-regex@^1.0.4:
   dependencies:
     has "^1.0.1"
 
+is-stream@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+  integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+
 is-svg@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9"
@@ -4288,6 +4426,11 @@ is-windows@^1.0.2:
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
   integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
 
+is-wsl@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
+  integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
+
 is2@2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is2/-/is2-2.0.1.tgz#8ac355644840921ce435d94f05d3a94634d3481a"
@@ -4312,6 +4455,11 @@ isarray@2.0.1:
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
   integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
 
+isarray@^2.0.1:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.4.tgz#38e7bcbb0f3ba1b7933c86ba1894ddfc3781bbb7"
+  integrity sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==
+
 isbinaryfile@^3.0.0:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80"
@@ -4389,24 +4537,24 @@ istanbul@0.4.5, istanbul@^0.4.0:
     wordwrap "^1.0.0"
 
 js-base64@^2.1.9:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
-  integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
-
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
-  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e"
+  integrity sha512-wlEBIZ5LP8usDylWbDNhKPEFVFdI5hCHpnVoT/Ysvoi/PRhJENm/Rlh9TvjYB38HFfKZN7OzEbRjmjvLkFw11g==
 
-js-tokens@^3.0.2:
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
 
+js-tokens@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
 js-yaml@3.x, js-yaml@^3.4.3:
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e"
-  integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==
+  version "3.12.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
+  integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==
   dependencies:
     argparse "^1.0.7"
     esprima "^4.0.0"
@@ -4618,6 +4766,13 @@ kind-of@^6.0.0, kind-of@^6.0.2:
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
   integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
 
+lcid@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
+  integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==
+  dependencies:
+    invert-kv "^2.0.0"
+
 levn@^0.3.0, levn@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -5099,6 +5254,13 @@ mamacro@^0.0.3:
   resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
   integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==
 
+map-age-cleaner@^0.1.1:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
+  integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
+  dependencies:
+    p-defer "^1.0.0"
+
 map-cache@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@@ -5122,9 +5284,9 @@ math-expression-evaluator@^1.2.14:
   integrity sha1-3oGf282E3M2PrlnGrreWFbnSZqw=
 
 math-random@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
-  integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
+  integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w=
 
 md5.js@^1.3.4:
   version "1.3.5"
@@ -5140,6 +5302,15 @@ media-typer@0.3.0:
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
 
+mem@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-4.1.0.tgz#aeb9be2d21f47e78af29e4ac5978e8afa2ca5b8a"
+  integrity sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==
+  dependencies:
+    map-age-cleaner "^0.1.1"
+    mimic-fn "^1.0.0"
+    p-is-promise "^2.0.0"
+
 memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@@ -5220,32 +5391,39 @@ miller-rabin@^4.0.0:
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-mime-db@~1.38.0:
-  version "1.38.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad"
-  integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==
+mime-db@1.40.0:
+  version "1.40.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
+  integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
+
+mime-db@~1.37.0:
+  version "1.37.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
+  integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
 
 mime-types@^2.1.12, mime-types@~2.1.18, mime-types@~2.1.19:
-  version "2.1.22"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd"
-  integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==
+  version "2.1.21"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
+  integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
   dependencies:
-    mime-db "~1.38.0"
+    mime-db "~1.37.0"
+
+mime-types@~2.1.24:
+  version "2.1.24"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
+  integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
+  dependencies:
+    mime-db "1.40.0"
 
 mime@1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
   integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
 
-mime@^2.0.3:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.1.tgz#19eb7357bebbda37df585b14038347721558c715"
-  integrity sha512-VRUfmQO0rCd3hKwBymAn3kxYzBHr3I/wdVMywgG3HhXOwrCQgN84ZagpdTm2tZ4TNtwsSmyJWYO88mb5XvzGqQ==
-
-mime@^2.3.1:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6"
-  integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==
+mime@^2.0.3, mime@^2.3.1, mime@^2.4.2:
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.3.tgz#229687331e86f68924e6cb59e1cdd937f18275fe"
+  integrity sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==
 
 mimic-fn@^1.0.0:
   version "1.2.0"
@@ -5285,7 +5463,7 @@ minimatch@3.0.3:
   dependencies:
     brace-expansion "^1.0.0"
 
-minimist@0.0.8:
+minimist@0.0.8, minimist@~0.0.1:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
   integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
@@ -5295,11 +5473,6 @@ minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
   integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
 
-minimist@~0.0.1:
-  version "0.0.10"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
-  integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
-
 minipass@^2.2.1, minipass@^2.3.4:
   version "2.3.5"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
@@ -5418,10 +5591,10 @@ mute-stream@0.0.7:
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
   integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
 
-nan@^2.9.2:
-  version "2.13.1"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.1.tgz#a15bee3790bde247e8f38f1d446edcdaeb05f2dd"
-  integrity sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==
+nan@^2.12.1:
+  version "2.14.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
+  integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
 
 nanomatch@^1.2.9:
   version "1.2.13"
@@ -5464,10 +5637,15 @@ negotiator@0.6.1:
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
   integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
 
-neo-async@^2.5.0, neo-async@^2.6.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
-  integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
+negotiator@0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
+  integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
+
+neo-async@^2.5.0:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+  integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
 
 netmask@~1.0.4:
   version "1.0.6"
@@ -5531,10 +5709,10 @@ node-libs-browser@^2.0.0:
     util "^0.11.0"
     vm-browserify "0.0.4"
 
-node-pre-gyp@^0.10.0:
-  version "0.10.3"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"
-  integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==
+node-pre-gyp@^0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
+  integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==
   dependencies:
     detect-libc "^1.0.2"
     mkdirp "^0.5.1"
@@ -5571,12 +5749,12 @@ nopt@^4.0.1:
     osenv "^0.1.4"
 
 normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
-  integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
+  integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
   dependencies:
     hosted-git-info "^2.1.4"
-    resolve "^1.10.0"
+    is-builtin-module "^1.0.0"
     semver "2 || 3 || 4 || 5"
     validate-npm-package-license "^3.0.1"
 
@@ -5608,18 +5786,25 @@ normalize-url@^1.4.0:
     sort-keys "^1.0.0"
 
 npm-bundled@^1.0.1:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
-  integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979"
+  integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==
 
 npm-packlist@^1.1.6:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc"
-  integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.2.0.tgz#55a60e793e272f00862c7089274439a4cc31fc7f"
+  integrity sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==
   dependencies:
     ignore-walk "^3.0.1"
     npm-bundled "^1.0.1"
 
+npm-run-path@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+  integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
+  dependencies:
+    path-key "^2.0.0"
+
 npmlog@^4.0.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
@@ -5677,9 +5862,9 @@ object-hash@^1.1.4:
   integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
 
 object-keys@^1.0.12:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032"
-  integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
 
 object-path@^0.11.3:
   version "0.11.4"
@@ -5790,7 +5975,16 @@ os-homedir@^1.0.0, os-homedir@^1.0.1:
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
 
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
+os-locale@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
+  integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
+  dependencies:
+    execa "^1.0.0"
+    lcid "^2.0.0"
+    mem "^4.0.0"
+
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
@@ -5803,6 +5997,21 @@ osenv@^0.1.4:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
+p-defer@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
+  integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
+
+p-finally@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+  integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+
+p-is-promise@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.0.0.tgz#7554e3d572109a87e1f3f53f6a7d85d1b194f4c5"
+  integrity sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==
+
 p-limit@^1.1.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@@ -5811,9 +6020,9 @@ p-limit@^1.1.0:
     p-try "^1.0.0"
 
 p-limit@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
-  integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68"
+  integrity sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==
   dependencies:
     p-try "^2.0.0"
 
@@ -5842,9 +6051,9 @@ p-try@^1.0.0:
   integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
 
 p-try@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.1.0.tgz#c1a0f1030e97de018bb2c718929d2af59463e505"
-  integrity sha512-H2RyIJ7+A3rjkwKC2l5GGtU4H1vkxKCAGsWasNVd0Set+6i4znxbWy6/j16YDPJDWxhsgZiKAstMEP8wCdSpjA==
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
+  integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
 
 pac-proxy-agent@1:
   version "1.1.0"
@@ -5985,7 +6194,7 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2:
   resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
   integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
 
-path-key@^2.0.1:
+path-key@^2.0.0, path-key@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
   integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
@@ -6045,9 +6254,9 @@ performance-now@^2.1.0:
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
 phoenix@^1.3.0:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.4.2.tgz#b71491dbf18613c17b3704c0fb2102d23c7b3c07"
-  integrity sha512-ykIhZbqdmgoUaME8ArXWHMuVvTiV6vXQktqPPM2n082bGGVFnIhjEVk/PCKYrRNhfmWTtTkci8VknG4qwksy+w==
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.4.0.tgz#9cec8dbd8cbc59ecd2147bc09ca8ceb56b860d75"
+  integrity sha512-+M7erjPRtrHM53Bc9sT/WSyNFOEhsAc48PBsex0R87hu0GhBRMNE2uAb8MT2gKtJmAYxv5Quaozh/PBuhO8tdQ==
 
 pify@^2.0.0:
   version "2.3.0"
@@ -6097,9 +6306,15 @@ pkg-dir@^3.0.0:
   dependencies:
     find-up "^3.0.0"
 
+pngjs@^3.3.0:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
+  integrity sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==
+
 popper.js@^1.14.7:
   version "1.14.7"
   resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e"
+  integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==
 
 portal-vue@^2.1.4:
   version "2.1.4"
@@ -6416,13 +6631,13 @@ postcss@^6.0.1, postcss@^6.0.8:
     supports-color "^5.4.0"
 
 postcss@^7.0.5:
-  version "7.0.14"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5"
-  integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==
+  version "7.0.8"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696"
+  integrity sha512-WudsIzuTKRw9IInRTPBgVXJ7DKR26HT09Rxp0g3w0Fqh3TUtYICcUmvC0xURj04o3vdcDtnjCAUCECg/p341iQ==
   dependencies:
     chalk "^2.4.2"
     source-map "^0.6.1"
-    supports-color "^6.1.0"
+    supports-color "^6.0.0"
 
 prelude-ls@~1.1.2:
   version "1.1.2"
@@ -6440,9 +6655,9 @@ preserve@^0.2.0:
   integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
 
 prettier@^1.16.0:
-  version "1.16.4"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
-  integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
+  version "1.17.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.1.tgz#ed64b4e93e370cb8a25b9ef7fef3e4fd1c0995db"
+  integrity sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==
 
 pretty-error@^2.0.2:
   version "2.1.1"
@@ -6566,21 +6781,27 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-q@1.4.1:
+q@1.4.1, q@^1.1.2:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
   integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=
 
-q@^1.1.2:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
-  integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
-
 qjobs@^1.1.4:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
   integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
 
+qrcode@^1.3.0:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.3.3.tgz#5ef50c0c890cffa1897f452070f0f094936993de"
+  integrity sha512-SH7V13AcJusH3GT8bMNOGz4w0L+LjcpNOU/NiOgtBhT/5DoWeZE6D5ntMJnJ84AMkoaM4kjJJoHoh9g++8lWFg==
+  dependencies:
+    can-promise "0.0.1"
+    dijkstrajs "^1.0.1"
+    isarray "^2.0.1"
+    pngjs "^3.3.0"
+    yargs "^12.0.5"
+
 qs@6.5.2, qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -6628,11 +6849,16 @@ randomfill@^1.0.3:
     randombytes "^2.0.5"
     safe-buffer "^5.1.0"
 
-range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
+range-parser@^1.2.0, range-parser@~1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
   integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
 
+range-parser@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
 raw-body@2, raw-body@2.3.3:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
@@ -6692,7 +6918,7 @@ read-pkg@^2.0.0:
     normalize-package-data "^2.3.2"
     path-type "^2.0.0"
 
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
+"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
   integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
@@ -6705,6 +6931,16 @@ read-pkg@^2.0.0:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
+readable-stream@1.0:
+  version "1.0.34"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+  integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
 readable-stream@1.1.x:
   version "1.1.14"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@@ -6715,10 +6951,10 @@ readable-stream@1.1.x:
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@3, readable-stream@^3.1.1:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d"
-  integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==
+readable-stream@^3.0.6:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
+  integrity sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==
   dependencies:
     inherits "^2.0.3"
     string_decoder "^1.1.1"
@@ -6774,10 +7010,10 @@ regenerator-runtime@^0.11.0:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
   integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
 
-regenerator-runtime@^0.13.2:
-  version "0.13.2"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
-  integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
+regenerator-runtime@^0.12.0:
+  version "0.12.1"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
+  integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
 
 regenerator-transform@^0.10.0:
   version "0.10.1"
@@ -6849,13 +7085,13 @@ remove-trailing-separator@^1.0.1:
   integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
 
 renderkid@^2.0.1:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149"
-  integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa"
+  integrity sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg==
   dependencies:
     css-select "^1.1.0"
-    dom-converter "^0.2"
-    htmlparser2 "^3.3.0"
+    dom-converter "~0.2"
+    htmlparser2 "~3.3.0"
     strip-ansi "^3.0.0"
     utila "^0.4.0"
 
@@ -6907,11 +7143,21 @@ request@^2.88.0:
     tunnel-agent "^0.6.0"
     uuid "^3.3.2"
 
+require-directory@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+  integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+
 require-from-string@^1.1.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
   integrity sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=
 
+require-main-filename@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+  integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
+
 require-package-name@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/require-package-name/-/require-package-name-2.0.1.tgz#c11e97276b65b8e2923f75dabf5fb2ef0c3841b9"
@@ -6932,15 +7178,15 @@ resolve-url@^0.2.1:
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
   integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
 
-resolve@1.1.x:
+resolve@1.1.x, resolve@^1.1.6:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
   integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
 
-resolve@^1.1.6, resolve@^1.10.0, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
-  integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
+resolve@^1.10.0, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1:
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
+  integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
   dependencies:
     path-parse "^1.0.6"
 
@@ -6966,9 +7212,9 @@ ret@~0.1.10:
   integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
 rfdc@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
-  integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
+  integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
 
 rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
   version "2.6.3"
@@ -7000,9 +7246,9 @@ run-queue@^1.0.0, run-queue@^1.0.3:
     aproba "^1.1.1"
 
 rxjs@^6.4.0:
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504"
-  integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
+  integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==
   dependencies:
     tslib "^1.9.0"
 
@@ -7046,7 +7292,7 @@ sanitize-html@^1.13.0:
 
 "sass-loader@git://github.com/webpack-contrib/sass-loader":
   version "7.1.0"
-  resolved "git://github.com/webpack-contrib/sass-loader#9162e45cfc291c57bd09172892ac254dd68a0c00"
+  resolved "git://github.com/webpack-contrib/sass-loader#e279f2a129eee0bd0b624b5acd498f23a81ee35e"
   dependencies:
     clone-deep "^4.0.1"
     loader-utils "^1.0.1"
@@ -7056,9 +7302,9 @@ sanitize-html@^1.13.0:
     semver "^5.5.0"
 
 sass@^1.17.3:
-  version "1.17.3"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.17.3.tgz#19f9164cf8653b9fca670a64e53285272c96d192"
-  integrity sha512-S4vJawbrNUxJUBiHLXPYUKZCoO6cvq3/3ZFBV66a+PafTxcDEFJB+FHLDFl0P+rUfha/703ajEXMuGTYhJESkQ==
+  version "1.20.1"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.20.1.tgz#737b901fe072336da540b6d00ec155e2267420da"
+  integrity sha512-BnCawee/L5kVG3B/5Jg6BFwASqUwFVE6fj2lnkVuSXDgQ7gMAhY9a2yPeqsKhJMCN+Wgx0r2mAW7XF/aTF5qtA==
   dependencies:
     chokidar "^2.0.0"
 
@@ -7115,10 +7361,10 @@ send@0.16.2:
     range-parser "~1.2.0"
     statuses "~1.4.0"
 
-serialize-javascript@^1.4.0:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879"
-  integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==
+serialize-javascript@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
+  integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
 
 serve-static@1.13.2:
   version "1.13.2"
@@ -7137,7 +7383,7 @@ serviceworker-webpack-plugin@^1.0.0:
   dependencies:
     minimatch "^3.0.4"
 
-set-blocking@~2.0.0:
+set-blocking@^2.0.0, set-blocking@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
@@ -7181,9 +7427,9 @@ sha.js@^2.4.0, sha.js@^2.4.8:
     safe-buffer "^5.0.1"
 
 shallow-clone@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.0.tgz#317b701facce5e742d4c04c64e1d52f957e22b28"
-  integrity sha512-Drg+nOI+ofeuslBf0nulyWLZhK1BZprqNvPJaiB4VvES+9gC6GG+qOVAfuO12zVSgxq9SKevcme7S3uDT6Be8w==
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+  integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
   dependencies:
     kind-of "^6.0.2"
 
@@ -7375,9 +7621,9 @@ source-map-support@^0.4.15:
     source-map "^0.5.6"
 
 source-map-support@~0.5.10:
-  version "0.5.11"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.11.tgz#efac2ce0800355d026326a0ca23e162aeac9a4e2"
-  integrity sha512-//sajEx/fGL3iw6fltKMdPvy8kL3kJ2O3iuYlRoT3k9Kb4BjOoZ+BZzaNHeuaruSt+Kf3Zk9tnfAQg9/AJqUVQ==
+  version "0.5.12"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
+  integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
   dependencies:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
@@ -7451,9 +7697,9 @@ srcset@^1.0.0:
     number-is-nan "^1.0.0"
 
 sshpk@^1.7.0:
-  version "1.16.1"
-  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
-  integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
+  version "1.16.0"
+  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de"
+  integrity sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==
   dependencies:
     asn1 "~0.2.3"
     assert-plus "^1.0.0"
@@ -7480,25 +7726,20 @@ static-extend@^0.1.1:
     define-property "^0.2.5"
     object-copy "^0.1.0"
 
-"statuses@>= 1.4.0 < 2":
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
-  integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
+"statuses@>= 1.4.0 < 2", statuses@~1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
+  integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
 
 statuses@~1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
   integrity sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=
 
-statuses@~1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
-  integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
-
 stream-browserify@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
-  integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
+  integrity sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=
   dependencies:
     inherits "~2.0.1"
     readable-stream "^2.0.2"
@@ -7551,7 +7792,7 @@ string-width@^1.0.1:
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
-"string-width@^1.0.2 || 2", string-width@^2.1.0:
+"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
   integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
@@ -7601,7 +7842,7 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@^5.0.0, strip-ansi@^5.1.0:
+strip-ansi@^5.1.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
   integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
@@ -7625,6 +7866,11 @@ strip-bom@^3.0.0:
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
   integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
 
+strip-eof@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+  integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
+
 strip-indent@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
@@ -7637,7 +7883,7 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
-supports-color@3.1.2:
+supports-color@3.1.2, supports-color@^3.1.0:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
   integrity sha1-cqJiiU2dQIuVbKBf83su2KbiotU=
@@ -7649,7 +7895,7 @@ supports-color@^2.0.0:
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
   integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
 
-supports-color@^3.1.0, supports-color@^3.2.3:
+supports-color@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
   integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=
@@ -7663,7 +7909,7 @@ supports-color@^5.3.0, supports-color@^5.4.0:
   dependencies:
     has-flag "^3.0.0"
 
-supports-color@^6.1.0:
+supports-color@^6.0.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
   integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
@@ -7684,9 +7930,9 @@ svgo@^0.7.0:
     whet.extend "~0.9.9"
 
 table@^5.2.3:
-  version "5.2.3"
-  resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2"
-  integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==
+  version "5.3.3"
+  resolved "https://registry.yarnpkg.com/table/-/table-5.3.3.tgz#eae560c90437331b74200e011487a33442bd28b4"
+  integrity sha512-3wUNCgdWX6PNpOe3amTTPWPuF6VGvgzjKCaO1snFj0z7Y3mUPWf5+zDtxUVGispJkDECPmR29wbzh6bVMOHbcw==
   dependencies:
     ajv "^6.9.1"
     lodash "^4.17.11"
@@ -7694,9 +7940,9 @@ table@^5.2.3:
     string-width "^3.0.0"
 
 tapable@^1.0.0, tapable@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e"
-  integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
+  integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
 
 tar@^4:
   version "4.4.8"
@@ -7720,20 +7966,21 @@ tcp-port-used@^1.0.1:
     is2 "2.0.1"
 
 terser-webpack-plugin@^1.1.0:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz#3f98bc902fac3e5d0de730869f50668561262ec8"
-  integrity sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.4.tgz#56f87540c28dd5265753431009388f473b5abba3"
+  integrity sha512-64IiILNQlACWZLzFlpzNaG0bpQ4ytaB7fwOsbpsdIV70AfLUmIGGeuKL0YV2WmtcrURjE2aOvHD4/lrFV3Rg+Q==
   dependencies:
-    cacache "^11.0.2"
+    cacache "^11.3.2"
     find-cache-dir "^2.0.0"
+    is-wsl "^1.1.0"
     schema-utils "^1.0.0"
-    serialize-javascript "^1.4.0"
+    serialize-javascript "^1.7.0"
     source-map "^0.6.1"
-    terser "^3.16.1"
-    webpack-sources "^1.1.0"
-    worker-farm "^1.5.2"
+    terser "^3.17.0"
+    webpack-sources "^1.3.0"
+    worker-farm "^1.7.0"
 
-terser@^3.16.1:
+terser@^3.17.0:
   version "3.17.0"
   resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
   integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==
@@ -7777,13 +8024,20 @@ timers-browserify@^2.0.4:
   dependencies:
     setimmediate "^1.0.4"
 
-tmp@0.0.33, tmp@0.0.x, tmp@^0.0.33:
+tmp@0.0.33, tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
   integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
   dependencies:
     os-tmpdir "~1.0.2"
 
+tmp@0.0.x:
+  version "0.0.31"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
+  integrity sha1-jzirlDjhcxXl29izZX6L+yd65Kc=
+  dependencies:
+    os-tmpdir "~1.0.1"
+
 to-array@0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
@@ -7909,20 +8163,12 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-uglify-js@3.4.x:
-  version "3.4.10"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
-  integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==
+uglify-js@3.4.x, uglify-js@^3.1.4:
+  version "3.4.9"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
+  integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
   dependencies:
-    commander "~2.19.0"
-    source-map "~0.6.1"
-
-uglify-js@^3.1.4:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.2.tgz#dc0c7ac2da0a4b7d15e84266818ff30e82529474"
-  integrity sha512-imog1WIsi9Yb56yRt5TfYVxGmnWs3WSGU73ieSOlMVFwhJCA9W8fqFFMMj4kgDqiS/80LGdsYnWL7O9UcjEBlg==
-  dependencies:
-    commander "~2.19.0"
+    commander "~2.17.1"
     source-map "~0.6.1"
 
 ultron@~1.1.0:
@@ -8077,8 +8323,9 @@ uuid@^3.3.2:
   integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
 
 v-click-outside@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.1.tgz#5af80b68a1c82eac89c597890434fa3994b42ed1"
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/v-click-outside/-/v-click-outside-2.1.3.tgz#b7297abe833a439dc0895e6418a494381e64b5e7"
+  integrity sha512-8d11/fN+nkSPeor87K8OtGc/lDbRwbUiFwdzxQEGidlXt6eko3gIgRM7ghgi4p/zohF3Ja9hAaydAajV3gnlPQ==
 
 validate-npm-package-license@^3.0.1:
   version "3.0.4"
@@ -8168,6 +8415,7 @@ vue-loader@^14.0.0:
 vue-popperjs@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/vue-popperjs/-/vue-popperjs-2.0.3.tgz#7c446d0ba7c63170ccb33a02669d0df4efc3d8cd"
+  integrity sha512-Gqxl569ZPNVQrEszGan3hTgzzrF8316Z0fThk32pKRUF2lqg9FV9p56mQEDJjc5FbQuyoBmWblm7ki5CTufDYA==
   dependencies:
     popper.js "^1.14.7"
 
@@ -8185,9 +8433,9 @@ vue-style-loader@^4.0.0, vue-style-loader@^4.0.1:
     loader-utils "^1.0.2"
 
 vue-template-compiler@^2.3.4:
-  version "2.6.10"
-  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
-  integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
+  version "2.5.21"
+  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
+  integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
   dependencies:
     de-indent "^1.0.2"
     he "^1.1.0"
@@ -8203,9 +8451,9 @@ vue-timeago@^3.1.2:
   integrity sha512-8V93SP2XXWLThysx8F1rSMyKuEevDll0sA5tg8AxtmgDTuKR7XKug8OVVdYoZXpynZqHK9u93LXPwCmVhVaJvQ==
 
 vue@^2.5.13:
-  version "2.6.10"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
-  integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
+  version "2.5.21"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
+  integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
 
 vuelidate@^0.7.4:
   version "0.7.4"
@@ -8213,9 +8461,9 @@ vuelidate@^0.7.4:
   integrity sha512-QHZWYOL325Zo+2K7VBNEJTZ496Kd8Z31p85aQJFldKudUUGBmgw4zu4ghl4CyqPwjRCmqZ9lDdx4FSdMnu4fGg==
 
 vuex@^3.0.1:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9"
-  integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg==
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
+  integrity sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w==
 
 watchpack@^1.5.0:
   version "1.6.0"
@@ -8226,24 +8474,14 @@ watchpack@^1.5.0:
     graceful-fs "^4.1.2"
     neo-async "^2.5.0"
 
-webpack-dev-middleware@^3.2.0:
-  version "3.6.2"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.2.tgz#f37a27ad7c09cd7dc67cd97655413abaa1f55942"
-  integrity sha512-A47I5SX60IkHrMmZUlB0ZKSWi29TZTcPz7cha1Z75yYOsgWh/1AcPmQEbC8ZIbU3A1ytSv1PMU0PyPz2Lmz2jg==
-  dependencies:
-    memory-fs "^0.4.1"
-    mime "^2.3.1"
-    range-parser "^1.0.3"
-    webpack-log "^2.0.0"
-
-webpack-dev-middleware@^3.6.0:
-  version "3.6.1"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.1.tgz#91f2531218a633a99189f7de36045a331a4b9cd4"
-  integrity sha512-XQmemun8QJexMEvNFbD2BIg4eSKrmSIMrTfnl2nql2Sc6OGAYFyb8rwuYrCjl/IiEYYuyTEiimMscu7EXji/Dw==
+webpack-dev-middleware@^3.2.0, webpack-dev-middleware@^3.6.0:
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz#ef751d25f4e9a5c8a35da600c5fda3582b5c6cff"
+  integrity sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==
   dependencies:
     memory-fs "^0.4.1"
-    mime "^2.3.1"
-    range-parser "^1.0.3"
+    mime "^2.4.2"
+    range-parser "^1.2.1"
     webpack-log "^2.0.0"
 
 webpack-hot-middleware@^2.12.2:
@@ -8283,9 +8521,9 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0:
     source-map "~0.6.1"
 
 webpack@^4.0.0:
-  version "4.29.6"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.6.tgz#66bf0ec8beee4d469f8b598d3988ff9d8d90e955"
-  integrity sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw==
+  version "4.32.1"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.32.1.tgz#afe0cc7dd2b196e5a58f8d1d385311cfbb5d68c0"
+  integrity sha512-R0S2tfWP2tZ8ZC2dwgnUVfa9LPvhGWJXjqfgIQ6jply+9ncBbt8IZ9p83uVeqsZ/s8zKA3XyepciWNHnSxxnHg==
   dependencies:
     "@webassemblyjs/ast" "1.8.5"
     "@webassemblyjs/helper-module-context" "1.8.5"
@@ -8322,6 +8560,11 @@ whet.extend@~0.9.9:
   resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
   integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=
 
+which-module@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+  integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
+
 which@^1.0.9, which@^1.1.1, which@^1.2.9:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@@ -8336,6 +8579,11 @@ wide-align@^1.1.0:
   dependencies:
     string-width "^1.0.2 || 2"
 
+window-or-global@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/window-or-global/-/window-or-global-1.0.1.tgz#dbe45ba2a291aabc56d62cf66c45b7fa322946de"
+  integrity sha1-2+RboqKRqrxW1iz2bEW3+jIpRt4=
+
 wordwrap@^1.0.0, wordwrap@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
@@ -8346,13 +8594,21 @@ wordwrap@~0.0.2:
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
   integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
 
-worker-farm@^1.5.2:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"
-  integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==
+worker-farm@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
+  integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==
   dependencies:
     errno "~0.1.7"
 
+wrap-ansi@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+  integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
+  dependencies:
+    string-width "^1.0.1"
+    strip-ansi "^3.0.1"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -8389,7 +8645,7 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
   integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
 
-y18n@^4.0.0:
+"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
   integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
@@ -8404,6 +8660,32 @@ yallist@^3.0.0, yallist@^3.0.2:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
   integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
 
+yargs-parser@^11.1.1:
+  version "11.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
+  integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
+yargs@^12.0.5:
+  version "12.0.5"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
+  integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
+  dependencies:
+    cliui "^4.0.0"
+    decamelize "^1.2.0"
+    find-up "^3.0.0"
+    get-caller-file "^1.0.1"
+    os-locale "^3.0.0"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^2.0.0"
+    which-module "^2.0.0"
+    y18n "^3.2.1 || ^4.0.0"
+    yargs-parser "^11.1.1"
+
 yauzl@2.4.1:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
-- 
cgit v1.2.3-70-g09d2

